bool BodyHasSingleGetCurrent(Block body, ILVariable enumerator, ILInstruction moveNextUsage, out CallInstruction singleGetter, out bool needsUninlining, out ILVariable existingVariable) { singleGetter = null; needsUninlining = false; existingVariable = null; var loads = (enumerator.LoadInstructions.OfType <ILInstruction>().Concat(enumerator.AddressInstructions.OfType <ILInstruction>())).Where(ld => !ld.IsDescendantOf(moveNextUsage)).ToArray(); if (loads.Length == 1 && ParentIsCurrentGetter(loads[0])) { singleGetter = (CallInstruction)loads[0].Parent; ILInstruction inst = singleGetter; while (inst.Parent is UnboxAny || inst.Parent is CastClass) { inst = inst.Parent; } needsUninlining = !inst.Parent.MatchStLoc(out existingVariable); } return(singleGetter != null && singleGetter.IsDescendantOf(body.Instructions[0]) && ILInlining.CanUninline(singleGetter, body.Instructions[0])); }
/// <summary> /// Determines whether <paramref name="enumerator"/> is only used once inside <paramref name="loopBody"/> for accessing the Current property. /// </summary> /// <param name="usingContainer">The using body container. This is only used for variable usage checks.</param> /// <param name="loopBody">The loop body. The first statement of this block is analyzed.</param> /// <param name="enumerator">The current enumerator.</param> /// <param name="moveNextUsage">The call MoveNext(ldloc enumerator) pattern.</param> /// <param name="singleGetter">Returns the call instruction invoking Current's getter.</param> /// <param name="foreachVariable">Returns the the foreach variable, if a suitable was found. This variable is only assigned once and its assignment is the first statement in <paramref name="loopBody"/>.</param> /// <returns><see cref="RequiredGetCurrentTransformation"/> for details.</returns> RequiredGetCurrentTransformation DetectGetCurrentTransformation(BlockContainer usingContainer, Block loopBody, ILVariable enumerator, ILInstruction moveNextUsage, out CallInstruction singleGetter, out ILVariable foreachVariable) { singleGetter = null; foreachVariable = null; var loads = (enumerator.LoadInstructions.OfType <ILInstruction>().Concat(enumerator.AddressInstructions.OfType <ILInstruction>())).Where(ld => !ld.IsDescendantOf(moveNextUsage)).ToArray(); // enumerator is used in multiple locations or not in conjunction with get_Current // => no foreach if (loads.Length != 1 || !ParentIsCurrentGetter(loads[0])) { return(RequiredGetCurrentTransformation.NoForeach); } singleGetter = (CallInstruction)loads[0].Parent; // singleGetter is not part of the first instruction in body or cannot be uninlined // => no foreach if (!(singleGetter.IsDescendantOf(loopBody.Instructions[0]) && ILInlining.CanUninline(singleGetter, loopBody.Instructions[0]))) { return(RequiredGetCurrentTransformation.NoForeach); } ILInstruction inst = singleGetter; // in some cases, i.e. foreach variable with explicit type different from the collection-item-type, // the result of call get_Current is casted. while (inst.Parent is UnboxAny || inst.Parent is CastClass) { inst = inst.Parent; } // One variable was found. if (inst.Parent is StLoc stloc) { // Must be a plain assignment expression and variable must only be used in 'body' + only assigned once. if (stloc.Parent == loopBody && VariableIsOnlyUsedInBlock(stloc, usingContainer)) { foreachVariable = stloc.Variable; return(RequiredGetCurrentTransformation.UseExistingVariable); } } // No suitable variable was found: we need a new one. return(RequiredGetCurrentTransformation.IntroduceNewVariable); }