File: CodeGen\EmitStatement.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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CSharp.Binder;
 
namespace Microsoft.CodeAnalysis.CSharp.CodeGen
{
    internal partial class CodeGenerator
    {
        private void EmitStatement(BoundStatement statement)
        {
            switch (statement.Kind)
            {
                case BoundKind.Block:
                    EmitBlock((BoundBlock)statement);
                    break;
 
                case BoundKind.Scope:
                    EmitScope((BoundScope)statement);
                    break;
 
                case BoundKind.SequencePoint:
                    this.EmitSequencePointStatement((BoundSequencePoint)statement);
                    break;
 
                case BoundKind.SequencePointWithSpan:
                    this.EmitSequencePointStatement((BoundSequencePointWithSpan)statement);
                    break;
 
                case BoundKind.SavePreviousSequencePoint:
                    this.EmitSavePreviousSequencePoint((BoundSavePreviousSequencePoint)statement);
                    break;
 
                case BoundKind.RestorePreviousSequencePoint:
                    this.EmitRestorePreviousSequencePoint((BoundRestorePreviousSequencePoint)statement);
                    break;
 
                case BoundKind.StepThroughSequencePoint:
                    this.EmitStepThroughSequencePoint((BoundStepThroughSequencePoint)statement);
                    break;
 
                case BoundKind.ExpressionStatement:
                    EmitExpression(((BoundExpressionStatement)statement).Expression, false);
                    break;
 
                case BoundKind.StatementList:
                    EmitStatementList((BoundStatementList)statement);
                    break;
 
                case BoundKind.ReturnStatement:
                    EmitReturnStatement((BoundReturnStatement)statement);
                    break;
 
                case BoundKind.GotoStatement:
                    EmitGotoStatement((BoundGotoStatement)statement);
                    break;
 
                case BoundKind.LabelStatement:
                    EmitLabelStatement((BoundLabelStatement)statement);
                    break;
 
                case BoundKind.ConditionalGoto:
                    EmitConditionalGoto((BoundConditionalGoto)statement);
                    break;
 
                case BoundKind.ThrowStatement:
                    EmitThrowStatement((BoundThrowStatement)statement);
                    break;
 
                case BoundKind.TryStatement:
                    EmitTryStatement((BoundTryStatement)statement);
                    break;
 
                case BoundKind.SwitchDispatch:
                    EmitSwitchDispatch((BoundSwitchDispatch)statement);
                    break;
 
                case BoundKind.StateMachineScope:
                    EmitStateMachineScope((BoundStateMachineScope)statement);
                    break;
 
                case BoundKind.NoOpStatement:
                    EmitNoOpStatement((BoundNoOpStatement)statement);
                    break;
 
                default:
                    // Code gen should not be invoked if there are errors.
                    throw ExceptionUtilities.UnexpectedValue(statement.Kind);
            }
 
#if DEBUG
            if (_stackLocals == null || _stackLocals.Count == 0)
            {
                _builder.AssertStackEmpty();
            }
#endif
 
            ReleaseExpressionTemps();
        }
 
        private int EmitStatementAndCountInstructions(BoundStatement statement)
        {
            int n = _builder.InstructionsEmitted;
            this.EmitStatement(statement);
            return _builder.InstructionsEmitted - n;
        }
 
        private void EmitStatementList(BoundStatementList list)
        {
            for (int i = 0, n = list.Statements.Length; i < n; i++)
            {
                EmitStatement(list.Statements[i]);
            }
        }
 
        private void EmitNoOpStatement(BoundNoOpStatement statement)
        {
            switch (statement.Flavor)
            {
                case NoOpStatementFlavor.Default:
                    if (_ilEmitStyle == ILEmitStyle.Debug)
                    {
                        _builder.EmitOpCode(ILOpCode.Nop);
                    }
                    break;
 
                case NoOpStatementFlavor.AwaitYieldPoint:
                    Debug.Assert((_asyncYieldPoints == null) == (_asyncResumePoints == null));
                    if (_asyncYieldPoints == null)
                    {
                        _asyncYieldPoints = ArrayBuilder<int>.GetInstance();
                        _asyncResumePoints = ArrayBuilder<int>.GetInstance();
                    }
                    Debug.Assert(_asyncYieldPoints.Count == _asyncResumePoints.Count);
                    _asyncYieldPoints.Add(_builder.AllocateILMarker());
                    break;
 
                case NoOpStatementFlavor.AwaitResumePoint:
                    Debug.Assert(_asyncYieldPoints != null);
                    Debug.Assert(_asyncYieldPoints != null);
                    _asyncResumePoints.Add(_builder.AllocateILMarker());
                    Debug.Assert(_asyncYieldPoints.Count == _asyncResumePoints.Count);
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(statement.Flavor);
            }
        }
 
        private void EmitThrowStatement(BoundThrowStatement node)
        {
            EmitThrow(node.ExpressionOpt);
        }
 
        private void EmitThrow(BoundExpression thrown)
        {
            if (thrown != null)
            {
                this.EmitExpression(thrown, true);
 
                var exprType = thrown.Type;
                // Expression type will be null for "throw null;".
                if (exprType?.TypeKind == TypeKind.TypeParameter)
                {
                    this.EmitBox(exprType, thrown.Syntax);
                }
            }
 
            _builder.EmitThrow(isRethrow: thrown == null);
        }
 
        private void EmitConditionalGoto(BoundConditionalGoto boundConditionalGoto)
        {
            object label = boundConditionalGoto.Label;
            Debug.Assert(label != null);
            EmitCondBranch(boundConditionalGoto.Condition, ref label, boundConditionalGoto.JumpIfTrue);
        }
 
        // 3.17 The brfalse instruction transfers control to target if value (of type int32, int64, object reference, managed
        //pointer, unmanaged pointer or native int) is zero (false). If value is non-zero (true), execution continues at
        //the next instruction.
 
        private static bool CanPassToBrfalse(TypeSymbol ts)
        {
            if (ts.IsEnumType())
            {
                // valid enums are all primitives
                return true;
            }
 
            var tc = ts.PrimitiveTypeCode;
            switch (tc)
            {
                case Microsoft.Cci.PrimitiveTypeCode.Float32:
                case Microsoft.Cci.PrimitiveTypeCode.Float64:
                    return false;
 
                case Microsoft.Cci.PrimitiveTypeCode.NotPrimitive:
                    // if this is a generic type param, verifier will want us to box
                    // EmitCondBranch knows that
                    return ts.IsReferenceType;
 
                default:
                    Debug.Assert(tc != Microsoft.Cci.PrimitiveTypeCode.Invalid);
                    Debug.Assert(tc != Microsoft.Cci.PrimitiveTypeCode.Void);
 
                    return true;
            }
        }
 
        private static BoundExpression TryReduce(BoundBinaryOperator condition, ref bool sense)
        {
            var opKind = condition.OperatorKind.Operator();
 
            Debug.Assert(opKind == BinaryOperatorKind.Equal ||
                        opKind == BinaryOperatorKind.NotEqual);
 
            BoundExpression nonConstOp;
            BoundExpression constOp = (condition.Left.ConstantValueOpt != null) ? condition.Left : null;
 
            if (constOp != null)
            {
                nonConstOp = condition.Right;
            }
            else
            {
                constOp = (condition.Right.ConstantValueOpt != null) ? condition.Right : null;
                if (constOp == null)
                {
                    return null;
                }
                nonConstOp = condition.Left;
            }
 
            var nonConstType = nonConstOp.Type;
            if (!CanPassToBrfalse(nonConstType))
            {
                return null;
            }
 
            bool isBool = nonConstType.PrimitiveTypeCode == Microsoft.Cci.PrimitiveTypeCode.Boolean;
            bool isZero = constOp.ConstantValueOpt.IsDefaultValue;
 
            // bool is special, only it can be compared to true and false...
            if (!isBool && !isZero)
            {
                return null;
            }
 
            // if comparing to zero, flip the sense
            if (isZero)
            {
                sense = !sense;
            }
 
            // if comparing != flip the sense
            if (opKind == BinaryOperatorKind.NotEqual)
            {
                sense = !sense;
            }
 
            return nonConstOp;
        }
 
        private const int IL_OP_CODE_ROW_LENGTH = 4;
 
        private static readonly ILOpCode[] s_condJumpOpCodes = new ILOpCode[]
        {
            //  <            <=               >                >=
            ILOpCode.Blt,    ILOpCode.Ble,    ILOpCode.Bgt,    ILOpCode.Bge,     // Signed
            ILOpCode.Bge,    ILOpCode.Bgt,    ILOpCode.Ble,    ILOpCode.Blt,     // Signed Invert
            ILOpCode.Blt_un, ILOpCode.Ble_un, ILOpCode.Bgt_un, ILOpCode.Bge_un,  // Unsigned
            ILOpCode.Bge_un, ILOpCode.Bgt_un, ILOpCode.Ble_un, ILOpCode.Blt_un,  // Unsigned Invert
            ILOpCode.Blt,    ILOpCode.Ble,    ILOpCode.Bgt,    ILOpCode.Bge,     // Float
            ILOpCode.Bge_un, ILOpCode.Bgt_un, ILOpCode.Ble_un, ILOpCode.Blt_un,  // Float Invert
        };
 
