Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/linker/Linker.Steps/SealerStep.cs148
-rw-r--r--src/linker/Linker/Driver.cs5
-rw-r--r--src/linker/Linker/LinkContext.cs7
-rw-r--r--test/Mono.Linker.Tests.Cases/Sealer/MethodsDevirtualization.cs140
-rw-r--r--test/Mono.Linker.Tests.Cases/Sealer/TypesCanBeSealed.cs96
-rw-r--r--test/Mono.Linker.Tests/TestCases/TestDatabase.cs5
-rw-r--r--test/Mono.Linker.Tests/TestCases/TestSuites.cs6
-rw-r--r--test/Mono.Linker.Tests/Tests/CodeOptimizationsSettingsTests.cs11
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