/// <summary> /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable. /// </summary> /// <param name="next">The next top-level expression</param> /// <param name="loadInst">The load within 'next'</param> /// <param name="inlinedExpression">The expression being inlined</param> static bool NonAggressiveInlineInto(ILInstruction next, ILInstruction loadInst, ILInstruction inlinedExpression, ILVariable v) { Debug.Assert(loadInst.IsDescendantOf(next)); // decide based on the source expression being inlined switch (inlinedExpression.OpCode) { case OpCode.DefaultValue: case OpCode.StObj: case OpCode.NumericCompoundAssign: case OpCode.UserDefinedCompoundAssign: case OpCode.Await: return(true); case OpCode.LdLoc: if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null) { // Roslyn likes to put the result of fetching a state machine field into a temporary variable, // so inline more aggressively in such cases. return(true); } break; } var parent = loadInst.Parent; if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _)) { // inline into nullable ctor call in lifted operator parent = parent.Parent; } if (parent is ILiftableInstruction liftable && liftable.IsLifted) { return(true); // inline into lifted operators } switch (parent.OpCode) { case OpCode.NullCoalescingInstruction: if (NullableType.IsNullable(v.Type)) { return(true); // inline nullables into ?? operator } break; case OpCode.NullableUnwrap: return(true); // inline into ?. operator case OpCode.UserDefinedLogicOperator: case OpCode.DynamicLogicOperatorInstruction: return(true); // inline into (left slot of) user-defined && or || operator case OpCode.DynamicGetMemberInstruction: case OpCode.DynamicGetIndexInstruction: case OpCode.LdObj: if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign) { return(true); // inline into dynamic compound assignments } break; } // decide based on the target into which we are inlining switch (next.OpCode) { case OpCode.Leave: case OpCode.YieldReturn: return(parent == next); case OpCode.IfInstruction: while (parent.MatchLogicNot(out _)) { parent = parent.Parent; } return(parent == next); case OpCode.BlockContainer: if (((BlockContainer)next).EntryPoint.Instructions[0] is SwitchInstruction switchInst) { next = switchInst; goto case OpCode.SwitchInstruction; } else { return(false); } case OpCode.SwitchInstruction: return(parent == next || (parent.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub) && parent.Parent == next)); default: return(false); } }
/// <summary> /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable. /// </summary> /// <param name="next">The next top-level expression</param> /// <param name="v">The variable being eliminated by inlining.</param> /// <param name="inlinedExpression">The expression being inlined</param> static bool NonAggressiveInlineInto(ILInstruction next, FindResult findResult, ILInstruction inlinedExpression, ILVariable v) { if (findResult.Type == FindResultType.NamedArgument) { var originalStore = (StLoc)inlinedExpression.Parent; return(!originalStore.ILStackWasEmpty); } Debug.Assert(findResult.Type == FindResultType.Found); var loadInst = findResult.LoadInst; Debug.Assert(loadInst.IsDescendantOf(next)); // decide based on the source expression being inlined switch (inlinedExpression.OpCode) { case OpCode.DefaultValue: case OpCode.StObj: case OpCode.NumericCompoundAssign: case OpCode.UserDefinedCompoundAssign: case OpCode.Await: case OpCode.SwitchInstruction: return(true); case OpCode.LdLoc: if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null) { // Roslyn likes to put the result of fetching a state machine field into a temporary variable, // so inline more aggressively in such cases. return(true); } break; } if (inlinedExpression.ResultType == StackType.Ref) { // VB likes to use ref locals for compound assignment // (the C# compiler uses ref stack slots instead). // We want to avoid unnecessary ref locals, so we'll always inline them if possible. return(true); } var parent = loadInst.Parent; if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _)) { // inline into nullable ctor call in lifted operator parent = parent.Parent; } if (parent is ILiftableInstruction liftable && liftable.IsLifted) { return(true); // inline into lifted operators } // decide based on the new parent into which we are inlining: switch (parent.OpCode) { case OpCode.NullCoalescingInstruction: if (NullableType.IsNullable(v.Type)) { return(true); // inline nullables into ?? operator } break; case OpCode.NullableUnwrap: return(true); // inline into ?. operator case OpCode.UserDefinedLogicOperator: case OpCode.DynamicLogicOperatorInstruction: return(true); // inline into (left slot of) user-defined && or || operator case OpCode.DynamicGetMemberInstruction: case OpCode.DynamicGetIndexInstruction: if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign) { return(true); // inline into dynamic compound assignments } break; case OpCode.DynamicCompoundAssign: return(true); case OpCode.GetPinnableReference: case OpCode.LocAllocSpan: return(true); // inline size-expressions into localloc.span case OpCode.Call: case OpCode.CallVirt: // Aggressive inline into property/indexer getter calls for compound assignment calls // (The compiler generates locals for these because it doesn't want to evalute the args twice for getter+setter) if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot) { return(true); } if (((CallInstruction)parent).Method is SyntheticRangeIndexAccessor) { return(true); } break; case OpCode.CallIndirect when loadInst.SlotInfo == CallIndirect.FunctionPointerSlot: return(true); case OpCode.LdElema: if (((LdElema)parent).WithSystemIndex) { return(true); } break; case OpCode.Leave: case OpCode.YieldReturn: return(true); case OpCode.SwitchInstruction: //case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot: case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot: return(true); case OpCode.MatchInstruction when((MatchInstruction)parent).IsDeconstructTuple: return(true); } // decide based on the top-level target instruction into which we are inlining: switch (next.OpCode) { case OpCode.IfInstruction: while (parent.MatchLogicNot(out _)) { parent = parent.Parent; } return(parent == next); default: return(false); } }
/// <summary> /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable. /// </summary> /// <param name="next">The next top-level expression</param> /// <param name="v">The variable being eliminated by inlining.</param> /// <param name="inlinedExpression">The expression being inlined</param> static bool NonAggressiveInlineInto(ILInstruction next, FindResult findResult, ILInstruction inlinedExpression, ILVariable v) { if (findResult.Type == FindResultType.NamedArgument) { var originalStore = (StLoc)inlinedExpression.Parent; return(!originalStore.ILStackWasEmpty); } Debug.Assert(findResult.Type == FindResultType.Found); var loadInst = findResult.LoadInst; Debug.Assert(loadInst.IsDescendantOf(next)); // decide based on the source expression being inlined switch (inlinedExpression.OpCode) { case OpCode.DefaultValue: case OpCode.StObj: case OpCode.NumericCompoundAssign: case OpCode.UserDefinedCompoundAssign: case OpCode.Await: return(true); case OpCode.LdLoc: if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null) { // Roslyn likes to put the result of fetching a state machine field into a temporary variable, // so inline more aggressively in such cases. return(true); } break; } var parent = loadInst.Parent; if (NullableLiftingTransform.MatchNullableCtor(parent, out _, out _)) { // inline into nullable ctor call in lifted operator parent = parent.Parent; } if (parent is ILiftableInstruction liftable && liftable.IsLifted) { return(true); // inline into lifted operators } // decide based on the new parent into which we are inlining: switch (parent.OpCode) { case OpCode.NullCoalescingInstruction: if (NullableType.IsNullable(v.Type)) { return(true); // inline nullables into ?? operator } break; case OpCode.NullableUnwrap: return(true); // inline into ?. operator case OpCode.UserDefinedLogicOperator: case OpCode.DynamicLogicOperatorInstruction: return(true); // inline into (left slot of) user-defined && or || operator case OpCode.DynamicGetMemberInstruction: case OpCode.DynamicGetIndexInstruction: if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign) { return(true); // inline into dynamic compound assignments } break; case OpCode.DynamicCompoundAssign: return(true); case OpCode.ArrayToPointer: case OpCode.LocAllocSpan: return(true); // inline size-expressions into localloc.span case OpCode.Call: case OpCode.CallVirt: // Aggressive inline into property/indexer getter calls for compound assignment calls // (The compiler generates locals for these because it doesn't want to evalute the args twice for getter+setter) if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot) { return(true); } break; } // decide based on the top-level target instruction into which we are inlining: switch (next.OpCode) { case OpCode.Leave: case OpCode.YieldReturn: return(parent == next); case OpCode.IfInstruction: while (parent.MatchLogicNot(out _)) { parent = parent.Parent; } return(parent == next); case OpCode.BlockContainer: if (((BlockContainer)next).EntryPoint.Instructions[0] is SwitchInstruction switchInst) { next = switchInst; goto case OpCode.SwitchInstruction; } else { return(false); } case OpCode.SwitchInstruction: if (parent == next) { return(true); } if (parent.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub) && parent.Parent == next) { return(true); } if (parent is StringToInt stringToInt && stringToInt.Parent == next) { return(true); } return(false); default: return(false); } }