        /// <summary>
        /// Produces opcode for a jump that corresponds to given operation and sense.
        /// Also produces a reverse opcode - opcode for the same condition with inverted sense.
        /// </summary>
        private static ILOpCode CodeForJump(BoundBinaryOperator op, bool sense, out ILOpCode revOpCode)
        {
            int opIdx;
 
            switch (op.OperatorKind.Operator())
            {
                case BinaryOperatorKind.Equal:
                    revOpCode = !sense ? ILOpCode.Beq : ILOpCode.Bne_un;
                    return sense ? ILOpCode.Beq : ILOpCode.Bne_un;
 
                case BinaryOperatorKind.NotEqual:
                    revOpCode = !sense ? ILOpCode.Bne_un : ILOpCode.Beq;
                    return sense ? ILOpCode.Bne_un : ILOpCode.Beq;
 
                case BinaryOperatorKind.LessThan:
                    opIdx = 0;
                    break;
 
                case BinaryOperatorKind.LessThanOrEqual:
                    opIdx = 1;
                    break;
 
                case BinaryOperatorKind.GreaterThan:
                    opIdx = 2;
                    break;
 
                case BinaryOperatorKind.GreaterThanOrEqual:
                    opIdx = 3;
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(op.OperatorKind.Operator());
            }
 
            if (IsUnsignedBinaryOperator(op))
            {
                opIdx += 2 * IL_OP_CODE_ROW_LENGTH; //unsigned
            }
            else if (IsFloat(op.OperatorKind))
            {
                opIdx += 4 * IL_OP_CODE_ROW_LENGTH;  //float
            }
 
            int revOpIdx = opIdx;
 
            if (!sense)
            {
                opIdx += IL_OP_CODE_ROW_LENGTH; //invert op
            }
            else
            {
                revOpIdx += IL_OP_CODE_ROW_LENGTH; //invert rev
            }
 
            revOpCode = s_condJumpOpCodes[revOpIdx];
            return s_condJumpOpCodes[opIdx];
        }
 
        // generate a jump to dest if (condition == sense) is true
        private void EmitCondBranch(BoundExpression condition, ref object dest, bool sense)
        {
            _recursionDepth++;
 
            if (_recursionDepth > 1)
            {
                StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
 
                EmitCondBranchCore(condition, ref dest, sense);
            }
            else
            {
                EmitCondBranchCoreWithStackGuard(condition, ref dest, sense);
            }
 
            _recursionDepth--;
        }
 
        private void EmitCondBranchCoreWithStackGuard(BoundExpression condition, ref object dest, bool sense)
        {
            Debug.Assert(_recursionDepth == 1);
 
            try
            {
                EmitCondBranchCore(condition, ref dest, sense);
                Debug.Assert(_recursionDepth == 1);
            }
            catch (InsufficientExecutionStackException)
            {
                _diagnostics.Add(ErrorCode.ERR_InsufficientStack,
                                 BoundTreeVisitor.CancelledByStackGuardException.GetTooLongOrComplexExpressionErrorLocation(condition));
                throw new EmitCancelledException();
            }
        }
 
        private void EmitCondBranchCore(BoundExpression condition, ref object dest, bool sense)
        {
oneMoreTime:
 
            ILOpCode ilcode;
 
            if (condition.ConstantValueOpt != null)
            {
                bool taken = condition.ConstantValueOpt.IsDefaultValue != sense;
 
                if (taken)
                {
                    dest = dest ?? new object();
                    _builder.EmitBranch(ILOpCode.Br, dest);
                }
                else
                {
                    // otherwise this branch will never be taken, so just fall through...
                }
 
                return;
            }
 
            switch (condition.Kind)
            {
                case BoundKind.BinaryOperator:
 
                    var binOp = (BoundBinaryOperator)condition;
                    Debug.Assert(binOp.ConstantValueOpt is null);
 
#nullable enable 
                    if (binOp.OperatorKind.OperatorWithLogical() is BinaryOperatorKind.LogicalOr or BinaryOperatorKind.LogicalAnd)
                    {
                        var stack = ArrayBuilder<(BoundExpression? condition, StrongBox<object?> destBox, bool sense)>.GetInstance();
                        var destBox = new StrongBox<object?>(dest);
                        stack.Push((binOp, destBox, sense));
 
                        do
                        {
                            (BoundExpression? condition, StrongBox<object?> destBox, bool sense) top = stack.Pop();
 
                            if (top.condition is null)
                            {
                                // This is a special entry to indicate that it is time to append the block
                                object? fallThrough = top.destBox.Value;
                                if (fallThrough != null)
                                {
                                    _builder.MarkLabel(fallThrough);
                                }
                            }
                            else if (top.condition.ConstantValueOpt is null &&
                                     top.condition is BoundBinaryOperator binary &&
                                     binary.OperatorKind.OperatorWithLogical() is BinaryOperatorKind.LogicalOr or BinaryOperatorKind.LogicalAnd)
                            {
                                if (binary.OperatorKind.OperatorWithLogical() is BinaryOperatorKind.LogicalOr ? !top.sense : top.sense)
                                {
                                    // gotoif(a != sense) fallThrough
                                    // gotoif(b == sense) dest
                                    // fallThrough:
 
                                    var fallThrough = new StrongBox<object?>();
 
                                    // Note, operations are pushed to the stack in opposite order
                                    stack.Push((null, fallThrough, true)); // This is a special entry to indicate that it is time to append the fallThrough block
                                    stack.Push((binary.Right, top.destBox, top.sense));
                                    stack.Push((binary.Left, fallThrough, !top.sense));
                                }
                                else
                                {
                                    // gotoif(a == sense) labDest
                                    // gotoif(b == sense) labDest
 
                                    // Note, operations are pushed to the stack in opposite order
                                    stack.Push((binary.Right, top.destBox, top.sense));
                                    stack.Push((binary.Left, top.destBox, top.sense));
                                }
                            }
                            else if (stack.Count == 0 && ReferenceEquals(destBox, top.destBox))
                            {
                                // Instead of recursion we can restart from the top with new condition
                                condition = top.condition;
                                sense = top.sense;
                                dest = destBox.Value;
                                stack.Free();
                                goto oneMoreTime;
                            }
                            else
                            {
                                EmitCondBranch(top.condition, ref top.destBox.Value, top.sense);
                            }
                        }
                        while (stack.Count != 0);
 
                        dest = destBox.Value;
                        stack.Free();
                        return;
                    }
#nullable disable
 
                    switch (binOp.OperatorKind.OperatorWithLogical())
                    {
                        case BinaryOperatorKind.LogicalOr:
                        case BinaryOperatorKind.LogicalAnd:
                            throw ExceptionUtilities.Unreachable();
 
                        case BinaryOperatorKind.Equal:
                        case BinaryOperatorKind.NotEqual:
                            var reduced = TryReduce(binOp, ref sense);
                            if (reduced != null)
                            {
                                condition = reduced;
                                goto oneMoreTime;
                            }
                            // Fall through
                            goto case BinaryOperatorKind.LessThan;
 
                        case BinaryOperatorKind.LessThan:
                        case BinaryOperatorKind.LessThanOrEqual:
                        case BinaryOperatorKind.GreaterThan:
                        case BinaryOperatorKind.GreaterThanOrEqual:
                            EmitExpression(binOp.Left, true);
                            EmitExpression(binOp.Right, true);
                            ILOpCode revOpCode;
                            ilcode = CodeForJump(binOp, sense, out revOpCode);
                            dest = dest ?? new object();
                            _builder.EmitBranch(ilcode, dest, revOpCode);
                            return;
                    }
 
                    // none of above.
                    // then it is regular binary expression - Or, And, Xor ...
                    goto default;
 
                case BoundKind.LoweredConditionalAccess:
                    {
                        var ca = (BoundLoweredConditionalAccess)condition;
                        var receiver = ca.Receiver;
                        var receiverType = receiver.Type;
 
                        // we need a copy if we deal with nonlocal value (to capture the value)
                        // or if we deal with stack local (reads are destructive)
                        var complexCase = !receiverType.IsReferenceType ||
                                          LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) ||
                                          (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)) ||
                                          (ca.WhenNullOpt?.IsDefaultValue() == false);
 
                        if (complexCase)
                        {
                            goto default;
                        }
 
                        if (sense)
                        {
                            // gotoif(receiver != null) fallThrough
                            // gotoif(receiver.Access) dest
                            // fallThrough:
 
                            object fallThrough = null;
 
                            EmitCondBranch(receiver, ref fallThrough, sense: false);
                            // receiver is a reference type, and we only intend to read it
                            EmitReceiverRef(receiver, AddressKind.ReadOnly);
                            EmitCondBranch(ca.WhenNotNull, ref dest, sense: true);
 
                            if (fallThrough != null)
                            {
                                _builder.MarkLabel(fallThrough);
                            }
                        }
                        else
                        {
                            // gotoif(receiver == null) labDest
                            // gotoif(!receiver.Access) labDest
                            EmitCondBranch(receiver, ref dest, sense: false);
                            // receiver is a reference type, and we only intend to read it
                            EmitReceiverRef(receiver, AddressKind.ReadOnly);
                            condition = ca.WhenNotNull;
                            goto oneMoreTime;
                        }
                    }
                    return;
 
                case BoundKind.UnaryOperator:
                    var unOp = (BoundUnaryOperator)condition;
                    if (unOp.OperatorKind == UnaryOperatorKind.BoolLogicalNegation)
                    {
                        sense = !sense;
                        condition = unOp.Operand;
                        goto oneMoreTime;
                    }
                    goto default;
 
