From c59b3a0375e3988f2eda27128e979ca45cdb6426 Mon Sep 17 00:00:00 2001 From: Marek Safar Date: Tue, 18 Jan 2022 09:31:08 +0100 Subject: [PATCH] Add constant propagation through methods with parameter or complex bodies This change also - Adds simple inlining of method return values. - Fixes several shortcomings of existing implementation like ignoring methods called from unreachable blocks. This helps to reduce the size of all runtime assemblies. For example size of SPC for the default iOS project is reduced by 4% mostly due to metadata removal. The linker speed improves a little bit (~3%) for the same project. --- external/cecil | 2 +- .../UnreachableBlocksOptimizer.cs | 1424 ++++++++++------- .../FeatureSettings/FeatureSubstitutions.cs | 33 +- .../FeatureSubstitutionsNested.cs | 34 +- .../Libraries/RootLibrary.cs | 3 +- .../Substitutions/StubBody.cs | 88 +- .../Substitutions/StubBodyWithValue.cs | 13 + .../BodiesWithSubstitutions.cs | 47 +- .../UnreachableBlock/ComplexConditions.cs | 20 +- .../UnreachableBlock/DeadVariables.cs | 2 - .../InstanceMethodSubstitutions.cs | 43 +- .../MethodArgumentPropagation.cs | 377 +++++ .../MethodWithParametersSubstitutions.cs | 18 +- .../UnreachableBlock/MultiStageRemoval.cs | 8 - .../UnreachableBlock/ReplacedReturns.cs | 41 +- .../ResultInliningNotPossible.cs | 161 ++ .../SimpleConditionalProperty.cs | 55 +- .../UnreachableBlock/TryCatchBlocks.cs | 16 +- .../UnreachableBlock/TryFilterBlocks.cs | 21 +- .../UnreachableBlock/TryFinallyBlocks.cs | 18 +- .../UnreachableBlock/UninitializedLocals.cs | 9 + .../WorksWithDynamicAssembly.cs | 8 +- 22 files changed, 1638 insertions(+), 803 deletions(-) create mode 100644 test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodArgumentPropagation.cs create mode 100644 test/Mono.Linker.Tests.Cases/UnreachableBlock/ResultInliningNotPossible.cs diff --git a/external/cecil b/external/cecil index cdc0adc432b2..0780d4e15ef4 160000 --- a/external/cecil +++ b/external/cecil @@ -1 +1 @@ -Subproject commit cdc0adc432b275c379f6db3db90a731b58525335 +Subproject commit 0780d4e15ef42b56a4e40dafac0d5486f09ce005 diff --git a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs index b48f143bf1aa..0caca61b16f9 100644 --- a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs +++ b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Collections; using System.Collections.Generic; @@ -12,77 +15,32 @@ 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; - } - - public ProcessingNode (in ProcessingNode other, int newLastAttempStackVersion) - { - Method = other.Method; - LastAttemptStackVersion = newLastAttempStackVersion; - } - - public readonly MethodDefinition Method; - public readonly int LastAttemptStackVersion; - } + readonly Dictionary cache_no_parameter = new (2048); + readonly Dictionary cache_unknown_parameters = new (2048); + readonly Stack resursion_guard = new (); - // 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 _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> _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 _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 (); - _processingMethods = new Dictionary> (); - _processedMethods = new Dictionary (); + Stack GetRecursionGuard () + { + resursion_guard.Clear (); + return resursion_guard; } /// /// Processes the specified and 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. /// /// The method to process public void ProcessMethod (MethodDefinition method) @@ -93,16 +51,29 @@ public void ProcessMethod (MethodDefinition method) 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)); + // + // We cannot run before reducer without as it would require another + // recomputing offsets due to instructions replacement + // + var inliner = new CallInliner (method.Body, this); + inliner.RewriteBody (); } static bool IsMethodSupported (MethodDefinition method) @@ -122,152 +93,109 @@ static bool IsMethodSupported (MethodDefinition method) return true; } - void AddMethodForProcessing (MethodDefinition method) + static bool HasJumpIntoTargetRange (Collection instructions, int firstInstr, int lastInstr, Func? 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 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? 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? instructions) + MethodResult? AnalyzeMethodForConstantResult (in CalleePayload callee, Stack callStack) { + MethodDefinition method = callee.Method; + if (method.ReturnType.MetadataType == MetadataType.Void) return null; @@ -278,7 +206,8 @@ void ProcessStack () 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) @@ -287,186 +216,323 @@ void ProcessStack () 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; } - /// - /// 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. - /// - /// The method to determine result for - /// 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. - /// - /// 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 - /// - 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, Stack callStack) { - 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; + MethodResult? value; + + MethodDefinition method = callee.Method; + if (!method.HasParameters) { + if (!cache_no_parameter.TryGetValue (method, out value)) { + value = AnalyzeMethodForConstantResult (callee, callStack); + cache_no_parameter.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; + if (callee.HasUnknownArguments) { + if (!cache_unknown_parameters.TryGetValue (method, out value)) { + value = AnalyzeMethodForConstantResult (callee, callStack); + cache_unknown_parameters.Add (method, value); + } + + return value; } - return true; + return AnalyzeMethodForConstantResult (callee, callStack); } - bool TryInlineBodyDependencies (ref BodyReducer reducer, bool treatUnprocessedDependenciesAsNonConst, out bool changed) + Instruction? GetSizeOfResult (TypeReference type) { - changed = false; - bool hasUnprocessedDependencies = false; - var instructions = reducer.Body.Instructions; - Instruction? targetResult; + MethodDefinition? sizeOfImpl = null; - for (int i = 0; i < instructions.Count; ++i) { - var instr = instructions[i]; - switch (instr.OpCode.Code) { + // + // 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))); + } - case Code.Call: - case Code.Callvirt: - var md = _context.TryResolve ((MethodReference) instr.Operand); - if (md == null) - break; + if (sizeOfImpl == null) + return null; - if (md.CallingConvention == MethodCallingConvention.VarArg) - break; + return TryGetMethodCallResult (new CalleePayload (sizeOfImpl, Array.Empty ()), GetRecursionGuard ())?.Instruction; + } - bool explicitlyAnnotated = _context.Annotations.GetAction (md) == MethodAction.ConvertToStub; + static Instruction? EvaluateIntrinsicCall (MethodReference method, Instruction[] arguments) + { + // + // In theory any pure method could be executed easily via reflection + // but for now we focus only on selected list method that helps + // reduce with 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) + 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); + } - // 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; + break; + } + } - if (md == reducer.Body.Method) { - // Special case for direct recursion - simply assume non-const value - // since we can't tell. - break; - } + //Console.WriteLine (method.GetDisplayName ()); + return null; + } - if (!TryGetConstantResultForMethod (md, out targetResult)) { - if (!treatUnprocessedDependenciesAsNonConst) - hasUnprocessedDependencies = true; - break; - } + static Instruction[]? GetArgumentsOnStack (MethodDefinition method, Collection instructions, int index) + { + if (!method.HasParameters) + return Array.Empty (); - 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; - } + Instruction[]? result = null; + for (int i = method.Parameters.Count; i != 0; --i) { + Instruction instr = instructions[index - i]; + if (!IsConstantValue (instr)) + return null; - // - // 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; + if (result == null) + result = new Instruction[i]; - if (depth != 0) - reducer.RewriteToNop (i - 1, depth); + result[i - 1] = instr; + } - reducer.Rewrite (i, targetResult); - changed = true; - break; + if (result != null && HasJumpIntoTargetRange (instructions, index - method.Parameters.Count + 1, index)) + return null; - case Code.Ldsfld: - var ftarget = (FieldReference) instr.Operand; - var field = _context.TryResolve (ftarget); - if (field == null) - break; + // Primary target are methods with a single parameter. We could support more + // but it needs more work on stack extraction logic + return result; - if (_context.Annotations.TryGetFieldUserValue (field, out object? value)) { - targetResult = CodeRewriterStep.CreateConstantResultInstruction (_context, field.FieldType, value); - if (targetResult == null) + 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; + } + + return false; + } + } + + 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; + } + } + + static MethodDefinition? FindSizeMethod (TypeDefinition? type) + { + if (type == null) + return 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 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, optimizer.GetRecursionGuard ()); + 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 instructions, int index) + { + 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 instructions, int index) + { + for (int i = 1; i <= method.Parameters.Count; ++i) { + if (!IsSideEffectFreeLoad (instructions[index - i])) + return false; + } + + return true; + } } struct BodyReducer @@ -495,14 +561,21 @@ public BodyReducer (MethodBody body, LinkContext context) public int InstructionsReplaced { get; set; } - public Collection? FoldedInstructions { get; private set; } + Collection? FoldedInstructions { get; set; } + + [MemberNotNull ("FoldedInstructions")] + [MemberNotNull ("mapping")] + void InitializeFoldedInstruction () + { + FoldedInstructions = new Collection (Body.Instructions); + mapping = new Dictionary (); + } public void Rewrite (int index, Instruction newInstruction) { - if (FoldedInstructions == null) { - FoldedInstructions = new Collection (Body.Instructions); - mapping = new Dictionary (); - } + if (FoldedInstructions == null) + InitializeFoldedInstruction (); + Debug.Assert (mapping != null); // Tracks mapping for replaced instructions for easier @@ -523,10 +596,8 @@ void RewriteConditionToNop (int index) public void RewriteToNop (int index, int stackDepth) { - if (FoldedInstructions == null) { - FoldedInstructions = new Collection (Body.Instructions); - mapping = new Dictionary (); - } + if (FoldedInstructions == null) + InitializeFoldedInstruction (); int start_index; for (start_index = index; start_index >= 0 && stackDepth > 0; --start_index) { @@ -639,7 +710,7 @@ void RewriteToNop (int index) public bool RewriteBody () { if (FoldedInstructions == null) - return false; + InitializeFoldedInstruction (); if (!RemoveConditions ()) return false; @@ -649,10 +720,7 @@ public bool RewriteBody () 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; @@ -673,6 +741,80 @@ public bool RewriteBody () 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), optimizer.GetRecursionGuard ())?.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 (); @@ -925,90 +1067,39 @@ BitArray GetReachableInstructionsMap (out List? unreachableHan static bool HasAnyBitSet (BitArray bitArray, int startIndex, int endIndex) { - for (int i = startIndex; i <= endIndex; ++i) { - if (bitArray[i]) - return true; - } - - return false; - } - - // - // Returns index of instruction in folded instruction body - // - int GetInstructionIndex (Instruction instruction) - { - Debug.Assert (FoldedInstructions != null && mapping != null); - if (mapping.TryGetValue (instruction, out int idx)) - return idx; - - idx = FoldedInstructions.IndexOf (instruction); - Debug.Assert (idx >= 0); - return idx; - } - - bool GetOperandsConstantValues (int index, out object? left, out object? right) - { - Debug.Assert (FoldedInstructions != null); - left = default; - right = default; - - if (index < 2) - return false; - - return GetConstantValue (FoldedInstructions[index - 2], out left) && - 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; + for (int i = startIndex; i <= endIndex; ++i) { + if (bitArray[i]) + return true; } + + return false; + } + + // + // Returns index of instruction in folded instruction body + // + int GetInstructionIndex (Instruction instruction) + { + Debug.Assert (FoldedInstructions != null && mapping != null); + if (mapping.TryGetValue (instruction, out int idx)) + return idx; + + idx = FoldedInstructions.IndexOf (instruction); + Debug.Assert (idx >= 0); + return idx; + } + + bool GetOperandsConstantValues (int index, out object? left, out object? right) + { + Debug.Assert (FoldedInstructions != null); + left = default; + right = default; + + if (index < 2) + return false; + + return GetConstantValue (FoldedInstructions[index - 2], out left) && + GetConstantValue (FoldedInstructions[index - 1], out right); } static bool IsPairedStlocLdloc (Instruction first, Instruction second) @@ -1033,47 +1124,6 @@ static bool IsPairedStlocLdloc (Instruction first, Instruction second) 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) { @@ -1091,27 +1141,7 @@ static bool IsConstantBranch (OpCode opCode, int operand) 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); } } @@ -1142,7 +1172,7 @@ public BodySweeper (MethodBody body, BitArray reachable, List? public int InstructionsReplaced { get; set; } - public bool Initialize () + public void Initialize () { var instrs = body.Instructions; @@ -1169,7 +1199,6 @@ public bool Initialize () } ilprocessor = body.GetLinkerILProcessor (); - return true; } public void Process (List? conditionInstrsToRemove, out List? sentinelNops) @@ -1332,80 +1361,58 @@ void CleanExceptionHandlers () 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 instructions; readonly LinkContext context; + readonly UnreachableBlocksOptimizer optimizer; Stack? stack_instr; Dictionary? 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 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 callStack) { - var body = method.Body; - if (body.HasExceptionHandlers) - return false; + MethodDefinition method = callee.Method; + Instruction[]? arguments = callee.Arguments; + Collection 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) { if (instr != jmpTarget) continue; @@ -1415,11 +1422,11 @@ public bool Analyze () 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: @@ -1427,6 +1434,60 @@ public bool Analyze () 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: @@ -1501,8 +1562,154 @@ public bool Analyze () 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 (); + } 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 ()) @@ -1517,6 +1724,43 @@ public bool Analyze () 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: + 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 () { @@ -1551,6 +1795,28 @@ bool ConvertStackToResult () 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)) @@ -1563,6 +1829,58 @@ bool ConvertStackToResult () 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) @@ -1581,5 +1899,15 @@ void StoreToLocals (int index) 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 bb93e90c0fcb..23b8f57b0314 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 @@ public static void Main () } [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 @@ static void UseFallback () { } - [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 741f2ad58132..b9a4f1c13e5a 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 @@ public static void Main () _ = 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 e9e7ecf29ee6..8cc979367e53 100644 --- a/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs +++ b/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs @@ -137,8 +137,7 @@ public void NotUsed () [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 df86662f2536..07f3d9dd7724 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(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 @@ public StubBody () 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 @@ static decimal TestMethod_4 () throw new NotImplementedException (); } - [Kept] - [ExpectedInstructionSequence (new[] { - "ldc.i4.0", - "ret", - })] static bool TestMethod_5 () { throw new NotImplementedException (); @@ -112,12 +128,6 @@ static void TestMethod_6 () TestMethod_5 (); } - [Kept] - [ExpectedInstructionSequence (new[] { - "ldc.r8 0", - "ret", - })] - [ExpectLocalsModified] static double TestMethod_7 () { double d = 1.1; @@ -137,53 +147,27 @@ static T TestMethod_8 (T t) 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 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 7c1974f94909..ce067938ee76 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 @@ public static void Main () "ldstr 'abcd'", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static string TestMethod_1 () { throw new NotImplementedException (); @@ -38,6 +40,7 @@ static string TestMethod_1 () "ldc.i4 0x4", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static byte TestMethod_2 () { throw new NotImplementedException (); @@ -48,6 +51,7 @@ static byte TestMethod_2 () "ldc.i4 0x78", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static char TestMethod_3 () { throw new NotImplementedException (); @@ -58,6 +62,7 @@ static char TestMethod_3 () "ldc.i4 0x3", "ret" })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static sbyte TestMethod_4 () { throw new NotImplementedException (); @@ -68,6 +73,7 @@ static sbyte TestMethod_4 () "ldc.i4.1", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static bool TestMethod_5 () { throw new NotImplementedException (); @@ -78,6 +84,7 @@ static bool TestMethod_5 () "ldc.i4.1", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static bool TestMethod_6 () { throw new NotImplementedException (); @@ -88,6 +95,7 @@ static bool TestMethod_6 () "ldc.r8 2.5", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static double TestMethod_7 () { throw new NotImplementedException (); @@ -98,6 +106,7 @@ static double TestMethod_7 () "ldc.i4 0xfffffffd", "ret" })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static int TestMethod_8 () { throw new NotImplementedException (); @@ -108,6 +117,7 @@ static int TestMethod_8 () "ldc.r4 6", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static float TestMethod_9 () { throw new NotImplementedException (); @@ -118,6 +128,7 @@ static float TestMethod_9 () "ldc.i8 0x1e240", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static ulong TestMethod_10 () { throw new NotImplementedException (); @@ -128,6 +139,7 @@ static ulong TestMethod_10 () "ldc.i8 0xfffffffffffffc18", "ret", })] + [MethodImplAttribute (MethodImplOptions.NoInlining)] static long TestMethod_11 () { throw new NotImplementedException (); @@ -138,6 +150,7 @@ static long TestMethod_11 () "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 1c64776d5cac..e4dc6110ccca 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs @@ -66,10 +66,7 @@ static void TestField_int_1 () NeverReached_1 (); } - [Kept] static int Property { - [Kept] - [ExpectBodyModified] get { return field; } @@ -99,9 +96,7 @@ static void Reached_1 () { } - [Kept] static int PropagateProperty { - [Kept] get { return Property; } @@ -198,7 +193,6 @@ static class LoopWithConstantsComplex [Kept] static int depth = 0; - [Kept] static bool IsTrue () { return true; @@ -250,7 +244,6 @@ static class MultiLoopWithConstantsComplex [Kept] static int depth = 0; - [Kept] static bool IsTrue () { return true; @@ -325,16 +318,16 @@ public static void Test () 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 @@ public static void Test () } } - [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 @@ static bool NoInliningProperty { } [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 @@ static void TestSubstitutionOnNoInlining () 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 e759c0e28909..ea7d62c5fb7d 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs @@ -24,20 +24,20 @@ public static void Main () "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 @@ static void Test_1 (object type) #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 @@ static void Test_1 (object type) "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 @@ static void Test_2 (int a) 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 80a3908f94ca..94468e88a96c 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/DeadVariables.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/DeadVariables.cs @@ -65,9 +65,7 @@ static int Test_3 () 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 d204ca55a281..2e5364dce1fb 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/InstanceMethodSubstitutions.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/InstanceMethodSubstitutions.cs @@ -35,17 +35,23 @@ bool IsEnabled () 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 @@ void TestCallOnInstance () [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 @@ void SimpleCallsite_Reached () } [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 @@ void InstanceMethodWithoutSubstitution_Reached1 () { } - [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 000000000000..2a1c611ac4f3 --- /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 36383f68b45b..51e60defe966 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodWithParametersSubstitutions.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MethodWithParametersSubstitutions.cs @@ -34,8 +34,6 @@ public static void Main () static bool _isEnabledField; - [Kept] - [ExpectBodyModified] static bool IsEnabledWithValueParam (int param) { return _isEnabledField; @@ -54,8 +52,6 @@ static void TestMethodWithValueParam () [Kept] static void MethodWithValueParam_Reached () { } static void MethodWithValueParam_NeverReached () { } - [Kept] - [ExpectBodyModified] static bool IsEnabledWithReferenceParam (string param) { return _isEnabledField; @@ -76,13 +72,14 @@ static void MethodWithReferenceParam_NeverReached () { } [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 @@ static void TestMethodWithMultipleRefParams () [Kept] static void MethodWithMultipleRefParams_Reached1 () { } static void MethodWithMultipleRefParams_Reached2 () { } - [Kept] static bool IsEnabledWithValueParamAndConstReturn_NoSubstitutions (int param) { return true; @@ -298,8 +294,8 @@ static bool IsEnabledWithValueParamAndConstReturn_NoSubstitutions (int param) [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 95a435eea1f2..f7929fcf2fd5 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs @@ -30,8 +30,6 @@ static void TestMethod_2 () NeverReached_2 (); } - [Kept] - [ExpectBodyModified] static int TestProperty_int () { if (Prop > 5) { @@ -41,8 +39,6 @@ static int TestProperty_int () return 0; } - [Kept] - [ExpectBodyModified] static int TestProperty_bool_twice () { if (PropBool) { @@ -55,17 +51,13 @@ static int TestProperty_bool_twice () 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 fc80675e8627..398da5925ac6 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedReturns.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ReplacedReturns.cs @@ -33,7 +33,7 @@ enum TestEnum [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 @@ static int Test1 () [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 @@ static bool Test2 () [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 @@ static DateTime Test3 () [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 @@ static DateTime Test3b () [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 @@ static TestEnum Test4 () [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 @@ static void Test5 () [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 @@ static long Test6 () "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 @@ static byte Test7 () [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 @@ static void Test8 () [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 @@ static void Test9 () } } - [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 000000000000..aa965b9fd829 --- /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 2cf077da1149..db096df2f067 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs @@ -27,9 +27,9 @@ public static void Main () [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 @@ static void TestProperty_int_1 () [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 @@ static void TestProperty_int_2 () [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 @@ static int TestProperty_int_3 () [Kept] [ExpectedInstructionSequence (new[] { - "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.SimpleConditionalProperty::get_Prop()", + "ldc.i4.3", "pop", "ldloca.s", "initobj System.Nullable`1", @@ -90,8 +91,8 @@ static int TestProperty_int_3 () [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 @@ static void TestProperty_bool_1 () [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 @@ static void TestProperty_bool_2 () [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 @@ static void TestProperty_bool_3 () [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 @@ static void TestProperty_enum_1 () [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 @@ static void TestProperty_UnsignedComparisons () 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 @@ static void NeverReached_1 () { } - [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 ae8520bbac59..c6fff22f04c1 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryCatchBlocks.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryCatchBlocks.cs @@ -18,13 +18,14 @@ class TryCatchInRemovedBranch { [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 @@ public static int Test () return 3; } - [Kept] static int Prop { - [Kept] get { return 6; } @@ -56,16 +55,16 @@ class TryCatchInKeptBranchBeforeRemovedBranch { [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 @@ public static void Test () } } - [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 2718e58699c3..2ba4628eb923 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFilterBlocks.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFilterBlocks.cs @@ -17,20 +17,20 @@ public static void Main () [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 @@ static int TestUnreachableInsideTry () [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 @@ static int TestUnreachableInsideFilterCondition () 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 99ffc5d91d85..b1700077fd6a 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs @@ -19,9 +19,9 @@ class TryFinallyInConstantProperty { [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 @@ public static void Test () Unreached_1 (); } - [Kept] static int Prop { - [Kept] get { try { return 3; @@ -51,7 +49,7 @@ class TryFinallyInRemovedBranch { [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 @@ public static void Test () } } - [Kept] - static int Prop { [Kept] get => 0; } + static int Prop { get => 0; } [Kept] static void Reached () { } @@ -80,11 +77,11 @@ class TryFinallyInKeptBranchBeforeRemovedBranch { [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 @@ public static void Test () } } - [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 4a8427190c55..d0404ce3ca4c 100644 --- a/test/Mono.Linker.Tests.Cases/UnreachableBlock/UninitializedLocals.cs +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/UninitializedLocals.cs @@ -3,6 +3,8 @@ 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 516b056d9001..2b33fd2d3788 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