/// <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); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, InliningOptions options) { if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline if (expr.SlotInfo == StObj.TargetSlot && !((StObj)expr.Parent).CanInlineIntoTargetSlot(expressionBeingMoved)) { // special case: the StObj.TargetSlot does not accept some kinds of expressions return(FindResult.Stop); } return(FindResult.Found(expr)); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved)); default: return(FindResult.Stop); } } else if (expr is BlockContainer container && container.EntryPoint.IncomingEdgeCount == 1) { // Possibly a switch-container, allow inlining into the switch instruction: return(NoContinue(FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the blockcontainer. }
/// <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); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst) { loadInst = null; if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline loadInst = expr; return(FindResult.Found); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, out loadInst))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved, out loadInst)); default: return(FindResult.Stop); } } else if (expr is BlockContainer container && container.EntryPoint.IncomingEdgeCount == 1) { // Possibly a switch-container, allow inlining into the switch instruction: return(NoContinue(FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, out loadInst))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the blockcontainer. }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, InliningOptions options) { if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline if (expr.SlotInfo == StObj.TargetSlot && !((StObj)expr.Parent).CanInlineIntoTargetSlot(expressionBeingMoved)) { if ((options & InliningOptions.AllowChangingOrderOfEvaluationForExceptions) != 0) { // Intentionally change code semantics so that we can avoid a ref local if (expressionBeingMoved is LdFlda ldflda) { ldflda.DelayExceptions = true; } else if (expressionBeingMoved is LdElema ldelema) { ldelema.DelayExceptions = true; } } else { // special case: the StObj.TargetSlot does not accept some kinds of expressions return(FindResult.Stop); } } return(FindResult.Found(expr)); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ControlFlow when block.Parent is BlockContainer: case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved)); default: return(FindResult.Stop); } } else if (options.HasFlag(InliningOptions.FindDeconstruction) && expr is DeconstructInstruction di) { return(FindResult.Deconstruction(di)); } foreach (var child in expr.Children) { if (!expr.CanInlineIntoSlot(child.ChildIndex, expressionBeingMoved)) { return(FindResult.Stop); } // Recursively try to find the load instruction FindResult r = FindLoadInNext(child, v, expressionBeingMoved, options); if (r.Type != FindResultType.Continue) { if (r.Type == FindResultType.Stop && (options & InliningOptions.IntroduceNamedArguments) != 0 && expr is CallInstruction call) { return(NamedArgumentTransform.CanIntroduceNamedArgument(call, child, v, expressionBeingMoved)); } return(r); } } if (IsSafeForInlineOver(expr, expressionBeingMoved)) { return(FindResult.Continue); // continue searching } else { return(FindResult.Stop); // abort, inlining not possible } }