File: System\Xml\Serialization\CodeGenerator.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization.Configuration;
 
namespace System.Xml.Serialization
{
    internal sealed class CodeGenerator
    {
        internal const BindingFlags InstancePublicBindingFlags = BindingFlags.Instance | BindingFlags.Public;
        internal const BindingFlags InstanceBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        internal const BindingFlags StaticBindingFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
        internal const MethodAttributes PublicMethodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig;
        internal const MethodAttributes PublicOverrideMethodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig;
        internal const MethodAttributes ProtectedOverrideMethodAttributes = MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig;
        internal const MethodAttributes PrivateMethodAttributes = MethodAttributes.Private | MethodAttributes.HideBySig;
 
        private readonly TypeBuilder _typeBuilder;
        private MethodBuilder? _methodBuilder;
        private ILGenerator? _ilGen;
        private Dictionary<string, ArgBuilder>? _argList;
        private LocalScope? _currentScope;
        // Stores a queue of free locals available in the context of the method, keyed by
        // type and name of the local
        private Dictionary<(Type, string), Queue<LocalBuilder>>? _freeLocals;
        private Stack<object>? _blockStack;
        private Label _methodEndLabel;
 
        internal CodeGenerator(TypeBuilder typeBuilder)
        {
            System.Diagnostics.Debug.Assert(typeBuilder != null);
            _typeBuilder = typeBuilder;
        }
 
        internal static bool IsNullableGenericType(Type type)
        {
            return type.Name == "Nullable`1";
        }
 
