diff options
-rw-r--r-- | src/linker/Linker.Steps/SealerStep.cs | 148 | ||||
-rw-r--r-- | src/linker/Linker/Driver.cs | 5 | ||||
-rw-r--r-- | src/linker/Linker/LinkContext.cs | 7 | ||||
-rw-r--r-- | test/Mono.Linker.Tests.Cases/Sealer/MethodsDevirtualization.cs | 140 | ||||
-rw-r--r-- | test/Mono.Linker.Tests.Cases/Sealer/TypesCanBeSealed.cs | 96 | ||||
-rw-r--r-- | test/Mono.Linker.Tests/TestCases/TestDatabase.cs | 5 | ||||
-rw-r--r-- | test/Mono.Linker.Tests/TestCases/TestSuites.cs | 6 | ||||
-rw-r--r-- | test/Mono.Linker.Tests/Tests/CodeOptimizationsSettingsTests.cs | 11 |
8 files changed, 417 insertions, 1 deletions
diff --git a/src/linker/Linker.Steps/SealerStep.cs b/src/linker/Linker.Steps/SealerStep.cs new file mode 100644 index 000000000..bb409392a --- /dev/null +++ b/src/linker/Linker.Steps/SealerStep.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Collections.Generic; +using Mono.Cecil; + +namespace Mono.Linker.Steps +{ + public class SealerStep : BaseStep + { + HashSet<TypeDefinition> referencedBaseTypeCache; + + public SealerStep () + { + } + + protected override void ProcessAssembly (AssemblyDefinition assembly) + { + if (Annotations.GetAction (assembly) != AssemblyAction.Link) + return; + + if (!Context.Optimizations.IsEnabled (CodeOptimizations.Sealer, assembly)) + return; + + foreach (var type in assembly.MainModule.Types) + ProcessType (type); + } + + protected override void EndProcess () + { + referencedBaseTypeCache = null; + } + + bool IsSubclassed (TypeDefinition type) + { + if (referencedBaseTypeCache == null) { + referencedBaseTypeCache = new HashSet<TypeDefinition> (); + foreach (var a in Context.GetAssemblies ()) { + foreach (var s in a.MainModule.Types) { + var btd = s.BaseType?.Resolve (); + if (btd != null) + referencedBaseTypeCache.Add (btd); + + if (s.HasNestedTypes) { + foreach (var ns in s.NestedTypes) { + btd = s.BaseType?.Resolve (); + if (btd != null) + referencedBaseTypeCache.Add (btd); + } + } + } + } + } + + var bt = type.Resolve (); + return referencedBaseTypeCache.Contains (bt); + } + + void ProcessType (TypeDefinition type) + { + if (type.HasNestedTypes) { + foreach (var nt in type.NestedTypes) { + ProcessType (nt); + } + } + + // + // interface members are virtual (and we cannot change this) + // + if (type.IsInterface) + return; + + // + // the code does not include any subclass for this type + // + if (!type.IsAbstract && !type.IsSealed && !IsSubclassed (type)) + SealType (type); + + if (!type.HasMethods) + return; + + // process methods to see if we can seal or devirtualize them + foreach (var method in type.Methods) { + if (method.IsFinal || !method.IsVirtual || method.IsAbstract || method.IsRuntime) + continue; + + Debug.Assert (Annotations.IsMarked (method)); + if (!Annotations.IsMarked (method)) + continue; + + var overrides = Annotations.GetOverrides (method); + + // + // cannot de-virtualize nor seal methods if something overrides them + // + if (IsAnyMarked (overrides)) + continue; + + SealMethod (method); + + // subclasses might need this method to satisfy an interface requirement + // and requires dispatch/virtual support + if (!type.IsSealed) + continue; + + var bases = Annotations.GetBaseMethods (method); + // Devirtualize if a method is not override to existing marked methods + if (!IsAnyMarked (bases)) + method.IsVirtual = method.IsFinal = method.IsNewSlot = false; + } + } + + protected virtual void SealType (TypeDefinition type) + { + type.IsSealed = true; + } + + protected virtual void SealMethod (MethodDefinition method) + { + method.IsFinal = true; + } + + bool IsAnyMarked (List<OverrideInformation> list) + { + if (list == null) + return false; + + foreach (var m in list) { + if (Annotations.IsMarked (m.Override)) + return true; + } + return false; + } + + bool IsAnyMarked (List<MethodDefinition> list) + { + if (list == null) + return false; + foreach (var m in list) { + if (Annotations.IsMarked (m)) + return true; + } + return false; + } + } +} diff --git a/src/linker/Linker/Driver.cs b/src/linker/Linker/Driver.cs index 256bd2bbc..faf90259d 100644 --- a/src/linker/Linker/Driver.cs +++ b/src/linker/Linker/Driver.cs @@ -553,6 +553,7 @@ namespace Mono.Linker { p.AddStepBefore (typeof (MarkStep), new RemoveUnreachableBlocksStep ()); p.AddStepBefore (typeof (OutputStep), new ClearInitLocalsStep ()); + p.AddStepBefore (typeof (OutputStep), new SealerStep ()); // // Pipeline setup with all steps enabled @@ -754,6 +755,9 @@ namespace Mono.Linker { case "ipconstprop": optimization = CodeOptimizations.IPConstantPropagation; return true; + case "sealer": + optimization = CodeOptimizations.Sealer; + return true; } Console.WriteLine ($"Invalid optimization value '{text}'"); @@ -891,6 +895,7 @@ namespace Mono.Linker { Console.WriteLine (" unusedinterfaces: Removes interface types from declaration when not used"); Console.WriteLine (" --enable-opt NAME [ASM] Enable one of the additional optimizations globaly or for a specific assembly name"); Console.WriteLine (" clearinitlocals: Remove initlocals"); + Console.WriteLine (" sealer: Any method or type which does not have override is marked as sealed"); Console.WriteLine (" --exclude-feature NAME Any code which has a feature <name> in linked assemblies will be removed"); Console.WriteLine (" com: Support for COM Interop"); Console.WriteLine (" etw: Event Tracing for Windows"); diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 4d6231967..3dec3f767 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -495,7 +495,7 @@ namespace Mono.Linker { } if (!perAssembly.ContainsKey (assemblyContext)) { - perAssembly.Add (assemblyContext, ~optimizations); + perAssembly.Add (assemblyContext, 0); return; } @@ -535,5 +535,10 @@ namespace Mono.Linker { /// Option to do interprocedural constant propagation on return values /// </summary> IPConstantPropagation = 1 << 5, + + /// <summary> + /// Devirtualizes methods and seals types + /// </summary> + Sealer = 1 << 6 } } diff --git a/test/Mono.Linker.Tests.Cases/Sealer/MethodsDevirtualization.cs b/test/Mono.Linker.Tests.Cases/Sealer/MethodsDevirtualization.cs new file mode 100644 index 000000000..ecfae8485 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Sealer/MethodsDevirtualization.cs @@ -0,0 +1,140 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Sealer +{ + [SetupLinkerArgument ("--enable-opt", "sealer")] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + public class MethodsDevirtualization + { + public static void Main () + { + var s0 = new Data.BaseClassMethods (); + s0.A (); + s0.C (); + + var s1 = new Data.Sealable (); + s1.A (); + s1.B (); + s1.B2 (); + s1.C (); + s1.D (); + + var s2 = new Data.SealableAbstract (); + s2.A (); + + Data.IA s3 = new Data.Subclass (); + s3.A (); + } + } +} + +namespace Mono.Linker.Tests.Cases.Sealer.Data +{ + [Kept] + public class BaseClassMethods + { + [Kept] + public BaseClassMethods () + { + } + + [Kept] + [AddedPseudoAttributeAttribute ((uint)MethodAttributes.Final)] + public virtual bool A () => true; + [Kept] + public virtual bool B () => false; + [Kept] + [AddedPseudoAttributeAttribute ((uint)MethodAttributes.Final)] + public virtual bool C () => false; + [Kept] + public virtual bool D () => false; + } + + [Kept] + [KeptBaseType (typeof (BaseClassMethods))] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + public class Sealable : BaseClassMethods + { + [Kept] + public Sealable () + { + } + + [Kept] + [AddedPseudoAttributeAttribute ((uint)MethodAttributes.Final)] + public override bool B () => true; + + [Kept] + [RemovedPseudoAttributeAttribute ((uint)MethodAttributes.Virtual)] + [RemovedPseudoAttributeAttribute ((uint)MethodAttributes.VtableLayoutMask)] + public virtual bool B2 () => false; + + [Kept] + public new bool C () => false; + + [Kept] + public sealed override bool D () => false; + } + + [Kept] + public abstract class BaseAbstractClassMethods + { + [Kept] + public BaseAbstractClassMethods () + { + } + + [Kept] + public abstract bool A (); + } + + [Kept] + [KeptBaseType (typeof (BaseAbstractClassMethods))] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + public class SealableAbstract : BaseAbstractClassMethods + { + [Kept] + public SealableAbstract () + { + } + + [Kept] + [AddedPseudoAttributeAttribute ((uint)MethodAttributes.Final)] + public override bool A () => true; + } + + [Kept] + interface IA + { + [Kept] + bool A (); + } + + [Kept] + public class BaseA + { + [Kept] + public BaseA () + { + } + + [Kept] + public bool A () => true; + } + + [Kept] + [KeptBaseType (typeof (BaseA))] + [KeptInterface (typeof (IA))] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + public class Subclass : BaseA, IA + { + [Kept] + public Subclass () + { + } + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Sealer/TypesCanBeSealed.cs b/test/Mono.Linker.Tests.Cases/Sealer/TypesCanBeSealed.cs new file mode 100644 index 000000000..93110ab9f --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Sealer/TypesCanBeSealed.cs @@ -0,0 +1,96 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Sealer +{ + [SetupLinkerArgument ("--enable-opt", "sealer")] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + public class TypesCanBeSealed + { + public static void Main () + { + Type t; + t = typeof (SimpleNestedClass); + t = typeof (SimpleNestedIface); + + t = typeof (Data.SimpleClass); + t = typeof (Data.AlreadySealed); + t = typeof (Data.Derived); + t = typeof (Data.DerivedWithNested.Nested); + t = typeof (Data.DerivedWithNested); + t = typeof (Data.BaseWithUnusedDerivedClass); + } + + [Kept] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + class SimpleNestedClass + { + } + + [Kept] + interface SimpleNestedIface + { + } + } +} + +namespace Mono.Linker.Tests.Cases.Sealer.Data +{ + [Kept] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + class SimpleClass + { + } + + [Kept] + static class AlreadySealed + { + } + + [Kept] + class Base + { + } + + [Kept] + [KeptBaseType (typeof (Base))] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + class Derived : Base + { + } + + [Kept] + class BaseWithNested + { + [Kept] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + internal class Nested + { + } + } + + [Kept] + [KeptBaseType (typeof (BaseWithNested))] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + class DerivedWithNested : BaseWithNested + { + } + + class UnusedClass + { + } + + [Kept] + [AddedPseudoAttributeAttribute ((uint)TypeAttributes.Sealed)] + class BaseWithUnusedDerivedClass + { + + } + + class UnusedDerivedClass: BaseWithUnusedDerivedClass + { + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs index fe40ae4d5..5e603ba19 100644 --- a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs +++ b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs @@ -147,6 +147,11 @@ namespace Mono.Linker.Tests.TestCases return NUnitCasesBySuiteName ("UnreachableBlock"); } + public static IEnumerable<TestCaseData> SealerTests () + { + return NUnitCasesBySuiteName ("Sealer"); + } + public static IEnumerable<TestCaseData> SubstitutionsTests () { return NUnitCasesBySuiteName ("Substitutions"); diff --git a/test/Mono.Linker.Tests/TestCases/TestSuites.cs b/test/Mono.Linker.Tests/TestCases/TestSuites.cs index 2fda3c3e9..60f2ff9ed 100644 --- a/test/Mono.Linker.Tests/TestCases/TestSuites.cs +++ b/test/Mono.Linker.Tests/TestCases/TestSuites.cs @@ -187,6 +187,12 @@ namespace Mono.Linker.Tests.TestCases Run (testCase); } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.SealerTests))] + public void SealerTests (TestCase testCase) + { + Run (testCase); + } + protected virtual void Run (TestCase testCase) { var runner = new TestRunner (new ObjectFactory ()); diff --git a/test/Mono.Linker.Tests/Tests/CodeOptimizationsSettingsTests.cs b/test/Mono.Linker.Tests/Tests/CodeOptimizationsSettingsTests.cs index e7b8ff2f6..a05fb5393 100644 --- a/test/Mono.Linker.Tests/Tests/CodeOptimizationsSettingsTests.cs +++ b/test/Mono.Linker.Tests/Tests/CodeOptimizationsSettingsTests.cs @@ -40,5 +40,16 @@ namespace Mono.Linker.Tests Assert.False (cos.IsEnabled (CodeOptimizations.ClearInitLocals, "any")); Assert.That (cos.IsEnabled (CodeOptimizations.BeforeFieldInit, "testasm.dll")); } + + [Test] + public void OnlyOneOptIsDisabled () + { + CodeOptimizationsSettings cos = new CodeOptimizationsSettings (CodeOptimizations.OverrideRemoval); + cos.Disable (CodeOptimizations.BeforeFieldInit, "testasm.dll"); + + Assert.False (cos.IsEnabled (CodeOptimizations.BeforeFieldInit, "testasm.dll")); + Assert.False (cos.IsEnabled (CodeOptimizations.Sealer, "testasm.dll")); + Assert.False (cos.IsEnabled (CodeOptimizations.UnreachableBodies, "testasm.dll")); + } } }
\ No newline at end of file |