                case BoundKind.IsOperator:
                    var isOp = (BoundIsOperator)condition;
                    var operand = isOp.Operand;
                    EmitExpression(operand, true);
                    Debug.Assert((object)operand.Type != null);
                    if (!operand.Type.IsVerifierReference())
                    {
                        // box the operand for isinst if it is not a verifier reference
                        EmitBox(operand.Type, operand.Syntax);
                    }
                    _builder.EmitOpCode(ILOpCode.Isinst);
                    EmitSymbolToken(isOp.TargetType.Type, isOp.TargetType.Syntax);
                    ilcode = sense ? ILOpCode.Brtrue : ILOpCode.Brfalse;
                    dest = dest ?? new object();
                    _builder.EmitBranch(ilcode, dest);
                    return;
 
                case BoundKind.Sequence:
                    var seq = (BoundSequence)condition;
                    EmitSequenceCondBranch(seq, ref dest, sense);
                    return;
 
                default:
                    EmitExpression(condition, true);
 
                    var conditionType = condition.Type;
                    if (conditionType.IsReferenceType && !conditionType.IsVerifierReference())
                    {
                        EmitBox(conditionType, condition.Syntax);
                    }
 
                    ilcode = sense ? ILOpCode.Brtrue : ILOpCode.Brfalse;
                    dest = dest ?? new object();
                    _builder.EmitBranch(ilcode, dest);
                    return;
            }
        }
 
        private void EmitSequenceCondBranch(BoundSequence sequence, ref object dest, bool sense)
        {
            DefineLocals(sequence);
            EmitSideEffects(sequence);
            EmitCondBranch(sequence.Value, ref dest, sense);
 
            // sequence is used as a value, can release all locals
            FreeLocals(sequence);
        }
 
        private void EmitLabelStatement(BoundLabelStatement boundLabelStatement)
        {
            _builder.MarkLabel(boundLabelStatement.Label);
        }
 
        private void EmitGotoStatement(BoundGotoStatement boundGotoStatement)
        {
            _builder.EmitBranch(ILOpCode.Br, boundGotoStatement.Label);
        }
 
        // used by HandleReturn method which tries to inject
        // indirect ret sequence as a last statement in the block
        // that is the last statement of the current method
        // NOTE: it is important that there is no code after this "ret"
        //       it is desirable, for debug purposes, that this ret is emitted inside top level { }
        private bool IsLastBlockInMethod(BoundBlock block)
        {
            if (_boundBody == block)
            {
                return true;
            }
 
            //sometimes top level node is a statement list containing
            //epilogue and then a block. If we are having that block, it will do.
            var list = _boundBody as BoundStatementList;
            if (list != null && list.Statements.LastOrDefault() == block)
            {
                return true;
            }
 
            return false;
        }
 
        private void EmitBlock(BoundBlock block)
        {
            if (block.Instrumentation is not null)
            {
                EmitInstrumentedBlock(block.Instrumentation, block);
            }
            else
            {
                EmitUninstrumentedBlock(block);
            }
        }
 
        private void EmitInstrumentedBlock(BoundBlockInstrumentation instrumentation, BoundBlock block)
        {
            if (!instrumentation.Locals.IsEmpty)
            {
                _builder.OpenLocalScope();
 
                foreach (var local in instrumentation.Locals)
                {
                    DefineLocal(local, block.Syntax);
                }
            }
 
            if (instrumentation.Prologue != null)
            {
                if (_emitPdbSequencePoints)
                {
                    EmitHiddenSequencePoint();
                }
 
                EmitStatement(instrumentation.Prologue);
            }
 
            _builder.AssertStackEmpty();
 
            if (instrumentation.Epilogue != null)
            {
                // Check if we're emitting instrumentation try/finally in a catch filter, which produces invalid IL
                if (_inCatchFilterLevel > 0)
                {
                    // Try/finallys are not allowed in catch filters by spec, an error should be reported for this in initial binding.
                    Debug.Fail("Exception handling constructs should be blocked at the binding layer, not here at emit time");
 
                    // Report an error as this would produce invalid IL
                    _diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, block.Syntax.Location, ((Cci.INamedEntity)_module).Name, "Exception handling is not allowed in exception filters");
                }
 
                _builder.OpenLocalScope(ScopeType.TryCatchFinally);
 
                _builder.OpenLocalScope(ScopeType.Try);
 
                EmitUninstrumentedBlock(block);
                _builder.CloseLocalScope(); // try
 
                _builder.OpenLocalScope(ScopeType.Finally);
 
                if (_emitPdbSequencePoints)
                {
                    EmitHiddenSequencePoint();
                }
 
                EmitStatement(instrumentation.Epilogue);
                _builder.CloseLocalScope(); // finally
 
                _builder.CloseLocalScope(); // try-finally
            }
            else
            {
                EmitUninstrumentedBlock(block);
            }
 
            if (!instrumentation.Locals.IsEmpty)
            {
                foreach (var local in instrumentation.Locals)
                {
                    FreeLocal(local);
                }
 
                _builder.CloseLocalScope();
            }
        }
 
        private void EmitUninstrumentedBlock(BoundBlock block)
        {
            var hasLocals = !block.Locals.IsEmpty;
 
            if (hasLocals)
            {
                _builder.OpenLocalScope();
 
                foreach (var local in block.Locals)
                {
                    Debug.Assert(local.RefKind == RefKind.None || local.SynthesizedKind.IsLongLived(),
                        "A ref local ended up in a block and claims it is shortlived. That is dangerous. Are we sure it is short lived?");
 
                    var declaringReferences = local.DeclaringSyntaxReferences;
                    DefineLocal(local, !declaringReferences.IsEmpty ? (CSharpSyntaxNode)declaringReferences[0].GetSyntax() : block.Syntax);
                }
            }
 
            EmitStatements(block.Statements);
 
            if (_indirectReturnState == IndirectReturnState.Needed &&
                IsLastBlockInMethod(block))
            {
                if (block.Instrumentation != null)
                {
                    // jump out of try-finally
                    _builder.EmitBranch(ILOpCode.Br, s_returnLabel);
                }
                else
                {
                    HandleReturn();
                }
            }
 
            if (hasLocals)
            {
                foreach (var local in block.Locals)
                {
                    FreeLocal(local);
                }
 
                _builder.CloseLocalScope();
            }
        }
 
        private void EmitStatements(ImmutableArray<BoundStatement> statements)
        {
            foreach (var statement in statements)
            {
                EmitStatement(statement);
            }
        }
 
        private void EmitScope(BoundScope block)
        {
            Debug.Assert(!block.Locals.IsEmpty);
 
            _builder.OpenLocalScope();
 
            foreach (var local in block.Locals)
            {
                Debug.Assert(local.Name != null);
                Debug.Assert(local.SynthesizedKind == SynthesizedLocalKind.UserDefined &&
                    (local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchSection || local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchExpressionArm));
                if (!local.IsConst && !IsStackLocal(local))
                {
                    _builder.AddLocalToScope(_builder.LocalSlotManager.GetLocal(local));
                }
            }
 
            EmitStatements(block.Statements);
 
            _builder.CloseLocalScope();
        }
 
        private void EmitStateMachineScope(BoundStateMachineScope scope)
        {
            _builder.OpenLocalScope(ScopeType.StateMachineVariable);
            foreach (var field in scope.Fields)
            {
                if (field.SlotIndex >= 0)
                {
                    _builder.DefineUserDefinedStateMachineHoistedLocal(field.SlotIndex);
                }
            }
 
            EmitStatement(scope.Statement);
            _builder.CloseLocalScope();
        }
 
        // There are two ways a value can be returned from a function:
        // - Using ret opcode
        // - Store return value if any to a predefined temp and jump to the epilogue block
        // Sometimes ret is not an option (try/catch etc.). We also do this when emitting
        // debuggable code. This function is a stub for the logic that decides that.
        private bool ShouldUseIndirectReturn()
        {
            // If the method/lambda body is a block we define a sequence point for the closing brace of the body
            // and associate it with the ret instruction. If there is a return statement we need to store the value
            // to a long-lived synthesized local since a sequence point requires an empty evaluation stack.
            //
            // The emitted pattern is:
            //   <evaluate return statement expression>
            //   stloc $ReturnValue
            //   ldloc  $ReturnValue // sequence point
            //   ret
            //
            // Do not emit this pattern if the method doesn't include user code or doesn't have a block body.
            return _ilEmitStyle == ILEmitStyle.Debug && _method.GenerateDebugInfo && _methodBodySyntaxOpt?.IsKind(SyntaxKind.Block) == true ||
                   _builder.InExceptionHandler;
        }
 
        // Compiler generated return mapped to a block is very likely the synthetic return
        // that was added at the end of the last block of a void method by analysis.
        // This is likely to be the last return in the method, so if we have not yet
        // emitted return sequence, it is convenient to do it right here (if we can).
        private bool CanHandleReturnLabel(BoundReturnStatement boundReturnStatement)
        {
            return boundReturnStatement.WasCompilerGenerated &&
                    (boundReturnStatement.Syntax.IsKind(SyntaxKind.Block) || _method?.IsImplicitConstructor == true) &&
                    !_builder.InExceptionHandler;
        }
 
        private void EmitReturnStatement(BoundReturnStatement boundReturnStatement)
        {
            var expressionOpt = boundReturnStatement.ExpressionOpt;
            if (boundReturnStatement.RefKind == RefKind.None)
            {
                this.EmitExpression(expressionOpt, true);
            }
            else
            {
                // NOTE: passing "ReadOnlyStrict" here.
                //       we should never return an address of a copy
                var unexpectedTemp = this.EmitAddress(expressionOpt, this._method.RefKind == RefKind.RefReadOnly ? AddressKind.ReadOnlyStrict : AddressKind.Writeable);
                Debug.Assert(unexpectedTemp == null, "ref-returning a temp?");
            }
 
            if (ShouldUseIndirectReturn())
            {
                if (expressionOpt != null)
                {
                    _builder.EmitLocalStore(LazyReturnTemp);
                }
 
                if (_indirectReturnState != IndirectReturnState.Emitted && CanHandleReturnLabel(boundReturnStatement))
                {
                    HandleReturn();
                }
                else
                {
                    _builder.EmitBranch(ILOpCode.Br, s_returnLabel);
 
                    if (_indirectReturnState == IndirectReturnState.NotNeeded)
                    {
                        _indirectReturnState = IndirectReturnState.Needed;
                    }
                }
            }
            else
            {
                if (_indirectReturnState == IndirectReturnState.Needed && CanHandleReturnLabel(boundReturnStatement))
                {
                    if (expressionOpt != null)
                    {
                        _builder.EmitLocalStore(LazyReturnTemp);
                    }
 
                    HandleReturn();
                }
                else
                {
                    if (expressionOpt != null)
                    {
                        // Ensure the return type has been translated. (Necessary
                        // for cases of untranslated anonymous types.)
                        _module.Translate(expressionOpt.Type, boundReturnStatement.Syntax, _diagnostics.DiagnosticBag);
                    }
                    _builder.EmitRet(expressionOpt == null);
                }
            }
        }
 
        private void EmitTryStatement(BoundTryStatement statement, bool emitCatchesOnly = false)
        {
            if (_inCatchFilterLevel > 0)
            {
                // Try/finallys are not allowed in catch filters by spec, an error should be reported for this in initial binding.
                Debug.Fail("Exception handling constructs should be blocked at the binding layer, not here at emit time");
 
                // Report an error as this would produce invalid IL
                _diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, statement.Syntax.Location, ((Cci.INamedEntity)_module).Name, "Exception handling is not allowed in exception filters");
            }
 
            Debug.Assert(!statement.CatchBlocks.IsDefault);
 
            // Stack must be empty at beginning of try block.
            _builder.AssertStackEmpty();
 
            // IL requires catches and finally block to be distinct try
            // blocks so if the source contained both a catch and
            // a finally, nested scopes are emitted.
            bool emitNestedScopes = (!emitCatchesOnly &&
                (statement.CatchBlocks.Length > 0) &&
                (statement.FinallyBlockOpt != null));
 
            _builder.OpenLocalScope(ScopeType.TryCatchFinally);
 
            _builder.OpenLocalScope(ScopeType.Try);
            // IL requires catches and finally block to be distinct try
            // blocks so if the source contained both a catch and
            // a finally, nested scopes are emitted.
 
            _tryNestingLevel++;
            if (emitNestedScopes)
            {
                EmitTryStatement(statement, emitCatchesOnly: true);
            }
            else
            {
                EmitBlock(statement.TryBlock);
            }
 
            _tryNestingLevel--;
            // Close the Try scope
            _builder.CloseLocalScope();
 
            if (!emitNestedScopes)
            {
                foreach (var catchBlock in statement.CatchBlocks)
                {
                    EmitCatchBlock(catchBlock);
                }
            }
 
            if (!emitCatchesOnly && (statement.FinallyBlockOpt != null))
            {
                _builder.OpenLocalScope(statement.PreferFaultHandler ? ScopeType.Fault : ScopeType.Finally);
                EmitBlock(statement.FinallyBlockOpt);
 
                // close Finally scope
                _builder.CloseLocalScope();
 
                // close the whole try statement scope
                _builder.CloseLocalScope();
 
                // in a case where we emit surrogate Finally using Fault, we emit code like this
                //
                // try{
                //      . . .
                // } fault {
                //      finallyBlock;
                // }
                // finallyBlock;
                //
                // This is where the second copy of finallyBlock is emitted.
                if (statement.PreferFaultHandler)
                {
                    var finallyClone = FinallyCloner.MakeFinallyClone(statement);
                    EmitBlock(finallyClone);
                }
            }
            else
            {
                // close the whole try statement scope
                _builder.CloseLocalScope();
            }
        }
 
        /// <remarks>
        /// The interesting part in the following method is the support for exception filters.
        /// === Example:
        ///
        /// try
        /// {
        ///    TryBlock
        /// }
        /// catch (ExceptionType ex) when (Condition)
        /// {
        ///    Handler
        /// }
        ///
        /// gets emitted as something like ===>
        ///
        /// Try
        ///     TryBlock
        /// Filter
        ///     var tmp = Pop() as {ExceptionType}
        ///     if (tmp == null)
        ///     {
        ///         Push 0
        ///     }
        ///     else
        ///     {
        ///         ex = tmp
        ///         Push Condition ? 1 : 0
        ///     }
        /// End Filter // leaves 1 or 0 on the stack
        /// Catch      // gets called after finalization of nested exception frames if condition above produced 1
        ///     Pop    // CLR pushes the exception object again
        ///     variable ex can be used here
        ///     Handler
        /// EndCatch
        ///
        /// When evaluating `Condition` requires additional statements be executed first, those
        /// statements are stored in `catchBlock.ExceptionFilterPrologueOpt` and emitted before the condition.
        /// </remarks>
        private void EmitCatchBlock(BoundCatchBlock catchBlock)
        {
            object typeCheckFailedLabel = null;
#if DEBUG
            int currentCatchFilterLevel = _inCatchFilterLevel;
#endif
 
            _builder.AdjustStack(1); // Account for exception on the stack.
 
            // Open appropriate exception handler scope. (Catch or Filter)
            // if it is a Filter, emit prologue that checks if the type on the stack
            // converts to what we want.
            if (catchBlock.ExceptionFilterOpt == null)
            {
                var exceptionType = ((object)catchBlock.ExceptionTypeOpt != null) ?
                    _module.Translate(catchBlock.ExceptionTypeOpt, catchBlock.Syntax, _diagnostics.DiagnosticBag) :
                    _module.GetSpecialType(SpecialType.System_Object, catchBlock.Syntax, _diagnostics.DiagnosticBag);
 
                _builder.OpenLocalScope(ScopeType.Catch, exceptionType);
 
                RecordAsyncCatchHandlerOffset(catchBlock);
 
                // Dev12 inserts the sequence point on catch clause without a filter, just before
                // the exception object is assigned to the variable.
                //
                // Also in Dev12 the exception variable scope span starts right after the stloc instruction and
                // ends right before leave instruction. So when stopped at the sequence point Dev12 inserts,
                // the exception variable is not visible.
                if (_emitPdbSequencePoints)
                {
                    var syntax = catchBlock.Syntax as CatchClauseSyntax;
                    if (syntax != null)
                    {
                        TextSpan spSpan;
                        var declaration = syntax.Declaration;
 
                        if (declaration == null)
                        {
                            spSpan = syntax.CatchKeyword.Span;
                        }
                        else
                        {
                            spSpan = TextSpan.FromBounds(syntax.SpanStart, syntax.Declaration.Span.End);
                        }
 
                        this.EmitSequencePoint(catchBlock.SyntaxTree, spSpan);
                    }
                }
            }
            else
            {
                _builder.OpenLocalScope(ScopeType.Filter);
                _inCatchFilterLevel++;
 
                RecordAsyncCatchHandlerOffset(catchBlock);
 
                // Filtering starts with simulating regular catch through a
                // type check. If this is not our type then we are done.
                var typeCheckPassedLabel = new object();
                typeCheckFailedLabel = new object();
 
                if ((object)catchBlock.ExceptionTypeOpt != null)
                {
                    var exceptionType = _module.Translate(catchBlock.ExceptionTypeOpt, catchBlock.Syntax, _diagnostics.DiagnosticBag);
 
                    _builder.EmitOpCode(ILOpCode.Isinst);
                    _builder.EmitToken(exceptionType, catchBlock.Syntax);
                    _builder.EmitOpCode(ILOpCode.Dup);
                    _builder.EmitBranch(ILOpCode.Brtrue, typeCheckPassedLabel);
                    _builder.EmitOpCode(ILOpCode.Pop);
                    _builder.EmitIntConstant(0);
                    _builder.EmitBranch(ILOpCode.Br, typeCheckFailedLabel);
                }
                else
                {
                    // no formal exception type means we always pass the check
                }
 
                _builder.MarkLabel(typeCheckPassedLabel);
            }
 
            foreach (var local in catchBlock.Locals)
            {
                var declaringReferences = local.DeclaringSyntaxReferences;
                var localSyntax = !declaringReferences.IsEmpty ? (CSharpSyntaxNode)declaringReferences[0].GetSyntax() : catchBlock.Syntax;
                DefineLocal(local, localSyntax);
            }
 
            var exceptionSourceOpt = catchBlock.ExceptionSourceOpt;
            if (exceptionSourceOpt != null)
            {
                // here we have our exception on the stack in a form of a reference type (O)
                // it means that we have to "unbox" it before storing to the local
                // if exception's type is a generic type parameter.
                if (!exceptionSourceOpt.Type.IsVerifierReference())
                {
                    Debug.Assert(exceptionSourceOpt.Type.IsTypeParameter()); // only expecting type parameters
                    _builder.EmitOpCode(ILOpCode.Unbox_any);
                    EmitSymbolToken(exceptionSourceOpt.Type, exceptionSourceOpt.Syntax);
                }
 
                BoundExpression exceptionSource = exceptionSourceOpt;
                while (exceptionSource.Kind == BoundKind.Sequence)
                {
                    var seq = (BoundSequence)exceptionSource;
                    Debug.Assert(seq.Locals.IsDefaultOrEmpty);
                    EmitSideEffects(seq);
                    exceptionSource = seq.Value;
                }
 
                switch (exceptionSource.Kind)
                {
                    case BoundKind.Local:
                        var exceptionSourceLocal = (BoundLocal)exceptionSource;
                        Debug.Assert(exceptionSourceLocal.LocalSymbol.RefKind == RefKind.None);
                        if (!IsStackLocal(exceptionSourceLocal.LocalSymbol))
                        {
                            _builder.EmitLocalStore(GetLocal(exceptionSourceLocal));
                        }
 
                        break;
 
                    case BoundKind.FieldAccess:
                        var left = (BoundFieldAccess)exceptionSource;
                        Debug.Assert(!left.FieldSymbol.IsStatic, "Not supported");
                        Debug.Assert(!left.ReceiverOpt.Type.IsTypeParameter());
                        Debug.Assert(left.FieldSymbol.RefKind == RefKind.None);
 
                        var stateMachineField = left.FieldSymbol as StateMachineFieldSymbol;
                        if (((object)stateMachineField != null) && (stateMachineField.SlotIndex >= 0))
                        {
                            _builder.DefineUserDefinedStateMachineHoistedLocal(stateMachineField.SlotIndex);
                        }
 
                        // When assigning to a field
                        // we need to push param address below the exception
                        var temp = AllocateTemp(exceptionSource.Type, exceptionSource.Syntax);
                        _builder.EmitLocalStore(temp);
 
                        var receiverTemp = EmitReceiverRef(left.ReceiverOpt, AddressKind.Writeable);
                        Debug.Assert(receiverTemp == null);
 
                        _builder.EmitLocalLoad(temp);
                        FreeTemp(temp);
 
                        EmitFieldStore(left, refAssign: false);
                        break;
 
                    default:
                        throw ExceptionUtilities.UnexpectedValue(exceptionSource.Kind);
                }
            }
            else
            {
                _builder.EmitOpCode(ILOpCode.Pop);
            }
 
            if (catchBlock.ExceptionFilterPrologueOpt != null)
            {
                Debug.Assert(_builder.IsStackEmpty);
                EmitStatements(catchBlock.ExceptionFilterPrologueOpt.Statements);
            }
 
            // Emit the actual filter expression, if we have one, and normalize
            // results.
            if (catchBlock.ExceptionFilterOpt != null)
            {
                EmitCondExpr(catchBlock.ExceptionFilterOpt, true);
                // Normalize the return value because values other than 0 or 1
                // produce unspecified results.
                _builder.EmitIntConstant(0);
                _builder.EmitOpCode(ILOpCode.Cgt_un);
                _builder.MarkLabel(typeCheckFailedLabel);
 
                // Now we are starting the actual handler
                _builder.MarkFilterConditionEnd();
                _inCatchFilterLevel--;
 
                // Pop the exception; it should have already been stored to the
                // variable by the filter.
                _builder.EmitOpCode(ILOpCode.Pop);
            }
 
#if DEBUG
            Debug.Assert(currentCatchFilterLevel == _inCatchFilterLevel);
#endif
            EmitBlock(catchBlock.Body);
 
            _builder.CloseLocalScope();
        }
 
        private void RecordAsyncCatchHandlerOffset(BoundCatchBlock catchBlock)
        {
            if (catchBlock.IsSynthesizedAsyncCatchAll)
            {
                Debug.Assert(_asyncCatchHandlerOffset < 0); // only one expected
                _asyncCatchHandlerOffset = _builder.AllocateILMarker();
            }
        }
 
        private void EmitSwitchDispatch(BoundSwitchDispatch dispatch)
        {
            // Switch expression must have a valid switch governing type
            Debug.Assert((object)dispatch.Expression.Type != null);
            Debug.Assert(dispatch.Expression.Type.IsValidV6SwitchGoverningType() || dispatch.Expression.Type.IsSpanOrReadOnlySpanChar());
 
            // We must have rewritten nullable switch expression into non-nullable constructs.
            Debug.Assert(!dispatch.Expression.Type.IsNullableType());
 
            // This must be used only for nontrivial dispatches.
            Debug.Assert(dispatch.Cases.Any());
 
            EmitSwitchHeader(
                dispatch.Expression,
                dispatch.Cases.Select(p => new KeyValuePair<ConstantValue, object>(p.value, p.label)).ToArray(),
                dispatch.DefaultLabel,
                dispatch.LengthBasedStringSwitchDataOpt);
        }
 
        private void EmitSwitchHeader(
            BoundExpression expression,
            KeyValuePair<ConstantValue, object>[] switchCaseLabels,
            LabelSymbol fallThroughLabel,
            LengthBasedStringSwitchData lengthBasedSwitchStringJumpTableOpt)
        {
            Debug.Assert(expression.ConstantValueOpt == null);
            Debug.Assert((object)expression.Type != null &&
                (expression.Type.IsValidV6SwitchGoverningType() || expression.Type.IsSpanOrReadOnlySpanChar()));
            Debug.Assert(switchCaseLabels.Length > 0);
 
            Debug.Assert(switchCaseLabels != null || lengthBasedSwitchStringJumpTableOpt != null);
            LocalDefinition temp = null;
            LocalOrParameter key;
            BoundSequence sequence = null;
 
            if (expression.Kind == BoundKind.Sequence)
            {
                sequence = (BoundSequence)expression;
                DefineLocals(sequence);
                EmitSideEffects(sequence);
                expression = sequence.Value;
            }
 
            if (expression.Kind == BoundKind.SequencePointExpression)
            {
                var sequencePointExpression = (BoundSequencePointExpression)expression;
                EmitSequencePoint(sequencePointExpression);
                expression = sequencePointExpression.Expression;
            }
 
            switch (expression.Kind)
            {
                case BoundKind.Local:
                    var local = ((BoundLocal)expression).LocalSymbol;
                    if (local.RefKind == RefKind.None && !IsStackLocal(local))
                    {
                        key = this.GetLocal(local);
                        break;
                    }
                    goto default;
 
                case BoundKind.Parameter:
                    var parameter = (BoundParameter)expression;
                    if (parameter.ParameterSymbol.RefKind == RefKind.None)
                    {
                        key = ParameterSlot(parameter);
                        break;
                    }
                    goto default;
 
                default:
                    EmitExpression(expression, true);
                    temp = AllocateTemp(expression.Type, expression.Syntax);
                    _builder.EmitLocalStore(temp);
                    key = temp;
                    break;
            }
 
            Debug.Assert(lengthBasedSwitchStringJumpTableOpt is null ||
                expression.Type.SpecialType == SpecialType.System_String || expression.Type.IsSpanOrReadOnlySpanChar());
 
            // Emit switch jump table
            if (expression.Type.SpecialType == SpecialType.System_String || expression.Type.IsSpanOrReadOnlySpanChar())
            {
                if (lengthBasedSwitchStringJumpTableOpt is null)
                {
                    this.EmitStringSwitchJumpTable(switchCaseLabels, fallThroughLabel, key, expression.Syntax, expression.Type);
                }
                else
                {
                    this.EmitLengthBasedStringSwitchJumpTable(lengthBasedSwitchStringJumpTableOpt, fallThroughLabel, key, expression.Syntax, expression.Type);
                }
            }
            else
            {
                _builder.EmitIntegerSwitchJumpTable(switchCaseLabels, fallThroughLabel, key, expression.Type.EnumUnderlyingTypeOrSelf().PrimitiveTypeCode, expression.Syntax);
            }
 
            if (temp != null)
            {
                FreeTemp(temp);
            }
 
            if (sequence != null)
            {
                // sequence was used as a value, can release all its locals.
                FreeLocals(sequence);
            }
        }
 
