/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs
1 541 строка
69 KB
Jan Jones
Validate pooled objects in compiler tests (#82784)
15 май 2026, 09:55
Не верифицирован
15 май 2026, 09:55
e141f0d
Код
Авторство
О чём код?
// 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.Collections.Immutable; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal sealed class SpillSequenceSpiller : BoundTreeRewriterWithStackGuard { private const BoundKind SpillSequenceBuilderKind = (BoundKind)byte.MaxValue; private readonly SyntheticBoundNodeFactory _F; private readonly PooledDictionary<LocalSymbol, LocalSymbol> _tempSubstitution; private readonly PooledDictionary<LocalSymbol, BoundComplexConditionalReceiver> _receiverSubstitution; private SpillSequenceSpiller( MethodSymbol method, SyntaxNode syntaxNode, TypeCompilationState compilationState, PooledDictionary<LocalSymbol, LocalSymbol> tempSubstitution, PooledDictionary<LocalSymbol, BoundComplexConditionalReceiver> receiverSubstitution, BindingDiagnosticBag diagnostics) { _F = new SyntheticBoundNodeFactory(method, syntaxNode, compilationState, diagnostics); _F.CurrentFunction = method; _tempSubstitution = tempSubstitution; _receiverSubstitution = receiverSubstitution; } private sealed class BoundSpillSequenceBuilder : BoundExpression { public readonly BoundExpression Value; private ArrayBuilder<LocalSymbol> _locals; private ArrayBuilder<BoundStatement> _statements; public BoundSpillSequenceBuilder(SyntaxNode syntax, BoundExpression value = null) : base(SpillSequenceBuilderKind, syntax, value?.Type) { Debug.Assert(value?.Kind != SpillSequenceBuilderKind); this.Value = value; } public bool HasStatements { get { return _statements != null; } } public bool HasLocals { get { return _locals != null; } } public ImmutableArray<LocalSymbol> GetLocals() { return (_locals == null) ? ImmutableArray<LocalSymbol>.Empty : _locals.ToImmutable(); } public ImmutableArray<BoundStatement> GetStatements() { if (_statements == null) { return ImmutableArray<BoundStatement>.Empty; } return _statements.ToImmutable(); } internal BoundSpillSequenceBuilder Update(BoundExpression value) { var result = new BoundSpillSequenceBuilder(this.Syntax, value); result._locals = _locals; result._statements = _statements; return result; } public void Free() { if (_locals != null) _locals.Free(); if (_statements != null) _statements.Free(); } internal void Include(BoundSpillSequenceBuilder other) { if (other != null) { IncludeAndFree(ref _locals, ref other._locals); IncludeAndFree(ref _statements, ref other._statements); } } private static void IncludeAndFree<T>(ref ArrayBuilder<T> left, ref ArrayBuilder<T> right) { if (right == null) { return; } if (left == null) { left = right; return; } left.AddRange(right); right.Free(); } public void AddLocal(LocalSymbol local) { if (_locals == null) { _locals = ArrayBuilder<LocalSymbol>.GetInstance(); } _locals.Add(local); } public void AddLocals(ImmutableArray<LocalSymbol> locals) { foreach (var local in locals) { AddLocal(local); } } public void AddStatement(BoundStatement statement) { if (_statements == null) { _statements = ArrayBuilder<BoundStatement>.GetInstance(); } _statements.Add(statement); } public void AddStatements(ImmutableArray<BoundStatement> statements) { foreach (var statement in statements) { AddStatement(statement); } } internal void AddExpressions(ImmutableArray<BoundExpression> expressions) { foreach (var expression in expressions) { AddStatement(new BoundExpressionStatement(expression.Syntax, expression) { WasCompilerGenerated = true }); } } #if DEBUG internal override string Dump() { var node = new TreeDumperNode("boundSpillSequenceBuilder", null, new TreeDumperNode[] { new TreeDumperNode("locals", this.GetLocals(), null), new TreeDumperNode("statements", null, from x in this.GetStatements() select BoundTreeDumperNodeProducer.MakeTree(x)), new TreeDumperNode("value", null, new TreeDumperNode[] { BoundTreeDumperNodeProducer.MakeTree(this.Value) }), new TreeDumperNode("type", this.Type, null) }); return TreeDumper.DumpCompact(node); } #endif } private sealed class LocalSubstituter : BoundTreeRewriterWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator { private readonly PooledDictionary<LocalSymbol, LocalSymbol> _tempSubstitution; private readonly PooledDictionary<LocalSymbol, BoundComplexConditionalReceiver> _receiverSubstitution; private LocalSubstituter( PooledDictionary<LocalSymbol, LocalSymbol> tempSubstitution, PooledDictionary<LocalSymbol, BoundComplexConditionalReceiver> receiverSubstitution, int recursionDepth = 0) : base(recursionDepth) { _tempSubstitution = tempSubstitution; _receiverSubstitution = receiverSubstitution; } public static BoundNode Rewrite( PooledDictionary<LocalSymbol, LocalSymbol> tempSubstitution, PooledDictionary<LocalSymbol, BoundComplexConditionalReceiver> receiverSubstitution, BoundNode node) { if (tempSubstitution.Count == 0) { return node; } var substituter = new LocalSubstituter(tempSubstitution, receiverSubstitution); return substituter.Visit(node); } public override BoundNode VisitLocal(BoundLocal node) { if (!node.LocalSymbol.SynthesizedKind.IsLongLived()) { LocalSymbol longLived; if (_tempSubstitution.TryGetValue(node.LocalSymbol, out longLived)) { Debug.Assert(!_receiverSubstitution.ContainsKey(node.LocalSymbol)); return node.Update(longLived, node.ConstantValueOpt, node.Type); } if (_receiverSubstitution.TryGetValue(node.LocalSymbol, out var receiver)) { return Visit(receiver); } } return base.VisitLocal(node); } } internal static BoundStatement Rewrite(BoundStatement body, MethodSymbol method, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) { var tempSubstitution = PooledDictionary<LocalSymbol, LocalSymbol>.GetInstance(); var receiverSubstitution = PooledDictionary<LocalSymbol, BoundComplexConditionalReceiver>.GetInstance(); var spiller = new SpillSequenceSpiller(method, body.Syntax, compilationState, tempSubstitution, receiverSubstitution, diagnostics); BoundNode result = spiller.Visit(body); result = LocalSubstituter.Rewrite(tempSubstitution, receiverSubstitution, result); tempSubstitution.Free(); receiverSubstitution.Free(); return (BoundStatement)result; } private BoundExpression VisitExpression(ref BoundSpillSequenceBuilder builder, BoundExpression expression) { var e = (BoundExpression)this.Visit(expression); if (e == null || e.Kind != SpillSequenceBuilderKind) { return e; } var newBuilder = (BoundSpillSequenceBuilder)e; if (builder == null) { builder = newBuilder.Update(null); } else { builder.Include(newBuilder); } return newBuilder.Value; } private static BoundExpression UpdateExpression(BoundSpillSequenceBuilder builder, BoundExpression expression) { if (builder == null) { return expression; } Debug.Assert(builder.Value == null); if (!builder.HasLocals && !builder.HasStatements) { builder.Free(); return expression; } return builder.Update(expression); } private BoundStatement UpdateStatement(BoundSpillSequenceBuilder builder, BoundStatement statement) { if (builder == null) { Debug.Assert(statement != null); return statement; } Debug.Assert(builder.Value == null); if (statement != null) { builder.AddStatement(statement); } var result = new BoundBlock(statement.Syntax, builder.GetLocals(), builder.GetStatements()) { WasCompilerGenerated = true }; builder.Free(); return result; } private BoundExpression Spill( BoundSpillSequenceBuilder builder, BoundExpression expression, RefKind refKind = RefKind.None, bool sideEffectsOnly = false) { Debug.Assert(builder != null); if (builder.Syntax != null) _F.Syntax = builder.Syntax; while (true) { switch (expression.Kind) { case BoundKind.ArrayInitialization: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var arrayInitialization = (BoundArrayInitialization)expression; var newInitializers = VisitExpressionList(ref builder, arrayInitialization.Initializers, forceSpill: true); return arrayInitialization.Update(newInitializers); case BoundKind.ArgListOperator: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var argumentList = (BoundArgListOperator)expression; var newArgs = VisitExpressionList(ref builder, argumentList.Arguments, argumentList.ArgumentRefKindsOpt, forceSpill: true); return argumentList.Update(newArgs, argumentList.ArgumentRefKindsOpt, argumentList.Type); case SpillSequenceBuilderKind: var sequenceBuilder = (BoundSpillSequenceBuilder)expression; builder.Include(sequenceBuilder); expression = sequenceBuilder.Value; continue; case BoundKind.Sequence: if (refKind != RefKind.None || expression.Type?.IsRefLikeOrAllowsRefLikeType() == true) { var sequence = (BoundSequence)expression; PromoteAndAddLocals(builder, sequence.Locals); builder.AddExpressions(sequence.SideEffects); expression = sequence.Value; continue; } goto default; case BoundKind.AssignmentOperator: var assignment = (BoundAssignmentOperator)expression; if (assignment.IsRef && assignment is not { Left.Kind: BoundKind.Local, Right.Kind: BoundKind.ArrayAccess }) // Optimize for some known to be safe scenarios. { if (sideEffectsOnly && IsComplexConditionalInitializationOfReceiverRef( assignment, out LocalSymbol receiverRefLocal, out BoundComplexConditionalReceiver complexReceiver, out BoundLocal valueTypeReceiver, out BoundLocal referenceTypeReceiver)) { Debug.Assert(receiverRefLocal.IsKnownToReferToTempIfReferenceType); builder.AddStatement(_F.ExpressionStatement(complexReceiver)); _receiverSubstitution.Add(receiverRefLocal, complexReceiver.Update(valueTypeReceiver, referenceTypeReceiver, complexReceiver.Type)); return null; } else { var left = Spill(builder, assignment.Left, RefKind.Ref); var right = Spill(builder, assignment.Right, RefKind.Ref); expression = assignment.Update(left, right, assignment.IsRef, assignment.Type); } } goto default; case BoundKind.ThisReference: case BoundKind.BaseReference: if (refKind != RefKind.None || expression.Type.IsReferenceType) { return expression; } goto default; case BoundKind.Parameter: if (refKind != RefKind.None) { return expression; } goto default; case BoundKind.Local: var local = (BoundLocal)expression; if (local.LocalSymbol.SynthesizedKind == SynthesizedLocalKind.Spill || refKind != RefKind.None) { return local; } goto default; case BoundKind.FieldAccess: var field = (BoundFieldAccess)expression; var fieldSymbol = field.FieldSymbol; if (fieldSymbol.IsStatic) { // no need to spill static fields if used as locations or if readonly if (refKind != RefKind.None || fieldSymbol.IsReadOnly) { return field; } goto default; } if (refKind == RefKind.None) goto default; var receiver = Spill(builder, field.ReceiverOpt, fieldSymbol.ContainingType.IsValueType ? refKind : RefKind.None); return field.Update(receiver, fieldSymbol, field.ConstantValueOpt, field.ResultKind, field.Type); case BoundKind.Literal: case BoundKind.TypeExpression: return expression; case BoundKind.ConditionalReceiver: // we will rewrite this as a part of rewriting whole LoweredConditionalAccess // later, if needed return expression; case BoundKind.Call: var call = (BoundCall)expression; // If the method is known to have no observable side-effects, we can spill its receiver and arguments, // and call it later, after some other (unrelated) side-effects are evaluated. // It is similar to spilling a field/array access. if (refKind != RefKind.None) { if (call.Method.OriginalDefinition is SynthesizedInlineArrayFirstElementRefMethod or SynthesizedInlineArrayFirstElementRefReadOnlyMethod) { Debug.Assert(call.Arguments.Length == 1); return call.Update(ImmutableArray.Create(Spill(builder, call.Arguments[0], call.ArgumentRefKindsOpt[0]))); } else if (call.Method.OriginalDefinition is SynthesizedInlineArrayElementRefMethod or SynthesizedInlineArrayElementRefReadOnlyMethod) { return spillInlineArrayHelperWithTwoArguments(builder, call); } else if (call.Method.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Span_T__get_Item) || call.Method.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__get_Item)) { Debug.Assert(call.Arguments.Length == 1); return call.Update(Spill(builder, call.ReceiverOpt, ReceiverSpillRefKind(call.ReceiverOpt)), initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, call.Method, ImmutableArray.Create(Spill(builder, call.Arguments[0]))); } } else if (call.Method.OriginalDefinition is SynthesizedInlineArrayAsSpanMethod or SynthesizedInlineArrayAsReadOnlySpanMethod) { return spillInlineArrayHelperWithTwoArguments(builder, call); } else if (call.Method.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Span_T__Slice_Int_Int) || call.Method.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int)) { Debug.Assert(call.Arguments.Length == 2); return call.Update(Spill(builder, call.ReceiverOpt, ReceiverSpillRefKind(call.ReceiverOpt)), initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, call.Method, ImmutableArray.Create(Spill(builder, call.Arguments[0]), Spill(builder, call.Arguments[1]))); } else if (call.Method == _F.Compilation.GetSpecialTypeMember(SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) { Debug.Assert(call.Arguments.Length == 1); return call.Update([Spill(builder, call.Arguments[0])]); } goto default; case BoundKind.ObjectCreationExpression: var objectCreationExpression = (BoundObjectCreationExpression)expression; if (refKind == RefKind.None && objectCreationExpression.InitializerExpressionOpt is null && objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetSpecialTypeMember(SpecialMember.System_ReadOnlySpan_T__ctor_Reference)) { Debug.Assert(objectCreationExpression.Arguments.Length == 1); var argRefKinds = objectCreationExpression.ArgumentRefKindsOpt; return objectCreationExpression.Update(objectCreationExpression.Constructor, [Spill(builder, objectCreationExpression.Arguments[0], argRefKinds.IsDefault ? RefKind.None : argRefKinds[0])], objectCreationExpression.ArgumentRefKindsOpt, newInitializerExpression: null); } goto default; default: if (expression.Type.IsVoidType() || sideEffectsOnly) { builder.AddStatement(_F.ExpressionStatement(expression)); return null; } else { BoundAssignmentOperator assignToTemp; var replacement = _F.StoreToTemp( expression, out assignToTemp, refKind: refKind, kind: SynthesizedLocalKind.Spill, syntaxOpt: _F.Syntax); builder.AddLocal(replacement.LocalSymbol); builder.AddStatement(_F.ExpressionStatement(assignToTemp)); return replacement; } } } BoundExpression spillInlineArrayHelperWithTwoArguments(BoundSpillSequenceBuilder builder, BoundCall call) { Debug.Assert(call.Arguments.Length == 2); return call.Update(ImmutableArray.Create(Spill(builder, call.Arguments[0], call.ArgumentRefKindsOpt[0]), call.Arguments[1].ConstantValueOpt is { } ? call.Arguments[1] : Spill(builder, call.Arguments[1]))); } } internal static bool IsComplexConditionalInitializationOfReceiverRef( BoundAssignmentOperator assignment, out LocalSymbol outReceiverRefLocal, out BoundComplexConditionalReceiver outComplexReceiver, out BoundLocal outValueTypeReceiver, out BoundLocal outReferenceTypeReceiver) { if (assignment is { IsRef: true, Left: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: not RefKind.None } receiverRefLocal }, Right: BoundComplexConditionalReceiver { ValueTypeReceiver: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: not RefKind.None } } valueTypeReceiver, ReferenceTypeReceiver: BoundSequence { Locals.IsEmpty: true, SideEffects: [ BoundAssignmentOperator { IsRef: false, Left: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.None } referenceTypeClone }, Right: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: not RefKind.None } originalReceiverReference } } ], Value: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.None } } referenceTypeReceiver } } complexReceiver, } && (object)referenceTypeClone == referenceTypeReceiver.LocalSymbol && (object)originalReceiverReference == valueTypeReceiver.LocalSymbol && (object)receiverRefLocal != valueTypeReceiver.LocalSymbol && (object)receiverRefLocal != referenceTypeClone && receiverRefLocal.Type.IsTypeParameter() && !receiverRefLocal.Type.IsReferenceType && !receiverRefLocal.Type.IsValueType && valueTypeReceiver.Type.Equals(receiverRefLocal.Type, TypeCompareKind.AllIgnoreOptions) && receiverRefLocal.RefKind == valueTypeReceiver.LocalSymbol.RefKind && referenceTypeReceiver.Type.Equals(receiverRefLocal.Type, TypeCompareKind.AllIgnoreOptions) ) { outReceiverRefLocal = receiverRefLocal; outComplexReceiver = complexReceiver; outValueTypeReceiver = valueTypeReceiver; outReferenceTypeReceiver = referenceTypeReceiver; return true; } outReceiverRefLocal = null; outComplexReceiver = null; outValueTypeReceiver = null; outReferenceTypeReceiver = null; return false; } private ImmutableArray<BoundExpression> VisitExpressionList( ref BoundSpillSequenceBuilder builder, ImmutableArray<BoundExpression> args, ImmutableArray<RefKind> refKinds = default(ImmutableArray<RefKind>), bool forceSpill = false, bool sideEffectsOnly = false) { Debug.Assert(!sideEffectsOnly || refKinds.IsDefault); Debug.Assert(refKinds.IsDefault || refKinds.Length == args.Length); if (args.Length == 0) { return args; } var newList = VisitList(args); Debug.Assert(newList.Length == args.Length); int lastSpill; if (forceSpill) { lastSpill = newList.Length; } else { lastSpill = -1; for (int i = newList.Length - 1; i >= 0; i--) { if (newList[i].Kind == SpillSequenceBuilderKind) { lastSpill = i; break; } } } if (lastSpill == -1) { return newList; } if (builder == null) { builder = new BoundSpillSequenceBuilder(lastSpill < newList.Length ? (newList[lastSpill] as BoundSpillSequenceBuilder)?.Syntax : null); } var result = ArrayBuilder<BoundExpression>.GetInstance(newList.Length); // everything up until the last spill must be spilled entirely for (int i = 0; i < lastSpill; i++) { var refKind = refKinds.IsDefault ? RefKind.None : refKinds[i]; var replacement = Spill(builder, newList[i], refKind, sideEffectsOnly); Debug.Assert(sideEffectsOnly || replacement != null); if (!sideEffectsOnly) { result.Add(replacement); } } // the value of the last spill and everything that follows is not spilled if (lastSpill < newList.Length) { var lastSpillNode = (BoundSpillSequenceBuilder)newList[lastSpill]; builder.Include(lastSpillNode); result.Add(lastSpillNode.Value); for (int i = lastSpill + 1; i < newList.Length; i++) { result.Add(newList[i]); } } return result.ToImmutableAndFree(); } #region Statement Visitors public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node) { BoundSpillSequenceBuilder builder = null; var expression = VisitExpression(ref builder, node.Expression); return UpdateStatement(builder, node.Update(expression, node.Cases, node.DefaultLabel, node.LengthBasedStringSwitchDataOpt)); } public override BoundNode VisitThrowStatement(BoundThrowStatement node) { BoundSpillSequenceBuilder builder = null; BoundExpression expression = VisitExpression(ref builder, node.ExpressionOpt); return UpdateStatement(builder, node.Update(expression)); } public override BoundNode VisitExpressionStatement(BoundExpressionStatement node) { BoundSpillSequenceBuilder builder = null; BoundExpression expr = VisitExpression(ref builder, node.Expression); Debug.Assert(expr != null); Debug.Assert(builder == null || builder.Value == null); return UpdateStatement(builder, node.Update(expr)); } public override BoundNode VisitConditionalGoto(BoundConditionalGoto node) { BoundSpillSequenceBuilder builder = null; var condition = VisitExpression(ref builder, node.Condition); return UpdateStatement(builder, node.Update(condition, node.JumpIfTrue, node.Label)); } public override BoundNode VisitReturnStatement(BoundReturnStatement node) { BoundSpillSequenceBuilder builder = null; var expression = VisitExpression(ref builder, node.ExpressionOpt); return UpdateStatement(builder, node.Update(node.RefKind, expression, @checked: node.Checked)); } public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement node) { BoundSpillSequenceBuilder builder = null; var expression = VisitExpression(ref builder, node.Expression); return UpdateStatement(builder, node.Update(expression)); } #nullable enable public override BoundNode VisitCatchBlock(BoundCatchBlock node) { BoundExpression? exceptionSourceOpt = (BoundExpression?)this.Visit(node.ExceptionSourceOpt); var locals = node.Locals; var exceptionFilterPrologueOpt = node.ExceptionFilterPrologueOpt; if (exceptionFilterPrologueOpt is not null) { exceptionFilterPrologueOpt = (BoundStatementList?)VisitStatementList(exceptionFilterPrologueOpt); } BoundSpillSequenceBuilder? builder = null; var exceptionFilterOpt = VisitExpression(ref builder, node.ExceptionFilterOpt); Debug.Assert(exceptionFilterPrologueOpt is null || builder is null, "You are exercising SpillSequenceSpiller in a new fashion, causing a spill in an exception filter after LocalRewriting is complete. This is not someting " + "that this builder supports today, so please update this rewrite to include the statements from exceptionFilterPrologueOpt with the appropriate " + "syntax node and tracking."); if (builder is { }) { Debug.Assert(builder.Value is null); locals = locals.AddRange(builder.GetLocals()); exceptionFilterPrologueOpt = new BoundStatementList(node.Syntax, builder.GetStatements()); builder.Free(); } BoundBlock body = (BoundBlock)this.Visit(node.Body); TypeSymbol? exceptionTypeOpt = this.VisitType(node.ExceptionTypeOpt); return node.Update(locals, exceptionSourceOpt, exceptionTypeOpt, exceptionFilterPrologueOpt, exceptionFilterOpt, body, node.IsSynthesizedAsyncCatchAll); } #nullable disable #if DEBUG public override BoundNode DefaultVisit(BoundNode node) { Debug.Assert(!(node is BoundStatement)); return base.DefaultVisit(node); } #endif #endregion #region Expression Visitors public override BoundNode VisitAwaitExpression(BoundAwaitExpression node) { // An await expression has already been wrapped in a BoundSpillSequence if not at the top level, so // the spilling will occur in the enclosing node. BoundSpillSequenceBuilder builder = null; var expr = VisitExpression(ref builder, node.Expression); return UpdateExpression(builder, node.Update(expr, node.AwaitableInfo, node.DebugInfo, node.Type)); } public override BoundNode VisitSpillSequence(BoundSpillSequence node) { var builder = new BoundSpillSequenceBuilder(node.Syntax); // Ensure later errors (e.g. in async rewriting) are associated with the correct node. _F.Syntax = node.Syntax; builder.AddStatements(VisitList(node.SideEffects)); builder.AddLocals(node.Locals); var value = VisitExpression(ref builder, node.Value); return builder.Update(value); } public override BoundNode VisitAddressOfOperator(BoundAddressOfOperator node) { BoundSpillSequenceBuilder builder = null; var expr = VisitExpression(ref builder, node.Operand); return UpdateExpression(builder, node.Update(expr, node.IsManaged, node.Type)); } public override BoundNode VisitArgListOperator(BoundArgListOperator node) { BoundSpillSequenceBuilder builder = null; var newArgs = VisitExpressionList(ref builder, node.Arguments); return UpdateExpression(builder, node.Update(newArgs, node.ArgumentRefKindsOpt, node.Type)); } public override BoundNode VisitArrayAccess(BoundArrayAccess node) { BoundSpillSequenceBuilder builder = null; var expression = VisitExpression(ref builder, node.Expression); BoundSpillSequenceBuilder indicesBuilder = null; var indices = this.VisitExpressionList(ref indicesBuilder, node.Indices); if (indicesBuilder != null) { // spill the array if there were await expressions in the indices if (builder == null) { builder = new BoundSpillSequenceBuilder(indicesBuilder.Syntax); } expression = Spill(builder, expression); } if (builder != null) { builder.Include(indicesBuilder); indicesBuilder = builder; builder = null; } return UpdateExpression(indicesBuilder, node.Update(expression, indices, node.Type)); } public override BoundNode VisitArrayCreation(BoundArrayCreation node) { BoundSpillSequenceBuilder builder = null; var init = (BoundArrayInitialization)VisitExpression(ref builder, node.InitializerOpt); ImmutableArray<BoundExpression> bounds; if (builder == null) { bounds = VisitExpressionList(ref builder, node.Bounds); } else { // spill bounds expressions if initializers contain await var boundsBuilder = new BoundSpillSequenceBuilder(builder.Syntax); bounds = VisitExpressionList(ref boundsBuilder, node.Bounds, forceSpill: true); boundsBuilder.Include(builder); builder = boundsBuilder; } return UpdateExpression(builder, node.Update(bounds, init, node.Type)); } public override BoundNode VisitArrayInitialization(BoundArrayInitialization node) { BoundSpillSequenceBuilder builder = null; var initializers = this.VisitExpressionList(ref builder, node.Initializers); return UpdateExpression(builder, node.Update(initializers)); } public override BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node) { BoundSpillSequenceBuilder builder = null; BoundExpression count = VisitExpression(ref builder, node.Count); var initializerOpt = (BoundArrayInitialization)VisitExpression(ref builder, node.InitializerOpt); return UpdateExpression(builder, node.Update(node.ElementType, count, initializerOpt, node.Type)); } public override BoundNode VisitArrayLength(BoundArrayLength node) { BoundSpillSequenceBuilder builder = null; var expression = VisitExpression(ref builder, node.Expression); return UpdateExpression(builder, node.Update(expression, node.Type)); } public override BoundNode VisitAsOperator(BoundAsOperator node) { BoundSpillSequenceBuilder builder = null; var operand = VisitExpression(ref builder, node.Operand); Debug.Assert(node.OperandPlaceholder is null); Debug.Assert(node.OperandConversion is null); return UpdateExpression(builder, node.Update(operand, node.TargetType, node.OperandPlaceholder, node.OperandConversion, node.Type)); } public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.Right); BoundExpression left = node.Left; if (builder == null) { left = VisitExpression(ref builder, left); } else { // if the right-hand-side has await, spill the left var leftBuilder = new BoundSpillSequenceBuilder(builder.Syntax); switch (left.Kind) { case BoundKind.Local: case BoundKind.Parameter: // locals and parameters are directly assignable, LHS is not on the stack so nothing to spill break; case BoundKind.FieldAccess: var field = (BoundFieldAccess)left; // static fields are directly assignable, LHS is not on the stack, nothing to spill if (field.FieldSymbol.IsStatic) break; // instance fields are directly assignable, but receiver is pushed, so need to spill that. left = fieldWithSpilledReceiver(field, ref leftBuilder, isAssignmentTarget: true); break; case BoundKind.ArrayAccess: var arrayAccess = (BoundArrayAccess)left; // array and indices are pushed on stack so need to spill that var expression = VisitExpression(ref leftBuilder, arrayAccess.Expression); expression = Spill(leftBuilder, expression, RefKind.None); var indices = this.VisitExpressionList(ref leftBuilder, arrayAccess.Indices, forceSpill: true); left = arrayAccess.Update(expression, indices, arrayAccess.Type); break; default: // must be something indirectly assignable, just visit and spill as an ordinary Ref (not a RefReadOnly!!) // // NOTE: in some cases this will result in spiller producing an error. // For example if the LHS is a ref-returning method like // // obj.RefReturning(a, b, c) = await Something(); // // the spiller would eventually have to spill the evaluation result of "refReturning" call as an ordinary Ref, // which it can't. left = Spill(leftBuilder, VisitExpression(ref leftBuilder, left), RefKind.Ref); break; } leftBuilder.Include(builder); builder = leftBuilder; } return UpdateExpression(builder, node.Update(left, right, node.IsRef, node.Type)); BoundExpression fieldWithSpilledReceiver(BoundFieldAccess field, ref BoundSpillSequenceBuilder leftBuilder, bool isAssignmentTarget) { var generateDummyFieldAccess = false; if (!field.FieldSymbol.IsStatic) { Debug.Assert(field.ReceiverOpt is object); BoundExpression receiver; if (field.FieldSymbol.ContainingType.IsReferenceType) { // a reference type can always live across await so Spill using leftBuilder receiver = Spill(leftBuilder, VisitExpression(ref leftBuilder, field.ReceiverOpt)); // dummy field access to trigger NRE // a.b = c will trigger a NRE if a is null on assignment, // but a.b.c = d will trigger a NRE if a is null before evaluating d // so check whether we assign to the field directly generateDummyFieldAccess = !isAssignmentTarget; } else if (field.ReceiverOpt is BoundArrayAccess arrayAccess) { // an arrayAccess returns a ref so can only be called after the await, but spill expression and indices var expression = VisitExpression(ref leftBuilder, arrayAccess.Expression); expression = Spill(leftBuilder, expression, RefKind.None); var indices = this.VisitExpressionList(ref leftBuilder, arrayAccess.Indices, forceSpill: true); receiver = arrayAccess.Update(expression, indices, arrayAccess.Type); // dummy array access to trigger IndexOutRangeException or NRE // we only need this if the array access is a receiver since // a[0] = b triggers a NRE/IORE on assignment // but a[0].b = c triggers an NRE/IORE before evaluating c Spill(leftBuilder, receiver, sideEffectsOnly: true); } else if (field.ReceiverOpt is BoundFieldAccess receiverField) { receiver = fieldWithSpilledReceiver(receiverField, ref leftBuilder, isAssignmentTarget: false); } else { receiver = Spill(leftBuilder, VisitExpression(ref leftBuilder, field.ReceiverOpt), RefKind.Ref); } field = field.Update(receiver, field.FieldSymbol, field.ConstantValueOpt, field.ResultKind, field.Type); } if (generateDummyFieldAccess) { Spill(leftBuilder, field, sideEffectsOnly: true); } return field; } } public override BoundNode VisitBadExpression(BoundBadExpression node) { // Cannot recurse into BadExpression children return node; } public override BoundNode VisitUserDefinedConditionalLogicalOperator(BoundUserDefinedConditionalLogicalOperator node) { throw ExceptionUtilities.Unreachable(); } public override BoundNode VisitBinaryOperator(BoundBinaryOperator node) { Debug.Assert(!node.OperatorKind.IsDynamic()); BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.Right); BoundExpression left; if (builder == null) { left = VisitExpression(ref builder, node.Left); } else { var leftBuilder = new BoundSpillSequenceBuilder(builder.Syntax); left = VisitExpression(ref leftBuilder, node.Left); left = Spill(leftBuilder, left); if (node.OperatorKind == BinaryOperatorKind.LogicalBoolOr || node.OperatorKind == BinaryOperatorKind.LogicalBoolAnd) { var tmp = _F.SynthesizedLocal(node.Type, kind: SynthesizedLocalKind.Spill, syntax: _F.Syntax); leftBuilder.AddLocal(tmp); leftBuilder.AddStatement(_F.Assignment(_F.Local(tmp), left)); leftBuilder.AddStatement(_F.If( node.OperatorKind == BinaryOperatorKind.LogicalBoolAnd ? _F.Local(tmp) : _F.Not(_F.Local(tmp)), UpdateStatement(builder, _F.Assignment(_F.Local(tmp), right)))); return UpdateExpression(leftBuilder, _F.Local(tmp)); } else { // if the right-hand-side has await, spill the left leftBuilder.Include(builder); builder = leftBuilder; } } return UpdateExpression(builder, node.Update(node.OperatorKind, node.ConstantValueOpt, node.BinaryOperatorMethod, node.ConstrainedToType, node.ResultKind, left, right, node.Type)); } public override BoundNode VisitCall(BoundCall node) { BoundSpillSequenceBuilder builder = null; var arguments = this.VisitExpressionList(ref builder, node.Arguments, node.ArgumentRefKindsOpt); BoundExpression receiver = null; if (builder == null || node.ReceiverOpt is BoundTypeExpression) { receiver = VisitExpression(ref builder, node.ReceiverOpt); } else if (node.Method.RequiresInstanceReceiver) { // spill the receiver if there were await expressions in the arguments var receiverBuilder = new BoundSpillSequenceBuilder(builder.Syntax); receiver = node.ReceiverOpt; RefKind refKind = ReceiverSpillRefKind(receiver); Debug.Assert(refKind == RefKind.None || !receiver.Type.IsReferenceType); receiver = Spill(receiverBuilder, VisitExpression(ref receiverBuilder, receiver), refKind: refKind); if (refKind != RefKind.None && CodeGenerator.IsPossibleReferenceTypeReceiverOfConstrainedCall(receiver) && !CodeGenerator.ReceiverIsKnownToReferToTempIfReferenceType(receiver) && !CodeGenerator.IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(node.Arguments)) { var receiverType = receiver.Type; Debug.Assert(!receiverType.IsReferenceType); // A case where T is actually a class must be handled specially. // Taking a reference to a class instance is fragile because the value behind the // reference might change while arguments are evaluated. However, the call should be // performed on the instance that is behind reference at the time we push the // reference to the stack. So, for a class we need to emit a reference to a temporary // location, rather than to the original location var save_Syntax = _F.Syntax; _F.Syntax = node.Syntax; var cache = _F.Local(_F.SynthesizedLocal(receiverType)); receiverBuilder.AddLocal(cache.LocalSymbol); receiverBuilder.AddStatement(_F.ExpressionStatement(new BoundComplexConditionalReceiver(node.Syntax, cache, _F.Sequence(new[] { _F.AssignmentExpression(cache, receiver) }, cache), receiverType) { WasCompilerGenerated = true })); receiver = _F.ComplexConditionalReceiver(receiver, cache); _F.Syntax = save_Syntax; } receiverBuilder.Include(builder); builder = receiverBuilder; } return UpdateExpression(builder, node.Update(receiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, node.Method, arguments)); } private static RefKind ReceiverSpillRefKind(BoundExpression receiver) { var result = RefKind.None; if (!receiver.Type.IsReferenceType && LocalRewriter.CanBePassedByReference(receiver)) { result = receiver.Type.IsReadOnly ? RefKind.In : RefKind.Ref; } return result; } public override BoundNode VisitFunctionPointerInvocation(BoundFunctionPointerInvocation node) { BoundSpillSequenceBuilder builder = null; var arguments = this.VisitExpressionList(ref builder, node.Arguments, node.ArgumentRefKindsOpt); BoundExpression invokedExpression; if (builder == null) { invokedExpression = VisitExpression(ref builder, node.InvokedExpression); } else { var invokedExpressionBuilder = new BoundSpillSequenceBuilder(builder.Syntax); invokedExpression = Spill(invokedExpressionBuilder, VisitExpression(ref invokedExpressionBuilder, node.InvokedExpression)); invokedExpressionBuilder.Include(builder); builder = invokedExpressionBuilder; } return UpdateExpression(builder, node.Update(invokedExpression, arguments, node.ArgumentRefKindsOpt, node.ResultKind, node.Type)); } public override BoundNode VisitConditionalOperator(BoundConditionalOperator node) { BoundSpillSequenceBuilder conditionBuilder = null; var condition = VisitExpression(ref conditionBuilder, node.Condition); BoundSpillSequenceBuilder consequenceBuilder = null; var consequence = VisitExpression(ref consequenceBuilder, node.Consequence); BoundSpillSequenceBuilder alternativeBuilder = null; var alternative = VisitExpression(ref alternativeBuilder, node.Alternative); if (consequenceBuilder == null && alternativeBuilder == null) { return UpdateExpression(conditionBuilder, node.Update(node.IsRef, condition, consequence, alternative, node.ConstantValueOpt, node.NaturalTypeOpt, node.WasTargetTyped, node.Type)); } if (conditionBuilder == null) conditionBuilder = new BoundSpillSequenceBuilder((consequenceBuilder ?? alternativeBuilder).Syntax); if (consequenceBuilder == null) consequenceBuilder = new BoundSpillSequenceBuilder(alternativeBuilder.Syntax); if (alternativeBuilder == null) alternativeBuilder = new BoundSpillSequenceBuilder(consequenceBuilder.Syntax); if (node.Type.IsVoidType()) { conditionBuilder.AddStatement( _F.If(condition, UpdateStatement(consequenceBuilder, _F.ExpressionStatement(consequence)), UpdateStatement(alternativeBuilder, _F.ExpressionStatement(alternative)))); return conditionBuilder.Update(_F.Default(node.Type)); } else if (!node.IsRef) { var tmp = _F.SynthesizedLocal(node.Type, kind: SynthesizedLocalKind.Spill, syntax: _F.Syntax); conditionBuilder.AddLocal(tmp); conditionBuilder.AddStatement( _F.If(condition, UpdateStatement(consequenceBuilder, _F.Assignment(_F.Local(tmp), consequence)), UpdateStatement(alternativeBuilder, _F.Assignment(_F.Local(tmp), alternative)))); return conditionBuilder.Update(_F.Local(tmp)); } else { Debug.Assert(condition.Type.SpecialType == SpecialType.System_Boolean); // 1. Capture the boolean value (the condition) in a temp var tmp = _F.SynthesizedLocal(condition.Type, kind: SynthesizedLocalKind.Spill, syntax: _F.Syntax); conditionBuilder.AddLocal(tmp); conditionBuilder.AddStatement(_F.Assignment(_F.Local(tmp), condition)); condition = _F.Local(tmp); // 2. Conditionally execute side-effects from the builders based on the temp conditionBuilder.AddLocals(consequenceBuilder.GetLocals()); conditionBuilder.AddLocals(alternativeBuilder.GetLocals()); conditionBuilder.AddStatement( _F.If(condition, _F.StatementList(consequenceBuilder.GetStatements()), _F.StatementList(alternativeBuilder.GetStatements()))); consequenceBuilder.Free(); alternativeBuilder.Free(); // 3. Use updated conditional operator as the result. Note, we are using the captured temp as its condition, // plus rewritten consequence and alternative. return conditionBuilder.Update(node.Update(node.IsRef, condition, consequence, alternative, node.ConstantValueOpt, node.NaturalTypeOpt, node.WasTargetTyped, node.Type)); } } public override BoundNode VisitConversion(BoundConversion node) { if (node.ConversionKind == ConversionKind.AnonymousFunction && node.Type.IsExpressionTree()) { // Expression trees do not contain any code that requires spilling. return node; } BoundSpillSequenceBuilder builder = null; var operand = VisitExpression(ref builder, node.Operand); return UpdateExpression( builder, node.UpdateOperand(operand)); } public override BoundNode VisitPassByCopy(BoundPassByCopy node) { BoundSpillSequenceBuilder builder = null; var expression = VisitExpression(ref builder, node.Expression); return UpdateExpression( builder, node.Update( expression, type: node.Type)); } public override BoundNode VisitMethodGroup(BoundMethodGroup node) { throw ExceptionUtilities.Unreachable(); } public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationExpression node) { BoundSpillSequenceBuilder builder = null; var argument = VisitExpression(ref builder, node.Argument); return UpdateExpression(builder, node.Update(argument, node.MethodOpt, node.IsExtensionMethod, node.WasTargetTyped, node.Type)); } public override BoundNode VisitFieldAccess(BoundFieldAccess node) { BoundSpillSequenceBuilder builder = null; var receiver = VisitExpression(ref builder, node.ReceiverOpt); return UpdateExpression(builder, node.Update(receiver, node.FieldSymbol, node.ConstantValueOpt, node.ResultKind, node.Type)); } public override BoundNode VisitIsOperator(BoundIsOperator node) { BoundSpillSequenceBuilder builder = null; var operand = VisitExpression(ref builder, node.Operand); return UpdateExpression(builder, node.Update(operand, node.TargetType, node.ConversionKind, node.Type)); } public override BoundNode VisitMakeRefOperator(BoundMakeRefOperator node) { BoundSpillSequenceBuilder builder = null; var operand = VisitExpression(ref builder, node.Operand); return UpdateExpression(builder, node.Update(operand, node.Type)); } public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { Debug.Assert(node.LeftPlaceholder is null); Debug.Assert(node.LeftConversion is null); BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.RightOperand); BoundExpression left; if (builder == null) { left = VisitExpression(ref builder, node.LeftOperand); } else { var leftBuilder = new BoundSpillSequenceBuilder(builder.Syntax); left = VisitExpression(ref leftBuilder, node.LeftOperand); left = Spill(leftBuilder, left); var tmp = _F.SynthesizedLocal(node.Type, kind: SynthesizedLocalKind.Spill, syntax: _F.Syntax); leftBuilder.AddLocal(tmp); leftBuilder.AddStatement(_F.Assignment(_F.Local(tmp), left)); leftBuilder.AddStatement(_F.If( _F.ObjectEqual(_F.Local(tmp), _F.Null(left.Type)), UpdateStatement(builder, _F.Assignment(_F.Local(tmp), right)))); return UpdateExpression(leftBuilder, _F.Local(tmp)); } return UpdateExpression(builder, node.Update(left, right, node.LeftPlaceholder, node.LeftConversion, node.OperatorResultKind, @checked: node.Checked, node.Type)); } public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalAccess node) { var receiverRefKind = ReceiverSpillRefKind(node.Receiver); BoundSpillSequenceBuilder receiverBuilder = null; var receiver = VisitExpression(ref receiverBuilder, node.Receiver); BoundSpillSequenceBuilder whenNotNullBuilder = null; var whenNotNull = VisitExpression(ref whenNotNullBuilder, node.WhenNotNull); BoundSpillSequenceBuilder whenNullBuilder = null; var whenNullOpt = VisitExpression(ref whenNullBuilder, node.WhenNullOpt); if (whenNotNullBuilder == null && whenNullBuilder == null) { return UpdateExpression(receiverBuilder, node.Update(receiver, node.HasValueMethodOpt, whenNotNull, whenNullOpt, node.Id, node.ForceCopyOfNullableValueType, node.Type)); } if (receiverBuilder == null) receiverBuilder = new BoundSpillSequenceBuilder((whenNotNullBuilder ?? whenNullBuilder).Syntax); if (whenNotNullBuilder == null) whenNotNullBuilder = new BoundSpillSequenceBuilder(whenNullBuilder.Syntax); if (whenNullBuilder == null) whenNullBuilder = new BoundSpillSequenceBuilder(whenNotNullBuilder.Syntax); BoundExpression condition; if (receiver.Type.IsReferenceType || receiver.Type.IsValueType || receiverRefKind == RefKind.None) { // spill to a clone receiver = Spill(receiverBuilder, receiver, RefKind.None); var hasValueOpt = node.HasValueMethodOpt; if (hasValueOpt == null) { condition = _F.IsNotNullReference(receiver); } else { condition = _F.Call(receiver, hasValueOpt); } } else { Debug.Assert(node.HasValueMethodOpt == null); receiver = Spill(receiverBuilder, receiver, RefKind.Ref); var clone = _F.SynthesizedLocal(receiver.Type, _F.Syntax, refKind: RefKind.None, kind: SynthesizedLocalKind.Spill); receiverBuilder.AddLocal(clone); // (object)default(T) != null var isNotClass = _F.IsNotNullReference(_F.Default(receiver.Type)); // isNotCalss || {clone = receiver; (object)clone != null} condition = _F.LogicalOr( isNotClass, _F.MakeSequence( _F.AssignmentExpression(_F.Local(clone), receiver), _F.IsNotNullReference(_F.Local(clone))) ); receiver = _F.ComplexConditionalReceiver(receiver, _F.Local(clone)); } if (node.Type.IsVoidType()) { var whenNotNullStatement = UpdateStatement(whenNotNullBuilder, _F.ExpressionStatement(whenNotNull)); whenNotNullStatement = ConditionalReceiverReplacer.Replace(whenNotNullStatement, receiver, node.Id, RecursionDepth); Debug.Assert(whenNullOpt == null || !LocalRewriter.ReadIsSideeffecting(whenNullOpt)); receiverBuilder.AddStatement(_F.If(condition, whenNotNullStatement)); return receiverBuilder.Update(_F.Default(node.Type)); } else { var tmp = _F.SynthesizedLocal(node.Type, kind: SynthesizedLocalKind.Spill, syntax: _F.Syntax); var whenNotNullStatement = UpdateStatement(whenNotNullBuilder, _F.Assignment(_F.Local(tmp), whenNotNull)); whenNotNullStatement = ConditionalReceiverReplacer.Replace(whenNotNullStatement, receiver, node.Id, RecursionDepth); whenNullOpt = whenNullOpt ?? _F.Default(node.Type); receiverBuilder.AddLocal(tmp); receiverBuilder.AddStatement( _F.If(condition, whenNotNullStatement, UpdateStatement(whenNullBuilder, _F.Assignment(_F.Local(tmp), whenNullOpt)))); return receiverBuilder.Update(_F.Local(tmp)); } } private sealed class ConditionalReceiverReplacer : BoundTreeRewriterWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator { private readonly BoundExpression _receiver; private readonly int _receiverId; #if DEBUG // we must replace exactly one node private int _replaced; #endif private ConditionalReceiverReplacer(BoundExpression receiver, int receiverId, int recursionDepth) : base(recursionDepth) { _receiver = receiver; _receiverId = receiverId; } public static BoundStatement Replace(BoundNode node, BoundExpression receiver, int receiverID, int recursionDepth) { var replacer = new ConditionalReceiverReplacer(receiver, receiverID, recursionDepth); var result = (BoundStatement)replacer.Visit(node); #if DEBUG Debug.Assert(replacer._replaced == 1, "should have replaced exactly one node"); #endif return result; } public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node) { if (node.Id == _receiverId) { #if DEBUG _replaced++; #endif return _receiver; } return node; } } public override BoundNode VisitLambda(BoundLambda node) { var oldCurrentFunction = _F.CurrentFunction; _F.CurrentFunction = node.Symbol; var result = base.VisitLambda(node); _F.CurrentFunction = oldCurrentFunction; return result; } public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement node) { var oldCurrentFunction = _F.CurrentFunction; _F.CurrentFunction = node.Symbol; var result = base.VisitLocalFunctionStatement(node); _F.CurrentFunction = oldCurrentFunction; return result; } public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpression node) { Debug.Assert(node.InitializerExpressionOpt == null); BoundSpillSequenceBuilder builder = null; var arguments = this.VisitExpressionList(ref builder, node.Arguments, node.ArgumentRefKindsOpt); return UpdateExpression(builder, node.Update(node.Constructor, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.DefaultArguments, node.ConstantValueOpt, node.InitializerExpressionOpt, node.Type)); } public override BoundNode VisitPointerElementAccess(BoundPointerElementAccess node) { BoundSpillSequenceBuilder builder = null; var index = VisitExpression(ref builder, node.Index); BoundExpression expression; if (builder == null) { expression = VisitExpression(ref builder, node.Expression); } else { var expressionBuilder = new BoundSpillSequenceBuilder(builder.Syntax); expression = VisitExpression(ref expressionBuilder, node.Expression); expression = Spill(expressionBuilder, expression); expressionBuilder.Include(builder); builder = expressionBuilder; } return UpdateExpression(builder, node.Update(expression, index, node.Checked, node.RefersToLocation, node.Type)); } public override BoundNode VisitPointerIndirectionOperator(BoundPointerIndirectionOperator node) { BoundSpillSequenceBuilder builder = null; var operand = VisitExpression(ref builder, node.Operand); return UpdateExpression(builder, node.Update(operand, node.RefersToLocation, node.Type)); } public override BoundNode VisitSequence(BoundSequence node) { BoundSpillSequenceBuilder valueBuilder = null; var value = VisitExpression(ref valueBuilder, node.Value); BoundSpillSequenceBuilder builder = null; var sideEffects = VisitExpressionList(ref builder, node.SideEffects, forceSpill: valueBuilder != null, sideEffectsOnly: true); if (builder == null && valueBuilder == null) { return node.Update(node.Locals, sideEffects, value, node.Type); } if (builder == null) { builder = new BoundSpillSequenceBuilder(valueBuilder.Syntax); } PromoteAndAddLocals(builder, node.Locals); builder.AddExpressions(sideEffects); builder.Include(valueBuilder); return builder.Update(value); } public override BoundNode VisitThrowExpression(BoundThrowExpression node) { BoundSpillSequenceBuilder builder = null; BoundExpression operand = VisitExpression(ref builder, node.Expression); return UpdateExpression(builder, node.Update(operand, node.Type)); } /// <summary> /// If an expression node that declares synthesized short-lived locals (currently only sequence) contains /// a spill sequence (from an await or switch expression), these locals become long-lived since their /// values may be read by code that follows. We promote these variables to long-lived of kind /// <see cref="SynthesizedLocalKind.Spill"/>. /// </summary> private void PromoteAndAddLocals(BoundSpillSequenceBuilder builder, ImmutableArray<LocalSymbol> locals) { foreach (var local in locals) { if (local.SynthesizedKind.IsLongLived()) { builder.AddLocal(local); } else if (!_receiverSubstitution.ContainsKey(local)) { LocalSymbol longLived = local.WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind.Spill, _F.Syntax); _tempSubstitution.Add(local, longLived); builder.AddLocal(longLived); } } } public override BoundNode VisitUnaryOperator(BoundUnaryOperator node) { BoundSpillSequenceBuilder builder = null; BoundExpression operand = VisitExpression(ref builder, node.Operand); return UpdateExpression(builder, node.Update(node.OperatorKind, operand, node.ConstantValueOpt, node.MethodOpt, node.ConstrainedToTypeOpt, node.ResultKind, node.Type)); } public override BoundNode VisitReadOnlySpanFromArray(BoundReadOnlySpanFromArray node) { BoundSpillSequenceBuilder builder = null; BoundExpression operand = VisitExpression(ref builder, node.Operand); return UpdateExpression(builder, node.Update(operand, node.ConversionMethod, node.Type)); } public override BoundNode VisitSequencePointExpression(BoundSequencePointExpression node) { BoundSpillSequenceBuilder builder = null; BoundExpression expression = VisitExpression(ref builder, node.Expression); return UpdateExpression(builder, node.Update(expression, node.Type)); } #endregion } }