File: Lowering\SpillSequenceSpiller.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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: RefKind.Ref } receiverRefLocal },
                    Right: BoundComplexConditionalReceiver
                    {
                        ValueTypeReceiver: BoundLocal { LocalSymbol: { SynthesizedKind: SynthesizedLocalKind.LoweringTemp, RefKind: RefKind.Ref } } 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: RefKind.Ref } 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)
                && 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));
        }
 
        public override BoundNode VisitCatchBlock(BoundCatchBlock node)
        {
            BoundExpression exceptionSourceOpt = (BoundExpression)this.Visit(node.ExceptionSourceOpt);
            var locals = node.Locals;
 
            var exceptionFilterPrologueOpt = node.ExceptionFilterPrologueOpt;
            Debug.Assert(exceptionFilterPrologueOpt is null); // it is introduced by this pass
            BoundSpillSequenceBuilder builder = null;
            var exceptionFilterOpt = VisitExpression(ref builder, node.ExceptionFilterOpt);
            if (builder is { })
            {
                Debug.Assert(builder.Value is null);
                locals = locals.AddRange(builder.GetLocals());
                exceptionFilterPrologueOpt = new BoundStatementList(node.Syntax, builder.GetStatements());
            }
 
            BoundBlock body = (BoundBlock)this.Visit(node.Body);
            TypeSymbol exceptionTypeOpt = this.VisitType(node.ExceptionTypeOpt);
            return node.Update(locals, exceptionSourceOpt, exceptionTypeOpt, exceptionFilterPrologueOpt, exceptionFilterOpt, body, node.IsSynthesizedAsyncCatchAll);
        }
 
#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)
        {
            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.Method, 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
    }
}