#nullable enable
        private void EmitLengthBasedStringSwitchJumpTable(
            LengthBasedStringSwitchData lengthBasedSwitchData,
            LabelSymbol fallThroughLabel,
            LocalOrParameter keyTemp,
            SyntaxNode syntaxNode,
            TypeSymbol keyType)
        {
            // For the LengthJumpTable, emit:
            //   if (keyTemp is null)
            //     goto nullCaseLabel; OR goto fallThroughLabel;
            //
            //   var lengthTmp = keyTemp.Length;
            //   switch dispatch on lengthTemp using fallThroughLabel and cases:
            //     lengthConstant -> corresponding label (may be the label to a CharJumpTable, or to a StringJumpTable in 1-length scenario, or in 0-length scenario, a final case label)
            //
            //   var charTemp;
            //
            // For each CharJumpTable, emit:
            //   label for CharJumpTable:
            //   charTemp = keyTemp[selectedCharPosition];
            //   switch dispatch on charTemp using fallThroughLabel and cases:
            //     charConstant -> corresponding label (may be the label for a StringJumpTable or, in 1-length scenario, a final case label)
            //
            // For each StringJumpTable label, emit:
            //   label for StringJumpTable:
            //   switch dispatch on keyTemp using fallThroughLabel and cases:
            //     stringConstant -> corresponding label
 
            bool isSpan = keyType.IsSpanChar();
            bool isReadOnlySpan = keyType.IsReadOnlySpanChar();
            bool isSpanOrReadOnlySpan = isSpan || isReadOnlySpan;
            var indexerRef = GetIndexerRef(syntaxNode, keyType, isReadOnlySpan, isSpanOrReadOnlySpan);
            var lengthMethodRef = GetLengthMethodRef(syntaxNode, keyType, isReadOnlySpan, isSpanOrReadOnlySpan);
            Debug.Assert(indexerRef is not null);
            Debug.Assert(lengthMethodRef is not null);
 
            emitLengthDispatch(lengthBasedSwitchData, keyTemp, fallThroughLabel, syntaxNode);
            emitCharDispatches(lengthBasedSwitchData, keyTemp, fallThroughLabel, syntaxNode);
            emitFinalDispatches(lengthBasedSwitchData, keyTemp, keyType, fallThroughLabel, syntaxNode);
 
            return;
 
            void emitLengthDispatch(LengthBasedStringSwitchData lengthBasedSwitchInfo, LocalOrParameter keyTemp, LabelSymbol fallThroughLabel, SyntaxNode syntaxNode)
            {
                if (!isSpanOrReadOnlySpan)
                {
                    // if (keyTemp is null)
                    //   goto nullCaseLabel; OR goto fallThroughLabel;
                    _builder.EmitLoad(keyTemp);
                    _builder.EmitBranch(ILOpCode.Brfalse, lengthBasedSwitchInfo.LengthBasedJumpTable.NullCaseLabel ?? fallThroughLabel, ILOpCode.Brtrue);
                }
 
                // var stringLength = keyTemp.Length;
                var int32Type = Binder.GetSpecialType(_module.Compilation, SpecialType.System_Int32, syntaxNode, _diagnostics);
                var stringLength = AllocateTemp(int32Type, syntaxNode);
                if (isSpanOrReadOnlySpan)
                {
                    _builder.EmitLoadAddress(keyTemp);
                }
                else
                {
                    _builder.EmitLoad(keyTemp);
                }
                _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
                emitMethodRef(lengthMethodRef);
                _builder.EmitLocalStore(stringLength);
 
                // switch dispatch on lengthTemp using fallThroughLabel and cases:
                //   lengthConstant -> corresponding label
                _builder.EmitIntegerSwitchJumpTable(
                    lengthBasedSwitchInfo.LengthBasedJumpTable.LengthCaseLabels.Select(p => new KeyValuePair<ConstantValue, object>(ConstantValue.Create(p.value), p.label)).ToArray(),
                    fallThroughLabel,
                    stringLength,
                    int32Type.PrimitiveTypeCode,
                    syntaxNode);
 
                FreeTemp(stringLength);
            }
 
            void emitCharDispatches(LengthBasedStringSwitchData lengthBasedSwitchInfo, LocalOrParameter keyTemp, LabelSymbol fallThroughLabel, SyntaxNode syntaxNode)
            {
                var charType = Binder.GetSpecialType(_module.Compilation, SpecialType.System_Char, syntaxNode, _diagnostics);
                var charTemp = AllocateTemp(charType, syntaxNode);
 
                foreach (var charJumpTable in lengthBasedSwitchInfo.CharBasedJumpTables)
                {
                    // label for CharJumpTable:
                    _builder.MarkLabel(charJumpTable.Label);
 
                    //   charTemp = keyTemp[selectedCharPosition];
                    if (isSpanOrReadOnlySpan)
                    {
                        _builder.EmitLoadAddress(keyTemp);
                    }
                    else
                    {
                        _builder.EmitLoad(keyTemp);
                    }
                    _builder.EmitIntConstant(charJumpTable.SelectedCharPosition);
                    _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
                    emitMethodRef(indexerRef);
                    if (isSpanOrReadOnlySpan)
                    {
                        _builder.EmitOpCode(ILOpCode.Ldind_u2);
                    }
                    _builder.EmitLocalStore(charTemp);
 
                    // switch dispatch on charTemp using fallThroughLabel and cases:
                    //   charConstant -> corresponding label
                    _builder.EmitIntegerSwitchJumpTable(
                        charJumpTable.CharCaseLabels.Select(p => new KeyValuePair<ConstantValue, object>(ConstantValue.Create(p.value), p.label)).ToArray(),
                        fallThroughLabel,
                        charTemp,
                        charType.PrimitiveTypeCode,
                        syntaxNode);
                }
 
                FreeTemp(charTemp);
            }
 
            void emitFinalDispatches(LengthBasedStringSwitchData lengthBasedSwitchInfo, LocalOrParameter keyTemp, TypeSymbol keyType, LabelSymbol fallThroughLabel, SyntaxNode syntaxNode)
            {
                foreach (var stringJumpTable in lengthBasedSwitchInfo.StringBasedJumpTables)
                {
                    // label for StringJumpTable:
                    _builder.MarkLabel(stringJumpTable.Label);
 
                    // switch dispatch on keyTemp using fallThroughLabel and cases:
                    //   stringConstant -> corresponding label
                    EmitStringSwitchJumpTable(
                        stringJumpTable.StringCaseLabels.Select(p => new KeyValuePair<ConstantValue, object>(ConstantValue.Create(p.value), p.label)).ToArray(),
                        fallThroughLabel, keyTemp, syntaxNode, keyType);
                }
            }
 
            void emitMethodRef(Microsoft.Cci.IMethodReference lengthMethodRef)
            {
                var diag = DiagnosticBag.GetInstance();
                _builder.EmitToken(lengthMethodRef, syntaxNode: null);
                Debug.Assert(diag.IsEmptyWithoutResolution);
                diag.Free();
            }
        }