        [Conditional("DEBUG")]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
            Justification = "Debug only code, we don't ship debug binaries.")]
        internal static void AssertHasInterface(Type type, Type iType)
        {
            Debug.Assert(iType.IsInterface);
            foreach (Type iFace in type.GetInterfaces())
            {
                if (iFace == iType)
                    return;
            }
            Debug.Fail("Interface not found");
        }
 
        internal void BeginMethod(Type returnType, string methodName, Type[] argTypes, string[] argNames, MethodAttributes methodAttributes)
        {
            _methodBuilder = _typeBuilder.DefineMethod(methodName, methodAttributes, returnType, argTypes);
            _ilGen = _methodBuilder.GetILGenerator();
            InitILGeneration(argTypes, argNames, (_methodBuilder.Attributes & MethodAttributes.Static) == MethodAttributes.Static);
        }
 
        internal void BeginMethod(Type returnType, MethodBuilderInfo methodBuilderInfo, Type[] argTypes, string[] argNames, MethodAttributes methodAttributes)
        {
#if DEBUG
            methodBuilderInfo.Validate(returnType, argTypes, methodAttributes);
#endif
            _methodBuilder = methodBuilderInfo.MethodBuilder;
            _ilGen = _methodBuilder.GetILGenerator();
            InitILGeneration(argTypes, argNames, (_methodBuilder.Attributes & MethodAttributes.Static) == MethodAttributes.Static);
        }
 
        private void InitILGeneration(Type[] argTypes, string[] argNames, bool isStatic)
        {
            _methodEndLabel = _ilGen!.DefineLabel();
            this.retLabel = _ilGen.DefineLabel();
            _blockStack = new Stack<object>();
            _whileStack = new Stack<WhileState>();
            _currentScope = new LocalScope();
            _freeLocals = new Dictionary<(Type, string), Queue<LocalBuilder>>();
            _argList = new Dictionary<string, ArgBuilder>();
            // this ptr is arg 0 for non static, assuming ref type (not value type)
            if (!isStatic)
                _argList.Add("this", new ArgBuilder("this", 0, _typeBuilder.BaseType!));
            for (int i = 0; i < argTypes.Length; i++)
            {
                ArgBuilder arg = new ArgBuilder(argNames[i], _argList.Count, argTypes[i]);
                _argList.Add(arg.Name, arg);
                _methodBuilder!.DefineParameter(arg.Index, ParameterAttributes.None, arg.Name);
            }
        }
 
        internal MethodBuilder? EndMethod()
        {
            MarkLabel(_methodEndLabel);
            Ret();
 
            MethodBuilder? retVal;
            retVal = _methodBuilder;
            _methodBuilder = null;
            _ilGen = null;
            _freeLocals = null;
            _blockStack = null;
            _whileStack = null;
            _argList = null;
            _currentScope = null;
            retLocal = null;
            return retVal;
        }
 
        internal MethodBuilder? MethodBuilder
        {
            get { return _methodBuilder; }
        }
 
        internal ArgBuilder GetArg(string name)
        {
            System.Diagnostics.Debug.Assert(_argList != null && _argList.ContainsKey(name));
            return (ArgBuilder)_argList[name];
        }
 
        internal LocalBuilder GetLocal(string name)
        {
            System.Diagnostics.Debug.Assert(_currentScope != null && _currentScope.ContainsKey(name));
            return _currentScope[name]!;
        }
 
        internal LocalBuilder? retLocal;
        internal Label retLabel;
        internal LocalBuilder ReturnLocal => retLocal ??= DeclareLocal(_methodBuilder!.ReturnType, "_ret");
        internal Label ReturnLabel
        {
            get { return retLabel; }
        }
 
        private readonly Dictionary<Type, LocalBuilder> _tmpLocals = new Dictionary<Type, LocalBuilder>();
        internal LocalBuilder GetTempLocal(Type type)
        {
            LocalBuilder? localTmp;
            if (!_tmpLocals.TryGetValue(type, out localTmp))
            {
                localTmp = DeclareLocal(type, $"_tmp{_tmpLocals.Count}");
                _tmpLocals.Add(type, localTmp);
            }
            return localTmp;
        }
 
        internal static Type GetVariableType(object var)
        {
            if (var is ArgBuilder)
                return ((ArgBuilder)var).ArgType;
            else if (var is LocalBuilder)
                return ((LocalBuilder)var).LocalType;
            else
                return var.GetType();
        }
 
        internal object GetVariable(string name)
        {
            object? var;
            if (TryGetVariable(name, out var))
                return var;
            System.Diagnostics.Debug.Fail("Variable not found");
            return null;
        }
 
        internal bool TryGetVariable(string name, [NotNullWhen(true)] out object? variable)
        {
            LocalBuilder? loc;
            if (_currentScope != null && _currentScope.TryGetValue(name, out loc))
            {
                variable = loc;
                return true;
            }
            ArgBuilder? arg;
            if (_argList != null && _argList.TryGetValue(name, out arg))
            {
                variable = arg;
                return true;
            }
            int val;
            if (int.TryParse(name, out val))
            {
                variable = val;
                return true;
            }
            variable = null;
            return false;
        }
 
        internal void EnterScope()
        {
            LocalScope newScope = new LocalScope(_currentScope);
            _currentScope = newScope;
        }
 
        internal void ExitScope()
        {
            Debug.Assert(_currentScope!.parent != null);
            _currentScope.AddToFreeLocals(_freeLocals!);
            _currentScope = _currentScope.parent;
        }
 
        private bool TryDequeueLocal(Type type, string name, [NotNullWhen(true)] out LocalBuilder? local)
        {
            // This method can only be called between BeginMethod and EndMethod (i.e.
            // while we are emitting code for a method
            Debug.Assert(_freeLocals != null);
 
            Queue<LocalBuilder>? freeLocalQueue;
            (Type, string) key = (type, name);
            if (_freeLocals.TryGetValue(key, out freeLocalQueue))
            {
                local = freeLocalQueue.Dequeue();
 
                // If the queue is empty, remove this key from the dictionary
                // of free locals
                if (freeLocalQueue.Count == 0)
                {
                    _freeLocals.Remove(key);
                }
                return true;
            }
            local = null;
            return false;
        }
 
        internal LocalBuilder DeclareLocal(Type type, string name)
        {
            Debug.Assert(!_currentScope!.ContainsKey(name));
            LocalBuilder? local;
            if (!TryDequeueLocal(type, name, out local))
            {
                local = _ilGen!.DeclareLocal(type, false);
            }
            _currentScope[name] = local;
            return local;
        }
 
        internal LocalBuilder DeclareOrGetLocal(Type type, string name)
        {
            LocalBuilder? local;
            if (!_currentScope!.TryGetValue(name, out local))
                local = DeclareLocal(type, name);
            else
                Debug.Assert(local.LocalType == type);
            return local;
        }
 
        internal object For(LocalBuilder local, object start, object end)
        {
            ForState forState = new ForState(local, DefineLabel(), DefineLabel(), end);
            if (forState.Index != null)
            {
                Load(start);
                Stloc(forState.Index);
                Br(forState.TestLabel);
            }
            MarkLabel(forState.BeginLabel);
            _blockStack!.Push(forState);
            return forState;
        }
 
        internal void EndFor()
        {
            object stackTop = _blockStack!.Pop();
            ForState? forState = stackTop as ForState;
            Debug.Assert(forState != null);
            if (forState.Index != null)
            {
                Ldloc(forState.Index);
                Ldc(1);
                Add();
                Stloc(forState.Index);
                MarkLabel(forState.TestLabel);
                Ldloc(forState.Index);
                Load(forState.End);
                Type varType = GetVariableType(forState.End);
                if (varType.IsArray)
                {
                    Ldlen();
                }
                else
                {
#if DEBUG
                    CodeGenerator.AssertHasInterface(varType, typeof(ICollection));
#endif
                    MethodInfo ICollection_get_Count = typeof(ICollection).GetMethod(
                          "get_Count",
                          CodeGenerator.InstanceBindingFlags,
                          Type.EmptyTypes
                          )!;
                    // ICollection is not a value type, and ICollection::get_Count is a virtual method. So Call() here
                    // will do a 'callvirt'. If we are working with a value type, box it before calling.
                    Debug.Assert(ICollection_get_Count.IsVirtual && !ICollection_get_Count.DeclaringType!.IsValueType);
                    if (varType.IsValueType)
                        Box(varType);
                    Call(ICollection_get_Count);
                }
                Blt(forState.BeginLabel);
            }
            else
                Br(forState.BeginLabel);
        }
 
        internal void If()
        {
            InternalIf(false);
        }
 
        internal void IfNot()
        {
            InternalIf(true);
        }
 
        private static readonly OpCode[] s_branchCodes = new OpCode[] {
            OpCodes.Bge,
            OpCodes.Bne_Un,
            OpCodes.Bgt,
            OpCodes.Ble,
            OpCodes.Beq,
            OpCodes.Blt,
        };
 
        private static OpCode GetBranchCode(Cmp cmp)
        {
            return s_branchCodes[(int)cmp];
        }
 
        internal void If(Cmp cmpOp)
        {
            IfState ifState = new IfState();
            ifState.EndIf = DefineLabel();
            ifState.ElseBegin = DefineLabel();
            _ilGen!.Emit(GetBranchCode(cmpOp), ifState.ElseBegin);
            _blockStack!.Push(ifState);
        }
 
        internal void If(object value1, Cmp cmpOp, object? value2)
        {
            Load(value1);
            Load(value2);
            If(cmpOp);
        }
 
        internal void Else()
        {
            IfState ifState = PopIfState();
            Br(ifState.EndIf);
            MarkLabel(ifState.ElseBegin);
 
            ifState.ElseBegin = ifState.EndIf;
            _blockStack!.Push(ifState);
        }
 
        internal void EndIf()
        {
            IfState ifState = PopIfState();
            if (!ifState.ElseBegin.Equals(ifState.EndIf))
                MarkLabel(ifState.ElseBegin);
            MarkLabel(ifState.EndIf);
        }
 
        private readonly Stack<Label> _leaveLabels = new Stack<Label>();
        internal void BeginExceptionBlock()
        {
            _leaveLabels.Push(DefineLabel());
            _ilGen!.BeginExceptionBlock();
        }
 
        internal void BeginCatchBlock(Type exception)
        {
            _ilGen!.BeginCatchBlock(exception);
        }
 
        internal void EndExceptionBlock()
        {
            _ilGen!.EndExceptionBlock();
            _ilGen.MarkLabel(_leaveLabels.Pop());
        }
 
        internal void Leave()
        {
            _ilGen!.Emit(OpCodes.Leave, _leaveLabels.Peek());
        }
 
        internal void Call(MethodInfo methodInfo)
        {
            Debug.Assert(methodInfo != null);
            if (methodInfo.IsVirtual && !methodInfo.DeclaringType!.IsValueType)
                _ilGen!.Emit(OpCodes.Callvirt, methodInfo);
            else
                _ilGen!.Emit(OpCodes.Call, methodInfo);
        }
 
        internal void Call(ConstructorInfo ctor)
        {
            Debug.Assert(ctor != null);
            _ilGen!.Emit(OpCodes.Call, ctor);
        }
 
        internal void New(ConstructorInfo constructorInfo)
        {
            Debug.Assert(constructorInfo != null);
            _ilGen!.Emit(OpCodes.Newobj, constructorInfo);
        }
 
        internal void InitObj(Type valueType)
        {
            _ilGen!.Emit(OpCodes.Initobj, valueType);
        }
 
        internal void NewArray(Type elementType, object len)
        {
            Load(len);
            _ilGen!.Emit(OpCodes.Newarr, elementType);
        }
 
        internal void StackallocSpan(Type elementType, int len)
        {
            Ldc(len);
            _ilGen!.Emit(OpCodes.Conv_U);
            _ilGen!.Emit(OpCodes.Localloc);
            Ldc(len);
            _ilGen!.Emit(OpCodes.Newobj, typeof(Span<>).MakeGenericType(elementType).GetConstructor(new Type[] { typeof(void*), typeof(int) })!);
        }
 
        internal void LoadArrayElement(object obj, object arrayIndex)
        {
            Type objType = GetVariableType(obj).GetElementType()!;
            Load(obj);
            Load(arrayIndex);
            if (IsStruct(objType))
            {
                Ldelema(objType);
                Ldobj(objType);
            }
            else
                Ldelem(objType);
        }
 
        internal void StoreArrayElement(object obj, object arrayIndex, object value)
        {
            Type arrayType = GetVariableType(obj);
            if (arrayType == typeof(Array))
            {
                Load(obj);
                Call(typeof(Array).GetMethod("SetValue", new Type[] { typeof(object), typeof(int) })!);
            }
            else
            {
                Type objType = arrayType.GetElementType()!;
                Load(obj);
                Load(arrayIndex);
                if (IsStruct(objType))
                    Ldelema(objType);
                Load(value);
                ConvertValue(GetVariableType(value), objType);
                if (IsStruct(objType))
                    Stobj(objType);
                else
                    Stelem(objType);
            }
        }
 
        private static bool IsStruct(Type objType)
        {
            return objType.IsValueType && !objType.IsPrimitive;
        }
 
        [RequiresUnreferencedCode("calls LoadMember")]
        internal Type LoadMember(object obj, MemberInfo memberInfo)
        {
            if (GetVariableType(obj).IsValueType)
                LoadAddress(obj);
            else
                Load(obj);
            return LoadMember(memberInfo);
        }
 
        [RequiresUnreferencedCode("GetProperty on PropertyInfo type's base type")]
        private static MethodInfo? GetPropertyMethodFromBaseType(PropertyInfo propertyInfo, bool isGetter)
        {
            // we only invoke this when the propertyInfo does not have a GET or SET method on it
 
            Type? currentType = propertyInfo.DeclaringType!.BaseType;
            PropertyInfo? currentProperty;
            string propertyName = propertyInfo.Name;
            MethodInfo? result = null;
 
            while (currentType != null)
            {
                currentProperty = currentType.GetProperty(propertyName);
 
                if (currentProperty != null)
                {
                    if (isGetter)
                    {
                        result = currentProperty.GetMethod;
                    }
                    else
                    {
                        result = currentProperty.SetMethod;
                    }
 
                    if (result != null)
                    {
                        // we found the GetMethod/SetMethod on the type closest to the current declaring type
                        break;
                    }
                }
 
                // keep looking at the base type like compiler does
                currentType = currentType.BaseType;
            }
 
            return result;
        }
 
        [RequiresUnreferencedCode("calls GetPropertyMethodFromBaseType")]
        internal Type LoadMember(MemberInfo memberInfo)
        {
            Type? memberType;
            if (memberInfo is FieldInfo fieldInfo)
            {
                memberType = fieldInfo.FieldType;
                if (fieldInfo.IsStatic)
                {
                    _ilGen!.Emit(OpCodes.Ldsfld, fieldInfo);
                }
                else
                {
                    _ilGen!.Emit(OpCodes.Ldfld, fieldInfo);
                }
            }
            else
            {
                System.Diagnostics.Debug.Assert(memberInfo is PropertyInfo);
                PropertyInfo property = (PropertyInfo)memberInfo;
                memberType = property.PropertyType;
                if (property != null)
                {
                    MethodInfo? getMethod = property.GetMethod ?? GetPropertyMethodFromBaseType(property, true);
 
                    System.Diagnostics.Debug.Assert(getMethod != null);
                    Call(getMethod);
                }
            }
 
            return memberType;
        }
 
        [RequiresUnreferencedCode("calls GetPropertyMethodFromBaseType")]
        internal Type LoadMemberAddress(MemberInfo memberInfo)
        {
            Type? memberType;
            if (memberInfo is FieldInfo fieldInfo)
            {
                memberType = fieldInfo.FieldType;
                if (fieldInfo.IsStatic)
                {
                    _ilGen!.Emit(OpCodes.Ldsflda, fieldInfo);
                }
                else
                {
                    _ilGen!.Emit(OpCodes.Ldflda, fieldInfo);
                }
            }
            else
            {
                System.Diagnostics.Debug.Assert(memberInfo is PropertyInfo);
                PropertyInfo property = (PropertyInfo)memberInfo;
                memberType = property.PropertyType;
                if (property != null)
                {
                    MethodInfo? getMethod = property.GetMethod ?? GetPropertyMethodFromBaseType(property, true);
 
                    System.Diagnostics.Debug.Assert(getMethod != null);
                    Call(getMethod);
 
                    LocalBuilder tmpLoc = GetTempLocal(memberType);
                    Stloc(tmpLoc);
                    Ldloca(tmpLoc);
                }
            }
 
            return memberType;
        }
 
        [RequiresUnreferencedCode("calls GetPropertyMethodFromBaseType")]
        internal void StoreMember(MemberInfo memberInfo)
        {
            if (memberInfo is FieldInfo fieldInfo)
            {
                if (fieldInfo.IsStatic)
                {
                    _ilGen!.Emit(OpCodes.Stsfld, fieldInfo);
                }
                else
                {
                    _ilGen!.Emit(OpCodes.Stfld, fieldInfo);
                }
            }
            else
            {
                System.Diagnostics.Debug.Assert(memberInfo is PropertyInfo);
                PropertyInfo property = (PropertyInfo)memberInfo;
                if (property != null)
                {
                    MethodInfo? setMethod = property.SetMethod ?? GetPropertyMethodFromBaseType(property, false);
 
                    System.Diagnostics.Debug.Assert(setMethod != null);
                    Call(setMethod);
                }
            }
        }
 
        internal void Load(object? obj)
        {
            if (obj == null)
                _ilGen!.Emit(OpCodes.Ldnull);
            else if (obj is ArgBuilder)
                Ldarg((ArgBuilder)obj);
            else if (obj is LocalBuilder)
                Ldloc((LocalBuilder)obj);
            else
                Ldc(obj);
        }
 
        internal void LoadAddress(object obj)
        {
            if (obj is ArgBuilder)
                LdargAddress((ArgBuilder)obj);
            else if (obj is LocalBuilder)
                LdlocAddress((LocalBuilder)obj);
            else
                Load(obj);
        }
 
 
        internal void ConvertAddress(Type source, Type target)
        {
            InternalConvert(source, target, true);
        }
 
        internal void ConvertValue(Type source, Type target)
        {
            InternalConvert(source, target, false);
        }
 
        internal void Castclass(Type target)
        {
            _ilGen!.Emit(OpCodes.Castclass, target);
        }
 
        internal void Box(Type type)
        {
            _ilGen!.Emit(OpCodes.Box, type);
        }
 
        internal void Unbox(Type type)
        {
            _ilGen!.Emit(OpCodes.Unbox, type);
        }
 
        private static readonly OpCode[] s_ldindOpCodes = new OpCode[] {
            OpCodes.Nop, //Empty = 0,
            OpCodes.Nop, //Object = 1,
            OpCodes.Nop, //DBNull = 2,
            OpCodes.Ldind_I1, //Boolean = 3,
            OpCodes.Ldind_I2, //Char = 4,
            OpCodes.Ldind_I1, //SByte = 5,
            OpCodes.Ldind_U1, //Byte = 6,
            OpCodes.Ldind_I2, //Int16 = 7,
            OpCodes.Ldind_U2, //UInt16 = 8,
            OpCodes.Ldind_I4, //Int32 = 9,
            OpCodes.Ldind_U4, //UInt32 = 10,
            OpCodes.Ldind_I8, //Int64 = 11,
            OpCodes.Ldind_I8, //UInt64 = 12,
            OpCodes.Ldind_R4, //Single = 13,
            OpCodes.Ldind_R8, //Double = 14,
            OpCodes.Nop, //Decimal = 15,
            OpCodes.Nop, //DateTime = 16,
            OpCodes.Nop, //17
            OpCodes.Ldind_Ref, //String = 18,
        };
 
 
        private static OpCode GetLdindOpCode(TypeCode typeCode)
        {
            return s_ldindOpCodes[(int)typeCode];
        }
 
        internal void Ldobj(Type type)
        {
            OpCode opCode = GetLdindOpCode(Type.GetTypeCode(type));
            if (!opCode.Equals(OpCodes.Nop))
            {
                _ilGen!.Emit(opCode);
            }
            else
            {
                _ilGen!.Emit(OpCodes.Ldobj, type);
            }
        }
 
        internal void LdindU1()
        {
            _ilGen!.Emit(OpCodes.Ldind_U1);
        }
 
        internal void StindI1()
        {
            _ilGen!.Emit(OpCodes.Stind_I1);
        }
 
        internal void Stobj(Type type)
        {
            _ilGen!.Emit(OpCodes.Stobj, type);
        }
 
        internal void Ceq()
        {
            _ilGen!.Emit(OpCodes.Ceq);
        }
 
        internal void Clt()
        {
            _ilGen!.Emit(OpCodes.Clt);
        }
 
        internal void Cne()
        {
            Ceq();
            Ldc(0);
            Ceq();
        }
 
        internal void Ble(Label label)
        {
            _ilGen!.Emit(OpCodes.Ble, label);
        }
 
        internal void Throw()
        {
            _ilGen!.Emit(OpCodes.Throw);
        }
 
        internal void Ldtoken(Type t)
        {
            _ilGen!.Emit(OpCodes.Ldtoken, t);
        }
 
        internal void Ldc(object o)
        {
            Type valueType = o.GetType();
            if (o is Type)
            {
                Ldtoken((Type)o);
                Call(typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(RuntimeTypeHandle) })!);
            }
            else if (valueType.IsEnum)
            {
                Ldc(Convert.ChangeType(o, Enum.GetUnderlyingType(valueType), null));
            }
            else
            {
                switch (Type.GetTypeCode(valueType))
                {
                    case TypeCode.Boolean:
                        Ldc((bool)o);
                        break;
                    case TypeCode.Char:
                        Debug.Fail("Char is not a valid schema primitive and should be treated as int in DataContract");
                        throw new NotSupportedException(SR.XmlInvalidCharSchemaPrimitive);
                    case TypeCode.SByte:
                    case TypeCode.Byte:
                    case TypeCode.Int16:
                    case TypeCode.UInt16:
                        Ldc(Convert.ToInt32(o, CultureInfo.InvariantCulture));
                        break;
                    case TypeCode.Int32:
                        Ldc((int)o);
                        break;
                    case TypeCode.UInt32:
                        Ldc((int)(uint)o);
                        break;
                    case TypeCode.UInt64:
                        Ldc((long)(ulong)o);
                        break;
                    case TypeCode.Int64:
                        Ldc((long)o);
                        break;
                    case TypeCode.Single:
                        Ldc((float)o);
                        break;
                    case TypeCode.Double:
                        Ldc((double)o);
                        break;
                    case TypeCode.String:
                        Ldstr((string)o);
                        break;
                    case TypeCode.Decimal:
                        ConstructorInfo Decimal_ctor = typeof(decimal).GetConstructor(
                             CodeGenerator.InstanceBindingFlags,
                             new Type[] { typeof(int), typeof(int), typeof(int), typeof(bool), typeof(byte) }
                             )!;
                        int[] bits = decimal.GetBits((decimal)o);
                        Ldc(bits[0]); // digit
                        Ldc(bits[1]); // digit
                        Ldc(bits[2]); // digit
                        Ldc((bits[3] & 0x80000000) == 0x80000000); // sign
                        Ldc((byte)((bits[3] >> 16) & 0xFF)); // decimal location
                        New(Decimal_ctor);
                        break;
                    case TypeCode.DateTime:
                        ConstructorInfo DateTime_ctor = typeof(DateTime).GetConstructor(
                            CodeGenerator.InstanceBindingFlags,
                            new Type[] { typeof(long) }
                            )!;
                        Ldc(((DateTime)o).Ticks); // ticks
                        New(DateTime_ctor);
                        break;
                    case TypeCode.Object:
                    case TypeCode.Empty:
                    case TypeCode.DBNull:
                    default:
                        if (valueType == typeof(TimeSpan))
                        {
                            ConstructorInfo TimeSpan_ctor = typeof(TimeSpan).GetConstructor(
                            CodeGenerator.InstanceBindingFlags,
                            null,
                            new Type[] { typeof(long) },
                            null
                            )!;
                            Ldc(((TimeSpan)o).Ticks); // ticks
                            New(TimeSpan_ctor);
                            break;
                        }
                        else if (valueType == typeof(DateTimeOffset))
                        {
                            ConstructorInfo DateTimeOffset_ctor = typeof(DateTimeOffset).GetConstructor(
                            CodeGenerator.InstanceBindingFlags,
                            null,
                            new Type[] { typeof(long), typeof(TimeSpan) },
                            null
                            )!;
                            Ldc(((DateTimeOffset)o).Ticks); // ticks
                            Ldc(((DateTimeOffset)o).Offset); // offset
                            New(DateTimeOffset_ctor);
                            break;
                        }
                        else
                        {
                            throw new NotSupportedException(SR.Format(SR.UnknownConstantType, valueType.AssemblyQualifiedName));
                        }
                }
            }
        }
 
        internal void Ldc(bool boolVar)
        {
            if (boolVar)
            {
                _ilGen!.Emit(OpCodes.Ldc_I4_1);
            }
            else
            {
                _ilGen!.Emit(OpCodes.Ldc_I4_0);
            }
        }
 
        internal void Ldc(int intVar)
        {
            _ilGen!.Emit(OpCodes.Ldc_I4, intVar);
        }
 
        internal void Ldc(long l)
        {
            _ilGen!.Emit(OpCodes.Ldc_I8, l);
        }
 
        internal void Ldc(float f)
        {
            _ilGen!.Emit(OpCodes.Ldc_R4, f);
        }
 
        internal void Ldc(double d)
        {
            _ilGen!.Emit(OpCodes.Ldc_R8, d);
        }
 
        internal void Ldstr(string? strVar)
        {
            if (strVar == null)
                _ilGen!.Emit(OpCodes.Ldnull);
            else
                _ilGen!.Emit(OpCodes.Ldstr, strVar);
        }
 
        internal void LdlocAddress(LocalBuilder localBuilder)
        {
            if (localBuilder.LocalType.IsValueType)
                Ldloca(localBuilder);
            else
                Ldloc(localBuilder);
        }
 
        internal void Ldloc(LocalBuilder localBuilder)
        {
            _ilGen!.Emit(OpCodes.Ldloc, localBuilder);
        }
 
        internal void Ldloc(string name)
        {
            Debug.Assert(_currentScope!.ContainsKey(name));
            LocalBuilder local = _currentScope[name]!;
            Ldloc(local);
        }
 
        internal void Stloc(Type type, string name)
        {
            LocalBuilder? local;
            if (!_currentScope!.TryGetValue(name, out local))
            {
                local = DeclareLocal(type, name);
            }
            Debug.Assert(local.LocalType == type);
            Stloc(local);
        }
 
        internal void Stloc(LocalBuilder local)
        {
            _ilGen!.Emit(OpCodes.Stloc, local);
        }
 
        internal void Ldloc(Type type, string name)
        {
            Debug.Assert(_currentScope!.ContainsKey(name));
            LocalBuilder local = _currentScope[name]!;
            Debug.Assert(local.LocalType == type);
            Ldloc(local);
        }
 
        internal void Ldloca(LocalBuilder localBuilder)
        {
            _ilGen!.Emit(OpCodes.Ldloca, localBuilder);
        }
 
        internal void LdargAddress(ArgBuilder argBuilder)
        {
            if (argBuilder.ArgType.IsValueType)
                Ldarga(argBuilder);
            else
                Ldarg(argBuilder);
        }
 
        internal void Ldarg(string arg)
        {
            Ldarg(GetArg(arg));
        }
 
        internal void Ldarg(ArgBuilder arg)
        {
            Ldarg(arg.Index);
        }
 
        internal void Ldarg(int slot)
        {
            _ilGen!.Emit(OpCodes.Ldarg, slot);
        }
 
        internal void Ldarga(ArgBuilder argBuilder)
        {
            Ldarga(argBuilder.Index);
        }
 
        internal void Ldarga(int slot)
        {
            _ilGen!.Emit(OpCodes.Ldarga, slot);
        }
 
        internal void Ldlen()
        {
            _ilGen!.Emit(OpCodes.Ldlen);
            _ilGen.Emit(OpCodes.Conv_I4);
        }
 
        private static readonly OpCode[] s_ldelemOpCodes = new OpCode[] {
            OpCodes.Nop, //Empty = 0,
            OpCodes.Ldelem_Ref, //Object = 1,
            OpCodes.Ldelem_Ref, //DBNull = 2,
            OpCodes.Ldelem_I1, //Boolean = 3,
            OpCodes.Ldelem_I2, //Char = 4,
            OpCodes.Ldelem_I1, //SByte = 5,
            OpCodes.Ldelem_U1, //Byte = 6,
            OpCodes.Ldelem_I2, //Int16 = 7,
            OpCodes.Ldelem_U2, //UInt16 = 8,
            OpCodes.Ldelem_I4, //Int32 = 9,
            OpCodes.Ldelem_U4, //UInt32 = 10,
            OpCodes.Ldelem_I8, //Int64 = 11,
            OpCodes.Ldelem_I8, //UInt64 = 12,
            OpCodes.Ldelem_R4, //Single = 13,
            OpCodes.Ldelem_R8, //Double = 14,
            OpCodes.Nop, //Decimal = 15,
            OpCodes.Nop, //DateTime = 16,
            OpCodes.Nop, //17
            OpCodes.Ldelem_Ref, //String = 18,
        };
 
        private static OpCode GetLdelemOpCode(TypeCode typeCode)
        {
            return s_ldelemOpCodes[(int)typeCode];
        }
 
        internal void Ldelem(Type arrayElementType)
        {
            if (arrayElementType.IsEnum)
            {
                Ldelem(Enum.GetUnderlyingType(arrayElementType));
            }
            else
            {
                OpCode opCode = GetLdelemOpCode(Type.GetTypeCode(arrayElementType));
                Debug.Assert(!opCode.Equals(OpCodes.Nop));
                if (opCode.Equals(OpCodes.Nop))
                    throw new InvalidOperationException(SR.Format(SR.ArrayTypeIsNotSupported, arrayElementType.AssemblyQualifiedName));
                _ilGen!.Emit(opCode);
            }
        }
        internal void Ldelema(Type arrayElementType)
        {
            OpCode opCode = OpCodes.Ldelema;
            _ilGen!.Emit(opCode, arrayElementType);
        }
 
        private static readonly OpCode[] s_stelemOpCodes = new OpCode[] {
            OpCodes.Nop, //Empty = 0,
            OpCodes.Stelem_Ref, //Object = 1,
            OpCodes.Stelem_Ref, //DBNull = 2,
            OpCodes.Stelem_I1, //Boolean = 3,
            OpCodes.Stelem_I2, //Char = 4,
            OpCodes.Stelem_I1, //SByte = 5,
            OpCodes.Stelem_I1, //Byte = 6,
            OpCodes.Stelem_I2, //Int16 = 7,
            OpCodes.Stelem_I2, //UInt16 = 8,
            OpCodes.Stelem_I4, //Int32 = 9,
            OpCodes.Stelem_I4, //UInt32 = 10,
            OpCodes.Stelem_I8, //Int64 = 11,
            OpCodes.Stelem_I8, //UInt64 = 12,
            OpCodes.Stelem_R4, //Single = 13,
            OpCodes.Stelem_R8, //Double = 14,
            OpCodes.Nop, //Decimal = 15,
            OpCodes.Nop, //DateTime = 16,
            OpCodes.Nop, //17
            OpCodes.Stelem_Ref, //String = 18,
        };
 
        private static OpCode GetStelemOpCode(TypeCode typeCode)
        {
            return s_stelemOpCodes[(int)typeCode];
        }
 
        internal void Stelem(Type arrayElementType)
        {
            if (arrayElementType.IsEnum)
                Stelem(Enum.GetUnderlyingType(arrayElementType));
            else
            {
                OpCode opCode = GetStelemOpCode(Type.GetTypeCode(arrayElementType));
                if (opCode.Equals(OpCodes.Nop))
                    throw new InvalidOperationException(SR.Format(SR.ArrayTypeIsNotSupported, arrayElementType.AssemblyQualifiedName));
                _ilGen!.Emit(opCode);
            }
        }
 
        internal Label DefineLabel()
        {
            return _ilGen!.DefineLabel();
        }
 
        internal void MarkLabel(Label label)
        {
            _ilGen!.MarkLabel(label);
        }
 
        internal void Nop()
        {
            _ilGen!.Emit(OpCodes.Nop);
        }
 
        internal void Add()
        {
            _ilGen!.Emit(OpCodes.Add);
        }
 
        internal void Ret()
        {
            _ilGen!.Emit(OpCodes.Ret);
        }
 
        internal void Br(Label label)
        {
            _ilGen!.Emit(OpCodes.Br, label);
        }
 
        internal void Br_S(Label label)
        {
            _ilGen!.Emit(OpCodes.Br_S, label);
        }
 
        internal void Blt(Label label)
        {
            _ilGen!.Emit(OpCodes.Blt, label);
        }
 
        internal void Brfalse(Label label)
        {
            _ilGen!.Emit(OpCodes.Brfalse, label);
        }
 
        internal void Brtrue(Label label)
        {
            _ilGen!.Emit(OpCodes.Brtrue, label);
        }
 
        internal void Pop()
        {
            _ilGen!.Emit(OpCodes.Pop);
        }
 
        internal void Dup()
        {
            _ilGen!.Emit(OpCodes.Dup);
        }
 
        private void InternalIf(bool negate)
        {
            IfState ifState = new IfState();
            ifState.EndIf = DefineLabel();
            ifState.ElseBegin = DefineLabel();
            if (negate)
                Brtrue(ifState.ElseBegin);
            else
                Brfalse(ifState.ElseBegin);
            _blockStack!.Push(ifState);
        }
 
        private static readonly OpCode[] s_convOpCodes = new OpCode[] {
            OpCodes.Nop, //Empty = 0,
            OpCodes.Nop, //Object = 1,
            OpCodes.Nop, //DBNull = 2,
            OpCodes.Conv_I1, //Boolean = 3,
            OpCodes.Conv_I2, //Char = 4,
            OpCodes.Conv_I1, //SByte = 5,
            OpCodes.Conv_U1, //Byte = 6,
            OpCodes.Conv_I2, //Int16 = 7,
            OpCodes.Conv_U2, //UInt16 = 8,
            OpCodes.Conv_I4, //Int32 = 9,
            OpCodes.Conv_U4, //UInt32 = 10,
            OpCodes.Conv_I8, //Int64 = 11,
            OpCodes.Conv_U8, //UInt64 = 12,
            OpCodes.Conv_R4, //Single = 13,
            OpCodes.Conv_R8, //Double = 14,
            OpCodes.Nop, //Decimal = 15,
            OpCodes.Nop, //DateTime = 16,
            OpCodes.Nop, //17
            OpCodes.Nop, //String = 18,
        };
 
        private static OpCode GetConvOpCode(TypeCode typeCode)
        {
            return s_convOpCodes[(int)typeCode];
        }
 
        private void InternalConvert(Type source, Type target, bool isAddress)
        {
            if (target == source)
                return;
            if (target.IsValueType)
            {
                if (source.IsValueType)
                {
                    OpCode opCode = GetConvOpCode(Type.GetTypeCode(target));
                    if (opCode.Equals(OpCodes.Nop))
                    {
                        throw new CodeGeneratorConversionException(source, target, isAddress, "NoConversionPossibleTo");
                    }
                    else
                    {
                        _ilGen!.Emit(opCode);
                    }
                }
                else if (source.IsAssignableFrom(target))
                {
                    Unbox(target);
                    if (!isAddress)
                        Ldobj(target);
                }
                else
                {
                    throw new CodeGeneratorConversionException(source, target, isAddress, "IsNotAssignableFrom");
                }
            }
            else if (target.IsAssignableFrom(source))
            {
                if (source.IsValueType)
                {
                    if (isAddress)
                        Ldobj(source);
                    Box(source);
                }
            }
            else if (source.IsAssignableFrom(target))
            {
                Castclass(target);
            }
            else if (target.IsInterface || source.IsInterface)
            {
                Castclass(target);
            }
            else
            {
                throw new CodeGeneratorConversionException(source, target, isAddress, "IsNotAssignableFrom");
            }
        }
 
        private IfState PopIfState()
        {
            object stackTop = _blockStack!.Pop();
            IfState? ifState = stackTop as IfState;
            Debug.Assert(ifState != null);
            return ifState;
        }
 
        internal static AssemblyBuilder CreateAssemblyBuilder(string name)
        {
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = name;
            assemblyName.Version = new Version(1, 0, 0, 0);
            return AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        }
 
        internal static ModuleBuilder CreateModuleBuilder(AssemblyBuilder assemblyBuilder, string name)
        {
            return assemblyBuilder.DefineDynamicModule(name);
        }
 
        internal static TypeBuilder CreateTypeBuilder(
            ModuleBuilder moduleBuilder,
            string name,
            TypeAttributes attributes,
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type parent,
            Type[] interfaces)
        {
            // parent is nullable if no base class
            return moduleBuilder.DefineType($"{TempAssembly.GeneratedAssemblyNamespace}.{name}",
                attributes, parent, interfaces);
        }
 
        private int _initElseIfStack = -1;
        private IfState? _elseIfState;
        internal void InitElseIf()
        {
            Debug.Assert(_initElseIfStack == -1);
            _elseIfState = (IfState)_blockStack!.Pop();
            _initElseIfStack = _blockStack.Count;
            Br(_elseIfState.EndIf);
            MarkLabel(_elseIfState.ElseBegin);
        }
 
        private int _initIfStack = -1;
        internal void InitIf()
        {
            Debug.Assert(_initIfStack == -1);
            _initIfStack = _blockStack!.Count;
        }
 
        internal void AndIf(Cmp cmpOp)
        {
            if (_initIfStack == _blockStack!.Count)
            {
                _initIfStack = -1;
                If(cmpOp);
                return;
            }
            if (_initElseIfStack == _blockStack.Count)
            {
                _initElseIfStack = -1;
                _elseIfState!.ElseBegin = DefineLabel();
                _ilGen!.Emit(GetBranchCode(cmpOp), _elseIfState.ElseBegin);
                _blockStack.Push(_elseIfState);
                return;
            }
            Debug.Assert(_initIfStack == -1 && _initElseIfStack == -1);
            IfState ifState = (IfState)_blockStack.Peek();
            _ilGen!.Emit(GetBranchCode(cmpOp), ifState.ElseBegin);
        }
 
        internal void AndIf()
        {
            if (_initIfStack == _blockStack!.Count)
            {
                _initIfStack = -1;
                If();
                return;
            }
            if (_initElseIfStack == _blockStack.Count)
            {
                _initElseIfStack = -1;
                _elseIfState!.ElseBegin = DefineLabel();
                Brfalse(_elseIfState.ElseBegin);
                _blockStack.Push(_elseIfState);
                return;
            }
            Debug.Assert(_initIfStack == -1 && _initElseIfStack == -1);
            IfState ifState = (IfState)_blockStack.Peek();
            Brfalse(ifState.ElseBegin);
        }
 
        internal void IsInst(Type type)
        {
            _ilGen!.Emit(OpCodes.Isinst, type);
        }
 
        internal void Beq(Label label)
        {
            _ilGen!.Emit(OpCodes.Beq, label);
        }
 
        internal void Bne(Label label)
        {
            _ilGen!.Emit(OpCodes.Bne_Un, label);
        }
 
        internal void GotoMethodEnd()
        {
            //limit to only short forward (127 CIL instruction)
            //Br_S(this.methodEndLabel);
            Br(_methodEndLabel);
        }
 
        internal sealed class WhileState
        {
            public Label StartLabel;
            public Label CondLabel;
            public Label EndLabel;
            public WhileState(CodeGenerator ilg)
            {
                StartLabel = ilg.DefineLabel();
                CondLabel = ilg.DefineLabel();
                EndLabel = ilg.DefineLabel();
            }
        }
 
        // Usage:
        // WhileBegin()
        //  WhileBreak()
        //  WhileContinue()
        // WhileBeginCondition()
        // (bool on stack)
        // WhileEndCondition()
        // WhileEnd()
        private Stack<WhileState>? _whileStack;
        internal void WhileBegin()
        {
            WhileState whileState = new WhileState(this);
            // ECMA-335 III.1.7.5 states that code blocks after unconditional
            // branch which could only be reached by backward branch are assumed
            // to have empty stack on the entrance.
            //
            // Since we don't have control over the current stack contents here
            // we have to generate conditional branch here instead of
            // Br(whileState.CondLabel) to make the IL verifiable.
            Ldc(true);
            Brtrue(whileState.CondLabel);
            MarkLabel(whileState.StartLabel);
            _whileStack!.Push(whileState);
        }
 
        internal void WhileEnd()
        {
            WhileState whileState = _whileStack!.Pop();
            MarkLabel(whileState.EndLabel);
        }
 
        internal void WhileContinue()
        {
            WhileState whileState = _whileStack!.Peek();
            Br(whileState.CondLabel);
        }
 
        internal void WhileBeginCondition()
        {
            WhileState whileState = _whileStack!.Peek();
            // If there are two MarkLabel ILs consecutively, Labels will converge to one label.
            // This could cause the code to look different.  We insert Nop here specifically
            // that the While label stands out.
            Nop();
            MarkLabel(whileState.CondLabel);
        }
 
        internal void WhileEndCondition()
        {
            WhileState whileState = _whileStack!.Peek();
            Brtrue(whileState.StartLabel);
        }
    }
 
 
    internal sealed class ArgBuilder
    {
        internal string Name;
        internal int Index;
        internal Type ArgType;
        internal ArgBuilder(string name, int index, Type argType)
        {
            this.Name = name;
            this.Index = index;
            this.ArgType = argType;
        }
    }
 
    internal sealed class ForState
    {
        private readonly LocalBuilder _indexVar;
        private readonly Label _beginLabel;
        private readonly Label _testLabel;
        private readonly object _end;
 
        internal ForState(LocalBuilder indexVar, Label beginLabel, Label testLabel, object end)
        {
            _indexVar = indexVar;
            _beginLabel = beginLabel;
            _testLabel = testLabel;
            _end = end;
        }
 
        internal LocalBuilder Index
        {
            get
            {
                return _indexVar;
            }
        }
 
        internal Label BeginLabel
        {
            get
            {
                return _beginLabel;
            }
        }
 
        internal Label TestLabel
        {
            get
            {
                return _testLabel;
            }
        }
 
        internal object End
        {
            get
            {
                return _end;
            }
        }
    }
 
    internal enum Cmp : int
    {
        LessThan = 0,
        EqualTo,
        LessThanOrEqualTo,
        GreaterThan,
        NotEqualTo,
        GreaterThanOrEqualTo
    }
 
    internal sealed class IfState
    {
        private Label _elseBegin;
        private Label _endIf;
 
        internal Label EndIf
        {
            get
            {
                return _endIf;
            }
            set
            {
                _endIf = value;
            }
        }
 
        internal Label ElseBegin
        {
            get
            {
                return _elseBegin;
            }
            set
            {
                _elseBegin = value;
            }
        }
    }
 
    internal sealed class LocalScope
    {
        public readonly LocalScope? parent;
        private readonly Dictionary<string, LocalBuilder> _locals;
 
        // Root scope
        public LocalScope()
        {
            _locals = new Dictionary<string, LocalBuilder>();
        }
 
        public LocalScope(LocalScope? parent) : this()
        {
            this.parent = parent;
        }
 
        public bool ContainsKey(string key)
        {
            return _locals.ContainsKey(key) || (parent != null && parent.ContainsKey(key));
        }
 
        public bool TryGetValue(string key, [NotNullWhen(true)] out LocalBuilder? value)
        {
            if (_locals.TryGetValue(key, out value))
            {
                return true;
            }
            else if (parent != null)
            {
                return parent.TryGetValue(key, out value);
            }
            else
            {
                value = null;
                return false;
            }
        }
 
        [DisallowNull]
        public LocalBuilder? this[string key]
        {
            get
            {
                LocalBuilder? value;
                TryGetValue(key, out value);
                return value;
            }
            set
            {
                _locals[key] = value;
            }
        }
 
        public void AddToFreeLocals(Dictionary<(Type, string), Queue<LocalBuilder>> freeLocals)
        {
            foreach (var item in _locals)
            {
                (Type, string) key = (item.Value.LocalType, item.Key);
                Queue<LocalBuilder>? freeLocalQueue;
                if (freeLocals.TryGetValue(key, out freeLocalQueue))
                {
                    // Add to end of the queue so that it will be re-used in
                    // FIFO manner
                    freeLocalQueue.Enqueue(item.Value);
                }
                else
                {
                    freeLocalQueue = new Queue<LocalBuilder>();
                    freeLocalQueue.Enqueue(item.Value);
                    freeLocals.Add(key, freeLocalQueue);
                }
            }
        }
    }
 
    internal sealed class MethodBuilderInfo
    {
        public readonly MethodBuilder MethodBuilder;
        public readonly Type[] ParameterTypes;
        public MethodBuilderInfo(MethodBuilder methodBuilder, Type[] parameterTypes)
        {
            this.MethodBuilder = methodBuilder;
            this.ParameterTypes = parameterTypes;
        }
 
        [Conditional("DEBUG")]
        public void Validate(Type? returnType, Type[] parameterTypes, MethodAttributes attributes)
        {
            Debug.Assert(this.MethodBuilder.ReturnType == returnType);
            Debug.Assert(this.MethodBuilder.Attributes == attributes);
            Debug.Assert(this.ParameterTypes.Length == parameterTypes.Length);
            for (int i = 0; i < parameterTypes.Length; ++i)
            {
                Debug.Assert(this.ParameterTypes[i] == parameterTypes[i]);
            }
        }
    }
 
    internal sealed class CodeGeneratorConversionException : Exception
    {
        private readonly Type _sourceType;
        private readonly Type _targetType;
        private readonly bool _isAddress;
        private readonly string _reason;
 
        public CodeGeneratorConversionException(Type sourceType, Type targetType, bool isAddress, string reason)
            : base(SR.Format(SR.CodeGenConvertError, reason, sourceType.ToString(), targetType.ToString()))
        {
            _sourceType = sourceType;
            _targetType = targetType;
            _isAddress = isAddress;
            _reason = reason;
        }
    }
}