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); }
/// <summary> /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction, if possible. /// </summary> public static bool InlineOneIfPossible(Block block, int pos, InliningOptions options, ILTransformContext context) { context.CancellationToken.ThrowIfCancellationRequested(); StLoc stloc = block.Instructions[pos] as StLoc; if (stloc == null || stloc.Variable.Kind == VariableKind.PinnedLocal) { return(false); } ILVariable v = stloc.Variable; // ensure the variable is accessed only a single time if (v.StoreCount != 1) { return(false); } if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1) { return(false); } // TODO: inlining of small integer types might be semantically incorrect, // but we can't avoid it this easily without breaking lots of tests. //if (v.Type.IsSmallIntegerType()) // return false; // stloc might perform implicit truncation return(InlineOne(stloc, options, context)); }
public static void Propagate(StLoc store, ILTransformContext context) { Debug.Assert(store.Variable.IsSingleDefinition); Block block = (Block)store.Parent; int i = store.ChildIndex; DoPropagate(store.Variable, store.Value, block, ref i, context); }
static bool IsClosureInit(ILTransformContext context, StLoc inst, out ITypeDefinition closureType) { if (inst.Value is NewObj newObj) { closureType = newObj.Method.DeclaringTypeDefinition; return closureType != null && IsPotentialClosure(context, newObj); } closureType = null; return false; }
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); } }
bool IsClosureInit(StLoc inst, out ITypeDefinition closureType) { if (inst.Value is NewObj newObj) { closureType = newObj.Method.DeclaringTypeDefinition; return(closureType != null && IsPotentialClosure(this.context, newObj)); } closureType = null; return(false); }
protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); // Sometimes display class references are copied into other local variables. // We remove the assignment and store the relationship between the display class and the variable in the // displayClasses dictionary. if (inst.Value.MatchLdLoc(out var closureVariable) && displayClasses.TryGetValue(closureVariable, out var displayClass)) { displayClasses[inst.Variable] = displayClass; instructionsToRemove.Add(inst); }
bool MatchLockEntryPoint(Block entryPoint, ILVariable flag, out StLoc obj) { obj = null; if (entryPoint.Instructions.Count == 0 || entryPoint.IncomingEdgeCount != 1) { return(false); } if (!MatchCall(entryPoint.Instructions[0] as Call, "Enter", flag, out obj)) { return(false); } return(true); }
protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); if (inst.Variable == targetLoad.Variable) { orphanedVariableInits.Add(inst); } if (MatchesTargetOrCopyLoad(inst.Value)) { targetAndCopies.Add(inst.Variable); orphanedVariableInits.Add(inst); } }
/// <summary> /// Determines whether storeInst.Variable is only assigned once and used only inside <paramref name="usingContainer"/>. /// Loads by reference (ldloca) are only allowed in the context of this pointer in call instructions. /// (This only applies to value types.) /// </summary> bool VariableIsOnlyUsedInBlock(StLoc storeInst, BlockContainer usingContainer) { if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(usingContainer))) { return(false); } if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(usingContainer) || !ILInlining.IsUsedAsThisPointerInCall(la))) { return(false); } if (storeInst.Variable.StoreInstructions.OfType <ILInstruction>().Any(st => st != storeInst)) { return(false); } return(true); }
bool MatchCall(Call call, string methodName, ILVariable flag, out StLoc obj) { obj = null; const string ThreadingMonitor = "System.Threading.Monitor"; if (call == null || call.Method.Name != methodName || call.Method.DeclaringType.FullName != ThreadingMonitor || call.Method.TypeArguments.Count != 0 || call.Arguments.Count != 2) { return(false); } if (!call.Arguments[1].MatchLdLoca(flag) || !(call.Arguments[0] is StLoc val)) { return(false); } obj = val; return(true); }
protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); if (targetLoad is ILInstruction instruction && instruction.MatchLdThis()) { return; } if (inst.Variable == targetLoad.Variable) { orphanedVariableInits.Add(inst); } if (MatchesTargetOrCopyLoad(inst.Value)) { targetAndCopies.Add(inst.Variable); orphanedVariableInits.Add(inst); } }
protected internal override void VisitStLoc(StLoc inst) { DisplayClass displayClass; if (inst.Parent is Block parentBlock && inst.Variable.IsSingleDefinition) { if ((inst.Variable.Kind == VariableKind.Local || inst.Variable.Kind == VariableKind.StackSlot) && inst.Variable.LoadCount == 0) { // traverse pre-order, so that we do not have to deal with more special cases afterwards base.VisitStLoc(inst); if (inst.Value is StLoc || inst.Value is CompoundAssignmentInstruction) { context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst); inst.ReplaceWith(inst.Value); } return; } if (displayClasses.TryGetValue(inst.Variable, out displayClass) && displayClass.Initializer == inst) { // inline contents of object initializer block if (inst.Value is Block initBlock && initBlock.Kind == BlockKind.ObjectInitializer) { context.Step($"Remove initializer of {inst.Variable.Name}", inst); for (int i = 1; i < initBlock.Instructions.Count; i++) { var stobj = (StObj)initBlock.Instructions[i]; var variable = displayClass.VariablesToDeclare[(IField)((LdFlda)stobj.Target).Field.MemberDefinition]; parentBlock.Instructions.Insert(inst.ChildIndex + i, new StLoc(variable.GetOrDeclare(), stobj.Value).WithILRange(stobj)); } } context.Step($"Remove initializer of {inst.Variable.Name}", inst); parentBlock.Instructions.Remove(inst); return; } if (inst.Value is LdLoc || inst.Value is LdObj) { // in some cases (e.g. if inlining fails), there can be a reference to a display class in a stack slot, // in that case it is necessary to resolve the reference and iff it can be propagated, replace all loads // of the single-definition. var referencedDisplayClass = ResolveVariableToPropagate(inst.Value); if (referencedDisplayClass != null && displayClasses.TryGetValue(referencedDisplayClass, out _)) { context.Step($"Propagate reference to {referencedDisplayClass.Name} in {inst.Variable}", inst); foreach (var ld in inst.Variable.LoadInstructions.ToArray()) { ld.ReplaceWith(new LdLoc(referencedDisplayClass).WithILRange(ld)); } parentBlock.Instructions.Remove(inst); return; } } } base.VisitStLoc(inst); }
// This transform is required because ILInlining only works with stloc/ldloc internal static bool StObjToStLoc(StObj inst, ILTransformContext context) { if (inst.Target.MatchLdLoca(out ILVariable v) && TypeUtils.IsCompatibleTypeForMemoryAccess(v.Type, inst.Type) && inst.UnalignedPrefix == 0 && !inst.IsVolatile) { context.Step($"stobj(ldloca {v.Name}, ...) => stloc {v.Name}(...)", inst); ILInstruction replacement = new StLoc(v, inst.Value).WithILRange(inst); if (v.StackType == StackType.Unknown && inst.Type.Kind != TypeKind.Unknown && inst.SlotInfo != Block.InstructionSlot) { replacement = new Conv(replacement, inst.Type.ToPrimitiveType(), checkForOverflow: false, Sign.None); } inst.ReplaceWith(replacement); return(true); } return(false); }
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; } } }
/// <summary> /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction, if possible. /// </summary> public static bool InlineOneIfPossible(Block block, int pos, bool aggressive, ILTransformContext context) { context.CancellationToken.ThrowIfCancellationRequested(); StLoc stloc = block.Instructions[pos] as StLoc; if (stloc == null || stloc.Variable.Kind == VariableKind.PinnedLocal) { return(false); } ILVariable v = stloc.Variable; // ensure the variable is accessed only a single time if (v.StoreCount != 1) { return(false); } if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1) { return(false); } return(InlineOne(stloc, aggressive, context)); }
/// <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> /// Block finallyHead (incoming: 2) { /// [body of finally] /// stloc V_4(ldloc V_1) /// if (comp(ldloc V_4 == ldnull)) br afterFinally /// br typeCheckBlock /// } /// /// Block typeCheckBlock (incoming: 1) { /// stloc S_110(isinst System.Exception(ldloc V_4)) /// if (comp(ldloc S_110 != ldnull)) br captureBlock /// br throwBlock /// } /// /// Block throwBlock (incoming: 1) { /// throw(ldloc V_4) /// } /// /// Block captureBlock (incoming: 1) { /// callvirt Throw(call Capture(ldloc S_110)) /// br afterFinally /// } /// /// Block afterFinally (incoming: 2) { /// stloc V_1(ldnull) /// [after finally] /// } /// </summary> static bool MatchExceptionCaptureBlock(StLoc tempStore, out Block endOfFinally, out Block afterFinally, out List <Block> blocksToRemove) { afterFinally = null; endOfFinally = (Block)tempStore.Parent; blocksToRemove = new List <Block>(); int count = endOfFinally.Instructions.Count; if (tempStore.ChildIndex != count - 3) { return(false); } if (!(endOfFinally.Instructions[count - 2] is IfInstruction ifInst)) { return(false); } if (!endOfFinally.Instructions.Last().MatchBranch(out var typeCheckBlock)) { return(false); } if (!ifInst.TrueInst.MatchBranch(out afterFinally)) { return(false); } // match typeCheckBlock if (typeCheckBlock.Instructions.Count != 3) { return(false); } if (!typeCheckBlock.Instructions[0].MatchStLoc(out var castStore, out var cast) || !cast.MatchIsInst(out var arg, out var type) || !type.IsKnownType(KnownTypeCode.Exception) || !arg.MatchLdLoc(tempStore.Variable)) { return(false); } if (!typeCheckBlock.Instructions[1].MatchIfInstruction(out var cond, out var jumpToCaptureBlock)) { return(false); } if (!cond.MatchCompNotEqualsNull(out arg) || !arg.MatchLdLoc(castStore)) { return(false); } if (!typeCheckBlock.Instructions[2].MatchBranch(out var throwBlock)) { return(false); } if (!jumpToCaptureBlock.MatchBranch(out var captureBlock)) { return(false); } // match throwBlock if (throwBlock.Instructions.Count != 1 || !throwBlock.Instructions[0].MatchThrow(out arg) || !arg.MatchLdLoc(tempStore.Variable)) { return(false); } // match captureBlock if (captureBlock.Instructions.Count != 2) { return(false); } if (!captureBlock.Instructions[1].MatchBranch(afterFinally)) { return(false); } if (!(captureBlock.Instructions[0] is CallVirt callVirt) || callVirt.Method.FullName != "System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw" || callVirt.Arguments.Count != 1) { return(false); } if (!(callVirt.Arguments[0] is Call call) || call.Method.FullName != "System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture" || call.Arguments.Count != 1) { return(false); } if (!call.Arguments[0].MatchLdLoc(castStore)) { return(false); } blocksToRemove.Add(typeCheckBlock); blocksToRemove.Add(throwBlock); blocksToRemove.Add(captureBlock); return(true); }
/// <summary> /// if (logic.not(ldloc V_1)) Block IL_004a { /// stloc V_2(dynamic.getmember B(target)) /// } /// </summary> static bool MatchLhsCacheIfInstruction(ILInstruction ifInst, ILVariable flagVar, out StLoc cacheStore) { cacheStore = null; if (!ifInst.MatchIfInstruction(out var condition, out var trueInst)) { return(false); } if (!MatchFlagEqualsZero(condition, flagVar)) { return(false); } cacheStore = Block.Unwrap(trueInst) as StLoc; return(cacheStore != null); }
protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); TransformAssignment.HandleCompoundAssign(inst, context); }
protected internal override void VisitStLoc(StLoc inst) { inst.Value.AcceptVisitor(this); HandleStore(inst.Variable); }
bool CreatePinnedRegion(Block block) { // After SplitBlocksAtWritesToPinnedLocals(), only the second-to-last instruction in each block // can be a write to a pinned local. var stLoc = block.Instructions.SecondToLastOrDefault() as StLoc; if (stLoc == null || stLoc.Variable.Kind != VariableKind.PinnedLocal) { return(false); } // stLoc is a store to a pinned local. if (IsNullOrZero(stLoc.Value)) { return(false); // ignore unpin instructions } // stLoc is a store that starts a new pinned region // Collect the blocks to be moved into the region: BlockContainer sourceContainer = (BlockContainer)block.Parent; int[] reachedEdgesPerBlock = new int[sourceContainer.Blocks.Count]; Queue <Block> workList = new Queue <Block>(); Block entryBlock = ((Branch)block.Instructions.Last()).TargetBlock; if (entryBlock.Parent != sourceContainer) { // we didn't find a single block to be added to the pinned region return(false); } reachedEdgesPerBlock[entryBlock.ChildIndex]++; workList.Enqueue(entryBlock); while (workList.Count > 0) { Block workItem = workList.Dequeue(); StLoc workStLoc = workItem.Instructions.SecondToLastOrDefault() as StLoc; int instructionCount; if (workStLoc != null && workStLoc.Variable == stLoc.Variable && IsNullOrZero(workStLoc.Value)) { // found unpin instruction: only consider branches prior to that instruction instructionCount = workStLoc.ChildIndex; } else { instructionCount = workItem.Instructions.Count; } for (int i = 0; i < instructionCount; i++) { foreach (var branch in workItem.Instructions[i].Descendants.OfType <Branch>()) { if (branch.TargetBlock.Parent == sourceContainer) { if (branch.TargetBlock == block) { // pin instruction is within a loop, and can loop around without an unpin instruction // This should never happen for C#-compiled code, but may happen with C++/CLI code. return(false); } if (reachedEdgesPerBlock[branch.TargetBlock.ChildIndex]++ == 0) { // detected first edge to that block: add block as work item workList.Enqueue(branch.TargetBlock); } } } } } // Validate that all uses of a block consistently are inside or outside the pinned region. // (we cannot do this anymore after we start moving blocks around) for (int i = 0; i < sourceContainer.Blocks.Count; i++) { if (reachedEdgesPerBlock[i] != 0 && reachedEdgesPerBlock[i] != sourceContainer.Blocks[i].IncomingEdgeCount) { return(false); } } context.Step("CreatePinnedRegion", block); BlockContainer body = new BlockContainer(); for (int i = 0; i < sourceContainer.Blocks.Count; i++) { if (reachedEdgesPerBlock[i] > 0) { var innerBlock = sourceContainer.Blocks[i]; Branch br = innerBlock.Instructions.LastOrDefault() as Branch; if (br != null && br.TargetContainer == sourceContainer && reachedEdgesPerBlock[br.TargetBlock.ChildIndex] == 0) { // branch that leaves body. // Should have an instruction that resets the pin; delete that instruction: StLoc innerStLoc = innerBlock.Instructions.SecondToLastOrDefault() as StLoc; if (innerStLoc != null && innerStLoc.Variable == stLoc.Variable && IsNullOrZero(innerStLoc.Value)) { innerBlock.Instructions.RemoveAt(innerBlock.Instructions.Count - 2); } } body.Blocks.Add(innerBlock); // move block into body sourceContainer.Blocks[i] = new Block(); // replace with dummy block // we'll delete the dummy block later } } stLoc.ReplaceWith(new PinnedRegion(stLoc.Variable, stLoc.Value, body)); block.Instructions.RemoveAt(block.Instructions.Count - 1); // remove branch into body ProcessPinnedRegion((PinnedRegion)block.Instructions.Last()); return(true); }
public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.Dynamic) { return; } this.context = context; Dictionary <IField, CallSiteInfo> callsites = new Dictionary <IField, CallSiteInfo>(); HashSet <BlockContainer> modifiedContainers = new HashSet <BlockContainer>(); foreach (var block in function.Descendants.OfType <Block>()) { if (block.Instructions.Count < 2) { continue; } // Check if, we deal with a callsite cache field null check: // if (comp(ldsfld <>p__3 == ldnull)) br IL_000c // br IL_002b if (!(block.Instructions.SecondToLastOrDefault() is IfInstruction ifInst)) { continue; } if (!(block.Instructions.LastOrDefault() is Branch branchAfterInit)) { continue; } if (!MatchCallSiteCacheNullCheck(ifInst.Condition, out var callSiteCacheField, out var callSiteDelegate, out bool invertBranches)) { continue; } if (!ifInst.TrueInst.MatchBranch(out var trueBlock)) { continue; } Block callSiteInitBlock, targetBlockAfterInit; if (invertBranches) { callSiteInitBlock = branchAfterInit.TargetBlock; targetBlockAfterInit = trueBlock; } else { callSiteInitBlock = trueBlock; targetBlockAfterInit = branchAfterInit.TargetBlock; } if (!ScanCallSiteInitBlock(callSiteInitBlock, callSiteCacheField, callSiteDelegate, out var callSiteInfo, out var blockAfterInit)) { continue; } if (targetBlockAfterInit != blockAfterInit) { continue; } callSiteInfo.DelegateType = callSiteDelegate; callSiteInfo.ConditionalJumpToInit = ifInst; callSiteInfo.Inverted = invertBranches; callSiteInfo.BranchAfterInit = branchAfterInit; callsites.Add(callSiteCacheField, callSiteInfo); } var storesToRemove = new List <StLoc>(); foreach (var invokeCall in function.Descendants.OfType <CallVirt>()) { if (invokeCall.Method.DeclaringType.Kind != TypeKind.Delegate || invokeCall.Method.Name != "Invoke" || invokeCall.Arguments.Count == 0) { continue; } var firstArgument = invokeCall.Arguments[0]; if (firstArgument.MatchLdLoc(out var stackSlot) && stackSlot.Kind == VariableKind.StackSlot && stackSlot.IsSingleDefinition) { firstArgument = ((StLoc)stackSlot.StoreInstructions[0]).Value; } if (!firstArgument.MatchLdFld(out var cacheFieldLoad, out var targetField)) { continue; } if (!cacheFieldLoad.MatchLdsFld(out var cacheField)) { continue; } if (!callsites.TryGetValue(cacheField, out var callsite)) { continue; } context.Stepper.Step("Transform callsite for " + callsite.MemberName); var deadArguments = new List <ILInstruction>(); ILInstruction replacement = MakeDynamicInstruction(callsite, invokeCall, deadArguments); if (replacement == null) { continue; } invokeCall.ReplaceWith(replacement); Debug.Assert(callsite.ConditionalJumpToInit?.Parent is Block); var block = ((Block)callsite.ConditionalJumpToInit.Parent); if (callsite.Inverted) { block.Instructions.Remove(callsite.ConditionalJumpToInit); callsite.BranchAfterInit.ReplaceWith(callsite.ConditionalJumpToInit.TrueInst); } else { block.Instructions.Remove(callsite.ConditionalJumpToInit); } foreach (var arg in deadArguments) { if (arg.MatchLdLoc(out var temporary) && temporary.Kind == VariableKind.StackSlot && temporary.IsSingleDefinition && temporary.LoadCount == 0) { StLoc stLoc = (StLoc)temporary.StoreInstructions[0]; if (stLoc.Parent is Block storeParentBlock) { var value = stLoc.Value; if (value.MatchLdsFld(out var cacheFieldCopy) && cacheFieldCopy.Equals(cacheField)) { storesToRemove.Add(stLoc); } if (value.MatchLdFld(out cacheFieldLoad, out var targetFieldCopy) && cacheFieldLoad.MatchLdsFld(out cacheFieldCopy) && cacheField.Equals(cacheFieldCopy) && targetField.Equals(targetFieldCopy)) { storesToRemove.Add(stLoc); } } } } modifiedContainers.Add((BlockContainer)block.Parent); } foreach (var inst in storesToRemove) { Block parentBlock = (Block)inst.Parent; parentBlock.Instructions.RemoveAt(inst.ChildIndex); } foreach (var container in modifiedContainers) { container.SortBlocks(deleteUnreachableBlocks: true); } }
Statement TransformToForeach(UsingInstruction inst, out Expression resource) { // Check if the using resource matches the GetEnumerator pattern. resource = exprBuilder.Translate(inst.ResourceExpression); var m = getEnumeratorPattern.Match(resource); // The using body must be a BlockContainer. if (!(inst.Body is BlockContainer container) || !m.Success) { return(null); } // The using-variable is the enumerator. var enumeratorVar = inst.Variable; // If there's another BlockContainer nested in this container and it only has one child block, unwrap it. // If there's an extra leave inside the block, extract it into optionalReturnAfterLoop. var loopContainer = UnwrapNestedContainerIfPossible(container, out var optionalReturnAfterLoop); // Detect whether we're dealing with a while loop with multiple embedded statements. var loop = DetectedLoop.DetectLoop(loopContainer); if (loop.Kind != LoopKind.While || !(loop.Body is Block body)) { return(null); } // The loop condition must be a call to enumerator.MoveNext() var condition = exprBuilder.TranslateCondition(loop.Conditions.Single()); var m2 = moveNextConditionPattern.Match(condition.Expression); if (!m2.Success) { return(null); } // Check enumerator variable references. var enumeratorVar2 = m2.Get <IdentifierExpression>("enumerator").Single().GetILVariable(); if (enumeratorVar2 != enumeratorVar) { return(null); } // Detect which foreach-variable transformation is necessary/possible. var transformation = DetectGetCurrentTransformation(container, body, enumeratorVar, condition.ILInstructions.Single(), out var singleGetter, out var foreachVariable); if (transformation == RequiredGetCurrentTransformation.NoForeach) { return(null); } // The existing foreach variable, if found, can only be used in the loop container. if (foreachVariable != null && !(foreachVariable.CaptureScope == null || foreachVariable.CaptureScope == loopContainer)) { return(null); } // Extract in-expression var collectionExpr = m.Get <Expression>("collection").Single(); // Special case: foreach (var item in this) is decompiled as foreach (var item in base) // but a base reference is not valid in this context. if (collectionExpr is BaseReferenceExpression) { collectionExpr = new ThisReferenceExpression().CopyAnnotationsFrom(collectionExpr); } // Handle explicit casts: // This is the case if an explicit type different from the collection-item-type was used. // For example: foreach (ClassA item in nonGenericEnumerable) var type = singleGetter.Method.ReturnType; ILInstruction instToReplace = singleGetter; switch (instToReplace.Parent) { case CastClass cc: type = cc.Type; instToReplace = cc; break; case UnboxAny ua: type = ua.Type; instToReplace = ua; break; } // Handle the required foreach-variable transformation: switch (transformation) { case RequiredGetCurrentTransformation.UseExistingVariable: foreachVariable.Type = type; foreachVariable.Kind = VariableKind.ForeachLocal; foreachVariable.Name = AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>(), foreachVariable); break; case RequiredGetCurrentTransformation.UninlineAndUseExistingVariable: // Unwrap stloc chain. var nestedStores = new Stack <ILVariable>(); var currentInst = instToReplace; // instToReplace is the innermost value of the stloc chain. while (currentInst.Parent is StLoc stloc) { // Exclude nested stores to foreachVariable // we'll insert one store at the beginning of the block. if (stloc.Variable != foreachVariable && stloc.Parent is StLoc) { nestedStores.Push(stloc.Variable); } currentInst = stloc; } // Rebuild the nested store instructions: ILInstruction reorderedStores = new LdLoc(foreachVariable); while (nestedStores.Count > 0) { reorderedStores = new StLoc(nestedStores.Pop(), reorderedStores); } currentInst.ReplaceWith(reorderedStores); body.Instructions.Insert(0, new StLoc(foreachVariable, instToReplace)); // Adjust variable type, kind and name. goto case RequiredGetCurrentTransformation.UseExistingVariable; case RequiredGetCurrentTransformation.IntroduceNewVariable: foreachVariable = currentFunction.RegisterVariable( VariableKind.ForeachLocal, type, AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>()) ); instToReplace.ReplaceWith(new LdLoc(foreachVariable)); body.Instructions.Insert(0, new StLoc(foreachVariable, instToReplace)); break; } // Convert the modified body to C# AST: var whileLoop = (WhileStatement)ConvertAsBlock(container).First(); BlockStatement foreachBody = (BlockStatement)whileLoop.EmbeddedStatement.Detach(); // Remove the first statement, as it is the foreachVariable = enumerator.Current; statement. foreachBody.Statements.First().Detach(); // Construct the foreach loop. var foreachStmt = new ForeachStatement { VariableType = settings.AnonymousTypes && foreachVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : exprBuilder.ConvertType(foreachVariable.Type), VariableName = foreachVariable.Name, InExpression = collectionExpr.Detach(), EmbeddedStatement = foreachBody }; // Add the variable annotation for highlighting (TokenTextWriter expects it directly on the ForeachStatement). foreachStmt.AddAnnotation(new ILVariableResolveResult(foreachVariable, foreachVariable.Type)); // If there was an optional return statement, return it as well. if (optionalReturnAfterLoop != null) { return(new BlockStatement { Statements = { foreachStmt, optionalReturnAfterLoop.AcceptVisitor(this) } }); } return(foreachStmt); }