File: src\System\Reflection\Emit\RuntimeILGenerator.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.SymbolStore;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace System.Reflection.Emit
{
    internal class RuntimeILGenerator : ILGenerator
    {
        #region Const Members
        private const int DefaultSize = 16;
        private const int DefaultFixupArraySize = 8;
        private const int DefaultLabelArraySize = 4;
        private const int DefaultExceptionArraySize = 2;
        #endregion
 
        #region Internal Statics
        internal static T[] EnlargeArray<T>(T[] incoming)
        {
            return EnlargeArray(incoming, incoming.Length * 2);
        }
 
        internal static T[] EnlargeArray<T>(T[] incoming, int requiredSize)
        {
            Debug.Assert(incoming != null);
 
            T[] temp = new T[requiredSize];
            Array.Copy(incoming, temp, incoming.Length);
            return temp;
        }
 
        #endregion
 
        #region Internal Data Members
        private int m_length;
        private byte[] m_ILStream;
 
        private __LabelInfo[]? m_labelList;
        private int m_labelCount;
 
        private __FixupData[]? m_fixupData;
 
        private int m_fixupCount;
 
        private int[]? m_RelocFixupList;
        private int m_RelocFixupCount;
 
        private int m_exceptionCount;
        private int m_currExcStackCount;
        private __ExceptionInfo[]? m_exceptions;           // This is the list of all of the exceptions in this ILStream.
        private __ExceptionInfo[]? m_currExcStack;         // This is the stack of exceptions which we're currently in.
 
        internal ScopeTree m_ScopeTree;            // this variable tracks all debugging scope information
 
        internal MethodInfo m_methodBuilder;
        internal int m_localCount;
        internal SignatureHelper m_localSignature;
 
        private int m_curDepth; // Current stack depth, with -1 meaning unknown.
        private int m_targetDepth; // Stack depth at a target of the previous instruction (when it is branching).
        private int m_maxDepth; // Running max of the stack depth.
 
        // Adjustment to add to m_maxDepth for incorrect/invalid IL. For example, when branch instructions
        // with different stack depths target the same label.
        private long m_depthAdjustment;
 
        internal int CurrExcStackCount => m_currExcStackCount;
 
        internal __ExceptionInfo[]? CurrExcStack => m_currExcStack;
 
        #endregion
 
        #region Constructor
        // package private constructor. This code path is used when client create
        // ILGenerator through MethodBuilder.
        internal RuntimeILGenerator(MethodInfo methodBuilder) : this(methodBuilder, 64)
        {
        }
 
        internal RuntimeILGenerator(MethodInfo methodBuilder, int size)
        {
            Debug.Assert(methodBuilder != null);
            Debug.Assert(methodBuilder is MethodBuilder || methodBuilder is DynamicMethod);
 
            m_ILStream = new byte[Math.Max(size, DefaultSize)];
 
            // initialize the scope tree
            m_ScopeTree = new ScopeTree();
            m_methodBuilder = methodBuilder;
 
            // initialize local signature
            RuntimeMethodBuilder? mb = m_methodBuilder as RuntimeMethodBuilder;
            m_localSignature = SignatureHelper.GetLocalVarSigHelper(mb?.GetTypeBuilder().Module);
        }
 
        #endregion
 
        #region Internal Members
        internal virtual void RecordTokenFixup()
        {
            if (m_RelocFixupList == null)
            {
                m_RelocFixupList = new int[DefaultFixupArraySize];
            }
            else if (m_RelocFixupList.Length <= m_RelocFixupCount)
            {
                m_RelocFixupList = EnlargeArray(m_RelocFixupList);
            }
 
            m_RelocFixupList[m_RelocFixupCount++] = m_length;
        }
 
        internal void InternalEmit(OpCode opcode)
        {
            short opcodeValue = opcode.Value;
            if (opcode.Size != 1)
            {
                BinaryPrimitives.WriteInt16BigEndian(m_ILStream.AsSpan(m_length), opcodeValue);
                m_length += 2;
            }
            else
            {
                m_ILStream[m_length++] = (byte)opcodeValue;
            }
 
            UpdateStackSize(opcode, opcode.EvaluationStackDelta);
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void UpdateStackSize(OpCode opcode, int stackchange)
        {
            // Updates internal variables for keeping track of the stack size
            // requirements for the function.  stackchange specifies the amount
            // by which the stacksize needs to be updated.
 
            if (m_curDepth < 0)
            {
                // Current depth is "unknown". We get here when:
                // * this is unreachable code.
                // * the client uses explicit numeric offsets rather than Labels.
                m_curDepth = 0;
            }
 
            m_curDepth += stackchange;
            if (m_curDepth < 0)
            {
                // Stack underflow. Assume our previous depth computation was flawed.
                m_depthAdjustment -= m_curDepth;
                m_curDepth = 0;
            }
            else if (m_maxDepth < m_curDepth)
                m_maxDepth = m_curDepth;
            Debug.Assert(m_depthAdjustment >= 0);
            Debug.Assert(m_curDepth >= 0);
 
            // Record the stack depth at a "target" of this instruction.
            m_targetDepth = m_curDepth;
 
            // If the current instruction can't fall through, set the depth to unknown.
            if (opcode.EndsUncondJmpBlk())
                m_curDepth = -1;
        }
 
        private int GetMethodToken(MethodBase method, Type[]? optionalParameterTypes, bool useMethodDef)
        {
            return ((RuntimeModuleBuilder)m_methodBuilder.Module).GetMethodTokenInternal(method, optionalParameterTypes, useMethodDef);
        }
 
        internal SignatureHelper GetMemberRefSignature(
            CallingConventions call,
            Type? returnType,
            Type[]? parameterTypes,
            Type[]? optionalParameterTypes)
        {
            return ((RuntimeModuleBuilder)m_methodBuilder.Module).GetMemberRefSignature(call, returnType, parameterTypes, null, null, optionalParameterTypes, 0);
        }
 
        internal byte[]? BakeByteArray()
        {
            // BakeByteArray is an internal function designed to be called by MethodBuilder to do
            // all of the fixups and return a new byte array representing the byte stream with labels resolved, etc.
 
            if (m_currExcStackCount != 0)
            {
                throw new ArgumentException(SR.Argument_UnclosedExceptionBlock);
            }
 
            if (m_length == 0)
                return null;
 
            // Allocate space for the new array.
            byte[] newBytes = new byte[m_length];
 
            // Copy the data from the old array
            Array.Copy(m_ILStream, newBytes, m_length);
 
            // Do the fixups.
            // This involves iterating over all of the labels and
            // replacing them with their proper values.
            for (int i = 0; i < m_fixupCount; i++)
            {
                __FixupData fixupData = m_fixupData![i];
                int updateAddr = GetLabelPos(fixupData.m_fixupLabel) - (fixupData.m_fixupPos + fixupData.m_fixupInstSize);
 
                // Handle single byte instructions
                // Throw an exception if they're trying to store a jump in a single byte instruction that doesn't fit.
                if (fixupData.m_fixupInstSize == 1)
                {
                    // Verify that our one-byte arg will fit into a Signed Byte.
                    if (updateAddr < sbyte.MinValue || updateAddr > sbyte.MaxValue)
                    {
                        throw new NotSupportedException(SR.Format(SR.NotSupported_IllegalOneByteBranch, fixupData.m_fixupPos, updateAddr));
                    }
 
                    // Place the one-byte arg
                    newBytes[fixupData.m_fixupPos] = (byte)updateAddr;
                }
                else
                {
                    // Place the four-byte arg
                    BinaryPrimitives.WriteInt32LittleEndian(newBytes.AsSpan(fixupData.m_fixupPos), updateAddr);
                }
            }
            return newBytes;
        }
 
        internal __ExceptionInfo[]? GetExceptions()
        {
            if (m_currExcStackCount != 0)
            {
                throw new NotSupportedException(SR.Argument_UnclosedExceptionBlock);
            }
 
            if (m_exceptionCount == 0)
            {
                return null;
            }
 
            var temp = new __ExceptionInfo[m_exceptionCount];
            Array.Copy(m_exceptions!, temp, m_exceptionCount);
            SortExceptions(temp);
            return temp;
        }
 
        internal void EnsureCapacity(int size)
        {
            // Guarantees an array capable of holding at least size elements.
            if (m_length + size >= m_ILStream.Length)
            {
                IncreaseCapacity(size);
            }
        }
 
        private void IncreaseCapacity(int size)
        {
            byte[] temp = new byte[Math.Max(m_ILStream.Length * 2, m_length + size)];
            Array.Copy(m_ILStream, temp, m_ILStream.Length);
            m_ILStream = temp;
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void PutInteger4(int value)
        {
            BinaryPrimitives.WriteInt32LittleEndian(m_ILStream.AsSpan(m_length), value);
            m_length += 4;
        }
 
        private int GetLabelPos(Label lbl)
        {
            // Gets the position in the stream of a particular label.
            // Verifies that the label exists and that it has been given a value.
 
            int index = lbl.Id;
 
            if (index < 0 || index >= m_labelCount || m_labelList is null)
                throw new ArgumentException(SR.Argument_BadLabel);
 
            int pos = m_labelList[index].m_pos;
            if (pos < 0)
                throw new ArgumentException(SR.Argument_BadLabelContent);
 
            return pos;
        }
 
        private void AddFixup(Label lbl, int pos, int instSize)
        {
            // Notes the label, position, and instruction size of a new fixup.  Expands
            // all of the fixup arrays as appropriate.
 
            if (m_fixupData == null)
            {
                m_fixupData = new __FixupData[DefaultFixupArraySize];
            }
            else if (m_fixupData.Length <= m_fixupCount)
            {
                m_fixupData = EnlargeArray(m_fixupData);
            }
 
            m_fixupData[m_fixupCount++] = new __FixupData
            {
                m_fixupPos = pos,
                m_fixupLabel = lbl,
                m_fixupInstSize = instSize
            };
 
            int labelIndex = lbl.Id;
            if (labelIndex < 0 || labelIndex >= m_labelCount || m_labelList is null)
                throw new ArgumentException(SR.Argument_BadLabel);
 
            int depth = m_labelList[labelIndex].m_depth;
            int targetDepth = m_targetDepth;
            Debug.Assert(depth >= -1);
            Debug.Assert(targetDepth >= -1);
            if (depth < targetDepth)
            {
                // Either unknown depth for this label or this branch location has a larger depth than previously recorded.
                // In the latter case, the IL is (likely) invalid, but we just compensate for it.
                if (depth >= 0)
                    m_depthAdjustment += targetDepth - depth;
                m_labelList[labelIndex].m_depth = targetDepth;
            }
        }
 
        internal int GetMaxStackSize()
        {
            // Limit the computed max stack to 2^16 - 1, since the value is mod`ed by 2^16 by other code.
            Debug.Assert(m_depthAdjustment >= 0);
            return (int)Math.Min(ushort.MaxValue, m_maxDepth + m_depthAdjustment);
        }
 
        private static void SortExceptions(__ExceptionInfo[] exceptions)
        {
            // In order to call exceptions properly we have to sort them in ascending order by their end position.
            // Just a cheap insertion sort.  We don't expect many exceptions (<10), where InsertionSort beats QuickSort.
            // If we have more exceptions than this in real life, we should consider moving to a QuickSort.
 
            for (int i = 0; i < exceptions.Length; i++)
            {
                int least = i;
                for (int j = i + 1; j < exceptions.Length; j++)
                {
                    if (exceptions[least].IsInner(exceptions[j]))
                    {
                        least = j;
                    }
                }
                __ExceptionInfo temp = exceptions[i];
                exceptions[i] = exceptions[least];
                exceptions[least] = temp;
            }
        }
 
        internal int[]? GetTokenFixups()
        {
            if (m_RelocFixupCount == 0)
            {
                Debug.Assert(m_RelocFixupList == null);
                return null;
            }
 
            int[] narrowTokens = new int[m_RelocFixupCount];
            Array.Copy(m_RelocFixupList!, narrowTokens, m_RelocFixupCount);
            return narrowTokens;
        }
        #endregion
 
        #region Public Members
 
        #region Emit
        public override void Emit(OpCode opcode)
        {
            EnsureCapacity(3);
            InternalEmit(opcode);
        }
 
        public override void Emit(OpCode opcode, byte arg)
        {
            EnsureCapacity(4);
            InternalEmit(opcode);
            m_ILStream[m_length++] = arg;
        }
 
        public override void Emit(OpCode opcode, short arg)
        {
            // Puts opcode onto the stream of instructions followed by arg
            EnsureCapacity(5);
            InternalEmit(opcode);
            BinaryPrimitives.WriteInt16LittleEndian(m_ILStream.AsSpan(m_length), arg);
            m_length += 2;
        }
 
        public override void Emit(OpCode opcode, int arg)
        {
            // Special-case several opcodes that have shorter variants for common values.
            if (opcode.Equals(OpCodes.Ldc_I4))
            {
                if (arg >= -1 && arg <= 8)
                {
                    opcode = arg switch
                    {
                        -1 => OpCodes.Ldc_I4_M1,
                        0 => OpCodes.Ldc_I4_0,
                        1 => OpCodes.Ldc_I4_1,
                        2 => OpCodes.Ldc_I4_2,
                        3 => OpCodes.Ldc_I4_3,
                        4 => OpCodes.Ldc_I4_4,
                        5 => OpCodes.Ldc_I4_5,
                        6 => OpCodes.Ldc_I4_6,
                        7 => OpCodes.Ldc_I4_7,
                        _ => OpCodes.Ldc_I4_8,
                    };
                    Emit(opcode);
                    return;
                }
 
                if (arg >= -128 && arg <= 127)
                {
                    Emit(OpCodes.Ldc_I4_S, (sbyte)arg);
                    return;
                }
            }
            else if (opcode.Equals(OpCodes.Ldarg))
            {
                if ((uint)arg <= 3)
                {
                    Emit(arg switch
                    {
                        0 => OpCodes.Ldarg_0,
                        1 => OpCodes.Ldarg_1,
                        2 => OpCodes.Ldarg_2,
                        _ => OpCodes.Ldarg_3,
                    });
                    return;
                }
 
                if ((uint)arg <= byte.MaxValue)
                {
                    Emit(OpCodes.Ldarg_S, (byte)arg);
                    return;
                }
 
                if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode
                {
                    Emit(OpCodes.Ldarg, (short)arg);
                    return;
                }
            }
            else if (opcode.Equals(OpCodes.Ldarga))
            {
                if ((uint)arg <= byte.MaxValue)
                {
                    Emit(OpCodes.Ldarga_S, (byte)arg);
                    return;
                }
 
                if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode
                {
                    Emit(OpCodes.Ldarga, (short)arg);
                    return;
                }
            }
            else if (opcode.Equals(OpCodes.Starg))
            {
                if ((uint)arg <= byte.MaxValue)
                {
                    Emit(OpCodes.Starg_S, (byte)arg);
                    return;
                }
 
                if ((uint)arg <= ushort.MaxValue) // this will be true except on misuse of the opcode
                {
                    Emit(OpCodes.Starg, (short)arg);
                    return;
                }
            }
 
            // For everything else, put the opcode followed by the arg onto the stream of instructions.
            EnsureCapacity(7);
            InternalEmit(opcode);
            PutInteger4(arg);
        }
 
        public override void Emit(OpCode opcode, MethodInfo meth)
        {
            ArgumentNullException.ThrowIfNull(meth);
 
            if (opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj))
            {
                EmitCall(opcode, meth, null);
            }
            else
            {
                // Reflection doesn't distinguish between these two concepts:
                //   1. A generic method definition: Foo`1
                //   2. A generic method definition instantiated over its own generic arguments: Foo`1<!!0>
                // In RefEmit, we always want 1 for Ld* opcodes and 2 for Call* and Newobj.
                bool useMethodDef = opcode.Equals(OpCodes.Ldtoken) || opcode.Equals(OpCodes.Ldftn) || opcode.Equals(OpCodes.Ldvirtftn);
                int tk = GetMethodToken(meth, null, useMethodDef);
 
                EnsureCapacity(7);
                InternalEmit(opcode);
 
                UpdateStackSize(opcode, 0);
                RecordTokenFixup();
                PutInteger4(tk);
            }
        }
 
        public override void EmitCalli(OpCode opcode, CallingConventions callingConvention,
            Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes)
        {
            int stackchange = 0;
            if (optionalParameterTypes != null)
            {
                if ((callingConvention & CallingConventions.VarArgs) == 0)
                {
                    // Client should not supply optional parameter in default calling convention
                    throw new InvalidOperationException(SR.InvalidOperation_NotAVarArgCallingConvention);
                }
            }
 
            ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module;
            SignatureHelper sig = GetMemberRefSignature(callingConvention,
                returnType,
                parameterTypes,
                optionalParameterTypes);
 
            EnsureCapacity(7);
            Emit(OpCodes.Calli);
 
            // If there is a non-void return type, push one.
            if (returnType != typeof(void))
                stackchange++;
            // Pop off arguments if any.
            if (parameterTypes != null)
                stackchange -= parameterTypes.Length;
            // Pop off vararg arguments.
            if (optionalParameterTypes != null)
                stackchange -= optionalParameterTypes.Length;
            // Pop the this parameter if the method has a this parameter.
            if ((callingConvention & CallingConventions.HasThis) == CallingConventions.HasThis)
                stackchange--;
            // Pop the native function pointer.
            stackchange--;
            UpdateStackSize(OpCodes.Calli, stackchange);
 
            RecordTokenFixup();
            PutInteger4(modBuilder.GetSignatureMetadataToken(sig));
        }
 
        public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes)
        {
            int stackchange = 0;
            int cParams = 0;
 
            ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module;
 
            if (parameterTypes != null)
            {
                cParams = parameterTypes.Length;
            }
 
            SignatureHelper sig = SignatureHelper.GetMethodSigHelper(
                modBuilder,
                unmanagedCallConv,
                returnType);
 
            if (parameterTypes != null)
            {
                for (int i = 0; i < cParams; i++)
                {
                    sig.AddArgument(parameterTypes[i]);
                }
            }
 
            // If there is a non-void return type, push one.
            if (returnType != typeof(void))
                stackchange++;
 
            // Pop off arguments if any.
            if (parameterTypes != null)
                stackchange -= cParams;
 
            // Pop the native function pointer.
            stackchange--;
            UpdateStackSize(OpCodes.Calli, stackchange);
 
            EnsureCapacity(7);
            Emit(OpCodes.Calli);
            RecordTokenFixup();
            PutInteger4(modBuilder.GetSignatureMetadataToken(sig));
        }
 
        public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes)
        {
            ArgumentNullException.ThrowIfNull(methodInfo);
 
            if (!(opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj)))
                throw new ArgumentException(SR.Argument_NotMethodCallOpcode, nameof(opcode));
 
            int stackchange = 0;
            int tk = GetMethodToken(methodInfo, optionalParameterTypes, false);
 
            EnsureCapacity(7);
            InternalEmit(opcode);
 
            // Push the return value if there is one.
            if (methodInfo.ReturnType != typeof(void))
                stackchange++;
            // Pop the parameters.
            Type[] parameters = methodInfo.GetParameterTypes();
            if (parameters != null)
                stackchange -= parameters.Length;
 
            // Pop the this parameter if the method is non-static and the
            // instruction is not newobj.
            if (methodInfo is not SymbolMethod && !methodInfo.IsStatic && !opcode.Equals(OpCodes.Newobj))
                stackchange--;
            // Pop the optional parameters off the stack.
            if (optionalParameterTypes != null)
                stackchange -= optionalParameterTypes.Length;
            UpdateStackSize(opcode, stackchange);
 
            RecordTokenFixup();
            PutInteger4(tk);
        }
 
        public override void Emit(OpCode opcode, SignatureHelper signature)
        {
            ArgumentNullException.ThrowIfNull(signature);
 
            int stackchange = 0;
            ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module;
            int sig = modBuilder.GetSignatureMetadataToken(signature);
 
            int tempVal = sig;
 
            EnsureCapacity(7);
            InternalEmit(opcode);
 
            // The only IL instruction that has VarPop behaviour, that takes a
            // Signature token as a parameter is calli.  Pop the parameters and
            // the native function pointer.  To be conservative, do not pop the
            // this pointer since this information is not easily derived from
            // SignatureHelper.
            if (opcode.StackBehaviourPop == StackBehaviour.Varpop)
            {
                Debug.Assert(opcode.Equals(OpCodes.Calli),
                                "Unexpected opcode encountered for StackBehaviour VarPop.");
                // Pop the arguments..
                stackchange -= signature.ArgumentCount;
                // Pop native function pointer off the stack.
                stackchange--;
                UpdateStackSize(opcode, stackchange);
            }
 
            RecordTokenFixup();
            PutInteger4(tempVal);
        }
 
        public override void Emit(OpCode opcode, ConstructorInfo con)
        {
            ArgumentNullException.ThrowIfNull(con);
 
            int stackchange = 0;
 
            // Constructors cannot be generic so the value of UseMethodDef doesn't matter.
            int tk = GetMethodToken(con, null, true);
 
            EnsureCapacity(7);
            InternalEmit(opcode);
 
            // Make a conservative estimate by assuming a return type and no
            // this parameter.
            if (opcode.StackBehaviourPush == StackBehaviour.Varpush)
            {
                // Instruction must be one of call or callvirt.
                Debug.Assert(opcode.Equals(OpCodes.Call) ||
                                opcode.Equals(OpCodes.Callvirt),
                                "Unexpected opcode encountered for StackBehaviour of VarPush.");
                stackchange++;
            }
            if (opcode.StackBehaviourPop == StackBehaviour.Varpop)
            {
                // Instruction must be one of call, callvirt or newobj.
                Debug.Assert(opcode.Equals(OpCodes.Call) ||
                                opcode.Equals(OpCodes.Callvirt) ||
                                opcode.Equals(OpCodes.Newobj),
                                "Unexpected opcode encountered for StackBehaviour of VarPop.");
 
                Type[] parameters = con.GetParameterTypes();
                if (parameters != null)
                    stackchange -= parameters.Length;
            }
            UpdateStackSize(opcode, stackchange);
 
            RecordTokenFixup();
            PutInteger4(tk);
        }
 
        public override void Emit(OpCode opcode, Type cls)
        {
            // Puts opcode onto the stream and then the metadata token represented
            // by cls.  The location of cls is recorded so that the token can be
            // patched if necessary when persisting the module to a PE.
 
            RuntimeModuleBuilder modBuilder = (RuntimeModuleBuilder)m_methodBuilder.Module;
            bool getGenericDefinition = (opcode == OpCodes.Ldtoken && cls != null && cls.IsGenericTypeDefinition);
            int tempVal = modBuilder.GetTypeTokenInternal(cls!, getGenericDefinition);
 
            EnsureCapacity(7);
            InternalEmit(opcode);
            RecordTokenFixup();
            PutInteger4(tempVal);
        }
 
        public override void Emit(OpCode opcode, long arg)
        {
            EnsureCapacity(11);
            InternalEmit(opcode);
            BinaryPrimitives.WriteInt64LittleEndian(m_ILStream.AsSpan(m_length), arg);
            m_length += 8;
        }
 
        public override void Emit(OpCode opcode, float arg)
        {
            EnsureCapacity(7);
            InternalEmit(opcode);
            BinaryPrimitives.WriteInt32LittleEndian(m_ILStream.AsSpan(m_length), BitConverter.SingleToInt32Bits(arg));
            m_length += 4;
        }
 
        public override void Emit(OpCode opcode, double arg)
        {
            EnsureCapacity(11);
            InternalEmit(opcode);
            BinaryPrimitives.WriteInt64LittleEndian(m_ILStream.AsSpan(m_length), BitConverter.DoubleToInt64Bits(arg));
            m_length += 8;
        }
 
        public override void Emit(OpCode opcode, Label label)
        {
            // Puts opcode onto the stream and leaves space to include label
            // when fixups are done.  Labels are created using ILGenerator.DefineLabel and
            // their location within the stream is fixed by using ILGenerator.MarkLabel.
            // If a single-byte instruction (designated by the _S suffix in OpCodes.cs) is used,
            // the label can represent a jump of at most 127 bytes along the stream.
            //
            // opcode must represent a branch instruction (although we don't explicitly
            // verify this).  Since branches are relative instructions, label will be replaced with the
            // correct offset to branch during the fixup process.
 
            EnsureCapacity(7);
 
            InternalEmit(opcode);
            if (OpCodes.TakesSingleByteArgument(opcode))
            {
                AddFixup(label, m_length++, 1);
            }
            else
            {
                AddFixup(label, m_length, 4);
                m_length += 4;
            }
        }
 
        public override void Emit(OpCode opcode, Label[] labels)
        {
            ArgumentNullException.ThrowIfNull(labels);
 
            // Emitting a switch table
 
            int i;
            int remaining;                  // number of bytes remaining for this switch instruction to be subtracted
            // for computing the offset
 
            int count = labels.Length;
 
            EnsureCapacity(count * 4 + 7);
            InternalEmit(opcode);
            PutInteger4(count);
            for (remaining = count * 4, i = 0; remaining > 0; remaining -= 4, i++)
            {
                AddFixup(labels[i], m_length, remaining);
                m_length += 4;
            }
        }
 
        public override void Emit(OpCode opcode, FieldInfo field)
        {
            ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module;
            int tempVal = modBuilder.GetFieldMetadataToken(field);
            EnsureCapacity(7);
            InternalEmit(opcode);
            RecordTokenFixup();
            PutInteger4(tempVal);
        }
 
        public override void Emit(OpCode opcode, string str)
        {
            // Puts the opcode onto the IL stream followed by the metadata token
            // represented by str.  The location of str is recorded for future
            // fixups if the module is persisted to a PE.
 
            ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module;
            int tempVal = modBuilder.GetStringMetadataToken(str);
            EnsureCapacity(7);
            InternalEmit(opcode);
            PutInteger4(tempVal);
        }
 
        public override void Emit(OpCode opcode, LocalBuilder local)
        {
            ArgumentNullException.ThrowIfNull(local);
 
            // Puts the opcode onto the IL stream followed by the information for local variable local.
            int tempVal = local.LocalIndex;
            if (local is not RuntimeLocalBuilder localBuilder || localBuilder.GetMethodBuilder() != m_methodBuilder)
            {
                throw new ArgumentException(SR.Argument_UnmatchedMethodForLocal, nameof(local));
            }
            // If the instruction is a ldloc, ldloca a stloc, morph it to the optimal form.
            if (opcode.Equals(OpCodes.Ldloc))
            {
                switch (tempVal)
                {
                    case 0:
                        opcode = OpCodes.Ldloc_0;
                        break;
                    case 1:
                        opcode = OpCodes.Ldloc_1;
                        break;
                    case 2:
                        opcode = OpCodes.Ldloc_2;
                        break;
                    case 3:
                        opcode = OpCodes.Ldloc_3;
                        break;
                    default:
                        if (tempVal <= 255)
                            opcode = OpCodes.Ldloc_S;
                        break;
                }
            }
            else if (opcode.Equals(OpCodes.Stloc))
            {
                switch (tempVal)
                {
                    case 0:
                        opcode = OpCodes.Stloc_0;
                        break;
                    case 1:
                        opcode = OpCodes.Stloc_1;
                        break;
                    case 2:
                        opcode = OpCodes.Stloc_2;
                        break;
                    case 3:
                        opcode = OpCodes.Stloc_3;
                        break;
                    default:
                        if (tempVal <= 255)
                            opcode = OpCodes.Stloc_S;
                        break;
                }
            }
            else if (opcode.Equals(OpCodes.Ldloca))
            {
                if (tempVal <= 255)
                    opcode = OpCodes.Ldloca_S;
            }
 
            EnsureCapacity(7);
            InternalEmit(opcode);
 
            if (opcode.OperandType == OperandType.InlineNone)
                return;
 
            if (!OpCodes.TakesSingleByteArgument(opcode))
            {
                BinaryPrimitives.WriteInt16LittleEndian(m_ILStream.AsSpan(m_length), (short)tempVal);
                m_length += 2;
            }
            else
            {
                // Handle stloc_1, ldloc_1
                if (tempVal > byte.MaxValue)
                {
                    throw new InvalidOperationException(SR.InvalidOperation_BadInstructionOrIndexOutOfBound);
                }
                m_ILStream[m_length++] = (byte)tempVal;
            }
        }
        #endregion
 
        #region Exceptions
        public override Label BeginExceptionBlock()
        {
            // Begin an Exception block.  Creating an Exception block records some information,
            // but does not actually emit any IL onto the stream.  Exceptions should be created and
            // marked in the following form:
            //
            // Emit Some IL
            // BeginExceptionBlock
            // Emit the IL which should appear within the "try" block
            // BeginCatchBlock
            // Emit the IL which should appear within the "catch" block
            // Optional: BeginCatchBlock (this can be repeated an arbitrary number of times
            // EndExceptionBlock
 
            // Delay init
            m_exceptions ??= new __ExceptionInfo[DefaultExceptionArraySize];
            m_currExcStack ??= new __ExceptionInfo[DefaultExceptionArraySize];
 
            if (m_exceptionCount >= m_exceptions.Length)
            {
                m_exceptions = EnlargeArray(m_exceptions);
            }
 
            if (m_currExcStackCount >= m_currExcStack.Length)
            {
                m_currExcStack = EnlargeArray(m_currExcStack);
            }
 
            Label endLabel = DefineLabel(0);
            __ExceptionInfo exceptionInfo = new __ExceptionInfo(m_length, endLabel);
 
            // add the exception to the tracking list
            m_exceptions[m_exceptionCount++] = exceptionInfo;
 
            // Make this exception the current active exception
            m_currExcStack[m_currExcStackCount++] = exceptionInfo;
 
            // Stack depth for "try" starts at zero.
            m_curDepth = 0;
 
            return endLabel;
        }
 
        public override void EndExceptionBlock()
        {
            if (m_currExcStackCount == 0)
            {
                throw new NotSupportedException(SR.Argument_NotInExceptionBlock);
            }
 
            // Pop the current exception block
            __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1];
            m_currExcStack[--m_currExcStackCount] = null!;
 
            Label endLabel = current.GetEndLabel();
            int state = current.GetCurrentState();
 
            if (state == __ExceptionInfo.State_Filter ||
                state == __ExceptionInfo.State_Try)
            {
                throw new InvalidOperationException(SR.Argument_BadExceptionCodeGen);
            }
 
            if (state == __ExceptionInfo.State_Catch)
            {
                Emit(OpCodes.Leave, endLabel);
            }
            else if (state == __ExceptionInfo.State_Finally || state == __ExceptionInfo.State_Fault)
            {
                Emit(OpCodes.Endfinally);
            }
 
            // Check if we've already set this label.
            // The only reason why we might have set this is if we have a finally block.
 
            Label label = m_labelList![endLabel.Id].m_pos != -1
                ? current.m_finallyEndLabel
                : endLabel;
 
            MarkLabel(label);
 
            current.Done(m_length);
        }
 
        public override void BeginExceptFilterBlock()
        {
            // Begins an exception filter block.  Emits a branch instruction to the end of the current exception block.
 
            if (m_currExcStackCount == 0)
                throw new NotSupportedException(SR.Argument_NotInExceptionBlock);
 
            __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1];
 
            Emit(OpCodes.Leave, current.GetEndLabel());
 
            current.MarkFilterAddr(m_length);
 
            // Stack depth for "filter" starts at one.
            m_curDepth = 1;
        }
 
        public override void BeginCatchBlock(Type? exceptionType)
        {
            // Begins a catch block.  Emits a branch instruction to the end of the current exception block.
 
            if (m_currExcStackCount == 0)
            {
                throw new NotSupportedException(SR.Argument_NotInExceptionBlock);
            }
            __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1];
 
            if (current.GetCurrentState() == __ExceptionInfo.State_Filter)
            {
                if (exceptionType != null)
                {
                    throw new ArgumentException(SR.Argument_ShouldNotSpecifyExceptionType);
                }
 
                Emit(OpCodes.Endfilter);
            }
            else
            {
                // execute this branch if previous clause is Catch or Fault
                ArgumentNullException.ThrowIfNull(exceptionType);
 
                Emit(OpCodes.Leave, current.GetEndLabel());
            }
 
            current.MarkCatchAddr(m_length, exceptionType);
 
            // Stack depth for "catch" starts at one.
            m_curDepth = 1;
        }
 
        public override void BeginFaultBlock()
        {
            if (m_currExcStackCount == 0)
            {
                throw new NotSupportedException(SR.Argument_NotInExceptionBlock);
            }
            __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1];
 
            // emit the leave for the clause before this one.
            Emit(OpCodes.Leave, current.GetEndLabel());
 
            current.MarkFaultAddr(m_length);
 
            // Stack depth for "fault" starts at zero.
            m_curDepth = 0;
        }
 
        public override void BeginFinallyBlock()
        {
            if (m_currExcStackCount == 0)
            {
                throw new NotSupportedException(SR.Argument_NotInExceptionBlock);
            }
            __ExceptionInfo current = m_currExcStack![m_currExcStackCount - 1];
            int state = current.GetCurrentState();
            Label endLabel = current.GetEndLabel();
            int catchEndAddr = 0;
            if (state != __ExceptionInfo.State_Try)
            {
                // generate leave for any preceding catch clause
                Emit(OpCodes.Leave, endLabel);
                catchEndAddr = m_length;
            }
 
            MarkLabel(endLabel);
 
            Label finallyEndLabel = DefineLabel(0);
            current.SetFinallyEndLabel(finallyEndLabel);
 
            // generate leave for try clause
            Emit(OpCodes.Leave, finallyEndLabel);
            if (catchEndAddr == 0)
                catchEndAddr = m_length;
            current.MarkFinallyAddr(m_length, catchEndAddr);
 
            // Stack depth for "finally" starts at zero.
            m_curDepth = 0;
        }
 
        #endregion
 
        #region Labels
        public override Label DefineLabel()
        {
            // We don't know the stack depth at the label yet, so set it to -1.
            return DefineLabel(-1);
        }
 
        private Label DefineLabel(int depth)
        {
            // Declares a new Label.  This is just a token and does not yet represent any particular location
            // within the stream.  In order to set the position of the label within the stream, you must call
            // Mark Label.
            Debug.Assert(depth >= -1);
 
            // Delay init the label array in case we dont use it
            m_labelList ??= new __LabelInfo[DefaultLabelArraySize];
 
            if (m_labelCount >= m_labelList.Length)
            {
                m_labelList = EnlargeArray(m_labelList);
            }
            m_labelList[m_labelCount].m_pos = -1;
            m_labelList[m_labelCount].m_depth = depth;
            return new Label(m_labelCount++);
        }
 
        public override void MarkLabel(Label loc)
        {
            // Defines a label by setting the position where that label is found within the stream.
            // Does not allow a label to be defined more than once.
 
            int labelIndex = loc.Id;
 
            // This should only happen if a label from another generator is used with this one.
            if (m_labelList is null || labelIndex < 0 || labelIndex >= m_labelList.Length)
            {
                throw new ArgumentException(SR.Argument_InvalidLabel);
            }
 
            if (m_labelList[labelIndex].m_pos != -1)
            {
                throw new ArgumentException(SR.Argument_RedefinedLabel);
            }
 
            m_labelList[labelIndex].m_pos = m_length;
 
            int depth = m_labelList[labelIndex].m_depth;
            if (depth < 0)
            {
                // Unknown depth for this label, indicating that it hasn't been used yet.
                // If m_curDepth is unknown, we're in the Backward branch constraint case. See ECMA-335 III.1.7.5.
                // The m_depthAdjustment field will compensate for violations of this constraint, as we
                // discover them. That is, here we assume a depth of zero. If a (later) branch to this label
                // has a positive stack depth, we'll record that as the new depth and add the delta into
                // m_depthAdjustment.
                if (m_curDepth < 0)
                    m_curDepth = 0;
                m_labelList[labelIndex].m_depth = m_curDepth;
            }
            else if (depth < m_curDepth)
            {
                // A branch location with smaller stack targets this label. In this case, the IL is
                // invalid, but we just compensate for it.
                m_depthAdjustment += m_curDepth - depth;
                m_labelList[labelIndex].m_depth = m_curDepth;
            }
            else if (depth > m_curDepth)
            {
                // Either the current depth is unknown, or a branch location with larger stack targets
                // this label, so the IL is invalid. In either case, just adjust the current depth.
                m_curDepth = depth;
            }
        }
 
        #endregion
 
        #region Debug API
        public override LocalBuilder DeclareLocal(Type localType, bool pinned)
        {
            // Declare a local of type "local". The current active lexical scope
            // will be the scope that local will live.
 
            if (m_methodBuilder is not RuntimeMethodBuilder methodBuilder)
                throw new NotSupportedException();
 
            if (methodBuilder.IsTypeCreated())
            {
                // cannot change method after its containing type has been created
                throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated);
            }
 
            ArgumentNullException.ThrowIfNull(localType);
 
            if (methodBuilder.m_bIsBaked)
            {
                throw new InvalidOperationException(SR.InvalidOperation_MethodBaked);
            }
 
            // add the localType to local signature
            m_localSignature.AddArgument(localType, pinned);
 
            return new RuntimeLocalBuilder(m_localCount++, localType, methodBuilder, pinned);
        }
 
        public override void UsingNamespace(string usingNamespace)
        {
            // Specifying the namespace to be used in evaluating locals and watches
            // for the current active lexical scope.
 
            ArgumentException.ThrowIfNullOrEmpty(usingNamespace);
 
            if (m_methodBuilder is not RuntimeMethodBuilder methodBuilder)
                throw new NotSupportedException();
 
            int index = ((RuntimeILGenerator)methodBuilder.GetILGenerator()).m_ScopeTree.GetCurrentActiveScopeIndex();
            if (index == -1)
            {
                methodBuilder.m_localSymInfo!.AddUsingNamespace(usingNamespace);
            }
            else
            {
                m_ScopeTree.AddUsingNamespaceToCurrentScope(usingNamespace);
            }
        }
 
        public override void BeginScope()
        {
            m_ScopeTree.AddScopeInfo(ScopeAction.Open, m_length);
        }
 
        public override void EndScope()
        {
            m_ScopeTree.AddScopeInfo(ScopeAction.Close, m_length);
        }
 
        public override int ILOffset => m_length;
 
        #endregion
 
        #endregion
    }
 
    internal struct __LabelInfo
    {
        internal int m_pos; // Position in the il stream, with -1 meaning unknown.
        internal int m_depth; // Stack depth, with -1 meaning unknown.
    }
 
    internal struct __FixupData
    {
        internal Label m_fixupLabel;
        internal int m_fixupPos;
 
        internal int m_fixupInstSize;
    }
 
    internal sealed class __ExceptionInfo
    {
        internal const int None = 0x0000;  // COR_ILEXCEPTION_CLAUSE_NONE
        internal const int Filter = 0x0001;  // COR_ILEXCEPTION_CLAUSE_FILTER
        internal const int Finally = 0x0002;  // COR_ILEXCEPTION_CLAUSE_FINALLY
        internal const int Fault = 0x0004;  // COR_ILEXCEPTION_CLAUSE_FAULT
        internal const int PreserveStack = 0x0004;  // COR_ILEXCEPTION_CLAUSE_PRESERVESTACK
 
        internal const int State_Try = 0;
        internal const int State_Filter = 1;
        internal const int State_Catch = 2;
        internal const int State_Finally = 3;
        internal const int State_Fault = 4;
        internal const int State_Done = 5;
 
        internal int m_startAddr;
        internal int[] m_filterAddr;
        internal int[] m_catchAddr;
        internal int[] m_catchEndAddr;
        internal int[] m_type;
        internal Type[] m_catchClass;
        internal Label m_endLabel;
        internal Label m_finallyEndLabel;
        internal int m_endAddr;
        internal int m_endFinally;
        internal int m_currentCatch;
 
        private int m_currentState;
 
        internal __ExceptionInfo(int startAddr, Label endLabel)
        {
            m_startAddr = startAddr;
            m_endAddr = -1;
            m_filterAddr = new int[4];
            m_catchAddr = new int[4];
            m_catchEndAddr = new int[4];
            m_catchClass = new Type[4];
            m_currentCatch = 0;
            m_endLabel = endLabel;
            m_type = new int[4];
            m_endFinally = -1;
            m_currentState = State_Try;
        }
 
        private void MarkHelper(
            int catchorfilterAddr,      // the starting address of a clause
            int catchEndAddr,           // the end address of a previous catch clause. Only use when finally is following a catch
            Type? catchClass,             // catch exception type
            int type)                   // kind of clause
        {
            int currentCatch = m_currentCatch;
            if (currentCatch >= m_catchAddr.Length)
            {
                m_filterAddr = RuntimeILGenerator.EnlargeArray(m_filterAddr);
                m_catchAddr = RuntimeILGenerator.EnlargeArray(m_catchAddr);
                m_catchEndAddr = RuntimeILGenerator.EnlargeArray(m_catchEndAddr);
                m_catchClass = RuntimeILGenerator.EnlargeArray(m_catchClass);
                m_type = RuntimeILGenerator.EnlargeArray(m_type);
            }
            if (type == Filter)
            {
                m_type[currentCatch] = type;
                m_filterAddr[currentCatch] = catchorfilterAddr;
                m_catchAddr[currentCatch] = -1;
                if (currentCatch > 0)
                {
                    Debug.Assert(m_catchEndAddr[currentCatch - 1] == -1, "m_catchEndAddr[m_currentCatch-1] == -1");
                    m_catchEndAddr[currentCatch - 1] = catchorfilterAddr;
                }
            }
            else
            {
                // catch or Fault clause
                m_catchClass[currentCatch] = catchClass!;
                if (m_type[currentCatch] != Filter)
                {
                    m_type[currentCatch] = type;
                }
                m_catchAddr[currentCatch] = catchorfilterAddr;
                if (currentCatch > 0)
                {
                    if (m_type[currentCatch] != Filter)
                    {
                        Debug.Assert(m_catchEndAddr[currentCatch - 1] == -1, "m_catchEndAddr[m_currentCatch-1] == -1");
                        m_catchEndAddr[currentCatch - 1] = catchEndAddr;
                    }
                }
                m_catchEndAddr[currentCatch] = -1;
                m_currentCatch++;
            }
 
            if (m_endAddr == -1)
            {
                m_endAddr = catchorfilterAddr;
            }
        }
 
        internal void MarkFilterAddr(int filterAddr)
        {
            m_currentState = State_Filter;
            MarkHelper(filterAddr, filterAddr, null, Filter);
        }
 
        internal void MarkFaultAddr(int faultAddr)
        {
            m_currentState = State_Fault;
            MarkHelper(faultAddr, faultAddr, null, Fault);
        }
 
        internal void MarkCatchAddr(int catchAddr, Type? catchException)
        {
            m_currentState = State_Catch;
            MarkHelper(catchAddr, catchAddr, catchException, None);
        }
 
        internal void MarkFinallyAddr(int finallyAddr, int endCatchAddr)
        {
            if (m_endFinally != -1)
            {
                throw new ArgumentException(SR.Argument_TooManyFinallyClause);
            }
 
            m_currentState = State_Finally;
            m_endFinally = finallyAddr;
            MarkHelper(finallyAddr, endCatchAddr, null, Finally);
        }
 
        internal void Done(int endAddr)
        {
            Debug.Assert(m_currentCatch > 0);
            Debug.Assert(m_catchAddr[m_currentCatch - 1] > 0);
            Debug.Assert(m_catchEndAddr[m_currentCatch - 1] == -1);
            m_catchEndAddr[m_currentCatch - 1] = endAddr;
            m_currentState = State_Done;
        }
 
        internal int GetStartAddress()
        {
            return m_startAddr;
        }
 
        internal int GetEndAddress()
        {
            return m_endAddr;
        }
 
        internal int GetFinallyEndAddress()
        {
            return m_endFinally;
        }
 
        internal Label GetEndLabel()
        {
            return m_endLabel;
        }
 
        internal int[] GetFilterAddresses()
        {
            return m_filterAddr;
        }
 
        internal int[] GetCatchAddresses()
        {
            return m_catchAddr;
        }
 
        internal int[] GetCatchEndAddresses()
        {
            return m_catchEndAddr;
        }
 
        internal Type[] GetCatchClass()
        {
            return m_catchClass;
        }
 
        internal int GetNumberOfCatches()
        {
            return m_currentCatch;
        }
 
        internal int[] GetExceptionTypes()
        {
            return m_type;
        }
 
        internal void SetFinallyEndLabel(Label lbl)
        {
            m_finallyEndLabel = lbl;
        }
 
        internal Label GetFinallyEndLabel()
        {
            return m_finallyEndLabel;
        }
 
        // Specifies whether exc is an inner exception for "this".  The way
        // its determined is by comparing the end address for the last catch
        // clause for both exceptions.  If they're the same, the start address
        // for the exception is compared.
        // WARNING: This is not a generic function to determine the innerness
        // of an exception.  This is somewhat of a mis-nomer.  This gives a
        // random result for cases where the two exceptions being compared do
        // not having a nesting relation.
        internal bool IsInner(__ExceptionInfo exc)
        {
            Debug.Assert(exc != null);
            Debug.Assert(m_currentCatch > 0);
            Debug.Assert(exc.m_currentCatch > 0);
 
            int exclast = exc.m_currentCatch - 1;
            int last = m_currentCatch - 1;
 
            if (exc.m_catchEndAddr[exclast] < m_catchEndAddr[last])
                return true;
 
            if (exc.m_catchEndAddr[exclast] != m_catchEndAddr[last])
                return false;
            Debug.Assert(exc.GetEndAddress() != GetEndAddress());
 
            return exc.GetEndAddress() > GetEndAddress();
        }
 
        // 0 indicates in a try block
        // 1 indicates in a filter block
        // 2 indicates in a catch block
        // 3 indicates in a finally block
        // 4 indicates Done
        internal int GetCurrentState()
        {
            return m_currentState;
        }
    }
 
    /// <summary>
    /// Scope Tree is a class that track the scope structure within a method body
    /// It keeps track two parallel array. m_ScopeAction keeps track the action. It can be
    /// OpenScope or CloseScope. m_iOffset records the offset where the action
    /// takes place.
    /// </summary>
    internal enum ScopeAction : sbyte
    {
        Open = -0x1,
        Close = 0x1
    }
 
    internal sealed class ScopeTree
    {
        internal ScopeTree()
        {
            // initialize data variables
            m_iOpenScopeCount = 0;
            m_iCount = 0;
        }
 
        /// <summary>
        /// Find the current active lexical scope. For example, if we have
        /// "Open Open Open Close",
        /// we will return 1 as the second BeginScope is currently active.
        /// </summary>
        internal int GetCurrentActiveScopeIndex()
        {
            if (m_iCount == 0)
            {
                return -1;
            }
 
            int i = m_iCount - 1;
 
            for (int cClose = 0; cClose > 0 || m_ScopeActions[i] == ScopeAction.Close; i--)
            {
                cClose += (sbyte)m_ScopeActions[i];
            }
 
            return i;
        }
 
        internal void AddLocalSymInfoToCurrentScope(
            string strName,
            byte[] signature,
            int slot,
            int startOffset,
            int endOffset)
        {
            int i = GetCurrentActiveScopeIndex();
            m_localSymInfos[i] ??= new LocalSymInfo();
            m_localSymInfos[i]!.AddLocalSymInfo(strName, signature, slot, startOffset, endOffset);
        }
 
        internal void AddUsingNamespaceToCurrentScope(string strNamespace)
        {
            int i = GetCurrentActiveScopeIndex();
            m_localSymInfos[i] ??= new LocalSymInfo();
            m_localSymInfos[i]!.AddUsingNamespace(strNamespace);
        }
 
        internal void AddScopeInfo(ScopeAction sa, int iOffset)
        {
            if (sa == ScopeAction.Close && m_iOpenScopeCount <= 0)
            {
                throw new ArgumentException(SR.Argument_UnmatchingSymScope);
            }
 
            // make sure that arrays are large enough to hold addition info
            EnsureCapacity();
 
            m_ScopeActions[m_iCount] = sa;
            m_iOffsets[m_iCount] = iOffset;
            m_localSymInfos[m_iCount] = null;
            checked { m_iCount++; }
 
            m_iOpenScopeCount += -(sbyte)sa;
        }
 
        /// <summary>
        /// Helper to ensure arrays are large enough
        /// </summary>
        internal void EnsureCapacity()
        {
            if (m_iCount == 0)
            {
                // First time. Allocate the arrays.
                m_iOffsets = new int[InitialSize];
                m_ScopeActions = new ScopeAction[InitialSize];
                m_localSymInfos = new LocalSymInfo[InitialSize];
            }
            else if (m_iCount == m_iOffsets.Length)
            {
                // the arrays are full. Enlarge the arrays
                // It would probably be simpler to just use Lists here.
                int newSize = checked(m_iCount * 2);
                int[] temp = new int[newSize];
                Array.Copy(m_iOffsets, temp, m_iCount);
                m_iOffsets = temp;
 
                ScopeAction[] tempSA = new ScopeAction[newSize];
                Array.Copy(m_ScopeActions, tempSA, m_iCount);
                m_ScopeActions = tempSA;
 
                LocalSymInfo[] tempLSI = new LocalSymInfo[newSize];
                Array.Copy(m_localSymInfos, tempLSI, m_iCount);
                m_localSymInfos = tempLSI;
            }
        }
 
        internal int[] m_iOffsets = null!;                 // array of offsets
        internal ScopeAction[] m_ScopeActions = null!;             // array of scope actions
        internal int m_iCount;                   // how many entries in the arrays are occupied
        internal int m_iOpenScopeCount;          // keep track how many scopes are open
        internal const int InitialSize = 16;
        internal LocalSymInfo?[] m_localSymInfos = null!;            // keep track debugging local information
    }
}