/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.PatternLocalRewriter.cs
721 строка
39 KB
AlekseyTs
Initial support for non-boxing union TryGetValue access pattern (#82325)
13 фев 2026, 05:31
Не верифицирован
13 фев 2026, 05:31
d6040ad
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal sealed partial class LocalRewriter { /// <summary> /// A common base class for lowering constructs that use pattern-matching. /// </summary> private abstract class PatternLocalRewriter { protected readonly LocalRewriter _localRewriter; protected readonly SyntheticBoundNodeFactory _factory; protected readonly DagTempAllocator _tempAllocator; public PatternLocalRewriter(SyntaxNode node, LocalRewriter localRewriter, bool generateInstrumentation) { _localRewriter = localRewriter; _factory = localRewriter._factory; GenerateInstrumentation = generateInstrumentation; _tempAllocator = new DagTempAllocator(_factory, node, generateInstrumentation); } /// <summary> /// True if we should produce instrumentation and sequence points, which we do for a switch statement and a switch expression. /// This affects /// - whether or not we invoke the instrumentation APIs /// - production of sequence points /// - synthesized local variable kind /// The temp variables must be long lived in a switch statement since their lifetime spans across sequence points. /// </summary> protected bool GenerateInstrumentation { get; } public void Free() { _tempAllocator.Free(); } public sealed class DagTempAllocator { private readonly SyntheticBoundNodeFactory _factory; private readonly PooledDictionary<BoundDagTemp, BoundExpression> _map = PooledDictionary<BoundDagTemp, BoundExpression>.GetInstance(); private readonly ArrayBuilder<LocalSymbol> _temps = ArrayBuilder<LocalSymbol>.GetInstance(); private readonly SyntaxNode _node; private readonly bool _generateSequencePoints; public DagTempAllocator(SyntheticBoundNodeFactory factory, SyntaxNode node, bool generateSequencePoints) { _factory = factory; _node = node; _generateSequencePoints = generateSequencePoints; } public void Free() { _temps.Free(); _map.Free(); } #if DEBUG public string Dump() { var poolElement = PooledStringBuilder.GetInstance(); var builder = poolElement.Builder; foreach (var kv in _map) { builder.Append("Key: "); builder.AppendLine(kv.Key.Dump()); builder.Append("Value: "); builder.AppendLine(kv.Value.Dump()); } var result = builder.ToString(); poolElement.Free(); return result; } #endif public BoundExpression GetTemp(BoundDagTemp dagTemp) { if (!_map.TryGetValue(dagTemp, out BoundExpression result)) { var kind = _generateSequencePoints ? SynthesizedLocalKind.SwitchCasePatternMatching : SynthesizedLocalKind.LoweringTemp; LocalSymbol temp = _factory.SynthesizedLocal(dagTemp.Type, syntax: _node, kind: kind); result = _factory.Local(temp); _map.Add(dagTemp, result); _temps.Add(temp); } return result; } /// <summary> /// Try setting a user-declared variable (given by its accessing expression) to be /// used for a pattern-matching temporary variable. Returns true when not already /// assigned. The return value of this method is typically ignored by the caller as /// once we have made an assignment we can keep it (we keep the first assignment we /// find), but we return a success bool to emphasize that the assignment is not unconditional. /// </summary> public bool TrySetTemp(BoundDagTemp dagTemp, BoundExpression translation) { if (!_map.ContainsKey(dagTemp)) { _map.Add(dagTemp, translation); return true; } return false; } public ImmutableArray<LocalSymbol> AllTemps() { return _temps.ToImmutableArray(); } } /// <summary> /// Return the side-effect expression corresponding to an evaluation. /// </summary> protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) { BoundExpression input = _tempAllocator.GetTemp(evaluation.Input); switch (evaluation) { case BoundDagFieldEvaluation f: { FieldSymbol field = f.Field; var outputTemp = f.MakeResultTemp(); BoundExpression output = _tempAllocator.GetTemp(outputTemp); BoundExpression access = _localRewriter.MakeFieldAccess(f.Syntax, input, field, null, LookupResultKind.Viable, field.Type); access.WasCompilerGenerated = true; return _factory.AssignmentExpression(output, access); } case BoundDagPropertyEvaluation p: { PropertySymbol property = p.Property; var outputTemp = p.MakeResultTemp(); BoundExpression output = _tempAllocator.GetTemp(outputTemp); // Tracked by https://github.com/dotnet/roslyn/issues/78827 : MQ, Consider preserving the BoundConversion from initial binding instead of using markAsChecked here input = _localRewriter.ConvertReceiverForExtensionMemberIfNeeded(property, input, markAsChecked: true); return _factory.AssignmentExpression(output, _localRewriter.MakePropertyAccess(_factory.Syntax, input, property, LookupResultKind.Viable, property.Type, isLeftOfAssignment: false)); } case BoundDagDeconstructEvaluation d: { MethodSymbol method = d.DeconstructMethod; var refKindBuilder = ArrayBuilder<RefKind>.GetInstance(); var argBuilder = ArrayBuilder<BoundExpression>.GetInstance(); BoundExpression receiver; void addArg(RefKind refKind, BoundExpression expression) { refKindBuilder.Add(refKind); argBuilder.Add(expression); } Debug.Assert(method.Name is WellKnownMemberNames.DeconstructMethodName or WellKnownMemberNames.TryGetValueMethodName); if (method.IsStatic) { Debug.Assert(method.IsExtensionMethod); receiver = _factory.Type(method.ContainingType); // Tracked by https://github.com/dotnet/roslyn/issues/78827 : MQ, Consider preserving the BoundConversion from initial binding instead of using markAsChecked here addArg(method.ParameterRefKinds[0], _localRewriter.ConvertReceiverForExtensionIfNeeded(input, markAsChecked: true, method.Parameters[0])); } else { receiver = input; } ArrayBuilder<BoundDagTemp> outParamTemps = d.MakeOutParameterTemps(); foreach (var outputTemp in outParamTemps) { addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp)); } outParamTemps.Free(); BoundExpression returnValue = null; if (!method.ReturnsVoid) { Debug.Assert(method.Name is WellKnownMemberNames.TryGetValueMethodName); Debug.Assert(method.ReturnType.SpecialType == SpecialType.System_Boolean); returnValue = _tempAllocator.GetTemp(d.MakeReturnValueTemp()); } // Tracked by https://github.com/dotnet/roslyn/issues/78827 : MQ, Consider preserving the BoundConversion from initial binding instead of using markAsChecked here receiver = _localRewriter.ConvertReceiverForExtensionMemberIfNeeded(method, receiver, markAsChecked: true); BoundExpression result = _factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree()); if (returnValue is not null) { result = _factory.AssignmentExpression(returnValue, result); } return result; } case BoundDagTypeEvaluation t: { TypeSymbol inputType = input.Type; Debug.Assert(inputType is { }); if (inputType.IsDynamic()) { // Avoid using dynamic conversions for pattern-matching. inputType = _factory.SpecialType(SpecialType.System_Object); Debug.Assert(_factory.ClassifyEmitConversion(input, inputType).IsIdentity); input = _factory.Convert(inputType, input, Conversion.Identity); } TypeSymbol type = t.Type; var outputTemp = t.MakeResultTemp(); BoundExpression output = _tempAllocator.GetTemp(outputTemp); CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = _localRewriter.GetNewCompoundUseSiteInfo(); Conversion conversion = _factory.Compilation.Conversions.ClassifyBuiltInConversion(inputType, output.Type, isChecked: false, ref useSiteInfo); Debug.Assert(!conversion.IsUserDefined); Debug.Assert(!conversion.IsUnion); _localRewriter._diagnostics.Add(t.Syntax, useSiteInfo); BoundExpression evaluated; if (conversion.Exists) { if (conversion.Kind == ConversionKind.ExplicitNullable && inputType.GetNullableUnderlyingType().Equals(output.Type, TypeCompareKind.AllIgnoreOptions) && _localRewriter.TryGetNullableMethod(t.Syntax, inputType, SpecialMember.System_Nullable_T_GetValueOrDefault, out MethodSymbol getValueOrDefault)) { // As a special case, since the null test has already been done we can use Nullable<T>.GetValueOrDefault evaluated = _factory.Call(input, getValueOrDefault); } else { conversion.MarkUnderlyingConversionsCheckedRecursive(); // Assuming that Binder.ExpressionOfTypeMatchesPatternType wouldn't let anything non-trivial through evaluated = _localRewriter.MakeConversionNode(t.Syntax, input, conversion, type, @checked: false); } } else { evaluated = _factory.As(input, type); } return _factory.AssignmentExpression(output, evaluated); } case BoundDagIndexEvaluation e: { // This is an evaluation of an indexed property with a constant int value. // The input type must be ITuple, and the property must be a property of ITuple. Debug.Assert(e.Property.GetMethod.ParameterCount == 1); Debug.Assert(e.Property.GetMethod.Parameters[0].Type.SpecialType == SpecialType.System_Int32); TypeSymbol type = e.Property.GetMethod.ReturnType; var outputTemp = e.MakeResultTemp(); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return _factory.AssignmentExpression(output, _factory.Indexer(input, e.Property, _factory.Literal(e.Index))); } case BoundDagIndexerEvaluation e: { // this[Index] // this[int] // array[Index] var indexerAccess = e.IndexerAccess; if (indexerAccess is BoundImplicitIndexerAccess implicitAccess) { indexerAccess = implicitAccess.WithLengthOrCountAccess(_tempAllocator.GetTemp(e.LengthTemp)); } var placeholderValues = PooledDictionary<BoundEarlyValuePlaceholderBase, BoundExpression>.GetInstance(); placeholderValues.Add(e.ReceiverPlaceholder, input); placeholderValues.Add(e.ArgumentPlaceholder, makeUnloweredIndexArgument(e.Index)); indexerAccess = PlaceholderReplacer.Replace(placeholderValues, indexerAccess); placeholderValues.Free(); var access = (BoundExpression)_localRewriter.Visit(indexerAccess); var outputTemp = e.MakeResultTemp(); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return _factory.AssignmentExpression(output, access); } case BoundDagSliceEvaluation e: { // this[Range] // Slice(int, int) // string.Substring(int, int) // array[Range] var indexerAccess = e.IndexerAccess; if (indexerAccess is BoundImplicitIndexerAccess implicitAccess) { indexerAccess = implicitAccess.WithLengthOrCountAccess(_tempAllocator.GetTemp(e.LengthTemp)); } var placeholderValues = PooledDictionary<BoundEarlyValuePlaceholderBase, BoundExpression>.GetInstance(); placeholderValues.Add(e.ReceiverPlaceholder, input); placeholderValues.Add(e.ArgumentPlaceholder, makeUnloweredRangeArgument(e)); indexerAccess = PlaceholderReplacer.Replace(placeholderValues, indexerAccess); placeholderValues.Free(); var access = (BoundExpression)_localRewriter.Visit(indexerAccess); var outputTemp = e.MakeResultTemp(); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return _factory.AssignmentExpression(output, access); } case BoundDagAssignmentEvaluation e: { Debug.Assert(!e.Target.Equals(e.Input)); BoundExpression left = _tempAllocator.GetTemp(e.Target); BoundExpression right = _tempAllocator.GetTemp(e.Input); return _factory.AssignmentExpression(left, right); } case BoundDagPassThroughEvaluation: { return input; } default: throw ExceptionUtilities.UnexpectedValue(evaluation); } BoundExpression makeUnloweredIndexArgument(int index) { // LocalRewriter.MakePatternIndexOffsetExpression understands this format var ctor = (MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Index__ctor); if (index < 0) { return new BoundFromEndIndexExpression(_factory.Syntax, _factory.Literal(-index), methodOpt: ctor, _factory.WellKnownType(WellKnownType.System_Index)); } return _factory.New(ctor, _factory.Literal(index), _factory.Literal(false)); } BoundExpression makeUnloweredRangeArgument(BoundDagSliceEvaluation e) { // LocalRewriter.VisitRangeImplicitIndexerAccess understands this format var indexCtor = (MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Index__ctor); var end = new BoundFromEndIndexExpression(_factory.Syntax, _factory.Literal(-e.EndIndex), methodOpt: indexCtor, _factory.WellKnownType(WellKnownType.System_Index)); var rangeCtor = (MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Range__ctor); return new BoundRangeExpression(e.Syntax, makeUnloweredIndexArgument(e.StartIndex), end, methodOpt: rangeCtor, _factory.WellKnownType(WellKnownType.System_Range)); } } /// <summary> /// Return the boolean expression to be evaluated for the given test. Returns `null` if the test is trivially true. /// </summary> protected BoundExpression LowerTest(BoundDagTest test) { _factory.Syntax = test.Syntax; BoundExpression input = _tempAllocator.GetTemp(test.Input); Debug.Assert(input.Type is { }); switch (test) { case BoundDagNonNullTest d: return MakeNullCheck(d.Syntax, input, input.Type.IsNullableType() ? BinaryOperatorKind.NullableNullNotEqual : BinaryOperatorKind.NotEqual); case BoundDagTypeTest d: // Note that this tests for non-null as a side-effect. We depend on that to sometimes avoid the null check. return _factory.Is(input, d.Type); case BoundDagExplicitNullTest d: return MakeNullCheck(d.Syntax, input, input.Type.IsNullableType() ? BinaryOperatorKind.NullableNullEqual : BinaryOperatorKind.Equal); case BoundDagValueTest d: Debug.Assert(!input.Type.IsNullableType()); return MakeValueTest(d.Syntax, input, d.Value); case BoundDagRelationalTest d: Debug.Assert(!input.Type.IsNullableType()); Debug.Assert(input.Type.IsValueType); return MakeRelationalTest(d.Syntax, input, d.OperatorKind, d.Value); default: throw ExceptionUtilities.UnexpectedValue(test); } } private BoundExpression MakeNullCheck(SyntaxNode syntax, BoundExpression rewrittenExpr, BinaryOperatorKind operatorKind) { Debug.Assert(!rewrittenExpr.Type.IsSpanOrReadOnlySpanChar()); if (rewrittenExpr.Type.IsPointerOrFunctionPointer()) { TypeSymbol objectType = _factory.SpecialType(SpecialType.System_Object); var operandType = new PointerTypeSymbol(TypeWithAnnotations.Create(_factory.SpecialType(SpecialType.System_Void))); return _localRewriter.MakeBinaryOperator( syntax, operatorKind, _factory.Convert(operandType, rewrittenExpr, Conversion.PointerToVoid), _factory.Convert(operandType, new BoundLiteral(syntax, ConstantValue.Null, objectType), Conversion.NullToPointer), _factory.SpecialType(SpecialType.System_Boolean), method: null, constrainedToTypeOpt: null); } return _localRewriter.MakeNullCheck(syntax, rewrittenExpr, operatorKind); } protected BoundExpression MakeValueTest(SyntaxNode syntax, BoundExpression input, ConstantValue value) { if (value.IsString && input.Type.IsSpanOrReadOnlySpanChar()) { return MakeSpanStringTest(input, value); } TypeSymbol comparisonType = input.Type.EnumUnderlyingTypeOrSelf(); var operatorType = Binder.RelationalOperatorType(comparisonType); Debug.Assert(operatorType != BinaryOperatorKind.Error); var operatorKind = BinaryOperatorKind.Equal | operatorType; return MakeRelationalTest(syntax, input, operatorKind, value); } protected BoundExpression MakeRelationalTest(SyntaxNode syntax, BoundExpression input, BinaryOperatorKind operatorKind, ConstantValue value) { if (input.Type.SpecialType == SpecialType.System_Double && double.IsNaN(value.DoubleValue) || input.Type.SpecialType == SpecialType.System_Single && float.IsNaN(value.SingleValue)) { Debug.Assert(operatorKind.Operator() == BinaryOperatorKind.Equal); return _factory.MakeIsNotANumberTest(input); } BoundExpression literal = _localRewriter.MakeLiteral(syntax, value, input.Type); TypeSymbol comparisonType = input.Type.EnumUnderlyingTypeOrSelf(); if (operatorKind.OperandTypes() == BinaryOperatorKind.Int && comparisonType.SpecialType != SpecialType.System_Int32) { // Promote operands to int before comparison for byte, sbyte, short, ushort Debug.Assert(comparisonType.SpecialType switch { SpecialType.System_Byte => true, SpecialType.System_SByte => true, SpecialType.System_Int16 => true, SpecialType.System_UInt16 => true, _ => false }); comparisonType = _factory.SpecialType(SpecialType.System_Int32); Conversion c = _factory.ClassifyEmitConversion(input, comparisonType); Debug.Assert(c.IsNumeric || c.IsEnumeration); input = _factory.Convert(comparisonType, input, c); c = _factory.ClassifyEmitConversion(literal, comparisonType); Debug.Assert(c.IsNumeric || c.IsEnumeration); literal = _factory.Convert(comparisonType, literal, c); } return this._localRewriter.MakeBinaryOperator(_factory.Syntax, operatorKind, input, literal, _factory.SpecialType(SpecialType.System_Boolean), method: null, constrainedToTypeOpt: null); } private BoundExpression MakeSpanStringTest(BoundExpression input, ConstantValue value) { var isReadOnlySpan = input.Type.IsReadOnlySpanChar(); // Binder.ConvertPatternExpression() has checked for these well-known members. var sequenceEqual = ((MethodSymbol)_factory.WellKnownMember(isReadOnlySpan ? WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T : WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T)) .Construct(_factory.SpecialType(SpecialType.System_Char)); var asSpan = (MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_MemoryExtensions__AsSpan_String); Debug.Assert(sequenceEqual != null && asSpan != null); return _factory.Call(null, sequenceEqual, input, _factory.Call(null, asSpan, _factory.StringLiteral(value))); } /// <summary> /// Lower a test followed by an evaluation into a side-effect followed by a test. This permits us to optimize /// a type test followed by a cast into an `as` expression followed by a null check. Returns true if the optimization /// applies and the results are placed into <paramref name="sideEffect"/> and <paramref name="test"/>. The caller /// should place the side-effect before the test in the generated code. /// </summary> /// <param name="evaluation"></param> /// <param name="test"></param> /// <param name="sideEffect"></param> /// <param name="testExpression"></param> /// <returns>true if the optimization is applied</returns> protected bool TryLowerTypeTestAndCast( BoundDagTest test, BoundDagEvaluation evaluation, [NotNullWhen(true)] out BoundExpression sideEffect, [NotNullWhen(true)] out BoundExpression testExpression) { CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = _localRewriter.GetNewCompoundUseSiteInfo(); // case 1: type test followed by cast to that type if (test is BoundDagTypeTest typeDecision && evaluation is BoundDagTypeEvaluation typeEvaluation1 && typeDecision.Type.IsReferenceType && typeEvaluation1.Type.Equals(typeDecision.Type, TypeCompareKind.AllIgnoreOptions) && typeEvaluation1.Input.Equals(typeDecision.Input)) { BoundExpression input = _tempAllocator.GetTemp(test.Input); BoundExpression output = _tempAllocator.GetTemp(evaluation.MakeResultTemp()); Debug.Assert(output.Type is { }); sideEffect = _factory.AssignmentExpression(output, _factory.As(input, typeEvaluation1.Type)); testExpression = _factory.ObjectNotEqual(output, _factory.Null(output.Type)); return true; } // case 2: null check followed by cast to a base type if (test is BoundDagNonNullTest nonNullTest && evaluation is BoundDagTypeEvaluation typeEvaluation2 && _factory.Compilation.Conversions.ClassifyBuiltInConversion(test.Input.Type, typeEvaluation2.Type, isChecked: false, ref useSiteInfo) is Conversion conv && (conv.IsIdentity || conv.Kind == ConversionKind.ImplicitReference || conv.IsBoxing) && typeEvaluation2.Input == nonNullTest.Input) { BoundExpression input = _tempAllocator.GetTemp(test.Input); var baseType = typeEvaluation2.Type; BoundExpression output = _tempAllocator.GetTemp(evaluation.MakeResultTemp()); sideEffect = _factory.AssignmentExpression(output, _factory.Convert(baseType, input, conv)); testExpression = _factory.ObjectNotEqual(output, _factory.Null(baseType)); _localRewriter._diagnostics.Add(test.Syntax, useSiteInfo); return true; } sideEffect = testExpression = null; return false; } /// <summary> /// Produce assignment of the input expression. This method is also responsible for assigning /// variables for some pattern-matching temps that can be shared with user variables. /// </summary> protected BoundDecisionDag ShareTempsAndEvaluateInput( BoundExpression loweredInput, BoundDecisionDag decisionDag, Action<BoundExpression> addCode, out BoundExpression savedInputExpression) { Debug.Assert(loweredInput.Type is { }); // We share input variables if there is no when clause (because a when clause might mutate them). bool anyWhenClause = decisionDag.TopologicallySortedNodes .Any(static node => node is BoundWhenDecisionDagNode { WhenExpression: { ConstantValueOpt: null } }); var inputDagTemp = BoundDagTemp.ForOriginalInput(loweredInput); if ((loweredInput.Kind == BoundKind.Local || loweredInput.Kind == BoundKind.Parameter) && loweredInput.GetRefKind() == RefKind.None && !anyWhenClause) { // If we're switching on a local variable and there is no when clause, // we assume the value of the local variable does not change during the execution of the // decision automaton and we just reuse the local variable when we need the input expression. // It is possible for this assumption to be violated by a side-effecting Deconstruct that // modifies the local variable which has been captured in a lambda. Since the language assumes // that functions called by pattern-matching are idempotent and not side-effecting, we feel // justified in taking this assumption in the compiler too. bool tempAssigned = _tempAllocator.TrySetTemp(inputDagTemp, loweredInput); Debug.Assert(tempAssigned); } foreach (BoundDecisionDagNode node in decisionDag.TopologicallySortedNodes) { if (node is BoundWhenDecisionDagNode w) { // We share a slot for a user-declared pattern-matching variable with a pattern temp if there // is no user-written when-clause that could modify the variable before the matching // automaton is done with it (checked by the caller). foreach (BoundPatternBinding binding in w.Bindings) { if (binding.VariableAccess is BoundLocal l) { Debug.Assert(l.LocalSymbol.DeclarationKind == LocalDeclarationKind.PatternVariable); _ = _tempAllocator.TrySetTemp(binding.TempContainingValue, binding.VariableAccess); } } } } if (loweredInput.Type.IsTupleType && !loweredInput.Type.OriginalDefinition.Equals(_factory.Compilation.GetWellKnownType(WellKnownType.System_ValueTuple_TRest)) && loweredInput.Syntax.Kind() == SyntaxKind.TupleExpression && loweredInput is BoundObjectCreationExpression expr && !decisionDag.TopologicallySortedNodes.Any(static n => usesOriginalInput(n))) { // If the switch governing expression is a tuple literal whose whole value is not used anywhere, // (though perhaps its component parts are used), then we can save the component parts // and assign them into temps (or perhaps user variables) to avoid the creation of // the tuple altogether. decisionDag = RewriteTupleInput(decisionDag, expr, addCode, !anyWhenClause, out savedInputExpression); } else { // Otherwise we emit an assignment of the input expression to a temporary variable. BoundExpression inputTemp = _tempAllocator.GetTemp(inputDagTemp); savedInputExpression = inputTemp; if (inputTemp != loweredInput) { addCode(_factory.AssignmentExpression(inputTemp, loweredInput)); } } Debug.Assert(savedInputExpression != null); return decisionDag; static bool usesOriginalInput(BoundDecisionDagNode node) { switch (node) { case BoundWhenDecisionDagNode n: return n.Bindings.Any(static b => b.TempContainingValue.IsOriginalInput); case BoundTestDecisionDagNode t: return t.Test.Input.IsOriginalInput; case BoundEvaluationDecisionDagNode e: switch (e.Evaluation) { case BoundDagFieldEvaluation f: return f.Input.IsOriginalInput && !f.Field.IsTupleElement(); default: return e.Evaluation.Input.IsOriginalInput; } default: return false; } } } /// <summary> /// We have a decision dag whose input is a tuple literal, and the decision dag does not need the tuple itself. /// We rewrite the decision dag into one which doesn't touch the tuple, but instead works directly with the /// values that have been stored in temps. This permits the caller to avoid creation of the tuple object /// itself. We also emit assignments of the tuple values into their corresponding temps. /// </summary> /// <param name="savedInputExpression">An expression that produces the value of the original input if needed /// by the caller.</param> /// <returns>A new decision dag that does not reference the input directly</returns> private BoundDecisionDag RewriteTupleInput( BoundDecisionDag decisionDag, BoundObjectCreationExpression loweredInput, Action<BoundExpression> addCode, bool canShareInputs, out BoundExpression savedInputExpression) { int count = loweredInput.Arguments.Length; // first evaluate the inputs (in order) into temps var originalInput = BoundDagTemp.ForOriginalInput(loweredInput.Syntax, loweredInput.Type); var newArguments = ArrayBuilder<BoundExpression>.GetInstance(loweredInput.Arguments.Length); for (int i = 0; i < count; i++) { var field = loweredInput.Type.TupleElements[i].CorrespondingTupleField; Debug.Assert(field != null); var expr = loweredInput.Arguments[i]; var fieldFetchEvaluation = new BoundDagFieldEvaluation(expr.Syntax, field, originalInput); var temp = fieldFetchEvaluation.MakeResultTemp(); storeToTemp(temp, expr); newArguments.Add(_tempAllocator.GetTemp(temp)); } var rewrittenDag = decisionDag.Rewrite(makeReplacement); savedInputExpression = loweredInput.Update( loweredInput.Constructor, arguments: newArguments.ToImmutableAndFree(), loweredInput.ArgumentNamesOpt, loweredInput.ArgumentRefKindsOpt, loweredInput.Expanded, loweredInput.ArgsToParamsOpt, loweredInput.DefaultArguments, loweredInput.ConstantValueOpt, loweredInput.InitializerExpressionOpt, loweredInput.Type); return rewrittenDag; void storeToTemp(BoundDagTemp temp, BoundExpression expr) { Debug.Assert(!IsCapturedPrimaryConstructorParameter(expr)); if (canShareInputs && (expr.Kind == BoundKind.Parameter || expr.Kind == BoundKind.Local) && _tempAllocator.TrySetTemp(temp, expr)) { // we've arranged to use the input value from the variable it is already stored in } else { var tempToHoldInput = _tempAllocator.GetTemp(temp); addCode(_factory.AssignmentExpression(tempToHoldInput, expr)); } } static BoundDecisionDagNode makeReplacement(BoundDecisionDagNode node, IReadOnlyDictionary<BoundDecisionDagNode, BoundDecisionDagNode> replacement) { switch (node) { case BoundEvaluationDecisionDagNode evalNode: if (evalNode.Evaluation is BoundDagFieldEvaluation eval && eval.Input.IsOriginalInput && eval.Field is var field && field.CorrespondingTupleField != null && field.TupleElementIndex is int i) { // The elements of an input tuple were evaluated beforehand, so don't need to be evaluated now. return replacement[evalNode.Next]; } // Since we are performing an optimization whose precondition is that the original // input is not used except to get its elements, we can assert here that the original // input is not used for anything else. Debug.Assert(!evalNode.Evaluation.Input.IsOriginalInput); break; case BoundTestDecisionDagNode testNode: Debug.Assert(!testNode.Test.Input.IsOriginalInput); break; } return BoundDecisionDag.TrivialReplacement(node, replacement); } } } } }