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:
authorVitek Karas <10670590+vitek-karas@users.noreply.github.com>2022-11-02 00:17:26 +0300
committerGitHub <noreply@github.com>2022-11-02 00:17:26 +0300
commite502e7255030dde029e984e397924338485d1079 (patch)
treecfac1687d5af8ab75b30c584d064a449ed89acf2
parentaea1d9fb47b24d5e7cac1db048babbed1df1067f (diff)
Fix branch removal in compiler generated code (#3088)
Changes to processing of compiler generated methods lead to a state where we don't call constant prop and branch removal in all cases before we mark instructions of the method. This can lead to overmarking This change fixes this by making sure that the branch removal executes on the method in all cases before we mark instructions of the method. The change guarantees that all accesses to Body are after the constant prop/branch removal happened on the method. This does have one possibly negative impact: the issue described in https://github.com/dotnet/linker/issues/2937 is now consistent and happens always. Added tests. Note that there's still a whole in analysis of compiler generated code around state machines, see https://github.com/dotnet/linker/issues/3087 Basically if there's a local function which is going to be removed due to branch removal and if the body of that method contains code which produces a warning due to generic parameter validation, such warning will always be generated even though it's "dead" code and even if it's suppressed via RUC or similar. In such case the analysis can't figure out to which method the local function belongs (since the call site has been removed).
-rw-r--r--src/linker/BannedSymbols.txt4
-rw-r--r--src/linker/Linker.Dataflow/CompilerGeneratedState.cs4
-rw-r--r--src/linker/Linker.Dataflow/FlowAnnotations.cs2
-rw-r--r--src/linker/Linker.Dataflow/InterproceduralState.cs39
-rw-r--r--src/linker/Linker.Dataflow/MethodBodyScanner.cs49
-rw-r--r--src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs14
-rw-r--r--src/linker/Linker.Dataflow/ScannerExtensions.cs6
-rw-r--r--src/linker/Linker.Steps/AddBypassNGenStep.cs2
-rw-r--r--src/linker/Linker.Steps/CodeRewriterStep.cs13
-rw-r--r--src/linker/Linker.Steps/MarkStep.cs106
-rw-r--r--src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs58
-rw-r--r--src/linker/Linker/LinkContext.cs24
-rw-r--r--src/linker/Linker/MethodBodyScanner.cs21
-rw-r--r--src/linker/Linker/MethodIL.cs31
-rw-r--r--src/linker/Linker/TypeReferenceWalker.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedCodeInPreservedAssemblyWithWarning.cs14
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/CompilerGeneratedCodeSubstitutions.cs245
17 files changed, 481 insertions, 153 deletions
diff --git a/src/linker/BannedSymbols.txt b/src/linker/BannedSymbols.txt
index de67d0d93..c92f6876a 100644
--- a/src/linker/BannedSymbols.txt
+++ b/src/linker/BannedSymbols.txt
@@ -6,3 +6,7 @@ M:Mono.Cecil.MethodReference.Resolve();Use LinkContext.Resolve and LinkContext.T
M:Mono.Cecil.ExportedType.Resolve();Use LinkContext.Resolve and LinkContext.TryResolve helpers instead
P:Mono.Collections.Generic.Collection`1{Mono.Cecil.ParameterDefinition}.Item(System.Int32); use x
P:Mono.Cecil.ParameterDefinitionCollection.Item(System.Int32); use x
+P:Mono.Cecil.Cil.MethodBody.Instructions;Use LinkContext.MethodBodyInstructionProvider instead
+P:Mono.Cecil.Cil.MethodBody.ExceptionHandlers;Use LinkContext.MethodBodyInstructionProvider instead
+P:Mono.Cecil.Cil.MethodBody.Variables;Use LinkContext.MethodBodyInstructionProvider instead
+M:Mono.Linker.Steps.ILProvider/MethodIL.Create;Use ILProvider GetMethodIL instead
diff --git a/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/linker/Linker.Dataflow/CompilerGeneratedState.cs
index 3033cef70..36950fb9c 100644
--- a/src/linker/Linker.Dataflow/CompilerGeneratedState.cs
+++ b/src/linker/Linker.Dataflow/CompilerGeneratedState.cs
@@ -145,7 +145,7 @@ namespace Mono.Linker.Dataflow
// Discover calls or references to lambdas or local functions. This includes
// calls to local functions, and lambda assignments (which use ldftn).
if (method.Body != null) {
- foreach (var instruction in method.Body.Instructions) {
+ foreach (var instruction in _context.GetMethodIL (method).Instructions) {
switch (instruction.OpCode.OperandType) {
case OperandType.InlineMethod: {
MethodDefinition? referencedMethod = _context.TryResolve ((MethodReference) instruction.Operand);
@@ -354,7 +354,7 @@ namespace Mono.Linker.Dataflow
GenericInstanceType? ScanForInit (TypeDefinition compilerGeneratedType, MethodBody body)
{
- foreach (var instr in body.Instructions) {
+ foreach (var instr in _context.GetMethodIL (body).Instructions) {
bool handled = false;
switch (instr.OpCode.Code) {
case Code.Initobj:
diff --git a/src/linker/Linker.Dataflow/FlowAnnotations.cs b/src/linker/Linker.Dataflow/FlowAnnotations.cs
index 183e55ce1..45d1bc073 100644
--- a/src/linker/Linker.Dataflow/FlowAnnotations.cs
+++ b/src/linker/Linker.Dataflow/FlowAnnotations.cs
@@ -418,7 +418,7 @@ namespace ILLink.Shared.TrimAnalysis
FieldReference? foundReference = null;
- foreach (Instruction instruction in body.Instructions) {
+ foreach (Instruction instruction in _context.GetMethodIL (body).Instructions) {
switch (instruction.OpCode.Code) {
case Code.Ldsfld when !write:
case Code.Ldfld when !write:
diff --git a/src/linker/Linker.Dataflow/InterproceduralState.cs b/src/linker/Linker.Dataflow/InterproceduralState.cs
index 09bc3be22..bfb5cf573 100644
--- a/src/linker/Linker.Dataflow/InterproceduralState.cs
+++ b/src/linker/Linker.Dataflow/InterproceduralState.cs
@@ -14,23 +14,25 @@ using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.Single
namespace Mono.Linker.Dataflow
{
- // Wrapper that implements IEquatable for MethodBody.
- readonly record struct MethodBodyValue (MethodBody MethodBody);
-
// Tracks the set of methods which get analyzer together during interprocedural analysis,
// and the possible states of hoisted locals in state machine methods and lambdas/local functions.
struct InterproceduralState : IEquatable<InterproceduralState>
{
- public ValueSet<MethodBodyValue> MethodBodies;
+ public ValueSet<MethodIL> MethodBodies;
public HoistedLocalState HoistedLocals;
readonly InterproceduralStateLattice lattice;
- public InterproceduralState (ValueSet<MethodBodyValue> methodBodies, HoistedLocalState hoistedLocals, InterproceduralStateLattice lattice)
+ public InterproceduralState (ValueSet<MethodIL> methodBodies, HoistedLocalState hoistedLocals, InterproceduralStateLattice lattice)
=> (MethodBodies, HoistedLocals, this.lattice) = (methodBodies, hoistedLocals, lattice);
public bool Equals (InterproceduralState other)
=> MethodBodies.Equals (other.MethodBodies) && HoistedLocals.Equals (other.HoistedLocals);
+ public override bool Equals (object? obj)
+ => obj is InterproceduralState state && Equals (state);
+
+ public override int GetHashCode () => base.GetHashCode ();
+
public InterproceduralState Clone ()
=> new (MethodBodies.Clone (), HoistedLocals.Clone (), lattice);
@@ -44,22 +46,27 @@ namespace Mono.Linker.Dataflow
public void TrackMethod (MethodBody methodBody)
{
+ TrackMethod (lattice.Context.GetMethodIL (methodBody));
+ }
+
+ public void TrackMethod (MethodIL methodIL)
+ {
// Work around the fact that ValueSet is readonly
- var methodsList = new List<MethodBodyValue> (MethodBodies);
- methodsList.Add (new MethodBodyValue (methodBody));
+ var methodsList = new List<MethodIL> (MethodBodies);
+ methodsList.Add (methodIL);
// For state machine methods, also scan the state machine members.
// Simplification: assume that all generated methods of the state machine type are
// reached at the point where the state machine method is reached.
- if (CompilerGeneratedState.TryGetStateMachineType (methodBody.Method, out TypeDefinition? stateMachineType)) {
+ if (CompilerGeneratedState.TryGetStateMachineType (methodIL.Method, out TypeDefinition? stateMachineType)) {
foreach (var stateMachineMethod in stateMachineType.Methods) {
Debug.Assert (!CompilerGeneratedNames.IsLambdaOrLocalFunction (stateMachineMethod.Name));
if (stateMachineMethod.Body is MethodBody stateMachineMethodBody)
- methodsList.Add (new MethodBodyValue (stateMachineMethodBody));
+ methodsList.Add (lattice.Context.GetMethodIL (stateMachineMethodBody));
}
}
- MethodBodies = new ValueSet<MethodBodyValue> (methodsList);
+ MethodBodies = new ValueSet<MethodIL> (methodsList);
}
public void SetHoistedLocal (HoistedLocalKey key, MultiValue value)
@@ -76,15 +83,17 @@ namespace Mono.Linker.Dataflow
=> HoistedLocals.Get (key);
}
- struct InterproceduralStateLattice : ILattice<InterproceduralState>
+ readonly struct InterproceduralStateLattice : ILattice<InterproceduralState>
{
- public readonly ValueSetLattice<MethodBodyValue> MethodBodyLattice;
+ public readonly ValueSetLattice<MethodIL> MethodBodyLattice;
public readonly DictionaryLattice<HoistedLocalKey, MultiValue, ValueSetLattice<SingleValue>> HoistedLocalsLattice;
+ public readonly LinkContext Context;
public InterproceduralStateLattice (
- ValueSetLattice<MethodBodyValue> methodBodyLattice,
- DictionaryLattice<HoistedLocalKey, MultiValue, ValueSetLattice<SingleValue>> hoistedLocalsLattice)
- => (MethodBodyLattice, HoistedLocalsLattice) = (methodBodyLattice, hoistedLocalsLattice);
+ ValueSetLattice<MethodIL> methodBodyLattice,
+ DictionaryLattice<HoistedLocalKey, MultiValue, ValueSetLattice<SingleValue>> hoistedLocalsLattice,
+ LinkContext context)
+ => (MethodBodyLattice, HoistedLocalsLattice, Context) = (methodBodyLattice, hoistedLocalsLattice, context);
public InterproceduralState Top => new InterproceduralState (MethodBodyLattice.Top, HoistedLocalsLattice.Top, this);
diff --git a/src/linker/Linker.Dataflow/MethodBodyScanner.cs b/src/linker/Linker.Dataflow/MethodBodyScanner.cs
index bc729794e..fab08ede8 100644
--- a/src/linker/Linker.Dataflow/MethodBodyScanner.cs
+++ b/src/linker/Linker.Dataflow/MethodBodyScanner.cs
@@ -51,7 +51,7 @@ namespace Mono.Linker.Dataflow
protected MethodBodyScanner (LinkContext context)
{
this._context = context;
- this.InterproceduralStateLattice = default;
+ this.InterproceduralStateLattice = new InterproceduralStateLattice (default, default, context);
}
internal MultiValue ReturnValue { private set; get; }
@@ -151,9 +151,9 @@ namespace Mono.Linker.Dataflow
int _currentBlockIndex;
bool _foundEndOfPrevBlock;
- public BasicBlockIterator (MethodBody methodBody)
+ public BasicBlockIterator (MethodIL methodIL)
{
- _methodBranchTargets = methodBody.ComputeBranchTargets ();
+ _methodBranchTargets = methodIL.ComputeBranchTargets ();
_currentBlockIndex = -1;
_foundEndOfPrevBlock = true;
}
@@ -226,9 +226,9 @@ namespace Mono.Linker.Dataflow
// Scans the method as well as any nested functions (local functions or lambdas) and state machines
// reachable from it.
- public virtual void InterproceduralScan (MethodBody startingMethodBody)
+ public virtual void InterproceduralScan (MethodIL startingMethodIL)
{
- MethodDefinition startingMethod = startingMethodBody.Method;
+ MethodDefinition startingMethod = startingMethodIL.Method;
// Note that the default value of a hoisted local will be MultiValueLattice.Top, not UnknownValue.Instance.
// This ensures that there are no warnings for the "unassigned state" of a parameter.
@@ -236,15 +236,15 @@ namespace Mono.Linker.Dataflow
var interproceduralState = InterproceduralStateLattice.Top;
var oldInterproceduralState = interproceduralState.Clone ();
- interproceduralState.TrackMethod (startingMethodBody);
+ interproceduralState.TrackMethod (startingMethodIL);
while (!interproceduralState.Equals (oldInterproceduralState)) {
oldInterproceduralState = interproceduralState.Clone ();
// Flow state through all methods encountered so far, as long as there
// are changes discovered in the hoisted local state on entry to any method.
- foreach (var methodBodyValue in oldInterproceduralState.MethodBodies)
- Scan (methodBodyValue.MethodBody, ref interproceduralState);
+ foreach (var methodIL in oldInterproceduralState.MethodBodies)
+ Scan (methodIL, ref interproceduralState);
}
#if DEBUG
@@ -274,21 +274,22 @@ namespace Mono.Linker.Dataflow
interproceduralState.TrackMethod (method);
}
- protected virtual void Scan (MethodBody methodBody, ref InterproceduralState interproceduralState)
+ protected virtual void Scan (MethodIL methodIL, ref InterproceduralState interproceduralState)
{
+ MethodBody methodBody = methodIL.Body;
MethodDefinition thisMethod = methodBody.Method;
- LocalVariableStore locals = new (methodBody.Variables.Count);
+ LocalVariableStore locals = new (methodIL.Variables.Count);
Dictionary<int, Stack<StackSlot>> knownStacks = new Dictionary<int, Stack<StackSlot>> ();
Stack<StackSlot>? currentStack = new Stack<StackSlot> (methodBody.MaxStackSize);
- ScanExceptionInformation (knownStacks, methodBody);
+ ScanExceptionInformation (knownStacks, methodIL);
- BasicBlockIterator blockIterator = new BasicBlockIterator (methodBody);
+ BasicBlockIterator blockIterator = new BasicBlockIterator (methodIL);
ReturnValue = new ();
- foreach (Instruction operation in methodBody.Instructions) {
+ foreach (Instruction operation in methodIL.Instructions) {
int curBasicBlock = blockIterator.MoveNext (operation);
if (knownStacks.ContainsKey (operation.Offset)) {
@@ -411,7 +412,7 @@ namespace Mono.Linker.Dataflow
case Code.Ldloc_S:
case Code.Ldloca:
case Code.Ldloca_S:
- ScanLdloc (operation, currentStack, methodBody, locals);
+ ScanLdloc (operation, currentStack, methodIL, locals);
ValidateNoReferenceToReference (locals, methodBody.Method, operation.Offset);
break;
@@ -576,7 +577,7 @@ namespace Mono.Linker.Dataflow
case Code.Stloc_1:
case Code.Stloc_2:
case Code.Stloc_3:
- ScanStloc (operation, currentStack, methodBody, locals, curBasicBlock);
+ ScanStloc (operation, currentStack, methodIL, locals, curBasicBlock);
ValidateNoReferenceToReference (locals, methodBody.Method, operation.Offset);
break;
@@ -699,9 +700,9 @@ namespace Mono.Linker.Dataflow
}
}
- private static void ScanExceptionInformation (Dictionary<int, Stack<StackSlot>> knownStacks, MethodBody methodBody)
+ private static void ScanExceptionInformation (Dictionary<int, Stack<StackSlot>> knownStacks, MethodIL methodIL)
{
- foreach (ExceptionHandler exceptionClause in methodBody.ExceptionHandlers) {
+ foreach (ExceptionHandler exceptionClause in methodIL.ExceptionHandlers) {
Stack<StackSlot> catchStack = new Stack<StackSlot> (1);
catchStack.Push (new StackSlot ());
@@ -755,12 +756,12 @@ namespace Mono.Linker.Dataflow
private void ScanLdloc (
Instruction operation,
Stack<StackSlot> currentStack,
- MethodBody methodBody,
+ MethodIL methodIL,
LocalVariableStore locals)
{
- VariableDefinition localDef = GetLocalDef (operation, methodBody.Variables);
+ VariableDefinition localDef = GetLocalDef (operation, methodIL.Variables);
if (localDef == null) {
- PushUnknownAndWarnAboutInvalidIL (currentStack, methodBody, operation.Offset);
+ PushUnknownAndWarnAboutInvalidIL (currentStack, methodIL.Body, operation.Offset);
return;
}
@@ -818,14 +819,14 @@ namespace Mono.Linker.Dataflow
private void ScanStloc (
Instruction operation,
Stack<StackSlot> currentStack,
- MethodBody methodBody,
+ MethodIL methodIL,
LocalVariableStore locals,
int curBasicBlock)
{
- StackSlot valueToStore = PopUnknown (currentStack, 1, methodBody, operation.Offset);
- VariableDefinition localDef = GetLocalDef (operation, methodBody.Variables);
+ StackSlot valueToStore = PopUnknown (currentStack, 1, methodIL.Body, operation.Offset);
+ VariableDefinition localDef = GetLocalDef (operation, methodIL.Variables);
if (localDef == null) {
- WarnAboutInvalidILInMethod (methodBody, operation.Offset);
+ WarnAboutInvalidILInMethod (methodIL.Body, operation.Offset);
return;
}
diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
index f47941755..804e5a214 100644
--- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
+++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
@@ -61,21 +61,21 @@ namespace Mono.Linker.Dataflow
TrimAnalysisPatterns = new TrimAnalysisPatternStore (MultiValueLattice, context);
}
- public override void InterproceduralScan (MethodBody methodBody)
+ public override void InterproceduralScan (MethodIL methodIL)
{
- base.InterproceduralScan (methodBody);
+ base.InterproceduralScan (methodIL);
var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true);
TrimAnalysisPatterns.MarkAndProduceDiagnostics (reflectionMarker, _markStep);
}
- protected override void Scan (MethodBody methodBody, ref InterproceduralState interproceduralState)
+ protected override void Scan (MethodIL methodIL, ref InterproceduralState interproceduralState)
{
- _origin = new MessageOrigin (methodBody.Method);
- base.Scan (methodBody, ref interproceduralState);
+ _origin = new MessageOrigin (methodIL.Method);
+ base.Scan (methodIL, ref interproceduralState);
- if (!methodBody.Method.ReturnsVoid ()) {
- var method = methodBody.Method;
+ if (!methodIL.Method.ReturnsVoid ()) {
+ var method = methodIL.Method;
var methodReturnValue = _annotations.GetMethodReturnValue (method);
if (methodReturnValue.DynamicallyAccessedMemberTypes != 0)
HandleAssignmentPattern (_origin, ReturnValue, methodReturnValue);
diff --git a/src/linker/Linker.Dataflow/ScannerExtensions.cs b/src/linker/Linker.Dataflow/ScannerExtensions.cs
index c445f7d61..7b8dbba87 100644
--- a/src/linker/Linker.Dataflow/ScannerExtensions.cs
+++ b/src/linker/Linker.Dataflow/ScannerExtensions.cs
@@ -16,10 +16,10 @@ namespace Mono.Linker.Dataflow
|| (opcode.FlowControl == FlowControl.Return && opcode.Code != Code.Ret);
}
- public static HashSet<int> ComputeBranchTargets (this MethodBody methodBody)
+ public static HashSet<int> ComputeBranchTargets (this MethodIL methodIL)
{
HashSet<int> branchTargets = new HashSet<int> ();
- foreach (Instruction operation in methodBody.Instructions) {
+ foreach (Instruction operation in methodIL.Instructions) {
if (!operation.OpCode.IsControlFlowInstruction ())
continue;
Object value = operation.Operand;
@@ -31,7 +31,7 @@ namespace Mono.Linker.Dataflow
}
}
}
- foreach (ExceptionHandler einfo in methodBody.ExceptionHandlers) {
+ foreach (ExceptionHandler einfo in methodIL.ExceptionHandlers) {
if (einfo.HandlerType == ExceptionHandlerType.Filter) {
branchTargets.Add (einfo.FilterStart.Offset);
}
diff --git a/src/linker/Linker.Steps/AddBypassNGenStep.cs b/src/linker/Linker.Steps/AddBypassNGenStep.cs
index 20d11f64d..affbf4e16 100644
--- a/src/linker/Linker.Steps/AddBypassNGenStep.cs
+++ b/src/linker/Linker.Steps/AddBypassNGenStep.cs
@@ -94,7 +94,9 @@ namespace Mono.Linker.Steps
const MethodAttributes ctorAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
bypassNGenAttributeDefaultConstructor = new MethodDefinition (".ctor", ctorAttributes, coreLibAssembly.MainModule.TypeSystem.Void);
+#pragma warning disable RS0030 // Anything after MarkStep should use Cecil directly as all method bodies should be processed by this point
var instructions = bypassNGenAttributeDefaultConstructor.Body.Instructions;
+#pragma warning restore RS0030
instructions.Add (Instruction.Create (OpCodes.Ldarg_0));
instructions.Add (Instruction.Create (OpCodes.Call, systemAttributeDefaultConstructor));
instructions.Add (Instruction.Create (OpCodes.Ret));
diff --git a/src/linker/Linker.Steps/CodeRewriterStep.cs b/src/linker/Linker.Steps/CodeRewriterStep.cs
index 6d70b1a09..566a7ab1a 100644
--- a/src/linker/Linker.Steps/CodeRewriterStep.cs
+++ b/src/linker/Linker.Steps/CodeRewriterStep.cs
@@ -64,12 +64,15 @@ namespace Mono.Linker.Steps
ret = Instruction.Create (OpCodes.Ret);
processor.Append (ret);
} else {
- ret = cctor.Body.Instructions.Last (l => l.OpCode.Code == Code.Ret);
var body = cctor.Body;
- processor = cctor.Body.GetLinkerILProcessor ();
+#pragma warning disable RS0030 // After MarkStep all methods should be processed and thus accessing Cecil directly is the right approach
+ var instructions = body.Instructions;
+#pragma warning restore RS0030
+ ret = instructions.Last (l => l.OpCode.Code == Code.Ret);
+ processor = body.GetLinkerILProcessor ();
- for (int i = 0; i < body.Instructions.Count; ++i) {
- var instr = body.Instructions[i];
+ for (int i = 0; i < instructions.Count; ++i) {
+ var instr = instructions[i];
if (instr.OpCode.Code != Code.Stsfld)
continue;
@@ -201,7 +204,9 @@ namespace Mono.Linker.Steps
case MetadataType.MVar:
case MetadataType.ValueType:
var vd = new VariableDefinition (method.ReturnType);
+#pragma warning disable RS0030 // Anything after MarkStep should not use ILProvider since all methods are guaranteed processed
body.Variables.Add (vd);
+#pragma warning restore RS0030
body.InitLocals = true;
il.Emit (OpCodes.Ldloca_S, vd);
diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs
index e8c29e29e..646e2e6bd 100644
--- a/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/linker/Linker.Steps/MarkStep.cs
@@ -75,13 +75,6 @@ namespace Mono.Linker.Steps
// method body scanner.
readonly Dictionary<MethodBody, bool> _compilerGeneratedMethodRequiresScanner;
- UnreachableBlocksOptimizer? _unreachableBlocksOptimizer;
- UnreachableBlocksOptimizer UnreachableBlocksOptimizer {
- get {
- Debug.Assert (_unreachableBlocksOptimizer != null);
- return _unreachableBlocksOptimizer;
- }
- }
MarkStepContext? _markContext;
MarkStepContext MarkContext {
get {
@@ -245,7 +238,6 @@ namespace Mono.Linker.Steps
public virtual void Process (LinkContext context)
{
_context = context;
- _unreachableBlocksOptimizer = new UnreachableBlocksOptimizer (_context);
_markContext = new MarkStepContext ();
_scopeStack = new MarkScopeStack ();
_dynamicallyAccessedMembersTypeHierarchy = new DynamicallyAccessedMembersTypeHierarchy (_context, this);
@@ -2569,7 +2561,7 @@ namespace Mono.Linker.Steps
} while ((type = Context.TryResolve (type.BaseType)) != null);
}
- static bool IsNonEmptyStaticConstructor (MethodDefinition method)
+ bool IsNonEmptyStaticConstructor (MethodDefinition method)
{
if (!method.IsStaticConstructor ())
return false;
@@ -2577,10 +2569,12 @@ namespace Mono.Linker.Steps
if (!method.HasBody || !method.IsIL)
return true;
- if (method.Body.CodeSize != 1)
+ var body = Context.GetMethodIL (method);
+
+ if (body.Body.CodeSize != 1)
return true;
- return method.Body.Instructions[0].OpCode.Code != Code.Ret;
+ return body.Instructions[0].OpCode.Code != Code.Ret;
}
static bool HasOnSerializeOrDeserializeAttribute (MethodDefinition method)
@@ -2856,14 +2850,14 @@ namespace Mono.Linker.Steps
return true;
}
- static PropertyDefinition? SearchPropertiesForMatchingFieldDefinition (FieldDefinition field)
+ PropertyDefinition? SearchPropertiesForMatchingFieldDefinition (FieldDefinition field)
{
foreach (var property in field.DeclaringType.Properties) {
- var instr = property.GetMethod?.Body?.Instructions;
- if (instr == null)
+ var body = property.GetMethod?.Body;
+ if (body == null)
continue;
- foreach (var ins in instr) {
+ foreach (var ins in Context.GetMethodIL (body).Instructions) {
if (ins?.Operand == field)
return property;
}
@@ -2955,7 +2949,7 @@ namespace Mono.Linker.Steps
// the reflection scanner. Checking this will also mark direct dependencies of the method body, if it
// hasn't been marked already. A cache ensures this only happens once for the method, whether or not
// it is accessed via reflection.
- return CheckRequiresReflectionMethodBodyScanner (method.Body);
+ return CheckRequiresReflectionMethodBodyScanner (Context.GetMethodIL (method));
}
void ProcessAnalysisAnnotationsForMethod (MethodDefinition method, DependencyKind dependencyKind, in MessageOrigin origin)
@@ -3137,8 +3131,6 @@ namespace Mono.Linker.Steps
if (CheckProcessed (method))
return;
- UnreachableBlocksOptimizer.ProcessMethod (method);
-
foreach (Action<MethodDefinition> handleMarkMethod in MarkContext.MarkMethodActions)
handleMarkMethod (method);
@@ -3488,7 +3480,9 @@ namespace Mono.Linker.Steps
protected virtual void MarkMethodBody (MethodBody body)
{
- if (Context.IsOptimizationEnabled (CodeOptimizations.UnreachableBodies, body.Method) && IsUnreachableBody (body)) {
+ var processedMethodBody = Context.GetMethodIL (body);
+
+ if (Context.IsOptimizationEnabled (CodeOptimizations.UnreachableBodies, body.Method) && IsUnreachableBody (processedMethodBody)) {
MarkAndCacheConvertToThrowExceptionCtor (new DependencyInfo (DependencyKind.UnreachableBodyRequirement, body.Method));
_unreachableBodies.Add ((body, ScopeStack.CurrentScope));
return;
@@ -3499,24 +3493,24 @@ namespace Mono.Linker.Steps
// But for compiler-generated methods we only do dataflow analysis if they're used through their
// corresponding user method, so we will skip dataflow for compiler-generated methods which
// are only accessed via reflection.
- bool requiresReflectionMethodBodyScanner = MarkAndCheckRequiresReflectionMethodBodyScanner (body);
+ bool requiresReflectionMethodBodyScanner = MarkAndCheckRequiresReflectionMethodBodyScanner (processedMethodBody);
// Data-flow (reflection scanning) for compiler-generated methods will happen as part of the
// data-flow scan of the user-defined method which uses this compiler-generated method.
if (CompilerGeneratedState.IsNestedFunctionOrStateMachineMember (body.Method))
return;
- MarkReflectionLikeDependencies (body, requiresReflectionMethodBodyScanner);
+ MarkReflectionLikeDependencies (processedMethodBody, requiresReflectionMethodBodyScanner);
}
- bool CheckRequiresReflectionMethodBodyScanner (MethodBody body)
+ bool CheckRequiresReflectionMethodBodyScanner (MethodIL methodIL)
{
// This method is only called on reflection access to compiler-generated methods.
// This should be uncommon, so don't cache the result.
- if (ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForMethodBody (Context, body.Method))
+ if (ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForMethodBody (Context, methodIL.Method))
return true;
- foreach (Instruction instruction in body.Instructions) {
+ foreach (Instruction instruction in methodIL.Instructions) {
switch (instruction.OpCode.OperandType) {
case OperandType.InlineField:
if (InstructionRequiresReflectionMethodBodyScannerForFieldAccess (instruction))
@@ -3536,64 +3530,64 @@ namespace Mono.Linker.Steps
// It computes the same value, while also marking as it goes, as an optimization.
// This should only be called behind a check to IsProcessed for the method or corresponding user method,
// to avoid recursion.
- bool MarkAndCheckRequiresReflectionMethodBodyScanner (MethodBody body)
+ bool MarkAndCheckRequiresReflectionMethodBodyScanner (MethodIL methodIL)
{
#if DEBUG
- if (!Annotations.IsProcessed (body.Method)) {
- Debug.Assert (CompilerGeneratedState.IsNestedFunctionOrStateMachineMember (body.Method));
- MethodDefinition owningMethod = body.Method;
+ if (!Annotations.IsProcessed (methodIL.Method)) {
+ Debug.Assert (CompilerGeneratedState.IsNestedFunctionOrStateMachineMember (methodIL.Method));
+ MethodDefinition owningMethod = methodIL.Method;
while (Context.CompilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember (owningMethod, out var owner))
owningMethod = owner;
- Debug.Assert (owningMethod != body.Method);
+ Debug.Assert (owningMethod != methodIL.Method);
Debug.Assert (Annotations.IsProcessed (owningMethod));
}
#endif
// This may get called multiple times for compiler-generated code: once for
// reflection access, and once as part of the interprocedural scan of the user method.
// This check ensures that we only do the work and produce warnings once.
- if (_compilerGeneratedMethodRequiresScanner.TryGetValue (body, out bool requiresReflectionMethodBodyScanner))
+ if (_compilerGeneratedMethodRequiresScanner.TryGetValue (methodIL.Body, out bool requiresReflectionMethodBodyScanner))
return requiresReflectionMethodBodyScanner;
- foreach (VariableDefinition var in body.Variables)
- MarkType (var.VariableType, new DependencyInfo (DependencyKind.VariableType, body.Method));
+ foreach (VariableDefinition var in methodIL.Variables)
+ MarkType (var.VariableType, new DependencyInfo (DependencyKind.VariableType, methodIL.Method));
- foreach (ExceptionHandler eh in body.ExceptionHandlers)
+ foreach (ExceptionHandler eh in methodIL.ExceptionHandlers)
if (eh.HandlerType == ExceptionHandlerType.Catch)
- MarkType (eh.CatchType, new DependencyInfo (DependencyKind.CatchType, body.Method));
+ MarkType (eh.CatchType, new DependencyInfo (DependencyKind.CatchType, methodIL.Method));
requiresReflectionMethodBodyScanner =
- ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForMethodBody (Context, body.Method);
- using var _ = ScopeStack.PushScope (new MessageOrigin (body.Method));
- foreach (Instruction instruction in body.Instructions)
- MarkInstruction (instruction, body.Method, ref requiresReflectionMethodBodyScanner);
+ ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForMethodBody (Context, methodIL.Method);
+ using var _ = ScopeStack.PushScope (new MessageOrigin (methodIL.Method));
+ foreach (Instruction instruction in methodIL.Instructions)
+ MarkInstruction (instruction, methodIL.Method, ref requiresReflectionMethodBodyScanner);
- MarkInterfacesNeededByBodyStack (body);
+ MarkInterfacesNeededByBodyStack (methodIL);
- if (CompilerGeneratedState.IsNestedFunctionOrStateMachineMember (body.Method))
- _compilerGeneratedMethodRequiresScanner.Add (body, requiresReflectionMethodBodyScanner);
+ if (CompilerGeneratedState.IsNestedFunctionOrStateMachineMember (methodIL.Method))
+ _compilerGeneratedMethodRequiresScanner.Add (methodIL.Body, requiresReflectionMethodBodyScanner);
- PostMarkMethodBody (body);
+ PostMarkMethodBody (methodIL.Body);
- Debug.Assert (requiresReflectionMethodBodyScanner == CheckRequiresReflectionMethodBodyScanner (body));
+ Debug.Assert (requiresReflectionMethodBodyScanner == CheckRequiresReflectionMethodBodyScanner (methodIL));
return requiresReflectionMethodBodyScanner;
}
- bool IsUnreachableBody (MethodBody body)
+ bool IsUnreachableBody (MethodIL methodIL)
{
- return !body.Method.IsStatic
- && !Annotations.IsInstantiated (body.Method.DeclaringType)
- && MethodBodyScanner.IsWorthConvertingToThrow (body);
+ return !methodIL.Method.IsStatic
+ && !Annotations.IsInstantiated (methodIL.Method.DeclaringType)
+ && MethodBodyScanner.IsWorthConvertingToThrow (methodIL);
}
partial void PostMarkMethodBody (MethodBody body);
- void MarkInterfacesNeededByBodyStack (MethodBody body)
+ void MarkInterfacesNeededByBodyStack (MethodIL methodIL)
{
// If a type could be on the stack in the body and an interface it implements could be on the stack on the body
// then we need to mark that interface implementation. When this occurs it is not safe to remove the interface implementation from the type
// even if the type is never instantiated
- var implementations = new InterfacesOnStackScanner (Context).GetReferencedInterfaces (body);
+ var implementations = new InterfacesOnStackScanner (Context).GetReferencedInterfaces (methodIL);
if (implementations == null)
return;
@@ -3726,24 +3720,24 @@ namespace Mono.Linker.Steps
//
// Tries to mark additional dependencies used in reflection like calls (e.g. typeof (MyClass).GetField ("fname"))
//
- protected virtual void MarkReflectionLikeDependencies (MethodBody body, bool requiresReflectionMethodBodyScanner)
+ protected virtual void MarkReflectionLikeDependencies (MethodIL methodIL, bool requiresReflectionMethodBodyScanner)
{
- Debug.Assert (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember (body.Method));
+ Debug.Assert (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember (methodIL.Method));
// requiresReflectionMethodBodyScanner tells us whether the method body itself requires a dataflow scan.
// If the method body owns any compiler-generated code, we might still need to do a scan of it together with
// all of the compiler-generated code it owns, so first check any compiler-generated callees.
- if (Context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod (body.Method, out List<IMemberDefinition>? compilerGeneratedCallees)) {
+ if (Context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod (methodIL.Method, out List<IMemberDefinition>? compilerGeneratedCallees)) {
foreach (var compilerGeneratedCallee in compilerGeneratedCallees) {
switch (compilerGeneratedCallee) {
case MethodDefinition nestedFunction:
if (nestedFunction.Body is MethodBody nestedBody)
- requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner (nestedBody);
+ requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner (Context.GetMethodIL (nestedBody));
break;
case TypeDefinition stateMachineType:
foreach (var method in stateMachineType.Methods) {
if (method.Body is MethodBody stateMachineBody)
- requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner (stateMachineBody);
+ requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner (Context.GetMethodIL (stateMachineBody));
}
break;
default:
@@ -3755,9 +3749,9 @@ namespace Mono.Linker.Steps
if (!requiresReflectionMethodBodyScanner)
return;
- Debug.Assert (ScopeStack.CurrentScope.Origin.Provider == body.Method);
+ Debug.Assert (ScopeStack.CurrentScope.Origin.Provider == methodIL.Method);
var scanner = new ReflectionMethodBodyScanner (Context, this, ScopeStack.CurrentScope.Origin);
- scanner.InterproceduralScan (body);
+ scanner.InterproceduralScan (methodIL);
}
protected class AttributeProviderPair
diff --git a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs
index 43f59dd25..179f15d87 100644
--- a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs
+++ b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs
@@ -433,9 +433,11 @@ namespace Mono.Linker.Steps
{
bool changed = false;
LinkerILProcessor processor = body.GetLinkerILProcessor ();
+#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
Collection<Instruction> instrs = body.Instructions;
+#pragma warning restore RS0030
- for (int i = 0; i < body.Instructions.Count; ++i) {
+ for (int i = 0; i < instrs.Count; ++i) {
Instruction instr = instrs[i];
switch (instr.OpCode.Code) {
@@ -454,7 +456,7 @@ namespace Mono.Linker.Steps
if (md.NoInlining)
break;
- var cpl = new CalleePayload (md, GetArgumentsOnStack (md, body.Instructions, i));
+ var cpl = new CalleePayload (md, GetArgumentsOnStack (md, instrs, i));
MethodResult? call_result = optimizer.TryGetMethodCallResult (cpl);
if (call_result is not MethodResult result)
break;
@@ -549,6 +551,11 @@ namespace Mono.Linker.Steps
public MethodBody Body { get; }
+#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
+ Collection<Instruction> Instructions => Body.Instructions;
+ Collection<ExceptionHandler> ExceptionHandlers => Body.ExceptionHandlers;
+#pragma warning restore RS0030
+
public int InstructionsReplaced { get; set; }
Collection<Instruction>? FoldedInstructions { get; set; }
@@ -557,7 +564,7 @@ namespace Mono.Linker.Steps
[MemberNotNull ("mapping")]
void InitializeFoldedInstruction ()
{
- FoldedInstructions = new Collection<Instruction> (Body.Instructions);
+ FoldedInstructions = new Collection<Instruction> (Instructions);
mapping = new Dictionary<Instruction, int> ();
}
@@ -570,7 +577,7 @@ namespace Mono.Linker.Steps
// Tracks mapping for replaced instructions for easier
// branch targets resolution later
- mapping[Body.Instructions[index]] = index;
+ mapping[Instructions[index]] = index;
FoldedInstructions[index] = newInstruction;
}
@@ -733,7 +740,7 @@ namespace Mono.Linker.Steps
public bool ApplyTemporaryInlining (in UnreachableBlocksOptimizer optimizer)
{
bool changed = false;
- var instructions = Body.Instructions;
+ var instructions = Instructions;
Instruction? targetResult;
for (int i = 0; i < instructions.Count; ++i) {
@@ -823,10 +830,10 @@ namespace Mono.Linker.Steps
// inject "ldnull; throw;" at the end - this branch should never be reachable and it's always valid
// (ret may need to return a value of the right type if the method has a return value which is complicated
// to construct out of nothing).
- if (index == Body.Instructions.Count - 1 && Body.Instructions[index].OpCode == OpCodes.Ret &&
- index > 0 && IsConditionalBranch (Body.Instructions[index - 1].OpCode)) {
+ if (index == Instructions.Count - 1 && Instructions[index].OpCode == OpCodes.Ret &&
+ index > 0 && IsConditionalBranch (Instructions[index - 1].OpCode)) {
processor.Replace (index, Instruction.Create (OpCodes.Ldnull));
- processor.InsertAfter (Body.Instructions[index], Instruction.Create (OpCodes.Throw));
+ processor.InsertAfter (Instructions[index], Instruction.Create (OpCodes.Throw));
} else {
processor.RemoveAt (index);
++removed;
@@ -1042,8 +1049,8 @@ namespace Mono.Linker.Steps
if (!exceptionHandlersChecked) {
exceptionHandlersChecked = true;
- var instrs = Body.Instructions;
- foreach (var handler in Body.ExceptionHandlers) {
+ var instrs = Instructions;
+ foreach (var handler in ExceptionHandlers) {
int start = instrs.IndexOf (handler.TryStart);
int end = instrs.IndexOf (handler.TryEnd) - 1;
@@ -1154,6 +1161,11 @@ namespace Mono.Linker.Steps
struct BodySweeper
{
readonly MethodBody body;
+#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
+ Collection<Instruction> Instructions => body.Instructions;
+ Collection<VariableDefinition> Variables => body.Variables;
+ Collection<ExceptionHandler> ExceptionHandlers => body.ExceptionHandlers;
+#pragma warning restore RS0030
readonly BitArray reachable;
readonly List<ExceptionHandler>? unreachableExceptionHandlers;
readonly LinkContext context;
@@ -1180,7 +1192,7 @@ namespace Mono.Linker.Steps
public void Initialize ()
{
- var instrs = body.Instructions;
+ var instrs = Instructions;
//
// Reusing same reachable map and altering it at indexes
@@ -1215,7 +1227,7 @@ namespace Mono.Linker.Steps
// Initial pass which replaces unreachable instructions with nops or
// ret to keep the body verifiable
//
- var instrs = body.Instructions;
+ var instrs = Instructions;
for (int i = 0; i < instrs.Count; ++i) {
if (reachable[i])
continue;
@@ -1302,7 +1314,7 @@ namespace Mono.Linker.Steps
void CleanRemovedVariables (List<VariableDefinition> variables)
{
- foreach (var instr in body.Instructions) {
+ foreach (var instr in Instructions) {
VariableDefinition? variable = GetVariableReference (instr);
if (variable == null)
continue;
@@ -1315,7 +1327,7 @@ namespace Mono.Linker.Steps
}
variables.Sort ((a, b) => b.Index.CompareTo (a.Index));
- var body_variables = body.Variables;
+ var body_variables = Variables;
foreach (var variable in variables) {
var index = body_variables.IndexOf (variable);
@@ -1340,7 +1352,7 @@ namespace Mono.Linker.Steps
return;
foreach (var eh in unreachableExceptionHandlers)
- body.ExceptionHandlers.Remove (eh);
+ ExceptionHandlers.Remove (eh);
}
VariableDefinition? GetVariableReference (Instruction instruction)
@@ -1348,16 +1360,16 @@ namespace Mono.Linker.Steps
switch (instruction.OpCode.Code) {
case Code.Stloc_0:
case Code.Ldloc_0:
- return body.Variables[0];
+ return Variables[0];
case Code.Stloc_1:
case Code.Ldloc_1:
- return body.Variables[1];
+ return Variables[1];
case Code.Stloc_2:
case Code.Ldloc_2:
- return body.Variables[2];
+ return Variables[2];
case Code.Stloc_3:
case Code.Ldloc_3:
- return body.Variables[3];
+ return Variables[3];
}
if (instruction.Operand is VariableReference vr)
@@ -1402,7 +1414,9 @@ namespace Mono.Linker.Steps
{
MethodDefinition method = callee.Method;
Instruction[]? arguments = callee.Arguments;
+#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
Collection<Instruction> instructions = callee.Method.Body.Instructions;
+#pragma warning restore RS0030
MethodBody body = method.Body;
VariableReference vr;
@@ -1836,8 +1850,12 @@ namespace Mono.Linker.Steps
if (!body.InitLocals)
return null;
+#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
+ var variables = body.Variables;
+#pragma warning restore RS0030
+
// local variables don't need to be explicitly initialized
- return CodeRewriterStep.CreateConstantResultInstruction (context, body.Variables[index].VariableType);
+ return CodeRewriterStep.CreateConstantResultInstruction (context, variables[index].VariableType);
}
bool GetOperandConstantValue ([NotNullWhen (true)] out object? value)
diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs
index baa348ff1..6bd0ff73b 100644
--- a/src/linker/Linker/LinkContext.cs
+++ b/src/linker/Linker/LinkContext.cs
@@ -80,6 +80,7 @@ namespace Mono.Linker
readonly List<MessageContainer> _cachedWarningMessageContainers;
readonly ILogger _logger;
readonly Dictionary<AssemblyDefinition, bool> _isTrimmable;
+ readonly UnreachableBlocksOptimizer _unreachableBlocksOptimizer;
public Pipeline Pipeline {
get { return _pipeline; }
@@ -223,6 +224,7 @@ namespace Mono.Linker
GeneralSingleWarn = false;
SingleWarn = new Dictionary<string, bool> ();
AssembliesWithGeneratedSingleWarning = new HashSet<string> ();
+ _unreachableBlocksOptimizer = new UnreachableBlocksOptimizer (this);
const CodeOptimizations defaultOptimizations =
CodeOptimizations.BeforeFieldInit |
@@ -939,6 +941,28 @@ namespace Mono.Linker
: null;
}
+ readonly HashSet<MethodDefinition> _processed_bodies_for_method = new HashSet<MethodDefinition> (2048);
+
+ /// <summary>
+ /// Linker applies some optimization on method bodies. For example it can remove dead branches of code
+ /// based on constant propagation. To avoid overmarking, all code which processes the method's IL
+ /// should only view the IL after it's been optimized.
+ /// As such typically MethodDefinition.MethodBody should not be accessed directly on the Cecil object model
+ /// instead all accesses to method body should go through the ILProvider here
+ /// which will make sure the IL of the method is fully optimized before it's handed out.
+ /// </summary>
+ public MethodIL GetMethodIL (Cecil.Cil.MethodBody methodBody)
+ => GetMethodIL (methodBody.Method);
+
+ public MethodIL GetMethodIL (MethodDefinition method)
+ {
+ if (_processed_bodies_for_method.Add (method)) {
+ _unreachableBlocksOptimizer.ProcessMethod (method);
+ }
+
+ return MethodIL.Create (method.Body);
+ }
+
readonly HashSet<MemberReference> unresolved_reported = new ();
readonly HashSet<ExportedType> unresolved_exported_types_reported = new ();
diff --git a/src/linker/Linker/MethodBodyScanner.cs b/src/linker/Linker/MethodBodyScanner.cs
index 8f2aa05a6..535b1244c 100644
--- a/src/linker/Linker/MethodBodyScanner.cs
+++ b/src/linker/Linker/MethodBodyScanner.cs
@@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -12,7 +11,7 @@ namespace Mono.Linker
{
public static class MethodBodyScanner
{
- public static bool IsWorthConvertingToThrow (MethodBody body)
+ public static bool IsWorthConvertingToThrow (MethodIL body)
{
// Some bodies are cheaper size wise to leave alone than to convert to a throw
Instruction? previousMeaningful = null;
@@ -64,9 +63,9 @@ namespace Mono.Linker
this.context = context;
}
- public IEnumerable<(InterfaceImplementation, TypeDefinition)>? GetReferencedInterfaces (MethodBody body)
+ public IEnumerable<(InterfaceImplementation, TypeDefinition)>? GetReferencedInterfaces (MethodIL methodIL)
{
- var possibleStackTypes = AllPossibleStackTypes (body.Method);
+ var possibleStackTypes = AllPossibleStackTypes (methodIL);
if (possibleStackTypes.Count == 0)
return null;
@@ -95,27 +94,23 @@ namespace Mono.Linker
return interfaceImplementations;
}
- HashSet<TypeDefinition> AllPossibleStackTypes (MethodDefinition method)
+ HashSet<TypeDefinition> AllPossibleStackTypes (MethodIL methodIL)
{
- if (!method.HasBody)
- throw new ArgumentException ("Method does not have body", nameof (method));
-
- var body = method.Body;
var types = new HashSet<TypeDefinition> ();
- foreach (VariableDefinition var in body.Variables)
+ foreach (VariableDefinition var in methodIL.Variables)
AddIfResolved (types, var.VariableType);
- foreach (var param in method.GetParameters ())
+ foreach (var param in methodIL.Method.GetParameters ())
AddIfResolved (types, param.ParameterType);
- foreach (ExceptionHandler eh in body.ExceptionHandlers) {
+ foreach (ExceptionHandler eh in methodIL.ExceptionHandlers) {
if (eh.HandlerType == ExceptionHandlerType.Catch) {
AddIfResolved (types, eh.CatchType);
}
}
- foreach (Instruction instruction in body.Instructions) {
+ foreach (Instruction instruction in methodIL.Instructions) {
if (instruction.Operand is FieldReference fieldReference) {
if (context.TryResolve (fieldReference)?.FieldType is TypeReference fieldType)
AddIfResolved (types, fieldType);
diff --git a/src/linker/Linker/MethodIL.cs b/src/linker/Linker/MethodIL.cs
new file mode 100644
index 000000000..fc97a6e24
--- /dev/null
+++ b/src/linker/Linker/MethodIL.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Collections.Generic;
+
+namespace Mono.Linker
+{
+ /// <summary>
+ /// This is a wrapper which should be used by anything accessing method's body - see LinkContext.GetMethodIL for more details.
+ /// Any accesses made throught this wrapper are considered "safe"/OK since the wrapper is only created
+ /// once all of the optimizations are applied.
+ /// </summary>
+ public readonly record struct MethodIL
+ {
+ MethodIL (MethodBody body) => this.Body = body;
+
+ public readonly MethodBody Body;
+
+ public MethodDefinition Method => Body.Method;
+
+#pragma warning disable RS0030 // Wrapper which provides safe access to the property
+ public Collection<Instruction> Instructions => Body.Instructions;
+ public Collection<ExceptionHandler> ExceptionHandlers => Body.ExceptionHandlers;
+ public Collection<VariableDefinition> Variables => Body.Variables;
+#pragma warning restore RS0030
+
+ public static MethodIL Create (MethodBody body) => new MethodIL (body);
+ }
+}
diff --git a/src/linker/Linker/TypeReferenceWalker.cs b/src/linker/Linker/TypeReferenceWalker.cs
index 700e2c716..844f8bd3c 100644
--- a/src/linker/Linker/TypeReferenceWalker.cs
+++ b/src/linker/Linker/TypeReferenceWalker.cs
@@ -154,6 +154,7 @@ namespace Mono.Linker
void WalkTypeScope (MethodBody body)
{
+#pragma warning disable RS0030 // Processing type references should not trigger method marking/processing, so access Cecil directly
if (body.HasVariables) {
foreach (var v in body.Variables) {
WalkScopeOfTypeReference (v.VariableType);
@@ -205,6 +206,7 @@ namespace Mono.Linker
}
}
}
+#pragma warning restore RS0030 // Do not used banned APIs
}
void WalkMethodReference (MethodReference mr)
diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedCodeInPreservedAssemblyWithWarning.cs b/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedCodeInPreservedAssemblyWithWarning.cs
index b66b0cd30..f05f1b7fb 100644
--- a/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedCodeInPreservedAssemblyWithWarning.cs
+++ b/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedCodeInPreservedAssemblyWithWarning.cs
@@ -26,12 +26,13 @@ namespace Mono.Linker.Tests.Cases.DataFlow
WithLocalFunction ();
}
+ // The compiler generated state will see the modified body,
+ // and will not associate the local function with the user method.
+ // Generic argument warnings from the local function will not be suppressed
+ // by RUC on the user method.
+
class Inner
{
- // In this case the compiler generated state will see the modified body,
- // and will not associate the local function with the user method.
- // Generic argument warnings from the local function will not be suppressed
- // by RUC on the user method.
[RequiresUnreferencedCode ("--" + nameof (Inner) + "." + nameof (WithLocalFunctionInner) + "--")]
public static void WithLocalFunctionInner ()
{
@@ -49,10 +50,6 @@ namespace Mono.Linker.Tests.Cases.DataFlow
}
}
- // In this case the compiler generated state will see the original body,
- // and will associate the local function with the user method.
- // Generic argument warnings from the local function will be suppressed
- // by RUC on the user method.
[RequiresUnreferencedCode ("--" + nameof (WithLocalFunction) + "--")]
public static void WithLocalFunction ()
{
@@ -61,6 +58,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow
}
// https://github.com/dotnet/linker/issues/2937
+ [ExpectedWarning ("IL2091", ProducedBy = ProducedBy.Trimmer)]
void LocalWithWarning<T> ()
{
// No warning
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/CompilerGeneratedCodeSubstitutions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/CompilerGeneratedCodeSubstitutions.cs
new file mode 100644
index 000000000..5b1edc376
--- /dev/null
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/CompilerGeneratedCodeSubstitutions.cs
@@ -0,0 +1,245 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading.Tasks;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.UnreachableBlock
+{
+ [SetupLinkerArgument ("--enable-opt", "ipconstprop")]
+
+ // Using Kept validation on compiler generated code is tricky as we would have to describe
+ // all of the compiler generated classes and members which are expected to be kept.
+ // So not using that here (at least until we come up with a better way to do this).
+ // Instead this test relies on RUC and warnings to detect "kept" and "removed" calls.
+ [SkipKeptItemsValidation]
+ [ExpectedNoWarnings]
+ class CompilerGeneratedCodeSubstitutions
+ {
+ static void Main ()
+ {
+ Lambda.Test ();
+ LocalFunction.Test ();
+ Iterator.Test ();
+ Async.Test ();
+ }
+
+ class Lambda
+ {
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static void TestBranchInLambda ()
+ {
+ var a = () => {
+ if (AlwaysFalse) {
+ RemovedMethod ();
+ } else {
+ UsedMethod ();
+ }
+ };
+
+ a ();
+ }
+
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static void TestBranchAroundLambda ()
+ {
+ Action a;
+ if (AlwaysFalse) {
+ a = () => RemovedMethod ();
+ } else {
+ a = () => UsedMethod ();
+ }
+
+ a ();
+ }
+
+ public static void Test ()
+ {
+ TestBranchInLambda ();
+ TestBranchAroundLambda ();
+ }
+ }
+
+ class LocalFunction
+ {
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static void TestBranchInLocalFunction ()
+ {
+ void LocalFunction ()
+ {
+ if (AlwaysFalse) {
+ RemovedMethod ();
+ } else {
+ UsedMethod ();
+ }
+ }
+
+ LocalFunction ();
+ }
+
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static void TestBranchAroundLocalFunction ()
+ {
+ Action a;
+ if (AlwaysFalse) {
+ void RemovedLocalFunction ()
+ {
+ RemovedMethod ();
+ }
+
+ RemovedLocalFunction ();
+ } else {
+ void UsedLocalFunction ()
+ {
+ UsedMethod ();
+ }
+
+ UsedLocalFunction ();
+ }
+ }
+
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static void TestBranchAroundUsageOfLocalFunction ()
+ {
+ Action a;
+ if (AlwaysFalse) {
+ RemovedLocalFunction ();
+ } else {
+ UsedLocalFunction ();
+ }
+
+ void RemovedLocalFunction ()
+ {
+ RemovedMethod ();
+ }
+
+ void UsedLocalFunction ()
+ {
+ UsedMethod ();
+ }
+ }
+
+ public static void Test ()
+ {
+ TestBranchInLocalFunction ();
+ TestBranchAroundLocalFunction ();
+ TestBranchAroundUsageOfLocalFunction ();
+ }
+ }
+
+ class Iterator
+ {
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static IEnumerable<int> TestBranchWithNormalCall ()
+ {
+ if (AlwaysFalse) {
+ RemovedMethod ();
+ } else {
+ UsedMethod ();
+ }
+
+ yield return 1;
+ }
+
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static IEnumerable<int> TestBranchWithYieldAfter ()
+ {
+ if (AlwaysFalse) {
+ RemovedMethod ();
+ yield return 1;
+ } else {
+ UsedMethod ();
+ yield return 1;
+ }
+
+ yield return 1;
+ }
+
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ // https://github.com/dotnet/linker/issues/3087
+ [ExpectedWarning ("IL2026", "--RemovedMethod--", CompilerGeneratedCode = true)]
+ static IEnumerable<int> TestBranchWithYieldBefore ()
+ {
+ if (AlwaysFalse) {
+ yield return 1;
+ RemovedMethod ();
+ } else {
+ yield return 1;
+ UsedMethod ();
+ }
+
+ yield return 1;
+ }
+
+ public static void Test ()
+ {
+ TestBranchWithNormalCall ();
+ TestBranchWithYieldAfter ();
+ TestBranchWithYieldBefore ();
+ }
+ }
+
+ class Async
+ {
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ static async Task TestBranchWithNormalCall ()
+ {
+ if (AlwaysFalse) {
+ RemovedMethod ();
+ } else {
+ UsedMethod ();
+ }
+
+ await Task.FromResult (0);
+ }
+
+ [ExpectedWarning ("IL2026", "--UsedMethod--", CompilerGeneratedCode = true)]
+ // https://github.com/dotnet/linker/issues/3087
+ [ExpectedWarning ("IL2026", "--RemovedMethod--", CompilerGeneratedCode = true)]
+ static async Task TestBranchWithNormalCallAfterWAwait ()
+ {
+ if (AlwaysFalse) {
+ await Task.FromResult (0);
+ RemovedMethod ();
+ } else {
+ await Task.FromResult (0);
+ UsedMethod ();
+ }
+
+ await Task.FromResult (0);
+ }
+
+ [ExpectedWarning ("IL2026", "--UsedAsyncMethod--", CompilerGeneratedCode = true)]
+ static async Task TestBranchWithAsyncCall ()
+ {
+ if (AlwaysFalse) {
+ await RemovedAsyncMethod ();
+ } else {
+ await UsedAsyncMethod ();
+ }
+ }
+
+ public static void Test ()
+ {
+ TestBranchWithNormalCall ().RunSynchronously (); ;
+ TestBranchWithNormalCallAfterWAwait ().RunSynchronously ();
+ TestBranchWithAsyncCall ().RunSynchronously ();
+ }
+ }
+
+ static bool AlwaysFalse => false;
+
+ [RequiresUnreferencedCode ("--UsedAsyncMethod--")]
+ static async Task UsedAsyncMethod () => await Task.FromResult (0);
+
+ [RequiresUnreferencedCode ("--RemovedAsyncMethod--")]
+ static async Task RemovedAsyncMethod () => await Task.FromResult (-1);
+
+ [RequiresUnreferencedCode ("--UsedMethod--")]
+ static void UsedMethod () { }
+
+ [RequiresUnreferencedCode ("--RemovedMethod--")]
+ static void RemovedMethod () { }
+ }
+}