/// <summary> /// Lift a C# comparison. /// /// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c>. /// Otherwise, the output instruction should evaluate to the same value as the input instruction. /// The output instruction should have the same side-effects (incl. exceptions being thrown) as the input instruction. /// This means unlike LiftNormal(), we cannot rely on the input instruction not being evaluated if /// a variable is <c>null</c>. /// </summary> Comp LiftCSharpComparison(Comp comp, ComparisonKind newComparisonKind) { var(left, leftBits) = DoLift(comp.Left); var(right, rightBits) = DoLift(comp.Right); if (left != null && right == null && SemanticHelper.IsPure(comp.Right.Flags)) { // Embed non-nullable pure expression in lifted expression. right = comp.Right.Clone(); } if (left == null && right != null && SemanticHelper.IsPure(comp.Left.Flags)) { // Embed non-nullable pure expression in lifted expression. left = comp.Left.Clone(); } // due to the restrictions on side effects, we only allow instructions that are pure after lifting. // (we can't check this before lifting due to the calls to GetValueOrDefault()) if (left != null && right != null && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags)) { var bits = leftBits ?? rightBits; if (rightBits != null) { bits.UnionWith(rightBits); } if (!bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result return(null); } context.Step("NullableLiftingTransform: C# comparison", comp); return(new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, comp.InputType, comp.Sign, left, right)); } return(null); }
public void Run(ILFunction function, ILTransformContext context) { try { if (this.context != null || this.currentFunction != null) { throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage)); } this.context = context; var decompilationContext = new SimpleTypeResolveContext(context.Function.Method); // Traverse nested functions in post-order: // Inner functions are transformed before outer functions foreach (var f in function.Descendants.OfType <ILFunction>()) { foreach (var v in f.Variables.ToArray()) { if (HandleMonoStateMachine(function, v, decompilationContext, f)) { continue; } if (!(v.IsSingleDefinition && v.StoreInstructions.SingleOrDefault() is StLoc inst)) { continue; } if (IsClosureInit(inst, out ITypeDefinition closureType)) { // TODO : figure out whether it is a mono compiled closure, without relying on the type name bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey"); displayClasses.Add(inst.Variable, new DisplayClass { IsMono = isMono, Initializer = inst, Variable = v, Definition = closureType, Variables = new Dictionary <IField, DisplayClassVariable>(), CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : inst.Variable.CaptureScope }); instructionsToRemove.Add(inst); } } foreach (var displayClass in displayClasses.Values.OrderByDescending(d => d.Initializer.StartILOffset).ToArray()) { context.Step($"Transform references to " + displayClass.Variable, displayClass.Initializer); this.currentFunction = f; VisitILFunction(f); } } context.Step($"Remove instructions", function); foreach (var store in instructionsToRemove) { if (store.Parent is Block containingBlock) { containingBlock.Instructions.Remove(store); } } } finally { instructionsToRemove.Clear(); displayClasses.Clear(); this.context = null; this.currentFunction = null; } }
private void ReplaceReferencesToDisplayClassThis(Dictionary <MethodDefinitionHandle, LocalFunctionInfo> .ValueCollection localFunctions) { foreach (var info in localFunctions) { var localFunction = info.Definition; if (localFunction.Method.IsStatic) { continue; } var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); if (thisVar == null) { continue; } var compatibleArgument = FindCompatibleArgument( info, info.UseSites.OfType <CallInstruction>().Select(u => u.Arguments[0]).ToArray(), ignoreStructure: true ); if (compatibleArgument == null) { continue; } context.Step($"Replace 'this' with {compatibleArgument}", localFunction); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(compatibleArgument, thisVar)); DetermineCaptureAndDeclarationScope(info, -1, compatibleArgument); } }
static void RunOnBlock(Block block, ILTransformContext context, HashSet <ILVariable> splitVariables = null) { for (int i = 0; i < block.Instructions.Count; i++) { if (block.Instructions[i].MatchStLoc(out ILVariable v, out ILInstruction copiedExpr)) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { // dead store to stack if (SemanticHelper.IsPure(copiedExpr.Flags)) { // no-op -> delete context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); block.Instructions.RemoveAt(i); // This can open up new inlining opportunities: int c = ILInlining.InlineInto(block, i, InliningOptions.None, context: context); i -= c + 1; } else { // evaluate the value for its side-effects context.Step("remove dead store to stack: evaluate the value for its side-effects", block.Instructions[i]); copiedExpr.AddILRange(block.Instructions[i]); block.Instructions[i] = copiedExpr; } } else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr, splitVariables)) { DoPropagate(v, copiedExpr, block, ref i, context); } } } }
void IILTransform.Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AnonymousMethods) { return; } this.context = context; this.decompilationContext = new SimpleTypeResolveContext(function.Method); var orphanedVariableInits = new List <ILInstruction>(); var targetsToReplace = new List <IInstructionWithVariableOperand>(); var translatedDisplayClasses = new HashSet <ITypeDefinition>(); var cancellationToken = context.CancellationToken; foreach (var inst in function.Descendants) { cancellationToken.ThrowIfCancellationRequested(); if (inst is NewObj call) { context.StepStartGroup($"TransformDelegateConstruction {call.StartILOffset}", call); ILFunction f = TransformDelegateConstruction(call, out ILInstruction target); if (f != null && target is IInstructionWithVariableOperand instWithVar) { if (instWithVar.Variable.Kind == VariableKind.Local) { instWithVar.Variable.Kind = VariableKind.DisplayClassLocal; } targetsToReplace.Add(instWithVar); } context.StepEndGroup(); } if (inst.MatchStLoc(out ILVariable targetVariable, out ILInstruction value)) { var newObj = value as NewObj; // TODO : it is probably not a good idea to remove *all* display-classes // is there a way to minimize the false-positives? if (newObj != null && IsInSimpleDisplayClass(newObj.Method)) { targetVariable.CaptureScope = BlockContainer.FindClosestContainer(inst); targetsToReplace.Add((IInstructionWithVariableOperand)inst); translatedDisplayClasses.Add(newObj.Method.DeclaringTypeDefinition); } } } foreach (var target in targetsToReplace.OrderByDescending(t => ((ILInstruction)t).StartILOffset)) { context.Step($"TransformDisplayClassUsages {target.Variable}", (ILInstruction)target); function.AcceptVisitor(new TransformDisplayClassUsages(function, target, target.Variable.CaptureScope, orphanedVariableInits, translatedDisplayClasses)); } context.Step($"Remove orphanedVariableInits", function); foreach (var store in orphanedVariableInits) { if (store.Parent is Block containingBlock) { containingBlock.Instructions.Remove(store); } } }
/// <summary> /// The transform works like this: /// /// <para> /// local functions can either be used in method calls, i.e., call and callvirt instructions, /// or can be used as part of the "delegate construction" pattern, i.e., <c>newobj Delegate(<target-expression>, ldftn <method>)</c>. /// </para> /// As local functions can be declared practically anywhere, we have to take a look at all use-sites and infer the declaration location from that. Use-sites can be call, callvirt and ldftn instructions. /// After all use-sites are collected we construct the ILAst of the local function and add it to the parent function. /// Then all use-sites of the local-function are transformed to a call to the <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>. /// In a next step we handle all nested local functions. /// After all local functions are transformed, we move all local functions that capture any variables to their respective declaration scope. /// </summary> public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.LocalFunctions) { return; } this.context = context; this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary <MethodDefinitionHandle, LocalFunctionInfo>(); var cancellationToken = context.CancellationToken; // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. FindUseSites(function, context, localFunctions); foreach (var(method, info) in localFunctions) { cancellationToken.ThrowIfCancellationRequested(); var firstUseSite = info.UseSites[0]; context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; if (!localFunction.Method.IsStatic) { var target = firstUseSite.Arguments[0]; context.Step($"Replace 'this' with {target}", localFunction); var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } foreach (var useSite in info.UseSites) { context.Step("Transform use site at " + useSite.StartILOffset, useSite); if (useSite.OpCode == OpCode.NewObj) { TransformToLocalFunctionReference(localFunction, useSite); } else { DetermineCaptureAndDeclarationScope(localFunction, useSite); TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); } } if (localFunction.DeclarationScope == null) { localFunction.DeclarationScope = (BlockContainer)function.Body; } else if (localFunction.DeclarationScope != function.Body && localFunction.DeclarationScope.Parent is ILFunction declaringFunction) { function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } } finally { context.StepEndGroup(); } } }
protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); if (inst.Variable.Kind == VariableKind.Local && inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 0 && inst.Value is StLoc) { context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst); inst.ReplaceWith(inst.Value); } }
public void Run(ILFunction function, ILTransformContext context) { try { if (this.context != null || this.currentFunction != null) { throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage)); } this.context = context; var decompilationContext = new SimpleTypeResolveContext(context.Function.Method); // Traverse nested functions in post-order: // Inner functions are transformed before outer functions foreach (var f in function.Descendants.OfType <ILFunction>()) { foreach (var v in f.Variables.ToArray()) { if (HandleMonoStateMachine(function, v, decompilationContext, f)) { continue; } if (IsClosure(v, out ITypeDefinition closureType, out var inst)) { AddOrUpdateDisplayClass(f, v, closureType, inst, localFunctionClosureParameter: false); } if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p, decompilationContext)) { AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body, localFunctionClosureParameter: true); } } foreach (var displayClass in displayClasses.Values.OrderByDescending(d => d.Initializer.StartILOffset).ToArray()) { context.Step($"Transform references to " + displayClass.Variable, displayClass.Initializer); this.currentFunction = f; VisitILFunction(f); } } if (instructionsToRemove.Count > 0) { context.Step($"Remove instructions", function); foreach (var store in instructionsToRemove) { if (store.Parent is Block containingBlock) { containingBlock.Instructions.Remove(store); } } } RemoveDeadVariableInit.ResetHasInitialValueFlag(function, context); } finally { instructionsToRemove.Clear(); displayClasses.Clear(); this.context = null; this.currentFunction = null; } }
ILInstruction Lift(IfInstruction ifInst, ILInstruction trueInst, ILInstruction falseInst) { if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) { return(null); } if (!MatchNull(falseInst, utype)) { return(null); } ILInstruction lifted; if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) { // v != null ? call GetValueOrDefault(ldloca v) : null // => conv.nop.lifted(ldloc v) // This case is handled separately from DoLift() because // that doesn't introduce nop-conversions. context.Step("if => conv.nop.lifted", ifInst); var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type); lifted = new Conv( new LdLoc(nullableVars[0]), inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), checkForOverflow: false, isLifted: true ) { ILRange = ifInst.ILRange }; } else { context.Step("NullableLiftingTransform.DoLift", ifInst); BitSet bits; (lifted, bits) = DoLift(exprToLift); if (lifted != null && !bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result lifted = null; } } if (lifted != null) { Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted && liftable.UnderlyingResultType == exprToLift.ResultType); } return(lifted); }
internal static bool DoInline(ILVariable v, StLoc originalStore, LdLoc loadInst, InliningOptions options, ILTransformContext context) { if ((options & InliningOptions.Aggressive) == 0 && originalStore.ILStackWasEmpty) { return(false); } context.Step($"Introduce named argument '{v.Name}'", originalStore); var call = (CallInstruction)loadInst.Parent; if (!(call.Parent is Block namedArgBlock) || namedArgBlock.Kind != BlockKind.CallWithNamedArgs) { // create namedArgBlock: namedArgBlock = new Block(BlockKind.CallWithNamedArgs); call.ReplaceWith(namedArgBlock); namedArgBlock.FinalInstruction = call; if (call.IsInstanceCall) { IType thisVarType = call.Method.DeclaringType; if (CallInstruction.ExpectedTypeForThisPointer(thisVarType) == StackType.Ref) { thisVarType = new ByReferenceType(thisVarType); } var function = call.Ancestors.OfType <ILFunction>().First(); var thisArgVar = function.RegisterVariable(VariableKind.NamedArgument, thisVarType, "this_arg"); namedArgBlock.Instructions.Add(new StLoc(thisArgVar, call.Arguments[0])); call.Arguments[0] = new LdLoc(thisArgVar); } } v.Kind = VariableKind.NamedArgument; namedArgBlock.Instructions.Insert(call.IsInstanceCall ? 1 : 0, originalStore); return(true); }
public void Run(ILFunction function, ILTransformContext context) { try { if (this.context != null) { throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage)); } this.context = context; var decompilationContext = new SimpleTypeResolveContext(context.Function.Method); // Traverse nested functions in post-order: // Inner functions are transformed before outer functions foreach (var f in function.Descendants.OfType <ILFunction>()) { foreach (var v in f.Variables.ToArray()) { if (context.Settings.YieldReturn && HandleMonoStateMachine(function, v, decompilationContext, f)) { continue; } if ((context.Settings.AnonymousMethods || context.Settings.ExpressionTrees) && IsClosure(context, v, out ITypeDefinition closureType, out var inst)) { if (!CanRemoveAllReferencesTo(context, v)) { continue; } if (inst is StObj || inst is StLoc) { instructionsToRemove.Add(inst); } AddOrUpdateDisplayClass(f, v, closureType, inst, localFunctionClosureParameter: false); continue; } if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p, decompilationContext)) { AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body, localFunctionClosureParameter: true); continue; } AnalyzeUseSites(v); } } VisitILFunction(function); if (instructionsToRemove.Count > 0) { context.Step($"Remove instructions", function); foreach (var store in instructionsToRemove) { if (store.Parent is Block containingBlock) { containingBlock.Instructions.Remove(store); } } } RemoveDeadVariableInit.ResetHasInitialValueFlag(function, context); } finally { instructionsToRemove.Clear(); displayClasses.Clear(); fieldAssignmentsWithVariableValue.Clear(); this.context = null; } }
static void DoPropagate(ILVariable v, ILInstruction copiedExpr, Block block, ref int i, ILTransformContext context) { context.Step($"Copy propagate {v.Name}", copiedExpr); // un-inline the arguments of the ldArg instruction ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Children.Count]; for (int j = 0; j < uninlinedArgs.Length; j++) { var arg = copiedExpr.Children[j]; var type = context.TypeSystem.Compilation.FindType(arg.ResultType.ToKnownTypeCode()); uninlinedArgs[j] = new ILVariable(VariableKind.StackSlot, type, arg.ResultType, arg.ILRange.Start) { Name = "C_" + arg.ILRange.Start }; block.Instructions.Insert(i++, new StLoc(uninlinedArgs[j], arg)); } CollectionExtensions.AddRange(v.Function.Variables, uninlinedArgs); // perform copy propagation: foreach (var expr in v.LoadInstructions.ToArray()) { var clone = copiedExpr.Clone(); for (int j = 0; j < uninlinedArgs.Length; j++) { clone.Children[j].ReplaceWith(new LdLoc(uninlinedArgs[j])); } expr.ReplaceWith(clone); } block.Instructions.RemoveAt(i); int c = ILInlining.InlineInto(block, i, aggressive: false, context: context); i -= c + 1; }
/// <summary> /// Introduce a named argument for 'arg' and evaluate it before the other arguments /// (except for the "this" pointer) /// </summary> internal static void IntroduceNamedArgument(ILInstruction arg, ILTransformContext context) { var call = (CallInstruction)arg.Parent; Debug.Assert(context.Function == call.Ancestors.OfType <ILFunction>().First()); var v = context.Function.RegisterVariable(VariableKind.NamedArgument, arg.ResultType); context.Step($"Introduce named argument '{v.Name}'", arg); if (!(call.Parent is Block namedArgBlock) || namedArgBlock.Kind != BlockKind.CallWithNamedArgs) { // create namedArgBlock: namedArgBlock = new Block(BlockKind.CallWithNamedArgs); call.ReplaceWith(namedArgBlock); namedArgBlock.FinalInstruction = call; if (call.IsInstanceCall) { IType thisVarType = call.Method.DeclaringType; if (CallInstruction.ExpectedTypeForThisPointer(thisVarType) == StackType.Ref) { thisVarType = new ByReferenceType(thisVarType); } var thisArgVar = context.Function.RegisterVariable(VariableKind.NamedArgument, thisVarType, "this_arg"); namedArgBlock.Instructions.Add(new StLoc(thisArgVar, call.Arguments[0])); call.Arguments[0] = new LdLoc(thisArgVar); } } int argIndex = arg.ChildIndex; Debug.Assert(call.Arguments[argIndex] == arg); namedArgBlock.Instructions.Insert(call.IsInstanceCall ? 1 : 0, new StLoc(v, arg)); call.Arguments[argIndex] = new LdLoc(v); }
private bool TryTransform(Block block, int i, ILTransformContext context) { if (block.Instructions[i] is not StLoc { Variable : var s, Value : LdLoca { Variable : var v } } inst1) { return(false); } if (block.Instructions.ElementAtOrDefault(i + 1) is not StObj inst2) { return(false); } if (!(inst2.Target.MatchLdLoc(s) && TypeUtils.IsCompatibleTypeForMemoryAccess(v.Type, inst2.Type) && inst2.UnalignedPrefix == 0 && !inst2.IsVolatile && inst2.Value is DefaultValue)) { return(false); } context.Step("LdLocaDupInitObjTransform", inst1); block.Instructions[i] = new StLoc(v, inst2.Value).WithILRange(inst2); block.Instructions[i + 1] = inst1; return(true); }
public void Run(ILFunction function, ILTransformContext context) { foreach (var catchBlock in function.Descendants.OfType <TryCatchHandler>()) { if (catchBlock.Filter is BlockContainer container && MatchCatchWhenEntryPoint(catchBlock.Variable, container, container.EntryPoint, out var exceptionType, out var exceptionSlot, out var whenConditionBlock) && exceptionType.GetStackType() == catchBlock.Variable.StackType) { // set exceptionType catchBlock.Variable.Type = exceptionType; // Block entryPoint (incoming: 1) { // stloc temp(isinst exceptionType(ldloc exceptionVar)) // if (comp(ldloc temp != ldnull)) br whenConditionBlock // br falseBlock // } // => // Block entryPoint (incoming: 1) { // stloc temp(ldloc exceptionSlot) // br whenConditionBlock // } var instructions = container.EntryPoint.Instructions; if (instructions.Count == 3) { // stloc temp(isinst exceptionType(ldloc exceptionVar)) // if (comp(ldloc temp != ldnull)) br whenConditionBlock // br falseBlock context.Step($"Detected catch-when for {catchBlock.Variable.Name} (extra store)", instructions[0]); ((StLoc)instructions[0]).Value = exceptionSlot; instructions[1].ReplaceWith(new Branch(whenConditionBlock)); instructions.RemoveAt(2); container.SortBlocks(deleteUnreachableBlocks: true); } else if (instructions.Count == 2) { // if (comp(isinst exceptionType(ldloc exceptionVar) != ldnull)) br whenConditionBlock // br falseBlock context.Step($"Detected catch-when for {catchBlock.Variable.Name}", instructions[0]); instructions[0].ReplaceWith(new Branch(whenConditionBlock)); instructions.RemoveAt(1); container.SortBlocks(deleteUnreachableBlocks: true); } PropagateExceptionVariable(context, catchBlock); } } }
internal static bool StObjToStLoc(StObj inst, ILTransformContext context) { if (inst.Target.MatchLdLoca(out ILVariable v) && TypeUtils.IsCompatibleTypeForMemoryAccess(new ByReferenceType(v.Type), inst.Type) && inst.UnalignedPrefix == 0 && !inst.IsVolatile) { context.Step($"stobj(ldloca {v.Name}, ...) => stloc {v.Name}(...)", inst); inst.ReplaceWith(new StLoc(v, inst.Value)); return(true); } return(false); }
internal static bool LdObjToLdLoc(LdObj inst, ILTransformContext context) { if (inst.Target.MatchLdLoca(out ILVariable v) && TypeUtils.IsCompatibleTypeForMemoryAccess(v.Type, inst.Type) && inst.UnalignedPrefix == 0 && !inst.IsVolatile) { context.Step($"ldobj(ldloca {v.Name}) => ldloc {v.Name}", inst); inst.ReplaceWith(new LdLoc(v).WithILRange(inst)); return(true); } return(false); }
/// <summary> /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> public static bool InlineOne(StLoc stloc, InliningOptions options, ILTransformContext context) { ILVariable v = stloc.Variable; Block block = (Block)stloc.Parent; int pos = stloc.ChildIndex; if (DoInline(v, stloc.Value, block.Instructions.ElementAtOrDefault(pos + 1), options, context)) { // Assign the ranges of the stloc instruction: stloc.Value.AddILRange(stloc); // Remove the stloc instruction: Debug.Assert(block.Instructions[pos] == stloc); block.Instructions.RemoveAt(pos); return(true); } else if (v.LoadCount == 0 && v.AddressCount == 0) { // The variable is never loaded if (SemanticHelper.IsPure(stloc.Value.Flags)) { // Remove completely if the instruction has no effects // (except for reading locals) context.Step("Remove dead store without side effects", stloc); block.Instructions.RemoveAt(pos); return(true); } else if (v.Kind == VariableKind.StackSlot) { context.Step("Remove dead store, but keep expression", stloc); // Assign the ranges of the stloc instruction: stloc.Value.AddILRange(stloc); // Remove the stloc, but keep the inner expression stloc.ReplaceWith(stloc.Value); return(true); } } return(false); }
/// <summary> /// Inlines 'expr' into 'next', if possible. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstruction next, InliningOptions options, ILTransformContext context) { var r = FindLoadInNext(next, v, inlinedExpression, options); if (r.Type == FindResultType.Found || r.Type == FindResultType.NamedArgument) { var loadInst = r.LoadInst; if (loadInst.OpCode == OpCode.LdLoca) { if (!IsGeneratedValueTypeTemporary((LdLoca)loadInst, v, inlinedExpression)) { return(false); } } else { Debug.Assert(loadInst.OpCode == OpCode.LdLoc); bool aggressive = (options & InliningOptions.Aggressive) != 0; if (!aggressive && v.Kind != VariableKind.StackSlot && !NonAggressiveInlineInto(next, r, inlinedExpression, v)) { return(false); } } if (r.Type == FindResultType.NamedArgument) { NamedArgumentTransform.IntroduceNamedArgument(r.CallArgument, context); // Now that the argument is evaluated early, we can inline as usual } context.Step($"Inline variable '{v.Name}'", inlinedExpression); // Assign the ranges of the ldloc instruction: inlinedExpression.AddILRange(loadInst); if (loadInst.OpCode == OpCode.LdLoca) { // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' // to preserve the semantics of the compiler-generated temporary Debug.Assert(((LdLoca)loadInst).Variable == v); loadInst.ReplaceWith(new AddressOf(inlinedExpression, v.Type)); } else { loadInst.ReplaceWith(inlinedExpression); } return(true); } return(false); }
private bool DoTransform(Block block, ILTransformContext context) { if (!MatchBlock1(block, out var s, out int value, out var br)) { return(false); } if (!MatchBlock2(br.TargetBlock, s, value, out var exitInst)) { return(false); } context.Step("RemoveInfeasiblePath", br); br.ReplaceWith(exitInst.Clone()); s.RemoveIfRedundant = true; return(true); }
/// <summary> /// Inlines 'expr' into 'next', if possible. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstruction next, InliningOptions options, ILTransformContext context) { var r = FindLoadInNext(next, v, inlinedExpression, out var loadInst); if (r == FindResult.Found) { if (loadInst.OpCode == OpCode.LdLoca) { if (!IsGeneratedValueTypeTemporary(next, (LdLoca)loadInst, v, inlinedExpression)) { return(false); } } else { Debug.Assert(loadInst.OpCode == OpCode.LdLoc); bool aggressive = (options & InliningOptions.Aggressive) != 0; if (!aggressive && v.Kind != VariableKind.StackSlot && !NonAggressiveInlineInto(next, loadInst, inlinedExpression, v)) { return(false); } } context.Step($"Inline variable '{v.Name}'", inlinedExpression); // Assign the ranges of the ldloc instruction: inlinedExpression.AddILRange(loadInst.ILRange); if (loadInst.OpCode == OpCode.LdLoca) { // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' // to preserve the semantics of the compiler-generated temporary loadInst.ReplaceWith(new AddressOf(inlinedExpression)); } else { loadInst.ReplaceWith(inlinedExpression); } return(true); } else if (r == FindResult.NamedArgument && (options & InliningOptions.IntroduceNamedArguments) != 0) { return(NamedArgumentTransform.DoInline(v, (StLoc)inlinedExpression.Parent, (LdLoc)loadInst, options, context)); } return(false); }
internal static ILInstruction HandleCall(Call inst, ILTransformContext context) { if (inst.Method.IsConstructor && !inst.Method.IsStatic && inst.Method.DeclaringType.Kind == TypeKind.Struct) { Debug.Assert(inst.Arguments.Count == inst.Method.Parameters.Count + 1); context.Step("Transform call to struct constructor", inst); // call(ref, ...) // => stobj(ref, newobj(...)) var newObj = new NewObj(inst.Method); newObj.ILRange = inst.ILRange; newObj.Arguments.AddRange(inst.Arguments.Skip(1)); var expr = new StObj(inst.Arguments[0], newObj, inst.Method.DeclaringType); inst.ReplaceWith(expr); return(expr); } return(null); }
/// <summary> /// stobj(target, binary.op(ldobj(target), ...)) /// where target is pure /// => compound.op(target, ...) /// </summary> /// <remarks> /// Called by ExpressionTransforms. /// </remarks> internal static bool HandleStObjCompoundAssign(StObj inst, ILTransformContext context) { if (!(UnwrapSmallIntegerConv(inst.Value, out var conv) is BinaryNumericInstruction binary)) { return(false); } if (!(binary.Left is LdObj ldobj)) { return(false); } if (!inst.Target.Match(ldobj.Target).Success) { return(false); } if (!SemanticHelper.IsPure(ldobj.Target.Flags)) { return(false); } // ldobj.Type may just be 'int' (due to ldind.i4) when we're actually operating on a 'ref MyEnum'. // Try to determine the real type of the object we're modifying: IType targetType = ldobj.Target.InferType(); if (targetType.Kind == TypeKind.Pointer || targetType.Kind == TypeKind.ByReference) { targetType = ((TypeWithElementType)targetType).ElementType; if (targetType.Kind == TypeKind.Unknown || targetType.GetSize() != ldobj.Type.GetSize()) { targetType = ldobj.Type; } } else { targetType = ldobj.Type; } if (!ValidateCompoundAssign(binary, conv, targetType)) { return(false); } context.Step("compound assignment", inst); inst.ReplaceWith(new CompoundAssignmentInstruction( binary, binary.Left, binary.Right, targetType, CompoundAssignmentType.EvaluatesToNewValue)); return(true); }
internal static bool LdObjToLdLoc(LdObj inst, ILTransformContext context) { if (inst.Target.MatchLdLoca(out ILVariable v) && TypeUtils.IsCompatibleTypeForMemoryAccess(v.Type, inst.Type) && inst.UnalignedPrefix == 0 && !inst.IsVolatile) { context.Step($"ldobj(ldloca {v.Name}) => ldloc {v.Name}", inst); ILInstruction replacement = new LdLoc(v).WithILRange(inst); if (v.StackType == StackType.Unknown && inst.Type.Kind != TypeKind.Unknown) { replacement = new Conv(replacement, inst.Type.ToPrimitiveType(), checkForOverflow: false, Sign.None); } inst.ReplaceWith(replacement); return(true); } return(false); }
protected internal override void VisitLdFlda(LdFlda inst) { base.VisitLdFlda(inst); // Get display class info if (!IsDisplayClassFieldAccess(inst, out _, out DisplayClass displayClass, out IField field)) return; var keyField = (IField)field.MemberDefinition; var v = displayClass.VariablesToDeclare[keyField]; context.Step($"Replace {field.Name} with captured variable {v.Name}", inst); ILVariable variable = v.GetOrDeclare(); inst.ReplaceWith(new LdLoca(variable).WithILRange(inst)); // add captured variable to all descendant functions from the declaring function to this use-site function foreach (var f in currentFunctions) { if (f == variable.Function) break; f.CapturedVariables.Add(variable); } }
/// <summary> /// Inlines 'expr' into 'next', if possible. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstruction next, bool aggressive, ILTransformContext context) { ILInstruction loadInst; if (FindLoadInNext(next, v, inlinedExpression, out loadInst) == true) { if (loadInst.OpCode == OpCode.LdLoca) { if (!IsGeneratedValueTypeTemporary(next, loadInst.Parent, loadInst.ChildIndex, v, inlinedExpression)) { return(false); } } else { Debug.Assert(loadInst.OpCode == OpCode.LdLoc); if (!aggressive && v.Kind != VariableKind.StackSlot && !NonAggressiveInlineInto(next, loadInst, inlinedExpression, v)) { return(false); } } context.Step($"Inline variable '{v.Name}'", inlinedExpression); // Assign the ranges of the ldloc instruction: inlinedExpression.AddILRange(loadInst.ILRange); if (loadInst.OpCode == OpCode.LdLoca) { // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' // to preserve the semantics of the compiler-generated temporary loadInst.ReplaceWith(new AddressOf(inlinedExpression)); } else { loadInst.ReplaceWith(inlinedExpression); } return(true); } return(false); }
internal static void FixComparisonKindLdNull(Comp inst, ILTransformContext context) { if (inst.IsLifted) { return; } if (inst.Right.MatchLdNull()) { if (inst.Kind == ComparisonKind.GreaterThan) { context.Step("comp(left > ldnull) => comp(left != ldnull)", inst); inst.Kind = ComparisonKind.Inequality; } else if (inst.Kind == ComparisonKind.LessThanOrEqual) { context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst); inst.Kind = ComparisonKind.Equality; } } else if (inst.Left.MatchLdNull()) { if (inst.Kind == ComparisonKind.LessThan) { context.Step("comp(ldnull < right) => comp(ldnull != right)", inst); inst.Kind = ComparisonKind.Inequality; } else if (inst.Kind == ComparisonKind.GreaterThanOrEqual) { context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst); inst.Kind = ComparisonKind.Equality; } } if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out var arg, out var type) && type.Kind == TypeKind.TypeParameter) { if (inst.Kind == ComparisonKind.Equality) { context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst); inst.Left = arg; } if (inst.Kind == ComparisonKind.Inequality) { context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst); inst.Left = arg; } } }
/// <summary> /// Called by expression transforms. /// Handles the `array[System.Index]` cases. /// </summary> public static bool HandleLdElema(LdElema ldelema, ILTransformContext context) { if (!context.Settings.Ranges) { return(false); } if (!ldelema.Array.MatchLdLoc(out ILVariable array)) { return(false); } if (ldelema.Indices.Count != 1) { return(false); // the index/range feature doesn't support multi-dimensional arrays } var index = ldelema.Indices[0]; if (index is CallInstruction call && call.Method.Name == "GetOffset" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) { // ldelema T(ldloc array, call GetOffset(..., ldlen.i4(ldloc array))) // -> withsystemindex.ldelema T(ldloc array, ...) if (call.Arguments.Count != 2) { return(false); } if (!(call.Arguments[1].MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array))) { return(false); } context.Step("ldelema with System.Index", ldelema); foreach (var node in call.Arguments[1].Descendants) { ldelema.AddILRange(node); } ldelema.AddILRange(call); ldelema.WithSystemIndex = true; // The method call had a `ref System.Index` argument for the this pointer, but we want a `System.Index` by-value. ldelema.Indices[0] = new LdObj(call.Arguments[0], call.Method.DeclaringType); return(true); }
internal static void AddressOfLdLocToLdLoca(LdObj inst, ILTransformContext context) { // ldobj(...(addressof(ldloc V))) where ... can be zero or more ldflda instructions // => // ldobj(...(ldloca V)) var temp = inst.Target; var range = temp.ILRanges; while (temp.MatchLdFlda(out var ldfldaTarget, out _)) { temp = ldfldaTarget; range = range.Concat(temp.ILRanges); } if (temp.MatchAddressOf(out var addressOfTarget, out _) && addressOfTarget.MatchLdLoc(out var v)) { context.Step($"ldobj(...(addressof(ldloca {v.Name}))) => ldobj(...(ldloca {v.Name}))", inst); var replacement = new LdLoca(v).WithILRange(addressOfTarget); foreach (var r in range) { replacement = replacement.WithILRange(r); } temp.ReplaceWith(replacement); } }
protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); if (inst.Parent is Block && inst.Variable.IsSingleDefinition) { if (inst.Variable.Kind == VariableKind.Local && inst.Variable.LoadCount == 0 && inst.Value is StLoc) { context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst); inst.ReplaceWith(inst.Value); return; } if (inst.Value.MatchLdLoc(out var displayClassVariable) && displayClasses.TryGetValue(displayClassVariable, out var displayClass)) { context.Step($"Found copy-assignment of display-class variable {displayClassVariable.Name}", inst); displayClasses.Add(inst.Variable, displayClass); instructionsToRemove.Add(inst); return; } } }