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)
            {
                _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)
        {
            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;
 
            _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);
 
                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, _diagnostics.DiagnosticBag);
                    _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();
 
                // Pop the exception; it should have already been stored to the
                // variable by the filter.
                _builder.EmitOpCode(ILOpCode.Pop);
            }
 
            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);
            }
 
            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);
 
                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);
                }
 
                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, diag);
                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, _diagnostics.DiagnosticBag);
 
                    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, diag);
                        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(
                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);
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
            _builder.EmitToken(stringEqualityMethodRef, syntaxNode, _diagnostics.DiagnosticBag);
 
            // 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);
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
            _builder.EmitToken(asSpanRef, syntaxNode, _diagnostics.DiagnosticBag);
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
            _builder.EmitToken(sequenceEqualsRef, syntaxNode, _diagnostics.DiagnosticBag);
 
            // 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;
            }
        }
    }
}