#nullable disable
 
        private void EmitStringSwitchJumpTable(
            KeyValuePair<ConstantValue, object>[] switchCaseLabels,
            LabelSymbol fallThroughLabel,
            LocalOrParameter key,
            SyntaxNode syntaxNode,
            TypeSymbol keyType)
        {
            var isSpan = keyType.IsSpanChar();
            var isReadOnlySpan = keyType.IsReadOnlySpanChar();
            var isSpanOrReadOnlySpan = isSpan || isReadOnlySpan;
 
            LocalDefinition keyHash = null;
 
            // Condition is necessary, but not sufficient (e.g. might be missing a special or well-known member).
            if (SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch(switchCaseLabels.Length))
            {
                var privateImplClass = _module.GetPrivateImplClass(syntaxNode, _diagnostics.DiagnosticBag).PrivateImplementationDetails;
                Cci.IReference stringHashMethodRef = privateImplClass.GetMethod(
                    isSpanOrReadOnlySpan
                        ? isReadOnlySpan
                            ? PrivateImplementationDetails.SynthesizedReadOnlySpanHashFunctionName
                            : PrivateImplementationDetails.SynthesizedSpanHashFunctionName
                        : PrivateImplementationDetails.SynthesizedStringHashFunctionName);
 
                // Heuristics and well-known member availability determine the existence
                // of this helper.  Rather than reproduce that (language-specific) logic here,
                // we simply check for the information we really want - whether the helper is
                // available.
                if (stringHashMethodRef != null)
                {
                    // static uint ComputeStringHash(string s)
                    // pop 1 (s)
                    // push 1 (uint return value)
                    // stackAdjustment = (pushCount - popCount) = 0
 
                    _builder.EmitLoad(key);
                    _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
                    _builder.EmitToken(stringHashMethodRef, syntaxNode);
 
                    var UInt32Type = Binder.GetSpecialType(_module.Compilation, SpecialType.System_UInt32, syntaxNode, _diagnostics);
                    keyHash = AllocateTemp(UInt32Type, syntaxNode);
 
                    _builder.EmitLocalStore(keyHash);
                }
            }
 
            Cci.IMethodReference stringEqualityMethodRef = null;
 
            Cci.IMethodReference sequenceEqualsMethodRef = null;
            Cci.IMethodReference asSpanMethodRef = null;
 
            if (isSpanOrReadOnlySpan)
            {
                // Binder.ConvertPatternExpression() has checked for these well-known members.
                var sequenceEqualsTMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation,
                    (isReadOnlySpan
                    ? WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T
                    : WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T),
                    _diagnostics, syntax: syntaxNode);
                Debug.Assert(sequenceEqualsTMethod != null && !sequenceEqualsTMethod.HasUseSiteError);
                var sequenceEqualsCharMethod = sequenceEqualsTMethod.Construct(Binder.GetSpecialType(_module.Compilation, SpecialType.System_Char, syntaxNode, _diagnostics));
                sequenceEqualsMethodRef = _module.Translate(sequenceEqualsCharMethod, null, _diagnostics.DiagnosticBag);
 
                var asSpanMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation, WellKnownMember.System_MemoryExtensions__AsSpan_String, _diagnostics, syntax: syntaxNode);
                Debug.Assert(asSpanMethod != null && !asSpanMethod.HasUseSiteError);
                asSpanMethodRef = _module.Translate(asSpanMethod, null, _diagnostics.DiagnosticBag);
            }
            else
            {
                var stringEqualityMethod = _module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__op_Equality) as MethodSymbol;
                Debug.Assert(stringEqualityMethod != null && !stringEqualityMethod.HasUseSiteError);
                stringEqualityMethodRef = _module.Translate(stringEqualityMethod, syntaxNode, _diagnostics.DiagnosticBag);
            }
 
            Microsoft.Cci.IMethodReference lengthMethodRef = GetLengthMethodRef(syntaxNode, keyType, isReadOnlySpan, isSpanOrReadOnlySpan);
 
            SwitchStringJumpTableEmitter.EmitStringCompareAndBranch emitStringCondBranchDelegate =
                (keyArg, stringConstant, targetLabel) =>
                {
                    if (stringConstant == ConstantValue.Null)
                    {
                        Debug.Assert(!isSpanOrReadOnlySpan);
 
                        // if (key == null)
                        //      goto targetLabel
                        _builder.EmitLoad(keyArg);
                        _builder.EmitBranch(ILOpCode.Brfalse, targetLabel, ILOpCode.Brtrue);
                    }
                    else if (stringConstant.StringValue.Length == 0 && lengthMethodRef != null)
                    {
                        // if (key != null && key.Length == 0)
                        //      goto targetLabel
 
                        object skipToNext = new object();
                        if (isSpanOrReadOnlySpan)
                        {
                            // The caller ensures that the key is not byref, and is not a stack local
                            _builder.EmitLoadAddress(keyArg);
                        }
                        else
                        {
                            _builder.EmitLoad(keyArg);
                            _builder.EmitBranch(ILOpCode.Brfalse, skipToNext, ILOpCode.Brtrue);
 
                            _builder.EmitLoad(keyArg);
                        }
 
                        // Stack: key --> length
                        _builder.EmitOpCode(ILOpCode.Call, 0);
                        var diag = DiagnosticBag.GetInstance();
                        _builder.EmitToken(lengthMethodRef, null);
                        Debug.Assert(diag.IsEmptyWithoutResolution);
                        diag.Free();
 
                        _builder.EmitBranch(ILOpCode.Brfalse, targetLabel, ILOpCode.Brtrue);
                        _builder.MarkLabel(skipToNext);
                    }
                    else
                    {
                        if (isSpanOrReadOnlySpan)
                        {
                            this.EmitCharCompareAndBranch(key, syntaxNode, stringConstant, targetLabel, sequenceEqualsMethodRef, asSpanMethodRef);
                        }
                        else
                        {
                            this.EmitStringCompareAndBranch(key, syntaxNode, stringConstant, targetLabel, stringEqualityMethodRef);
                        }
                    }
                };
 
            _builder.EmitStringSwitchJumpTable(
                syntaxNode,
                caseLabels: switchCaseLabels,
                fallThroughLabel: fallThroughLabel,
                key: key,
                keyHash: keyHash,
                emitStringCondBranchDelegate: emitStringCondBranchDelegate,
                computeStringHashcodeDelegate: SynthesizedStringSwitchHashMethod.ComputeStringHash);
 
            if (keyHash != null)
            {
                FreeTemp(keyHash);
            }
        }
 
