diff --git a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs index 75aaafaf5303..b4c90e88cfe8 100644 --- a/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs +++ b/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs @@ -15,77 +15,25 @@ namespace Mono.Linker.Steps { // // Evaluates simple properties or methods for constant expressions and - // then uses this information to remove unreachable conditional blocks. It does - // not do any inlining-like code changes. + // then uses this information to remove unreachable conditional blocks and + // inline collected constants. // public class UnreachableBlocksOptimizer { readonly LinkContext _context; - MethodDefinition? IntPtrSize, UIntPtrSize; - - readonly struct ProcessingNode - { - public ProcessingNode (MethodDefinition method, int lastAttemptStackVersion) - { - Method = method; - LastAttemptStackVersion = lastAttemptStackVersion; - } + readonly Dictionary _cache_method_results = new (2048); + readonly Stack _resursion_guard = new (); - public ProcessingNode (in ProcessingNode other, int newLastAttempStackVersion) - { - Method = other.Method; - LastAttemptStackVersion = newLastAttempStackVersion; - } - - public readonly MethodDefinition Method; - public readonly int LastAttemptStackVersion; - } - - // Stack of method nodes which are currently being processed. - // Implemented as linked list to allow easy referal to nodes and efficient moving of nodes within the list. - // The top of the stack is the first item in the list. - readonly LinkedList _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 (); } /// - /// Processes the specified and method and perform all branch removal optimizations on it. + /// Processes the specified method and perform all branch removal optimizations on it. /// When this returns it's guaranteed that the method has been optimized (if possible). - /// It may optimize other methods as well - those are remembered for future reuse. /// /// The method to process public void ProcessMethod (MethodDefinition method) @@ -96,16 +44,30 @@ 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)); + // + // Note: The inliner cannot run before reducer rewrites body as it + // would require another recomputing offsets due to instructions replacement + // done by inliner + // + var inliner = new CallInliner (method.Body, this); + inliner.RewriteBody (); } static bool IsMethodSupported (MethodDefinition method) @@ -125,152 +87,109 @@ 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; @@ -281,7 +200,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) @@ -290,186 +210,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) { - if (!_processedMethods.TryGetValue (method, out Instruction? methodValue)) { - if (_processingMethods.TryGetValue (method, out var stackNode)) { - // Method is already in the stack - not yet processed - // Move it to the top of the stack - _processingStack.Remove (stackNode); - _processingStack.AddFirst (stackNode); - - // Note that stack version is not changing - we're just postponing work, not resolving anything. - // There's no result available for this method, so return false. - constantResultInstruction = null; - return false; + _resursion_guard.Clear (); + return TryGetMethodCallResult (callee, _resursion_guard); + } + + MethodResult? TryGetMethodCallResult (in CalleePayload callee, Stack callStack) + { + MethodResult? value; + + MethodDefinition method = callee.Method; + if (!method.HasParameters || callee.HasUnknownArguments) { + if (!_cache_method_results.TryGetValue (method, out value) && !IsDeepStack (callStack)) { + value = AnalyzeMethodForConstantResult (callee, callStack); + _cache_method_results.Add (method, value); } - // Method is not yet in the stack - add it there - AddMethodForProcessing (method); - constantResultInstruction = null; - return false; + return value; } - if (methodValue == ProcessedUnchangedSentinel) { - // Method has been processed and no changes has been made to it. - // Also its value has not been needed yet. Now we need to know if it's constant, so run the analyzer on it - var result = AnalyzeMethodForConstantResult (method, instructions: null); - Debug.Assert (result is Instruction || result == null); - _processedMethods[method] = result ?? NonConstSentinel; - constantResultInstruction = result; - } else if (methodValue == NonConstSentinel) { - // Method was processed and found to not have a constant value - constantResultInstruction = null; - } else { - // Method was already processed and found to have a constant value - constantResultInstruction = methodValue; + return AnalyzeMethodForConstantResult (callee, callStack); + + static bool IsDeepStack (Stack callStack) => callStack.Count > 100; + } + + Instruction? GetSizeOfResult (TypeReference type) + { + MethodDefinition? sizeOfImpl = null; + + // + // sizeof (IntPtr) and sizeof (UIntPtr) are just aliases for IntPtr.Size and UIntPtr.Size + // which are simple static properties commonly overwritten. Instead of forcing C# code style + // we handle both via static get_Size method + // + if (type.MetadataType == MetadataType.UIntPtr) { + sizeOfImpl = (UIntPtrSize ??= FindSizeMethod (_context.TryResolve (type))); + } else if (type.MetadataType == MetadataType.IntPtr) { + sizeOfImpl = (IntPtrSize ??= FindSizeMethod (_context.TryResolve (type))); } - return true; + if (sizeOfImpl == null) + return null; + + return TryGetMethodCallResult (new CalleePayload (sizeOfImpl, Array.Empty ()))?.Instruction; } - bool TryInlineBodyDependencies (ref BodyReducer reducer, bool treatUnprocessedDependenciesAsNonConst, out bool changed) + static Instruction? EvaluateIntrinsicCall (MethodReference method, Instruction[] arguments) { - changed = false; - bool hasUnprocessedDependencies = false; - var instructions = reducer.Body.Instructions; - Instruction? targetResult; + // + // In theory any pure method could be executed via reflection but + // that would require loading all code path dependencies. + // For now we handle only few methods that help with core framework trimming + // + object? left, right; + if (method.DeclaringType.MetadataType == MetadataType.String) { + switch (method.Name) { + case "op_Equality": + case "op_Inequality": + case "Concat": + if (arguments.Length != 2) + return null; + + if (!GetConstantValue (arguments[0], out left) || + !GetConstantValue (arguments[1], out right)) + return null; + + if (left is string sleft && right is string sright) { + if (method.Name.Length == 6) // Concat case + return Instruction.Create (OpCodes.Ldstr, string.Concat (sleft, sright)); + + bool result = method.Name.Length == 11 ? sleft == sright : sleft != sright; + return Instruction.Create (OpCodes.Ldc_I4, result ? 1 : 0); // op_Equality / op_Inequality + } - for (int i = 0; i < instructions.Count; ++i) { - var instr = instructions[i]; - switch (instr.OpCode.Code) { + break; + } + } - case Code.Call: - case Code.Callvirt: - var md = _context.TryResolve ((MethodReference) instr.Operand); - if (md == null) - break; + return null; + } - if (md.CallingConvention == MethodCallingConvention.VarArg) - break; + static Instruction[]? GetArgumentsOnStack (MethodDefinition method, Collection instructions, int index) + { + if (!method.HasParameters) + return Array.Empty (); - bool explicitlyAnnotated = _context.Annotations.GetAction (md) == MethodAction.ConvertToStub; + Instruction[]? result = null; + for (int i = method.Parameters.Count, pos = 0; i != 0; --i, ++pos) { + Instruction instr = instructions[index - i]; + if (!IsConstantValue (instr)) + return null; - // Allow inlining results of instance methods which are explicitly annotated - // but don't allow inling results of any other instance method. - // See https://github.com/dotnet/linker/issues/1243 for discussion as to why. - // Also explicitly prevent inlining results of virtual methods. - if (!md.IsStatic && - (md.IsVirtual || !explicitlyAnnotated)) - break; + if (result == null) + result = new Instruction[method.Parameters.Count]; - if (md == reducer.Body.Method) { - // Special case for direct recursion - simply assume non-const value - // since we can't tell. - break; - } + result[pos] = instr; + } - if (!TryGetConstantResultForMethod (md, out targetResult)) { - if (!treatUnprocessedDependenciesAsNonConst) - hasUnprocessedDependencies = true; - break; - } + if (result != null && HasJumpIntoTargetRange (instructions, index - method.Parameters.Count + 1, index)) + return null; - if (targetResult == null || hasUnprocessedDependencies) { - // Even if const is detected, there's no point in rewriting anything - // if we've found unprocessed dependency since the results of this scan will - // be thrown away (we back off and wait for the unprocessed dependency to be processed first). - break; - } + return result; - // - // Do simple arguments stack removal by replacing argument expressions with nops to hide - // them for the constant evaluator. For cases which require full stack understanding the - // logic won't work and will leave more opcodes on the stack and constant won't be propagated - // - int depth = md.Parameters.Count; - if (!md.IsStatic) - ++depth; + static bool IsConstantValue (Instruction instr) + { + switch (instr.OpCode.Code) { + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4: + case Code.Ldc_I4_S: + case Code.Ldc_I4_M1: + case Code.Ldc_I8: + case Code.Ldc_R4: + case Code.Ldc_R8: + case Code.Ldnull: + case Code.Ldstr: + return true; + } + + 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; + } + } - if (depth != 0) - reducer.RewriteToNop (i - 1, depth); + static MethodDefinition? FindSizeMethod (TypeDefinition? type) + { + if (type == null) + return null; - reducer.Rewrite (i, targetResult); - changed = true; - break; + return type.Methods.First (l => !l.HasParameters && l.IsStatic && l.Name == "get_Size"); + } - case Code.Ldsfld: - var ftarget = (FieldReference) instr.Operand; - var field = _context.TryResolve (ftarget); - if (field == null) - break; + readonly struct CallInliner + { + readonly MethodBody body; + readonly UnreachableBlocksOptimizer optimizer; - if (_context.Annotations.TryGetFieldUserValue (field, out object? value)) { - targetResult = CodeRewriterStep.CreateConstantResultInstruction (_context, field.FieldType, value); - if (targetResult == null) + 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); + 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) + { + // + // Instance methods called on `this` have no side-effects + // + if (instructions[index - 1].OpCode.Code == Code.Ldarg_0) + return !body.Method.IsStatic; - static MethodDefinition? FindSizeMethod (TypeDefinition? type) - { - if (type == null) - return null; + // More cases can be added later + return false; + } - return type.Methods.First (l => !l.HasParameters && l.IsStatic && l.Name == "get_Size"); + static bool IsCalledWithoutSideEffects (MethodDefinition method, Collection instructions, int index) + { + for (int i = 1; i <= method.Parameters.Count; ++i) { + if (!IsSideEffectFreeLoad (instructions[index - i])) + return false; + } + + return true; + } } struct BodyReducer @@ -498,14 +555,21 @@ 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 @@ -526,10 +590,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) { @@ -642,7 +704,7 @@ void RewriteToNop (int index) public bool RewriteBody () { if (FoldedInstructions == null) - return false; + InitializeFoldedInstruction (); if (!RemoveConditions ()) return false; @@ -652,10 +714,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; @@ -676,6 +735,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))?.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 (); @@ -928,90 +1061,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) @@ -1036,47 +1118,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) { @@ -1094,27 +1135,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); } } @@ -1145,7 +1166,7 @@ public BodySweeper (MethodBody body, BitArray reachable, List? public int InstructionsReplaced { get; set; } - public bool Initialize () + public void Initialize () { var instrs = body.Instructions; @@ -1172,7 +1193,6 @@ public bool Initialize () } ilprocessor = body.GetLinkerILProcessor (); - return true; } public void Process (List? conditionInstrsToRemove, out List? sentinelNops) @@ -1335,81 +1355,62 @@ 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) { + // + // Handles both backward and forward jumps + // if (instr != jmpTarget) continue; @@ -1418,11 +1419,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: @@ -1430,6 +1431,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: @@ -1504,8 +1559,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 ()) @@ -1520,6 +1721,45 @@ 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: + // We are not inlining hence can evaluate anything and decide later + // how to handle sitation when the result is not deterministic + SideEffectFreeResult = false; + return true; + } + } + + bool EvaluateConditionalJump (Instruction instr, out Instruction? target) + { + if (!GetOperandsConstantValues (out object? right, out object? left)) { + target = null; + return false; + } + + if (left is int lint && right is int rint) { + if (IsComparisonAlwaysTrue (instr.OpCode, lint, rint)) + target = (Instruction) instr.Operand; + else + target = null; + + return true; + } + + target = null; + return false; + } + [MemberNotNullWhen (true, "Result")] bool ConvertStackToResult () { @@ -1554,6 +1794,28 @@ 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)) @@ -1566,6 +1828,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) @@ -1584,5 +1898,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