|
// 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 = new BoundDagTemp(f.Syntax, field.Type, f);
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 = new BoundDagTemp(p.Syntax, property.Type, p);
BoundExpression output = _tempAllocator.GetTemp(outputTemp);
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 == WellKnownMemberNames.DeconstructMethodName);
int extensionExtra;
if (method.IsStatic)
{
Debug.Assert(method.IsExtensionMethod);
receiver = _factory.Type(method.ContainingType);
addArg(method.ParameterRefKinds[0], input);
extensionExtra = 1;
}
else
{
receiver = input;
extensionExtra = 0;
}
for (int i = extensionExtra; i < method.ParameterCount; i++)
{
ParameterSymbol parameter = method.Parameters[i];
Debug.Assert(parameter.RefKind == RefKind.Out);
var outputTemp = new BoundDagTemp(d.Syntax, parameter.Type, d, i - extensionExtra);
addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp));
}
return _factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree());
}
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);
input = _factory.Convert(inputType, input);
}
TypeSymbol type = t.Type;
var outputTemp = new BoundDagTemp(t.Syntax, type, t);
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);
_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
{
evaluated = _factory.Convert(type, input, conversion);
}
}
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 = new BoundDagTemp(e.Syntax, type, e);
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 = new BoundDagTemp(e.Syntax, e.IndexerType, e);
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 = new BoundDagTemp(e.Syntax, e.SliceType, e);
BoundExpression output = _tempAllocator.GetTemp(outputTemp);
return _factory.AssignmentExpression(output, access);
}
case BoundDagAssignmentEvaluation:
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),
_factory.Convert(operandType, new BoundLiteral(syntax, ConstantValue.Null, objectType)),
_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);
input = _factory.Convert(comparisonType, input);
literal = _factory.Convert(comparisonType, literal);
}
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 == typeDecision.Input)
{
BoundExpression input = _tempAllocator.GetTemp(test.Input);
BoundExpression output = _tempAllocator.GetTemp(new BoundDagTemp(evaluation.Syntax, typeEvaluation1.Type, evaluation));
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(new BoundDagTemp(evaluation.Syntax, baseType, evaluation));
sideEffect = _factory.AssignmentExpression(output, _factory.Convert(baseType, input));
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 = new BoundDagTemp(expr.Syntax, expr.Type, fieldFetchEvaluation);
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);
}
}
}
}
}
|