File: CodeGen\EmitExpression.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.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
using static System.Linq.ImmutableArrayExtensions;
using static Microsoft.CodeAnalysis.CSharp.Binder;
 
namespace Microsoft.CodeAnalysis.CSharp.CodeGen
{
    internal partial class CodeGenerator
    {
        private int _recursionDepth;
 
        private class EmitCancelledException : Exception
        { }
 
        private enum UseKind
        {
            Unused,
            UsedAsValue,
            UsedAsAddress
        }
 
        private void EmitExpression(BoundExpression expression, bool used)
        {
            if (expression == null)
            {
                return;
            }
 
            var constantValue = expression.ConstantValueOpt;
            if (constantValue != null)
            {
                if (!used)
                {
                    // unused constants have no side-effects.
                    return;
                }
 
                if ((object)expression.Type == null ||
                    (expression.Type.SpecialType != SpecialType.System_Decimal &&
                     !expression.Type.IsNullableType()))
                {
                    EmitConstantExpression(expression.Type, constantValue, used, expression.Syntax);
                    return;
                }
            }
 
            _recursionDepth++;
 
            if (_recursionDepth > 1)
            {
                StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
 
                EmitExpressionCore(expression, used);
            }
            else
            {
                EmitExpressionCoreWithStackGuard(expression, used);
            }
 
            _recursionDepth--;
        }
 
        private void EmitExpressionCoreWithStackGuard(BoundExpression expression, bool used)
        {
            Debug.Assert(_recursionDepth == 1);
 
            try
            {
                EmitExpressionCore(expression, used);
                Debug.Assert(_recursionDepth == 1);
            }
            catch (InsufficientExecutionStackException)
            {
                _diagnostics.Add(ErrorCode.ERR_InsufficientStack,
                                 BoundTreeVisitor.CancelledByStackGuardException.GetTooLongOrComplexExpressionErrorLocation(expression));
                throw new EmitCancelledException();
            }
        }
 
        private void EmitExpressionCore(BoundExpression expression, bool used)
        {
            switch (expression.Kind)
            {
                case BoundKind.AssignmentOperator:
                    EmitAssignmentExpression((BoundAssignmentOperator)expression, used ? UseKind.UsedAsValue : UseKind.Unused);
                    break;
 
                case BoundKind.Call:
                    EmitCallExpression((BoundCall)expression, used ? UseKind.UsedAsValue : UseKind.Unused);
                    break;
 
                case BoundKind.ObjectCreationExpression:
                    EmitObjectCreationExpression((BoundObjectCreationExpression)expression, used);
                    break;
 
                case BoundKind.DelegateCreationExpression:
                    EmitDelegateCreationExpression((BoundDelegateCreationExpression)expression, used);
                    break;
 
                case BoundKind.ArrayCreation:
                    EmitArrayCreationExpression((BoundArrayCreation)expression, used);
                    break;
 
                case BoundKind.ConvertedStackAllocExpression:
                    EmitConvertedStackAllocExpression((BoundConvertedStackAllocExpression)expression, used);
                    break;
 
                case BoundKind.ReadOnlySpanFromArray:
                    EmitReadOnlySpanFromArrayExpression((BoundReadOnlySpanFromArray)expression, used);
                    break;
 
                case BoundKind.Conversion:
                    EmitConversionExpression((BoundConversion)expression, used);
                    break;
 
                case BoundKind.Local:
                    EmitLocalLoad((BoundLocal)expression, used);
                    break;
 
                case BoundKind.Dup:
                    EmitDupExpression((BoundDup)expression, used);
                    break;
 
                case BoundKind.PassByCopy:
                    EmitExpression(((BoundPassByCopy)expression).Expression, used);
                    break;
 
                case BoundKind.Parameter:
                    if (used)  // unused parameter has no side-effects
                    {
                        EmitParameterLoad((BoundParameter)expression);
                    }
                    break;
 
                case BoundKind.FieldAccess:
                    EmitFieldLoad((BoundFieldAccess)expression, used);
                    break;
 
                case BoundKind.ArrayAccess:
                    EmitArrayElementLoad((BoundArrayAccess)expression, used);
                    break;
 
                case BoundKind.RefArrayAccess:
                    EmitArrayElementRefLoad((BoundRefArrayAccess)expression, used);
                    break;
 
                case BoundKind.ArrayLength:
                    EmitArrayLength((BoundArrayLength)expression, used);
                    break;
 
                case BoundKind.ThisReference:
                    if (used) // unused this has no side-effects
                    {
                        EmitThisReferenceExpression((BoundThisReference)expression);
                    }
                    break;
 
                case BoundKind.PreviousSubmissionReference:
                    // Script references are lowered to a this reference and a field access.
                    throw ExceptionUtilities.UnexpectedValue(expression.Kind);
 
                case BoundKind.BaseReference:
                    if (used) // unused base has no side-effects
                    {
                        var thisType = _method.ContainingType;
                        _builder.EmitOpCode(ILOpCode.Ldarg_0);
                        if (thisType.IsValueType)
                        {
                            EmitLoadIndirect(thisType, expression.Syntax);
                            EmitBox(thisType, expression.Syntax);
                        }
                    }
                    break;
 
                case BoundKind.Sequence:
                    EmitSequenceExpression((BoundSequence)expression, used);
                    break;
 
                case BoundKind.SequencePointExpression:
                    EmitSequencePointExpression((BoundSequencePointExpression)expression, used);
                    break;
 
                case BoundKind.UnaryOperator:
                    EmitUnaryOperatorExpression((BoundUnaryOperator)expression, used);
                    break;
 
                case BoundKind.BinaryOperator:
                    EmitBinaryOperatorExpression((BoundBinaryOperator)expression, used);
                    break;
 
                case BoundKind.NullCoalescingOperator:
                    EmitNullCoalescingOperator((BoundNullCoalescingOperator)expression, used);
                    break;
 
                case BoundKind.IsOperator:
                    EmitIsExpression((BoundIsOperator)expression, used, omitBooleanConversion: false);
                    break;
 
                case BoundKind.AsOperator:
                    EmitAsExpression((BoundAsOperator)expression, used);
                    break;
 
                case BoundKind.DefaultExpression:
                    EmitDefaultExpression((BoundDefaultExpression)expression, used);
                    break;
 
                case BoundKind.TypeOfOperator:
                    if (used) // unused typeof has no side-effects
                    {
                        EmitTypeOfExpression((BoundTypeOfOperator)expression);
                    }
                    break;
 
                case BoundKind.SizeOfOperator:
                    if (used) // unused sizeof has no side-effects
                    {
                        EmitSizeOfExpression((BoundSizeOfOperator)expression);
                    }
                    break;
 
                case BoundKind.ModuleVersionId:
                    Debug.Assert(used);
                    EmitModuleVersionIdLoad((BoundModuleVersionId)expression);
                    break;
 
                case BoundKind.ModuleVersionIdString:
                    Debug.Assert(used);
                    EmitModuleVersionIdStringLoad();
                    break;
 
                case BoundKind.ThrowIfModuleCancellationRequested:
                    Debug.Assert(!used);
                    EmitThrowIfModuleCancellationRequested(expression.Syntax);
                    break;
 
                case BoundKind.ModuleCancellationTokenExpression:
                    Debug.Assert(used);
                    EmitModuleCancellationTokenLoad(expression.Syntax);
                    break;
 
                case BoundKind.InstrumentationPayloadRoot:
                    Debug.Assert(used);
                    EmitInstrumentationPayloadRootLoad((BoundInstrumentationPayloadRoot)expression);
                    break;
 
                case BoundKind.MethodDefIndex:
                    Debug.Assert(used);
                    EmitMethodDefIndexExpression((BoundMethodDefIndex)expression);
                    break;
 
                case BoundKind.MaximumMethodDefIndex:
                    Debug.Assert(used);
                    EmitMaximumMethodDefIndexExpression((BoundMaximumMethodDefIndex)expression);
                    break;
 
                case BoundKind.SourceDocumentIndex:
                    Debug.Assert(used);
                    EmitSourceDocumentIndex((BoundSourceDocumentIndex)expression);
                    break;
 
                case BoundKind.LocalId:
                    Debug.Assert(used);
                    EmitLocalIdExpression((BoundLocalId)expression);
                    break;
 
                case BoundKind.ParameterId:
                    Debug.Assert(used);
                    EmitParameterIdExpression((BoundParameterId)expression);
                    break;
 
                case BoundKind.MethodInfo:
                    if (used)
                    {
                        EmitMethodInfoExpression((BoundMethodInfo)expression);
                    }
                    break;
 
                case BoundKind.FieldInfo:
                    if (used)
                    {
                        EmitFieldInfoExpression((BoundFieldInfo)expression);
                    }
                    break;
 
                case BoundKind.ConditionalOperator:
                    EmitConditionalOperator((BoundConditionalOperator)expression, used);
                    break;
 
                case BoundKind.AddressOfOperator:
                    EmitAddressOfExpression((BoundAddressOfOperator)expression, used);
                    break;
 
                case BoundKind.PointerIndirectionOperator:
                    EmitPointerIndirectionOperator((BoundPointerIndirectionOperator)expression, used);
                    break;
 
                case BoundKind.ArgList:
                    EmitArgList(used);
                    break;
 
                case BoundKind.ArgListOperator:
                    Debug.Assert(used);
                    EmitArgListOperator((BoundArgListOperator)expression);
                    break;
 
                case BoundKind.RefTypeOperator:
                    EmitRefTypeOperator((BoundRefTypeOperator)expression, used);
                    break;
 
                case BoundKind.MakeRefOperator:
                    EmitMakeRefOperator((BoundMakeRefOperator)expression, used);
                    break;
 
                case BoundKind.RefValueOperator:
                    EmitRefValueOperator((BoundRefValueOperator)expression, used);
                    break;
 
                case BoundKind.LoweredConditionalAccess:
                    EmitLoweredConditionalAccessExpression((BoundLoweredConditionalAccess)expression, used);
                    break;
 
                case BoundKind.ConditionalReceiver:
                    EmitConditionalReceiver((BoundConditionalReceiver)expression, used);
                    break;
 
                case BoundKind.ComplexConditionalReceiver:
                    EmitComplexConditionalReceiver((BoundComplexConditionalReceiver)expression, used);
                    break;
 
                case BoundKind.PseudoVariable:
                    EmitPseudoVariableValue((BoundPseudoVariable)expression, used);
                    break;
 
                case BoundKind.ThrowExpression:
                    EmitThrowExpression((BoundThrowExpression)expression, used);
                    break;
 
                case BoundKind.FunctionPointerInvocation:
                    EmitCalli((BoundFunctionPointerInvocation)expression, used ? UseKind.UsedAsValue : UseKind.Unused);
                    break;
 
                case BoundKind.FunctionPointerLoad:
                    EmitLoadFunction((BoundFunctionPointerLoad)expression, used);
                    break;
 
                default:
                    // Code gen should not be invoked if there are errors.
                    Debug.Assert(expression.Kind != BoundKind.BadExpression);
 
                    // node should have been lowered:
                    throw ExceptionUtilities.UnexpectedValue(expression.Kind);
            }
        }
 
        private void EmitThrowExpression(BoundThrowExpression node, bool used)
        {
            this.EmitThrow(node.Expression);
 
            // to satisfy invariants, we push a default value to pretend to adjust the stack height
            EmitDefaultValue(node.Type, used, node.Syntax);
        }
 
        private void EmitComplexConditionalReceiver(BoundComplexConditionalReceiver expression, bool used)
        {
            Debug.Assert(!expression.Type.IsReferenceType);
            Debug.Assert(!expression.Type.IsValueType);
 
            var receiverType = expression.Type;
 
            var whenValueTypeLabel = new object();
            var doneLabel = new object();
 
            EmitInitObj(receiverType, true, expression.Syntax);
            EmitBox(receiverType, expression.Syntax);
            _builder.EmitBranch(ILOpCode.Brtrue, whenValueTypeLabel);
 
            EmitExpression(expression.ReferenceTypeReceiver, used);
            _builder.EmitBranch(ILOpCode.Br, doneLabel);
 
            if (used)
            {
                _builder.AdjustStack(-1);
            }
 
            _builder.MarkLabel(whenValueTypeLabel);
            EmitExpression(expression.ValueTypeReceiver, used);
 
            _builder.MarkLabel(doneLabel);
        }
 