#nullable enable
        private Cci.IMethodReference? GetLengthMethodRef(SyntaxNode syntaxNode, TypeSymbol keyType, bool isReadOnlySpan, bool isSpanOrReadOnlySpan)
        {
            if (isSpanOrReadOnlySpan)
            {
                var spanTLengthMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation,
                    (isReadOnlySpan ? WellKnownMember.System_ReadOnlySpan_T__get_Length : WellKnownMember.System_Span_T__get_Length),
                    _diagnostics, syntax: syntaxNode);
 
                Debug.Assert(spanTLengthMethod != null && !spanTLengthMethod.HasUseSiteError);
                var spanCharLengthMethod = spanTLengthMethod.AsMember((NamedTypeSymbol)keyType);
                return _module.Translate(spanCharLengthMethod, syntaxNode, _diagnostics.DiagnosticBag);
            }
            else
            {
                var stringLengthMethod = _module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__Length) as MethodSymbol;
                if (stringLengthMethod != null && !stringLengthMethod.HasUseSiteError)
                {
                    return _module.Translate(stringLengthMethod, syntaxNode, _diagnostics.DiagnosticBag);
                }
            }
 
            return null;
        }
 
        private Microsoft.Cci.IMethodReference? GetIndexerRef(SyntaxNode syntaxNode, TypeSymbol keyType, bool isReadOnlySpan, bool isSpanOrReadOnlySpan)
        {
            if (isSpanOrReadOnlySpan)
            {
                var spanTIndexerMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation,
                    (isReadOnlySpan ? WellKnownMember.System_ReadOnlySpan_T__get_Item : WellKnownMember.System_Span_T__get_Item),
                    _diagnostics, syntax: syntaxNode);
 
                if (spanTIndexerMethod != null && !spanTIndexerMethod.HasUseSiteError)
                {
                    var spanCharLengthMethod = spanTIndexerMethod.AsMember((NamedTypeSymbol)keyType);
                    return _module.Translate(spanCharLengthMethod, null, _diagnostics.DiagnosticBag);
                }
            }
            else
            {
                var stringCharsIndexer = _module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__Chars) as MethodSymbol;
                if (stringCharsIndexer != null && !stringCharsIndexer.HasUseSiteError)
                {
                    return _module.Translate(stringCharsIndexer, syntaxNode, _diagnostics.DiagnosticBag);
                }
            }
 
            return null;
        }
