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/UnreachableBlocksOptimizer.cs1360
-rw-r--r--test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutions.cs33
-rw-r--r--test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutionsNested.cs34
-rw-r--r--test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs3
-rw-r--r--test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs88
-rw-r--r--test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs13
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs47
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs20
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/DeadVariables.cs2
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/InstanceMethodSubstitutions.cs43
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodArgumentPropagation.cs377
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodWithParametersSubstitutions.cs18
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs8
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedReturns.cs41
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/ResultInliningNotPossible.cs161
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs55
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/TryCatchBlocks.cs16
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFilterBlocks.cs21
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs18
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/UninitializedLocals.cs9
-rw-r--r--test/Mono.Linker.Tests.Cases/UnreachableBlock/WorksWithDynamicAssembly.cs8
21 files changed, 1603 insertions, 772 deletions
diff --git a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs
index 75aaafaf5..b4c90e88c 100644
--- a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs
+++ b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs
@@ -15,77 +15,25 @@ namespace Mono.Linker.Steps
{
//
// Evaluates simple properties or methods for constant expressions and
- // then uses this information to remove unreachable conditional blocks. It does
- // not do any inlining-like code changes.
+ // then uses this information to remove unreachable conditional blocks and
+ // inline collected constants.
//
public class UnreachableBlocksOptimizer
{
readonly LinkContext _context;
- MethodDefinition? IntPtrSize, UIntPtrSize;
-
- readonly struct ProcessingNode
- {
- public ProcessingNode (MethodDefinition method, int lastAttemptStackVersion)
- {
- Method = method;
- LastAttemptStackVersion = lastAttemptStackVersion;
- }
+ readonly Dictionary<MethodDefinition, MethodResult?> _cache_method_results = new (2048);
+ readonly Stack<MethodDefinition> _resursion_guard = new ();
- public ProcessingNode (in ProcessingNode other, int newLastAttempStackVersion)
- {
- Method = other.Method;
- LastAttemptStackVersion = newLastAttempStackVersion;
- }
-
- public readonly MethodDefinition Method;
- public readonly int LastAttemptStackVersion;
- }
-
- // Stack of method nodes which are currently being processed.
- // Implemented as linked list to allow easy referal to nodes and efficient moving of nodes within the list.
- // The top of the stack is the first item in the list.
- readonly LinkedList<ProcessingNode> _processingStack;
-
- // Each time an item is added or removed from the processing stack this value is incremented.
- // Moving items in the stack doesn't increment.
- // This is used to track loops - if there are two methods which have dependencies on each other
- // the processing needs to detect that and mark at least one of them as nonconst (regardless of the method's body)
- // to break the loop.
- // This is done by storing the version of the stack on each method node when that method is processed,
- // if we get around to process the method again and the version of the stack didn't change, then there's a loop
- // (nothing changed in the stack - order is unimportant, as such no new information has been added and so
- // we can't resolve the situation with just the info at hand).
- int _processingStackVersion;
-
- // Just a fast lookup from method to the node on the stack. This is needed to be able to quickly
- // access the node and move it to the top of the stack.
- readonly Dictionary<MethodDefinition, LinkedListNode<ProcessingNode>> _processingMethods;
-
- // Stores results of method processing. This state is kept forever to avoid reprocessing of methods.
- // If method is not in the dictionary it has not yet been processed.
- // The value in this dictionary can be
- // - ProcessedUnchangedSentinel - method has been processed and nothing was changed on it - its value is unknown
- // - NonConstSentinel - method has been processed and the return value is not a const
- // - Instruction instance - method has been processed and it has a constant return value (the value of the instruction)
- // Note: ProcessedUnchangedSentinel is used as an optimization. running constant value analysis on a method is relatively expensive
- // and so we delay it and only do it for methods where the value is asked for (or in case of changed methods upfront due to implementation detailds)
- readonly Dictionary<MethodDefinition, Instruction> _processedMethods;
- static readonly Instruction ProcessedUnchangedSentinel = Instruction.Create (OpCodes.Ldstr, "ProcessedUnchangedSentinel");
- static readonly Instruction NonConstSentinel = Instruction.Create (OpCodes.Ldstr, "NonConstSentinel");
+ MethodDefinition? IntPtrSize, UIntPtrSize;
public UnreachableBlocksOptimizer (LinkContext context)
{
_context = context;
-
- _processingStack = new LinkedList<ProcessingNode> ();
- _processingMethods = new Dictionary<MethodDefinition, LinkedListNode<ProcessingNode>> ();
- _processedMethods = new Dictionary<MethodDefinition, Instruction> ();
}
/// <summary>
- /// Processes the specified and method and perform all branch removal optimizations on it.
+ /// Processes the specified method and perform all branch removal optimizations on it.
/// When this returns it's guaranteed that the method has been optimized (if possible).
- /// It may optimize other methods as well - those are remembered for future reuse.
/// </summary>
/// <param name="method">The method to process</param>
public void ProcessMethod (MethodDefinition method)
@@ -96,16 +44,30 @@ namespace Mono.Linker.Steps
if (_context.Annotations.GetAction (method.Module.Assembly) != AssemblyAction.Link)
return;
- Debug.Assert (_processingStack.Count == 0 && _processingMethods.Count == 0);
- _processingStackVersion = 0;
+ var reducer = new BodyReducer (method.Body, _context);
- if (!_processedMethods.ContainsKey (method)) {
- AddMethodForProcessing (method);
+ //
+ // If no external dependency can be extracted into constant there won't be
+ // anything to optimize in the method
+ //
+ if (!reducer.ApplyTemporaryInlining (this))
+ return;
- ProcessStack ();
- }
+ //
+ // This is the main step which evaluates if any expression can
+ // produce folded branches. When it finds them the unreachable
+ // branch is removed.
+ //
+ if (reducer.RewriteBody ())
+ _context.LogMessage ($"Reduced '{reducer.InstructionsReplaced}' instructions in conditional branches for [{method.DeclaringType.Module.Assembly.Name}] method '{method.GetDisplayName ()}'.");
- Debug.Assert (_processedMethods.ContainsKey (method));
+ //
+ // Note: The inliner cannot run before reducer rewrites body as it
+ // would require another recomputing offsets due to instructions replacement
+ // done by inliner
+ //
+ var inliner = new CallInliner (method.Body, this);
+ inliner.RewriteBody ();
}
static bool IsMethodSupported (MethodDefinition method)
@@ -125,152 +87,109 @@ namespace Mono.Linker.Steps
return true;
}
- void AddMethodForProcessing (MethodDefinition method)
+ static bool HasJumpIntoTargetRange (Collection<Instruction> instructions, int firstInstr, int lastInstr, Func<Instruction, int>? mapping = null)
{
- Debug.Assert (!_processedMethods.ContainsKey (method));
+ foreach (var instr in instructions) {
+ switch (instr.OpCode.FlowControl) {
+ case FlowControl.Branch:
+ case FlowControl.Cond_Branch:
+ if (instr.Operand is Instruction target) {
+ int index = mapping == null ? instructions.IndexOf (target) : mapping (target);
+ if (index >= firstInstr && index <= lastInstr)
+ return true;
+ } else {
+ foreach (var rtarget in (Instruction[]) instr.Operand) {
+ int index = mapping == null ? instructions.IndexOf (rtarget) : mapping (rtarget);
+ if (index >= firstInstr && index <= lastInstr)
+ return true;
+ }
+ }
- var processingNode = new ProcessingNode (method, -1);
+ break;
+ }
+ }
- var stackNode = _processingStack.AddFirst (processingNode);
- _processingMethods.Add (method, stackNode);
- _processingStackVersion++;
+ return false;
}
- void StoreMethodAsProcessedAndRemoveFromQueue (LinkedListNode<ProcessingNode> stackNode, Instruction methodValue)
+ static bool IsSideEffectFreeLoad (Instruction instr)
{
- Debug.Assert (stackNode.List == _processingStack);
- Debug.Assert (methodValue != null);
-
- var method = stackNode.Value.Method;
- _processingMethods.Remove (method);
- _processingStack.Remove (stackNode);
- _processingStackVersion++;
+ switch (instr.OpCode.Code) {
+ case Code.Ldarg:
+ case Code.Ldloc:
+ case Code.Ldloc_0:
+ case Code.Ldloc_1:
+ case Code.Ldloc_2:
+ case Code.Ldloc_3:
+ case Code.Ldloc_S:
+ case Code.Ldc_I4_0:
+ case Code.Ldc_I4_1:
+ case Code.Ldc_I4_2:
+ case Code.Ldc_I4_3:
+ case Code.Ldc_I4_4:
+ case Code.Ldc_I4_5:
+ case Code.Ldc_I4_6:
+ case Code.Ldc_I4_7:
+ case Code.Ldc_I4_8:
+ case Code.Ldc_I4:
+ case Code.Ldc_I4_S:
+ case Code.Ldc_I4_M1:
+ case Code.Ldc_I8:
+ case Code.Ldc_R4:
+ case Code.Ldc_R8:
+ case Code.Ldnull:
+ case Code.Ldstr:
+ return true;
+ }
- _processedMethods[method] = methodValue;
+ return false;
}
- void ProcessStack ()
+ static bool IsComparisonAlwaysTrue (OpCode opCode, int left, int right)
{
- while (_processingStack.Count > 0) {
- var stackNode = _processingStack.First!;
- var method = stackNode.Value.Method;
-
- bool treatUnprocessedDependenciesAsNonConst = false;
- if (stackNode.Value.LastAttemptStackVersion == _processingStackVersion) {
- // Loop was detected - the stack hasn't changed since the last time we tried to process this method
- // as such there's no way to resolve the situation (running the code below would produce the exact same result).
-
- // Observation:
- // All nodes on the stack which have `LastAttemptStackVersion` equal to `processingStackVersion` are part of the loop
- // meaning removing any of them should break the loop and allow to make progress.
- // There might be other methods in between these which don't have the current version but are dependencies of some of the method
- // in the loop.
- // If we don't process these, then we might miss constants and branches to remove. See the doc
- // `constant-propagation-and-branch-removal.md` in this repo for more details and a sample.
-
- // To fix this go over the stack and find the "oldest" node with the current version - the "oldest" node which
- // is part of the loop:
- LinkedListNode<ProcessingNode>? lastNodeWithCurrentVersion = null;
- var candidateNodeToMoveToTop = _processingStack.Last;
- bool foundNodesWithNonCurrentVersion = false;
- while (candidateNodeToMoveToTop != stackNode) {
- var previousNode = candidateNodeToMoveToTop!.Previous;
-
- if (candidateNodeToMoveToTop.Value.LastAttemptStackVersion == _processingStackVersion) {
- lastNodeWithCurrentVersion = candidateNodeToMoveToTop;
- } else if (lastNodeWithCurrentVersion != null) {
- // We've found the "oldest" node with current version and the current node is not of that version
- // so it's older version. Move this node to the top of the stack.
- _processingStack.Remove (candidateNodeToMoveToTop);
- _processingStack.AddFirst (candidateNodeToMoveToTop);
- foundNodesWithNonCurrentVersion = true;
- }
-
- candidateNodeToMoveToTop = previousNode;
- }
-
- // There should be at least 2 nodes with the latest version to form a loop
- Debug.Assert (lastNodeWithCurrentVersion != stackNode);
-
- // If any node was found which was not of current version (and moved to the top of the stack), move on to processing
- // the stack - this will give a chance for these methods to be processed. It doesn't break the loop and we should come back here
- // again due to the same loop as before, but now with more nodes processed (hopefully all of the dependencies of the nodes in the loop).
- // In the worst case all of those nodes will become part of the loop - in which case we will move on to break the loop anyway.
- if (foundNodesWithNonCurrentVersion) {
- continue;
- }
-
- // No such node was found -> we only have nodes in the loop now, so we have to break the loop.
- // We do this by processing it with special flag which will make it ignore any unprocessed dependencies
- // treating them as non-const. These should only be nodes in the loop.
- treatUnprocessedDependenciesAsNonConst = true;
- }
-
- stackNode.Value = new ProcessingNode (stackNode.Value, _processingStackVersion);
-
- if (!IsMethodSupported (method)) {
- StoreMethodAsProcessedAndRemoveFromQueue (stackNode, ProcessedUnchangedSentinel);
- continue;
- }
-
- var reducer = new BodyReducer (method.Body, _context);
-
- //
- // Temporarily inlines any calls which return contant expression.
- // If it needs to know the result of analysis of other methods and those has not been processed yet
- // it will still scan the entire body, but we will rerun the full processing one more time later.
- //
- if (!TryInlineBodyDependencies (ref reducer, treatUnprocessedDependenciesAsNonConst, out bool changed)) {
- // Method has unprocessed dependencies - so back off and try again later
- // Leave it in the stack on its current position.
- // It should not be on the first position anymore:
- // - There are unprocessed dependencies
- // - Those should be moved to the top of the stack above this method
- Debug.Assert (_processingStack.First != stackNode);
- continue;
- }
-
- if (!changed) {
- // All dependencies are processed and there were no const values found. There's nothing to optimize.
- // Mark the method as processed - without computing the const value of it (we don't know if it's going to be needed)
- StoreMethodAsProcessedAndRemoveFromQueue (stackNode, ProcessedUnchangedSentinel);
- continue;
- }
-
- // The method has been modified due to constant propagation - we will optimize it.
-
- //
- // This is the main step which evaluates if inlined calls can
- // produce folded branches. When it finds them the unreachable
- // branch is replaced with nops.
- //
- if (reducer.RewriteBody ())
- _context.LogMessage ($"Reduced '{reducer.InstructionsReplaced}' instructions in conditional branches for [{method.DeclaringType.Module.Assembly.Name}] method '{method.GetDisplayName ()}'.");
-
- // Even if the rewriter doesn't find any branches to fold the inlining above may have changed the method enough
- // such that we can now deduce its return value.
-
- if (method.ReturnType.MetadataType == MetadataType.Void) {
- // Method is fully processed and can't be const (since it doesn't return value) - so mark it as processed without const value
- StoreMethodAsProcessedAndRemoveFromQueue (stackNode, NonConstSentinel);
- continue;
- }
-
- //
- // Run the analyzer in case body change rewrote it to constant expression
- // Note that we have to run it always (even if we may not need the result ever) since it depends on the temporary inlining above
- // Otherwise we would have to remember the inlined code along with the method.
- //
- StoreMethodAsProcessedAndRemoveFromQueue (
- stackNode,
- AnalyzeMethodForConstantResult (method, reducer.FoldedInstructions) ?? NonConstSentinel);
+ switch (opCode.Code) {
+ case Code.Beq:
+ case Code.Beq_S:
+ case Code.Ceq:
+ return left == right;
+ case Code.Bne_Un:
+ case Code.Bne_Un_S:
+ return left != right;
+ case Code.Bge:
+ case Code.Bge_S:
+ return left >= right;
+ case Code.Bge_Un:
+ case Code.Bge_Un_S:
+ return (uint) left >= (uint) right;
+ case Code.Bgt:
+ case Code.Bgt_S:
+ case Code.Cgt:
+ return left > right;
+ case Code.Bgt_Un:
+ case Code.Bgt_Un_S:
+ return (uint) left > (uint) right;
+ case Code.Ble:
+ case Code.Ble_S:
+ return left <= right;
+ case Code.Ble_Un:
+ case Code.Ble_Un_S:
+ return (uint) left <= (uint) right;
+ case Code.Blt:
+ case Code.Blt_S:
+ case Code.Clt:
+ return left < right;
+ case Code.Blt_Un:
+ case Code.Blt_Un_S:
+ return (uint) left < (uint) right;
}
- Debug.Assert (_processingMethods.Count == 0);
+ throw new NotImplementedException (opCode.ToString ());
}
- Instruction? AnalyzeMethodForConstantResult (MethodDefinition method, Collection<Instruction>? instructions)
+ MethodResult? AnalyzeMethodForConstantResult (in CalleePayload callee, Stack<MethodDefinition> callStack)
{
+ MethodDefinition method = callee.Method;
+
if (method.ReturnType.MetadataType == MetadataType.Void)
return null;
@@ -281,7 +200,8 @@ namespace Mono.Linker.Steps
case MethodAction.ConvertToThrow:
return null;
case MethodAction.ConvertToStub:
- return CodeRewriterStep.CreateConstantResultInstruction (_context, method);
+ Instruction? constant = CodeRewriterStep.CreateConstantResultInstruction (_context, method);
+ return constant == null ? null : new MethodResult (constant, true);
}
if (method.IsIntrinsic () || method.NoInlining)
@@ -290,186 +210,323 @@ namespace Mono.Linker.Steps
if (!_context.IsOptimizationEnabled (CodeOptimizations.IPConstantPropagation, method))
return null;
- var analyzer = new ConstantExpressionMethodAnalyzer (_context, method, instructions ?? method.Body.Instructions);
- if (analyzer.Analyze ()) {
- return analyzer.Result;
+ var analyzer = new ConstantExpressionMethodAnalyzer (this);
+ if (analyzer.Analyze (callee, callStack)) {
+ return new MethodResult (analyzer.Result, analyzer.SideEffectFreeResult);
}
return null;
}
- /// <summary>
- /// Determines if a method has constant return value. If the method has not yet been processed it makes sure
- /// it is on the stack for processing and returns without a result.
- /// </summary>
- /// <param name="method">The method to determine result for</param>
- /// <param name="constantResultInstruction">If successfull and the method returns a constant value this will be set to the
- /// instruction with the constant value. If successfulll and the method doesn't have a constant value this is set to null.</param>
- /// <returns>
- /// true - if the method was analyzed and result is known
- /// constantResultInstruction is set to an instance if the method returns a constant, otherwise it's set to null
- /// false - if the method has not yet been analyzed and the caller should retry later
- /// </returns>
- bool TryGetConstantResultForMethod (MethodDefinition method, out Instruction? constantResultInstruction)
+
+ //
+ // Return expression with a value when method implementation can be
+ // interpreted during trimming
+ //
+ MethodResult? TryGetMethodCallResult (in CalleePayload callee)
{
- if (!_processedMethods.TryGetValue (method, out Instruction? methodValue)) {
- if (_processingMethods.TryGetValue (method, out var stackNode)) {
- // Method is already in the stack - not yet processed
- // Move it to the top of the stack
- _processingStack.Remove (stackNode);
- _processingStack.AddFirst (stackNode);
-
- // Note that stack version is not changing - we're just postponing work, not resolving anything.
- // There's no result available for this method, so return false.
- constantResultInstruction = null;
- return false;
+ _resursion_guard.Clear ();
+ return TryGetMethodCallResult (callee, _resursion_guard);
+ }
+
+ MethodResult? TryGetMethodCallResult (in CalleePayload callee, Stack<MethodDefinition> callStack)
+ {
+ MethodResult? value;
+
+ MethodDefinition method = callee.Method;
+ if (!method.HasParameters || callee.HasUnknownArguments) {
+ if (!_cache_method_results.TryGetValue (method, out value) && !IsDeepStack (callStack)) {
+ value = AnalyzeMethodForConstantResult (callee, callStack);
+ _cache_method_results.Add (method, value);
}
- // Method is not yet in the stack - add it there
- AddMethodForProcessing (method);
- constantResultInstruction = null;
- return false;
+ return value;
}
- if (methodValue == ProcessedUnchangedSentinel) {
- // Method has been processed and no changes has been made to it.
- // Also its value has not been needed yet. Now we need to know if it's constant, so run the analyzer on it
- var result = AnalyzeMethodForConstantResult (method, instructions: null);
- Debug.Assert (result is Instruction || result == null);
- _processedMethods[method] = result ?? NonConstSentinel;
- constantResultInstruction = result;
- } else if (methodValue == NonConstSentinel) {
- // Method was processed and found to not have a constant value
- constantResultInstruction = null;
- } else {
- // Method was already processed and found to have a constant value
- constantResultInstruction = methodValue;
+ return AnalyzeMethodForConstantResult (callee, callStack);
+
+ static bool IsDeepStack (Stack<MethodDefinition> callStack) => callStack.Count > 100;
+ }
+
+ Instruction? GetSizeOfResult (TypeReference type)
+ {
+ MethodDefinition? sizeOfImpl = null;
+
+ //
+ // sizeof (IntPtr) and sizeof (UIntPtr) are just aliases for IntPtr.Size and UIntPtr.Size
+ // which are simple static properties commonly overwritten. Instead of forcing C# code style
+ // we handle both via static get_Size method
+ //
+ if (type.MetadataType == MetadataType.UIntPtr) {
+ sizeOfImpl = (UIntPtrSize ??= FindSizeMethod (_context.TryResolve (type)));
+ } else if (type.MetadataType == MetadataType.IntPtr) {
+ sizeOfImpl = (IntPtrSize ??= FindSizeMethod (_context.TryResolve (type)));
}
- return true;
+ if (sizeOfImpl == null)
+ return null;
+
+ return TryGetMethodCallResult (new CalleePayload (sizeOfImpl, Array.Empty<Instruction> ()))?.Instruction;
}
- bool TryInlineBodyDependencies (ref BodyReducer reducer, bool treatUnprocessedDependenciesAsNonConst, out bool changed)
+ static Instruction? EvaluateIntrinsicCall (MethodReference method, Instruction[] arguments)
{
- changed = false;
- bool hasUnprocessedDependencies = false;
- var instructions = reducer.Body.Instructions;
- Instruction? targetResult;
+ //
+ // In theory any pure method could be executed via reflection but
+ // that would require loading all code path dependencies.
+ // For now we handle only few methods that help with core framework trimming
+ //
+ object? left, right;
+ if (method.DeclaringType.MetadataType == MetadataType.String) {
+ switch (method.Name) {
+ case "op_Equality":
+ case "op_Inequality":
+ case "Concat":
+ if (arguments.Length != 2)
+ return null;
+
+ if (!GetConstantValue (arguments[0], out left) ||
+ !GetConstantValue (arguments[1], out right))
+ return null;
+
+ if (left is string sleft && right is string sright) {
+ if (method.Name.Length == 6) // Concat case
+ return Instruction.Create (OpCodes.Ldstr, string.Concat (sleft, sright));
+
+ bool result = method.Name.Length == 11 ? sleft == sright : sleft != sright;
+ return Instruction.Create (OpCodes.Ldc_I4, result ? 1 : 0); // op_Equality / op_Inequality
+ }
- for (int i = 0; i < instructions.Count; ++i) {
- var instr = instructions[i];
- switch (instr.OpCode.Code) {
+ break;
+ }
+ }
- case Code.Call:
- case Code.Callvirt:
- var md = _context.TryResolve ((MethodReference) instr.Operand);
- if (md == null)
- break;
+ return null;
+ }
- if (md.CallingConvention == MethodCallingConvention.VarArg)
- break;
+ static Instruction[]? GetArgumentsOnStack (MethodDefinition method, Collection<Instruction> instructions, int index)
+ {
+ if (!method.HasParameters)
+ return Array.Empty<Instruction> ();
- bool explicitlyAnnotated = _context.Annotations.GetAction (md) == MethodAction.ConvertToStub;
+ Instruction[]? result = null;
+ for (int i = method.Parameters.Count, pos = 0; i != 0; --i, ++pos) {
+ Instruction instr = instructions[index - i];
+ if (!IsConstantValue (instr))
+ return null;
- // Allow inlining results of instance methods which are explicitly annotated
- // but don't allow inling results of any other instance method.
- // See https://github.com/dotnet/linker/issues/1243 for discussion as to why.
- // Also explicitly prevent inlining results of virtual methods.
- if (!md.IsStatic &&
- (md.IsVirtual || !explicitlyAnnotated))
- break;
+ if (result == null)
+ result = new Instruction[method.Parameters.Count];
- if (md == reducer.Body.Method) {
- // Special case for direct recursion - simply assume non-const value
- // since we can't tell.
- break;
- }
+ result[pos] = instr;
+ }
- if (!TryGetConstantResultForMethod (md, out targetResult)) {
- if (!treatUnprocessedDependenciesAsNonConst)
- hasUnprocessedDependencies = true;
- break;
- }
+ if (result != null && HasJumpIntoTargetRange (instructions, index - method.Parameters.Count + 1, index))
+ return null;
- if (targetResult == null || hasUnprocessedDependencies) {
- // Even if const is detected, there's no point in rewriting anything
- // if we've found unprocessed dependency since the results of this scan will
- // be thrown away (we back off and wait for the unprocessed dependency to be processed first).
- break;
- }
+ return result;
- //
- // Do simple arguments stack removal by replacing argument expressions with nops to hide
- // them for the constant evaluator. For cases which require full stack understanding the
- // logic won't work and will leave more opcodes on the stack and constant won't be propagated
- //
- int depth = md.Parameters.Count;
- if (!md.IsStatic)
- ++depth;
+ static bool IsConstantValue (Instruction instr)
+ {
+ switch (instr.OpCode.Code) {
+ case Code.Ldc_I4_0:
+ case Code.Ldc_I4_1:
+ case Code.Ldc_I4_2:
+ case Code.Ldc_I4_3:
+ case Code.Ldc_I4_4:
+ case Code.Ldc_I4_5:
+ case Code.Ldc_I4_6:
+ case Code.Ldc_I4_7:
+ case Code.Ldc_I4_8:
+ case Code.Ldc_I4:
+ case Code.Ldc_I4_S:
+ case Code.Ldc_I4_M1:
+ case Code.Ldc_I8:
+ case Code.Ldc_R4:
+ case Code.Ldc_R8:
+ case Code.Ldnull:
+ case Code.Ldstr:
+ return true;
+ }
- if (depth != 0)
- reducer.RewriteToNop (i - 1, depth);
+ return false;
+ }
+ }
- reducer.Rewrite (i, targetResult);
- changed = true;
- break;
+ static bool GetConstantValue (Instruction instruction, out object? value)
+ {
+ switch (instruction.OpCode.Code) {
+ case Code.Ldc_I4_0:
+ value = 0;
+ return true;
+ case Code.Ldc_I4_1:
+ value = 1;
+ return true;
+ case Code.Ldc_I4_2:
+ value = 2;
+ return true;
+ case Code.Ldc_I4_3:
+ value = 3;
+ return true;
+ case Code.Ldc_I4_4:
+ value = 4;
+ return true;
+ case Code.Ldc_I4_5:
+ value = 5;
+ return true;
+ case Code.Ldc_I4_6:
+ value = 6;
+ return true;
+ case Code.Ldc_I4_7:
+ value = 7;
+ return true;
+ case Code.Ldc_I4_8:
+ value = 8;
+ return true;
+ case Code.Ldc_I4_M1:
+ value = -1;
+ return true;
+ case Code.Ldc_I4:
+ value = (int) instruction.Operand;
+ return true;
+ case Code.Ldc_I4_S:
+ value = (int) (sbyte) instruction.Operand;
+ return true;
+ case Code.Ldc_I8:
+ value = (long) instruction.Operand;
+ return true;
+ case Code.Ldstr:
+ value = (string) instruction.Operand;
+ return true;
+ case Code.Ldnull:
+ value = null;
+ return true;
+ default:
+ value = null;
+ return false;
+ }
+ }
- case Code.Ldsfld:
- var ftarget = (FieldReference) instr.Operand;
- var field = _context.TryResolve (ftarget);
- if (field == null)
- break;
+ static MethodDefinition? FindSizeMethod (TypeDefinition? type)
+ {
+ if (type == null)
+ return null;
- if (_context.Annotations.TryGetFieldUserValue (field, out object? value)) {
- targetResult = CodeRewriterStep.CreateConstantResultInstruction (_context, field.FieldType, value);
- if (targetResult == null)
+ return type.Methods.First (l => !l.HasParameters && l.IsStatic && l.Name == "get_Size");
+ }
+
+ readonly struct CallInliner
+ {
+ readonly MethodBody body;
+ readonly UnreachableBlocksOptimizer optimizer;
+
+ public CallInliner (MethodBody body, UnreachableBlocksOptimizer optimizer)
+ {
+ this.body = body;
+ this.optimizer = optimizer;
+ }
+
+ public bool RewriteBody ()
+ {
+ bool changed = false;
+ LinkerILProcessor processor = body.GetLinkerILProcessor ();
+ Collection<Instruction> instrs = body.Instructions;
+
+ for (int i = 0; i < body.Instructions.Count; ++i) {
+ Instruction instr = instrs[i];
+ switch (instr.OpCode.Code) {
+
+ case Code.Call:
+ case Code.Callvirt:
+ MethodDefinition? md = optimizer._context.TryResolve ((MethodReference) instr.Operand);
+ if (md == null)
+ continue;
+
+ if (md.IsVirtual)
+ continue;
+
+ if (md.CallingConvention == MethodCallingConvention.VarArg)
break;
- reducer.Rewrite (i, targetResult);
- changed = true;
- }
- break;
- case Code.Sizeof:
- //
- // sizeof (IntPtr) and sizeof (UIntPtr) are just aliases for IntPtr.Size and UIntPtr.Size
- // which are simple static properties commonly overwritten. Instead of forcing C# code style
- // we handle both via static Size property
- //
- MethodDefinition? sizeOfImpl = null;
+ if (md.NoInlining)
+ break;
- var operand = (TypeReference) instr.Operand;
- if (operand.MetadataType == MetadataType.UIntPtr) {
- sizeOfImpl = (UIntPtrSize ??= FindSizeMethod (_context.TryResolve (operand)));
- } else if (operand.MetadataType == MetadataType.IntPtr) {
- sizeOfImpl = (IntPtrSize ??= FindSizeMethod (_context.TryResolve (operand)));
- }
+ var cpl = new CalleePayload (md, GetArgumentsOnStack (md, body.Instructions, i));
+ MethodResult? call_result = optimizer.TryGetMethodCallResult (cpl);
+ if (call_result is not MethodResult result)
+ break;
- if (sizeOfImpl != null) {
- if (!TryGetConstantResultForMethod (sizeOfImpl, out targetResult)) {
- if (!treatUnprocessedDependenciesAsNonConst)
- hasUnprocessedDependencies = true;
+ if (!result.IsSideEffectFree)
break;
- } else if (targetResult == null || hasUnprocessedDependencies) {
+
+ TypeDefinition methodType = md.DeclaringType;
+ if (methodType != body.Method.DeclaringType && !methodType.IsBeforeFieldInit) {
+ //
+ // Figuring out at this point if the explicit static ctor will be used is hard
+ //
+ optimizer._context.LogMessage ($"Cannot inline result of '{md.GetDisplayName ()}' call due to presence of static constructor");
break;
}
- reducer.Rewrite (i, targetResult);
+ if (!md.IsStatic) {
+ if (!md.HasParameters && CanInlineInstanceCall (instrs, i)) {
+ processor.Replace (i - 1, Instruction.Create (OpCodes.Nop));
+ processor.Replace (i, result.GetPrototype ()!);
+ changed = true;
+ }
+
+ continue;
+ }
+
+ if (md.HasParameters) {
+ if (!IsCalledWithoutSideEffects (md, instrs, i))
+ continue;
+
+ for (int p = 1; p <= md.Parameters.Count; ++p) {
+ processor.Replace (i - p, Instruction.Create (OpCodes.Nop));
+ }
+ }
+
+ processor.Replace (i, result.GetPrototype ());
changed = true;
- }
+ continue;
- break;
+ case Code.Sizeof:
+ var operand = (TypeReference) instr.Operand;
+ Instruction? value = optimizer.GetSizeOfResult (operand);
+ if (value != null) {
+ processor.Replace (i, value.GetPrototype ());
+ changed = true;
+ }
+
+ continue;
+ }
}
+
+ return changed;
}
- return !hasUnprocessedDependencies;
- }
+ bool CanInlineInstanceCall (Collection<Instruction> instructions, int index)
+ {
+ //
+ // Instance methods called on `this` have no side-effects
+ //
+ if (instructions[index - 1].OpCode.Code == Code.Ldarg_0)
+ return !body.Method.IsStatic;
- static MethodDefinition? FindSizeMethod (TypeDefinition? type)
- {
- if (type == null)
- return null;
+ // More cases can be added later
+ return false;
+ }
- return type.Methods.First (l => !l.HasParameters && l.IsStatic && l.Name == "get_Size");
+ static bool IsCalledWithoutSideEffects (MethodDefinition method, Collection<Instruction> instructions, int index)
+ {
+ for (int i = 1; i <= method.Parameters.Count; ++i) {
+ if (!IsSideEffectFreeLoad (instructions[index - i]))
+ return false;
+ }
+
+ return true;
+ }
}
struct BodyReducer
@@ -498,14 +555,21 @@ namespace Mono.Linker.Steps
public int InstructionsReplaced { get; set; }
- public Collection<Instruction>? FoldedInstructions { get; private set; }
+ Collection<Instruction>? FoldedInstructions { get; set; }
+
+ [MemberNotNull ("FoldedInstructions")]
+ [MemberNotNull ("mapping")]
+ void InitializeFoldedInstruction ()
+ {
+ FoldedInstructions = new Collection<Instruction> (Body.Instructions);
+ mapping = new Dictionary<Instruction, int> ();
+ }
public void Rewrite (int index, Instruction newInstruction)
{
- if (FoldedInstructions == null) {
- FoldedInstructions = new Collection<Instruction> (Body.Instructions);
- mapping = new Dictionary<Instruction, int> ();
- }
+ if (FoldedInstructions == null)
+ InitializeFoldedInstruction ();
+
Debug.Assert (mapping != null);
// Tracks mapping for replaced instructions for easier
@@ -526,10 +590,8 @@ namespace Mono.Linker.Steps
public void RewriteToNop (int index, int stackDepth)
{
- if (FoldedInstructions == null) {
- FoldedInstructions = new Collection<Instruction> (Body.Instructions);
- mapping = new Dictionary<Instruction, int> ();
- }
+ if (FoldedInstructions == null)
+ InitializeFoldedInstruction ();
int start_index;
for (start_index = index; start_index >= 0 && stackDepth > 0; --start_index) {
@@ -642,7 +704,7 @@ namespace Mono.Linker.Steps
public bool RewriteBody ()
{
if (FoldedInstructions == null)
- return false;
+ InitializeFoldedInstruction ();
if (!RemoveConditions ())
return false;
@@ -652,10 +714,7 @@ namespace Mono.Linker.Steps
return false;
var bodySweeper = new BodySweeper (Body, reachableInstrs, unreachableEH, context);
- if (!bodySweeper.Initialize ()) {
- context.LogMessage ($"Unreachable IL reduction is not supported for method '{Body.Method.GetDisplayName ()}'.");
- return false;
- }
+ bodySweeper.Initialize ();
bodySweeper.Process (conditionInstrsToRemove, out var nopInstructions);
InstructionsReplaced = bodySweeper.InstructionsReplaced;
@@ -676,6 +735,80 @@ namespace Mono.Linker.Steps
return true;
}
+ public bool ApplyTemporaryInlining (in UnreachableBlocksOptimizer optimizer)
+ {
+ bool changed = false;
+ var instructions = Body.Instructions;
+ Instruction? targetResult;
+
+ for (int i = 0; i < instructions.Count; ++i) {
+ var instr = instructions[i];
+ switch (instr.OpCode.Code) {
+
+ case Code.Call:
+ case Code.Callvirt:
+ var md = context.TryResolve ((MethodReference) instr.Operand);
+ if (md == null)
+ break;
+
+ // Not supported
+ if (md.IsVirtual || md.CallingConvention == MethodCallingConvention.VarArg)
+ break;
+
+ Instruction[]? args = GetArgumentsOnStack (md, FoldedInstructions ?? instructions, i);
+ targetResult = args?.Length > 0 && md.IsStatic ? EvaluateIntrinsicCall (md, args) : null;
+
+ if (targetResult == null)
+ targetResult = optimizer.TryGetMethodCallResult (new CalleePayload (md, args))?.Instruction;
+
+ if (targetResult == null)
+ break;
+
+ //
+ // Do simple arguments stack removal by replacing argument expressions with nops. For cases
+ // that require full stack understanding the logic won't work and will leave more opcodes
+ // on the stack and constant won't be propagated
+ //
+ int depth = args?.Length ?? 0;
+ if (!md.IsStatic)
+ ++depth;
+
+ if (depth != 0)
+ RewriteToNop (i - 1, depth);
+
+ Rewrite (i, targetResult);
+ changed = true;
+ break;
+
+ case Code.Ldsfld:
+ var ftarget = (FieldReference) instr.Operand;
+ var field = context.TryResolve (ftarget);
+ if (field == null)
+ break;
+
+ if (context.Annotations.TryGetFieldUserValue (field, out object? value)) {
+ targetResult = CodeRewriterStep.CreateConstantResultInstruction (context, field.FieldType, value);
+ if (targetResult == null)
+ break;
+ Rewrite (i, targetResult);
+ changed = true;
+ }
+ break;
+
+ case Code.Sizeof:
+ var operand = (TypeReference) instr.Operand;
+ targetResult = optimizer.GetSizeOfResult (operand);
+ if (targetResult != null) {
+ Rewrite (i, targetResult);
+ changed = true;
+ }
+ break;
+ }
+ }
+
+ return changed;
+ }
+
void RemoveUnreachableInstructions (BitArray reachable)
{
LinkerILProcessor processor = Body.GetLinkerILProcessor ();
@@ -963,57 +1096,6 @@ namespace Mono.Linker.Steps
GetConstantValue (FoldedInstructions[index - 1], out right);
}
- static bool GetConstantValue (Instruction instruction, out object? value)
- {
- switch (instruction.OpCode.Code) {
- case Code.Ldc_I4_0:
- value = 0;
- return true;
- case Code.Ldc_I4_1:
- value = 1;
- return true;
- case Code.Ldc_I4_2:
- value = 2;
- return true;
- case Code.Ldc_I4_3:
- value = 3;
- return true;
- case Code.Ldc_I4_4:
- value = 4;
- return true;
- case Code.Ldc_I4_5:
- value = 5;
- return true;
- case Code.Ldc_I4_6:
- value = 6;
- return true;
- case Code.Ldc_I4_7:
- value = 7;
- return true;
- case Code.Ldc_I4_8:
- value = 8;
- return true;
- case Code.Ldc_I4_M1:
- value = -1;
- return true;
- case Code.Ldc_I4:
- value = (int) instruction.Operand;
- return true;
- case Code.Ldc_I4_S:
- value = (int) (sbyte) instruction.Operand;
- return true;
- case Code.Ldc_I8:
- value = (long) instruction.Operand;
- return true;
- case Code.Ldnull:
- value = null;
- return true;
- default:
- value = null;
- return false;
- }
- }
-
static bool IsPairedStlocLdloc (Instruction first, Instruction second)
{
switch (first.OpCode.Code) {
@@ -1036,47 +1118,6 @@ namespace Mono.Linker.Steps
return false;
}
- static bool IsComparisonAlwaysTrue (OpCode opCode, int left, int right)
- {
- switch (opCode.Code) {
- case Code.Beq:
- case Code.Beq_S:
- case Code.Ceq:
- return left == right;
- case Code.Bne_Un:
- case Code.Bne_Un_S:
- return left != right;
- case Code.Bge:
- case Code.Bge_S:
- return left >= right;
- case Code.Bge_Un:
- case Code.Bge_Un_S:
- return (uint) left >= (uint) right;
- case Code.Bgt:
- case Code.Bgt_S:
- case Code.Cgt:
- return left > right;
- case Code.Bgt_Un:
- case Code.Bgt_Un_S:
- return (uint) left > (uint) right;
- case Code.Ble:
- case Code.Ble_S:
- return left <= right;
- case Code.Ble_Un:
- case Code.Ble_Un_S:
- return (uint) left <= (uint) right;
- case Code.Blt:
- case Code.Blt_S:
- case Code.Clt:
- return left < right;
- case Code.Blt_Un:
- case Code.Blt_Un_S:
- return (uint) left < (uint) right;
- }
-
- throw new NotImplementedException (opCode.ToString ());
- }
-
static bool IsConstantBranch (OpCode opCode, int operand)
{
switch (opCode.Code) {
@@ -1094,27 +1135,7 @@ namespace Mono.Linker.Steps
bool IsJumpTargetRange (int firstInstr, int lastInstr)
{
Debug.Assert (FoldedInstructions != null);
- foreach (var instr in FoldedInstructions) {
- switch (instr.OpCode.FlowControl) {
- case FlowControl.Branch:
- case FlowControl.Cond_Branch:
- if (instr.Operand is Instruction target) {
- var index = GetInstructionIndex (target);
- if (index >= firstInstr && index <= lastInstr)
- return true;
- } else {
- foreach (var rtarget in (Instruction[]) instr.Operand) {
- var index = GetInstructionIndex (rtarget);
- if (index >= firstInstr && index <= lastInstr)
- return true;
- }
- }
-
- break;
- }
- }
-
- return false;
+ return HasJumpIntoTargetRange (FoldedInstructions, firstInstr, lastInstr, GetInstructionIndex);
}
}
@@ -1145,7 +1166,7 @@ namespace Mono.Linker.Steps
public int InstructionsReplaced { get; set; }
- public bool Initialize ()
+ public void Initialize ()
{
var instrs = body.Instructions;
@@ -1172,7 +1193,6 @@ namespace Mono.Linker.Steps
}
ilprocessor = body.GetLinkerILProcessor ();
- return true;
}
public void Process (List<int>? conditionInstrsToRemove, out List<Instruction>? sentinelNops)
@@ -1335,81 +1355,62 @@ namespace Mono.Linker.Steps
return null;
}
-
- static bool IsSideEffectFreeLoad (Instruction instr)
- {
- switch (instr.OpCode.Code) {
- case Code.Ldarg:
- case Code.Ldloc:
- case Code.Ldloc_0:
- case Code.Ldloc_1:
- case Code.Ldloc_2:
- case Code.Ldloc_3:
- case Code.Ldloc_S:
- case Code.Ldc_I4_0:
- case Code.Ldc_I4_1:
- case Code.Ldc_I4_2:
- case Code.Ldc_I4_3:
- case Code.Ldc_I4_4:
- case Code.Ldc_I4_5:
- case Code.Ldc_I4_6:
- case Code.Ldc_I4_7:
- case Code.Ldc_I4_8:
- case Code.Ldc_I4:
- case Code.Ldc_I4_S:
- case Code.Ldc_I4_M1:
- case Code.Ldc_I8:
- case Code.Ldc_R4:
- case Code.Ldc_R8:
- case Code.Ldnull:
- case Code.Ldstr:
- return true;
- }
-
- return false;
- }
}
struct ConstantExpressionMethodAnalyzer
{
- readonly MethodDefinition method;
- readonly Collection<Instruction> instructions;
readonly LinkContext context;
+ readonly UnreachableBlocksOptimizer optimizer;
Stack<Instruction>? stack_instr;
Dictionary<int, Instruction>? locals;
- public ConstantExpressionMethodAnalyzer (LinkContext context, MethodDefinition method)
+ public ConstantExpressionMethodAnalyzer (UnreachableBlocksOptimizer optimizer)
{
- this.context = context;
- this.method = method;
- instructions = method.Body.Instructions;
+ this.optimizer = optimizer;
+ this.context = optimizer._context;
stack_instr = null;
locals = null;
Result = null;
+ SideEffectFreeResult = true;
}
- public ConstantExpressionMethodAnalyzer (LinkContext context, MethodDefinition method, Collection<Instruction> instructions)
- : this (context, method)
- {
- this.instructions = instructions;
- }
-
+ //
+ // Single expression that is representing the evaluation result with the specific
+ // callee arguments
+ //
public Instruction? Result { get; private set; }
+ //
+ // Returns true when the method evaluation with specific arguments does not cause
+ // any observable side effect (e.g. possible NRE, field access, etc)
+ //
+ public bool SideEffectFreeResult { get; private set; }
+
[MemberNotNullWhen (true, "Result")]
- public bool Analyze ()
+ public bool Analyze (in CalleePayload callee, Stack<MethodDefinition> callStack)
{
- var body = method.Body;
- if (body.HasExceptionHandlers)
- return false;
+ MethodDefinition method = callee.Method;
+ Instruction[]? arguments = callee.Arguments;
+ Collection<Instruction> instructions = callee.Method.Body.Instructions;
+ MethodBody body = method.Body;
VariableReference vr;
Instruction? jmpTarget = null;
Instruction? linstr;
+ object? left, right, operand;
+
+ //
+ // We could implement a full-blown interpreter here but for now, it handles
+ // cases used in runtime libraries
+ //
+ for (int i = 0; i < instructions.Count; ++i) {
+ var instr = instructions[i];
- foreach (var instr in instructions) {
if (jmpTarget != null) {
+ //
+ // Handles both backward and forward jumps
+ //
if (instr != jmpTarget)
continue;
@@ -1418,11 +1419,11 @@ namespace Mono.Linker.Steps
switch (instr.OpCode.Code) {
case Code.Nop:
+ case Code.Volatile:
continue;
case Code.Pop:
- if (stack_instr == null)
- Debug.Fail ("Invalid IL?");
- stack_instr.Pop ();
+ Debug.Assert (stack_instr != null, "invalid il?");
+ stack_instr?.Pop ();
continue;
case Code.Br_S:
@@ -1430,6 +1431,60 @@ namespace Mono.Linker.Steps
jmpTarget = (Instruction) instr.Operand;
continue;
+ case Code.Brfalse_S:
+ case Code.Brfalse: {
+ if (!GetOperandConstantValue (out operand))
+ return false;
+
+ if (operand is int oint) {
+ if (oint == 0)
+ jmpTarget = (Instruction) instr.Operand;
+
+ continue;
+ }
+
+ return false;
+ }
+
+ case Code.Brtrue_S:
+ case Code.Brtrue: {
+ if (!GetOperandConstantValue (out operand))
+ return false;
+
+ if (operand is int oint) {
+ if (oint == 1)
+ jmpTarget = (Instruction) instr.Operand;
+
+ continue;
+ }
+
+ return false;
+ }
+
+ case Code.Beq:
+ case Code.Beq_S:
+ case Code.Bne_Un:
+ case Code.Bne_Un_S:
+ case Code.Bge:
+ case Code.Bge_S:
+ case Code.Bge_Un:
+ case Code.Bge_Un_S:
+ case Code.Bgt:
+ case Code.Bgt_S:
+ case Code.Bgt_Un:
+ case Code.Bgt_Un_S:
+ case Code.Ble:
+ case Code.Ble_S:
+ case Code.Ble_Un:
+ case Code.Ble_Un_S:
+ case Code.Blt:
+ case Code.Blt_S:
+ case Code.Blt_Un:
+ case Code.Blt_Un_S:
+ if (EvaluateConditionalJump (instr, out jmpTarget))
+ continue;
+ return false;
+
case Code.Ldc_I4:
case Code.Ldc_I4_S:
case Code.Ldc_I4_0:
@@ -1504,8 +1559,154 @@ namespace Mono.Linker.Steps
StoreToLocals (vr.Index);
continue;
- // TODO: handle simple conversions
- //case Code.Conv_I:
+ case Code.Ldarg_0:
+ if (!method.IsStatic) {
+ PushOnStack (instr);
+ continue;
+ }
+
+ linstr = GetArgumentValue (arguments, 0);
+ if (linstr == null)
+ return false;
+
+ PushOnStack (linstr);
+ continue;
+
+ case Code.Ldarg_1:
+ if (!method.IsStatic)
+ return false;
+
+ linstr = GetArgumentValue (arguments, 1);
+ if (linstr == null)
+ return false;
+
+ PushOnStack (linstr);
+ continue;
+
+ case Code.Ldsfld: {
+ var ftarget = (FieldReference) instr.Operand;
+ FieldDefinition? field = context.TryResolve (ftarget);
+ if (field == null)
+ return false;
+
+ if (context.Annotations.TryGetFieldUserValue (field, out object? value)) {
+ linstr = CodeRewriterStep.CreateConstantResultInstruction (context, field.FieldType, value);
+ if (linstr == null)
+ return false;
+ } else {
+ SideEffectFreeResult = false;
+ linstr = instr;
+ }
+
+ PushOnStack (linstr);
+ continue;
+ }
+
+ case Code.Ceq: {
+ if (!GetOperandsConstantValues (out right, out left))
+ return false;
+
+ if (left is int lint && right is int rint) {
+ PushOnStack (Instruction.Create (OpCodes.Ldc_I4, lint == rint ? 1 : 0));
+ continue;
+ }
+
+ if (left is long llong && right is long rlong) {
+ PushOnStack (Instruction.Create (OpCodes.Ldc_I4, llong == rlong ? 1 : 0));
+ continue;
+ }
+
+ return false;
+ }
+
+ case Code.Conv_I8: {
+ if (!GetOperandConstantValue (out operand))
+ return false;
+
+ if (operand is int oint) {
+ PushOnStack (Instruction.Create (OpCodes.Ldc_I8, (long) oint));
+ continue;
+ }
+
+ // TODO: Handle more types
+ return false;
+ }
+
+ case Code.Call:
+ case Code.Callvirt: {
+ MethodReference mr = (MethodReference) instr.Operand;
+ MethodDefinition? md = optimizer._context.TryResolve (mr);
+ if (md == null || md == method)
+ return false;
+
+ if (md.IsVirtual)
+ return false;
+
+ Instruction[]? args;
+ if (!md.HasParameters) {
+ args = Array.Empty<Instruction> ();
+ } else {
+ //
+ // Don't need to check for ref/out because ldloca like instructions are not supported
+ //
+ args = GetArgumentsOnStack (md);
+ if (args == null)
+ return false;
+ }
+
+ if (md.ReturnType.MetadataType == MetadataType.Void) {
+ // For now consider all void methods as side-effect causing
+ SideEffectFreeResult = false;
+ continue;
+ }
+
+ if (!md.IsStatic && !CanEvaluateInstanceMethodCall (method))
+ return false;
+
+ //
+ // Evaluate known framework methods
+ //
+ if (args.Length > 0) {
+ linstr = EvaluateIntrinsicCall (md, args);
+ if (linstr != null) {
+ PushOnStack (linstr);
+ continue;
+ }
+ }
+
+ //
+ // Guard against stack overflow on recursive calls. This could be turned into
+ // a warning if we check arguments too
+ //
+ if (callStack.Contains (md))
+ return false;
+
+ callStack.Push (method);
+ MethodResult? call_result = optimizer.TryGetMethodCallResult (new CalleePayload (md, args), callStack);
+ if (!callStack.TryPop (out _))
+ return false;
+
+ if (call_result is MethodResult result) {
+ if (!result.IsSideEffectFree)
+ SideEffectFreeResult = false;
+
+ PushOnStack (result.Instruction);
+ continue;
+ }
+
+ return false;
+ }
+
+ case Code.Sizeof: {
+ var type = (TypeReference) instr.Operand;
+ linstr = optimizer.GetSizeOfResult (type);
+ if (linstr != null) {
+ PushOnStack (linstr);
+ continue;
+ }
+
+ return false;
+ }
case Code.Ret:
if (ConvertStackToResult ())
@@ -1520,6 +1721,45 @@ namespace Mono.Linker.Steps
return false;
}
+ bool CanEvaluateInstanceMethodCall (MethodDefinition context)
+ {
+ if (stack_instr == null || !stack_instr.TryPop (out Instruction? instr))
+ return false;
+
+ switch (instr.OpCode.Code) {
+ case Code.Ldarg_0:
+ if (!context.IsStatic)
+ return true;
+
+ goto default;
+ default:
+ // We are not inlining hence can evaluate anything and decide later
+ // how to handle sitation when the result is not deterministic
+ SideEffectFreeResult = false;
+ return true;
+ }
+ }
+
+ bool EvaluateConditionalJump (Instruction instr, out Instruction? target)
+ {
+ if (!GetOperandsConstantValues (out object? right, out object? left)) {
+ target = null;
+ return false;
+ }
+
+ if (left is int lint && right is int rint) {
+ if (IsComparisonAlwaysTrue (instr.OpCode, lint, rint))
+ target = (Instruction) instr.Operand;
+ else
+ target = null;
+
+ return true;
+ }
+
+ target = null;
+ return false;
+ }
+
[MemberNotNullWhen (true, "Result")]
bool ConvertStackToResult ()
{
@@ -1554,6 +1794,28 @@ namespace Mono.Linker.Steps
return false;
}
+ static Instruction? GetArgumentValue (Instruction[]? arguments, int index)
+ {
+ if (arguments == null)
+ return null;
+
+ return index < arguments.Length ? arguments[index] : null;
+ }
+
+ Instruction[]? GetArgumentsOnStack (MethodDefinition method)
+ {
+ int length = method.Parameters.Count;
+ Debug.Assert (length != 0);
+ if (stack_instr?.Count < length)
+ return null;
+
+ var result = new Instruction[length];
+ while (length != 0)
+ result[--length] = stack_instr!.Pop ();
+
+ return result;
+ }
+
Instruction? GetLocalsValue (int index, MethodBody body)
{
if (locals != null && locals.TryGetValue (index, out Instruction? instruction))
@@ -1566,6 +1828,58 @@ namespace Mono.Linker.Steps
return CodeRewriterStep.CreateConstantResultInstruction (context, body.Variables[index].VariableType);
}
+ bool GetOperandConstantValue ([NotNullWhen (true)] out object? value)
+ {
+ if (stack_instr == null) {
+ value = null;
+ return false;
+ }
+
+ Instruction? instr;
+ if (!stack_instr.TryPop (out instr)) {
+ value = null;
+ return false;
+ }
+
+ return GetConstantValue (instr, out value);
+ }
+
+ bool GetOperandsConstantValues ([NotNullWhen (true)] out object? left, [NotNullWhen (true)] out object? right)
+ {
+ if (stack_instr == null) {
+ left = right = null;
+ return false;
+ }
+
+ Instruction? instr;
+ if (!stack_instr.TryPop (out instr)) {
+ left = right = null;
+ return false;
+ }
+
+ if (instr == null) {
+ left = right = null;
+ return false;
+ }
+
+ if (!GetConstantValue (instr, out left)) {
+ left = right = null;
+ return false;
+ }
+
+ if (!stack_instr.TryPop (out instr)) {
+ left = right = null;
+ return false;
+ }
+
+ if (instr is null) {
+ left = right = null;
+ return false;
+ }
+
+ return GetConstantValue (instr, out right);
+ }
+
void PushOnStack (Instruction instruction)
{
if (stack_instr == null)
@@ -1584,5 +1898,15 @@ namespace Mono.Linker.Steps
locals[index] = stack_instr.Pop ();
}
}
+
+ readonly record struct CalleePayload (MethodDefinition Method, Instruction[]? Arguments = null)
+ {
+ public bool HasUnknownArguments => Arguments is null;
+ }
+
+ readonly record struct MethodResult (Instruction Instruction, bool IsSideEffectFree)
+ {
+ public Instruction GetPrototype () => Instruction.GetPrototype ();
+ }
}
}
diff --git a/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutions.cs b/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutions.cs
index bb93e90c0..23b8f57b0 100644
--- a/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutions.cs
+++ b/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutions.cs
@@ -9,16 +9,18 @@ namespace Mono.Linker.Tests.Cases.FeatureSettings
[SetupLinkerArgument ("--enable-opt", "ipconstprop")]
public class FeatureSubstitutions
{
- [Kept]
static bool IsOptionalFeatureEnabled {
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.0",
- "ret",
- })]
get;
}
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "call System.Void Mono.Linker.Tests.Cases.FeatureSettings.FeatureSubstitutions::TestOptionalFeature()",
+ "nop",
+ "ldc.i4.1",
+ "pop",
+ "ret",
+ })]
public static void Main ()
{
TestOptionalFeature ();
@@ -26,11 +28,16 @@ namespace Mono.Linker.Tests.Cases.FeatureSettings
}
[Kept]
- [ExpectBodyModified]
[ExpectedInstructionSequence (new[] {
- "call",
- "brfalse",
- "call",
+ "nop",
+ "ldc.i4.0",
+ "stloc.0",
+ "ldloc.0",
+ "brfalse.s il_6",
+ "nop",
+ "call System.Void Mono.Linker.Tests.Cases.FeatureSettings.FeatureSubstitutions::UseFallback()",
+ "nop",
+ "nop",
"ret",
})]
static void TestOptionalFeature ()
@@ -51,13 +58,7 @@ namespace Mono.Linker.Tests.Cases.FeatureSettings
{
}
- [Kept]
static bool IsDefaultFeatureEnabled {
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.1",
- "ret",
- })]
get => throw new NotImplementedException ();
}
}
diff --git a/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutionsNested.cs b/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutionsNested.cs
index 741f2ad58..b9a4f1c13 100644
--- a/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutionsNested.cs
+++ b/test/Mono.Linker.Tests.Cases/FeatureSettings/FeatureSubstitutionsNested.cs
@@ -19,6 +19,20 @@ namespace Mono.Linker.Tests.Cases.FeatureSettings
[KeptResource ("ResourceFileRemoveWhenFalse.txt")]
public class FeatureSubstitutionsNested
{
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4.1",
+ "pop",
+ "ldc.i4.0",
+ "pop",
+ "ldc.i4.1",
+ "pop",
+ "ldc.i4.0",
+ "pop",
+ "ldsfld System.Boolean Mono.Linker.Tests.Cases.FeatureSettings.FeatureSubstitutionsNested::FieldConditionField",
+ "pop",
+ "ret",
+ })]
public static void Main ()
{
GlobalConditionMethod ();
@@ -28,41 +42,21 @@ namespace Mono.Linker.Tests.Cases.FeatureSettings
_ = FieldConditionField;
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.1",
- "ret",
- })]
static bool GlobalConditionMethod ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.0",
- "ret",
- })]
static bool AssemblyConditionMethod ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.1",
- "ret",
- })]
static bool TypeConditionMethod ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.0",
- "ret",
- })]
static bool MethodConditionMethod ()
{
throw new NotImplementedException ();
diff --git a/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs b/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs
index e9e7ecf29..8cc979367 100644
--- a/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs
+++ b/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs
@@ -137,8 +137,7 @@ namespace Mono.Linker.Tests.Cases.Libraries
[Kept]
public class SubstitutionsTest
{
- [Kept]
- private static bool FalseProp { [Kept] get { return false; } }
+ private static bool FalseProp { get { return false; } }
[Kept]
[ExpectBodyModified]
diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs b/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs
index df86662f2..07f3d9dd7 100644
--- a/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs
+++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs
@@ -7,6 +7,42 @@ namespace Mono.Linker.Tests.Cases.Substitutions
[SetupLinkerSubstitutionFile ("StubBody.xml")]
public class StubBody
{
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "newobj System.Void Mono.Linker.Tests.Cases.Substitutions.StubBody::.ctor()",
+ "pop",
+ "ldc.i4.5",
+ "newobj System.Void Mono.Linker.Tests.Cases.Substitutions.StubBody/NestedType::.ctor(System.Int32)",
+ "pop",
+ "ldnull",
+ "pop",
+ "ldc.i4.0",
+ "pop",
+ "ldc.i4.0",
+ "pop",
+ "call System.Decimal Mono.Linker.Tests.Cases.Substitutions.StubBody::TestMethod_4()",
+ "pop",
+ "ldc.i4.0",
+ "pop",
+ "call System.Void Mono.Linker.Tests.Cases.Substitutions.StubBody::TestMethod_6()",
+ "nop",
+ "ldc.r8 0",
+ "pop",
+ "ldc.i4.5",
+ "call T Mono.Linker.Tests.Cases.Substitutions.StubBody::TestMethod_8<System.Int32>(T)",
+ "pop",
+ "ldc.r4 0",
+ "pop",
+ "ldc.i8 0x0",
+ "pop",
+ "ldnull",
+ "pop",
+ "ldnull",
+ "pop",
+ "ldnull",
+ "pop",
+ "ret",
+ })]
public static void Main ()
{
new StubBody ();
@@ -50,31 +86,16 @@ namespace Mono.Linker.Tests.Cases.Substitutions
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldnull",
- "ret",
- })]
static string TestMethod_1 ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.0",
- "ret",
- })]
static byte TestMethod_2 ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.0",
- "ret",
- })]
static char TestMethod_3 ()
{
throw new NotImplementedException ();
@@ -93,11 +114,6 @@ namespace Mono.Linker.Tests.Cases.Substitutions
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i4.0",
- "ret",
- })]
static bool TestMethod_5 ()
{
throw new NotImplementedException ();
@@ -112,12 +128,6 @@ namespace Mono.Linker.Tests.Cases.Substitutions
TestMethod_5 ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.r8 0",
- "ret",
- })]
- [ExpectLocalsModified]
static double TestMethod_7 ()
{
double d = 1.1;
@@ -137,53 +147,27 @@ namespace Mono.Linker.Tests.Cases.Substitutions
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.r4 0",
- "ret",
- })]
- [ExpectLocalsModified]
static float TestMethod_9 ()
{
float f = 1.1f;
return f;
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldc.i8 0x0",
- "ret",
- })]
static ulong TestMethod_10 ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldnull",
- "ret",
- })]
static long[] TestMethod_11 ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldnull",
- "ret",
- })]
static object TestMethod_12 ()
{
throw new NotImplementedException ();
}
- [Kept]
- [ExpectedInstructionSequence (new[] {
- "ldnull",
- "ret",
- })]
static System.Collections.Generic.List<int> TestMethod_13 ()
{
throw new NotImplementedException ();
diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs
index 7c1974f94..ce067938e 100644
--- a/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs
+++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.CompilerServices;
using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Mono.Linker.Tests.Cases.Expectations.Metadata;
@@ -28,6 +29,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldstr 'abcd'",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static string TestMethod_1 ()
{
throw new NotImplementedException ();
@@ -38,6 +40,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i4 0x4",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static byte TestMethod_2 ()
{
throw new NotImplementedException ();
@@ -48,6 +51,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i4 0x78",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static char TestMethod_3 ()
{
throw new NotImplementedException ();
@@ -58,6 +62,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i4 0x3",
"ret"
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static sbyte TestMethod_4 ()
{
throw new NotImplementedException ();
@@ -68,6 +73,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i4.1",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static bool TestMethod_5 ()
{
throw new NotImplementedException ();
@@ -78,6 +84,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i4.1",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static bool TestMethod_6 ()
{
throw new NotImplementedException ();
@@ -88,6 +95,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.r8 2.5",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static double TestMethod_7 ()
{
throw new NotImplementedException ();
@@ -98,6 +106,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i4 0xfffffffd",
"ret"
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static int TestMethod_8 ()
{
throw new NotImplementedException ();
@@ -108,6 +117,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.r4 6",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static float TestMethod_9 ()
{
throw new NotImplementedException ();
@@ -118,6 +128,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i8 0x1e240",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static ulong TestMethod_10 ()
{
throw new NotImplementedException ();
@@ -128,6 +139,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i8 0xfffffffffffffc18",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static long TestMethod_11 ()
{
throw new NotImplementedException ();
@@ -138,6 +150,7 @@ namespace Mono.Linker.Tests.Cases.Substitutions
"ldc.i4 0xffffffff",
"ret",
})]
+ [MethodImplAttribute (MethodImplOptions.NoInlining)]
static uint TestMethod_12 ()
{
throw new NotImplementedException ();
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs
index 1c64776d5..e4dc6110c 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs
@@ -66,10 +66,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
NeverReached_1 ();
}
- [Kept]
static int Property {
- [Kept]
- [ExpectBodyModified]
get {
return field;
}
@@ -99,9 +96,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
}
- [Kept]
static int PropagateProperty {
- [Kept]
get {
return Property;
}
@@ -198,7 +193,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
static int depth = 0;
- [Kept]
static bool IsTrue ()
{
return true;
@@ -250,7 +244,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
static int depth = 0;
- [Kept]
static bool IsTrue ()
{
return true;
@@ -325,16 +318,16 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
static class DeepConstant
{
- [Kept] static bool Method1 () => Method2 ();
- [Kept] static bool Method2 () => Method3 ();
- [Kept] static bool Method3 () => Method4 ();
- [Kept] static bool Method4 () => Method5 ();
- [Kept] static bool Method5 () => Method6 ();
- [Kept] static bool Method6 () => Method7 ();
- [Kept] static bool Method7 () => Method8 ();
- [Kept] static bool Method8 () => Method9 ();
- [Kept] static bool Method9 () => Method10 ();
- [Kept] static bool Method10 () => false;
+ static bool Method1 () => Method2 ();
+ static bool Method2 () => Method3 ();
+ static bool Method3 () => Method4 ();
+ static bool Method4 () => Method5 ();
+ static bool Method5 () => Method6 ();
+ static bool Method6 () => Method7 ();
+ static bool Method7 () => Method8 ();
+ static bool Method8 () => Method9 ();
+ static bool Method9 () => Method10 ();
+ static bool Method10 () => false;
static void NotReached () { }
[Kept] static void Reached () { }
@@ -382,10 +375,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
}
}
- [Kept]
static bool CollisionProperty {
- [Kept]
- [ExpectBodyModified]
get {
// Need to call something with constant value to force processing of this method
_ = Property;
@@ -422,7 +412,12 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
}
[Kept]
- [ExpectBodyModified]
+ [ExpectedInstructionSequence (new[] {
+ "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.BodiesWithSubstitutions::get_NoInliningProperty()",
+ "brfalse.s il_7",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.BodiesWithSubstitutions::NoInlining_Reached()",
+ "ret",
+ })]
static void TestSubstitutionOnNoInlining ()
{
if (NoInliningProperty)
@@ -435,17 +430,19 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
static void NoInlining_Reached () { }
static void NoInlining_NeverReached () { }
- [Kept]
static bool IntrinsicProperty {
- [Kept]
- [ExpectBodyModified]
[Intrinsic]
[KeptAttributeAttribute (typeof (IntrinsicAttribute))]
get { return true; }
}
[Kept]
- [ExpectBodyModified]
+ [ExpectedInstructionSequence (new[] {
+ "ldc.i4.0",
+ "brfalse.s il_3",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.BodiesWithSubstitutions::Intrinsic_Reached()",
+ "ret",
+ })]
static void TestSubstitutionOnIntrinsic ()
{
if (IntrinsicProperty)
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs
index e759c0e28..ea7d62c5f 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs
@@ -24,20 +24,20 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
"nop",
"ldarg.0",
"isinst System.Type",
- "brtrue.s il_19",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ComplexConditions::get_IsDynamicCodeSupported()",
+ "brtrue.s il_15",
+ "ldc.i4.1",
"pop",
"ldarg.0",
"pop",
"ldnull",
"ldnull",
"cgt.un",
- "br.s il_17",
- "br.s il_1a",
+ "br.s il_13",
+ "br.s il_16",
"ldc.i4.1",
"stloc.0",
"ldloc.0",
- "brfalse.s il_24",
+ "brfalse.s il_20",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.ComplexConditions::Reached_1()",
"nop",
"ret",
@@ -55,7 +55,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
#else
[ExpectedInstructionSequence (new[] {
"nop",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ComplexConditions::get_IsDynamicCodeSupported()",
+ "ldc.i4.1",
"stloc.1",
"ldloc.1",
"pop",
@@ -63,15 +63,15 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
"stloc.0",
"ldarg.0",
"ldc.i4.2",
- "beq.s il_15",
+ "beq.s il_11",
"ldarg.0",
"ldc.i4.3",
"ceq",
- "br.s il_16",
+ "br.s il_12",
"ldc.i4.1",
"stloc.2",
"ldloc.2",
- "brfalse.s il_20",
+ "brfalse.s il_1c",
"newobj System.Void System.ArgumentException::.ctor()",
"throw",
"newobj System.Void System.ApplicationException::.ctor()",
@@ -90,9 +90,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
throw new ApplicationException ();
}
- [Kept]
static bool IsDynamicCodeSupported {
- [Kept]
get {
return true;
}
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/DeadVariables.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/DeadVariables.cs
index 80a3908f9..94468e88a 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/DeadVariables.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/DeadVariables.cs
@@ -65,9 +65,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
return 2;
}
- [Kept]
static bool AlwaysTrue {
- [Kept]
get {
return true;
}
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/InstanceMethodSubstitutions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/InstanceMethodSubstitutions.cs
index d204ca55a..2e5364dce 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/InstanceMethodSubstitutions.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/InstanceMethodSubstitutions.cs
@@ -35,17 +35,23 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
return _isEnabledField;
}
- [Kept]
InstanceMethodSubstitutions GetInstance ()
{
return null;
}
- [Kept]
- static bool PropFalse { [Kept] get { return false; } }
+ static bool PropFalse { get { return false; } }
[Kept]
- [ExpectBodyModified]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldnull",
+ "callvirt System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.InstanceMethodSubstitutions::IsEnabled()",
+ "brfalse.s il_9",
+ "ldarg.0",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.InstanceMethodSubstitutions::CallOnInstance_Reached()",
+ "ret",
+ })]
void TestCallOnInstance ()
{
@@ -57,8 +63,8 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.InstanceMethodSubstitutions::get_PropFalse()",
- "brfalse.s il_7",
+ "ldc.i4.0",
+ "brfalse.s il_3",
"ldc.i4.1",
"ret"
})]
@@ -99,15 +105,23 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
}
[Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4.1",
+ "pop",
+ "ldarg.0",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.InstanceMethodSubstitutions::InstanceMethodWithoutSubstitution_Reached1()",
+ "ret",
+ })]
void TestInstanceMethodWithoutSubstitution ()
{
- if (InstanceMethodWithoutSubstitution ())
+ InstanceMethodSubstitutions ims = this;
+ if (ims.InstanceMethodWithoutSubstitution ())
InstanceMethodWithoutSubstitution_Reached1 ();
else
InstanceMethodWithoutSubstitution_Reached2 ();
}
- [Kept]
bool InstanceMethodWithoutSubstitution ()
{
return true;
@@ -118,29 +132,32 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
}
- [Kept]
void InstanceMethodWithoutSubstitution_Reached2 ()
{
}
[Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4.0",
+ "brfalse.s il_4",
+ "ldarg.0",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.InstanceMethodSubstitutions::Propagation_Reached2()",
+ "ret",
+ })]
void TestPropagation ()
{
- // Propagation of return value across instance method is not supported
- // (propagation of return value from a method which has call in the body is not supported)
if (PropagateIsEnabled ())
Propagation_Reached1 ();
else
Propagation_Reached2 ();
}
- [Kept]
bool PropagateIsEnabled ()
{
return IsEnabled ();
}
- [Kept]
void Propagation_Reached1 ()
{
}
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodArgumentPropagation.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodArgumentPropagation.cs
new file mode 100644
index 000000000..2a1c611ac
--- /dev/null
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodArgumentPropagation.cs
@@ -0,0 +1,377 @@
+using System;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.UnreachableBlock
+{
+ [SetupCSharpCompilerToUse ("csc")]
+ [SetupCompileArgument ("/optimize+")]
+ [SetupLinkerArgument ("--enable-opt", "ipconstprop")]
+ class MethodArgumentPropagation
+ {
+ public static void Main ()
+ {
+ TestSimpleStaticCall ();
+ TestFailedAndSuccessfullPropagation ();
+ TestComplexButAlwaysConstant ();
+ TestModifiesArgumentOnStack ();
+ TestConditionalStaticCall ();
+ TestSimpleLocalVariable ();
+ TestConditionalJumpIntoReplacedTarget (3);
+ TestNullPropagation ();
+ TestFirstLevelReduction ();
+ TestConditionalArguments ();
+ TestConditionalArguments_2 ();
+
+ TestRecursionFromDeadCode ();
+ TestIndirectRecursion ();
+ TestStringCalls ();
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4 0x0",
+ "brfalse.s il_8",
+ "ret",
+ })]
+ static void TestSimpleStaticCall ()
+ {
+ if (StaticBool (4))
+ NeverReached ();
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4 0x0",
+ "brfalse.s il_8",
+ "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::GetUnknownValue()",
+ "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::SimpleCompare(System.Int32)",
+ "brfalse.s il_19",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::Reached()",
+ "ret",
+ })]
+ static void TestFailedAndSuccessfullPropagation ()
+ {
+ if (SimpleCompare (GetConstValue ()))
+ NeverReached ();
+
+ if (SimpleCompare (GetUnknownValue ()))
+ Reached ();
+ }
+
+ [Kept]
+ static bool SimpleCompare (int arg)
+ {
+ return arg == 3;
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "ldc.i4.1",
+ "ldstr 'aa '",
+ "call System.String System.String::Trim()",
+ "ldc.i4.2",
+ "newarr System.Object",
+ "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::ComplexButAlwaysConstant(System.Int32,System.String,System.Object[])",
+ "ldc.i4.0",
+ "ble.s il_19",
+ "ret",
+ })]
+ static void TestComplexButAlwaysConstant ()
+ {
+ if (ComplexButAlwaysConstant (1, "aa ".Trim (), new object[] { null, null }) > 0)
+ NeverReached ();
+ }
+
+ [Kept]
+ static int ComplexButAlwaysConstant (int arg, string s, object[] array)
+ {
+ return -1;
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "ldc.i4.3",
+ "stloc.0",
+ "ldloca.s",
+ "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::ModifiesArgumentOnStack(System.Int32&)",
+ "ldc.i4.1",
+ "beq.s il_11",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::Reached()",
+ "ret",
+ })]
+ static void TestModifiesArgumentOnStack ()
+ {
+ int value = 3;
+ if (ModifiesArgumentOnStack (ref value) != 1)
+ Reached ();
+ }
+
+ [Kept]
+ static int ModifiesArgumentOnStack (ref int arg)
+ {
+ arg = 2;
+ return 1;
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4.s 0x9",
+ "ldc.i4.1",
+ "bne.un.s il_6",
+ "ret",
+ })]
+ static void TestConditionalStaticCall ()
+ {
+ if (ConditionalReturn (false) == 1)
+ NeverReached ();
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldstr 'a'",
+ "call System.Void System.Console::WriteLine(System.String)",
+ "ret",
+ })]
+ static void TestSimpleLocalVariable ()
+ {
+ Console.WriteLine (LocalVariableMix (int.MinValue));
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "ldarg.0",
+ "ldc.r8 0",
+ "bge.un.s il_1c",
+ "ldstr 'd'",
+ "ldstr 's'",
+ "newobj System.Void System.ArgumentOutOfRangeException::.ctor(System.String,System.String)",
+ "throw",
+ "ldstr 's'",
+ "call System.Void System.Console::WriteLine(System.String)",
+ "ldarg.0",
+ "ldc.r8 0",
+ "bge.un.s il_42",
+ "ldstr 'd'",
+ "ldstr 's'",
+ "newobj System.Void System.ArgumentOutOfRangeException::.ctor(System.String,System.String)",
+ "throw",
+ "ldstr 's'",
+ "call System.Void System.Console::WriteLine(System.String)",
+ "ret",
+ })]
+ static void TestConditionalJumpIntoReplacedTarget (double d)
+ {
+ if (d < 0)
+ throw new ArgumentOutOfRangeException (nameof (d), GetString ());
+
+ Console.WriteLine (GetString ());
+
+ if (d < 0)
+ throw new ArgumentOutOfRangeException (nameof (d), GetString ());
+
+ Console.WriteLine (GetString ());
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldnull",
+ "brfalse.s il_4",
+ "ret",
+ })]
+ static void TestNullPropagation ()
+ {
+ if (GetNull (2) is not null)
+ NeverReached ();
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4.4",
+ "ldc.i4.4",
+ "beq.s il_5",
+ "nop",
+ "ldc.i4.s 0xa",
+ "call System.Void System.Console::WriteLine(System.Int32)",
+ "ret",
+ })]
+ static void TestFirstLevelReduction ()
+ {
+ if (SimpleIntInOut (4) != 4)
+ NeverReached ();
+
+ Console.WriteLine (SimpleIntInOut (10));
+ }
+
+ [Kept]
+ static void TestConditionalArguments ()
+ {
+ if (KeptIntInOut (GetUnknownValue () > 0 ? 2 : 3) != 4)
+ Reached ();
+ }
+
+ [Kept]
+ static void TestConditionalArguments_2 ()
+ {
+ if (KeptIntInOut (GetUnknownValue () > 0 ? 2 : 3, 1) != 4)
+ Reached ();
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "ldc.i4.0",
+ "ldc.i4.1",
+ "bne.un.s il_5",
+ "ret",
+ })]
+ static void TestRecursionFromDeadCode ()
+ {
+ if (RecursionFromDeadCode (3) == 1) {
+ NeverReached ();
+ }
+ }
+
+ static int RecursionFromDeadCode (int arg)
+ {
+ if (arg > 0) {
+ if (StaticBool (4)) {
+ return 1;
+ }
+ } else {
+ RecursionFromDeadCode (--arg);
+ }
+
+ return 0;
+ }
+
+ [Kept]
+ static int TestIndirectRecursion ()
+ {
+ return TestIndirectRecursion_1 ();
+ }
+
+ [Kept]
+ static int TestIndirectRecursion_1 ()
+ {
+ return TestIndirectRecursion_2 ();
+ }
+
+ [Kept]
+ static int TestIndirectRecursion_2 ()
+ {
+ return TestIndirectRecursion ();
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "nop",
+ "nop",
+ "nop",
+ "ldc.i4 0x0",
+ "brfalse.s il_a",
+ "nop",
+ "nop",
+ "nop",
+ "ldc.i4 0x0",
+ "brfalse.s il_14",
+ "ret",
+ })]
+ static void TestStringCalls ()
+ {
+ string s = GetStringValue ("s");
+ if (StringsEqual (s, "v"))
+ NeverReached ();
+
+ if (StringsNotEqual (GetStringValue ("s"), "s"))
+ NeverReached ();
+ }
+
+ static bool StringsEqual (string a, string b)
+ {
+ return a == b;
+ }
+
+ static bool StringsNotEqual (string a, string b)
+ {
+ return a != b;
+ }
+
+ static bool StaticBool (int arg)
+ {
+ return arg == 3;
+ }
+
+ static int ConditionalReturn (bool arg)
+ {
+ if (arg)
+ return 1;
+
+ return 9;
+ }
+
+ static string LocalVariableMix (int s)
+ {
+ int l = int.MinValue;
+ return l == s ? "a" : "b";
+ }
+
+ static string GetString ()
+ {
+ return "s";
+ }
+
+ static object GetNull (int arg)
+ {
+ return arg > 5 ? 9 : null;
+ }
+
+ static int SimpleIntInOut (int arg)
+ {
+ return arg;
+ }
+
+ static int GetConstValue ()
+ {
+ return 5;
+ }
+
+ static string GetStringValue (string s)
+ {
+ return s;
+ }
+
+ [Kept]
+ static int GetUnknownValue ()
+ {
+ return Environment.ProcessId + 10;
+ }
+
+ [Kept]
+ static int KeptIntInOut (int arg)
+ {
+ return arg;
+ }
+
+ [Kept]
+ static int KeptIntInOut (int arg, int unused)
+ {
+ return arg;
+ }
+
+ [Kept]
+ static void Reached ()
+ {
+ }
+
+ static void NeverReached ()
+ {
+ }
+ }
+}
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodWithParametersSubstitutions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodWithParametersSubstitutions.cs
index 36383f68b..51e60defe 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodWithParametersSubstitutions.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodWithParametersSubstitutions.cs
@@ -34,8 +34,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
static bool _isEnabledField;
- [Kept]
- [ExpectBodyModified]
static bool IsEnabledWithValueParam (int param)
{
return _isEnabledField;
@@ -54,8 +52,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept] static void MethodWithValueParam_Reached () { }
static void MethodWithValueParam_NeverReached () { }
- [Kept]
- [ExpectBodyModified]
static bool IsEnabledWithReferenceParam (string param)
{
return _isEnabledField;
@@ -76,13 +72,14 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "ldnull",
- "ldnull",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.MethodWithParametersSubstitutions::StaticMethod(System.Object,System.Int32[])",
+ "nop",
+ "nop",
+ "ldc.i4.1",
"pop",
"ldc.i4.1",
- "ret"
+ "ret",
})]
+ [System.Runtime.CompilerServices.MethodImpl (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static int TestMethodWithComplexParams_1 ()
{
if (StaticMethod (null, null))
@@ -290,7 +287,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept] static void MethodWithMultipleRefParams_Reached1 () { }
static void MethodWithMultipleRefParams_Reached2 () { }
- [Kept]
static bool IsEnabledWithValueParamAndConstReturn_NoSubstitutions (int param)
{
return true;
@@ -298,8 +294,8 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "ldc.i4.0",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.MethodWithParametersSubstitutions::IsEnabledWithValueParamAndConstReturn_NoSubstitutions(System.Int32)",
+ "nop",
+ "ldc.i4.1",
"pop",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.MethodWithParametersSubstitutions::MethodWithValueParamAndConstReturn_NoSubstitutions_Reached1()",
"ret",
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs
index 95a435eea..f7929fcf2 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs
@@ -30,8 +30,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
NeverReached_2 ();
}
- [Kept]
- [ExpectBodyModified]
static int TestProperty_int ()
{
if (Prop > 5) {
@@ -41,8 +39,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
return 0;
}
- [Kept]
- [ExpectBodyModified]
static int TestProperty_bool_twice ()
{
if (PropBool) {
@@ -55,17 +51,13 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
return 0;
}
- [Kept]
static int Prop {
- [Kept]
get {
return 9;
}
}
- [Kept]
static bool PropBool {
- [Kept]
get {
return true;
}
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedReturns.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedReturns.cs
index fc80675e8..398da5925 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedReturns.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedReturns.cs
@@ -33,7 +33,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"call System.Void System.Console::WriteLine()",
"ldc.i4.1",
@@ -51,11 +51,11 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"call System.Void System.Console::WriteLine()",
"ldc.i4.0",
- "ret"
+ "ret",
})]
static bool Test2 ()
{
@@ -69,11 +69,11 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"ldsfld System.DateTime System.DateTime::MinValue",
"call System.Void System.Console::WriteLine()",
- "ret"
+ "ret",
})]
static DateTime Test3 ()
{
@@ -88,7 +88,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"ldsfld System.DateTime System.DateTime::MinValue",
"call System.Void System.Console::WriteLine()",
@@ -109,7 +109,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"ldsfld System.DateTime System.DateTime::MinValue",
"pop",
@@ -134,15 +134,15 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
".try",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"call System.Void System.Console::WriteLine()",
- "leave.s il_16",
+ "leave.s il_12",
".endtry",
".catch",
"pop",
"call System.Void System.Console::WriteLine()",
- "leave.s il_15",
+ "leave.s il_11",
".endcatch",
"ret",
"ret",
@@ -167,20 +167,20 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
".try",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"call System.Void System.Console::WriteLine()",
"ldc.i4.1",
"conv.i8",
"stloc.0",
- "leave.s il_16",
+ "leave.s il_12",
".endtry",
".catch",
"pop",
"ldc.i4.2",
"conv.i8",
"stloc.0",
- "leave.s il_16",
+ "leave.s il_12",
".endcatch",
"ldloc.0",
"ret",
@@ -204,18 +204,18 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
"ldc.i4.0",
"stloc.0",
".try",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"call System.Void System.Console::WriteLine()",
"ldc.i4.1",
"stloc.1",
- "leave.s il_1c",
+ "leave.s il_18",
".endtry",
".catch",
"pop",
"ldloc.0",
"call System.Void System.Console::WriteLine(System.Int32)",
- "leave.s il_1a",
+ "leave.s il_16",
".endcatch",
"ldc.i4.3",
"ret",
@@ -243,7 +243,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedLocalsSequence (new string[0])]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"call System.Void System.Console::WriteLine()",
"ret"
@@ -263,14 +263,14 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
".try",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.ReplacedReturns::AlwaysTrue()",
+ "ldc.i4.1",
"pop",
"call System.Void System.Console::WriteLine()",
- "leave.s il_10",
+ "leave.s il_c",
".endtry",
".catch",
"pop",
- "leave.s il_10",
+ "leave.s il_c",
".endcatch",
"ret",
})]
@@ -290,7 +290,6 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
}
}
- [Kept]
static bool AlwaysTrue ()
{
return true;
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ResultInliningNotPossible.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ResultInliningNotPossible.cs
new file mode 100644
index 000000000..aa965b9fd
--- /dev/null
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ResultInliningNotPossible.cs
@@ -0,0 +1,161 @@
+using System;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.UnreachableBlock
+{
+ [SetupCSharpCompilerToUse ("csc")]
+ [SetupCompileArgument ("/optimize+")]
+ [SetupLinkerArgument ("--enable-opt", "ipconstprop")]
+ public class ResultInliningNotPossible
+ {
+ public static void Main ()
+ {
+ Test_TypeWithStaticCtor ();
+ Test_TypeWithExplicitStaticCtor ();
+ Test_MethodWithRefArgument ();
+ Test_MethodWithInstanceCall ();
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.ResultInliningNotPossible/TypeWithStaticCtor::GetResult()",
+ "ldc.i4.1",
+ "beq.s il_8",
+ "ret",
+ })]
+ static void Test_TypeWithStaticCtor ()
+ {
+ if (TypeWithStaticCtor.GetResult () != 1) {
+ NeverReached ();
+ }
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.ResultInliningNotPossible/TypeWithExplicitStaticCtor::GetResult()",
+ "ldc.i4.1",
+ "beq.s il_8",
+ "ret",
+ })]
+ static void Test_TypeWithExplicitStaticCtor ()
+ {
+ if (TypeWithExplicitStaticCtor.GetResult () != 1) {
+ NeverReached ();
+ }
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "ldnull",
+ "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.ResultInliningNotPossible::MethodWithInstanceCall(Mono.Linker.Tests.Cases.UnreachableBlock.ResultInliningNotPossible/InstanceMethodType)",
+ "ldc.i4.2",
+ "beq.s il_9",
+ "ret",
+ })]
+ static void Test_MethodWithInstanceCall ()
+ {
+ if (MethodWithInstanceCall (null) != 2) {
+ NeverReached ();
+ }
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "ldarg.0",
+ "callvirt System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.ResultInliningNotPossible/InstanceMethodType::GetResult()",
+ "pop",
+ "ldc.i4.2",
+ "ret",
+ })]
+ static int MethodWithInstanceCall (InstanceMethodType imt)
+ {
+ if (imt.GetResult () > 0)
+ return 2;
+
+ return 1;
+ }
+
+ [Kept]
+ [ExpectedInstructionSequence (new[] {
+ "ldc.i4.0",
+ "stloc.0",
+ "ldloca.s",
+ "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.ResultInliningNotPossible::MethodWithRefArgument(System.Int32&)",
+ "ldc.i4.1",
+ "beq.s il_11",
+ "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.ResultInliningNotPossible::Reached()",
+ "ret",
+ })]
+ static void Test_MethodWithRefArgument ()
+ {
+ int i = 0;
+ if (MethodWithRefArgument (ref i) != 1) {
+ Reached ();
+ }
+ }
+
+ [Kept]
+ static int MethodWithRefArgument (ref int arg)
+ {
+ arg = 1;
+ return 1;
+ }
+
+ [Kept]
+ [KeptMember (".cctor()")]
+ class TypeWithStaticCtor
+ {
+ [Kept]
+ static int Field = 4;
+
+ [Kept]
+ public static int GetResult ()
+ {
+ Inside ();
+ return 1;
+ }
+
+ [Kept]
+ static void Inside ()
+ {
+ Field = 2;
+ }
+ }
+
+ [Kept]
+ class TypeWithExplicitStaticCtor
+ {
+ [Kept]
+ static TypeWithExplicitStaticCtor ()
+ {
+ Console.WriteLine ("Has to be called");
+ }
+
+ [Kept]
+ public static int GetResult ()
+ {
+ return 1;
+ }
+ }
+
+ [Kept]
+ class InstanceMethodType
+ {
+ [Kept]
+ public int GetResult ()
+ {
+ return 1;
+ }
+ }
+
+ [Kept]
+ static void Reached ()
+ {
+ }
+
+ static void NeverReached ()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs
index 2cf077da1..db096df2f 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs
@@ -27,9 +27,9 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_Prop()",
"ldc.i4.3",
- "beq.s il_8",
+ "ldc.i4.3",
+ "beq.s il_4",
"ret",
})]
static void TestProperty_int_1 ()
@@ -41,8 +41,8 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
"ldc.i4.3",
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_Prop()",
- "beq.s il_8",
+ "ldc.i4.3",
+ "beq.s il_4",
"ret"
})]
static void TestProperty_int_2 ()
@@ -56,12 +56,13 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_Prop()",
+ "ldc.i4.3",
"ldc.i4.5",
- "ble.s il_8",
+ "ble.s il_4",
"ldc.i4.0",
"ret"
})]
+ [System.Runtime.CompilerServices.MethodImpl (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
static int TestProperty_int_3 ()
{
if (Prop > 5 && TestProperty_int_3 () == 0) {
@@ -73,7 +74,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_Prop()",
+ "ldc.i4.3",
"pop",
"ldloca.s",
"initobj System.Nullable`1<System.Int64>",
@@ -90,8 +91,8 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_PropBool()",
- "brfalse.s il_7",
+ "ldc.i4.0",
+ "brfalse.s il_3",
"ret"
})]
static void TestProperty_bool_1 ()
@@ -105,9 +106,9 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_PropBool()",
- "brfalse.s il_7",
- "ret"
+ "ldc.i4.0",
+ "brfalse.s il_3",
+ "ret",
})]
static void TestProperty_bool_2 ()
{
@@ -118,9 +119,9 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_PropBool()",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_PropBool()",
- "beq.s il_c",
+ "ldc.i4.0",
+ "ldc.i4.0",
+ "beq.s il_4",
"ret"
})]
static void TestProperty_bool_3 ()
@@ -133,7 +134,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
"br.s il_2",
- "call Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty/TestEnum Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_PropEnum()",
+ "ldc.i4.1",
"pop",
"ret",
})]
@@ -146,8 +147,8 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.String Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_PropNull()",
- "brfalse.s il_7",
+ "ldnull",
+ "brfalse.s il_3",
"ret"
})]
static void TestProperty_null_1 ()
@@ -204,48 +205,36 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
NeverReached_1 ();
}
- [Kept]
static int Prop {
- [Kept]
get {
int i = 3;
return i;
}
}
- [Kept]
static bool PropBool {
- [Kept]
get {
return false;
}
}
- [Kept]
static TestEnum PropEnum {
- [Kept]
get {
return TestEnum.B;
}
}
- [Kept]
static string PropNull {
- [Kept]
get {
return null;
}
}
- [Kept]
static int PropInt {
- [Kept]
get => 10;
}
- [Kept]
static uint PropUInt {
- [Kept]
get => 10;
}
@@ -253,16 +242,10 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
}
- [Kept]
- [KeptMember ("value__")]
- [KeptBaseType (typeof (Enum))]
enum TestEnum
{
- [Kept]
A = 0,
- [Kept]
B = 1,
- [Kept]
C = 2
}
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryCatchBlocks.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryCatchBlocks.cs
index ae8520bba..c6fff22f0 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryCatchBlocks.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryCatchBlocks.cs
@@ -18,13 +18,14 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.TryCatchBlocks/TryCatchInRemovedBranch::get_Prop()",
"ldc.i4.6",
- "beq.s il_8",
+ "ldc.i4.6",
+ "beq.s il_4",
"ldc.i4.3",
"ret"
})]
[ExpectedLocalsSequence (new string[0])]
+ [System.Runtime.CompilerServices.MethodImpl (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public static int Test ()
{
if (Prop != 6) {
@@ -39,9 +40,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
return 3;
}
- [Kept]
static int Prop {
- [Kept]
get {
return 6;
}
@@ -56,16 +55,16 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.TryCatchBlocks/TryCatchInKeptBranchBeforeRemovedBranch::get_Prop()",
+ "ldc.i4.0",
"pop",
".try",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.TryCatchBlocks/TryCatchInKeptBranchBeforeRemovedBranch::Reached()",
- "leave.s il_15",
+ "leave.s il_11",
".endtry",
".catch",
"pop",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.TryCatchBlocks/TryCatchInKeptBranchBeforeRemovedBranch::Reached_2()",
- "leave.s il_15",
+ "leave.s il_11",
".endcatch",
"ret",
})]
@@ -78,8 +77,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
}
}
- [Kept]
- static int Prop { [Kept] get => 0; }
+ static int Prop { get => 0; }
[Kept]
static void Reached () { }
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFilterBlocks.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFilterBlocks.cs
index 2718e5869..2ba4628eb 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFilterBlocks.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFilterBlocks.cs
@@ -17,20 +17,20 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[Kept]
[ExpectedInstructionSequence (new[] {
".try",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.TryFilterBlocks::get_Prop()",
- "brfalse.s il_7",
+ "ldc.i4.0",
+ "brfalse.s il_3",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.TryFilterBlocks::Reached_1()",
- "leave.s il_1c",
+ "leave.s il_14",
".endtry",
".filter",
"pop",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.TryFilterBlocks::Log()",
+ "ldc.i4.0",
"ldc.i4.0",
"cgt.un",
"endfilter",
".catch",
"pop",
- "leave.s il_1c",
+ "leave.s il_14",
".endcatch",
"ldc.i4.2",
"ret",
@@ -52,19 +52,19 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[ExpectedInstructionSequence (new[] {
".try",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.TryFilterBlocks::Reached_2()",
- "leave.s il_18",
+ "leave.s il_14",
".endtry",
".filter",
"pop",
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.TryFilterBlocks::Log()",
- "brfalse.s il_f",
+ "ldc.i4.0",
+ "brfalse.s il_b",
"ldc.i4.0",
"ldc.i4.0",
"cgt.un",
"endfilter",
".catch",
"pop",
- "leave.s il_18",
+ "leave.s il_14",
".endcatch",
"ldc.i4.3",
"ret",
@@ -79,15 +79,12 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
return 3;
}
- [Kept]
static bool Prop {
- [Kept]
get {
return false;
}
}
- [Kept]
static bool Log () => false;
[Kept]
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs
index 99ffc5d91..b1700077f 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs
@@ -19,9 +19,9 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.TryFinallyBlocks/TryFinallyInConstantProperty::get_Prop()",
"ldc.i4.3",
- "beq.s il_8",
+ "ldc.i4.3",
+ "beq.s il_4",
"ret"
})]
public static void Test ()
@@ -30,9 +30,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
Unreached_1 ();
}
- [Kept]
static int Prop {
- [Kept]
get {
try {
return 3;
@@ -51,7 +49,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.TryFinallyBlocks/TryFinallyInRemovedBranch::get_Prop()",
+ "ldc.i4.0",
"pop",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.TryFinallyBlocks/TryFinallyInRemovedBranch::Reached()",
"ret",
@@ -65,8 +63,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
}
}
- [Kept]
- static int Prop { [Kept] get => 0; }
+ static int Prop { get => 0; }
[Kept]
static void Reached () { }
@@ -80,11 +77,11 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
[Kept]
[ExpectedInstructionSequence (new[] {
- "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.TryFinallyBlocks/TryFinallyInKeptBranchBeforeRemovedBranch::get_Prop()",
+ "ldc.i4.0",
"pop",
".try",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.TryFinallyBlocks/TryFinallyInKeptBranchBeforeRemovedBranch::Reached()",
- "leave.s il_13",
+ "leave.s il_f",
".endtry",
".catch",
"call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.TryFinallyBlocks/TryFinallyInKeptBranchBeforeRemovedBranch::Reached_2()",
@@ -101,8 +98,7 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
}
}
- [Kept]
- static int Prop { [Kept] get => 0; }
+ static int Prop { get => 0; }
[Kept]
static void Reached () { }
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/UninitializedLocals.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/UninitializedLocals.cs
index 4a8427190..d0404ce3c 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/UninitializedLocals.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/UninitializedLocals.cs
@@ -3,6 +3,8 @@ using Mono.Linker.Tests.Cases.Expectations.Metadata;
namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
+ [SetupCSharpCompilerToUse ("csc")]
+ [SetupCompileArgument ("/optimize+")]
[SetupLinkerArgument ("--skip-unresolved", "true")]
[Define ("IL_ASSEMBLY_AVAILABLE")]
[SetupCompileBefore ("library.dll", new[] { "Dependencies/LocalsWithoutStore.il" })]
@@ -10,6 +12,13 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[SkipPeVerify]
public class UninitializedLocals
{
+ [ExpectedInstructionSequence (new[] {
+ "ldnull",
+ "pop",
+ "call System.Object Mono.Linker.Tests.Cases.UnreachableBlock.Dependencies.ClassA::Method_2()",
+ "pop",
+ "ret",
+ })]
public static void Main ()
{
#if IL_ASSEMBLY_AVAILABLE
diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/WorksWithDynamicAssembly.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/WorksWithDynamicAssembly.cs
index 516b056d9..2b33fd2d3 100644
--- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/WorksWithDynamicAssembly.cs
+++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/WorksWithDynamicAssembly.cs
@@ -9,17 +9,15 @@ namespace Mono.Linker.Tests.Cases.UnreachableBlock
[SetupLinkerArgument ("--enable-opt", "ipconstprop")]
[SetupCompileBefore ("library.dll", new string[] { "Dependencies/ReferencedAssemblyWithUnreachableBlocks.cs" },
addAsReference: false, additionalArguments: "/optimize+", compilerToUse: "csc")]
- [KeptMemberInAssembly ("library.dll", "Mono.Linker.Tests.Cases.UnreachableBlock.Dependencies.AssemblyWithUnreachableBlocks",
- new string[] { ".ctor()", "TestProperty()", "get_PropBool()" })]
[RemovedMemberInAssembly ("library.dll", "Mono.Linker.Tests.Cases.UnreachableBlock.Dependencies.AssemblyWithUnreachableBlocks",
new string[] { "NeverReached()" })]
[ExpectedInstructionSequenceOnMemberInAssembly ("library.dll",
"Mono.Linker.Tests.Cases.UnreachableBlock.Dependencies.AssemblyWithUnreachableBlocks",
"TestProperty()",
new string[] {
- "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.Dependencies.AssemblyWithUnreachableBlocks::get_PropBool()",
- "brfalse.s il_7",
- "ret"
+ "ldc.i4.0",
+ "brfalse.s il_3",
+ "ret",
})]
[Kept]
public class WorksWithDynamicAssembly