File: Compiler\TypePreinit.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// 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.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

using ILCompiler.DependencyAnalysis;

using Internal.IL;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

using CombinedDependencyList = System.Collections.Generic.List<ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry>;
using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations;

namespace ILCompiler
{
    // Class that computes the initial state of static fields on a type by interpreting the static constructor.
    //
    // Values are represented by instances of an abstract Value class. Several specialized descendants of
    // the Value class exist, representing value types (including e.g. a specialized class representing
    // RuntimeFieldHandle), or reference types (including e.g. specialized class representing an array).
    //
    // For simplicity, non-reference values are represented as byte arrays. This requires many short lived array
    // allocations, but makes a lot of things simpler (e.g. byrefs to values are essentially free because they
    // only carry a reference to the original array and an optional index).
    //
    // When dealing with non-reference types (valuetypes and unmanaged pointers) we need to be careful
    // about assignment semantics. Some operations need to make a copy of the valuetype bytes while others
    // are fine to reuse the original byte array. Whenever storing a value into a location, we need to assign
    // a new value to the existing Value instance to keep byrefs working.
    public class TypePreinit
    {
        private readonly MetadataType _type;
        private readonly CompilationModuleGroup _compilationGroup;
        private readonly ILProvider _ilProvider;
        private readonly TypePreinitializationPolicy _policy;
        private readonly ReadOnlyFieldPolicy _readOnlyPolicy;
        private readonly FlowAnnotations _flowAnnotations;
        private readonly Dictionary<FieldDesc, Value> _fieldValues = new Dictionary<FieldDesc, Value>();
        private readonly Dictionary<string, StringInstance> _internedStrings = new Dictionary<string, StringInstance>();
        private readonly Dictionary<TypeDesc, RuntimeTypeValue> _internedTypes = new Dictionary<TypeDesc, RuntimeTypeValue>();
        private readonly Dictionary<MetadataType, NestedPreinitResult> _nestedPreinitResults = new Dictionary<MetadataType, NestedPreinitResult>();
        private readonly Dictionary<EcmaField, byte[]> _rvaFieldDatas = new Dictionary<EcmaField, byte[]>();

        private TypePreinit(MetadataType owningType, CompilationModuleGroup compilationGroup, ILProvider ilProvider, TypePreinitializationPolicy policy, ReadOnlyFieldPolicy readOnlyPolicy, FlowAnnotations flowAnnotations)
        {
            _type = owningType;
            _compilationGroup = compilationGroup;
            _ilProvider = ilProvider;
            _policy = policy;
            _readOnlyPolicy = readOnlyPolicy;
            _flowAnnotations = flowAnnotations;

            // Zero initialize all fields we model.
            foreach (var field in owningType.GetFields())
            {
                if (!field.IsStatic || field.IsLiteral || field.IsThreadStatic || field.HasRva)
                    continue;

                _fieldValues.Add(field, NewUninitializedLocationValue(field.FieldType, field));
            }
        }

        public static PreinitializationInfo ScanType(CompilationModuleGroup compilationGroup, ILProvider ilProvider, TypePreinitializationPolicy policy, ReadOnlyFieldPolicy readOnlyPolicy, FlowAnnotations flowAnnotations, MetadataType type)
        {
            Debug.Assert(type.HasStaticConstructor);
            Debug.Assert(!type.IsGenericDefinition);
            Debug.Assert(!type.IsRuntimeDeterminedSubtype);

            if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
            {
                // It's an odd question to ask about canonical types. Defer to policy that might
                // have more information.
                // If the policy allows it, we allow it, but create invalid field values so that
                // things still crash if someone wanted to do more with canonical types than just
                // ask if a cctor check is necessary to access.
                if (policy.CanPreinitializeAllConcreteFormsForCanonForm(type))
                    return new PreinitializationInfo(type, Array.Empty<KeyValuePair<FieldDesc, ISerializableValue>>());

                return new PreinitializationInfo(type, "Disallowed by policy");
            }

            if (!policy.CanPreinitialize(type))
                return new PreinitializationInfo(type, "Disallowed by policy");

            TypePreinit preinit = null;

            Status status;
            try
            {
                preinit = new TypePreinit(type, compilationGroup, ilProvider, policy, readOnlyPolicy, flowAnnotations);
                int instructions = 0;
                status = preinit.TryScanMethod(type.GetStaticConstructor(), null, null, ref instructions, out _);
            }
            catch (TypeSystemException ex)
            {
                status = Status.Fail(type.GetStaticConstructor(), ex.Message);
            }

            if (status.IsSuccessful)
            {
                var values = new List<KeyValuePair<FieldDesc, ISerializableValue>>();
                foreach (var kvp in preinit._fieldValues)
                    values.Add(new KeyValuePair<FieldDesc, ISerializableValue>(kvp.Key, kvp.Value));

                return new PreinitializationInfo(type, values);
            }

            return new PreinitializationInfo(type, status.FailureReason);
        }

        private bool TryGetNestedPreinitResult(MethodDesc callingMethod, MetadataType type, Stack<MethodDesc> recursionProtect, ref int instructionCounter, out NestedPreinitResult result)
        {
            if (!_nestedPreinitResults.TryGetValue(type, out result))
            {
                TypePreinit nestedPreinit = new TypePreinit(type, _compilationGroup, _ilProvider, _policy, _readOnlyPolicy, _flowAnnotations);
                recursionProtect ??= new Stack<MethodDesc>();
                recursionProtect.Push(callingMethod);

                // Since we don't reset the instruction counter as we interpret the nested cctor,
                // remember the instruction counter before we start interpreting so that we can subtract
                // the instructions later when we convert object instances allocated in the nested
                // cctor to foreign instances in the currently analyzed cctor.
                // E.g. if the nested cctor allocates a new object at the beginning of the cctor,
                // we should treat it as a ForeignTypeInstance with allocation site ID 0, not allocation
                // site ID of `instructionCounter + 0`.
                // We could also reset the counter, but we use the instruction counter as a complexity cutoff
                // and resetting it would lead to unpredictable analysis durations.
                int baseInstructionCounter = instructionCounter;
                Status status = nestedPreinit.TryScanMethod(type.GetStaticConstructor(), null, recursionProtect, ref instructionCounter, out Value _);
                if (!status.IsSuccessful)
                {
                    result = default;
                    return false;
                }
                recursionProtect.Pop();

                result = new NestedPreinitResult(nestedPreinit._fieldValues, baseInstructionCounter);

                _nestedPreinitResults.Add(type, result);
            }

            return true;
        }

        private byte[] GetFieldRvaData(EcmaField field)
        {
            if (!_rvaFieldDatas.TryGetValue(field, out byte[] result))
                _rvaFieldDatas.Add(field, result = field.GetFieldRvaData());
            return result;
        }

        private Status TryScanMethod(MethodDesc method, Value[] parameters, Stack<MethodDesc> recursionProtect, ref int instructionCounter, out Value returnValue)
        {
            MethodIL methodIL = _ilProvider.GetMethodIL(method);
            if (methodIL == null)
            {
                returnValue = null;
                return Status.Fail(method, "Extern method");
            }

            return TryScanMethod(methodIL, parameters, recursionProtect, ref instructionCounter, out returnValue);
        }

        private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<MethodDesc> recursionProtect, ref int instructionCounter, out Value returnValue)
        {
            returnValue = default;

            if (recursionProtect != null && recursionProtect.Contains(methodIL.OwningMethod))
                return Status.Fail(methodIL.OwningMethod, "Recursion");

            ILExceptionRegion[] ehRegions = methodIL.GetExceptionRegions();
            if (ehRegions != null && ehRegions.Length > 0)
            {
                // We don't care about catch/filter/fault because those only run when an exception happens
                // (exceptions will never happen here). But finally needs to run in non-exceptional paths
                // and we don't model that yet.
                foreach (ILExceptionRegion ehRegion in ehRegions)
                {
                    if (ehRegion.Kind == ILExceptionRegionKind.Finally)
                        return Status.Fail(methodIL.OwningMethod, "Finally regions");
                }
            }

            var reader = new ILReader(methodIL.GetILBytes());

            TypeSystemContext context = methodIL.OwningMethod.Context;

            var stack = new Stack(methodIL.MaxStack, context.Target);

            LocalVariableDefinition[] localTypes = methodIL.GetLocals();
            Value[] locals = new Value[localTypes.Length];
            for (int i = 0; i < localTypes.Length; i++)
            {
                locals[i] = NewUninitializedLocationValue(localTypes[i].Type, fieldThatOwnsMemory: null);
            }

            // Read IL opcodes and interpret their semantics.
            //
            // This is not a full interpreter and we're allowed to not interpret everything. If a semantic is
            // not implemented by the interpreter, we simply fail.
            //
            // We also need to do basic sanity checking for invalid IL to protect us from crashing. These
            // all throw the TypeSystem's InvalidProgramException. The exception doesn't need to exactly match
            // the runtime exception. We just need something reasonably catchable to abort interpreting.
            //
            // We throw instead of returning false to aid debuggability of the interpreter (we shouldn't see
            // exceptions in normal code so an exception is usually a bug).

            while (reader.HasNext)
            {
                if (instructionCounter == 100000)
                    return Status.Fail(methodIL.OwningMethod, "Instruction limit");

                instructionCounter++;

                TypeDesc constrainedType = null;

            again:
                ILOpcode opcode = reader.ReadILOpcode();
                switch (opcode)
                {
                    case ILOpcode.ldc_i4_m1:
                    case ILOpcode.ldc_i4_s:
                    case ILOpcode.ldc_i4:
                    case ILOpcode.ldc_i4_0:
                    case ILOpcode.ldc_i4_1:
                    case ILOpcode.ldc_i4_2:
                    case ILOpcode.ldc_i4_3:
                    case ILOpcode.ldc_i4_4:
                    case ILOpcode.ldc_i4_5:
                    case ILOpcode.ldc_i4_6:
                    case ILOpcode.ldc_i4_7:
                    case ILOpcode.ldc_i4_8:
                    {
                        int value = opcode switch
                        {
                            ILOpcode.ldc_i4_m1 => -1,
                            ILOpcode.ldc_i4_s => (sbyte)reader.ReadILByte(),
                            ILOpcode.ldc_i4 => (int)reader.ReadILUInt32(),
                            _ => opcode - ILOpcode.ldc_i4_0,
                        };
                        stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(value));
                    }
                    break;

                    case ILOpcode.ldc_i8:
                        stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64((long)reader.ReadILUInt64()));
                        break;

                    case ILOpcode.ldc_r4:
                    case ILOpcode.ldc_r8:
                        stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(
                            opcode == ILOpcode.ldc_r4 ? reader.ReadILFloat() : reader.ReadILDouble()));
                        break;