#nullable disable
 
        /// <summary>
        /// Delegate to emit string compare call and conditional branch based on the compare result.
        /// </summary>
        /// <param name="key">Key to compare</param>
        /// <param name="syntaxNode">Node for diagnostics.</param>
        /// <param name="stringConstant">Case constant to compare the key against</param>
        /// <param name="targetLabel">Target label to branch to if key = stringConstant</param>
        /// <param name="stringEqualityMethodRef">String equality method</param>
        private void EmitStringCompareAndBranch(LocalOrParameter key, SyntaxNode syntaxNode, ConstantValue stringConstant, object targetLabel, Microsoft.Cci.IReference stringEqualityMethodRef)
        {
            // Emit compare and branch:
 
            // if (key == stringConstant)
            //      goto targetLabel;
 
            Debug.Assert(stringEqualityMethodRef != null);
 
#if DEBUG
            var assertDiagnostics = DiagnosticBag.GetInstance();
            Debug.Assert(stringEqualityMethodRef == _module.Translate((MethodSymbol)_module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__op_Equality), (CSharpSyntaxNode)syntaxNode, assertDiagnostics));
            assertDiagnostics.Free();
#endif
 
            // static bool String.Equals(string a, string b)
            // pop 2 (a, b)
            // push 1 (bool return value)
 
            // stackAdjustment = (pushCount - popCount) = -1
 
            _builder.EmitLoad(key);
            _builder.EmitConstantValue(stringConstant, syntaxNode);
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
            _builder.EmitToken(stringEqualityMethodRef, syntaxNode);
 
            // Branch to targetLabel if String.Equals returned true.
            _builder.EmitBranch(ILOpCode.Brtrue, targetLabel, ILOpCode.Brfalse);
        }
 
        /// <summary>
        /// Delegate to emit ReadOnlySpanChar compare with string and conditional branch based on the compare result.
        /// </summary>
        /// <param name="key">Key to compare</param>
        /// <param name="syntaxNode">Node for diagnostics.</param>
        /// <param name="stringConstant">Case constant to compare the key against</param>
        /// <param name="targetLabel">Target label to branch to if key = stringConstant</param>
        /// <param name="sequenceEqualsRef">String equality method</param>
        private void EmitCharCompareAndBranch(LocalOrParameter key, SyntaxNode syntaxNode, ConstantValue stringConstant, object targetLabel, Cci.IReference sequenceEqualsRef, Cci.IReference asSpanRef)
        {
            // Emit compare and branch:
 
            // if (key.SequenceEqual(stringConstant.AsSpan()))
            //      goto targetLabel;
 
            Debug.Assert(sequenceEqualsRef != null);
            Debug.Assert(asSpanRef != null);
 
            _builder.EmitLoad(key);
            _builder.EmitConstantValue(stringConstant, syntaxNode);
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
            _builder.EmitToken(asSpanRef, syntaxNode);
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
            _builder.EmitToken(sequenceEqualsRef, syntaxNode);
 
            // Branch to targetLabel if SequenceEquals returned true.
            _builder.EmitBranch(ILOpCode.Brtrue, targetLabel, ILOpCode.Brfalse);
        }
 
        /// <summary>
        /// Gets already declared and initialized local.
        /// </summary>
        private LocalDefinition GetLocal(BoundLocal localExpression)
        {
            var symbol = localExpression.LocalSymbol;
            return GetLocal(symbol);
        }
 
        private LocalDefinition GetLocal(LocalSymbol symbol)
        {
            return _builder.LocalSlotManager.GetLocal(symbol);
        }
 
        private LocalDefinition DefineLocal(LocalSymbol local, SyntaxNode syntaxNode)
        {
            var dynamicTransformFlags = !local.IsCompilerGenerated && local.Type.ContainsDynamic() ?
                CSharpCompilation.DynamicTransformsEncoder.Encode(local.Type, RefKind.None, 0) :
                ImmutableArray<bool>.Empty;
            var tupleElementNames = !local.IsCompilerGenerated && local.Type.ContainsTupleNames() ?
                CSharpCompilation.TupleNamesEncoder.Encode(local.Type) :
                ImmutableArray<string>.Empty;
 
            if (local.IsConst)
            {
                Debug.Assert(local.HasConstantValue);
                MetadataConstant compileTimeValue = _module.CreateConstant(local.Type, local.ConstantValue, syntaxNode, _diagnostics.DiagnosticBag);
                LocalConstantDefinition localConstantDef = new LocalConstantDefinition(
                    local.Name,
                    local.GetFirstLocationOrNone(),
                    compileTimeValue,
                    dynamicTransformFlags: dynamicTransformFlags,
                    tupleElementNames: tupleElementNames);
                _builder.AddLocalConstantToScope(localConstantDef);
                return null;
            }
 
            if (IsStackLocal(local))
            {
                return null;
            }
 
            LocalSlotConstraints constraints;
            Cci.ITypeReference translatedType;
 
            if (local.DeclarationKind == LocalDeclarationKind.FixedVariable && local.IsPinned) // Excludes pointer local and string local in fixed string case.
            {
                Debug.Assert(local.RefKind == RefKind.None);
                Debug.Assert(local.TypeWithAnnotations.Type.IsPointerType());
 
                constraints = LocalSlotConstraints.ByRef | LocalSlotConstraints.Pinned;
                PointerTypeSymbol pointerType = (PointerTypeSymbol)local.Type;
                TypeSymbol pointedAtType = pointerType.PointedAtType;
 
                // We can't declare a reference to void, so if the pointed-at type is void, use native int
                // (represented here by IntPtr) instead.
                translatedType = pointedAtType.IsVoidType()
                    ? _module.GetSpecialType(SpecialType.System_IntPtr, syntaxNode, _diagnostics.DiagnosticBag)
                    : _module.Translate(pointedAtType, syntaxNode, _diagnostics.DiagnosticBag);
            }
            else
            {
                constraints = (local.IsPinned ? LocalSlotConstraints.Pinned : LocalSlotConstraints.None) |
                    (local.RefKind != RefKind.None ? LocalSlotConstraints.ByRef : LocalSlotConstraints.None);
                translatedType = _module.Translate(local.Type, syntaxNode, _diagnostics.DiagnosticBag);
            }
 
            // Even though we don't need the token immediately, we will need it later when signature for the local is emitted.
            // Also, requesting the token has side-effect of registering types used, which is critical for embedded types (NoPia, VBCore, etc).
            _module.GetFakeSymbolTokenForIL(translatedType, syntaxNode, _diagnostics.DiagnosticBag);
 
            LocalDebugId localId;
            var name = GetLocalDebugName(local, out localId);
 
            var localDef = _builder.LocalSlotManager.DeclareLocal(
                type: translatedType,
                symbol: local,
                name: name,
                kind: local.SynthesizedKind,
                id: localId,
                pdbAttributes: local.SynthesizedKind.PdbAttributes(),
                constraints: constraints,
                dynamicTransformFlags: dynamicTransformFlags,
                tupleElementNames: tupleElementNames,
                isSlotReusable: local.SynthesizedKind.IsSlotReusable(_ilEmitStyle != ILEmitStyle.Release));
 
            // If named, add it to the local debug scope.
            if (localDef.Name != null &&
                !(local.SynthesizedKind == SynthesizedLocalKind.UserDefined &&
                // Visibility scope of such locals is represented by BoundScope node.
                (local.ScopeDesignatorOpt?.Kind() is SyntaxKind.SwitchSection or SyntaxKind.SwitchExpressionArm)))
            {
                _builder.AddLocalToScope(localDef);
            }
 
            return localDef;
        }
 
        /// <summary>
        /// Gets the name and id of the local that are going to be generated into the debug metadata.
        /// </summary>
        private string GetLocalDebugName(ILocalSymbolInternal local, out LocalDebugId localId)
        {
            localId = LocalDebugId.None;
 
            if (local.IsImportedFromMetadata)
            {
                return local.Name;
            }
 
            var localKind = local.SynthesizedKind;
 
            // only user-defined locals should be named during lowering:
            Debug.Assert((local.Name == null) == (localKind != SynthesizedLocalKind.UserDefined));
 
            // Generating debug names for instrumentation payloads should be allowed, as described in https://github.com/dotnet/roslyn/issues/11024.
            // For now, skip naming locals generated by instrumentation as they might not have a local syntax offset.
            // Locals generated by instrumentation might exist in methods which do not contain a body (auto property initializers).
            if (!localKind.IsLongLived() || localKind == SynthesizedLocalKind.InstrumentationPayload)
            {
                return null;
            }
 
            if (_ilEmitStyle == ILEmitStyle.Debug)
            {
                var syntax = local.GetDeclaratorSyntax();
                int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(syntax), syntax.SyntaxTree);
                int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset);
 
                // user-defined locals should have 0 ordinal:
                Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined);
 
                localId = new LocalDebugId(syntaxOffset, ordinal);
            }
 
            return local.Name ?? GeneratedNames.MakeSynthesizedLocalName(localKind, ref _uniqueNameId);
        }
 
        private bool IsSlotReusable(LocalSymbol local)
        {
            return local.SynthesizedKind.IsSlotReusable(_ilEmitStyle != ILEmitStyle.Release);
        }
 
        /// <summary>
        /// Releases a local.
        /// </summary>
        private void FreeLocal(LocalSymbol local)
        {
            // TODO: releasing named locals is NYI.
            if (local.Name == null && IsSlotReusable(local) && !IsStackLocal(local))
            {
                _builder.LocalSlotManager.FreeLocal(local);
            }
        }
 
        /// <summary>
        /// Allocates a temp without identity.
        /// </summary>
        private LocalDefinition AllocateTemp(TypeSymbol type, SyntaxNode syntaxNode, LocalSlotConstraints slotConstraints = LocalSlotConstraints.None)
        {
            return _builder.LocalSlotManager.AllocateSlot(
                _module.Translate(type, syntaxNode, _diagnostics.DiagnosticBag),
                slotConstraints);
        }
 
        /// <summary>
        /// Frees a temp.
        /// </summary>
        private void FreeTemp(LocalDefinition temp)
        {
            _builder.LocalSlotManager.FreeSlot(temp);
        }
 
        /// <summary>
        /// Frees an optional temp.
        /// </summary>
        private void FreeOptTemp(LocalDefinition temp)
        {
            if (temp != null)
            {
                FreeTemp(temp);
            }
        }
 
        /// <summary>
        /// Clones all labels used in a finally block.
        /// This allows creating an emittable clone of finally.
        /// It is safe to do because no branches can go in or out of the finally handler.
        /// </summary>
        private class FinallyCloner : BoundTreeRewriterWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
        {
            private Dictionary<LabelSymbol, GeneratedLabelSymbol> _labelClones;
 
            private FinallyCloner() { }
 
            /// <summary>
            /// The argument is BoundTryStatement (and not a BoundBlock) specifically
            /// to support only Finally blocks where it is guaranteed to not have incoming or leaving branches.
            /// </summary>
            public static BoundBlock MakeFinallyClone(BoundTryStatement node)
            {
                var cloner = new FinallyCloner();
                return (BoundBlock)cloner.Visit(node.FinallyBlockOpt);
            }
 
            public override BoundNode VisitLabelStatement(BoundLabelStatement node)
            {
                return node.Update(GetLabelClone(node.Label));
            }
 
            public override BoundNode VisitGotoStatement(BoundGotoStatement node)
            {
                var labelClone = GetLabelClone(node.Label);
 
                // expressions do not contain labels or branches
                BoundExpression caseExpressionOpt = node.CaseExpressionOpt;
                // expressions do not contain labels or branches
                BoundLabel labelExpressionOpt = node.LabelExpressionOpt;
 
                return node.Update(labelClone, caseExpressionOpt, labelExpressionOpt);
            }
 
            public override BoundNode VisitConditionalGoto(BoundConditionalGoto node)
            {
                var labelClone = GetLabelClone(node.Label);
 
                // expressions do not contain labels or branches
                BoundExpression condition = node.Condition;
 
                return node.Update(condition, node.JumpIfTrue, labelClone);
            }
 
            public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node)
            {
                // expressions do not contain labels or branches
                BoundExpression expression = node.Expression;
 
                var defaultClone = GetLabelClone(node.DefaultLabel);
                var casesBuilder = ArrayBuilder<(ConstantValue, LabelSymbol)>.GetInstance();
                foreach (var (value, label) in node.Cases)
                {
                    casesBuilder.Add((value, GetLabelClone(label)));
                }
 
                var lengthBasedSwitchData = node.LengthBasedStringSwitchDataOpt;
                if (lengthBasedSwitchData is not null)
                {
                    // We don't currently produce switch dispatches inside `fault` handler
                    throw ExceptionUtilities.Unreachable();
                }
 
                return node.Update(expression, casesBuilder.ToImmutableAndFree(), defaultClone, lengthBasedSwitchData);
            }
 
            public override BoundNode VisitExpressionStatement(BoundExpressionStatement node)
            {
                // expressions do not contain labels or branches
                return node;
            }
 
            private GeneratedLabelSymbol GetLabelClone(LabelSymbol label)
            {
                var labelClones = _labelClones;
                if (labelClones == null)
                {
                    _labelClones = labelClones = new Dictionary<LabelSymbol, GeneratedLabelSymbol>();
                }
 
                GeneratedLabelSymbol clone;
                if (!labelClones.TryGetValue(label, out clone))
                {
                    clone = new GeneratedLabelSymbol("cloned_" + label.Name);
                    labelClones.Add(label, clone);
                }
 
                return clone;
            }
        }
    }
}