        private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAccess expression, bool used)
        {
            var receiver = expression.Receiver;
 
            var receiverType = receiver.Type;
            LocalDefinition receiverTemp = null;
            Debug.Assert(!receiverType.IsValueType ||
                (receiverType.IsNullableType() && expression.HasValueMethodOpt != null), "conditional receiver cannot be a struct");
 
            var receiverConstant = receiver.ConstantValueOpt;
            if (receiverConstant?.IsNull == false)
            {
                // const but not null, must be a reference type
                Debug.Assert(receiverType.IsVerifierReference());
                // receiver is a reference type, so addresskind does not matter, but we do not intend to write.
                receiverTemp = EmitReceiverRef(receiver, AddressKind.ReadOnly);
                EmitExpression(expression.WhenNotNull, used);
                if (receiverTemp != null)
                {
                    FreeTemp(receiverTemp);
                }
                return;
            }
 
            // labels
            object whenNotNullLabel = new object();
            object doneLabel = new object();
            LocalDefinition cloneTemp = null;
 
            var notConstrained = !receiverType.IsReferenceType && !receiverType.IsValueType;
 
            // we need a copy if we deal with nonlocal value (to capture the value)
            // or if we have a ref-constrained T (to do box just once) 
            // or if we deal with stack local (reads are destructive)
            // or if we have default(T) (to do box just once)
            var nullCheckOnCopy = (expression.ForceCopyOfNullableValueType && notConstrained &&
                                   ((TypeParameterSymbol)receiverType).EffectiveInterfacesNoUseSiteDiagnostics.IsEmpty) || // This could be a nullable value type, which must be copied in order to not mutate the original value
                                   LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) ||
                                   (receiverType.IsReferenceType && receiverType.TypeKind == TypeKind.TypeParameter) ||
                                   (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)) ||
                                   (notConstrained && IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker.Analyze(expression));
 
            // ===== RECEIVER
            if (nullCheckOnCopy)
            {
                if (notConstrained)
                {
                    // if T happens to be a value type, it could be a target of mutating calls.
                    receiverTemp = EmitReceiverRef(receiver, AddressKind.Constrained);
 
                    if (receiverTemp is null)
                    {
                        // unconstrained case needs to handle case where T is actually a struct.
                        // such values are never nulls
                        // we will emit a check for such case, but the check is really a JIT-time 
                        // constant since JIT will know if T is a struct or not.
 
                        // if ((object)default(T) != null) 
                        // {
                        //     goto whenNotNull
                        // }
                        // else
                        // {
                        //     temp = receiverRef
                        //     receiverRef = ref temp
                        // }
                        EmitDefaultValue(receiverType, true, receiver.Syntax);
                        EmitBox(receiverType, receiver.Syntax);
                        _builder.EmitBranch(ILOpCode.Brtrue, whenNotNullLabel);
                        EmitLoadIndirect(receiverType, receiver.Syntax);
 
                        cloneTemp = AllocateTemp(receiverType, receiver.Syntax);
                        _builder.EmitLocalStore(cloneTemp);
                        _builder.EmitLocalAddress(cloneTemp);
                        _builder.EmitLocalLoad(cloneTemp);
                        EmitBox(receiverType, receiver.Syntax);
 
                        // here we have loaded a ref to a temp and its boxed value { &T, O }
                    }
                    else
                    {
                        // We are calling the expression on a copy of the target anyway, 
                        // so even if T is a struct, we don't need to make sure we call the expression on the original target.
 
                        // We currently have an address on the stack. Duplicate it, and load the value of the address.
                        _builder.EmitOpCode(ILOpCode.Dup);
                        EmitLoadIndirect(receiverType, receiver.Syntax);
                        EmitBox(receiverType, receiver.Syntax);
                    }
                }
                else
                {
                    // this does not need to be writeable
                    // we may call "HasValue" on this, but it is not mutating 
                    var addressKind = AddressKind.ReadOnly;
 
                    receiverTemp = EmitReceiverRef(receiver, addressKind);
                    _builder.EmitOpCode(ILOpCode.Dup);
                    // here we have loaded two copies of a reference   { O, O }  or  {&nub, &nub}
                }
            }
            else
            {
                // this does not need to be writeable.
                // we may call "HasValue" on this, but it is not mutating
                // besides, since we are not making a copy, the receiver is not a field, 
                // so it cannot be readonly, in verifier sense, anyways.
                receiverTemp = EmitReceiverRef(receiver, AddressKind.ReadOnly);
                // here we have loaded just { O } or  {&nub}
                // we have the most trivial case where we can just reload receiver when needed again
            }
 
            // ===== CONDITION
 
            var hasValueOpt = expression.HasValueMethodOpt;
            if (hasValueOpt != null)
            {
                Debug.Assert(receiver.Type.IsNullableType());
                _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
                EmitSymbolToken(hasValueOpt, expression.Syntax, null);
            }
 
            _builder.EmitBranch(ILOpCode.Brtrue, whenNotNullLabel);
 
            // no longer need the temp if we are not holding a copy
            if (receiverTemp != null && !nullCheckOnCopy)
            {
                FreeTemp(receiverTemp);
                receiverTemp = null;
            }
 
            // ===== WHEN NULL
            if (nullCheckOnCopy)
            {
                _builder.EmitOpCode(ILOpCode.Pop);
            }
 
            var whenNull = expression.WhenNullOpt;
            if (whenNull == null)
            {
                EmitDefaultValue(expression.Type, used, expression.Syntax);
            }
            else
            {
                EmitExpression(whenNull, used);
            }
 
            _builder.EmitBranch(ILOpCode.Br, doneLabel);
 
            // ===== WHEN NOT NULL 
            if (nullCheckOnCopy)
            {
                // notNull branch pops copy of receiver off the stack when nullCheckOnCopy
                // however on the isNull branch we still have the stack as it was and need 
                // to adjust stack depth correspondingly.
                _builder.AdjustStack(+1);
            }
 
            if (used)
            {
                // notNull branch pushes default on the stack when used
                // however on the isNull branch we still have the stack as it was and need 
                // to adjust stack depth correspondingly.
                _builder.AdjustStack(-1);
            }
 
            _builder.MarkLabel(whenNotNullLabel);
 
            if (!nullCheckOnCopy)
            {
                Debug.Assert(receiverTemp == null);
                // receiver may be used as target of a struct call (if T happens to be a struct)
                receiverTemp = EmitReceiverRef(receiver, AddressKind.Constrained);
                Debug.Assert(receiverTemp == null || receiver.IsDefaultValue());
            }
 
            EmitExpression(expression.WhenNotNull, used);
 
            // ===== DONE
            _builder.MarkLabel(doneLabel);
 
            if (cloneTemp != null)
            {
                FreeTemp(cloneTemp);
            }
 
            if (receiverTemp != null)
            {
                FreeTemp(receiverTemp);
            }
        }
 
        /// <summary>
        /// We must use a temp when there is a chance that evaluation of the call arguments
        /// could actually modify value of the reference type reciever. The call must use
        /// the original (unmodified) receiver.
        /// </summary>
        private sealed class IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
        {
            private readonly BoundLoweredConditionalAccess _conditionalAccess;
            private bool? _result;
 
            private IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker(BoundLoweredConditionalAccess conditionalAccess)
            {
                _conditionalAccess = conditionalAccess;
            }
 
            public static bool Analyze(BoundLoweredConditionalAccess conditionalAccess)
            {
                var walker = new IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker(conditionalAccess);
                walker.Visit(conditionalAccess.WhenNotNull);
                Debug.Assert(walker._result.HasValue);
                return walker._result.GetValueOrDefault();
            }
 
            public override BoundNode Visit(BoundNode node)
            {
                if (_result.HasValue)
                {
                    return null;
                }
 
                return base.Visit(node);
            }
 
            protected override void VisitReceiver(BoundCall node)
            {
                if (node.ReceiverOpt is BoundConditionalReceiver { Id: var id } && id == _conditionalAccess.Id)
                {
                    Debug.Assert(!_result.HasValue);
                    _result = !IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(node.Arguments);
                }
            }
 
            public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
            {
                if (node.Id == _conditionalAccess.Id)
                {
                    Debug.Assert(!_result.HasValue);
                    _result = false;
                    return null;
                }
 
                return base.VisitConditionalReceiver(node);
            }
        }
 
        private void EmitConditionalReceiver(BoundConditionalReceiver expression, bool used)
        {
            Debug.Assert(!expression.Type.IsValueType);
 
            if (!expression.Type.IsReferenceType)
            {
                EmitLoadIndirect(expression.Type, expression.Syntax);
            }
 
            EmitPopIfUnused(used);
        }
 
        private void EmitRefValueOperator(BoundRefValueOperator expression, bool used)
        {
            EmitRefValueAddress(expression);
            EmitLoadIndirect(expression.Type, expression.Syntax);
            EmitPopIfUnused(used);
        }
 
        private void EmitMakeRefOperator(BoundMakeRefOperator expression, bool used)
        {
            // push address of variable
            // mkrefany [Type] -- takes address off stack, puts TypedReference on stack
 
            var temp = EmitAddress(expression.Operand, AddressKind.Writeable);
            Debug.Assert(temp == null, "makeref should not create temps");
 
            _builder.EmitOpCode(ILOpCode.Mkrefany);
            EmitSymbolToken(expression.Operand.Type, expression.Operand.Syntax);
            EmitPopIfUnused(used);
        }
 
        private void EmitRefTypeOperator(BoundRefTypeOperator expression, bool used)
        {
            // push TypedReference
            // refanytype -- takes TypedReference off stack, puts token on stack
            // call GetTypeFromHandle -- takes token off stack, puts Type on stack
 
            EmitExpression(expression.Operand, true);
            _builder.EmitOpCode(ILOpCode.Refanytype);
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
            var getTypeMethod = expression.GetTypeFromHandle;
            Debug.Assert((object)getTypeMethod != null);
            EmitSymbolToken(getTypeMethod, expression.Syntax, null);
            EmitPopIfUnused(used);
        }
 
        private void EmitArgList(bool used)
        {
            _builder.EmitOpCode(ILOpCode.Arglist);
            EmitPopIfUnused(used);
        }
 
        private void EmitArgListOperator(BoundArgListOperator expression)
        {
            for (int i = 0; i < expression.Arguments.Length; i++)
            {
                BoundExpression argument = expression.Arguments[i];
                RefKind refKind = expression.ArgumentRefKindsOpt.IsDefaultOrEmpty ? RefKind.None : expression.ArgumentRefKindsOpt[i];
                EmitArgument(argument, refKind);
            }
        }
 
        private void EmitArgument(BoundExpression argument, RefKind refKind)
        {
            switch (refKind)
            {
                case RefKind.None:
                    EmitExpression(argument, true);
                    break;
 
                case RefKind.In:
                    var temp = EmitAddress(argument, AddressKind.ReadOnly);
                    AddExpressionTemp(temp);
                    break;
 
                default:
                    Debug.Assert(refKind is RefKind.Ref or RefKind.Out or RefKindExtensions.StrictIn);
                    // NOTE: passing "ReadOnlyStrict" here. 
                    //       we should not get an address of a copy if at all possible
                    var unexpectedTemp = EmitAddress(argument, refKind == RefKindExtensions.StrictIn ? AddressKind.ReadOnlyStrict : AddressKind.Writeable);
                    if (unexpectedTemp != null)
                    {
                        // interestingly enough "ref dynamic" sometimes is passed via a clone
                        // receiver of a ref field can be cloned too
                        Debug.Assert(argument.Type.IsDynamic() || argument is BoundFieldAccess { FieldSymbol.RefKind: not RefKind.None }, "passing args byref should not clone them into temps");
                        AddExpressionTemp(unexpectedTemp);
                    }
 
                    break;
            }
        }
 
        private void EmitAddressOfExpression(BoundAddressOfOperator expression, bool used)
        {
            // NOTE: passing "ReadOnlyStrict" here. 
            //       we should not get an address of a copy if at all possible
            var temp = EmitAddress(expression.Operand, AddressKind.ReadOnlyStrict);
            Debug.Assert(temp == null, "If the operand is addressable, then a temp shouldn't be required.");
 
            if (used && !expression.IsManaged)
            {
                // When computing an address to be used to initialize a fixed-statement variable, we have to be careful
                // not to convert the managed reference to an unmanaged pointer before storing it.  Otherwise the GC might
                // come along and move memory around, invalidating the pointer before it is pinned by being stored in
                // the fixed variable.  But elsewhere in the code we do use a conv.u instruction to convert the managed
                // reference to the underlying type for unmanaged pointers, which is the type "unsigned int" (see CLI
                // standard, Partition I section 12.1.1.1).
                _builder.EmitOpCode(ILOpCode.Conv_u);
            }
 
            EmitPopIfUnused(used);
        }
 
        private void EmitPointerIndirectionOperator(BoundPointerIndirectionOperator expression, bool used)
        {
            EmitExpression(expression.Operand, used: true);
            if (!expression.RefersToLocation)
            {
                EmitLoadIndirect(expression.Type, expression.Syntax);
            }
 
            EmitPopIfUnused(used);
        }
 
        private void EmitDupExpression(BoundDup expression, bool used)
        {
            if (expression.RefKind == RefKind.None)
            {
                // unused dup is noop
                if (used)
                {
                    _builder.EmitOpCode(ILOpCode.Dup);
                }
            }
            else
            {
                _builder.EmitOpCode(ILOpCode.Dup);
 
                // must read in case if it is a null ref
                EmitLoadIndirect(expression.Type, expression.Syntax);
                EmitPopIfUnused(used);
            }
        }
 
        private void EmitDelegateCreationExpression(BoundDelegateCreationExpression expression, bool used)
        {
            var mg = expression.Argument as BoundMethodGroup;
            var receiver = mg != null ? mg.ReceiverOpt : expression.Argument;
            var meth = expression.MethodOpt ?? receiver.Type.DelegateInvokeMethod();
            Debug.Assert((object)meth != null);
            EmitDelegateCreation(expression, receiver, expression.IsExtensionMethod, meth, expression.Type, used);
        }
 
        private void EmitThisReferenceExpression(BoundThisReference thisRef)
        {
            var thisType = thisRef.Type;
            Debug.Assert(thisType.TypeKind != TypeKind.TypeParameter);
 
            _builder.EmitOpCode(ILOpCode.Ldarg_0);
            if (thisType.IsValueType)
            {
                EmitLoadIndirect(thisType, thisRef.Syntax);
            }
        }
 
        private void EmitPseudoVariableValue(BoundPseudoVariable expression, bool used)
        {
            EmitExpression(expression.EmitExpressions.GetValue(expression, _diagnostics.DiagnosticBag), used);
        }
 
        private void EmitSequencePointExpression(BoundSequencePointExpression node, bool used)
        {
            EmitSequencePoint(node);
 
            // used is true to ensure that something is emitted
            EmitExpression(node.Expression, used: true);
            EmitPopIfUnused(used);
        }
 
        private void EmitSequencePoint(BoundSequencePointExpression node)
        {
            var syntax = node.Syntax;
            if (_emitPdbSequencePoints)
            {
                if (syntax == null)
                {
                    EmitHiddenSequencePoint();
                }
                else
                {
                    EmitSequencePoint(syntax);
                }
            }
        }
 
        private void EmitSequenceExpression(BoundSequence sequence, bool used)
        {
            DefineLocals(sequence);
            EmitSideEffects(sequence);
 
            // CONSIDER:    LocalRewriter.RewriteNestedObjectOrCollectionInitializerExpression may create a bound sequence with an unused BoundTypeExpression as the value,
            // CONSIDER:    which must be ignored by codegen. See comments in RewriteNestedObjectOrCollectionInitializerExpression for details and an example.
            // CONSIDER:    We may want to instead consider making the Value field of BoundSequence node optional to allow a sequence with
            // CONSIDER:    only side effects and no value. Note that VB's BoundSequence node has an optional value field.
            // CONSIDER:    This will allow us to remove the below check before emitting the value.
 
            Debug.Assert(sequence.Value.Kind != BoundKind.TypeExpression || !used);
            if (sequence.Value.Kind != BoundKind.TypeExpression)
            {
                EmitExpression(sequence.Value, used);
            }
 
            // sequence is used as a value, can release all locals
            FreeLocals(sequence);
        }
 
        private void DefineLocals(BoundSequence sequence)
        {
            if (sequence.Locals.IsEmpty)
            {
                return;
            }
 
            _builder.OpenLocalScope();
 
            foreach (var local in sequence.Locals)
            {
                DefineLocal(local, sequence.Syntax);
            }
        }
 
        private void FreeLocals(BoundSequence sequence)
        {
            if (sequence.Locals.IsEmpty)
            {
                return;
            }
 
            _builder.CloseLocalScope();
 
            foreach (var local in sequence.Locals)
            {
                FreeLocal(local);
            }
        }
 
        /// <summary>
        /// Defines sequence locals and record them so that they could be retained for the duration of the encompassing expression
        /// Use this when taking a reference of the sequence, which can indirectly refer to any of its locals.
        /// </summary>
        private void DefineAndRecordLocals(BoundSequence sequence)
        {
            if (sequence.Locals.IsEmpty)
            {
                return;
            }
 
            _builder.OpenLocalScope();
 
            foreach (var local in sequence.Locals)
            {
                var seqLocal = DefineLocal(local, sequence.Syntax);
                AddExpressionTemp(seqLocal);
            }
        }
 
        /// <summary>
        /// Closes the visibility/debug scopes for the sequence locals, but keep the local slots from reuse
        /// for the duration of the encompassing expression.
        /// Use this paired with DefineAndRecordLocals when taking a reference of the sequence, which can indirectly refer to any of its locals.
        /// </summary>
        private void CloseScopeAndKeepLocals(BoundSequence sequence)
        {
            if (sequence.Locals.IsEmpty)
            {
                return;
            }
 
            _builder.CloseLocalScope();
        }
 
        private void EmitSideEffects(BoundSequence sequence)
        {
            var sideEffects = sequence.SideEffects;
            if (!sideEffects.IsDefaultOrEmpty)
            {
                foreach (var se in sideEffects)
                {
                    EmitExpression(se, false);
                }
            }
        }
 
        private void EmitArguments(ImmutableArray<BoundExpression> arguments, ImmutableArray<ParameterSymbol> parameters, ImmutableArray<RefKind> argRefKindsOpt)
        {
            // We might have an extra argument for the __arglist() of a varargs method.
            Debug.Assert(arguments.Length == parameters.Length ||
                (arguments.Length == parameters.Length + 1 && arguments is [.., BoundArgListOperator]), "argument count must match parameter count");
            Debug.Assert(parameters.All(p => p.RefKind == RefKind.None) || !argRefKindsOpt.IsDefault, "there are nontrivial parameters, so we must have argRefKinds");
            // We might have a missing ref kind for the __arglist() of a varargs method.
            Debug.Assert(argRefKindsOpt.IsDefault || argRefKindsOpt.Length == arguments.Length ||
                (argRefKindsOpt.Length == arguments.Length - 1 && arguments is [.., BoundArgListOperator]), "if we have argRefKinds, we should have one for each argument");
 
            for (int i = 0; i < arguments.Length; i++)
            {
                RefKind argRefKind = GetArgumentRefKind(arguments, parameters, argRefKindsOpt, i);
                EmitArgument(arguments[i], argRefKind);
            }
        }
 
        /// <summary>
        /// Computes the desired refkind of the argument.
        /// Considers all the cases - where ref kinds are explicit, omitted, vararg cases.
        /// </summary>
        internal static RefKind GetArgumentRefKind(ImmutableArray<BoundExpression> arguments, ImmutableArray<ParameterSymbol> parameters, ImmutableArray<RefKind> argRefKindsOpt, int i)
        {
            RefKind argRefKind;
            if (i < parameters.Length)
            {
                if (!argRefKindsOpt.IsDefault && i < argRefKindsOpt.Length)
                {
                    // if we have an explicit refKind for the given argument, use that
                    argRefKind = argRefKindsOpt[i];
 
                    Debug.Assert(argRefKind == parameters[i].RefKind ||
                            parameters[i].RefKind switch
                            {
                                RefKind.In => argRefKind == RefKindExtensions.StrictIn,
                                RefKind.RefReadOnlyParameter => argRefKind is RefKind.In or RefKindExtensions.StrictIn,
                                _ => false,
                            },
                            "in Emit the argument RefKind must be compatible with the corresponding parameter");
                }
                else
                {
                    Debug.Assert(parameters[i].RefKind != RefKind.RefReadOnlyParameter,
                        "LocalRewriter.GetEffectiveArgumentRefKinds should ensure 'ref readonly' parameters get an entry in 'argRefKindsOpt'.");
 
                    // otherwise fallback to the refKind of the parameter
                    argRefKind = parameters[i].RefKind switch
                    {
                        RefKind.RefReadOnlyParameter => RefKind.In, // should not happen, asserted above
                        var refKind => refKind
                    };
                }
            }
            else
            {
                // vararg case
                Debug.Assert(arguments[i].Kind == BoundKind.ArgListOperator);
                argRefKind = RefKind.None;
            }
 
            return argRefKind;
        }
 
        private void EmitArrayElementLoad(BoundArrayAccess arrayAccess, bool used)
        {
            EmitExpression(arrayAccess.Expression, used: true);
            EmitArrayIndices(arrayAccess.Indices);
 
            if (((ArrayTypeSymbol)arrayAccess.Expression.Type).IsSZArray)
            {
                var elementType = arrayAccess.Type;
                if (elementType.IsEnumType())
                {
                    //underlying primitives do not need type tokens.
                    elementType = ((NamedTypeSymbol)elementType).EnumUnderlyingType;
                }
 
                switch (elementType.PrimitiveTypeCode)
                {
                    case Microsoft.Cci.PrimitiveTypeCode.Int8:
                        _builder.EmitOpCode(ILOpCode.Ldelem_i1);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.Boolean:
                    case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                        _builder.EmitOpCode(ILOpCode.Ldelem_u1);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.Int16:
                        _builder.EmitOpCode(ILOpCode.Ldelem_i2);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.Char:
                    case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                        _builder.EmitOpCode(ILOpCode.Ldelem_u2);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.Int32:
                        _builder.EmitOpCode(ILOpCode.Ldelem_i4);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                        _builder.EmitOpCode(ILOpCode.Ldelem_u4);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.Int64:
                    case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                        _builder.EmitOpCode(ILOpCode.Ldelem_i8);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                    case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                    case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                    case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                        _builder.EmitOpCode(ILOpCode.Ldelem_i);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.Float32:
                        _builder.EmitOpCode(ILOpCode.Ldelem_r4);
                        break;
 
                    case Microsoft.Cci.PrimitiveTypeCode.Float64:
                        _builder.EmitOpCode(ILOpCode.Ldelem_r8);
                        break;
 
                    default:
                        if (elementType.IsVerifierReference())
                        {
                            _builder.EmitOpCode(ILOpCode.Ldelem_ref);
                        }
                        else
                        {
                            if (used)
                            {
                                _builder.EmitOpCode(ILOpCode.Ldelem);
                            }
                            else
                            {
                                // no need to read whole element of nontrivial type/size here
                                // just take a reference to an element for array access side-effects 
                                if (elementType.TypeKind == TypeKind.TypeParameter)
                                {
                                    _builder.EmitOpCode(ILOpCode.Readonly);
                                }
 
                                _builder.EmitOpCode(ILOpCode.Ldelema);
                            }
 
                            EmitSymbolToken(elementType, arrayAccess.Syntax);
                        }
                        break;
                }
            }
            else
            {
                _builder.EmitArrayElementLoad(_module.Translate((ArrayTypeSymbol)arrayAccess.Expression.Type), arrayAccess.Expression.Syntax, _diagnostics.DiagnosticBag);
            }
 
            EmitPopIfUnused(used);
        }
 
        private void EmitArrayElementRefLoad(BoundRefArrayAccess refArrayAccess, bool used)
        {
            if (used)
            {
                throw ExceptionUtilities.Unreachable();
            }
 
            EmitArrayElementAddress(refArrayAccess.ArrayAccess, AddressKind.Writeable);
            _builder.EmitOpCode(ILOpCode.Pop);
        }
 
        private void EmitFieldLoad(BoundFieldAccess fieldAccess, bool used)
        {
            var field = fieldAccess.FieldSymbol;
 
            if (!used)
            {
                // fetching unused captured frame is a no-op (like reading "this")
                if (field.IsCapturedFrame)
                {
                    return;
                }
 
                // Accessing a volatile field is sideeffecting because it establishes an acquire fence.
                // Otherwise, accessing an unused instance field on a struct is a noop. Just emit an unused receiver.
                if (!field.IsVolatile && !field.IsStatic && fieldAccess.ReceiverOpt.Type.IsVerifierValue() && field.RefKind == RefKind.None)
                {
                    EmitExpression(fieldAccess.ReceiverOpt, used: false);
                    return;
                }
            }
 
            Debug.Assert(!field.IsConst || field.ContainingType.SpecialType == SpecialType.System_Decimal,
                "rewriter should lower constant fields into constant expressions");
 
            EmitFieldLoadNoIndirection(fieldAccess, used);
 
            if (field.RefKind != RefKind.None)
            {
                EmitLoadIndirect(field.Type, fieldAccess.Syntax);
            }
 
            EmitPopIfUnused(used);
        }
 
        private void EmitFieldLoadNoIndirection(BoundFieldAccess fieldAccess, bool used)
        {
            var field = fieldAccess.FieldSymbol;
 
            // static field access is sideeffecting since it guarantees that ..ctor has run.
            // we emit static accesses even if unused.
            if (field.IsStatic)
            {
                if (field.IsVolatile)
                {
                    _builder.EmitOpCode(ILOpCode.Volatile);
                }
                _builder.EmitOpCode(ILOpCode.Ldsfld);
                EmitSymbolToken(field, fieldAccess.Syntax);
            }
            else
            {
                var receiver = fieldAccess.ReceiverOpt;
                TypeSymbol fieldType = field.Type;
                if (fieldType.IsValueType && (object)fieldType == (object)receiver.Type)
                {
                    //Handle emitting a field of a self-containing struct (only possible in mscorlib)
                    //since "val.field" is the same as val, we only need to emit val.
                    EmitExpression(receiver, used);
                }
                else
                {
                    var temp = EmitFieldLoadReceiver(receiver);
                    if (temp != null)
                    {
                        Debug.Assert(FieldLoadMustUseRef(receiver), "only clr-ambiguous structs use temps here");
                        FreeTemp(temp);
                    }
 
                    if (field.IsVolatile)
                    {
                        _builder.EmitOpCode(ILOpCode.Volatile);
                    }
 
                    _builder.EmitOpCode(ILOpCode.Ldfld);
                    EmitSymbolToken(field, fieldAccess.Syntax);
                }
            }
        }
 
        private LocalDefinition EmitFieldLoadReceiver(BoundExpression receiver)
        {
            // ldfld can work with structs directly or with their addresses
            // accessing via address is typically same or cheaper, but not for homeless values, obviously
            // there are also cases where we must emit receiver as a reference
            if (FieldLoadMustUseRef(receiver) || FieldLoadPrefersRef(receiver))
            {
                return EmitFieldLoadReceiverAddress(receiver) ? null : EmitReceiverRef(receiver, AddressKind.ReadOnly);
            }
 
            EmitExpression(receiver, true);
            return null;
        }
 
        // In special case of loading the sequence of field accesses we can perform all the 
        // necessary field loads using the following IL: 
        //
        //      <expr>.a.b...y.z
        //          |
        //          V
        //      Unbox -or- Load.Ref (<expr>)
        //      Ldflda a
        //      Ldflda b
        //      ...
        //      Ldflda y
        //      Ldfld z
        //
        // Returns 'true' if the receiver was actually emitted this way
        private bool EmitFieldLoadReceiverAddress(BoundExpression receiver)
        {
            if (receiver == null || !receiver.Type.IsValueType)
            {
                return false;
            }
            else if (receiver.Kind == BoundKind.Conversion)
            {
                var conversion = (BoundConversion)receiver;
                if (conversion.ConversionKind == ConversionKind.Unboxing)
                {
                    EmitExpression(conversion.Operand, true);
                    _builder.EmitOpCode(ILOpCode.Unbox);
                    EmitSymbolToken(receiver.Type, receiver.Syntax);
                    return true;
                }
            }
            else if (receiver.Kind == BoundKind.FieldAccess)
            {
                var fieldAccess = (BoundFieldAccess)receiver;
                var field = fieldAccess.FieldSymbol;
 
                if (!field.IsStatic && EmitFieldLoadReceiverAddress(fieldAccess.ReceiverOpt))
                {
                    Debug.Assert(!field.IsVolatile, "volatile valuetype fields are unexpected");
 
                    _builder.EmitOpCode(ILOpCode.Ldflda);
                    EmitSymbolToken(field, fieldAccess.Syntax);
                    return true;
                }
            }
 
            return false;
        }
 
        // ldfld can work with structs directly or with their addresses.
        // In some cases it results in same native code emitted, but in some cases JIT pushes values for real
        // resulting in much worse code (on x64 in particular).
        // So, we will always prefer references here except when receiver is a struct, non-ref local, or parameter. 
        private bool FieldLoadPrefersRef(BoundExpression receiver)
        {
            // only fields of structs can be accessed via value
            if (!receiver.Type.IsVerifierValue())
            {
                return true;
            }
 
            // can unbox directly into a ref.
            if (receiver.Kind == BoundKind.Conversion && ((BoundConversion)receiver).ConversionKind == ConversionKind.Unboxing)
            {
                return true;
            }
 
            // can we take address at all?
            if (!HasHome(receiver, AddressKind.ReadOnly))
            {
                return false;
            }
 
            switch (receiver.Kind)
            {
                case BoundKind.Parameter:
                    // prefer ldarg over ldarga
                    return ((BoundParameter)receiver).ParameterSymbol.RefKind != RefKind.None;
 
                case BoundKind.Local:
                    // prefer ldloc over ldloca
                    return ((BoundLocal)receiver).LocalSymbol.RefKind != RefKind.None;
 
                case BoundKind.Sequence:
                    return FieldLoadPrefersRef(((BoundSequence)receiver).Value);
 
                case BoundKind.FieldAccess:
                    var fieldAccess = (BoundFieldAccess)receiver;
                    var field = fieldAccess.FieldSymbol;
 
                    if (field.IsStatic || field.RefKind != RefKind.None)
                    {
                        return true;
                    }
 
                    if (DiagnosticsPass.IsNonAgileFieldAccess(fieldAccess, _module.Compilation))
                    {
                        return false;
                    }
 
                    return FieldLoadPrefersRef(fieldAccess.ReceiverOpt);
            }
 
            return true;
        }
 
        internal static bool FieldLoadMustUseRef(BoundExpression expr)
        {
            var type = expr.Type;
 
            // type parameter values must be boxed to get access to fields
            if (type.IsTypeParameter())
            {
                return true;
            }
 
            // From   Dev12/symbol.cpp
            //  
            //  // Used by ILGEN to determine if the type of this AggregateSymbol is one that the CLR
            //  // will consider ambiguous to an unmanaged pointer when it is on the stack (see VSW #396011)
            //  bool AggregateSymbol::IsCLRAmbigStruct()
            //      . . .
            switch (type.SpecialType)
            {
                // case PT_BYTE:
                case SpecialType.System_Byte:
                // case PT_SHORT:
                case SpecialType.System_Int16:
                // case PT_INT:
                case SpecialType.System_Int32:
                // case PT_LONG:
                case SpecialType.System_Int64:
                // case PT_CHAR:
                case SpecialType.System_Char:
                // case PT_BOOL:
                case SpecialType.System_Boolean:
                // case PT_SBYTE:
                case SpecialType.System_SByte:
                // case PT_USHORT:
                case SpecialType.System_UInt16:
                // case PT_UINT:
                case SpecialType.System_UInt32:
                // case PT_ULONG:
                case SpecialType.System_UInt64:
                // case PT_INTPTR:
                case SpecialType.System_IntPtr:
                // case PT_UINTPTR:
                case SpecialType.System_UIntPtr:
                // case PT_FLOAT:
                case SpecialType.System_Single:
                // case PT_DOUBLE:
                case SpecialType.System_Double:
                // case PT_TYPEHANDLE:
                case SpecialType.System_RuntimeTypeHandle:
                // case PT_FIELDHANDLE:
                case SpecialType.System_RuntimeFieldHandle:
                // case PT_METHODHANDLE:
                case SpecialType.System_RuntimeMethodHandle:
                //case PT_ARGUMENTHANDLE:
                case SpecialType.System_RuntimeArgumentHandle:
                    return true;
            }
 
            // this is for value__
            // I do not know how to hit this, since value__ is not bindable in C#, but Dev12 has code to handle this
            return type.IsEnumType();
        }
 
        private static int ParameterSlot(BoundParameter parameter)
        {
            var sym = parameter.ParameterSymbol;
            int slot = sym.Ordinal;
            if (!sym.ContainingSymbol.IsStatic)
            {
                slot++;  // skip "this"
            }
            return slot;
        }
 
        private void EmitLocalLoad(BoundLocal local, bool used)
        {
            bool isRefLocal = local.LocalSymbol.RefKind != RefKind.None;
            if (IsStackLocal(local.LocalSymbol))
            {
                // local must be already on the stack
                EmitPopIfUnused(used || isRefLocal);
            }
            else
            {
                if (used || isRefLocal)
                {
                    LocalDefinition definition = GetLocal(local);
                    _builder.EmitLocalLoad(definition);
                }
                else
                {
                    // do nothing. Unused local load has no side-effects.
                    return;
                }
            }
 
            if (isRefLocal)
            {
                EmitLoadIndirect(local.LocalSymbol.Type, local.Syntax);
                EmitPopIfUnused(used);
            }
        }
 
        private void EmitParameterLoad(BoundParameter parameter)
        {
            int slot = ParameterSlot(parameter);
            _builder.EmitLoadArgumentOpcode(slot);
 
            if (parameter.ParameterSymbol.RefKind != RefKind.None)
            {
                var parameterType = parameter.ParameterSymbol.Type;
                EmitLoadIndirect(parameterType, parameter.Syntax);
            }
        }
 
        private void EmitLoadIndirect(TypeSymbol type, SyntaxNode syntaxNode)
        {
            if (type.IsEnumType())
            {
                //underlying primitives do not need type tokens.
                type = ((NamedTypeSymbol)type).EnumUnderlyingType;
            }
 
            switch (type.PrimitiveTypeCode)
            {
                case Microsoft.Cci.PrimitiveTypeCode.Int8:
                    _builder.EmitOpCode(ILOpCode.Ldind_i1);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Boolean:
                case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                    _builder.EmitOpCode(ILOpCode.Ldind_u1);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Int16:
                    _builder.EmitOpCode(ILOpCode.Ldind_i2);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Char:
                case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                    _builder.EmitOpCode(ILOpCode.Ldind_u2);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Int32:
                    _builder.EmitOpCode(ILOpCode.Ldind_i4);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                    _builder.EmitOpCode(ILOpCode.Ldind_u4);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Int64:
                case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                    _builder.EmitOpCode(ILOpCode.Ldind_i8);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                    _builder.EmitOpCode(ILOpCode.Ldind_i);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Float32:
                    _builder.EmitOpCode(ILOpCode.Ldind_r4);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Float64:
                    _builder.EmitOpCode(ILOpCode.Ldind_r8);
                    break;
 
                default:
                    if (type.IsVerifierReference())
                    {
                        _builder.EmitOpCode(ILOpCode.Ldind_ref);
                    }
                    else
                    {
                        _builder.EmitOpCode(ILOpCode.Ldobj);
                        EmitSymbolToken(type, syntaxNode);
                    }
                    break;
            }
        }
 
        /// <summary>
        /// Used to decide if we need to emit call or callvirt.
        /// It basically checks if the receiver expression cannot be null, but it is not 100% precise. 
        /// There are cases where it really can be null, but we do not care.
        /// </summary>
        private bool CanUseCallOnRefTypeReceiver(BoundExpression receiver)
        {
            // It seems none of the ways that could produce a receiver typed as a type param 
            // can guarantee that it is not null.
            if (receiver.Type.IsTypeParameter())
            {
                return false;
            }
 
            Debug.Assert(receiver.Type.IsVerifierReference(), "this is not a reference");
            Debug.Assert(receiver.Kind != BoundKind.BaseReference, "base should always use call");
 
            var constVal = receiver.ConstantValueOpt;
            if (constVal != null)
            {
                // only when this is a constant Null, we need a callvirt
                return !constVal.IsNull;
            }
 
            switch (receiver.Kind)
            {
                case BoundKind.ArrayCreation:
                    return true;
 
                case BoundKind.ObjectCreationExpression:
                    // NOTE: there are cases involving ProxyAttribute
                    // where newobj may produce null
                    return true;
 
                case BoundKind.Conversion:
                    var conversion = (BoundConversion)receiver;
 
                    switch (conversion.ConversionKind)
                    {
                        case ConversionKind.Boxing:
                            // NOTE: boxing can produce null for Nullable, but any call through that
                            // will result in null reference exceptions anyways.
                            return true;
 
                        case ConversionKind.MethodGroup:
                        case ConversionKind.AnonymousFunction:
                            return true;
 
                        case ConversionKind.ExplicitReference:
                        case ConversionKind.ImplicitReference:
                            return CanUseCallOnRefTypeReceiver(conversion.Operand);
                    }
                    break;
 
                case BoundKind.ThisReference:
                    // NOTE: these actually can be null if called from a different language
                    // however, we assume it is responsibility of the caller to nullcheck "this"
                    // if we already have access to "this", we must be in a member and should 
                    // not redo the check
                    return true;
 
                case BoundKind.FieldAccess:
                    // same reason as for "ThisReference"
                    return ((BoundFieldAccess)receiver).FieldSymbol.IsCapturedFrame;
 
                case BoundKind.Local:
                    // same reason as for "ThisReference"
                    return ((BoundLocal)receiver).LocalSymbol.SynthesizedKind == SynthesizedLocalKind.FrameCache;
 
                case BoundKind.DelegateCreationExpression:
                    return true;
 
                case BoundKind.Sequence:
                    var seqValue = ((BoundSequence)(receiver)).Value;
                    return CanUseCallOnRefTypeReceiver(seqValue);
 
                case BoundKind.AssignmentOperator:
                    var rhs = ((BoundAssignmentOperator)receiver).Right;
                    return CanUseCallOnRefTypeReceiver(rhs);
 
                case BoundKind.TypeOfOperator:
                    return true;
 
                case BoundKind.ConditionalReceiver:
                    return true;
 
                    //TODO: there could be more cases where we can be sure that receiver is not a null.
            }
 
            return false;
        }
 
        /// <summary>
        /// checks if receiver is effectively ldarg.0
        /// </summary>
        private bool IsThisReceiver(BoundExpression receiver)
        {
            switch (receiver.Kind)
            {
                case BoundKind.ThisReference:
                    return true;
 
                case BoundKind.Sequence:
                    var seqValue = ((BoundSequence)(receiver)).Value;
                    return IsThisReceiver(seqValue);
            }
 
            return false;
        }
 
        private enum CallKind
        {
            Call,
            CallVirt,
            ConstrainedCallVirt,
        }
 
        private void EmitCallExpression(BoundCall call, UseKind useKind)
        {
            if (call.Method.IsDefaultValueTypeConstructor())
            {
                EmitDefaultValueTypeConstructorCallExpression(call);
            }
            else if (!call.Method.RequiresInstanceReceiver)
            {
                EmitStaticCallExpression(call, useKind);
            }
            else
            {
                EmitInstanceCallExpression(call, useKind);
            }
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void EmitDefaultValueTypeConstructorCallExpression(BoundCall call)
        {
            var method = call.Method;
            var receiver = call.ReceiverOpt;
 
            // Calls to the default struct constructor are emitted as initobj, rather than call.
            // NOTE: constructor invocations are represented as BoundObjectCreationExpressions,
            // rather than BoundCalls.  This is why we can be confident that if we see a call to a
            // constructor, it has this very specific form.
            Debug.Assert(method.IsImplicitlyDeclared);
            Debug.Assert(TypeSymbol.Equals(method.ContainingType, receiver.Type, TypeCompareKind.ConsiderEverything2));
            Debug.Assert(receiver.Kind == BoundKind.ThisReference);
 
            LocalDefinition tempOpt = EmitReceiverRef(receiver, AddressKind.Writeable);
            _builder.EmitOpCode(ILOpCode.Initobj);    //  initobj  <MyStruct>
            EmitSymbolToken(method.ContainingType, call.Syntax);
            FreeOptTemp(tempOpt);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void EmitStaticCallExpression(BoundCall call, UseKind useKind)
        {
            var method = call.Method;
            var receiver = call.ReceiverOpt;
            var arguments = call.Arguments;
 
            Debug.Assert(method.IsStatic);
 
            EmitArguments(arguments, method.Parameters, call.ArgumentRefKindsOpt);
            int stackBehavior = GetCallStackBehavior(method, arguments);
 
            if (method.IsAbstract || method.IsVirtual)
            {
                if (receiver is not BoundTypeExpression { Type: { TypeKind: TypeKind.TypeParameter } })
                {
                    throw ExceptionUtilities.Unreachable();
                }
 
                _builder.EmitOpCode(ILOpCode.Constrained);
                EmitSymbolToken(receiver.Type, receiver.Syntax);
            }
 
            _builder.EmitOpCode(ILOpCode.Call, stackBehavior);
 
            EmitSymbolToken(method, call.Syntax,
                            method.IsVararg ? (BoundArgListOperator)arguments[arguments.Length - 1] : null);
 
            EmitCallCleanup(call.Syntax, useKind, method);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void EmitInstanceCallExpression(BoundCall call, UseKind useKind)
        {
            CallKind callKind;
            AddressKind? addressKind;
            bool box;
            LocalDefinition tempOpt;
 
            if (receiverIsInstanceCall(call, out BoundCall nested))
            {
                var calls = ArrayBuilder<BoundCall>.GetInstance();
 
                calls.Push(call);
 
                call = nested;
                while (receiverIsInstanceCall(call, out nested))
                {
                    calls.Push(call);
                    call = nested;
                }
 
                callKind = determineEmitReceiverStrategy(call, out addressKind, out box);
                emitReceiver(call, callKind, addressKind, box, out tempOpt);
 
                while (calls.Count != 0)
                {
                    var parentCall = calls.Pop();
                    CallKind parentCallKind = determineEmitReceiverStrategy(parentCall, out addressKind, out box);
 
                    var parentCallReceiverType = call.Type;
                    UseKind receiverUseKind;
                    if (addressKind is null)
                    {
                        receiverUseKind = UseKind.UsedAsValue;
                    }
                    else if (BoxNonVerifierReferenceReceiver(parentCallReceiverType, addressKind.GetValueOrDefault()))
                    {
                        Debug.Assert(!box);
                        // This code path is covered by IL comparison in Microsoft.CodeAnalysis.CSharp.UnitTests.BreakingChanges.NestedCollectionInitializerOnGenericProperty​ unit-test
 
                        // EmitReceiverRef pushes boxed value rather than an address in this case 
                        receiverUseKind = UseKind.UsedAsValue;
                        box = true;
 
                        // not subject to emitGenericReceiverCloneIfNecessary effect
                        Debug.Assert(addressKind.GetValueOrDefault() != AddressKind.Constrained);
                        Debug.Assert(!parentCallReceiverType.IsVerifierValue());
                        Debug.Assert(parentCallKind != CallKind.ConstrainedCallVirt);
                    }
                    else
                    {
                        Debug.Assert(!box);
                        Debug.Assert(!parentCallReceiverType.IsVerifierReference());
 
                        var methodRefKind = call.Method.RefKind;
                        if (UseCallResultAsAddress(call, addressKind.GetValueOrDefault()))
                        {
                            // This code path is covered by IL comparison in
                            // - Microsoft.CodeAnalysis.CSharp.UnitTests.RefReturnTests.RefReturnConditionalAccess01​, and
                            // - Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGenRefReadOnlyReturnTests.RefReadOnlyMethod_PassThrough_ChainNoCopying
                            // unit tests
                            receiverUseKind = UseKind.UsedAsAddress;
                        }
                        else
                        {
                            // This code path is covered by IL comparison in Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen.CodeGenShortCircuitOperatorTests.TestConditionalMemberAccessUnused2a unit-test
 
                            // EmitAddress pushes a reference to a temp with a value in this case
                            receiverUseKind = UseKind.UsedAsValue;
                        }
                    }
 
                    emitArgumentsAndCallEpilogue(call, callKind, receiverUseKind);
                    FreeOptTemp(tempOpt);
                    tempOpt = null;
 
                    nested = call;
                    call = parentCall;
                    callKind = parentCallKind;
 
                    if (box)
                    {
                        Debug.Assert(receiverUseKind == UseKind.UsedAsValue);
 
                        // This code path is covered by IL comparison in
                        // - Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen.CodeGenTests.BoxingReceiver, and
                        // - Microsoft.CodeAnalysis.CSharp.UnitTests.BreakingChanges.NestedCollectionInitializerOnGenericProperty​
                        // unit-tests
                        EmitBox(parentCallReceiverType, nested.Syntax);
                    }
                    else if (addressKind is null)
                    {
                        Debug.Assert(receiverUseKind == UseKind.UsedAsValue);
                        // This code path is covered by IL comparison in Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen.CodeGenShortCircuitOperatorTests.TestConditionalMemberAccessUnused2a unit-test
                    }
                    else
                    {
                        Debug.Assert(!parentCallReceiverType.IsVerifierReference());
 
                        if (receiverUseKind != UseKind.UsedAsAddress)
                        {
                            Debug.Assert(receiverUseKind == UseKind.UsedAsValue);
                            Debug.Assert(!HasHome(nested, addressKind.GetValueOrDefault()));
 
                            // This code path is covered by IL comparison in Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen.CodeGenShortCircuitOperatorTests.TestConditionalMemberAccessUnused2a unit-test
 
                            // EmitAddress pushes a reference to a temp with a value in this case
                            tempOpt = this.AllocateTemp(parentCallReceiverType, nested.Syntax);
                            _builder.EmitLocalStore(tempOpt);
                            _builder.EmitLocalAddress(tempOpt);
                        }
                        else
                        {
                            // This code path is covered at least by IL comparison in
                            // - Microsoft.CodeAnalysis.CSharp.UnitTests.RefReturnTests.RefReturnConditionalAccess01​, and
                            // - Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGenRefReadOnlyReturnTests.RefReadOnlyMethod_PassThrough_ChainNoCopying
                            // unit tests
                        }
 
                        // Effect of this call is covered by IL comparison in Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen.CodeGenCallTests.ChainedCalls unit-test
                        emitGenericReceiverCloneIfNecessary(call, callKind, ref tempOpt);
                    }
                }
 
                calls.Free();
            }
            else
            {
                callKind = determineEmitReceiverStrategy(call, out addressKind, out box);
                emitReceiver(call, callKind, addressKind, box, out tempOpt);
            }
 
            emitArgumentsAndCallEpilogue(call, callKind, useKind);
            FreeOptTemp(tempOpt);
 
            return;
 
            [MethodImpl(MethodImplOptions.NoInlining)]
            CallKind determineEmitReceiverStrategy(BoundCall call, out AddressKind? addressKind, out bool box)
            {
                var method = call.Method;
                var receiver = call.ReceiverOpt;
                Debug.Assert(!method.IsStatic && !method.IsDefaultValueTypeConstructor() && method.RequiresInstanceReceiver);
 
                CallKind callKind;
 
                var receiverType = receiver.Type;
                box = false;
 
                if (receiverType.IsVerifierReference())
                {
                    addressKind = null;
 
                    // In some cases CanUseCallOnRefTypeReceiver returns true which means that 
                    // null check is unnecessary and we can use "call"
                    if (receiver.SuppressVirtualCalls ||
                        (!method.IsMetadataVirtual() && CanUseCallOnRefTypeReceiver(receiver)))
                    {
                        callKind = CallKind.Call;
                    }
                    else
                    {
                        callKind = CallKind.CallVirt;
                    }
                }
                else if (receiverType.IsVerifierValue())
                {
                    NamedTypeSymbol methodContainingType = method.ContainingType;
                    if (methodContainingType.IsVerifierValue())
                    {
                        // if method is defined in the struct itself it is assumed to be mutating, unless 
                        // it is a member of a readonly struct and is not a constructor
                        addressKind = IsReadOnlyCall(method, methodContainingType) ?
                                                                        AddressKind.ReadOnly :
                                                                        AddressKind.Writeable;
                        if (MayUseCallForStructMethod(method))
                        {
                            // NOTE: this should be either a method which overrides some abstract method or 
                            //       does not override anything (with few exceptions, see MayUseCallForStructMethod); 
                            //       otherwise we should not use direct 'call' and must use constrained call;
 
                            // calling a method defined in a value type
                            Debug.Assert(TypeSymbol.Equals(receiverType, methodContainingType, TypeCompareKind.ObliviousNullableModifierMatchesAny));
                            callKind = CallKind.Call;
                        }
                        else
                        {
                            callKind = CallKind.ConstrainedCallVirt;
                        }
                    }
                    else
                    {
                        // calling a method defined in a base class or interface.
 
                        // When calling a method that is virtual in metadata on a struct receiver, 
                        // we use a constrained virtual call. If possible, it will skip boxing.
                        if (method.IsMetadataVirtual())
                        {
                            addressKind = AddressKind.Writeable;
                            callKind = CallKind.ConstrainedCallVirt;
                        }
                        else
                        {
                            addressKind = null;
                            box = true;
                            callKind = CallKind.Call;
                        }
                    }
                }
                else
                {
                    // receiver is generic and method must come from the base or an interface or a generic constraint
                    // if the receiver is actually a value type it would need to be boxed.
                    // let .constrained sort this out. 
                    callKind = receiverType.IsReferenceType &&
                               (!IsRef(receiver) ||
                                (!ReceiverIsKnownToReferToTempIfReferenceType(receiver) && !IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(call.Arguments))) ?
                                CallKind.CallVirt :
                                CallKind.ConstrainedCallVirt;
 
                    addressKind = (callKind == CallKind.ConstrainedCallVirt) ? AddressKind.Constrained : AddressKind.Writeable;
                }
 
                Debug.Assert((callKind != CallKind.ConstrainedCallVirt) || (addressKind.GetValueOrDefault() == AddressKind.Constrained) || receiverType.IsVerifierValue());
 
                return callKind;
            }
 
            [MethodImpl(MethodImplOptions.NoInlining)]
            void emitReceiver(BoundCall call, CallKind callKind, AddressKind? addressKind, bool box, out LocalDefinition tempOpt)
            {
                var receiver = call.ReceiverOpt;
                var receiverType = receiver.Type;
                tempOpt = null;
 
                if (addressKind is null)
                {
                    EmitExpression(receiver, used: true);
 
                    if (box)
                    {
                        EmitBox(receiverType, receiver.Syntax);
                    }
                }
                else
                {
                    Debug.Assert(!box);
                    Debug.Assert(!receiverType.IsVerifierReference());
                    tempOpt = EmitReceiverRef(receiver, addressKind.GetValueOrDefault());
 
                    emitGenericReceiverCloneIfNecessary(call, callKind, ref tempOpt);
                }
            }
 
            [MethodImpl(MethodImplOptions.NoInlining)]
            void emitArgumentsAndCallEpilogue(BoundCall call, CallKind callKind, UseKind useKind)
            {
                var method = call.Method;
                var receiver = call.ReceiverOpt;
 
                // When emitting a callvirt to a virtual method we always emit the method info of the
                // method that first declared the virtual method, not the method info of an
                // overriding method. It would be a subtle breaking change to change that rule;
                // see bug 6156 for details.
 
                MethodSymbol actualMethodTargetedByTheCall = method;
                if (method.IsOverride && callKind != CallKind.Call)
                {
                    actualMethodTargetedByTheCall = method.GetConstructedLeastOverriddenMethod(_method.ContainingType, requireSameReturnType: true);
                }
 
                if (callKind == CallKind.ConstrainedCallVirt && actualMethodTargetedByTheCall.ContainingType.IsValueType)
                {
                    // special case for overridden methods like ToString(...) called on
                    // value types: if the original method used in emit cannot use callvirt in this
                    // case, change it to Call.
                    callKind = CallKind.Call;
                }
 
                // Devirtualizing of calls to effectively sealed methods.
                if (callKind == CallKind.CallVirt)
                {
                    // NOTE: we check that we call method in same module just to be sure
                    // that it cannot be recompiled as not final and make our call not verifiable. 
                    // such change by adversarial user would arguably be a compat break, but better be safe...
                    // In reality we would typically have one method calling another method in the same class (one GetEnumerator calling another).
                    // Other scenarios are uncommon since base class cannot be sealed and 
                    // referring to a derived type in a different module is not an easy thing to do.
                    if (IsThisReceiver(receiver) && actualMethodTargetedByTheCall.ContainingType.IsSealed &&
                            (object)actualMethodTargetedByTheCall.ContainingModule == (object)_method.ContainingModule)
                    {
                        // special case for target is in a sealed class and "this" receiver.
                        Debug.Assert(receiver.Type.IsVerifierReference());
                        callKind = CallKind.Call;
                    }
 
                    // NOTE: we do not check that we call method in same module.
                    // Because of the "GetOriginalConstructedOverriddenMethod" above, the actual target
                    // can only be final when it is "newslot virtual final".
                    // In such case Dev11 emits "call" and we will just replicate the behavior. (see DevDiv: 546853 )
                    else if (actualMethodTargetedByTheCall.IsMetadataFinal && CanUseCallOnRefTypeReceiver(receiver))
                    {
                        // special case for calling 'final' virtual method on reference receiver
                        Debug.Assert(receiver.Type.IsVerifierReference());
                        callKind = CallKind.Call;
                    }
                }
 
                var arguments = call.Arguments;
                EmitArguments(arguments, method.Parameters, call.ArgumentRefKindsOpt);
                int stackBehavior = GetCallStackBehavior(method, arguments);
                switch (callKind)
                {
                    case CallKind.Call:
                        _builder.EmitOpCode(ILOpCode.Call, stackBehavior);
                        break;
 
                    case CallKind.CallVirt:
                        _builder.EmitOpCode(ILOpCode.Callvirt, stackBehavior);
                        break;
 
                    case CallKind.ConstrainedCallVirt:
                        _builder.EmitOpCode(ILOpCode.Constrained);
                        EmitSymbolToken(receiver.Type, receiver.Syntax);
                        _builder.EmitOpCode(ILOpCode.Callvirt, stackBehavior);
                        break;
                }
 
                EmitSymbolToken(actualMethodTargetedByTheCall, call.Syntax,
                                actualMethodTargetedByTheCall.IsVararg ? (BoundArgListOperator)arguments[arguments.Length - 1] : null);
 
                EmitCallCleanup(call.Syntax, useKind, method);
            }
 
            [MethodImpl(MethodImplOptions.NoInlining)]
            void emitGenericReceiverCloneIfNecessary(BoundCall call, CallKind callKind, ref LocalDefinition tempOpt)
            {
                var receiver = call.ReceiverOpt;
                var receiverType = receiver.Type;
 
                if (callKind == CallKind.ConstrainedCallVirt && tempOpt is null && !receiverType.IsValueType &&
                    !ReceiverIsKnownToReferToTempIfReferenceType(receiver) &&
                    !IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(call.Arguments))
                {
                    // A case where T is actually a class must be handled specially.
                    // Taking a reference to a class instance is fragile because the value behind the 
                    // reference might change while arguments are evaluated. However, the call should be
                    // performed on the instance that is behind reference at the time we push the
                    // reference to the stack. So, for a class we need to emit a reference to a temporary
                    // location, rather than to the original location
 
                    // Struct values are never nulls.
                    // We will emit a check for such case, but the check is really a JIT-time 
                    // constant since JIT will know if T is a struct or not.
 
                    // if ((object)default(T) == null) 
                    // {
                    //     temp = receiverRef
                    //     receiverRef = ref temp
                    // }
 
                    object whenNotNullLabel = null;
 
                    if (!receiverType.IsReferenceType)
                    {
                        // if ((object)default(T) == null) 
                        EmitDefaultValue(receiverType, true, receiver.Syntax);
                        EmitBox(receiverType, receiver.Syntax);
                        whenNotNullLabel = new object();
                        _builder.EmitBranch(ILOpCode.Brtrue, whenNotNullLabel);
                    }
 
                    //     temp = receiverRef
                    //     receiverRef = ref temp
                    EmitLoadIndirect(receiverType, receiver.Syntax);
                    tempOpt = AllocateTemp(receiverType, receiver.Syntax);
                    _builder.EmitLocalStore(tempOpt);
                    _builder.EmitLocalAddress(tempOpt);
 
                    if (whenNotNullLabel is not null)
                    {
                        _builder.MarkLabel(whenNotNullLabel);
                    }
                }
            }
 
            static bool receiverIsInstanceCall(BoundCall call, out BoundCall nested)
            {
                if (call.ReceiverOpt is BoundCall { Method: { RequiresInstanceReceiver: true } method } receiver && !method.IsDefaultValueTypeConstructor())
                {
                    nested = receiver;
                    return true;
                }
 
                nested = null;
                return false;
            }
        }
 
        internal static bool IsPossibleReferenceTypeReceiverOfConstrainedCall(BoundExpression receiver)
        {
            var receiverType = receiver.Type;
 
            if (receiverType.IsVerifierReference() || receiverType.IsVerifierValue())
            {
                return false;
            }
 
            return !receiverType.IsValueType;
        }
 
        internal static bool ReceiverIsKnownToReferToTempIfReferenceType(BoundExpression receiver)
        {
            while (receiver is BoundSequence sequence)
            {
                receiver = sequence.Value;
            }
 
            if (receiver is
                    BoundLocal { LocalSymbol.IsKnownToReferToTempIfReferenceType: true } or
                    BoundComplexConditionalReceiver or
                    BoundConditionalReceiver { Type: { IsReferenceType: false, IsValueType: false } })
            {
                return true;
            }
 
            return false;
        }
 
        internal static bool IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(ImmutableArray<BoundExpression> arguments)
        {
            return arguments.All(isSafeToDereferenceReceiverRefAfterEvaluatingArgument);
 
            static bool isSafeToDereferenceReceiverRefAfterEvaluatingArgument(BoundExpression expression)
            {
                var current = expression;
                while (true)
                {
                    if (current.ConstantValueOpt != null)
                    {
                        return true;
                    }
 
                    switch (current.Kind)
                    {
                        default:
                            return false;
                        case BoundKind.TypeExpression:
                        case BoundKind.Parameter:
                        case BoundKind.Local:
                        case BoundKind.ThisReference:
                            return true;
                        case BoundKind.FieldAccess:
                            {
                                var field = (BoundFieldAccess)current;
                                current = field.ReceiverOpt;
                                if (current is null)
                                {
                                    return true;
                                }
 
                                break;
                            }
                        case BoundKind.PassByCopy:
                            current = ((BoundPassByCopy)current).Expression;
                            break;
                        case BoundKind.BinaryOperator:
                            {
                                BoundBinaryOperator b = (BoundBinaryOperator)current;
                                Debug.Assert(!b.OperatorKind.IsUserDefined());
 
                                if (b.OperatorKind.IsUserDefined() || !isSafeToDereferenceReceiverRefAfterEvaluatingArgument(b.Right))
                                {
                                    return false;
                                }
 
                                current = b.Left;
                                break;
                            }
                        case BoundKind.Conversion:
                            {
                                BoundConversion conv = (BoundConversion)current;
                                Debug.Assert(!conv.ConversionKind.IsUserDefinedConversion());
 
                                if (conv.ConversionKind.IsUserDefinedConversion())
                                {
                                    return false;
                                }
 
                                current = conv.Operand;
                                break;
                            }
                    }
                }
            }
        }
 
        private bool IsReadOnlyCall(MethodSymbol method, NamedTypeSymbol methodContainingType)
        {
            Debug.Assert(methodContainingType.IsVerifierValue(), "only struct calls can be readonly");
 
            if (method.IsEffectivelyReadOnly && method.MethodKind != MethodKind.Constructor)
            {
                return true;
            }
 
            if (methodContainingType.IsNullableType())
            {
                var originalMethod = method.OriginalDefinition;
 
                if ((object)originalMethod == this._module.Compilation.GetSpecialTypeMember(SpecialMember.System_Nullable_T_GetValueOrDefault) ||
                    (object)originalMethod == this._module.Compilation.GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_Value) ||
                    (object)originalMethod == this._module.Compilation.GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_HasValue))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        // returns true when receiver is already a ref.
        // in such cases calling through a ref could be preferred over 
        // calling through indirectly loaded value.
        internal static bool IsRef(BoundExpression receiver)
        {
            switch (receiver.Kind)
            {
                case BoundKind.Local:
                    return ((BoundLocal)receiver).LocalSymbol.RefKind != RefKind.None;
 
                case BoundKind.Parameter:
                    return ((BoundParameter)receiver).ParameterSymbol.RefKind != RefKind.None;
 
                case BoundKind.Call:
                    return ((BoundCall)receiver).Method.RefKind != RefKind.None;
 
                case BoundKind.FunctionPointerInvocation:
                    return ((BoundFunctionPointerInvocation)receiver).FunctionPointer.Signature.RefKind != RefKind.None;
 
                case BoundKind.Dup:
                    return ((BoundDup)receiver).RefKind != RefKind.None;
 
                case BoundKind.Sequence:
                    return IsRef(((BoundSequence)receiver).Value);
            }
 
            return false;
        }
 
        private static int GetCallStackBehavior(MethodSymbol method, ImmutableArray<BoundExpression> arguments)
        {
            int stack = 0;
 
            if (!method.ReturnsVoid)
            {
                // The call puts the return value on the stack.
                stack += 1;
            }
 
            if (method.RequiresInstanceReceiver)
            {
                // The call pops the receiver off the stack.
                stack -= 1;
            }
 
            if (method.IsVararg)
            {
                // The call pops all the arguments, fixed and variadic.
                int fixedArgCount = arguments.Length - 1;
                int varArgCount = ((BoundArgListOperator)arguments[fixedArgCount]).Arguments.Length;
                stack -= fixedArgCount;
                stack -= varArgCount;
            }
            else
            {
                // The call pops all the arguments.
                stack -= arguments.Length;
            }
 
            return stack;
        }
 
        private static int GetObjCreationStackBehavior(BoundObjectCreationExpression objCreation)
        {
            int stack = 0;
 
            // Constructor puts the return value on the stack.
            stack += 1;
 
            if (objCreation.Constructor.IsVararg)
            {
                // Constructor pops all the arguments, fixed and variadic.
                int fixedArgCount = objCreation.Arguments.Length - 1;
                int varArgCount = ((BoundArgListOperator)objCreation.Arguments[fixedArgCount]).Arguments.Length;
                stack -= fixedArgCount;
                stack -= varArgCount;
            }
            else
            {
                // Constructor pops all the arguments.
                stack -= objCreation.Arguments.Length;
            }
 
            return stack;
        }
 
        /// <summary>
        /// Used to decide if we need to emit 'call' or 'callvirt' for structure method.
        /// It basically checks if the method overrides any other and method's defining type
        /// is not a 'special' or 'special-by-ref' type. 
        /// </summary>
        internal static bool MayUseCallForStructMethod(MethodSymbol method)
        {
            Debug.Assert(method.ContainingType.IsVerifierValue(), "this is not a value type");
 
            if (!method.IsMetadataVirtual() || method.IsStatic)
            {
                return true;
            }
 
            var overriddenMethod = method.OverriddenMethod;
            if ((object)overriddenMethod == null || overriddenMethod.IsAbstract)
            {
                return true;
            }
 
            var containingType = method.ContainingType;
            // Overrides in structs of some special types can be called directly.
            // We can assume that these special types will not be removing overrides.
            // This pattern can probably be applied to all special types,
            // but that would introduce a silent change every time a new special type is added,
            // so we constrain the check to a fixed range of types
            return containingType.SpecialType.CanOptimizeBehavior();
        }
 
        /// <summary>
        /// When array operation get long or ulong arguments the args should be 
        /// cast to native int.
        /// Note that the cast is always checked.
        /// </summary>
        private void TreatLongsAsNative(Microsoft.Cci.PrimitiveTypeCode tc)
        {
            if (tc == Microsoft.Cci.PrimitiveTypeCode.Int64)
            {
                _builder.EmitOpCode(ILOpCode.Conv_ovf_i);
            }
            else if (tc == Microsoft.Cci.PrimitiveTypeCode.UInt64)
            {
                _builder.EmitOpCode(ILOpCode.Conv_ovf_i_un);
            }
        }
 
        private void EmitArrayLength(BoundArrayLength expression, bool used)
        {
            // The binder recognizes Array.Length and Array.LongLength and creates BoundArrayLength for them.
            // 
            // ArrayLength can be either 
            //      int32 for Array.Length
            //      int64 for Array.LongLength
            //      UIntPtr for synthetic code that needs just check if length != 0 - 
            //                  this is used in "fixed(int* ptr = arr)"
            Debug.Assert(expression.Type.SpecialType == SpecialType.System_Int32 ||
                expression.Type.SpecialType == SpecialType.System_Int64 ||
                expression.Type.SpecialType == SpecialType.System_UIntPtr);
 
            // ldlen will null-check the expression so it must be "used"
            EmitExpression(expression.Expression, used: true);
            _builder.EmitOpCode(ILOpCode.Ldlen);
 
            var typeTo = expression.Type.PrimitiveTypeCode;
 
            // NOTE: ldlen returns native uint, but newarr takes native int, so the length value is always 
            //       a positive native int. We can treat it as either signed or unsigned.
            //       We will use whatever typeTo says so we do not need to convert because of sign.
            var typeFrom = typeTo.IsUnsigned() ? Microsoft.Cci.PrimitiveTypeCode.UIntPtr : Microsoft.Cci.PrimitiveTypeCode.IntPtr;
 
            // NOTE: In Dev10 C# this cast is unchecked.
            // That seems to be wrong since that would cause silent truncation on 64bit platform if that implements large arrays. 
            // 
            // Emitting checked conversion however results in redundant overflow checks on 64bit and also inhibits range check hoisting in loops.
            // Therefore we will emit unchecked conversion here as C# compiler always did.
            _builder.EmitNumericConversion(typeFrom, typeTo, @checked: false);
 
            EmitPopIfUnused(used);
        }
 
        private void EmitArrayCreationExpression(BoundArrayCreation expression, bool used)
        {
            var arrayType = (ArrayTypeSymbol)expression.Type;
 
            EmitArrayIndices(expression.Bounds);
 
            if (arrayType.IsSZArray)
            {
                _builder.EmitOpCode(ILOpCode.Newarr);
                EmitSymbolToken(arrayType.ElementType, expression.Syntax);
            }
            else
            {
                _builder.EmitArrayCreation(_module.Translate(arrayType), expression.Syntax, _diagnostics.DiagnosticBag);
            }
 
            if (expression.InitializerOpt != null)
            {
                EmitArrayInitializers(arrayType, expression.InitializerOpt);
            }
 
            // newarr has side-effects (negative bounds etc) so always emitted.
            EmitPopIfUnused(used);
        }
 
        private void EmitConvertedStackAllocExpression(BoundConvertedStackAllocExpression expression, bool used)
        {
            var initializer = expression.InitializerOpt;
            if (used)
            {
                EmitStackAlloc(expression.Type, initializer, expression.Count);
            }
            else
            {
                // the only sideeffect of a localloc is a nondeterministic and generally fatal StackOverflow.
                // we can ignore that if the actual result is unused
                EmitExpression(expression.Count, used: false);
 
                if (initializer != null)
                {
                    // If not used, just emit initializer elements to preserve possible sideeffects
                    foreach (var init in initializer.Initializers)
                    {
                        EmitExpression(init, used: false);
                    }
                }
            }
        }
 
        private void EmitObjectCreationExpression(BoundObjectCreationExpression expression, bool used)
        {
            MethodSymbol constructor = expression.Constructor;
            if (constructor.IsDefaultValueTypeConstructor())
            {
                EmitInitObj(expression.Type, used, expression.Syntax);
            }
            else
            {
                // check if need to construct at all
                if (!used && ConstructorNotSideEffecting(constructor))
                {
                    // ctor has no side-effects, so we will just evaluate the arguments
                    foreach (var arg in expression.Arguments)
                    {
                        EmitExpression(arg, used: false);
                    }
 
                    return;
                }
 
                // ReadOnlySpan may just refer to the blob, if possible.
                if (TryEmitOptimizedReadonlySpan(expression, used, inPlaceTarget: null, out _))
                {
                    return;
                }
 
                // none of the above cases, so just create an instance
                EmitArguments(expression.Arguments, constructor.Parameters, expression.ArgumentRefKindsOpt);
 
                var stackAdjustment = GetObjCreationStackBehavior(expression);
                _builder.EmitOpCode(ILOpCode.Newobj, stackAdjustment);
 
                // for variadic ctors emit expanded ctor token
                EmitSymbolToken(constructor, expression.Syntax,
                                constructor.IsVararg ? (BoundArgListOperator)expression.Arguments[expression.Arguments.Length - 1] : null);
 
                EmitPopIfUnused(used);
            }
        }
 
        private bool TryEmitOptimizedReadonlySpan(BoundObjectCreationExpression expression, bool used, BoundExpression inPlaceTarget, out bool avoidInPlace)
        {
            int argumentsLength = expression.Arguments.Length;
            avoidInPlace = false;
            return ((argumentsLength == 1 &&
                     expression.Constructor.OriginalDefinition == (object)this._module.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Array)) ||
                    (argumentsLength == 3 &&
                     expression.Constructor.OriginalDefinition == (object)this._module.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length))) &&
                   TryEmitOptimizedReadonlySpanCreation((NamedTypeSymbol)expression.Type, expression.Arguments[0], used, inPlaceTarget, out avoidInPlace,
                           start: argumentsLength == 3 ? expression.Arguments[1] : null,
                           length: argumentsLength == 3 ? expression.Arguments[2] : null);
        }
 
        /// <summary>
        /// Recognizes constructors known to not have side-effects (which means they can be skipped unless the constructed object is used)
        /// </summary>
        private bool ConstructorNotSideEffecting(MethodSymbol constructor)
        {
            var originalDef = constructor.OriginalDefinition;
            var compilation = _module.Compilation;
 
            if (originalDef == compilation.GetSpecialTypeMember(SpecialMember.System_Nullable_T__ctor))
            {
                return true;
            }
 
            if (originalDef.ContainingType.Name == NamedTypeSymbol.ValueTupleTypeName &&
                    (originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_T2__ctor) ||
                    originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_T3__ctor) ||
                    originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_T4__ctor) ||
                    originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_T5__ctor) ||
                    originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_T6__ctor) ||
                    originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_T7__ctor) ||
                    originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_TRest__ctor) ||
                    originalDef == compilation.GetWellKnownTypeMember(WellKnownMember.System_ValueTuple_T1__ctor)))
            {
                return true;
            }
 
            return false;
        }
 
        private void EmitAssignmentExpression(BoundAssignmentOperator assignmentOperator, UseKind useKind)
        {
            if (TryEmitAssignmentInPlace(assignmentOperator, useKind != UseKind.Unused))
            {
                return;
            }
 
            // Assignment expression codegen has the following parts:
            //
            // * PreRHS: We need to emit instructions before the load of the right hand side if:
            //   - If the left hand side is a ref local or ref formal parameter and the right hand 
            //     side is a value then we must put the ref on the stack early so that we can store 
            //     indirectly into it.
            //   - If the left hand side is an array slot then we must evaluate the array and indices
            //     before we evaluate the right hand side. We ensure that the array and indices are 
            //     on the stack when the store is executed.
            //   - Similarly, if the left hand side is a non-static field then its receiver must be
            //     evaluated before the right hand side.
            //
            // * RHS: There are three possible ways to do an assignment with respect to "refness", 
            //   and all are found in the lowering of:
            //
            //   N().s += 10;
            //
            //   That expression is realized as 
            //
            //   ref int addr = ref N().s;   // Assign a ref on the right hand side to the left hand side.
            //   int sum = addr + 10;        // No refs at all; assign directly to sum.
            //   addr = sum;                 // Assigns indirectly through the address.
            //
            //   - If we are in the first case then assignmentOperator.RefKind is Ref and the left hand side is a 
            //     ref local temporary. We simply assign the ref on the RHS to the storage on the LHS with no indirection.
            //
            //   - If we are in the second case then nothing is ref; we have a value on one side an a local on the other.
            //     Again, there is no indirection.
            // 
            //   - If we are in the third case then we have a ref on the left and a value on the right. We must compute the
            //     value of the right hand side and then store it into the left hand side.
            //
            // * Duplication: The result of an assignment operation is the value that was assigned. It is possible that 
            //   later codegen is expecting this value to be on the stack when we're done here. This is controlled by
            //   the "used" formal parameter. There are two possible cases:
            //   - If the preamble put stuff on the stack for the usage of the store, then we must not put an extra copy
            //     of the right hand side value on the stack; that will be between the value and the stuff needed to 
            //     do the storage. In that case we put the right hand side value in a temporary and restore it later.
            //   - Otherwise we can just do a dup instruction; there's nothing before the dup on the stack that we'll need.
            // 
            // * Storage: Either direct or indirect, depending. See the RHS section above for details.
            // 
            // * Post-storage: If we stashed away the duplicated value in the temporary, we need to restore it back to the stack.
 
            bool lhsUsesStack = EmitAssignmentPreamble(assignmentOperator);
            EmitAssignmentValue(assignmentOperator);
            LocalDefinition temp = EmitAssignmentDuplication(assignmentOperator, useKind, lhsUsesStack);
            EmitStore(assignmentOperator);
            EmitAssignmentPostfix(assignmentOperator, temp, useKind);
        }
 
        // sometimes it is possible and advantageous to get an address of the LHS and 
        // perform assignment as an in-place initialization via initobj or constructor invocation.
        //
        // 1) initobj 
        //    is used when assigning default value to T that is not a verifier reference.
        //
        // 2) in-place ctor call 
        //    is used when assigning a freshly created struct. "x = new S(arg)" can be
        //    replaced by x.S(arg) as long as partial assignment cannot be observed -
        //    i.e. target must not be on the heap and we should not be in a try block.
        private bool TryEmitAssignmentInPlace(BoundAssignmentOperator assignmentOperator, bool used)
        {
            // If the left hand is itself a ref, then we can't use in-place assignment
            // because we need to spill the creation. This code can't be written in C#, but
            // can be produced by lowering.
            if (assignmentOperator.IsRef)
            {
                return false;
            }
 
            var left = assignmentOperator.Left;
 
            // if result is used, and lives on heap, we must keep RHS value on the stack.
            // otherwise we can try conjuring up the RHS value directly where it belongs.
            if (used && !TargetIsNotOnHeap(left))
            {
                return false;
            }
 
            if (!SafeToGetWriteableReference(left))
            {
                // cannot take a ref
                return false;
            }
 
            var right = assignmentOperator.Right;
            var rightType = right.Type;
 
            // in-place is not advantageous for reference types or constants
            if (!rightType.IsTypeParameter())
            {
                if (rightType.IsReferenceType || (right.ConstantValueOpt != null && rightType.SpecialType != SpecialType.System_Decimal))
                {
                    return false;
                }
            }
 
            if (right.IsDefaultValue())
            {
                InPlaceInit(left, used);
                return true;
            }
 
            if (right is BoundObjectCreationExpression objCreation)
            {
                // If we are creating a Span<T> from a stackalloc, which is a particular pattern of code
                // produced by lowering, we must use the constructor in its standard form because the stack
                // is required to contain nothing more than stackalloc's argument.
                if (objCreation.Arguments.Length > 0 && objCreation.Arguments[0].Kind == BoundKind.ConvertedStackAllocExpression)
                {
                    return false;
                }
 
                // It is desirable to do in-place ctor call if possible.
                // we could do newobj/stloc, but in-place call 
                // produces the same or better code in current JITs 
                if (PartialCtorResultCannotEscape(left))
                {
                    var ctor = objCreation.Constructor;
 
                    // ctor can possibly see its own assignments indirectly if there are ref parameters or __arglist
                    if (System.Linq.ImmutableArrayExtensions.All(ctor.Parameters, p => p.RefKind == RefKind.None) &&
                        !ctor.IsVararg &&
                        TryInPlaceCtorCall(left, objCreation, used))
                    {
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        private bool SafeToGetWriteableReference(BoundExpression left)
        {
            if (!HasHome(left, AddressKind.Writeable))
            {
                return false;
            }
 
            // because of array covariance, taking a reference to an element of 
            // generic array may fail even though assignment "arr[i] = default(T)" would always succeed.
            if (left.Kind == BoundKind.ArrayAccess && left.Type.TypeKind == TypeKind.TypeParameter && !left.Type.IsValueType)
            {
                return false;
            }
 
            if (left.Kind == BoundKind.FieldAccess)
            {
                var fieldAccess = (BoundFieldAccess)left;
                if (fieldAccess.FieldSymbol.IsVolatile ||
                    DiagnosticsPass.IsNonAgileFieldAccess(fieldAccess, _module.Compilation))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        private void InPlaceInit(BoundExpression target, bool used)
        {
            var temp = EmitAddress(target, AddressKind.Writeable);
            Debug.Assert(temp == null, "in-place init target should not create temps");
 
            _builder.EmitOpCode(ILOpCode.Initobj);    //  initobj  <MyStruct>
            EmitSymbolToken(target.Type, target.Syntax);
 
            if (used)
            {
                Debug.Assert(TargetIsNotOnHeap(target), "cannot read-back the target since it could have been modified");
                EmitExpression(target, used);
            }
        }
 
        private bool TryInPlaceCtorCall(BoundExpression target, BoundObjectCreationExpression objCreation, bool used)
        {
            Debug.Assert(TargetIsNotOnHeap(target), "in-place construction target should not be on heap");
 
            // ReadOnlySpan may just refer to the blob, if possible.
            if (TryEmitOptimizedReadonlySpan(objCreation, used, target, out bool avoidInPlace))
            {
                return true;
            }
 
            if (avoidInPlace)
            {
                // We can use an ROS wrapper around a blob if we don't initialize in-place.
                return false;
            }
 
            var temp = EmitAddress(target, AddressKind.Writeable);
            Debug.Assert(temp == null, "in-place ctor target should not create temps");
 
            var constructor = objCreation.Constructor;
            EmitArguments(objCreation.Arguments, constructor.Parameters, objCreation.ArgumentRefKindsOpt);
            // -2 to adjust for consumed target address and not produced value.
            var stackAdjustment = GetObjCreationStackBehavior(objCreation) - 2;
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment);
            // for variadic ctors emit expanded ctor token
            EmitSymbolToken(constructor, objCreation.Syntax,
                            constructor.IsVararg ? (BoundArgListOperator)objCreation.Arguments[objCreation.Arguments.Length - 1] : null);
 
            if (used)
            {
                EmitExpression(target, used: true);
            }
 
            return true;
        }
 
        // partial ctor results are not observable when target is not on the heap.
        // we also must not be in a try, otherwise if ctor throws
        // partially assigned value may be observed in the handler.
        private bool PartialCtorResultCannotEscape(BoundExpression left)
        {
            if (TargetIsNotOnHeap(left))
            {
                if (_tryNestingLevel != 0)
                {
                    var local = left as BoundLocal;
                    if (local != null && !_builder.PossiblyDefinedOutsideOfTry(GetLocal(local)))
                    {
                        // local defined inside immediate Try - cannot escape
                        return true;
                    }
 
                    // local defined outside of immediate try or it is a parameter - can escape
                    return false;
                }
 
                // we are not in a try - locals, parameters cannot escape
                return true;
            }
 
            // left is a reference, partial initializations can escape.
            return false;
        }
 
        // returns True when assignment target is definitely not on the heap
        private static bool TargetIsNotOnHeap(BoundExpression left)
        {
            switch (left.Kind)
            {
                case BoundKind.Parameter:
                    return ((BoundParameter)left).ParameterSymbol.RefKind == RefKind.None;
 
                case BoundKind.Local:
                    // NOTE: stack locals are either homeless or refs, no need to special case them
                    //       they will never be assigned in-place.
                    return ((BoundLocal)left).LocalSymbol.RefKind == RefKind.None;
            }
 
            return false;
        }
 
        private bool EmitAssignmentPreamble(BoundAssignmentOperator assignmentOperator)
        {
            var assignmentTarget = assignmentOperator.Left;
            bool lhsUsesStack = false;
 
            switch (assignmentTarget.Kind)
            {
                case BoundKind.RefValueOperator:
                    EmitRefValueAddress((BoundRefValueOperator)assignmentTarget);
                    break;
 
                case BoundKind.FieldAccess:
                    {
                        var left = (BoundFieldAccess)assignmentTarget;
                        if (left.FieldSymbol.RefKind != RefKind.None &&
                            !assignmentOperator.IsRef)
                        {
                            EmitFieldLoadNoIndirection(left, used: true);
                            lhsUsesStack = true;
                        }
                        else if (!left.FieldSymbol.IsStatic)
                        {
                            var temp = EmitReceiverRef(left.ReceiverOpt, AddressKind.Writeable);
                            Debug.Assert(temp == null, "temp is unexpected when assigning to a field");
                            lhsUsesStack = true;
                        }
                    }
                    break;
 
                case BoundKind.Parameter:
                    {
                        var left = (BoundParameter)assignmentTarget;
                        if (left.ParameterSymbol.RefKind != RefKind.None &&
                            !assignmentOperator.IsRef)
                        {
                            _builder.EmitLoadArgumentOpcode(ParameterSlot(left));
                            lhsUsesStack = true;
                        }
                    }
                    break;
 
                case BoundKind.Local:
                    {
                        var left = (BoundLocal)assignmentTarget;
 
                        // Again, consider our earlier case:
                        //
                        // ref int addr = ref N().s;
                        // int sum = addr + 10; 
                        // addr = sum;
                        //
                        // There are three different ways we could be assigning to a local.
                        //
                        // In the first case, we want to simply call N(), take the address
                        // of s, and then store that address in addr.
                        //
                        // In the second case again we simply want to compute the sum and
                        // store the result in sum.
                        //
                        // In the third case however we want to first load the contents of
                        // addr -- the address of field s -- then put the sum on the stack,
                        // and then do an indirect store. In that case we need to have the
                        // contents of addr on the stack.
 
                        if (left.LocalSymbol.RefKind != RefKind.None && !assignmentOperator.IsRef)
                        {
                            if (!IsStackLocal(left.LocalSymbol))
                            {
                                LocalDefinition localDefinition = GetLocal(left);
                                _builder.EmitLocalLoad(localDefinition);
                            }
                            else
                            {
                                // this is a case of indirect assignment to a stack temp.
                                // currently byref temp can only be a stack local in scenarios where 
                                // there is only one assignment and it is the last one. 
                                // I do not yet know how to support cases where we assign more than once. 
                                // That where Dup of LHS would be needed, but as a general scenario 
                                // it is not always possible to handle. Fortunately all the cases where we
                                // indirectly assign to a byref temp come from rewriter and all
                                // they all are write-once cases.
                                //
                                // For now analyzer asserts that indirect writes are final reads of 
                                // a ref local. And we never need a dup here.
 
                                // builder.EmitOpCode(ILOpCode.Dup);
                            }
 
                            lhsUsesStack = true;
                        }
                    }
                    break;
 
                case BoundKind.ArrayAccess:
                    {
                        var left = (BoundArrayAccess)assignmentTarget;
                        EmitExpression(left.Expression, used: true);
                        EmitArrayIndices(left.Indices);
                        lhsUsesStack = true;
                    }
                    break;
 
                case BoundKind.ThisReference:
                    {
                        var left = (BoundThisReference)assignmentTarget;
 
                        var temp = EmitAddress(left, AddressKind.Writeable);
                        Debug.Assert(temp == null, "taking ref of this should not create a temp");
 
                        lhsUsesStack = true;
                    }
                    break;
 
                case BoundKind.Dup:
                    {
                        var left = (BoundDup)assignmentTarget;
 
                        var temp = EmitAddress(left, AddressKind.Writeable);
                        Debug.Assert(temp == null, "taking ref of Dup should not create a temp");
 
                        lhsUsesStack = true;
                    }
                    break;
 
                case BoundKind.ConditionalOperator:
                    {
                        var left = (BoundConditionalOperator)assignmentTarget;
                        Debug.Assert(left.IsRef);
 
                        var temp = EmitAddress(left, AddressKind.Writeable);
                        Debug.Assert(temp == null, "taking ref of this should not create a temp");
 
                        lhsUsesStack = true;
                    }
                    break;
 
                case BoundKind.PointerIndirectionOperator:
                    {
                        var left = (BoundPointerIndirectionOperator)assignmentTarget;
 
                        EmitExpression(left.Operand, used: true);
 
                        lhsUsesStack = true;
                    }
                    break;
 
                case BoundKind.Sequence:
                    {
                        var sequence = (BoundSequence)assignmentTarget;
 
                        // NOTE: not releasing sequence locals right away. 
                        // Since sequence is used as a variable, we will keep the locals for the extent of the containing expression
                        DefineAndRecordLocals(sequence);
                        EmitSideEffects(sequence);
                        lhsUsesStack = EmitAssignmentPreamble(assignmentOperator.Update(sequence.Value, assignmentOperator.Right, assignmentOperator.IsRef, assignmentOperator.Type));
                        CloseScopeAndKeepLocals(sequence);
                    }
                    break;
 
                case BoundKind.Call:
                    {
                        var left = (BoundCall)assignmentTarget;
 
                        Debug.Assert(left.Method.RefKind != RefKind.None);
                        EmitCallExpression(left, UseKind.UsedAsAddress);
 
                        lhsUsesStack = true;
                    }
                    break;
 
                case BoundKind.FunctionPointerInvocation:
                    {
                        var left = (BoundFunctionPointerInvocation)assignmentTarget;
 
                        Debug.Assert(left.FunctionPointer.Signature.RefKind != RefKind.None);
                        EmitCalli(left, UseKind.UsedAsAddress);
 
                        lhsUsesStack = true;
                    }
                    break;
 
                case BoundKind.PropertyAccess:
                case BoundKind.IndexerAccess:
                // Property access should have been rewritten.
                case BoundKind.PreviousSubmissionReference:
                    // Script references are lowered to a this reference and a field access.
                    throw ExceptionUtilities.UnexpectedValue(assignmentTarget.Kind);
 
                case BoundKind.PseudoVariable:
                    EmitPseudoVariableAddress((BoundPseudoVariable)assignmentTarget);
                    lhsUsesStack = true;
                    break;
 
                case BoundKind.ModuleVersionId:
                case BoundKind.InstrumentationPayloadRoot:
                    break;
 
                case BoundKind.AssignmentOperator:
                    var assignment = (BoundAssignmentOperator)assignmentTarget;
                    if (!assignment.IsRef)
                    {
                        goto default;
                    }
                    EmitAssignmentExpression(assignment, UseKind.UsedAsAddress);
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(assignmentTarget.Kind);
            }
 
            return lhsUsesStack;
        }
 
        private void EmitAssignmentValue(BoundAssignmentOperator assignmentOperator)
        {
            if (!assignmentOperator.IsRef)
            {
                EmitExpression(assignmentOperator.Right, used: true);
            }
            else
            {
                int exprTempsBefore = _expressionTemps?.Count ?? 0;
                BoundExpression lhs = assignmentOperator.Left;
 
                // NOTE: passing "ReadOnlyStrict" here. 
                //       we should not get an address of a copy if at all possible
                LocalDefinition temp = EmitAddress(assignmentOperator.Right, lhs.GetRefKind() is RefKind.RefReadOnly or RefKindExtensions.StrictIn or RefKind.RefReadOnlyParameter ? AddressKind.ReadOnlyStrict : AddressKind.Writeable);
 
                // Generally taking a ref for the purpose of ref assignment should not be done on homeless values
                // however, there are very rare cases when we need to get a ref off a temp in synthetic code.
                // Retain those temps for the extent of the encompassing expression.
                AddExpressionTemp(temp);
 
                var exprTempsAfter = _expressionTemps?.Count ?? 0;
 
                // are we, by the way, ref-assigning to something that lives longer than encompassing expression?
                Debug.Assert(lhs.Kind != BoundKind.Parameter || exprTempsAfter <= exprTempsBefore);
 
                if (lhs.Kind == BoundKind.Local && ((BoundLocal)lhs).LocalSymbol.SynthesizedKind.IsLongLived())
                {
                    // This situation is extremely rare. We are assigning a ref to a local with unknown lifetime
                    // while computing that ref required expression temps.
                    //
                    // We cannot reuse any of those temps and must leak them from the retained set.
                    // Any of them could be directly or indirectly referred by the LHS after the assignment.
                    // and we do not know the scope of the LHS - could be the whole method.
                    if (exprTempsAfter > exprTempsBefore)
                    {
                        _expressionTemps.Count = exprTempsBefore;
                    }
                }
            }
        }
 
        private LocalDefinition EmitAssignmentDuplication(BoundAssignmentOperator assignmentOperator, UseKind useKind, bool lhsUsesStack)
        {
            LocalDefinition temp = null;
            if (useKind != UseKind.Unused)
            {
                _builder.EmitOpCode(ILOpCode.Dup);
 
                if (lhsUsesStack)
                {
                    // Today we sometimes have a case where we assign a ref directly to a temporary of ref type:
                    //
                    // ref int addr = ref N().y;  <-- copies the address by value; no indirection
                    // int sum = addr + 10;
                    // addr = sum;
                    //
                    // If we have something like:
                    //
                    // ref int t1 = (ref int t2 = ref M().s); 
                    //
                    // or the even more odd:
                    //
                    // int t1 = (ref int t2 = ref M().s);
                    //
                    // We need to figure out which of the situations above we are in, and ensure that the
                    // correct kind of temporary is created here. And also that either its value or its
                    // indirected value is read out after the store, in EmitAssignmentPostfix, below.
 
                    temp = AllocateTemp(
                        assignmentOperator.Left.Type,
                        assignmentOperator.Left.Syntax,
                        assignmentOperator.IsRef ? LocalSlotConstraints.ByRef : LocalSlotConstraints.None);
                    _builder.EmitLocalStore(temp);
                }
            }
            return temp;
        }
 
        private void EmitStore(BoundAssignmentOperator assignment)
        {
            BoundExpression expression = assignment.Left;
            switch (expression.Kind)
            {
                case BoundKind.FieldAccess:
                    EmitFieldStore((BoundFieldAccess)expression, assignment.IsRef);
                    break;
 
                case BoundKind.Local:
                    // If we are doing a 'normal' local assignment like 'int t = 10;', or
                    // if we are initializing a temporary like 'ref int t = ref M().s;' then
                    // we just emit a local store. If we are doing an assignment through
                    // a ref local temporary then we assume that the instruction to load
                    // the address is already on the stack, and we must indirect through it.
 
                    // See the comments in EmitAssignmentExpression above for details.
                    BoundLocal local = (BoundLocal)expression;
                    if (local.LocalSymbol.RefKind != RefKind.None && !assignment.IsRef)
                    {
                        EmitIndirectStore(local.LocalSymbol.Type, local.Syntax);
                    }
                    else
                    {
                        if (IsStackLocal(local.LocalSymbol))
                        {
                            // assign to stack var == leave original value on stack
                            break;
                        }
                        else
                        {
                            _builder.EmitLocalStore(GetLocal(local));
                        }
                    }
                    break;
 
                case BoundKind.ArrayAccess:
                    var array = ((BoundArrayAccess)expression).Expression;
                    var arrayType = (ArrayTypeSymbol)array.Type;
                    EmitArrayElementStore(arrayType, expression.Syntax);
                    break;
 
                case BoundKind.ThisReference:
                    EmitThisStore((BoundThisReference)expression);
                    break;
 
                case BoundKind.Parameter:
                    EmitParameterStore((BoundParameter)expression, assignment.IsRef);
                    break;
 
                case BoundKind.Dup:
                    Debug.Assert(((BoundDup)expression).RefKind != RefKind.None);
                    EmitIndirectStore(expression.Type, expression.Syntax);
                    break;
 
                case BoundKind.ConditionalOperator:
                    Debug.Assert(((BoundConditionalOperator)expression).IsRef);
                    EmitIndirectStore(expression.Type, expression.Syntax);
                    break;
 
                case BoundKind.RefValueOperator:
                case BoundKind.PointerIndirectionOperator:
                case BoundKind.PseudoVariable:
                    EmitIndirectStore(expression.Type, expression.Syntax);
                    break;
 
                case BoundKind.Sequence:
                    {
                        var sequence = (BoundSequence)expression;
                        EmitStore(assignment.Update(sequence.Value, assignment.Right, assignment.IsRef, assignment.Type));
                    }
                    break;
 
                case BoundKind.Call:
                    Debug.Assert(((BoundCall)expression).Method.RefKind != RefKind.None);
                    EmitIndirectStore(expression.Type, expression.Syntax);
                    break;
 
                case BoundKind.FunctionPointerInvocation:
                    Debug.Assert(((BoundFunctionPointerInvocation)expression).FunctionPointer.Signature.RefKind != RefKind.None);
                    EmitIndirectStore(expression.Type, expression.Syntax);
                    break;
 
                case BoundKind.ModuleVersionId:
                    EmitModuleVersionIdStore((BoundModuleVersionId)expression);
                    break;
 
                case BoundKind.InstrumentationPayloadRoot:
                    EmitInstrumentationPayloadRootStore((BoundInstrumentationPayloadRoot)expression);
                    break;
 
                case BoundKind.AssignmentOperator:
                    var nested = (BoundAssignmentOperator)expression;
                    if (!nested.IsRef)
                    {
                        goto default;
                    }
                    EmitIndirectStore(nested.Type, expression.Syntax);
                    break;
 
                case BoundKind.PreviousSubmissionReference:
                // Script references are lowered to a this reference and a field access.
                default:
                    throw ExceptionUtilities.UnexpectedValue(expression.Kind);
            }
        }
 
        private void EmitAssignmentPostfix(BoundAssignmentOperator assignment, LocalDefinition temp, UseKind useKind)
        {
            if (temp != null)
            {
                if (useKind == UseKind.UsedAsAddress)
                {
                    _builder.EmitLocalAddress(temp);
                }
                else
                {
                    _builder.EmitLocalLoad(temp);
                }
                FreeTemp(temp);
            }
 
            if (useKind == UseKind.UsedAsValue && assignment.IsRef)
            {
                EmitLoadIndirect(assignment.Type, assignment.Syntax);
            }
        }
 
        private void EmitThisStore(BoundThisReference thisRef)
        {
            Debug.Assert(thisRef.Type.IsValueType);
 
            _builder.EmitOpCode(ILOpCode.Stobj);
            EmitSymbolToken(thisRef.Type, thisRef.Syntax);
        }
 
        private void EmitArrayElementStore(ArrayTypeSymbol arrayType, SyntaxNode syntaxNode)
        {
            if (arrayType.IsSZArray)
            {
                EmitVectorElementStore(arrayType, syntaxNode);
            }
            else
            {
                _builder.EmitArrayElementStore(_module.Translate(arrayType), syntaxNode, _diagnostics.DiagnosticBag);
            }
        }
 
        /// <summary>
        /// Emit an element store instruction for a single dimensional array.
        /// </summary>
        private void EmitVectorElementStore(ArrayTypeSymbol arrayType, SyntaxNode syntaxNode)
        {
            var elementType = arrayType.ElementType;
 
            if (elementType.IsEnumType())
            {
                //underlying primitives do not need type tokens.
                elementType = ((NamedTypeSymbol)elementType).EnumUnderlyingType;
            }
 
            switch (elementType.PrimitiveTypeCode)
            {
                case Microsoft.Cci.PrimitiveTypeCode.Boolean:
                case Microsoft.Cci.PrimitiveTypeCode.Int8:
                case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                    _builder.EmitOpCode(ILOpCode.Stelem_i1);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Char:
                case Microsoft.Cci.PrimitiveTypeCode.Int16:
                case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                    _builder.EmitOpCode(ILOpCode.Stelem_i2);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Int32:
                case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                    _builder.EmitOpCode(ILOpCode.Stelem_i4);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Int64:
                case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                    _builder.EmitOpCode(ILOpCode.Stelem_i8);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                    _builder.EmitOpCode(ILOpCode.Stelem_i);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Float32:
                    _builder.EmitOpCode(ILOpCode.Stelem_r4);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Float64:
                    _builder.EmitOpCode(ILOpCode.Stelem_r8);
                    break;
 
                default:
                    if (elementType.IsVerifierReference())
                    {
                        _builder.EmitOpCode(ILOpCode.Stelem_ref);
                    }
                    else
                    {
                        _builder.EmitOpCode(ILOpCode.Stelem);
                        EmitSymbolToken(elementType, syntaxNode);
                    }
                    break;
            }
        }
 
        private void EmitFieldStore(BoundFieldAccess fieldAccess, bool refAssign)
        {
            var field = fieldAccess.FieldSymbol;
 
            if (field.IsVolatile)
            {
                _builder.EmitOpCode(ILOpCode.Volatile);
            }
 
            if (field.RefKind != RefKind.None && !refAssign)
            {
                //NOTE: we should have the actual field already loaded, 
                //now need to do a store to where it points to
                EmitIndirectStore(field.Type, fieldAccess.Syntax);
            }
            else
            {
                _builder.EmitOpCode(field.IsStatic ? ILOpCode.Stsfld : ILOpCode.Stfld);
                EmitSymbolToken(field, fieldAccess.Syntax);
            }
        }
 
        private void EmitParameterStore(BoundParameter parameter, bool refAssign)
        {
            if (parameter.ParameterSymbol.RefKind != RefKind.None && !refAssign)
            {
                //NOTE: we should have the actual parameter already loaded, 
                //now need to do a store to where it points to
                EmitIndirectStore(parameter.ParameterSymbol.Type, parameter.Syntax);
            }
            else
            {
                int slot = ParameterSlot(parameter);
                _builder.EmitStoreArgumentOpcode(slot);
            }
        }
 
        private void EmitIndirectStore(TypeSymbol type, SyntaxNode syntaxNode)
        {
            if (type.IsEnumType())
            {
                //underlying primitives do not need type tokens.
                type = ((NamedTypeSymbol)type).EnumUnderlyingType;
            }
 
            switch (type.PrimitiveTypeCode)
            {
                case Microsoft.Cci.PrimitiveTypeCode.Boolean:
                case Microsoft.Cci.PrimitiveTypeCode.Int8:
                case Microsoft.Cci.PrimitiveTypeCode.UInt8:
                    _builder.EmitOpCode(ILOpCode.Stind_i1);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Char:
                case Microsoft.Cci.PrimitiveTypeCode.Int16:
                case Microsoft.Cci.PrimitiveTypeCode.UInt16:
                    _builder.EmitOpCode(ILOpCode.Stind_i2);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Int32:
                case Microsoft.Cci.PrimitiveTypeCode.UInt32:
                    _builder.EmitOpCode(ILOpCode.Stind_i4);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Int64:
                case Microsoft.Cci.PrimitiveTypeCode.UInt64:
                    _builder.EmitOpCode(ILOpCode.Stind_i8);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.IntPtr:
                case Microsoft.Cci.PrimitiveTypeCode.UIntPtr:
                case Microsoft.Cci.PrimitiveTypeCode.Pointer:
                case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer:
                    _builder.EmitOpCode(ILOpCode.Stind_i);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Float32:
                    _builder.EmitOpCode(ILOpCode.Stind_r4);
                    break;
 
                case Microsoft.Cci.PrimitiveTypeCode.Float64:
                    _builder.EmitOpCode(ILOpCode.Stind_r8);
                    break;
 
                default:
                    if (type.IsVerifierReference())
                    {
                        _builder.EmitOpCode(ILOpCode.Stind_ref);
                    }
                    else
                    {
                        _builder.EmitOpCode(ILOpCode.Stobj);
                        EmitSymbolToken(type, syntaxNode);
                    }
                    break;
            }
        }
 
        private void EmitPopIfUnused(bool used)
        {
            if (!used)
            {
                _builder.EmitOpCode(ILOpCode.Pop);
            }
        }
 
        private void EmitIsExpression(BoundIsOperator isOp, bool used, bool omitBooleanConversion)
        {
            var operand = isOp.Operand;
            EmitExpression(operand, used);
            if (used)
            {
                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.Syntax);
 
                if (!omitBooleanConversion)
                {
                    _builder.EmitOpCode(ILOpCode.Ldnull);
                    _builder.EmitOpCode(ILOpCode.Cgt_un);
                }
            }
        }
 
        private void EmitAsExpression(BoundAsOperator asOp, bool used)
        {
            Debug.Assert(asOp.OperandPlaceholder is null);
            Debug.Assert(asOp.OperandConversion is null);
 
            var operand = asOp.Operand;
            EmitExpression(operand, used);
 
            if (used)
            {
                var operandType = operand.Type;
                var targetType = asOp.Type;
                Debug.Assert((object)targetType != null);
                if ((object)operandType != null && !operandType.IsVerifierReference())
                {
                    // box the operand for isinst if it is not a verifier reference
                    EmitBox(operandType, operand.Syntax);
                }
                _builder.EmitOpCode(ILOpCode.Isinst);
                EmitSymbolToken(targetType, asOp.Syntax);
                if (!targetType.IsVerifierReference())
                {
                    // We need to unbox if the target type is not a reference type
                    _builder.EmitOpCode(ILOpCode.Unbox_any);
                    EmitSymbolToken(targetType, asOp.Syntax);
                }
            }
        }
 
        private void EmitDefaultValue(TypeSymbol type, bool used, SyntaxNode syntaxNode)
        {
            if (used)
            {
                // default type parameter values must be emitted as 'initobj' regardless of constraints
                if (!type.IsTypeParameter() && type.SpecialType != SpecialType.System_Decimal)
                {
                    var constantValue = type.GetDefaultValue();
                    if (constantValue != null)
                    {
                        _builder.EmitConstantValue(constantValue);
                        return;
                    }
                }
 
                if (type.IsPointerOrFunctionPointer() || type.SpecialType == SpecialType.System_UIntPtr)
                {
                    // default(whatever*) and default(UIntPtr) can be emitted as:
                    _builder.EmitOpCode(ILOpCode.Ldc_i4_0);
                    _builder.EmitOpCode(ILOpCode.Conv_u);
                }
                else if (type.SpecialType == SpecialType.System_IntPtr)
                {
                    _builder.EmitOpCode(ILOpCode.Ldc_i4_0);
                    _builder.EmitOpCode(ILOpCode.Conv_i);
                }
                else
                {
                    EmitInitObj(type, true, syntaxNode);
                }
            }
        }
 
        private void EmitDefaultExpression(BoundDefaultExpression expression, bool used)
        {
            Debug.Assert(expression.Type.SpecialType == SpecialType.System_Decimal ||
                expression.Type.GetDefaultValue() == null, "constant should be set on this expression");
 
            // Default value for the given default expression is not a constant
            // Expression must be of type parameter type or a non-primitive value type
            // Emit an initobj instruction for these cases
            EmitDefaultValue(expression.Type, used, expression.Syntax);
        }
 
        private void EmitConstantExpression(TypeSymbol type, ConstantValue constantValue, bool used, SyntaxNode syntaxNode)
        {
            if (used)  // unused constant has no side-effects
            {
                // Null type parameter values must be emitted as 'initobj' rather than 'ldnull'.
                if (((object)type != null) && (type.TypeKind == TypeKind.TypeParameter) && constantValue.IsNull)
                {
                    EmitInitObj(type, used, syntaxNode);
                }
                else
                {
                    _builder.EmitConstantValue(constantValue);
                }
            }
        }
 
        private void EmitInitObj(TypeSymbol type, bool used, SyntaxNode syntaxNode)
        {
            if (used)
            {
                var temp = this.AllocateTemp(type, syntaxNode);
                _builder.EmitLocalAddress(temp);                  //  ldloca temp
                _builder.EmitOpCode(ILOpCode.Initobj);            //  initobj  <MyStruct>
                EmitSymbolToken(type, syntaxNode);
                _builder.EmitLocalLoad(temp);                     //  ldloc temp
                FreeTemp(temp);
            }
        }
 
        private void EmitGetTypeFromHandle(BoundTypeOf boundTypeOf)
        {
            _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0); //argument off, return value on
            var getTypeMethod = boundTypeOf.GetTypeFromHandle;
            Debug.Assert((object)getTypeMethod != null); // Should have been checked during binding
            EmitSymbolToken(getTypeMethod, boundTypeOf.Syntax, null);
        }
 
        private void EmitTypeOfExpression(BoundTypeOfOperator boundTypeOfOperator)
        {
            TypeSymbol type = boundTypeOfOperator.SourceType.Type;
            _builder.EmitOpCode(ILOpCode.Ldtoken);
            EmitSymbolToken(type, boundTypeOfOperator.SourceType.Syntax);
            EmitGetTypeFromHandle(boundTypeOfOperator);
        }
 
        private void EmitSizeOfExpression(BoundSizeOfOperator boundSizeOfOperator)
        {
            TypeSymbol type = boundSizeOfOperator.SourceType.Type;
            _builder.EmitOpCode(ILOpCode.Sizeof);
            EmitSymbolToken(type, boundSizeOfOperator.SourceType.Syntax);
        }
 
        private void EmitMethodDefIndexExpression(BoundMethodDefIndex node)
        {
            Debug.Assert(node.Method.IsDefinition);
            Debug.Assert(node.Type.SpecialType == SpecialType.System_Int32);
            _builder.EmitOpCode(ILOpCode.Ldtoken);
 
            // For partial methods, we emit pseudo token based on the symbol for the partial
            // definition part as opposed to the symbol for the partial implementation part.
            // We will need to resolve the symbol associated with each pseudo token in order
            // to compute the real method definition tokens later. For partial methods, this
            // resolution can only succeed if the associated symbol is the symbol for the
            // partial definition and not the symbol for the partial implementation (see
            // MethodSymbol.ResolvedMethodImpl()).
            var symbol = node.Method.PartialDefinitionPart ?? node.Method;
 
            EmitSymbolToken(symbol, node.Syntax, null, encodeAsRawDefinitionToken: true);
        }
 
        private void EmitLocalIdExpression(BoundLocalId node)
        {
            Debug.Assert(node.Type.SpecialType == SpecialType.System_Int32);
 
            if (node.HoistedField is null)
            {
                _builder.EmitIntConstant(GetLocal(node.Local).SlotIndex);
            }
            else
            {
                EmitHoistedVariableId(node.HoistedField, node.Syntax);
            }
        }
 
        private void EmitParameterIdExpression(BoundParameterId node)
        {
            Debug.Assert(node.Type.SpecialType == SpecialType.System_Int32);
 
            if (node.HoistedField is null)
            {
                _builder.EmitIntConstant(node.Parameter.Ordinal);
            }
            else
            {
                EmitHoistedVariableId(node.HoistedField, node.Syntax);
            }
        }
 
        private void EmitHoistedVariableId(FieldSymbol field, SyntaxNode syntax)
        {
            Debug.Assert(field.IsDefinition);
            var fieldRef = _module.Translate(field, syntax, _diagnostics.DiagnosticBag, needDeclaration: true);
 
            _builder.EmitOpCode(ILOpCode.Ldtoken);
            _builder.EmitToken(fieldRef, syntax, _diagnostics.DiagnosticBag, Cci.MetadataWriter.RawTokenEncoding.LiftedVariableId);
        }
 
        private void EmitMaximumMethodDefIndexExpression(BoundMaximumMethodDefIndex node)
        {
            Debug.Assert(node.Type.SpecialType == SpecialType.System_Int32);
            _builder.EmitOpCode(ILOpCode.Ldtoken);
            _builder.EmitGreatestMethodToken();
        }
 
        private void EmitModuleVersionIdLoad(BoundModuleVersionId node)
        {
            _builder.EmitOpCode(ILOpCode.Ldsfld);
            EmitModuleVersionIdToken(node);
        }
 
        private void EmitModuleVersionIdStore(BoundModuleVersionId node)
        {
            _builder.EmitOpCode(ILOpCode.Stsfld);
            EmitModuleVersionIdToken(node);
        }
 
        private void EmitModuleVersionIdToken(BoundModuleVersionId node)
        {
            _builder.EmitToken(
                _module.GetModuleVersionId(_module.Translate(node.Type, node.Syntax, _diagnostics.DiagnosticBag), node.Syntax, _diagnostics.DiagnosticBag),
                node.Syntax,
                _diagnostics.DiagnosticBag);
        }
 
        private void EmitThrowIfModuleCancellationRequested(SyntaxNode syntax)
        {
            var cancellationTokenType = _module.CommonCompilation.CommonGetWellKnownType(WellKnownType.System_Threading_CancellationToken);
 
            _builder.EmitOpCode(ILOpCode.Ldsflda);
            _builder.EmitToken(
                _module.GetModuleCancellationToken(_module.Translate(cancellationTokenType, syntax, _diagnostics.DiagnosticBag), syntax, _diagnostics.DiagnosticBag),
                syntax,
                _diagnostics.DiagnosticBag);
 
            var throwMethod = (MethodSymbol)_module.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Threading_CancellationToken__ThrowIfCancellationRequested);
 
            // BoundThrowIfModuleCancellationRequested should not be created if the method doesn't exist.
            Debug.Assert(throwMethod != null);
 
            _builder.EmitOpCode(ILOpCode.Call, -1);
            _builder.EmitToken(
                _module.Translate(throwMethod, syntax, _diagnostics.DiagnosticBag),
                syntax,
                _diagnostics.DiagnosticBag);
        }
 
        private void EmitModuleCancellationTokenLoad(SyntaxNode syntax)
        {
            var cancellationTokenType = _module.CommonCompilation.CommonGetWellKnownType(WellKnownType.System_Threading_CancellationToken);
 
            _builder.EmitOpCode(ILOpCode.Ldsfld);
            _builder.EmitToken(
                _module.GetModuleCancellationToken(_module.Translate(cancellationTokenType, syntax, _diagnostics.DiagnosticBag), syntax, _diagnostics.DiagnosticBag),
                syntax,
                _diagnostics.DiagnosticBag);
        }
 
        private void EmitModuleVersionIdStringLoad()
        {
            _builder.EmitOpCode(ILOpCode.Ldstr);
            _builder.EmitModuleVersionIdStringToken();
        }
 
        private void EmitInstrumentationPayloadRootLoad(BoundInstrumentationPayloadRoot node)
        {
            _builder.EmitOpCode(ILOpCode.Ldsfld);
            EmitInstrumentationPayloadRootToken(node);
        }
 
        private void EmitInstrumentationPayloadRootStore(BoundInstrumentationPayloadRoot node)
        {
            _builder.EmitOpCode(ILOpCode.Stsfld);
            EmitInstrumentationPayloadRootToken(node);
        }
 
        private void EmitInstrumentationPayloadRootToken(BoundInstrumentationPayloadRoot node)
        {
            _builder.EmitToken(_module.GetInstrumentationPayloadRoot(node.AnalysisKind, _module.Translate(node.Type, node.Syntax, _diagnostics.DiagnosticBag), node.Syntax, _diagnostics.DiagnosticBag), node.Syntax, _diagnostics.DiagnosticBag);
        }
 
        private void EmitSourceDocumentIndex(BoundSourceDocumentIndex node)
        {
            Debug.Assert(node.Type.SpecialType == SpecialType.System_Int32);
            _builder.EmitOpCode(ILOpCode.Ldtoken);
            _builder.EmitSourceDocumentIndexToken(node.Document);
        }
 
        private void EmitMethodInfoExpression(BoundMethodInfo node)
        {
            _builder.EmitOpCode(ILOpCode.Ldtoken);
            EmitSymbolToken(node.Method, node.Syntax, null);
 
            MethodSymbol getMethod = node.GetMethodFromHandle;
            Debug.Assert((object)getMethod != null);
 
            if (getMethod.ParameterCount == 1)
            {
                _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0); //argument off, return value on
            }
            else
            {
                Debug.Assert(getMethod.ParameterCount == 2);
                _builder.EmitOpCode(ILOpCode.Ldtoken);
                EmitSymbolToken(node.Method.ContainingType, node.Syntax);
                _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1); //2 arguments off, return value on
            }
 
            EmitSymbolToken(getMethod, node.Syntax, null);
            if (!TypeSymbol.Equals(node.Type, getMethod.ReturnType, TypeCompareKind.ConsiderEverything2))
            {
                _builder.EmitOpCode(ILOpCode.Castclass);
                EmitSymbolToken(node.Type, node.Syntax);
            }
        }
 
        private void EmitFieldInfoExpression(BoundFieldInfo node)
        {
            _builder.EmitOpCode(ILOpCode.Ldtoken);
            EmitSymbolToken(node.Field, node.Syntax);
            MethodSymbol getField = node.GetFieldFromHandle;
            Debug.Assert((object)getField != null);
 
            if (getField.ParameterCount == 1)
            {
                _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0); //argument off, return value on
            }
            else
            {
                Debug.Assert(getField.ParameterCount == 2);
                _builder.EmitOpCode(ILOpCode.Ldtoken);
                EmitSymbolToken(node.Field.ContainingType, node.Syntax);
                _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1); //2 arguments off, return value on
            }
 
            EmitSymbolToken(getField, node.Syntax, null);
            if (!TypeSymbol.Equals(node.Type, getField.ReturnType, TypeCompareKind.ConsiderEverything2))
            {
                _builder.EmitOpCode(ILOpCode.Castclass);
                EmitSymbolToken(node.Type, node.Syntax);
            }
        }
 
        /// <summary>
        /// Emit code for a conditional (aka ternary) operator.
        /// </summary>
        /// <remarks>
        /// (b ? x : y) becomes
        ///     push b
        ///     if pop then goto CONSEQUENCE
        ///     push y
        ///     goto DONE
        ///   CONSEQUENCE:
        ///     push x
        ///   DONE:
        /// </remarks>
        private void EmitConditionalOperator(BoundConditionalOperator expr, bool used)
        {
            Debug.Assert(expr.ConstantValueOpt == null, "Constant value should have been emitted directly");
 
            // Generate branchless IL for (b ? 1 : 0).
            if (used && _ilEmitStyle != ILEmitStyle.Debug &&
                (IsNumeric(expr.Type) || expr.Type.PrimitiveTypeCode == Cci.PrimitiveTypeCode.Boolean) &&
                expr.Consequence.ConstantValueOpt?.IsIntegralValueZeroOrOne(out bool isConsequenceOne) == true &&
                expr.Alternative.ConstantValueOpt?.IsIntegralValueZeroOrOne(out bool isAlternativeOne) == true &&
                isConsequenceOne != isAlternativeOne &&
                TryEmitComparison(expr.Condition, sense: isConsequenceOne))
            {
                var toType = expr.Type.PrimitiveTypeCode;
                if (toType != Cci.PrimitiveTypeCode.Boolean)
                {
                    _builder.EmitNumericConversion(Cci.PrimitiveTypeCode.Int32, toType, @checked: false);
                }
                return;
            }
 
            object consequenceLabel = new object();
            object doneLabel = new object();
 
            EmitCondBranch(expr.Condition, ref consequenceLabel, sense: true);
            EmitExpression(expr.Alternative, used);
 
            //
            // III.1.8.1.3 Merging stack states
            // . . . 
            // Let T be the type from the slot on the newly computed state and S
            // be the type from the corresponding slot on the previously stored state. The merged type, U, shall
            // be computed as follows (recall that S := T is the compatibility function defined
            // in §III.1.8.1.2.2):
            // 1. if S := T then U=S
            // 2. Otherwise, if T := S then U=T
            // 3. Otherwise, if S and T are both object types, then let V be the closest common supertype of S and T then U=V.
            // 4. Otherwise, the merge shall fail.
            //
            // When the target merge type is an interface that one or more classes implement, we emit static casts
            // from any class to the target interface.
            // You may think that it's possible to elide one of the static casts and have the CLR recognize
            // that merging a class and interface should succeed if the class implements the interface. Unfortunately,
            // it seems that either PEVerify or the runtime/JIT verifier will complain at you if you try to remove
            // either of the casts.
            //
            var mergeTypeOfAlternative = StackMergeType(expr.Alternative);
            if (used)
            {
                if (IsVarianceCast(expr.Type, mergeTypeOfAlternative))
                {
                    EmitStaticCast(expr.Type, expr.Syntax);
                    mergeTypeOfAlternative = expr.Type;
                }
                else if (expr.Type.IsInterfaceType() && !TypeSymbol.Equals(expr.Type, mergeTypeOfAlternative, TypeCompareKind.ConsiderEverything2))
                {
                    EmitStaticCast(expr.Type, expr.Syntax);
                }
            }
 
            _builder.EmitBranch(ILOpCode.Br, doneLabel);
            if (used)
            {
                // If we get to consequenceLabel, we should not have Alternative on stack, adjust for that.
                _builder.AdjustStack(-1);
            }
 
            _builder.MarkLabel(consequenceLabel);
            EmitExpression(expr.Consequence, used);
 
            if (used)
            {
                var mergeTypeOfConsequence = StackMergeType(expr.Consequence);
                if (IsVarianceCast(expr.Type, mergeTypeOfConsequence))
                {
                    EmitStaticCast(expr.Type, expr.Syntax);
                    mergeTypeOfConsequence = expr.Type;
                }
                else if (expr.Type.IsInterfaceType() && !TypeSymbol.Equals(expr.Type, mergeTypeOfConsequence, TypeCompareKind.ConsiderEverything2))
                {
                    EmitStaticCast(expr.Type, expr.Syntax);
                }
            }
 
            _builder.MarkLabel(doneLabel);
        }
 
        /// <summary>
        /// Emit code for a null-coalescing operator.
        /// </summary>
        /// <remarks>
        /// x ?? y becomes
        ///   push x
        ///   dup x
        ///   if pop != null goto LEFT_NOT_NULL
        ///     pop 
        ///     push y
        ///   LEFT_NOT_NULL:
        /// </remarks>
        private void EmitNullCoalescingOperator(BoundNullCoalescingOperator expr, bool used)
        {
            Debug.Assert(expr.LeftConversion is null, "coalesce with nontrivial left conversions are lowered into conditional.");
            Debug.Assert(expr.Type.IsReferenceType);
 
            EmitExpression(expr.LeftOperand, used: true);
 
            // See the notes about verification type merges in EmitConditionalOperator
            var mergeTypeOfLeftValue = StackMergeType(expr.LeftOperand);
            if (used)
            {
                if (IsVarianceCast(expr.Type, mergeTypeOfLeftValue))
                {
                    EmitStaticCast(expr.Type, expr.Syntax);
                    mergeTypeOfLeftValue = expr.Type;
                }
                else if (expr.Type.IsInterfaceType() && !TypeSymbol.Equals(expr.Type, mergeTypeOfLeftValue, TypeCompareKind.ConsiderEverything2))
                {
                    EmitStaticCast(expr.Type, expr.Syntax);
                }
 
                _builder.EmitOpCode(ILOpCode.Dup);
            }
 
            if (expr.Type.IsTypeParameter())
            {
                EmitBox(expr.Type, expr.LeftOperand.Syntax);
            }
 
            object ifLeftNotNullLabel = new object();
            _builder.EmitBranch(ILOpCode.Brtrue, ifLeftNotNullLabel);
 
            if (used)
            {
                _builder.EmitOpCode(ILOpCode.Pop);
            }
 
            EmitExpression(expr.RightOperand, used);
            if (used)
            {
                var mergeTypeOfRightValue = StackMergeType(expr.RightOperand);
                if (IsVarianceCast(expr.Type, mergeTypeOfRightValue))
                {
                    EmitStaticCast(expr.Type, expr.Syntax);
                    mergeTypeOfRightValue = expr.Type;
                }
            }
 
            _builder.MarkLabel(ifLeftNotNullLabel);
        }
 
        // Implicit casts are not emitted. As a result verifier may operate on a different 
        // types from the types of operands when performing stack merges in coalesce/conditional.
        // Such differences are in general irrelevant since merging rules work the same way
        // for base and derived types.
        //
        // Situation becomes more complicated with delegates, arrays and interfaces since they 
        // allow implicit casts from types that do not derive from them. In such cases
        // we may need to introduce static casts in the code to prod the verifier to the 
        // right direction
        //
        // This helper returns actual type of array|interface|delegate expression ignoring implicit 
        // casts. This would be the effective stack merge type in the verifier.
        // 
        // NOTE: In cases where stack merge type cannot be determined, we just return null.
        //       We still must assume that it can be an array, delegate or interface though.
        private TypeSymbol StackMergeType(BoundExpression expr)
        {
            // these cases are not interesting. Merge type is the same or derived. No difference.
            if (!(expr.Type.IsInterfaceType() || expr.Type.IsDelegateType()))
            {
                return expr.Type;
            }
 
            // Dig through casts. We only need to check for expressions that -
            // 1) implicit casts
            // 2) transparently return operands, so we need to dig deeper
            // 3) stack values
            switch (expr.Kind)
            {
                case BoundKind.Conversion:
                    var conversion = (BoundConversion)expr;
                    var conversionKind = conversion.ConversionKind;
                    Debug.Assert(conversionKind != ConversionKind.NullLiteral && conversionKind != ConversionKind.DefaultLiteral);
 
                    if (conversionKind.IsImplicitConversion() &&
                        conversionKind != ConversionKind.MethodGroup &&
                        conversionKind != ConversionKind.NullLiteral &&
                        conversionKind != ConversionKind.DefaultLiteral)
                    {
                        return StackMergeType(conversion.Operand);
                    }
                    break;
 
                case BoundKind.AssignmentOperator:
                    var assignment = (BoundAssignmentOperator)expr;
                    return StackMergeType(assignment.Right);
 
                case BoundKind.Sequence:
                    var sequence = (BoundSequence)expr;
                    return StackMergeType(sequence.Value);
 
                case BoundKind.Local:
                    var local = (BoundLocal)expr;
                    if (this.IsStackLocal(local.LocalSymbol))
                    {
                        // stack value, we cannot be sure what it is
                        return null;
                    }
                    break;
 
                case BoundKind.Dup:
                    // stack value, we cannot be sure what it is
                    return null;
            }
 
            return expr.Type;
        }
 
        // Although III.1.8.1.3 seems to imply that verifier understands variance casts.
        // It appears that verifier/JIT gets easily confused. 
        // So to not rely on whether that should work or not we will flag potentially 
        // "complicated" casts and make them static casts to ensure we are all on 
        // the same page with what type should be tracked.
        private static bool IsVarianceCast(TypeSymbol to, TypeSymbol from)
        {
            if (TypeSymbol.Equals(to, from, TypeCompareKind.ConsiderEverything2))
            {
                return false;
            }
 
            if ((object)from == null)
            {
                // from unknown type - this could be a variance conversion.
                return true;
            }
 
            // while technically variance casts, array conversions do not seem to be a problem
            // unless the element types are converted via variance.
            if (to.IsArray())
            {
                return IsVarianceCast(((ArrayTypeSymbol)to).ElementType, ((ArrayTypeSymbol)from).ElementType);
            }
 
            return (to.IsDelegateType() && !TypeSymbol.Equals(to, from, TypeCompareKind.ConsiderEverything2)) ||
                   (to.IsInterfaceType() && from.IsInterfaceType() && !from.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.ContainsKey((NamedTypeSymbol)to));
        }
 
        private void EmitStaticCast(TypeSymbol to, SyntaxNode syntax)
        {
            Debug.Assert(to.IsVerifierReference());
 
            // From ILGENREC::GenQMark
            // See VSWhidbey Bugs #49619 and 108643. If the destination type is an interface we need
            // to force a static cast to be generated for any cast result expressions. The static cast
            // should be done before the unifying jump so the code is verifiable and to allow the JIT to
            // optimize it away. NOTE: Since there is no staticcast instruction, we implement static cast
            // with a stloc / ldloc to a temporary.
            // Bug: VSWhidbey/49619
            // Bug: VSWhidbey/108643
            // Bug: Devdiv/42645
 
            var temp = AllocateTemp(to, syntax);
            _builder.EmitLocalStore(temp);
            _builder.EmitLocalLoad(temp);
            FreeTemp(temp);
        }
 
        private void EmitBox(TypeSymbol type, SyntaxNode syntaxNode)
        {
            Debug.Assert(!type.IsRefLikeType);
 
            _builder.EmitOpCode(ILOpCode.Box);
            EmitSymbolToken(type, syntaxNode);
        }
 
        private void EmitCalli(BoundFunctionPointerInvocation ptrInvocation, UseKind useKind)
        {
            EmitExpression(ptrInvocation.InvokedExpression, used: true);
            LocalDefinition temp = null;
            // The function pointer token must be the last thing on the stack before the
            // calli invocation, but we need to preserve left-to-right semantics of the
            // actual code. If there are arguments, therefore, we evaluate the code that
            // produces the function pointer token, store it in a local, evaluate the
            // arguments, then load that token again.
            if (ptrInvocation.Arguments.Length > 0)
            {
                temp = AllocateTemp(ptrInvocation.InvokedExpression.Type, ptrInvocation.Syntax);
                _builder.EmitLocalStore(temp);
            }
 
            FunctionPointerMethodSymbol method = ptrInvocation.FunctionPointer.Signature;
            EmitArguments(ptrInvocation.Arguments, method.Parameters, ptrInvocation.ArgumentRefKindsOpt);
            var stackBehavior = GetCallStackBehavior(ptrInvocation.FunctionPointer.Signature, ptrInvocation.Arguments);
 
            if (temp is object)
            {
                _builder.EmitLocalLoad(temp);
                FreeTemp(temp);
            }
 
            _builder.EmitOpCode(ILOpCode.Calli, stackBehavior);
            EmitSignatureToken(ptrInvocation.FunctionPointer, ptrInvocation.Syntax);
            EmitCallCleanup(ptrInvocation.Syntax, useKind, method);
        }
 
        private void EmitCallCleanup(SyntaxNode syntax, UseKind useKind, MethodSymbol method)
        {
            if (!method.ReturnsVoid)
            {
                EmitPopIfUnused(useKind != UseKind.Unused);
            }
            else if (_ilEmitStyle == ILEmitStyle.Debug)
            {
                // The only void methods with usable return values are constructors and the only
                // time we see them here, the return should be unused.
                Debug.Assert(useKind == UseKind.Unused, "Using the return value of a void method.");
                Debug.Assert(_method.GenerateDebugInfo, "Implied by this.emitSequencePoints");
 
                // DevDiv #15135.  When a method like System.Diagnostics.Debugger.Break() is called, the
                // debugger sees an event indicating that a user break (vs a breakpoint) has occurred.
                // When this happens, it uses ICorDebugILFrame.GetIP(out uint, out CorDebugMappingResult)
                // to determine the current instruction pointer.  This method returns the instruction
                // *after* the call.  The source location is then given by the last sequence point before
                // or on this instruction.  As a result, if the instruction after the call has its own
                // sequence point, then that sequence point will be used to determine the source location
                // and the debugging experience will be disrupted.  The easiest way to ensure that the next
                // instruction does not have a sequence point is to insert a nop.  Obviously, we only do this
                // if debugging is enabled and optimization is disabled.
 
                // From ILGENREC::genCall:
                //   We want to generate a NOP after CALL opcodes that end a statement so the debugger
                //   has better stepping behavior
 
                // CONSIDER: In the native compiler, there's an additional restriction on when this nop is
                // inserted.  It is quite complicated, but it basically seems to say that, if we thought
                // we could omit the temp-and-copy for a struct construction and it turned out that we
                // couldn't (perhaps because the assigned local was captured by a lambda), and if we're
                // not using the result of the constructor call (how can this even happen?), then we don't
                // want to insert the nop.  Since the consequence of not implementing this complicated logic
                // is an extra nop in debug code, this is likely not a priority.
 
                // CONSIDER: The native compiler also checks !(tree->flags & EXF_NODEBUGINFO).  We don't have
                // this mutable bit on our bound nodes, so we can't exactly match the behavior.  We might be
                // able to approximate the native behavior by inspecting call.WasCompilerGenerated, but it is
                // not in a reliable state after lowering.
 
                _builder.EmitOpCode(ILOpCode.Nop);
            }
 
            if (useKind == UseKind.UsedAsValue && method.RefKind != RefKind.None)
            {
                EmitLoadIndirect(method.ReturnType, syntax);
            }
            else if (useKind == UseKind.UsedAsAddress)
            {
                Debug.Assert(method.RefKind != RefKind.None);
            }
        }
 
        private void EmitLoadFunction(BoundFunctionPointerLoad load, bool used)
        {
            Debug.Assert(load.Type is { TypeKind: TypeKind.FunctionPointer });
 
            if (used)
            {
                if ((load.TargetMethod.IsAbstract || load.TargetMethod.IsVirtual) && load.TargetMethod.IsStatic)
                {
                    if (load.ConstrainedToTypeOpt is not { TypeKind: TypeKind.TypeParameter })
                    {
                        throw ExceptionUtilities.Unreachable();
                    }
 
                    _builder.EmitOpCode(ILOpCode.Constrained);
                    EmitSymbolToken(load.ConstrainedToTypeOpt, load.Syntax);
                }
 
                _builder.EmitOpCode(ILOpCode.Ldftn);
                EmitSymbolToken(load.TargetMethod, load.Syntax, optArgList: null);
            }
        }
    }
}