                    case ILOpcode.sizeof_:
                    {
                        TypeDesc type = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
                        stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(type.GetElementSize().AsInt));
                    }
                    break;

                    case ILOpcode.ldnull:
                        stack.Push((ReferenceTypeValue)null);
                        break;

                    case ILOpcode.newarr:
                    {
                        if (!stack.TryPopIntValue(out int elementCount))
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        const int MaximumInterpretedArraySize = 8192;

                        TypeDesc elementType = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
                        if (elementCount > 0
                            && (elementType.IsGCPointer
                            || (elementType.IsValueType && ((DefType)elementType).ContainsGCPointers)))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "GC pointers");
                        }

                        if (elementCount < 0
                            || elementCount > MaximumInterpretedArraySize)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Array out of bounds");
                        }

                        if (elementType.RequiresAlign8())
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Align8");
                        }

                        AllocationSite allocSite = new AllocationSite(_type, instructionCounter);
                        stack.Push(new ArrayInstance(elementType.MakeArrayType(), elementCount, allocSite));
                    }
                    break;

                    case ILOpcode.dup:
                        if (stack.Count == 0)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        stack.Push(stack.Peek());
                        break;

                    case ILOpcode.pop:
                    {
                        stack.Pop();
                        break;
                    }

                    case ILOpcode.ldstr:
                    {
                        string s = (string)methodIL.GetObject(reader.ReadILToken());
                        if (!_internedStrings.TryGetValue(s, out StringInstance instance))
                        {
                            instance = new StringInstance(context.GetWellKnownType(WellKnownType.String), s);
                            _internedStrings.Add(s, instance);
                        }
                        stack.Push(instance);
                    }
                    break;

                    case ILOpcode.ret:
                    {
                        bool returnsVoid = methodIL.OwningMethod.Signature.ReturnType.IsVoid;
                        if ((returnsVoid && stack.Count > 0)
                            || (!returnsVoid && stack.Count != 1))
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        if (!returnsVoid)
                        {
                            returnValue = stack.PopIntoLocation(methodIL.OwningMethod.Signature.ReturnType);
                        }
                        return Status.Success;
                    }

                    case ILOpcode.nop:
                    case ILOpcode.volatile_:
                        break;

                    case ILOpcode.stsfld:
                    {
                        FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
                        if (!field.IsStatic || field.IsLiteral)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        if (field.OwningType != _type)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Store into other static");
                        }

                        if (field.IsThreadStatic || field.HasRva)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported static");
                        }

                        if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
                        }

                        if (_fieldValues[field] is IAssignableValue assignableField)
                        {
                            if (!assignableField.TryAssign(stack.PopIntoLocation(field.FieldType)))
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported store");
                            }
                        }
                        else
                        {
                            Value value = stack.PopIntoLocation(field.FieldType);
                            if (value is IInternalModelingOnlyValue)
                                return Status.Fail(methodIL.OwningMethod, opcode, "Value with no external representation");
                            _fieldValues[field] = value;
                        }
                    }
                    break;

                    case ILOpcode.ldsfld:
                    case ILOpcode.ldsflda:
                    {
                        FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
                        if (!field.IsStatic || field.IsLiteral)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        if (field.IsThreadStatic)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported static");
                        }

                        if (opcode != ILOpcode.ldsfld
                            && _flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
                        }

                        Value fieldValue;
                        if (field.HasRva)
                        {
                            if (!field.IsInitOnly
                                || field.OwningType.HasStaticConstructor
                                || field.GetTypicalFieldDefinition() is not EcmaField ecmaField)
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported RVA static");
                            }

                            fieldValue = new ValueTypeValue(GetFieldRvaData(ecmaField));
                        }
                        else if (field.OwningType == _type)
                        {
                            fieldValue = _fieldValues[field];
                        }
                        else if (_readOnlyPolicy.IsReadOnly(field)
                            && field.OwningType.HasStaticConstructor
                            && _policy.CanPreinitialize(field.OwningType))
                        {
                            if (!TryGetNestedPreinitResult(methodIL.OwningMethod, field.OwningType, recursionProtect, ref instructionCounter, out NestedPreinitResult nestedPreinitResult))
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Nested cctor failed to preinit");
                            }

                            if (!nestedPreinitResult.TryGetFieldValue(this, field, out fieldValue))
                                return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                        else if (_readOnlyPolicy.IsReadOnly(field)
                            && opcode != ILOpcode.ldsflda // We need to intern these for correctness in ldsfda scenarios
                            && !field.OwningType.HasStaticConstructor)
                        {
                            // (Effectively) read only field but no static constructor to set it: the value is default-initialized.
                            fieldValue = NewUninitializedLocationValue(field.FieldType, field);
                        }
                        else
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Load from other non-initonly static");
                        }

                        if (opcode == ILOpcode.ldsfld)
                        {
                            stack.PushFromLocation(field.FieldType, fieldValue);
                        }
                        else
                        {
                            Debug.Assert(opcode == ILOpcode.ldsflda);
                            if (fieldValue == null || !fieldValue.TryCreateByRef(out Value byRefValue))
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported byref");
                            }

                            stack.Push(StackValueKind.ByRef, byRefValue);
                        }
                    }
                    break;

                    case ILOpcode.call:
                    case ILOpcode.callvirt:
                    {
                        MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken());
                        MethodSignature methodSig = method.Signature;
                        int paramOffset = methodSig.IsStatic ? 0 : 1;
                        int numParams = methodSig.Length + paramOffset;

                        if (constrainedType != null)
                        {
                            DefaultInterfaceMethodResolution staticResolution = default;
                            MethodDesc directMethod = constrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(method.OwningType, method, out bool forceUseRuntimeLookup, ref staticResolution);
                            if (directMethod == null || forceUseRuntimeLookup)
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Did not resolve constraint");
                            }
                            method = directMethod;
                        }

                        TypeDesc owningType = method.OwningType;
                        if (!_compilationGroup.CanInline(methodIL.OwningMethod, method))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Cannot inline");
                        }

                        if (owningType.HasStaticConstructor
                                && owningType != methodIL.OwningMethod.OwningType
                                && (method.Signature.IsStatic || method.IsConstructor || owningType.IsValueType || owningType.IsInterface) // ECMA-335 I.8.9.5
                                && !((MetadataType)owningType).IsBeforeFieldInit)
                        {
                            // Static constructor needs to execute before we do the call. If we can preinitialize, consider it executed,
                            // otherwise there might be side effects we'd miss by letting this through.
                            if (!TryGetNestedPreinitResult(methodIL.OwningMethod, (MetadataType)owningType, recursionProtect, ref instructionCounter, out _))
                                return Status.Fail(methodIL.OwningMethod, opcode, "Static constructor");
                        }

                        if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(method))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
                        }

                        Value[] methodParams = new Value[numParams];
                        for (int i = numParams - 1; i >= 0; i--)
                        {
                            methodParams[i] = stack.PopIntoLocation(GetArgType(method, i));
                        }

                        if (opcode == ILOpcode.callvirt)
                        {
                            // Only support non-virtual methods for now + we don't emulate NRE on null this
                            if (!owningType.IsValueType && (method.IsVirtual || methodParams[0] == null))
                                return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        Value retVal;
                        if (!method.IsIntrinsic || !TryHandleIntrinsicCall(method, methodParams, out retVal))
                        {
                            recursionProtect ??= new Stack<MethodDesc>();
                            recursionProtect.Push(methodIL.OwningMethod);
                            Status callResult = TryScanMethod(method, methodParams, recursionProtect, ref instructionCounter, out retVal);
                            if (!callResult.IsSuccessful)
                            {
                                recursionProtect.Pop();
                                return callResult;
                            }
                            recursionProtect.Pop();
                        }

                        if (!methodSig.ReturnType.IsVoid)
                            stack.PushFromLocation(methodSig.ReturnType, retVal);
                    }
                    break;

                    case ILOpcode.newobj:
                    {
                        MethodDesc ctor = (MethodDesc)methodIL.GetObject(reader.ReadILToken());
                        MethodSignature ctorSig = ctor.Signature;

                        TypeDesc owningType = ctor.OwningType;
                        if (!_compilationGroup.CanInline(methodIL.OwningMethod, ctor)
                            || !_compilationGroup.ContainsType(owningType))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Cannot inline");
                        }

                        if (owningType.HasStaticConstructor
                                && owningType != methodIL.OwningMethod.OwningType
                                && !((MetadataType)owningType).IsBeforeFieldInit)
                        {
                            // Static constructor needs to execute before we do the call. If we can preinitialize, consider it executed,
                            // otherwise there might be side effects we'd miss by letting this through.
                            if (!TryGetNestedPreinitResult(methodIL.OwningMethod, (MetadataType)owningType, recursionProtect, ref instructionCounter, out _))
                                return Status.Fail(methodIL.OwningMethod, opcode, "Static constructor");
                        }

                        if (!owningType.IsDefType)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Not a class or struct");
                        }

                        if (owningType.HasFinalizer)
                        {
                            // We have a finalizer. There's still a small chance it has been nopped out
                            // with a feature switch. Check for that.
                            byte[] finalizerMethodILBytes = _ilProvider.GetMethodIL(owningType.GetFinalizer()).GetILBytes();
                            if (finalizerMethodILBytes.Length != 1 || finalizerMethodILBytes[0] != (byte)ILOpcode.ret)
                            {
                                // Finalizer might have observable side effects
                                return Status.Fail(methodIL.OwningMethod, opcode, "Finalizable class");
                            }
                        }

                        if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(ctor))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
                        }

                        if (owningType.RequiresAlign8())
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Align8");
                        }

                        Value[] ctorParameters = new Value[ctorSig.Length + 1];
                        for (int i = ctorSig.Length - 1; i >= 0; i--)
                        {
                            ctorParameters[i + 1] = stack.PopIntoLocation(GetArgType(ctor, i + 1));
                        }

                        AllocationSite allocSite = new AllocationSite(_type, instructionCounter);

                        Value instance;
                        if (owningType.IsDelegate)
                        {
                            if (!(ctorParameters[2] is MethodPointerValue methodPointer))
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Unverifiable delegate creation");
                            }

                            ReferenceTypeValue firstParameter = null;
                            if (ctorParameters[1] != null)
                            {
                                firstParameter = ctorParameters[1] as ReferenceTypeValue;
                                if (firstParameter == null)
                                {
                                    ThrowHelper.ThrowInvalidProgramException();
                                }
                            }

                            MethodDesc pointedMethod = methodPointer.PointedToMethod;
                            if ((firstParameter == null) != pointedMethod.Signature.IsStatic)
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Open/closed static/instance delegate mismatch");
                            }

                            if (firstParameter != null && pointedMethod.HasInstantiation)
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Delegate with fat pointer");
                            }

                            instance = new DelegateInstance(owningType, pointedMethod, firstParameter, allocSite);
                        }
                        else
                        {
                            if (owningType.IsValueType)
                            {
                                instance = NewUninitializedLocationValue(owningType, fieldThatOwnsMemory: null);
                                if (!instance.TryCreateByRef(out ctorParameters[0]))
                                {
                                    return Status.Fail(methodIL.OwningMethod, opcode, "Can't make `this`");
                                }
                            }
                            else
                            {
                                instance = new ObjectInstance((DefType)owningType, allocSite);
                                ctorParameters[0] = instance;
                            }

                            if (((DefType)owningType).ContainsGCPointers)
                            {
                                // We don't want to end up with GC pointers in the frozen region
                                // because write barriers can't handle that.

                                // We can make an exception for readonly fields.
                                bool allGcPointersAreReadonly = true;
                                TypeDesc currentType = owningType;
                                do
                                {
                                    foreach (FieldDesc field in currentType.GetFields())
                                    {
                                        if (field.IsStatic)
                                            continue;

                                        TypeDesc fieldType = field.FieldType;
                                        if (fieldType.IsGCPointer)
                                        {
                                            if (!_readOnlyPolicy.IsReadOnly(field))
                                            {
                                                allGcPointersAreReadonly = false;
                                                break;
                                            }
                                        }
                                        else if (fieldType.IsValueType && ((DefType)fieldType).ContainsGCPointers)
                                        {
                                            allGcPointersAreReadonly = false;
                                            break;
                                        }
                                    }
                                } while (allGcPointersAreReadonly && (currentType = currentType.BaseType) != null && !currentType.IsValueType);

                                if (!allGcPointersAreReadonly)
                                    return Status.Fail(methodIL.OwningMethod, opcode, "GC pointers");
                            }

                            recursionProtect ??= new Stack<MethodDesc>();
                            recursionProtect.Push(methodIL.OwningMethod);
                            Status ctorCallResult = TryScanMethod(ctor, ctorParameters, recursionProtect, ref instructionCounter, out _);
                            if (!ctorCallResult.IsSuccessful)
                            {
                                recursionProtect.Pop();
                                return ctorCallResult;
                            }

                            recursionProtect.Pop();
                        }

                        stack.PushFromLocation(owningType, instance);
                    }
                    break;

                    case ILOpcode.localloc:
                    {
                        StackEntry entry = stack.Pop();
                        long size = entry.ValueKind switch
                        {
                            StackValueKind.Int32 => entry.Value.AsInt32(),
                            StackValueKind.NativeInt => (context.Target.PointerSize == 4)
                                ? entry.Value.AsInt32() : entry.Value.AsInt64(),
                            _ => long.MaxValue
                        };

                        // Arbitrary limit for allocation size to prevent compiler OOM
                        if (size < 0 || size > 8192)
                            return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc);

                        stack.Push(StackValueKind.NativeInt, new ByRefValue(new byte[size], pointedToOffset: 0));
                    }
                    break;

                    case ILOpcode.stfld:
                    {
                        FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());

                        if (field.IsStatic)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Static field with stfld");
                        }

                        Value value = stack.PopIntoLocation(field.FieldType);
                        StackEntry instance = stack.Pop();

                        if (field.FieldType.IsGCPointer && value != null)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Reference field");
                        }

                        if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
                        }

                        if (instance.Value is not IHasInstanceFields settableInstance
                            || !settableInstance.TrySetField(field, value))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Not settable");
                        }
                    }
                    break;

                    case ILOpcode.ldfld:
                    {
                        FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());

                        if (field.FieldType.IsGCPointer
                            || field.IsStatic)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        StackEntry instance = stack.Pop();

                        var loadableInstance = instance.Value as IHasInstanceFields;
                        if (loadableInstance == null)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        Value fieldValue = loadableInstance.GetField(field);

                        stack.PushFromLocation(field.FieldType, fieldValue);
                    }
                    break;

                    case ILOpcode.ldflda:
                    {
                        FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
                        if (field.FieldType.IsGCPointer
                            || field.IsStatic)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
                        }

                        StackEntry instance = stack.Pop();

                        var loadableInstance = instance.Value as IHasInstanceFields;
                        if (loadableInstance == null)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        stack.Push(StackValueKind.ByRef, loadableInstance.GetFieldAddress(field));
                    }
                    break;

                    case ILOpcode.conv_i:
                    case ILOpcode.conv_u:
                    case ILOpcode.conv_i1:
                    case ILOpcode.conv_i2:
                    case ILOpcode.conv_i4:
                    case ILOpcode.conv_i8:
                    case ILOpcode.conv_u1:
                    case ILOpcode.conv_u2:
                    case ILOpcode.conv_u4:
                    case ILOpcode.conv_u8:
                    case ILOpcode.conv_r4:
                    case ILOpcode.conv_r8:
                    {
                        StackEntry popped = stack.Pop();

                        if (opcode is ILOpcode.conv_i or ILOpcode.conv_u
                            && popped.ValueKind == StackValueKind.ByRef)
                        {
                            Debug.Assert(popped.Value is ByRefValueBase);
                            stack.Push(StackValueKind.NativeInt, popped.Value);
                        }
                        else if (popped.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
                        {
                            int val = popped.Value.AsInt32();
                            switch (opcode)
                            {
                                case ILOpcode.conv_i:
                                    stack.Push(StackValueKind.NativeInt,
                                        context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64(val) : ValueTypeValue.FromInt32(val));
                                    break;
                                case ILOpcode.conv_u:
                                    stack.Push(StackValueKind.NativeInt,
                                        context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64((uint)val) : ValueTypeValue.FromInt32(val));
                                    break;
                                case ILOpcode.conv_i1:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((sbyte)val));
                                    break;
                                case ILOpcode.conv_i2:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((short)val));
                                    break;
                                case ILOpcode.conv_i4:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(val));
                                    break;
                                case ILOpcode.conv_i8:
                                    stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(val));
                                    break;
                                case ILOpcode.conv_u1:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)val));
                                    break;
                                case ILOpcode.conv_u2:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)val));
                                    break;
                                case ILOpcode.conv_u4:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(val));
                                    break;
                                case ILOpcode.conv_u8:
                                    stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64((uint)val));
                                    break;
                                case ILOpcode.conv_r4:
                                    stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble((float)val));
                                    break;
                                case ILOpcode.conv_r8:
                                    stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble((double)val));
                                    break;
                                default:
                                    return Status.Fail(methodIL.OwningMethod, opcode);
                            }
                        }
                        else if (popped.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
                        {
                            long val = popped.Value.AsInt64();
                            switch (opcode)
                            {
                                case ILOpcode.conv_u:
                                case ILOpcode.conv_i:
                                    stack.Push(StackValueKind.NativeInt,
                                        context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64(val) : ValueTypeValue.FromInt32((int)val));
                                    break;
                                case ILOpcode.conv_i1:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((sbyte)val));
                                    break;
                                case ILOpcode.conv_i2:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((short)val));
                                    break;
                                case ILOpcode.conv_i4:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((int)val));
                                    break;
                                case ILOpcode.conv_i8:
                                    stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(val));
                                    break;
                                case ILOpcode.conv_u1:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)val));
                                    break;
                                case ILOpcode.conv_u2:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)val));
                                    break;
                                case ILOpcode.conv_u4:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((int)val));
                                    break;
                                case ILOpcode.conv_u8:
                                    stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(val));
                                    break;
                                case ILOpcode.conv_r4:
                                    stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble((float)val));
                                    break;
                                case ILOpcode.conv_r8:
                                    stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble((double)val));
                                    break;
                                default:
                                    return Status.Fail(methodIL.OwningMethod, opcode);
                            }
                        }
                        else if (popped.ValueKind == StackValueKind.Float)
                        {
                            double val = popped.Value.AsDouble();
                            switch (opcode)
                            {
                                case ILOpcode.conv_i:
                                    stack.Push(StackValueKind.NativeInt,
                                        context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64((long)val) : ValueTypeValue.FromInt32((int)val));
                                    break;
                                case ILOpcode.conv_u:
                                    stack.Push(StackValueKind.NativeInt,
                                        context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64((long)(ulong)val) : ValueTypeValue.FromInt32((int)(uint)val));
                                    break;
                                case ILOpcode.conv_i1:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((sbyte)val));
                                    break;
                                case ILOpcode.conv_i2:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((short)val));
                                    break;
                                case ILOpcode.conv_i4:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((int)val));
                                    break;
                                case ILOpcode.conv_i8:
                                    stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64((long)val));
                                    break;
                                case ILOpcode.conv_u1:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)val));
                                    break;
                                case ILOpcode.conv_u2:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)val));
                                    break;
                                case ILOpcode.conv_u4:
                                    stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((int)(uint)val));
                                    break;
                                case ILOpcode.conv_u8:
                                    stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64((long)(ulong)val));
                                    break;
                                case ILOpcode.conv_r4:
                                    stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble((float)val));
                                    break;
                                case ILOpcode.conv_r8:
                                    stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(val));
                                    break;
                                default:
                                    return Status.Fail(methodIL.OwningMethod, opcode);
                            }
                        }
                        else
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                    }
                    break;

                    case ILOpcode.ldarg_0:
                    case ILOpcode.ldarg_1:
                    case ILOpcode.ldarg_2:
                    case ILOpcode.ldarg_3:
                    case ILOpcode.ldarg_s:
                    case ILOpcode.ldarg:
                    {
                        int index = opcode switch
                        {
                            ILOpcode.ldarg_s => reader.ReadILByte(),
                            ILOpcode.ldarg => reader.ReadILUInt16(),
                            _ => opcode - ILOpcode.ldarg_0,
                        };
                        stack.PushFromLocation(GetArgType(methodIL.OwningMethod, index), parameters[index]);
                    }
                    break;

                    case ILOpcode.starg_s:
                    case ILOpcode.starg:
                    {
                        int index = opcode == ILOpcode.starg ? reader.ReadILUInt16() : reader.ReadILByte();
                        TypeDesc argType = GetArgType(methodIL.OwningMethod, index);
                        if (parameters[index] is IAssignableValue assignableParam)
                        {
                            if (!assignableParam.TryAssign(stack.PopIntoLocation(argType)))
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported store");
                            }
                        }
                        else
                            parameters[index] = stack.PopIntoLocation(argType);
                    }
                    break;

                    case ILOpcode.ldtoken:
                    {
                        var token = methodIL.GetObject(reader.ReadILToken());
                        if (token is FieldDesc field)
                        {
                            stack.Push(new StackEntry(StackValueKind.ValueType, new RuntimeFieldHandleValue(field)));
                        }
                        else if (token is TypeDesc type)
                        {
                            stack.Push(new StackEntry(StackValueKind.ValueType, new RuntimeTypeHandleValue(type)));
                        }
                        else
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                    }
                    break;

                    case ILOpcode.ldftn:
                    {
                        if (constrainedType != null)
                            return Status.Fail(methodIL.OwningMethod, ILOpcode.constrained);

                        var method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc;
                        if (method != null)
                            stack.Push(StackValueKind.NativeInt, new MethodPointerValue(method));
                        else
                            ThrowHelper.ThrowInvalidProgramException();
                    }
                    break;

                    case ILOpcode.ldloc_0:
                    case ILOpcode.ldloc_1:
                    case ILOpcode.ldloc_2:
                    case ILOpcode.ldloc_3:
                    case ILOpcode.ldloc_s:
                    case ILOpcode.ldloc:
                    {
                        int index = opcode switch
                        {
                            ILOpcode.ldloc_s => reader.ReadILByte(),
                            ILOpcode.ldloc => reader.ReadILUInt16(),
                            _ => opcode - ILOpcode.ldloc_0,
                        };

                        if (index >= locals.Length)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        stack.PushFromLocation(localTypes[index].Type, locals[index]);
                    }
                    break;

                    case ILOpcode.stloc_0:
                    case ILOpcode.stloc_1:
                    case ILOpcode.stloc_2:
                    case ILOpcode.stloc_3:
                    case ILOpcode.stloc_s:
                    case ILOpcode.stloc:
                    {
                        int index = opcode switch
                        {
                            ILOpcode.stloc_s => reader.ReadILByte(),
                            ILOpcode.stloc => reader.ReadILUInt16(),
                            _ => opcode - ILOpcode.stloc_0,
                        };

                        if (index >= locals.Length)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        TypeDesc localType = localTypes[index].Type;
                        if (locals[index] is IAssignableValue assignableLocal)
                        {
                            if (!assignableLocal.TryAssign(stack.PopIntoLocation(localType)))
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported store");
                            }
                        }
                        else
                            locals[index] = stack.PopIntoLocation(localType);

                    }
                    break;

                    case ILOpcode.ldarga_s:
                    case ILOpcode.ldarga:
                    case ILOpcode.ldloca_s:
                    case ILOpcode.ldloca:
                    {
                        int index = opcode switch
                        {
                            ILOpcode.ldloca_s or ILOpcode.ldarga_s => reader.ReadILByte(),
                            ILOpcode.ldloca or ILOpcode.ldarga => reader.ReadILUInt16(),
                            _ => throw new NotImplementedException(), // Unreachable
                        };

                        Value[] storage = opcode is ILOpcode.ldloca or ILOpcode.ldloca_s ? locals : parameters;
                        if (index >= storage.Length)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        Value localValue = storage[index];
                        if (localValue == null || !localValue.TryCreateByRef(out Value byrefValue))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                        else
                        {
                            stack.Push(StackValueKind.ByRef, byrefValue);
                        }
                    }
                    break;

                    case ILOpcode.initobj:
                    {
                        StackEntry popped = stack.Pop();
                        if (popped.ValueKind != StackValueKind.ByRef)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        TypeDesc token = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
                        if (token.IsGCPointer
                            || popped.Value is not ByRefValueBase byrefVal
                            || !byrefVal.TryInitialize(token.GetElementSize().AsInt))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                    }
                    break;

                    case ILOpcode.br:
                    case ILOpcode.brfalse:
                    case ILOpcode.brtrue:
                    case ILOpcode.blt:
                    case ILOpcode.blt_un:
                    case ILOpcode.bgt:
                    case ILOpcode.bgt_un:
                    case ILOpcode.beq:
                    case ILOpcode.bne_un:
                    case ILOpcode.bge:
                    case ILOpcode.bge_un:
                    case ILOpcode.ble:
                    case ILOpcode.ble_un:
                    case ILOpcode.br_s:
                    case ILOpcode.brfalse_s:
                    case ILOpcode.brtrue_s:
                    case ILOpcode.blt_s:
                    case ILOpcode.blt_un_s:
                    case ILOpcode.bgt_s:
                    case ILOpcode.bgt_un_s:
                    case ILOpcode.beq_s:
                    case ILOpcode.bne_un_s:
                    case ILOpcode.bge_s:
                    case ILOpcode.bge_un_s:
                    case ILOpcode.ble_s:
                    case ILOpcode.ble_un_s:
                    {
                        int delta = opcode >= ILOpcode.br ?
                            (int)reader.ReadILUInt32() :
                            (sbyte)reader.ReadILByte();
                        int target = reader.Offset + delta;
                        if (target < 0
                            || target > reader.Size)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        ILOpcode normalizedOpcode = opcode >= ILOpcode.br ?
                            opcode - ILOpcode.br + ILOpcode.br_s :
                            opcode;

                        bool branchTaken;
                        if (normalizedOpcode == ILOpcode.brtrue_s || normalizedOpcode == ILOpcode.brfalse_s)
                        {
                            StackEntry condition = stack.Pop();
                            if (condition.ValueKind == StackValueKind.Int32 || (condition.ValueKind == StackValueKind.NativeInt && context.Target.PointerSize == 4))
                                branchTaken = normalizedOpcode == ILOpcode.brfalse_s
                                    ? condition.Value.AsInt32() == 0 : condition.Value.AsInt32() != 0;
                            else if (condition.ValueKind == StackValueKind.Int64 || (condition.ValueKind == StackValueKind.NativeInt && context.Target.PointerSize == 8))
                                branchTaken = normalizedOpcode == ILOpcode.brfalse_s
                                    ? condition.Value.AsInt64() == 0 : condition.Value.AsInt64() != 0;
                            else if (condition.ValueKind == StackValueKind.ObjRef)
                                branchTaken = normalizedOpcode == ILOpcode.brfalse_s
                                    ? condition.Value == null : condition.Value != null;
                            else
                                return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                        else if (normalizedOpcode == ILOpcode.blt_s || normalizedOpcode == ILOpcode.bgt_s
                            || normalizedOpcode == ILOpcode.bge_s || normalizedOpcode == ILOpcode.beq_s
                            || normalizedOpcode == ILOpcode.ble_s || normalizedOpcode == ILOpcode.blt_un_s
                            || normalizedOpcode == ILOpcode.ble_un_s || normalizedOpcode == ILOpcode.bge_un_s
                            || normalizedOpcode == ILOpcode.bgt_un_s || normalizedOpcode == ILOpcode.bne_un_s)
                        {
                            StackEntry value2 = stack.Pop();
                            StackEntry value1 = stack.Pop();

                            if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
                            {
                                branchTaken = normalizedOpcode switch
                                {
                                    ILOpcode.blt_s => value1.Value.AsInt32() < value2.Value.AsInt32(),
                                    ILOpcode.blt_un_s => (uint)value1.Value.AsInt32() < (uint)value2.Value.AsInt32(),
                                    ILOpcode.bgt_s => value1.Value.AsInt32() > value2.Value.AsInt32(),
                                    ILOpcode.bgt_un_s => (uint)value1.Value.AsInt32() > (uint)value2.Value.AsInt32(),
                                    ILOpcode.bge_s => value1.Value.AsInt32() >= value2.Value.AsInt32(),
                                    ILOpcode.bge_un_s => (uint)value1.Value.AsInt32() >= (uint)value2.Value.AsInt32(),
                                    ILOpcode.beq_s => value1.Value.AsInt32() == value2.Value.AsInt32(),
                                    ILOpcode.bne_un_s => value1.Value.AsInt32() != value2.Value.AsInt32(),
                                    ILOpcode.ble_s => value1.Value.AsInt32() <= value2.Value.AsInt32(),
                                    ILOpcode.ble_un_s => (uint)value1.Value.AsInt32() <= (uint)value2.Value.AsInt32(),
                                    _ => throw new NotImplementedException() // unreachable
                                };
                            }
                            else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
                            {
                                branchTaken = normalizedOpcode switch
                                {
                                    ILOpcode.blt_s => value1.Value.AsInt64() < value2.Value.AsInt64(),
                                    ILOpcode.blt_un_s => (ulong)value1.Value.AsInt64() < (ulong)value2.Value.AsInt64(),
                                    ILOpcode.bgt_s => value1.Value.AsInt64() > value2.Value.AsInt64(),
                                    ILOpcode.bgt_un_s => (ulong)value1.Value.AsInt64() > (ulong)value2.Value.AsInt64(),
                                    ILOpcode.bge_s => value1.Value.AsInt64() >= value2.Value.AsInt64(),
                                    ILOpcode.bge_un_s => (ulong)value1.Value.AsInt64() >= (ulong)value2.Value.AsInt64(),
                                    ILOpcode.beq_s => value1.Value.AsInt64() == value2.Value.AsInt64(),
                                    ILOpcode.bne_un_s => value1.Value.AsInt64() != value2.Value.AsInt64(),
                                    ILOpcode.ble_s => value1.Value.AsInt64() <= value2.Value.AsInt64(),
                                    ILOpcode.ble_un_s => (ulong)value1.Value.AsInt64() <= (ulong)value2.Value.AsInt64(),
                                    _ => throw new NotImplementedException() // unreachable
                                };
                            }
                            else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
                            {
                                branchTaken = normalizedOpcode switch
                                {
                                    ILOpcode.blt_s => value1.Value.AsDouble() < value2.Value.AsDouble(),
                                    ILOpcode.blt_un_s => !(value1.Value.AsDouble() >= value2.Value.AsDouble()),
                                    ILOpcode.bgt_s => value1.Value.AsDouble() > value2.Value.AsDouble(),
                                    ILOpcode.bgt_un_s => !(value1.Value.AsDouble() <= value2.Value.AsDouble()),
                                    ILOpcode.bge_s => value1.Value.AsDouble() >= value2.Value.AsDouble(),
                                    ILOpcode.bge_un_s => !(value1.Value.AsDouble() < value2.Value.AsDouble()),
                                    ILOpcode.beq_s => value1.Value.AsDouble() == value2.Value.AsDouble(),
                                    ILOpcode.bne_un_s => value1.Value.AsDouble() != value2.Value.AsDouble(),
                                    ILOpcode.ble_s => value1.Value.AsDouble() <= value2.Value.AsDouble(),
                                    ILOpcode.ble_un_s => !(value1.Value.AsDouble() > value2.Value.AsDouble()),
                                    _ => throw new NotImplementedException() // unreachable
                                };
                            }
                            else
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode);
                            }
                        }
                        else
                        {
                            Debug.Assert(normalizedOpcode == ILOpcode.br_s);
                            branchTaken = true;
                        }

                        if (branchTaken)
                        {
                            reader.Seek(target);
                        }
                    }
                    break;

                    case ILOpcode.switch_:
                    {
                        StackEntry val = stack.Pop();
                        if (val.ValueKind is not StackValueKind.Int32)
                            ThrowHelper.ThrowInvalidProgramException();

                        uint target = (uint)val.Value.AsInt32();

                        uint count = reader.ReadILUInt32();
                        int nextInstruction = reader.Offset + (int)(4 * count);
                        if (target >= count)
                        {
                            reader.Seek(nextInstruction);
                        }
                        else
                        {
                            reader.Seek(reader.Offset + (int)(4 * target));
                            reader.Seek(nextInstruction + (int)reader.ReadILUInt32());
                        }
                    }
                    break;

                    case ILOpcode.leave:
                    case ILOpcode.leave_s:
                    {
                        stack.Clear();

                        // We assume no finally regions (would have to run them here)
                        // This is validated before, but we're being paranoid.
                        foreach (ILExceptionRegion ehRegion in ehRegions)
                        {
                            Debug.Assert(ehRegion.Kind != ILExceptionRegionKind.Finally);
                        }

                        int delta = opcode == ILOpcode.leave ?
                            (int)reader.ReadILUInt32() :
                            (sbyte)reader.ReadILByte();
                        int target = reader.Offset + delta;
                        if (target < 0
                            || target > reader.Size)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        reader.Seek(target);
                    }
                    break;

                    case ILOpcode.clt:
                    case ILOpcode.clt_un:
                    case ILOpcode.cgt:
                    case ILOpcode.cgt_un:
                    {
                        StackEntry value1 = stack.Pop();
                        StackEntry value2 = stack.Pop();

                        bool condition;
                        if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
                        {
                            if (opcode == ILOpcode.cgt)
                                condition = value1.Value.AsInt32() < value2.Value.AsInt32();
                            else if (opcode == ILOpcode.cgt_un)
                                condition = (uint)value1.Value.AsInt32() < (uint)value2.Value.AsInt32();
                            else if (opcode == ILOpcode.clt)
                                condition = value1.Value.AsInt32() > value2.Value.AsInt32();
                            else if (opcode == ILOpcode.clt_un)
                                condition = (uint)value1.Value.AsInt32() > (uint)value2.Value.AsInt32();
                            else
                                return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                        else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
                        {
                            if (opcode == ILOpcode.cgt)
                                condition = value1.Value.AsInt64() < value2.Value.AsInt64();
                            else if (opcode == ILOpcode.cgt_un)
                                condition = (ulong)value1.Value.AsInt64() < (ulong)value2.Value.AsInt64();
                            else if (opcode == ILOpcode.clt)
                                condition = value1.Value.AsInt64() > value2.Value.AsInt64();
                            else if (opcode == ILOpcode.clt_un)
                                condition = (ulong)value1.Value.AsInt64() > (ulong)value2.Value.AsInt64();
                            else
                                return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                        else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
                        {
                            if (opcode == ILOpcode.cgt)
                                condition = value1.Value.AsDouble() < value2.Value.AsDouble();
                            else if (opcode == ILOpcode.cgt_un)
                                condition = !(value1.Value.AsDouble() >= value2.Value.AsDouble());
                            else if (opcode == ILOpcode.clt)
                                condition = value1.Value.AsDouble() > value2.Value.AsDouble();
                            else if (opcode == ILOpcode.clt_un)
                                condition = !(value1.Value.AsDouble() <= value2.Value.AsDouble());
                            else
                                return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                        else if (value1.ValueKind == StackValueKind.ObjRef && value2.ValueKind == StackValueKind.ObjRef)
                        {
                            if (opcode == ILOpcode.cgt_un)
                                condition = value1.Value == null && value2.Value != null;
                            else
                                return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                        else
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        stack.Push(StackValueKind.Int32, condition
                                ? ValueTypeValue.FromInt32(1)
                                : ValueTypeValue.FromInt32(0));
                    }
                    break;

                    case ILOpcode.ceq:
                    {
                        StackEntry value1 = stack.Pop();
                        StackEntry value2 = stack.Pop();

                        bool compareResult;
                        if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
                        {
                            compareResult = value1.Value.AsDouble() == value2.Value.AsDouble();
                        }
                        else if (value1.ValueKind != value2.ValueKind
                            || !Value.TryCompareEquality(value1.Value, value2.Value, out compareResult))
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        stack.Push(StackValueKind.Int32,
                            compareResult
                            ? ValueTypeValue.FromInt32(1)
                            : ValueTypeValue.FromInt32(0));
                    }
                    break;

                    case ILOpcode.neg:
                    {
                        StackEntry value = stack.Pop();
                        if (value.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
                            stack.Push(value.ValueKind, ValueTypeValue.FromInt32(-value.Value.AsInt32()));
                        else if (value.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
                            stack.Push(value.ValueKind, ValueTypeValue.FromInt64(-value.Value.AsInt64()));
                        else if (value.ValueKind == StackValueKind.Float)
                            stack.Push(value.ValueKind, ValueTypeValue.FromDouble(-value.Value.AsDouble()));
                        else
                            return Status.Fail(methodIL.OwningMethod, opcode);
                    }
                    break;

                    case ILOpcode.or:
                    case ILOpcode.shl:
                    case ILOpcode.add:
                    case ILOpcode.sub:
                    case ILOpcode.mul:
                    case ILOpcode.and:
                    case ILOpcode.div:
                    case ILOpcode.div_un:
                    case ILOpcode.rem:
                    case ILOpcode.rem_un:
                    {
                        bool isDivRem = opcode == ILOpcode.div || opcode == ILOpcode.div_un
                            || opcode == ILOpcode.rem || opcode == ILOpcode.rem_un;

                        StackEntry value2 = stack.Pop();
                        StackEntry value1 = stack.Pop();

                        bool isNint = value1.ValueKind == StackValueKind.NativeInt || value2.ValueKind == StackValueKind.NativeInt;

                        if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
                        {
                            if (isDivRem && value2.Value.AsInt32() == 0)
                                return Status.Fail(methodIL.OwningMethod, opcode, "Division by zero");

                            if ((opcode == ILOpcode.div || opcode == ILOpcode.rem)
                                && value1.Value.AsInt32() == int.MinValue && value2.Value.AsInt32() == -1)
                                return Status.Fail(methodIL.OwningMethod, opcode, "Overflow");

                            int result = opcode switch
                            {
                                ILOpcode.or => value1.Value.AsInt32() | value2.Value.AsInt32(),
                                ILOpcode.shl => value1.Value.AsInt32() << value2.Value.AsInt32(),
                                ILOpcode.add => value1.Value.AsInt32() + value2.Value.AsInt32(),
                                ILOpcode.sub => value1.Value.AsInt32() - value2.Value.AsInt32(),
                                ILOpcode.and => value1.Value.AsInt32() & value2.Value.AsInt32(),
                                ILOpcode.mul => value1.Value.AsInt32() * value2.Value.AsInt32(),
                                ILOpcode.div => value1.Value.AsInt32() / value2.Value.AsInt32(),
                                ILOpcode.div_un => (int)((uint)value1.Value.AsInt32() / (uint)value2.Value.AsInt32()),
                                ILOpcode.rem => value1.Value.AsInt32() % value2.Value.AsInt32(),
                                ILOpcode.rem_un => (int)((uint)value1.Value.AsInt32() % (uint)value2.Value.AsInt32()),
                                _ => throw new NotImplementedException(), // unreachable
                            };

                            stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int32, ValueTypeValue.FromInt32(result));
                        }
                        else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
                        {
                            if (isDivRem && value2.Value.AsInt64() == 0)
                                return Status.Fail(methodIL.OwningMethod, opcode, "Division by zero");

                            if ((opcode == ILOpcode.div || opcode == ILOpcode.rem)
                                && value1.Value.AsInt64() == long.MinValue && value2.Value.AsInt64() == -1)
                                return Status.Fail(methodIL.OwningMethod, opcode, "Overflow");

                            long result = opcode switch
                            {
                                ILOpcode.or => value1.Value.AsInt64() | value2.Value.AsInt64(),
                                ILOpcode.shl => value1.Value.AsInt64() << (int)value2.Value.AsInt64(),
                                ILOpcode.add => value1.Value.AsInt64() + value2.Value.AsInt64(),
                                ILOpcode.sub => value1.Value.AsInt64() - value2.Value.AsInt64(),
                                ILOpcode.and => value1.Value.AsInt64() & value2.Value.AsInt64(),
                                ILOpcode.mul => value1.Value.AsInt64() * value2.Value.AsInt64(),
                                ILOpcode.div => value1.Value.AsInt64() / value2.Value.AsInt64(),
                                ILOpcode.div_un => (long)((ulong)value1.Value.AsInt64() / (ulong)value2.Value.AsInt64()),
                                ILOpcode.rem => value1.Value.AsInt64() % value2.Value.AsInt64(),
                                ILOpcode.rem_un => (long)((ulong)value1.Value.AsInt64() % (ulong)value2.Value.AsInt64()),
                                _ => throw new NotImplementedException(), // unreachable
                            };

                            stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int64, ValueTypeValue.FromInt64(result));
                        }
                        else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
                        {
                            if (isDivRem && value2.Value.AsDouble() == 0)
                                return Status.Fail(methodIL.OwningMethod, opcode, "Division by zero");

                            if (opcode == ILOpcode.or || opcode == ILOpcode.shl || opcode == ILOpcode.and || opcode == ILOpcode.div_un || opcode == ILOpcode.rem_un)
                                ThrowHelper.ThrowInvalidProgramException();

                            double result = opcode switch
                            {
                                ILOpcode.add => value1.Value.AsDouble() + value2.Value.AsDouble(),
                                ILOpcode.sub => value1.Value.AsDouble() - value2.Value.AsDouble(),
                                ILOpcode.mul => value1.Value.AsDouble() * value2.Value.AsDouble(),
                                ILOpcode.div => value1.Value.AsDouble() / value2.Value.AsDouble(),
                                ILOpcode.rem => value1.Value.AsDouble() % value2.Value.AsDouble(),
                                _ => throw new NotImplementedException(), // unreachable
                            };

                            stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(result));
                        }
                        else if (value1.ValueKind == StackValueKind.Int64 && value2.ValueKind == StackValueKind.Int32
                            && opcode == ILOpcode.shl)
                        {
                            long result = value1.Value.AsInt64() << value2.Value.AsInt32();
                            stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int64, ValueTypeValue.FromInt64(result));
                        }
                        else if ((value1.ValueKind == StackValueKind.ByRef && value2.ValueKind != StackValueKind.ByRef)
                            || (value2.ValueKind == StackValueKind.ByRef && value1.ValueKind != StackValueKind.ByRef))
                        {
                            if (opcode != ILOpcode.add)
                                ThrowHelper.ThrowInvalidProgramException();

                            StackEntry reference = value1.ValueKind == StackValueKind.ByRef ? value1 : value2;
                            StackEntry addend = value1.ValueKind != StackValueKind.ByRef ? value1 : value2;

                            if (addend.ValueKind is not StackValueKind.NativeInt and not StackValueKind.Int32)
                                ThrowHelper.ThrowInvalidProgramException();

                            long addition = addend.ValueKind switch
                            {
                                StackValueKind.Int32 => addend.Value.AsInt32(),
                                _ => context.Target.PointerSize == 8 ? addend.Value.AsInt64() : addend.Value.AsInt32()
                            };

                            if (reference.Value is not ByRefValue previousByRef)
                                return Status.Fail(methodIL.OwningMethod, "Byref math with unsupported byref");

                            if (addition > previousByRef.PointedToBytes.Length - previousByRef.PointedToOffset
                                || addition + previousByRef.PointedToOffset < 0)
                                return Status.Fail(methodIL.OwningMethod, "Out of range byref access");

                            stack.Push(StackValueKind.ByRef, new ByRefValue(previousByRef.PointedToBytes, (int)(previousByRef.PointedToOffset + addition)));
                        }
                        else
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }
                    }
                    break;

                    case ILOpcode.ldlen:
                    {
                        StackEntry popped = stack.Pop();
                        if (popped.Value is ArrayInstance arrayInstance)
                        {
                            stack.Push(StackValueKind.NativeInt, context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64(arrayInstance.Length) : ValueTypeValue.FromInt32(arrayInstance.Length));
                        }
                        else if (popped.Value == null)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Null array");
                        }
                        else
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                    }
                    break;

                    case ILOpcode.stelem:
                    case ILOpcode.stelem_i:
                    case ILOpcode.stelem_i1:
                    case ILOpcode.stelem_i2:
                    case ILOpcode.stelem_i4:
                    case ILOpcode.stelem_i8:
                    case ILOpcode.stelem_r4:
                    case ILOpcode.stelem_r8:
                    {
                        TypeDesc elementType = opcode switch
                        {
                            ILOpcode.stelem_i => context.GetWellKnownType(WellKnownType.IntPtr),
                            ILOpcode.stelem_i1 => context.GetWellKnownType(WellKnownType.SByte),
                            ILOpcode.stelem_i2 => context.GetWellKnownType(WellKnownType.Int16),
                            ILOpcode.stelem_i4 => context.GetWellKnownType(WellKnownType.Int32),
                            ILOpcode.stelem_i8 => context.GetWellKnownType(WellKnownType.Int64),
                            ILOpcode.stelem_r4 => context.GetWellKnownType(WellKnownType.Single),
                            ILOpcode.stelem_r8 => context.GetWellKnownType(WellKnownType.Double),
                            _ => (TypeDesc)methodIL.GetObject(reader.ReadILToken()),
                        };

                        if (elementType.IsGCPointer)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        Value value = stack.PopIntoLocation(elementType);
                        if (!stack.TryPopIntValue(out int index))
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        StackEntry array = stack.Pop();
                        if (array.Value is ArrayInstance arrayInstance)
                        {
                            if (!arrayInstance.TryStoreElement(index, value))
                                return Status.Fail(methodIL.OwningMethod, opcode, "Out of range access");
                        }
                        else if (array.Value == null)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Null array");
                        }
                        else
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                    }
                    break;

                    case ILOpcode.ldelem:
                    case ILOpcode.ldelem_i:
                    case ILOpcode.ldelem_i1:
                    case ILOpcode.ldelem_u1:
                    case ILOpcode.ldelem_i2:
                    case ILOpcode.ldelem_u2:
                    case ILOpcode.ldelem_i4:
                    case ILOpcode.ldelem_u4:
                    case ILOpcode.ldelem_i8:
                    case ILOpcode.ldelem_r4:
                    case ILOpcode.ldelem_r8:
                    {
                        TypeDesc elementType = opcode switch
                        {
                            ILOpcode.ldelem_i => context.GetWellKnownType(WellKnownType.IntPtr),
                            ILOpcode.ldelem_i1 => context.GetWellKnownType(WellKnownType.SByte),
                            ILOpcode.ldelem_u1 => context.GetWellKnownType(WellKnownType.Byte),
                            ILOpcode.ldelem_i2 => context.GetWellKnownType(WellKnownType.Int16),
                            ILOpcode.ldelem_u2 => context.GetWellKnownType(WellKnownType.UInt16),
                            ILOpcode.ldelem_i4 => context.GetWellKnownType(WellKnownType.Int32),
                            ILOpcode.ldelem_u4 => context.GetWellKnownType(WellKnownType.UInt32),
                            ILOpcode.ldelem_i8 => context.GetWellKnownType(WellKnownType.Int64),
                            ILOpcode.ldelem_r4 => context.GetWellKnownType(WellKnownType.Single),
                            ILOpcode.ldelem_r8 => context.GetWellKnownType(WellKnownType.Double),
                            _ => (TypeDesc)methodIL.GetObject(reader.ReadILToken()),
                        };

                        if (elementType.IsGCPointer)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode);
                        }

                        if (!stack.TryPopIntValue(out int index))
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                        StackEntry array = stack.Pop();
                        if (array.Value is ArrayInstance arrayInstance)
                        {
                            if (!arrayInstance.TryLoadElement(index, out Value value))
                                return Status.Fail(methodIL.OwningMethod, opcode, "Out of range access");

                            stack.PushFromLocation(elementType, value);
                        }
                        else if (array.Value == null)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Null array");
                        }
                        else if (array.Value is ForeignTypeInstance)
                        {
                            return Status.Fail(methodIL.OwningMethod, opcode, "Foreign array");
                        }
                        else
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }

                    }
                    break;

                    case ILOpcode.box:
                    {
                        TypeDesc type = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
                        if (type.IsValueType)
                        {
                            if (type.IsNullable)
                                return Status.Fail(methodIL.OwningMethod, opcode);

                            if (type.RequiresAlign8())
                                return Status.Fail(methodIL.OwningMethod, opcode, "Align8");

                            Value value = stack.PopIntoLocation(type);
                            AllocationSite allocSite = new AllocationSite(_type, instructionCounter);
                            if (!ObjectInstance.TryBox((DefType)type, value, allocSite, out ObjectInstance boxedResult))
                            {
                                return Status.Fail(methodIL.OwningMethod, opcode);
                            }


                            stack.Push(boxedResult);
                        }
                    }
                    break;

                    case ILOpcode.unbox_any:
                    {
                        TypeDesc type = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
                        StackEntry entry = stack.Pop();
                        if (entry.Value is ObjectInstance objInst
                            && objInst.TryUnboxAny(type, out Value unboxed))
                        {
                            stack.PushFromLocation(type, unboxed);
                        }
                        else
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                    }
                    break;

                    case ILOpcode.ldobj:
                    case ILOpcode.ldind_i1:
                    case ILOpcode.ldind_u1:
                    case ILOpcode.ldind_i2:
                    case ILOpcode.ldind_u2:
                    case ILOpcode.ldind_i4:
                    case ILOpcode.ldind_u4:
                    case ILOpcode.ldind_i8:
                    {
                        TypeDesc type = opcode switch
                        {
                            ILOpcode.ldind_i1 => context.GetWellKnownType(WellKnownType.SByte),
                            ILOpcode.ldind_u1 => context.GetWellKnownType(WellKnownType.Byte),
                            ILOpcode.ldind_i2 => context.GetWellKnownType(WellKnownType.Int16),
                            ILOpcode.ldind_u2 => context.GetWellKnownType(WellKnownType.UInt16),
                            ILOpcode.ldind_i4 => context.GetWellKnownType(WellKnownType.Int32),
                            ILOpcode.ldind_u4 => context.GetWellKnownType(WellKnownType.UInt32),
                            ILOpcode.ldind_i8 => context.GetWellKnownType(WellKnownType.Int64),
                            _ /* ldobj */ => (TypeDesc)methodIL.GetObject(reader.ReadILToken()),
                        };

                        StackEntry entry = stack.Pop();
                        if (entry.ValueKind != StackValueKind.ByRef && entry.ValueKind != StackValueKind.NativeInt)
                            ThrowHelper.ThrowInvalidProgramException();

                        if (entry.Value is ByRefValueBase byRefVal
                            && byRefVal.TryLoad(type, out Value dereferenced))
                        {
                            stack.PushFromLocation(type, dereferenced);
                        }
                        else
                        {
                            return Status.Fail(methodIL.OwningMethod, "Ldind from unsupported byref");
                        }
                    }
                    break;

                    case ILOpcode.stobj:
                    case ILOpcode.stind_i:
                    case ILOpcode.stind_i1:
                    case ILOpcode.stind_i2:
                    case ILOpcode.stind_i4:
                    case ILOpcode.stind_i8:
                    {
                        if (opcode == ILOpcode.stobj)
                        {
                            TypeDesc type = methodIL.GetObject(reader.ReadILToken()) as TypeDesc;
                            opcode = type.Category switch
                            {
                                TypeFlags.SByte or TypeFlags.Boolean or TypeFlags.Byte => ILOpcode.stind_i1,
                                TypeFlags.Int16 or TypeFlags.Char or TypeFlags.UInt16 => ILOpcode.stind_i2,
                                TypeFlags.Int32 or TypeFlags.UInt32 => ILOpcode.stind_i4,
                                TypeFlags.Int64 or TypeFlags.UInt64 => ILOpcode.stind_i8,
                                TypeFlags.IntPtr or TypeFlags.UIntPtr => ILOpcode.stind_i,
                                _ => ILOpcode.stobj,
                            };
                        }

                        Value val = opcode switch
                        {
                            ILOpcode.stind_i1 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.Byte)),
                            ILOpcode.stind_i2 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt16)),
                            ILOpcode.stind_i4 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt32)),
                            ILOpcode.stind_i8 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt64)),
                            ILOpcode.stind_i => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UIntPtr)),
                            _ => stack.Pop().Value
                        };

                        StackEntry location = stack.Pop();
                        if (location.ValueKind != StackValueKind.ByRef && location.ValueKind != StackValueKind.NativeInt)
                            ThrowHelper.ThrowInvalidProgramException();

                        if (location.Value is not ByRefValueBase destValue)
                            return Status.Fail(methodIL.OwningMethod, "Stind into usupported byref");

                        if (!destValue.TryStore(val))
                            return Status.Fail(methodIL.OwningMethod, "Byref doesn't support storing value");
                    }
                    break;

                    case ILOpcode.constrained:
                        constrainedType = methodIL.GetObject(reader.ReadILToken()) as TypeDesc;
                        goto again;

                    case ILOpcode.unaligned:
                        reader.ReadILByte();
                        break;

                    case ILOpcode.initblk:
                    {
                        StackEntry size = stack.Pop();
                        StackEntry value = stack.Pop();
                        StackEntry addr = stack.Pop();

                        if (size.ValueKind != StackValueKind.Int32
                            || value.ValueKind != StackValueKind.Int32
                            || addr.ValueKind != StackValueKind.ByRef)
                            return Status.Fail(methodIL.OwningMethod, opcode);

                        uint sizeBytes = (uint)size.Value.AsInt32();

                        if (addr.Value is not ByRefValue addressValue)
                            return Status.Fail(methodIL.OwningMethod, "initblk of unsupported byref");

                        if (sizeBytes > addressValue.PointedToBytes.Length - addressValue.PointedToOffset
                            || sizeBytes > int.MaxValue /* paranoid check that cast to int is legit */)
                            return Status.Fail(methodIL.OwningMethod, opcode);

                        Array.Fill(addressValue.PointedToBytes, (byte)value.Value.AsInt32(), addressValue.PointedToOffset, (int)sizeBytes);
                    }
                    break;

                    default:
                        return Status.Fail(methodIL.OwningMethod, opcode);
                }

            }

            return Status.Fail(methodIL.OwningMethod, "Control fell through");
        }

        private static bool TryGetSpanElementType(TypeDesc type, bool isReadOnlySpan, out MetadataType elementType)
        {
            if (type.IsByRefLike
                && type is MetadataType maybeSpan
                && maybeSpan.Module == type.Context.SystemModule
                && ((isReadOnlySpan && maybeSpan.Name.SequenceEqual("ReadOnlySpan`1"u8)) || (!isReadOnlySpan && maybeSpan.Name.SequenceEqual("Span`1"u8)))
                && maybeSpan.Namespace.SequenceEqual("System"u8)
                && maybeSpan.Instantiation[0] is MetadataType readOnlySpanElementType)
            {
                elementType = readOnlySpanElementType;
                return true;
            }
            elementType = null;
            return false;
        }

        private static BaseValueTypeValue NewUninitializedLocationValue(TypeDesc locationType, FieldDesc fieldThatOwnsMemory)
        {
            if (locationType.IsGCPointer || locationType.IsByRef)
            {
                return null;
            }
            else if (TryGetSpanElementType(locationType, isReadOnlySpan: true, out MetadataType readOnlySpanElementType))
            {
                return new SpanValue(readOnlySpanElementType, Array.Empty<byte>(), 0, 0);
            }
            else if (TryGetSpanElementType(locationType, isReadOnlySpan: false, out MetadataType spanElementType))
            {
                return new SpanValue(spanElementType, Array.Empty<byte>(), 0, 0);
            }
            else if (VTableLikeStructValue.IsCompatible(locationType))
            {
                return new VTableLikeStructValue((MetadataType)locationType, fieldThatOwnsMemory);
            }
            else if (ComInterfaceEntryArrayValue.IsCompatible(locationType, out TypeDesc comInterfaceEntryType))
            {
                return new ComInterfaceEntryArrayValue(locationType, comInterfaceEntryType);
            }
            else
            {
                Debug.Assert(locationType.IsValueType || locationType.IsPointer || locationType.IsFunctionPointer);
                return new ValueTypeValue(locationType);
            }
        }

        private bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters, out Value retVal)
        {
            retVal = default;

            switch (method.GetName())
            {
                case "InitializeArray":
                    if (method.OwningType is MetadataType mdType
                        && mdType.Name.SequenceEqual("RuntimeHelpers"u8) && mdType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8)
                        && mdType.Module == mdType.Context.SystemModule
                        && parameters[0] is ArrayInstance array
                        && parameters[1] is RuntimeFieldHandleValue fieldHandle
                        && fieldHandle.Field.IsStatic && fieldHandle.Field.HasRva
                        && fieldHandle.Field is Internal.TypeSystem.Ecma.EcmaField ecmaField)
                    {
                        byte[] rvaData = Internal.TypeSystem.Ecma.EcmaFieldExtensions.GetFieldRvaData(ecmaField);
                        return array.TryInitialize(rvaData);
                    }
                    return false;
                case "CreateSpan":
                    if (method.OwningType is MetadataType createSpanType
                        && createSpanType.Name.SequenceEqual("RuntimeHelpers"u8) && createSpanType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8)
                        && createSpanType.Module == createSpanType.Context.SystemModule
                        && parameters[0] is RuntimeFieldHandleValue createSpanFieldHandle
                        && createSpanFieldHandle.Field.IsStatic && createSpanFieldHandle.Field.HasRva
                        && createSpanFieldHandle.Field is Internal.TypeSystem.Ecma.EcmaField createSpanEcmaField
                        && method.Instantiation[0].IsValueType)
                    {
                        var elementType = (MetadataType)method.Instantiation[0];
                        int elementSize = elementType.InstanceFieldSize.AsInt;
                        byte[] rvaData = Internal.TypeSystem.Ecma.EcmaFieldExtensions.GetFieldRvaData(createSpanEcmaField);
                        if (rvaData.Length % elementSize != 0)
                            return false;
                        retVal = new SpanValue(elementType, rvaData, 0, rvaData.Length);
                        return true;
                    }
                    return false;
                case "GetTypeFromHandle" when IsSystemType(method.OwningType)
                        && parameters[0] is RuntimeTypeHandleValue typeHandle:
                {
                    if (!_internedTypes.TryGetValue(typeHandle.Type, out RuntimeTypeValue runtimeType))
                    {
                        _internedTypes.Add(typeHandle.Type, runtimeType = new RuntimeTypeValue(typeHandle.Type));
                    }
                    retVal = runtimeType;
                    return true;
                }
                case "get_IsValueType" when IsSystemType(method.OwningType)
                        && parameters[0] is RuntimeTypeValue typeToCheckForValueType:
                {
                    retVal = ValueTypeValue.FromSByte(typeToCheckForValueType.TypeRepresented.IsValueType ? (sbyte)1 : (sbyte)0);
                    return true;
                }
                case "op_Equality" when IsSystemType(method.OwningType)
                        && (parameters[0] is RuntimeTypeValue || parameters[1] is RuntimeTypeValue):
                {
                    retVal = ValueTypeValue.FromSByte(parameters[0] == parameters[1] ? (sbyte)1 : (sbyte)0);
                    return true;
                }
                case "IsReferenceOrContainsReferences" when method.Instantiation.Length == 1
                        && method.OwningType is MetadataType isReferenceOrContainsReferencesType
                        && isReferenceOrContainsReferencesType.Name.SequenceEqual("RuntimeHelpers"u8) && isReferenceOrContainsReferencesType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8)
                        && isReferenceOrContainsReferencesType.Module == method.Context.SystemModule:
                {
                    bool result = method.Instantiation[0].IsGCPointer || (method.Instantiation[0] is DefType defType && defType.ContainsGCPointers);
                    retVal = ValueTypeValue.FromSByte(result ? (sbyte)1 : (sbyte)0);
                    return true;
                }
                case "GetArrayDataReference" when method.Instantiation.Length == 1
                        && method.OwningType is MetadataType getArrayDataReferenceType
                        && getArrayDataReferenceType.Name.SequenceEqual("MemoryMarshal"u8) && getArrayDataReferenceType.Namespace.SequenceEqual("System.Runtime.InteropServices"u8)
                        && getArrayDataReferenceType.Module == method.Context.SystemModule
                        && parameters[0] is ArrayInstance arrayData
                        && ((ArrayType)arrayData.Type).ElementType == method.Instantiation[0]:
                {
                    retVal = arrayData.GetArrayData();
                    return true;
                }
            }

            static bool IsSystemType(TypeDesc type)
                => type is MetadataType typeType
                        && typeType.Name.SequenceEqual("Type"u8) && typeType.Namespace.SequenceEqual("System"u8)
                        && typeType.Module == typeType.Context.SystemModule;

            return false;
        }

        private static TypeDesc GetArgType(MethodDesc method, int index)
        {
            var sig = method.Signature;
            int offset = 0;
            if (!sig.IsStatic)
            {
                if (index == 0)
                    return method.OwningType.IsValueType ? method.OwningType.MakeByRefType() : method.OwningType;
                offset = 1;
            }

            if ((uint)(index - offset) >= (uint)sig.Length)
                ThrowHelper.ThrowInvalidProgramException();

            return sig[index - offset];
        }

        private sealed class Stack : Stack<StackEntry>
        {
            private readonly TargetDetails _target;

            public Stack(int capacity, TargetDetails target) : base(capacity)
            {
                _target = target;
            }

            public new StackEntry Pop()
            {
                if (Count < 1)
                {
                    ThrowHelper.ThrowInvalidProgramException();
                }

                return base.Pop();
            }

            public bool TryPopIntValue(out int value)
            {
                if (Count == 0)
                {
                    value = 0;
                    return false;
                }

                StackEntry entry = Pop();
                if (entry.ValueKind == StackValueKind.Int32)
                {
                    value = entry.Value.AsInt32();
                    return true;
                }
                else if (entry.ValueKind == StackValueKind.NativeInt)
                {
                    if (_target.PointerSize == 8)
                    {
                        long longValue = entry.Value.AsInt64();
                        if (longValue < int.MinValue || longValue > int.MaxValue)
                        {
                            value = 0;
                            return false;
                        }
                        value = (int)longValue;
                        return true;
                    }

                    value = entry.Value.AsInt32();
                    return true;
                }

                value = 0;
                return false;
            }

            public void Push(StackValueKind kind, Value val)
            {
                Push(new StackEntry(kind, val));
            }

            public void Push(ReferenceTypeValue value)
            {
                Push(StackValueKind.ObjRef, value);
            }

            public void PushFromLocation(TypeDesc locationType, Value value)
            {
                switch (locationType.UnderlyingType.Category)
                {
                    case TypeFlags.Boolean:
                    case TypeFlags.Byte:
                        Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)value.AsSByte())); break;
                    case TypeFlags.Char:
                    case TypeFlags.UInt16:
                        Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)value.AsInt16())); break;
                    case TypeFlags.SByte:
                        Push(StackValueKind.Int32, ValueTypeValue.FromInt32(value.AsSByte())); break;
                    case TypeFlags.Int16:
                        Push(StackValueKind.Int32, ValueTypeValue.FromInt32(value.AsInt16())); break;
                    case TypeFlags.Int32:
                    case TypeFlags.UInt32:
                        Push(StackValueKind.Int32, value.Clone()); break;
                    case TypeFlags.Int64:
                    case TypeFlags.UInt64:
                        Push(StackValueKind.Int64, value.Clone()); break;
                    case TypeFlags.IntPtr:
                    case TypeFlags.UIntPtr:
                    case TypeFlags.Pointer:
                    case TypeFlags.FunctionPointer:
                        Push(StackValueKind.NativeInt, value.Clone()); break;
                    case TypeFlags.Single:
                        Push(StackValueKind.Float, ValueTypeValue.FromDouble(value.AsSingle())); break;
                    case TypeFlags.Double:
                        Push(StackValueKind.Float, value.Clone()); break;
                    case TypeFlags.ValueType:
                    case TypeFlags.Nullable:
                        Push(StackValueKind.ValueType, value.Clone()); break;
                    case TypeFlags.Class:
                    case TypeFlags.Interface:
                    case TypeFlags.Array:
                    case TypeFlags.SzArray:
                        Push(StackValueKind.ObjRef, value); break;
                    case TypeFlags.ByRef:
                        Push(StackValueKind.ByRef, value); break;
                    default:
                        throw new NotImplementedException();
                }
            }

            public Value PopIntoLocation(TypeDesc locationType)
            {
                if (Count == 0)
                {
                    ThrowHelper.ThrowInvalidProgramException();
                }

                locationType = locationType.UnderlyingType;

                StackEntry popped = Pop();

                switch (popped.ValueKind)
                {
                    case StackValueKind.Int64:
                        if (!locationType.IsWellKnownType(WellKnownType.Int64)
                            && !locationType.IsWellKnownType(WellKnownType.UInt64))
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        return popped.Value;

                    case StackValueKind.Int32:
                        if (!locationType.IsWellKnownType(WellKnownType.Int32)
                            && !locationType.IsWellKnownType(WellKnownType.UInt32))
                        {
                            int value = popped.Value.AsInt32();
                            switch (locationType.Category)
                            {
                                case TypeFlags.SByte:
                                case TypeFlags.Byte:
                                case TypeFlags.Boolean:
                                    return ValueTypeValue.FromSByte((sbyte)value);
                                case TypeFlags.Int16:
                                case TypeFlags.UInt16:
                                case TypeFlags.Char:
                                    return ValueTypeValue.FromInt16((short)value);
                                // case TypeFlags.IntPtr: sign extend
                                // case TypeFlags.UIntPtr: zero extend
                            }
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        return popped.Value;

                    case StackValueKind.NativeInt:
                        // True byref that we converted to nint at some point.
                        if (locationType.IsByRef && popped.Value is ByRefValueBase)
                            return popped.Value;

                        // If it's none of the natural pointer types, we might need to truncate.
                        if (!locationType.IsPointer
                            && !locationType.IsFunctionPointer
                            && !locationType.IsWellKnownType(WellKnownType.IntPtr)
                            && !locationType.IsWellKnownType(WellKnownType.UIntPtr))
                        {
                            long value = _target.PointerSize == 8 ? popped.Value.AsInt64() : popped.Value.AsInt32();
                            switch (locationType.Category)
                            {
                                case TypeFlags.SByte:
                                case TypeFlags.Byte:
                                case TypeFlags.Boolean:
                                    return ValueTypeValue.FromSByte((sbyte)value);
                                case TypeFlags.Int16:
                                case TypeFlags.UInt16:
                                case TypeFlags.Char:
                                    return ValueTypeValue.FromInt16((short)value);
                                case TypeFlags.Int32:
                                case TypeFlags.UInt32:
                                    return ValueTypeValue.FromInt32((int)value);
                                // case TypeFlags.ByRef: start GC tracking
                            }

                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        return popped.Value;

                    case StackValueKind.Float:
                        if (locationType.IsWellKnownType(WellKnownType.Double))
                        {
                            return popped.Value;
                        }
                        else if (!locationType.IsWellKnownType(WellKnownType.Single))
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        return ValueTypeValue.FromSingle((float)popped.Value.AsDouble());

                    case StackValueKind.ByRef:
                        if (!locationType.IsByRef
                            && locationType.Category is not TypeFlags.IntPtr and not TypeFlags.UIntPtr and not TypeFlags.Pointer and not TypeFlags.FunctionPointer)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        return popped.Value;

                    case StackValueKind.ObjRef:
                        if (!locationType.IsGCPointer)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        return popped.Value;

                    case StackValueKind.ValueType:
                        if (!locationType.IsValueType
                            || ((BaseValueTypeValue)popped.Value).Size != ((DefType)locationType).InstanceFieldSize.AsInt)
                        {
                            ThrowHelper.ThrowInvalidProgramException();
                        }
                        return popped.Value;

                    default:
                        throw new NotImplementedException();
                }
            }
        }

        /// <summary>
        /// Represents a field value that can be serialized into a preinitialized blob.
        /// </summary>
        public interface ISerializableValue
        {
            void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory);

            bool GetRawData(NodeFactory factory, out object data);
        }

        /// <summary>
        /// Represents a frozen object whose contents can be serialized into the executable.
        /// </summary>
        public interface ISerializableReference : ISerializableValue
        {
            TypeDesc Type { get; }
            void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory);
            bool HasConditionalDependencies { get; }
            void GetConditionalDependencies(ref CombinedDependencyList dependencies, NodeFactory factory);
            bool IsKnownImmutable { get; }
            int ArrayLength { get; }
        }

        /// <summary>
        /// Represents a value with instance fields. This is either a reference type, or a byref to
        /// a valuetype.
        /// </summary>
        private interface IHasInstanceFields
        {
            bool TrySetField(FieldDesc field, Value value);
            Value GetField(FieldDesc field);
            ByRefValueBase GetFieldAddress(FieldDesc field);
        }

        /// <summary>
        /// Represents a special value that is used internally to model known constructs, but cannot
        /// be represented externally and that's why we don't allow field stores with it.
        /// </summary>
        private interface IInternalModelingOnlyValue
        {
        }

        private interface INativeIntConvertibleValue;

        /// <summary>
        /// Represents a value that can be assigned into.
        /// </summary>
        private interface IAssignableValue
        {
            bool TryAssign(Value value);
        }

        private abstract class Value : ISerializableValue
        {
            public abstract bool TryCompareEquality(Value value, out bool result);

            public static bool TryCompareEquality(Value value1, Value value2, out bool result)
            {
                if (value1 == value2)
                {
                    result = true;
                    return true;
                }
                if (value1 == null || value2 == null)
                {
                    result = false;
                    return true;
                }
                return value1.TryCompareEquality(value2, out result);
            }

            public virtual bool TryCreateByRef(out Value value)
            {
                value = null;
                return false;
            }

            public abstract void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory);

            public abstract bool GetRawData(NodeFactory factory, out object data);

            private static T ThrowInvalidProgram<T>()
            {
                ThrowHelper.ThrowInvalidProgramException();
                return default;
            }

            public virtual sbyte AsSByte() => ThrowInvalidProgram<sbyte>();
            public virtual short AsInt16() => ThrowInvalidProgram<short>();
            public virtual int AsInt32() => ThrowInvalidProgram<int>();
            public virtual long AsInt64() => ThrowInvalidProgram<long>();
            public virtual float AsSingle() => ThrowInvalidProgram<float>();
            public virtual double AsDouble() => ThrowInvalidProgram<double>();
            public virtual Value Clone() => ThrowInvalidProgram<Value>();
        }

        private abstract class BaseValueTypeValue : Value
        {
            public abstract int Size { get; }
        }

        // Also represents pointers and function pointer.
        private sealed class ValueTypeValue : BaseValueTypeValue, IAssignableValue
        {
            public readonly byte[] InstanceBytes;

            public override int Size => InstanceBytes.Length;

            public ValueTypeValue(TypeDesc type)
            {
                Debug.Assert(type.IsValueType || type.IsPointer || type.IsFunctionPointer);
                InstanceBytes = new byte[type.GetElementSize().AsInt];
            }

            public ValueTypeValue(byte[] bytes)
            {
                InstanceBytes = bytes;
            }

            public override Value Clone()
            {
                return new ValueTypeValue((byte[])InstanceBytes.Clone());
            }

            public override bool TryCreateByRef(out Value value)
            {
                value = new ByRefValue(InstanceBytes, 0);
                return true;
            }

            bool IAssignableValue.TryAssign(Value value)
            {
                if ((!(value is BaseValueTypeValue other) || other.Size != Size)
                    && value is not INativeIntConvertibleValue)
                {
                    ThrowHelper.ThrowInvalidProgramException();
                }

                if (!(value is ValueTypeValue vtvalue))
                {
                    return false;
                }

                Array.Copy(vtvalue.InstanceBytes, InstanceBytes, InstanceBytes.Length);
                return true;
            }

            public override bool TryCompareEquality(Value value, out bool result)
            {
                if (!(value is ValueTypeValue vtvalue)
                    || vtvalue.InstanceBytes.Length != InstanceBytes.Length)
                {
                    result = false;
                    return false;
                }

                for (int i = 0; i < InstanceBytes.Length; i++)
                {
                    if (InstanceBytes[i] != ((ValueTypeValue)value).InstanceBytes[i])
                    {
                        result = false;
                        return true;
                    }
                }

                result = true;
                return true;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                builder.EmitBytes(InstanceBytes);
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = InstanceBytes;
                return true;
            }

            private byte[] AsExactByteCount(int size)
            {
                if (InstanceBytes.Length != size)
                {
                    ThrowHelper.ThrowInvalidProgramException();
                }
                return InstanceBytes;
            }

            public override sbyte AsSByte() => (sbyte)AsExactByteCount(1)[0];
            public override short AsInt16() => BitConverter.ToInt16(AsExactByteCount(2), 0);
            public override int AsInt32() => BitConverter.ToInt32(AsExactByteCount(4), 0);
            public override long AsInt64() => BitConverter.ToInt64(AsExactByteCount(8), 0);
            public override float AsSingle() => BitConverter.ToSingle(AsExactByteCount(4), 0);
            public override double AsDouble() => BitConverter.ToDouble(AsExactByteCount(8), 0);
            public static ValueTypeValue FromSByte(sbyte value) => new ValueTypeValue(new byte[1] { (byte)value });
            public static ValueTypeValue FromInt16(short value) => new ValueTypeValue(BitConverter.GetBytes(value));
            public static ValueTypeValue FromInt32(int value) => new ValueTypeValue(BitConverter.GetBytes(value));
            public static ValueTypeValue FromInt64(long value) => new ValueTypeValue(BitConverter.GetBytes(value));
            public static ValueTypeValue FromSingle(float value) => new ValueTypeValue(BitConverter.GetBytes(value));
            public static ValueTypeValue FromDouble(double value) => new ValueTypeValue(BitConverter.GetBytes(value));
        }

        private sealed class ComInterfaceEntryArrayValue : BaseValueTypeValue
        {
            private readonly FieldDesc[] _targetFields;
            private readonly byte[][] _guidBytes;
            private readonly MetadataType _entryType;

            public override int Size => _entryType.InstanceFieldSize.AsInt * _targetFields.Length;

            public ComInterfaceEntryArrayValue(TypeDesc type, TypeDesc entryType)
            {
                Debug.Assert(IsCompatible(type, out _));
                Debug.Assert(IsComInterfaceEntryType(entryType));
                Debug.Assert(((MetadataType)type).InstanceFieldSize.AsInt % ((MetadataType)entryType).InstanceFieldSize.AsInt == 0);

                _entryType = (MetadataType)entryType;

                int numFields = ((MetadataType)type).InstanceFieldSize.AsInt / _entryType.InstanceFieldSize.AsInt;
                _targetFields = new FieldDesc[numFields];
                _guidBytes = new byte[numFields][];
                for (int i = 0; i < numFields; i++)
                    _guidBytes[i] = new byte[16];
            }

            private static bool IsComInterfaceEntryType(TypeDesc type)
                => type is MetadataType mdType
                    && mdType.Name.SequenceEqual("ComInterfaceEntry"u8)
                    && mdType.ContainingType is MetadataType comWrappersType
                    && comWrappersType.Name.SequenceEqual("ComWrappers"u8) && comWrappersType.Namespace.SequenceEqual("System.Runtime.InteropServices"u8)
                    && comWrappersType.Module == comWrappersType.Context.SystemModule;

            public static bool IsCompatible(TypeDesc type, out TypeDesc entryType)
            {
                entryType = null;

                if (!type.IsValueType
                    || type.HasInstantiation
                    || type is not MetadataType mdType
                    || !mdType.IsSequentialLayout
                    || mdType.GetClassLayout() is not { PackingSize: 0, Size: 0 }
                    || mdType.IsInlineArray)
                {
                    return false;
                }

                foreach (FieldDesc field in type.GetFields())
                {
                    if (field.IsStatic)
                        continue;

                    entryType = field.FieldType;

                    if (!IsComInterfaceEntryType(entryType))
                        return false;
                }

                return entryType != null;
            }

            public override bool TryCompareEquality(Value value, out bool result)
            {
                result = false;
                return false;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                for (int i = 0; i < _targetFields.Length; i++)
                {
                    Debug.Assert(_entryType.GetField("IID"u8).Offset.AsInt == 0);
                    builder.EmitBytes(_guidBytes[i]);

                    Debug.Assert(_entryType.GetField("Vtable"u8).Offset.AsInt == _guidBytes[i].Length);
                    if (_targetFields[i] is not FieldDesc targetField)
                    {
                        builder.EmitZeroPointer();
                    }
                    else
                    {
                        Debug.Assert(targetField.IsStatic && !targetField.HasGCStaticBase && !targetField.IsThreadStatic && !targetField.HasRva);
                        ISymbolNode nonGcStaticBase = factory.TypeNonGCStaticsSymbol(targetField.OwningType);
                        builder.EmitPointerReloc(nonGcStaticBase, targetField.Offset.AsInt);
                    }
                }
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = null;
                return false;
            }

            public override bool TryCreateByRef(out Value value)
            {
                value = new ComInterfaceEntrySlotReference(this, 0);
                return true;
            }

            private sealed class ComInterfaceEntrySlotReference : ByRefValueBase, IHasInstanceFields
            {
                private readonly ComInterfaceEntryArrayValue _parent;
                private readonly int _index;

                public ComInterfaceEntrySlotReference(ComInterfaceEntryArrayValue parent, int index)
                    => (_parent, _index) = (parent, index);

                public override bool TryCompareEquality(Value value, out bool result)
                {
                    result = false;
                    return false;
                }

                public override bool GetRawData(NodeFactory factory, out object data)
                {
                    data = null;
                    return false;
                }

                public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
                {
                    throw new NotSupportedException();
                }

                bool IHasInstanceFields.TrySetField(FieldDesc field, Value value)
                {
                    if (field.OwningType != _parent._entryType)
                        return false;

                    if (field.Name.SequenceEqual("IID"u8)
                        && value is ValueTypeValue guidValue
                        && guidValue.Size == _parent._guidBytes[_index].Length)
                    {
                        Array.Copy(guidValue.InstanceBytes, _parent._guidBytes[_index], _parent._guidBytes[_index].Length);
                        return true;
                    }
                    else if (field.Name.SequenceEqual("Vtable"u8)
                        && value is ByRefValueBase byrefValue
                        && byrefValue.BackingField != null)
                    {
                        _parent._targetFields[_index] = byrefValue.BackingField;
                        return true;
                    }

                    return false;
                }

                Value IHasInstanceFields.GetField(FieldDesc field)
                {
                    // Not actually invalid, but we don't need this.
                    ThrowHelper.ThrowInvalidProgramException();
                    return null; // unreached
                }

                ByRefValueBase IHasInstanceFields.GetFieldAddress(FieldDesc field)
                {
                    if (field.OwningType == _parent._entryType)
                    {
                        // Get address of IID or Vtable field on ComInterfaceEntry this ref points to.
                        // Not actually invalid, but we don't need this.
                        ThrowHelper.ThrowInvalidProgramException();
                    }
                    else if (field.FieldType == _parent._entryType
                        && _index == 0
                        && field.Offset.AsInt % _parent._entryType.InstanceFieldSize.AsInt == 0
                        && field.Offset.AsInt < _parent._entryType.InstanceFieldSize.AsInt * _parent._targetFields.Length)
                    {
                        // Get address of a field within an array of ComInterfaceEntry.
                        int index = field.Offset.AsInt / _parent._entryType.InstanceFieldSize.AsInt;
                        return new ComInterfaceEntrySlotReference(_parent, index);
                    }

                    ThrowHelper.ThrowInvalidProgramException();
                    return null; // unreached
                }
            }
        }

        private sealed class VTableLikeStructValue : BaseValueTypeValue, IAssignableValue
        {
            private readonly MetadataType _type;
            private readonly MethodDesc[] _methods;
            private readonly FieldDesc _fieldThatOwnsMemory;

            public VTableLikeStructValue(MetadataType type, FieldDesc fieldThatOwnsMemory)
                : this(type, new MethodDesc[GetFieldCount(type)], fieldThatOwnsMemory)
            {
            }

            private VTableLikeStructValue(MetadataType type, MethodDesc[] methods, FieldDesc fieldThatOwnsMemory)
                => (_type, _methods, _fieldThatOwnsMemory) = (type, methods, fieldThatOwnsMemory);

            private static int GetFieldCount(MetadataType type)
            {
                Debug.Assert(IsCompatible(type));
                Debug.Assert(type.InstanceFieldSize.AsInt % type.Context.Target.PointerSize == 0);
                return type.InstanceFieldSize.AsInt / type.Context.Target.PointerSize;
            }

            public override int Size => _methods.Length * _type.Context.Target.PointerSize;

            public static bool IsCompatible(TypeDesc type)
            {
                if (!type.IsValueType
                    || type.HasInstantiation
                    || type is not MetadataType mdType
                    || !mdType.IsSequentialLayout
                    || mdType.GetClassLayout() is not { PackingSize: 0, Size: 0 }
                    || mdType.IsInlineArray)
                {
                    return false;
                }

                bool hasFields = false;
                foreach (FieldDesc field in type.GetFields())
                {
                    if (field.IsStatic)
                        continue;

                    hasFields = true;

                    if (field.FieldType.Category != TypeFlags.FunctionPointer)
                        return false;
                }

                return hasFields;
            }

            public override bool TryCompareEquality(Value value, out bool result)
            {
                result = false;
                return false;
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = null;
                return false;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                foreach (MethodDesc method in _methods)
                {
                    if (method is null)
                        builder.EmitZeroPointer();
                    else
                        builder.EmitPointerReloc(factory.ExactCallableAddressTakenAddress(method, isUnboxingStub: false));
                }
            }

            public override bool TryCreateByRef(out Value value)
            {
                value = new VTableLikeSlotReferenceValue(this, index: 0);
                return true;
            }

            public override Value Clone()
            {
                return new VTableLikeStructValue(_type, (MethodDesc[])_methods.Clone(), fieldThatOwnsMemory: null);
            }

            bool IAssignableValue.TryAssign(Value value)
            {
                if (value is not VTableLikeStructValue other)
                    return false;

                if (other.Size > Size)
                    return false;

                Array.Copy(other._methods, _methods, other._methods.Length);
                return true;
            }

            private sealed class VTableLikeSlotReferenceValue : ByRefValueBase, IHasInstanceFields
            {
                private readonly VTableLikeStructValue _parent;
                private readonly int _index;

                public override FieldDesc BackingField => _index == 0 ? _parent._fieldThatOwnsMemory : null;

                public VTableLikeSlotReferenceValue(VTableLikeStructValue parent, int index)
                    => (_parent, _index) = (parent, index);

                public override bool TryCompareEquality(Value value, out bool result)
                {
                    result = false;
                    return false;
                }

                public override bool GetRawData(NodeFactory factory, out object data)
                {
                    data = null;
                    return false;
                }

                public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
                {
                    throw new NotSupportedException();
                }

                public override bool TryStore(Value value)
                {
                    if (value is MethodPointerValue methodPointer)
                    {
                        _parent._methods[_index] = methodPointer.PointedToMethod;
                        return true;
                    }
                    else if (value is VTableLikeStructValue otherStruct
                        && _parent._methods.Length - _index >= otherStruct._methods.Length)
                    {
                        Array.Copy(otherStruct._methods, 0, _parent._methods, _index, otherStruct._methods.Length);
                        return true;
                    }

                    return false;
                }

                public override bool TryLoad(TypeDesc type, out Value value)
                {
                    if (!VTableLikeStructValue.IsCompatible(type)
                        || type is not MetadataType mdType
                        || mdType.InstanceFieldSize.AsInt > (_parent._methods.Length - _index) * _parent._type.Context.Target.PointerSize)
                    {
                        value = null;
                        return false;
                    }

                    MethodDesc[] slots = new MethodDesc[GetFieldCount(mdType)];
                    Array.Copy(_parent._methods, _index, slots, 0, slots.Length);
                    value = new VTableLikeStructValue(mdType, slots, fieldThatOwnsMemory: null);
                    return true;
                }

                public override Value Clone() => this; // The reference is immutable

                private int GetFieldIndex(FieldDesc field)
                {
                    // Not actually invalid program, just difficult to model
                    if (!VTableLikeStructValue.IsCompatible(field.OwningType))
                        ThrowHelper.ThrowInvalidProgramException();

                    Debug.Assert(field.Offset.AsInt % _parent._type.Context.Target.PointerSize == 0 && field.FieldType.IsFunctionPointer);

                    int index = (field.Offset.AsInt / _parent._type.Context.Target.PointerSize) + _index;
                    if (index >= _parent._methods.Length)
                        ThrowHelper.ThrowInvalidProgramException();

                    return index;
                }

                bool IHasInstanceFields.TrySetField(FieldDesc field, Value value)
                {
                    if (value is not MethodPointerValue methodPtr)
                        return false;

                    _parent._methods[GetFieldIndex(field)] = methodPtr.PointedToMethod;
                    return true;
                }

                Value IHasInstanceFields.GetField(FieldDesc field)
                {
                    MethodDesc method = _parent._methods[GetFieldIndex(field)];

                    if (method is not null)
                        return new MethodPointerValue(method);
                    else
                        return _parent._type.Context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64(0) : ValueTypeValue.FromInt32(0);
                }

                ByRefValueBase IHasInstanceFields.GetFieldAddress(FieldDesc field)
                {
                    return new VTableLikeSlotReferenceValue(_parent, GetFieldIndex(field));
                }

                public override bool TryInitialize(int size)
                {
                    if (size % _parent._type.Context.Target.PointerSize != 0)
                        return false;

                    int numSlots = size / _parent._type.Context.Target.PointerSize;
                    if (_index + numSlots > _parent._methods.Length)
                        return false;

                    for (int i = _index; i < numSlots; i++)
                        _parent._methods[i] = null;

                    return true;
                }
            }
        }

        private sealed class RuntimeFieldHandleValue : BaseValueTypeValue, IInternalModelingOnlyValue
        {
            public FieldDesc Field { get; private set; }

            public RuntimeFieldHandleValue(FieldDesc field)
            {
                Field = field;
            }

            public override int Size => Field.Context.Target.PointerSize;

            public override bool TryCompareEquality(Value value, out bool result)
            {
                if (!(value is RuntimeFieldHandleValue))
                {
                    result = false;
                    return false;
                }

                result = Field == ((RuntimeFieldHandleValue)value).Field;
                return true;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                throw new NotSupportedException();
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = null;
                return false;
            }
        }

        private sealed class RuntimeTypeHandleValue : BaseValueTypeValue, IInternalModelingOnlyValue
        {
            public TypeDesc Type { get; }

            public RuntimeTypeHandleValue(TypeDesc type)
            {
                Type = type;
            }

            public override int Size => Type.Context.Target.PointerSize;

            public override bool TryCompareEquality(Value value, out bool result)
            {
                if (!(value is RuntimeTypeHandleValue))
                {
                    result = false;
                    return false;
                }

                result = Type == ((RuntimeTypeHandleValue)value).Type;
                return true;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                throw new NotSupportedException();
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = null;
                return false;
            }
        }

        private sealed class RuntimeTypeValue : ReferenceTypeValue
        {
            public TypeDesc TypeRepresented { get; }

            public RuntimeTypeValue(TypeDesc type)
                : base(type.Context.SystemModule.GetKnownType("System"u8, "RuntimeType"u8))
            {
                TypeRepresented = type;
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = factory.SerializedMetadataRuntimeTypeObject(TypeRepresented);
                return true;
            }

            public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext)
            {
                if (!preinitContext._internedTypes.TryGetValue(TypeRepresented, out RuntimeTypeValue result))
                {
                    preinitContext._internedTypes.Add(TypeRepresented, result = new RuntimeTypeValue(TypeRepresented));
                }
                return result;
            }
            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                builder.EmitPointerReloc(factory.SerializedMetadataRuntimeTypeObject(TypeRepresented));
            }
        }

        private sealed class SpanValue : BaseValueTypeValue, IInternalModelingOnlyValue
        {
            private readonly MetadataType _elementType;
            private byte[] _bytes;
            private int _index;
            private int _length;

            public SpanValue(MetadataType elementType, byte[] bytes, int index, int length)
            {
                Debug.Assert(index <= bytes.Length);
                Debug.Assert(length <= bytes.Length - index);
                _elementType = elementType;
                _bytes = bytes;
                _index = index;
                _length = length;
            }

            public override int Size => 2 * _elementType.Context.Target.PointerSize;

            public override bool TryCompareEquality(Value value, out bool result)
            {
                result = false;
                return false;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                throw new NotSupportedException();
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = null;
                return false;
            }

            public override Value Clone()
            {
                return new SpanValue(_elementType, _bytes, _index, _length);
            }

            public override bool TryCreateByRef(out Value value)
            {
                value = new SpanReferenceValue(this);
                return true;
            }

            private sealed class SpanReferenceValue : ByRefValueBase, IHasInstanceFields
            {
                private readonly SpanValue _value;

                public SpanReferenceValue(SpanValue value)
                {
                    _value = value;
                }

                public override bool TryCompareEquality(Value value, out bool result)
                {
                    result = false;
                    return false;
                }

                public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
                {
                    throw new NotSupportedException();
                }

                public override bool GetRawData(NodeFactory factory, out object data)
                {
                    data = null;
                    return false;
                }

                public bool TrySetField(FieldDesc field, Value value)
                {
                    MetadataType elementType;
                    if (!TryGetSpanElementType(field.OwningType, isReadOnlySpan: true, out elementType)
                        && !TryGetSpanElementType(field.OwningType, isReadOnlySpan: false, out elementType))
                        return false;

                    if (elementType != _value._elementType)
                        return false;

                    if (field.Name.SequenceEqual("_length"u8))
                    {
                        _value._length = value.AsInt32() * _value._elementType.InstanceFieldSize.AsInt;
                        return true;
                    }

                    if (value is ByRefValue byref)
                    {
                        Debug.Assert(field.Name.SequenceEqual("_reference"u8));
                        _value._bytes = byref.PointedToBytes;
                        _value._index = byref.PointedToOffset;
                        return true;
                    }

                    return false;
                }

                public Value GetField(FieldDesc field)
                {
                    MetadataType elementType;
                    if (!TryGetSpanElementType(field.OwningType, isReadOnlySpan: true, out elementType)
                        && !TryGetSpanElementType(field.OwningType, isReadOnlySpan: false, out elementType))
                        ThrowHelper.ThrowInvalidProgramException();

                    if (elementType != _value._elementType)
                        ThrowHelper.ThrowInvalidProgramException();

                    if (field.Name.SequenceEqual("_length"u8))
                        return ValueTypeValue.FromInt32(_value._length / elementType.InstanceFieldSize.AsInt);

                    Debug.Assert(field.Name.SequenceEqual("_reference"u8));
                    return new ByRefValue(_value._bytes, _value._index);
                }

                public ByRefValueBase GetFieldAddress(FieldDesc field)
                {
                    ThrowHelper.ThrowInvalidProgramException();
                    return null; // unreached
                }
            }
        }

        private sealed class MethodPointerValue : BaseValueTypeValue, IInternalModelingOnlyValue
        {
            public MethodDesc PointedToMethod { get; }

            public MethodPointerValue(MethodDesc pointedToMethod)
            {
                PointedToMethod = pointedToMethod;
            }

            public override int Size => PointedToMethod.Context.Target.PointerSize;

            public override bool TryCompareEquality(Value value, out bool result)
            {
                if (!(value is MethodPointerValue))
                {
                    result = false;
                    return false;
                }

                result = PointedToMethod == ((MethodPointerValue)value).PointedToMethod;
                return true;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                throw new NotSupportedException();
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = null;
                return false;
            }

            public override Value Clone() => this; // Immutable
        }

        private abstract class ByRefValueBase : Value, INativeIntConvertibleValue
        {
            public virtual bool TryStore(Value value) => false;
            public virtual bool TryLoad(TypeDesc type, out Value value)
            {
                value = null;
                return false;
            }
            public virtual bool TryInitialize(int size) => false;

            public virtual FieldDesc BackingField => null;
        }

        private sealed class ByRefValue : ByRefValueBase, IHasInstanceFields
        {
            public readonly byte[] PointedToBytes;
            public readonly int PointedToOffset;

            public ByRefValue(byte[] pointedToBytes, int pointedToOffset)
            {
                PointedToBytes = pointedToBytes;
                PointedToOffset = pointedToOffset;
            }

            public override bool TryCompareEquality(Value value, out bool result)
            {
                if (!(value is ByRefValue))
                {
                    result = false;
                    return false;
                }

                result = PointedToBytes == ((ByRefValue)value).PointedToBytes
                    && PointedToOffset == ((ByRefValue)value).PointedToOffset;
                return true;
            }

            Value IHasInstanceFields.GetField(FieldDesc field) => new FieldAccessor(PointedToBytes, PointedToOffset).GetField(field);
            bool IHasInstanceFields.TrySetField(FieldDesc field, Value value) => new FieldAccessor(PointedToBytes, PointedToOffset).TrySetField(field, value);
            ByRefValueBase IHasInstanceFields.GetFieldAddress(FieldDesc field) => new FieldAccessor(PointedToBytes, PointedToOffset).GetFieldAddress(field);

            public override bool TryInitialize(int size)
            {
                if ((uint)size > (uint)(PointedToBytes.Length - PointedToOffset))
                {
                    return false;
                }

                for (int i = PointedToOffset; i < PointedToOffset + size; i++)
                {
                    PointedToBytes[i] = 0;
                }

                return true;
            }

            public override bool TryStore(Value value)
            {
                if (value is not ValueTypeValue srcVal)
                    return false;

                byte[] src = srcVal.InstanceBytes;
                if (PointedToOffset + src.Length > PointedToBytes.Length)
                    return false;

                Array.Copy(src, 0, PointedToBytes, PointedToOffset, src.Length);

                return true;
            }

            public override bool TryLoad(TypeDesc type, out Value value)
            {
                if (!type.IsValueType
                    || ((MetadataType)type).InstanceFieldSize.AsInt > PointedToBytes.Length - PointedToOffset)
                {
                    value = null;
                    return false;
                }

                var result = new ValueTypeValue(type);
                Array.Copy(PointedToBytes, PointedToOffset, result.InstanceBytes, 0, result.InstanceBytes.Length);
                value = result;
                return true;
            }

            public override Value Clone() => this; // Immutable

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                // This would imply we have a byref-typed static field. The layout algorithm should have blocked this.
                throw new NotImplementedException();
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = null;
                return false;
            }
        }

        private abstract class ReferenceTypeValue : Value
        {
            public TypeDesc Type { get; }

            protected ReferenceTypeValue(TypeDesc type) { Type = type; }

            public override bool TryCompareEquality(Value value, out bool result)
            {
                result = this == value;
                return true;
            }

            public abstract ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext);
        }

        private struct AllocationSite
        {
            public MetadataType OwningType { get; }
            public int InstructionCounter { get; }
            public AllocationSite(MetadataType type, int instructionCounter)
            {
                Debug.Assert(type.HasStaticConstructor);
                OwningType = type;
                InstructionCounter = instructionCounter;
            }
        }

        /// <summary>
        /// A reference type that is not a string literal.
        /// </summary>
        private abstract class AllocatedReferenceTypeValue : ReferenceTypeValue
        {
            protected AllocationSite AllocationSite { get; }

            public AllocatedReferenceTypeValue(TypeDesc type, AllocationSite allocationSite)
                : base(type)
            {
                AllocationSite = allocationSite;
            }

            public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext) =>
                new ForeignTypeInstance(
                    Type,
                    new AllocationSite(AllocationSite.OwningType, AllocationSite.InstructionCounter - baseInstructionCounter),
                    this);

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                if (this is ISerializableReference serializableRef)
                {
                    data = factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, serializableRef);
                    return true;
                }
                data = null;
                return false;
            }

            public virtual bool HasConditionalDependencies => false;

            public virtual void GetConditionalDependencies(ref CombinedDependencyList dependencies, NodeFactory factory)
            {
            }
        }

        private sealed class DelegateInstance : AllocatedReferenceTypeValue, ISerializableReference
        {
            private readonly MethodDesc _methodPointed;
            private readonly ReferenceTypeValue _firstParameter;

            public DelegateInstance(TypeDesc delegateType, MethodDesc methodPointed, ReferenceTypeValue firstParameter, AllocationSite allocationSite)
                : base(delegateType, allocationSite)
            {
                _methodPointed = methodPointed;
                _firstParameter = firstParameter;
            }

            private DelegateCreationInfo GetDelegateCreationInfo(NodeFactory factory)
                => DelegateCreationInfo.Create(
                    Type.ConvertToCanonForm(CanonicalFormKind.Specific),
                    _methodPointed,
                    constrainedType: null,
                    factory,
                    followVirtualDispatch: false);

            public override bool HasConditionalDependencies => true;

            public override void GetConditionalDependencies(ref CombinedDependencyList dependencies, NodeFactory factory)
            {
                dependencies ??= new CombinedDependencyList();

                DelegateCreationInfo creationInfo = GetDelegateCreationInfo(factory);

                MethodDesc targetMethod = creationInfo.PossiblyUnresolvedTargetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                factory.MetadataManager.GetDependenciesDueToDelegateCreation(ref dependencies, factory, creationInfo.DelegateType, targetMethod);
            }

            public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory)
            {
                Debug.Assert(_methodPointed.Signature.IsStatic == (_firstParameter == null));

                DelegateCreationInfo creationInfo = GetDelegateCreationInfo(factory);

                Debug.Assert(!creationInfo.TargetNeedsVTableLookup);

                // MethodTable
                var node = factory.ConstructedTypeSymbol(Type);
                Debug.Assert(!node.RepresentsIndirectionCell);  // Shouldn't have allowed this
                builder.EmitPointerReloc(node);

                if (_methodPointed.Signature.IsStatic)
                {
                    Debug.Assert(creationInfo.Constructor.Method.Name.SequenceEqual("InitializeOpenStaticThunk"u8));

                    // _firstParameter
                    builder.EmitPointerReloc(thisNode);

                    // _helperObject
                    builder.EmitZeroPointer();

                    // _extraFunctionPointerOrData
                    builder.EmitPointerReloc(creationInfo.GetTargetNode(factory));

                    // _functionPointer
                    Debug.Assert(creationInfo.Thunk != null);
                    builder.EmitPointerReloc(creationInfo.Thunk);
                }
                else
                {
                    Debug.Assert(creationInfo.Constructor.Method.Name.SequenceEqual("InitializeClosedInstance"u8));

                    // _firstParameter
                    _firstParameter.WriteFieldData(ref builder, factory);

                    // _helperObject
                    builder.EmitZeroPointer();

                    // _extraFunctionPointerOrData
                    builder.EmitZeroPointer();

                    // _functionPointer
                    builder.EmitPointerReloc(creationInfo.GetTargetNode(factory));
                }
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, this));
            }

            public bool IsKnownImmutable => _methodPointed.Signature.IsStatic;

            public int ArrayLength => throw new NotSupportedException();
        }

        private sealed class ArrayInstance : AllocatedReferenceTypeValue, ISerializableReference
        {
            private readonly int _elementCount;
            private readonly int _elementSize;
            private readonly byte[] _data;

            public ArrayInstance(ArrayType type, int elementCount, AllocationSite allocationSite)
                : base(type, allocationSite)
            {
                _elementCount = elementCount;
                _elementSize = type.ElementType.GetElementSize().AsInt;
                _data = new byte[elementCount * _elementSize];
            }

            public bool TryInitialize(byte[] bytes)
            {
                if (bytes.Length != _data.Length)
                    return false;

                Array.Copy(bytes, _data, bytes.Length);
                return true;
            }

            public int Length
            {
                get
                {
                    return _elementCount;
                }
            }

            public bool TryStoreElement(int index, Value value)
            {
                if (!(value is ValueTypeValue valueToStore))
                    return false;

                if ((uint)index >= (uint)Length)
                    return false;

                Debug.Assert(valueToStore.InstanceBytes.Length == _elementSize);
                Array.Copy(valueToStore.InstanceBytes, 0, _data, index * _elementSize, valueToStore.InstanceBytes.Length);
                return true;
            }

            public bool TryLoadElement(int index, out Value value)
            {
                if ((uint)index >= (uint)Length)
                {
                    value = null;
                    return false;
                }

                ValueTypeValue result = new ValueTypeValue(((ArrayType)Type).ElementType);
                Array.Copy(_data, index * _elementSize, result.InstanceBytes, 0, _elementSize);
                value = result;
                return true;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, this));
            }

            public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory)
            {
                // MethodTable
                var node = factory.ConstructedTypeSymbol(Type);
                Debug.Assert(!node.RepresentsIndirectionCell);  // Arrays are always local
                builder.EmitPointerReloc(node);

                // numComponents
                builder.EmitInt(_elementCount);

                int pointerSize = Type.Context.Target.PointerSize;
                Debug.Assert(pointerSize == 8 || pointerSize == 4);

                if (pointerSize == 8)
                {
                    // padding numComponents in 64-bit
                    builder.EmitInt(0);
                }

                builder.EmitBytes(_data);
            }

            public ByRefValue GetArrayData()
            {
                return new ByRefValue(_data, 0);
            }

            public bool IsKnownImmutable => _elementCount == 0;

            public int ArrayLength => Length;
        }

        private sealed class ForeignTypeInstance : AllocatedReferenceTypeValue
        {
            public ReferenceTypeValue Data { get; }

            public ForeignTypeInstance(TypeDesc type, AllocationSite allocationSite, ReferenceTypeValue data)
                : base(type, allocationSite)
            {
                Data = data;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                if (Data is ISerializableReference serializableReference)
                {
                    builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, serializableReference));
                }
                else
                {
                    Data.WriteFieldData(ref builder, factory);
                }
            }

            public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext) => this;
        }

        private sealed class StringInstance : ReferenceTypeValue, IHasInstanceFields
        {
            private readonly byte[] _value;

            private string ValueAsString
            {
                get
                {
                    FieldDesc firstCharField = Type.GetField("_firstChar"u8);
                    int startOffset = firstCharField.Offset.AsInt;
                    int length = _value.Length - startOffset - sizeof(char) /* terminating null */;
                    return new string(MemoryMarshal.Cast<byte, char>(
                        ((ReadOnlySpan<byte>)_value).Slice(startOffset, length)));
                }
            }
            public StringInstance(TypeDesc stringType, string value)
                : base(stringType)
            {
                _value = ConstructStringInstance(stringType, value);
            }

            private static byte[] ConstructStringInstance(TypeDesc stringType, ReadOnlySpan<char> value)
            {
                int pointerSize = stringType.Context.Target.PointerSize;
                var bytes = new byte[
                    pointerSize /* MethodTable */
                    + sizeof(int) /* length */
                    + (value.Length * sizeof(char)) /* bytes */
                    + sizeof(char) /* null terminator */];

                FieldDesc lengthField = stringType.GetField("_stringLength"u8);
                Debug.Assert(lengthField.FieldType.IsWellKnownType(WellKnownType.Int32)
                    && lengthField.Offset.AsInt == pointerSize);
                bool success = new FieldAccessor(bytes).TrySetField(lengthField, ValueTypeValue.FromInt32(value.Length));
                Debug.Assert(success);

                FieldDesc firstCharField = stringType.GetField("_firstChar"u8);
                Debug.Assert(firstCharField.FieldType.IsWellKnownType(WellKnownType.Char)
                    && firstCharField.Offset.AsInt == pointerSize + sizeof(int) /* length */);

                value.CopyTo(MemoryMarshal.Cast<byte, char>(((Span<byte>)bytes).Slice(firstCharField.Offset.AsInt)));

                return bytes;
            }

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                builder.EmitPointerReloc(factory.SerializedStringObject(ValueAsString));
            }

            public override bool GetRawData(NodeFactory factory, out object data)
            {
                data = factory.SerializedStringObject(ValueAsString);
                return true;
            }

            public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext)
            {
                string value = ValueAsString;
                if (!preinitContext._internedStrings.TryGetValue(value, out StringInstance result))
                {
                    preinitContext._internedStrings.Add(value, result = new StringInstance(Type, value));
                }
                return result;
            }
            Value IHasInstanceFields.GetField(FieldDesc field) => new FieldAccessor(_value).GetField(field);
            bool IHasInstanceFields.TrySetField(FieldDesc field, Value value) => false;
            ByRefValueBase IHasInstanceFields.GetFieldAddress(FieldDesc field) => new FieldAccessor(_value).GetFieldAddress(field);
        }

        private sealed class ObjectInstance : AllocatedReferenceTypeValue, IHasInstanceFields, ISerializableReference
        {
            private readonly byte[] _data;

            public ObjectInstance(DefType type, AllocationSite allocationSite)
                : base(type, allocationSite)
            {
                int size = type.InstanceByteCount.AsInt;
                if (type.IsValueType)
                    size += type.Context.Target.PointerSize;
                _data = new byte[size];
            }

            public static bool TryBox(DefType type, Value value, AllocationSite allocationSite, out ObjectInstance result)
            {
                if (!(value is BaseValueTypeValue))
                    ThrowHelper.ThrowInvalidProgramException();

                if (!(value is ValueTypeValue valuetype))
                {
                    result = null;
                    return false;
                }

                result = new ObjectInstance(type, allocationSite);
                Array.Copy(valuetype.InstanceBytes, 0, result._data, type.Context.Target.PointerSize, valuetype.InstanceBytes.Length);
                return true;
            }

            public bool TryUnboxAny(TypeDesc type, out Value value)
            {
                value = null;

                if (!type.IsValueType || type.IsNullable)
                    return false;

                if (Type.UnderlyingType != type.UnderlyingType)
                    return false;

                var result = new ValueTypeValue(type);
                Array.Copy(_data, type.Context.Target.PointerSize, result.InstanceBytes, 0, result.InstanceBytes.Length);
                value = result;
                return true;
            }

            Value IHasInstanceFields.GetField(FieldDesc field) => new FieldAccessor(_data).GetField(field);
            bool IHasInstanceFields.TrySetField(FieldDesc field, Value value) => new FieldAccessor(_data).TrySetField(field, value);
            ByRefValueBase IHasInstanceFields.GetFieldAddress(FieldDesc field) => new FieldAccessor(_data).GetFieldAddress(field);

            public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
            {
                builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, this));
            }

            public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory)
            {
                // MethodTable
                var node = factory.ConstructedTypeSymbol(Type);
                Debug.Assert(!node.RepresentsIndirectionCell);  // Shouldn't have allowed preinitializing this
                builder.EmitPointerReloc(node);

                // We skip the first pointer because that's the MethodTable pointer
                // we just initialized above.
                int pointerSize = factory.Target.PointerSize;
                builder.EmitBytes(_data, pointerSize, _data.Length - pointerSize);
            }

            public bool IsKnownImmutable => !Type.GetFields().GetEnumerator().MoveNext();

            public int ArrayLength => throw new NotSupportedException();
        }

        private struct FieldAccessor
        {
            private readonly byte[] _instanceBytes;
            private readonly int _offset;

            public FieldAccessor(byte[] bytes, int offset = 0)
            {
                _instanceBytes = bytes;
                _offset = offset;
            }

            public ValueTypeValue GetField(FieldDesc field)
            {
                Debug.Assert(!field.IsStatic);
                Debug.Assert(!field.FieldType.IsGCPointer);
                int fieldOffset = field.Offset.AsInt;
                int fieldSize = field.FieldType.GetElementSize().AsInt;
                if (fieldOffset + fieldSize > _instanceBytes.Length - _offset)
                    ThrowHelper.ThrowInvalidProgramException();

                var result = new ValueTypeValue(field.FieldType);
                Array.Copy(_instanceBytes, _offset + fieldOffset, result.InstanceBytes, 0, fieldSize);
                return result;
            }

            public bool TrySetField(FieldDesc field, Value value)
            {
                Debug.Assert(!field.IsStatic);

                if (field.FieldType.IsGCPointer)
                {
                    // Allow setting reference type fields to null. Since this is the only value we can
                    // write, this is a no-op since reference type fields are always null
                    Debug.Assert(value == null);
                    return true;
                }

                int fieldOffset = field.Offset.AsInt;
                int fieldSize = field.FieldType.GetElementSize().AsInt;
                if (fieldOffset + fieldSize > _instanceBytes.Length - _offset)
                    ThrowHelper.ThrowInvalidProgramException();

                if (value is IInternalModelingOnlyValue)
                {
                    return false;
                }

                if (value is ByRefValueBase
                    && (field.FieldType.IsWellKnownType(WellKnownType.IntPtr) || field.FieldType.IsWellKnownType(WellKnownType.UIntPtr)))
                {
                    return false;
                }

                if (value is not ValueTypeValue vtValue)
                {
                    ThrowHelper.ThrowInvalidProgramException();
                    return false; // unreached
                }

                Array.Copy(vtValue.InstanceBytes, 0, _instanceBytes, _offset + fieldOffset, fieldSize);
                return true;
            }

            public ByRefValue GetFieldAddress(FieldDesc field)
            {
                Debug.Assert(!field.IsStatic);
                Debug.Assert(!field.FieldType.IsGCPointer);
                int fieldOffset = field.Offset.AsInt;
                int fieldSize = field.FieldType.GetElementSize().AsInt;
                if (fieldOffset + fieldSize > _instanceBytes.Length - _offset)
                    ThrowHelper.ThrowInvalidProgramException();

                return new ByRefValue(_instanceBytes, _offset + fieldOffset);
            }
        }

        private struct StackEntry
        {
            public readonly StackValueKind ValueKind;
            public readonly Value Value;

            public StackEntry(StackValueKind valueKind, Value value)
            {
                // TODO: can we assert invariants around value allowed for valueKind?
                ValueKind = valueKind;
                Value = value;
            }
        }

        private struct Status
        {
            public string FailureReason { get; }

            public static Status Success => default;

            public bool IsSuccessful => FailureReason == null;

            private Status(string message)
            {
                FailureReason = message;
            }

            public static Status Fail(MethodDesc method, ILOpcode opcode, string detail = null)
            {
                return new Status($"Method '{method}', opcode '{opcode}' {detail ?? ""}");
            }

            public static Status Fail(MethodDesc method, string detail)
            {
                return new Status($"Method '{method}': {detail}");
            }
        }

        private readonly struct NestedPreinitResult
        {
            private readonly Dictionary<FieldDesc, Value> _fieldValues;
            private readonly int _baseInstructionCounter;

            public NestedPreinitResult(Dictionary<FieldDesc, Value> fieldValues, int baseInstructionCounter)
                => (_fieldValues, _baseInstructionCounter) = (fieldValues, baseInstructionCounter);

            public bool TryGetFieldValue(TypePreinit context, FieldDesc field, out Value value)
            {
                Value fieldValue = _fieldValues[field];

                if (fieldValue is ReferenceTypeValue referenceType)
                {
                    value = referenceType.ToForeignInstance(_baseInstructionCounter, context);
                    return true;
                }
                else if (fieldValue is BaseValueTypeValue)
                {
                    value = fieldValue;
                    return true;
                }

                value = null;
                return false;
            }
        }

        public class PreinitializationInfo
        {
            private readonly Dictionary<FieldDesc, ISerializableValue> _fieldValues;

            public MetadataType Type { get; }

            public string FailureReason { get; }

            public bool IsPreinitialized => _fieldValues != null;

            public PreinitializationInfo(MetadataType type, IEnumerable<KeyValuePair<FieldDesc, ISerializableValue>> fieldValues)
            {
                Type = type;
                _fieldValues = new Dictionary<FieldDesc, ISerializableValue>();
                foreach (var field in fieldValues)
                    _fieldValues.Add(field.Key, field.Value);
            }

            public PreinitializationInfo(MetadataType type, string failureReason)
            {
                Type = type;
                FailureReason = failureReason;
            }

            public ISerializableValue GetFieldValue(FieldDesc field)
            {
                Debug.Assert(IsPreinitialized);
                Debug.Assert(field.OwningType == Type);
                Debug.Assert(field.IsStatic && !field.HasRva && !field.IsThreadStatic && !field.IsLiteral);
                return _fieldValues[field];
            }
        }

        public abstract class TypePreinitializationPolicy
        {
            /// <summary>
            /// Returns true if the preinitialization system may attempt to preinitialize this type.
            /// </summary>
            public abstract bool CanPreinitialize(DefType type);

            /// <summary>
            /// Returns true if all concrete forms of this canonical form will be preinitialized.
            /// This can only be answered by a whole program view.
            /// </summary>
            public abstract bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type);
        }

        /// <summary>
        /// Preinitialization policy that doesn't allow preinitialization.
        /// </summary>
        public sealed class DisabledPreinitializationPolicy : TypePreinitializationPolicy
        {
            public override bool CanPreinitialize(DefType type) => false;
            public override bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type) => false;
        }

        /// <summary>
        /// Preinitialization policy that assumes new canonical forms of types could be created
        /// at runtime.
        /// </summary>
        public sealed class TypeLoaderAwarePreinitializationPolicy : TypePreinitializationPolicy
        {
            public override bool CanPreinitialize(DefType type) => true;

            public override bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type) => false;
        }
    }

    file static class Extensions
    {
        public static StackValueKind WithNormalizedNativeInt(this StackValueKind kind, TypeSystemContext context)
            => kind switch
            {
                StackValueKind.NativeInt => context.Target.PointerSize == 8 ? StackValueKind.Int64 : StackValueKind.Int32,
                _ => kind
            };
    }
}