File: JitInterface\CorInfoImpl.RyuJit.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.RyuJit\ILCompiler.RyuJit.csproj (ILCompiler.RyuJit)
// 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.IO;
using System.Runtime.InteropServices;

using Internal.IL;
using Internal.ReadyToRunConstants;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

using ILCompiler;
using ILCompiler.DependencyAnalysis;

#if SUPPORT_JIT
using MethodCodeNode = Internal.Runtime.JitSupport.JitMethodCodeNode;
using RyuJitCompilation = ILCompiler.Compilation;
#endif

#pragma warning disable IDE0060

namespace Internal.JitInterface
{
    internal unsafe partial class CorInfoImpl
    {
        private const CORINFO_RUNTIME_ABI TargetABI = CORINFO_RUNTIME_ABI.CORINFO_NATIVEAOT_ABI;

        private uint OffsetOfDelegateFirstTarget => (uint)(4 * PointerSize); // Delegate._functionPointer
        private int SizeOfReversePInvokeTransitionFrame => 2 * PointerSize;

        private RyuJitCompilation _compilation;
        private MethodDebugInformation _debugInfo;
        private MethodCodeNode _methodCodeNode;
        private DebugLocInfo[] _debugLocInfos;
        private DebugVarInfo[] _debugVarInfos;
        private readonly UnboxingMethodDescFactory _unboxingThunkFactory = new UnboxingMethodDescFactory();
        private bool _isFallbackBodyCompilation;

        public CorInfoImpl(RyuJitCompilation compilation)
            : this()
        {
            _compilation = compilation;
        }

        private FrozenObjectNode HandleToObject(CORINFO_OBJECT_STRUCT_* obj) => (FrozenObjectNode)HandleToObject((void*)obj);
        private CORINFO_OBJECT_STRUCT_* ObjectToHandle(FrozenObjectNode obj) => (CORINFO_OBJECT_STRUCT_*)ObjectToHandle((object)obj);

        private UnboxingMethodDesc getUnboxingThunk(MethodDesc method)
        {
            return _unboxingThunkFactory.GetUnboxingMethod(method);
        }

        private CORINFO_METHOD_STRUCT_* getAsyncResumptionStub(ref void* entryPoint)
        {
            MethodDesc asyncResumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(MethodBeingCompiled, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType());

            entryPoint = (void*)ObjectToHandle(_compilation.NodeFactory.MethodEntrypoint(asyncResumptionStub));
            return ObjectToHandle(asyncResumptionStub);
        }

        public void CompileMethod(MethodCodeNode methodCodeNodeNeedingCode, MethodIL methodIL = null)
        {
            _methodCodeNode = methodCodeNodeNeedingCode;
            _isFallbackBodyCompilation = methodIL != null;

            methodIL ??= _compilation.GetMethodIL(MethodBeingCompiled);

            try
            {
                CompileMethodInternal(methodCodeNodeNeedingCode, methodIL);
            }
            finally
            {
#if DEBUG
                // RyuJIT makes assumptions around the value of type symbols - in particular, it assumes
                // that type handles and type symbols have a 1:1 relationship. We therefore need to
                // make sure RyuJIT never sees a constructed and unconstructed type symbol for the
                // same type. This check makes sure we didn't accidentally hand out a necessary type symbol
                // that the compilation class didn't agree to handing out.
                // https://github.com/dotnet/runtimelab/issues/1128
                for (int i = 0; i < _codeRelocs.Count; i++)
                {
                    if (_codeRelocs[i].Target is EETypeNode eetype)
                    {
                        IEETypeNode expectedeetype = _compilation.NecessaryTypeSymbolIfPossible(eetype.Type);
                        Debug.Assert(expectedeetype == eetype);
                    }
                }
#endif

                CompileMethodCleanup();
            }
        }

        private enum CFI_OPCODE
        {
            CFI_ADJUST_CFA_OFFSET,    // Offset is adjusted relative to the current one.
            CFI_DEF_CFA_REGISTER,     // New register is used to compute CFA
            CFI_REL_OFFSET,           // Register is saved at offset from the current CFA
            CFI_DEF_CFA               // Take address from register and add offset to it.
        }

        // Get the CFI data in the same shape as clang/LLVM generated one. This improves the compatibility with libunwind and other unwind solutions
        // - Combine in one single block for the whole prolog instead of one CFI block per assembler instruction
        // - Store CFA definition first
        // - Store all used registers in ascending order
        private static byte[] CompressARM64CFI(byte[] blobData)
        {
            if (blobData == null || blobData.Length == 0)
            {
                return blobData;
            }

            Debug.Assert(blobData.Length % 8 == 0);

            short spReg = -1;

            int codeOffset = 0;
            short cfaRegister = spReg;
            int cfaOffset = 0;
            int spOffset = 0;

            int[] registerOffset = new int[96];

            for (int i = 0; i < registerOffset.Length; i++)
            {
                registerOffset[i] = int.MinValue;
            }

            int offset = 0;
            while (offset < blobData.Length)
            {
                codeOffset = Math.Max(codeOffset, blobData[offset++]);
                CFI_OPCODE opcode = (CFI_OPCODE)blobData[offset++];
                short dwarfReg = BitConverter.ToInt16(blobData, offset);
                offset += sizeof(short);
                int cfiOffset = BitConverter.ToInt32(blobData, offset);
                offset += sizeof(int);

                switch (opcode)
                {
                    case CFI_OPCODE.CFI_DEF_CFA_REGISTER:
                        cfaRegister = dwarfReg;

                        if (spOffset != 0)
                        {
                            for (int i = 0; i < registerOffset.Length; i++)
                            {
                                if (registerOffset[i] != int.MinValue)
                                {
                                    registerOffset[i] -= spOffset;
                                }
                            }

                            cfaOffset += spOffset;
                            spOffset = 0;
                        }

                        break;

                    case CFI_OPCODE.CFI_REL_OFFSET:
                        Debug.Assert(cfaRegister == spReg);
                        registerOffset[dwarfReg] = cfiOffset;
                        break;

                    case CFI_OPCODE.CFI_ADJUST_CFA_OFFSET:
                        if (cfaRegister != spReg)
                        {
                            cfaOffset += cfiOffset;
                        }
                        else
                        {
                            spOffset += cfiOffset;

                            for (int i = 0; i < registerOffset.Length; i++)
                            {
                                if (registerOffset[i] != int.MinValue)
                                {
                                    registerOffset[i] += cfiOffset;
                                }
                            }
                        }
                        break;
                }
            }

            using (MemoryStream cfiStream = new MemoryStream())
            {
                int storeOffset = 0;

                using (BinaryWriter cfiWriter = new BinaryWriter(cfiStream))
                {
                    if (cfaRegister != -1)
                    {
                        cfiWriter.Write((byte)codeOffset);
                        cfiWriter.Write(cfaOffset != 0 ? (byte)CFI_OPCODE.CFI_DEF_CFA : (byte)CFI_OPCODE.CFI_DEF_CFA_REGISTER);
                        cfiWriter.Write(cfaRegister);
                        cfiWriter.Write(cfaOffset);
                        storeOffset = cfaOffset;
                    }
                    else
                    {
                        if (cfaOffset != 0)
                        {
                            cfiWriter.Write((byte)codeOffset);
                            cfiWriter.Write((byte)CFI_OPCODE.CFI_ADJUST_CFA_OFFSET);
                            cfiWriter.Write((short)-1);
                            cfiWriter.Write(cfaOffset);
                        }

                        if (spOffset != 0)
                        {
                            cfiWriter.Write((byte)codeOffset);
                            cfiWriter.Write((byte)CFI_OPCODE.CFI_DEF_CFA);
                            cfiWriter.Write((short)31);
                            cfiWriter.Write(spOffset);
                        }
                    }

                    for (int i = registerOffset.Length - 1; i >= 0; i--)
                    {
                        if (registerOffset[i] != int.MinValue)
                        {
                            cfiWriter.Write((byte)codeOffset);
                            cfiWriter.Write((byte)CFI_OPCODE.CFI_REL_OFFSET);
                            cfiWriter.Write((short)i);
                            cfiWriter.Write(registerOffset[i] + storeOffset);
                        }
                    }
                }

                return cfiStream.ToArray();
            }
        }

        private static CORINFO_RUNTIME_LOOKUP_KIND GetLookupKindFromContextSource(GenericContextSource contextSource)
        {
            switch (contextSource)
            {
                case GenericContextSource.MethodParameter:
                    return CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_METHODPARAM;
                case GenericContextSource.TypeParameter:
                    return CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_CLASSPARAM;
                default:
                    Debug.Assert(contextSource == GenericContextSource.ThisObject);
                    return CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_THISOBJ;
            }
        }

        private void ComputeLookup(ref CORINFO_RESOLVED_TOKEN pResolvedToken, object entity, ReadyToRunHelperId helperId, MethodDesc callerHandle, ref CORINFO_LOOKUP lookup)
        {
            Debug.Assert(callerHandle != null);

            if (_compilation.NeedsRuntimeLookup(helperId, entity))
            {
                GenericDictionaryLookup genericLookup = _compilation.ComputeGenericLookup(callerHandle, helperId, entity);

                lookup.lookupKind.needsRuntimeLookup = true;
                lookup.lookupKind.runtimeLookupKind = GetLookupKindFromContextSource(genericLookup.ContextSource);
                lookup.runtimeLookup.signature = null;

                if (genericLookup.UseHelper)
                {
                    // If this is from a different context and we need a ReadyToRun helper, abort.
                    // The ReadyToRun helpers need to be able to declare the dependencies and we can't
                    // currently do it for an inline. This is not a big issue because ReadyToRun helpers
                    // in optimized code only happen in special build configurations (such as
                    // `-O --noscan` or multimodule build).
                    if (pResolvedToken.tokenContext != contextFromMethodBeingCompiled())
                    {
                        lookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED;
                        return;
                    }

                    ISymbolNode helper = GetGenericLookupHelper(lookup.lookupKind.runtimeLookupKind, genericLookup.HelperId, callerHandle, genericLookup.HelperObject);
                    lookup.runtimeLookup.indirections = CORINFO.USEHELPER;
                    lookup.runtimeLookup.helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GENERIC_HANDLE;
                    lookup.runtimeLookup.helperEntryPoint = CreateConstLookupToSymbol(helper);
                }
                else if (genericLookup.UseNull)
                {
                    lookup.runtimeLookup.indirections = CORINFO.USENULL;
                }
                else
                {
                    if (genericLookup.ContextSource == GenericContextSource.MethodParameter)
                    {
                        lookup.runtimeLookup.helper = CorInfoHelpFunc.CORINFO_HELP_RUNTIMEHANDLE_METHOD;
                    }
                    else
                    {
                        lookup.runtimeLookup.helper = CorInfoHelpFunc.CORINFO_HELP_RUNTIMEHANDLE_CLASS;
                    }

                    lookup.runtimeLookup.indirections = (ushort)genericLookup.NumberOfIndirections;
                    lookup.runtimeLookup.offset0 = genericLookup[0];
                    if (genericLookup.NumberOfIndirections > 1)
                    {
                        lookup.runtimeLookup.offset1 = genericLookup[1];
                    }
                    lookup.runtimeLookup.sizeOffset = CORINFO.CORINFO_NO_SIZE_CHECK;
                    lookup.runtimeLookup.testForNull = false;
                    lookup.runtimeLookup.indirectFirstOffset = false;
                    lookup.runtimeLookup.indirectSecondOffset = false;
                }
            }
            else
            {
                lookup.lookupKind.needsRuntimeLookup = false;
                ISymbolNode constLookup = _compilation.ComputeConstantLookup(helperId, entity);
                lookup.constLookup = CreateConstLookupToSymbol(constLookup);
            }
        }

        private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CorInfoHelpFunc id, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_CONST_LOOKUP pLookup)
        {
            switch (id)
            {
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NEW:
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NEWARR_1:
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_ISINSTANCEOF:
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_CHKCAST:
                    return false;
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GCSTATIC_BASE:
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NONGCSTATIC_BASE:
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_THREADSTATIC_BASE:
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NONGCTHREADSTATIC_BASE:
                    {
                        var type = HandleToObject(pResolvedToken.hClass);
                        if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
                            return false;
                        var helperId = GetReadyToRunHelperFromStaticBaseHelper(id);
                        pLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ReadyToRunHelper(helperId, type));
                    }
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE:
                    {
                        // Token == 0 means "initialize this class". We only expect RyuJIT to call it for this case.
                        Debug.Assert(pResolvedToken.token == 0 && pResolvedToken.tokenScope == null);
                        Debug.Assert(MethodBeingCompiled.IsSharedByGenericInstantiations);

                        DefType typeToInitialize = (DefType)HandleToObject(callerHandle).OwningType;
                        Debug.Assert(typeToInitialize.IsCanonicalSubtype(CanonicalFormKind.Any));

                        DefType helperArg = typeToInitialize.ConvertToSharedRuntimeDeterminedForm();
                        ISymbolNode helper = GetGenericLookupHelper(GetGenericRuntimeLookupKind(MethodBeingCompiled), ReadyToRunHelperId.GetNonGCStaticBase, HandleToObject(callerHandle), helperArg);
                        pLookup = CreateConstLookupToSymbol(helper);
                    }
                    break;
                default:
                    throw new NotImplementedException("ReadyToRun: " + id.ToString());
            }
            return true;
        }

        private void getReadyToRunDelegateCtorHelper(ref CORINFO_RESOLVED_TOKEN pTargetMethod, mdToken targetConstraint, CORINFO_CLASS_STRUCT_* delegateType, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_LOOKUP pLookup)
        {
#if DEBUG
            // In debug, write some bogus data to the struct to ensure we have filled everything
            // properly.
            fixed (CORINFO_LOOKUP* tmp = &pLookup)
                NativeMemory.Fill(tmp, (nuint)sizeof(CORINFO_LOOKUP), 0xcc);
#endif

            MethodDesc expectedTargetMethod = HandleToObject(pTargetMethod.hMethod);
            TypeDesc delegateTypeDesc = HandleToObject(delegateType);

            MethodDesc targetMethod = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pTargetMethod);

            // If this was a constrained+ldftn sequence, we need to resolve the constraint
            TypeDesc constrainedType = null;
            if (targetConstraint != 0)
            {
                // Should really only be here for static virtuals since constrained ldftn is not allowed elsewhere.
                Debug.Assert(targetMethod.IsVirtual && targetMethod.Signature.IsStatic
                    && pTargetMethod.tokenType != CorInfoTokenKind.CORINFO_TOKENKIND_Ldvirtftn);

                var methodIL = HandleToObject(pTargetMethod.tokenScope);
                var typeOrMethodContext = (pTargetMethod.tokenContext == contextFromMethodBeingCompiled()) ?
                    MethodBeingCompiled : HandleToObject((void*)pTargetMethod.tokenContext);
                var canonConstrainedType = (TypeDesc)ResolveTokenInScope(methodIL, typeOrMethodContext, targetConstraint);
                TypeDesc interfaceType = HandleToObject(pTargetMethod.hClass);
                var interfaceMethod = (MethodDesc)ResolveTokenInScope(methodIL, typeOrMethodContext, pTargetMethod.token);
                constrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(methodIL, typeOrMethodContext, targetConstraint);

                DefaultInterfaceMethodResolution staticResolution = default;
                MethodDesc directMethod = canonConstrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(interfaceType, interfaceMethod, out bool forceRuntimeLookup, ref staticResolution);
                if (directMethod != null && !directMethod.IsCanonicalMethod(CanonicalFormKind.Any))
                {
                    targetMethod = directMethod;

                    // We resolved the constraint
                    constrainedType = null;
                }
                else if (directMethod != null)
                {
                    // We resolved on a canonical form. Now find the method on the runtime determined form.
                    Debug.Assert(directMethod.OwningType.IsValueType || directMethod.Signature.IsStatic);
                    Debug.Assert(!forceRuntimeLookup);

                    MethodDesc targetOfLookup;
                    if (constrainedType.IsRuntimeDeterminedType)
                        targetOfLookup = _compilation.TypeSystemContext.GetMethodForRuntimeDeterminedType(directMethod.GetTypicalMethodDefinition(), (RuntimeDeterminedType)constrainedType);
                    else if (constrainedType.HasInstantiation)
                        targetOfLookup = _compilation.TypeSystemContext.GetMethodForInstantiatedType(directMethod.GetTypicalMethodDefinition(), (InstantiatedType)constrainedType);
                    else
                        targetOfLookup = directMethod.GetMethodDefinition();

                    if (targetOfLookup.HasInstantiation)
                        targetOfLookup = targetOfLookup.MakeInstantiatedMethod(targetMethod.Instantiation);

                    targetMethod = targetOfLookup;

                    // We resolved the constraint
                    constrainedType = null;
                }
                else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction)
                {
                    // TODO: https://github.com/dotnet/runtime/issues/72589
                    ThrowHelper.ThrowInvalidProgramException();
                }
            }

            // We better come up with the same method that getCallInfo came up with, with the only difference being
            // that our targetMethod is RuntimeDetermined.
            Debug.Assert(expectedTargetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific) == targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific));

            bool isLdvirtftn = pTargetMethod.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Ldvirtftn;
            DelegateCreationInfo delegateInfo = _compilation.GetDelegateCtor(delegateTypeDesc, targetMethod, constrainedType, isLdvirtftn);

            if (delegateInfo.NeedsRuntimeLookup)
            {
                pLookup.lookupKind.needsRuntimeLookup = true;

                if (pTargetMethod.tokenContext != contextFromMethodBeingCompiled())
                {
                    pLookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_NOT_SUPPORTED;
                }
                else
                {
                    MethodDesc contextMethod = HandleToObject(callerHandle);
                    pLookup.lookupKind.runtimeLookupKind = GetGenericRuntimeLookupKind(contextMethod);
                    pLookup.runtimeLookup.indirections = CORINFO.USEHELPER;
                    pLookup.runtimeLookup.helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_DELEGATE_CTOR;
                    ISymbolNode helper = GetGenericLookupHelper(pLookup.lookupKind.runtimeLookupKind, ReadyToRunHelperId.DelegateCtor, contextMethod, delegateInfo);
                    pLookup.runtimeLookup.helperEntryPoint = CreateConstLookupToSymbol(helper);
                }
            }
            else
            {
                pLookup.lookupKind.needsRuntimeLookup = false;
                pLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ReadyToRunHelper(ReadyToRunHelperId.DelegateCtor, delegateInfo));
            }
        }

        private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
        {
            ReadyToRunHelper id;

            switch (ftnNum)
            {
                case CorInfoHelpFunc.CORINFO_HELP_THROW:
                    id = ReadyToRunHelper.Throw;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_RETHROW:
                    id = ReadyToRunHelper.Rethrow;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROWEXACT:
                    id = ReadyToRunHelper.ThrowExact;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_USER_BREAKPOINT:
                    id = ReadyToRunHelper.DebugBreak;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_OVERFLOW:
                    id = ReadyToRunHelper.Overflow;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_RNGCHKFAIL:
                    id = ReadyToRunHelper.RngChkFail;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_FAIL_FAST:
                    id = ReadyToRunHelper.FailFast;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROWNULLREF:
                    id = ReadyToRunHelper.ThrowNullRef;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROWDIVZERO:
                    id = ReadyToRunHelper.ThrowDivZero;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROW_ARGUMENTOUTOFRANGEEXCEPTION:
                    id = ReadyToRunHelper.ThrowArgumentOutOfRange;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROW_ARGUMENTEXCEPTION:
                    id = ReadyToRunHelper.ThrowArgument;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROW_NOT_IMPLEMENTED:
                    id = ReadyToRunHelper.ThrowNotImplemented;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED:
                    id = ReadyToRunHelper.ThrowPlatformNotSupported;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF:
                    id = ReadyToRunHelper.WriteBarrier;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF:
                    id = ReadyToRunHelper.CheckedWriteBarrier;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_BULK_WRITEBARRIER:
                    id = ReadyToRunHelper.BulkWriteBarrier;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_BYREF:
                    id = ReadyToRunHelper.ByRefWriteBarrier;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF_EAX:
                    id = ReadyToRunHelper.WriteBarrier_EAX;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF_EBX:
                    id = ReadyToRunHelper.WriteBarrier_EBX;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF_ECX:
                    id = ReadyToRunHelper.WriteBarrier_ECX;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF_EDI:
                    id = ReadyToRunHelper.WriteBarrier_EDI;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF_ESI:
                    id = ReadyToRunHelper.WriteBarrier_ESI;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF_EAX:
                    id = ReadyToRunHelper.CheckedWriteBarrier_EAX;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF_EBX:
                    id = ReadyToRunHelper.CheckedWriteBarrier_EBX;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF_ECX:
                    id = ReadyToRunHelper.CheckedWriteBarrier_ECX;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF_EDI:
                    id = ReadyToRunHelper.CheckedWriteBarrier_EDI;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF_ESI:
                    id = ReadyToRunHelper.CheckedWriteBarrier_ESI;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_ARRADDR_ST:
                    id = ReadyToRunHelper.Stelem_Ref;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LDELEMA_REF:
                    id = ReadyToRunHelper.Ldelema_Ref;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_MEMSET:
                    id = ReadyToRunHelper.MemSet;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_MEMZERO:
                    id = ReadyToRunHelper.MemZero;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_MEMCPY:
                    id = ReadyToRunHelper.MemCpy;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NATIVE_MEMSET:
                    id = ReadyToRunHelper.NativeMemSet;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE:
                    id = ReadyToRunHelper.GetRuntimeType;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_METHODDESC_TO_STUBRUNTIMEMETHOD:
                    id = ReadyToRunHelper.GetRuntimeMethodHandle;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD:
                    id = ReadyToRunHelper.GetRuntimeFieldHandle;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE:
                    id = ReadyToRunHelper.GetRuntimeTypeHandle;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOF_EXCEPTION:
                    id = ReadyToRunHelper.IsInstanceOfException;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_BOX:
                    id = ReadyToRunHelper.Box;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_BOX_NULLABLE:
                    id = ReadyToRunHelper.Box_Nullable;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_UNBOX:
                    id = ReadyToRunHelper.Unbox;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_UNBOX_TYPETEST:
                    id = ReadyToRunHelper.Unbox_TypeTest;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_UNBOX_NULLABLE:
                    id = ReadyToRunHelper.Unbox_Nullable;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEW_MDARR:
                    id = ReadyToRunHelper.NewMultiDimArr;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEW_MDARR_RARE:
                    id = ReadyToRunHelper.NewMultiDimArrRare;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEWFAST:
                    id = ReadyToRunHelper.NewObject;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEWSFAST:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewFast"u8));
                case CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_FINALIZE:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewFinalizable"u8));
                case CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_ALIGN8:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewFastAlign8"u8));
                case CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_ALIGN8_FINALIZE:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewFinalizableAlign8"u8));
                case CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_ALIGN8_VC:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewFastMisalign"u8));
                case CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_DIRECT:
                    id = ReadyToRunHelper.NewArray;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_PTR:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewPtrArrayFast"u8));
                case CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_ALIGN8:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewArrayFastAlign8"u8));
                case CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_VC:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpNewArrayFast"u8));

                case CorInfoHelpFunc.CORINFO_HELP_STACK_PROBE:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpStackProbe"u8));

                case CorInfoHelpFunc.CORINFO_HELP_POLL_GC:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpGcPoll"u8));

                case CorInfoHelpFunc.CORINFO_HELP_LMUL:
                    id = ReadyToRunHelper.LMul;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LMUL_OVF:
                    id = ReadyToRunHelper.LMulOfv;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ULMUL_OVF:
                    id = ReadyToRunHelper.ULMulOvf;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LDIV:
                    id = ReadyToRunHelper.LDiv;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LMOD:
                    id = ReadyToRunHelper.LMod;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ULDIV:
                    id = ReadyToRunHelper.ULDiv;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ULMOD:
                    id = ReadyToRunHelper.ULMod;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LLSH:
                    id = ReadyToRunHelper.LLsh;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LRSH:
                    id = ReadyToRunHelper.LRsh;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LRSZ:
                    id = ReadyToRunHelper.LRsz;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LNG2DBL:
                    id = ReadyToRunHelper.Lng2Dbl;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ULNG2DBL:
                    id = ReadyToRunHelper.ULng2Dbl;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_DIV:
                    id = ReadyToRunHelper.Div;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_MOD:
                    id = ReadyToRunHelper.Mod;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_UDIV:
                    id = ReadyToRunHelper.UDiv;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_UMOD:
                    id = ReadyToRunHelper.UMod;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_DBL2INT_OVF:
                    id = ReadyToRunHelper.Dbl2IntOvf;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_DBL2LNG:
                    id = ReadyToRunHelper.Dbl2Lng;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_DBL2LNG_OVF:
                    id = ReadyToRunHelper.Dbl2LngOvf;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_DBL2UINT_OVF:
                    id = ReadyToRunHelper.Dbl2UIntOvf;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_DBL2ULNG:
                    id = ReadyToRunHelper.Dbl2ULng;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_DBL2ULNG_OVF:
                    id = ReadyToRunHelper.Dbl2ULngOvf;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_LNG2FLT:
                    id = ReadyToRunHelper.Lng2Flt;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ULNG2FLT:
                    id = ReadyToRunHelper.ULng2Flt;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_FLTREM:
                    id = ReadyToRunHelper.FltRem;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_DBLREM:
                    id = ReadyToRunHelper.DblRem;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_JIT_PINVOKE_BEGIN:
                    id = ReadyToRunHelper.PInvokeBegin;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_JIT_PINVOKE_END:
                    id = ReadyToRunHelper.PInvokeEnd;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER:
                    id = ReadyToRunHelper.ReversePInvokeEnter;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT:
                    id = ReadyToRunHelper.ReversePInvokeExit;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_CHKCASTANY:
                case CorInfoHelpFunc.CORINFO_HELP_CHKCASTARRAY:
                    id = ReadyToRunHelper.CheckCastAny;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHKCASTINTERFACE:
                    id = ReadyToRunHelper.CheckCastInterface;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHKCASTCLASS:
                    id = ReadyToRunHelper.CheckCastClass;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHKCASTCLASS_SPECIAL:
                    id = ReadyToRunHelper.CheckCastClassSpecial;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY:
                case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFARRAY:
                    id = ReadyToRunHelper.CheckInstanceAny;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFINTERFACE:
                    id = ReadyToRunHelper.CheckInstanceInterface;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFCLASS:
                    id = ReadyToRunHelper.CheckInstanceClass;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_MON_ENTER:
                    id = ReadyToRunHelper.MonitorEnter;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_MON_EXIT:
                    id = ReadyToRunHelper.MonitorExit;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_ALLOC_CONTINUATION:
                    id = ReadyToRunHelper.AllocContinuation;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_GETSYNCFROMCLASSHANDLE:
                    return _compilation.NodeFactory.MethodEntrypoint(_compilation.NodeFactory.TypeSystemContext.GetCoreLibEntryPoint("System"u8, "Type"u8, "GetTypeFromMethodTable"u8, null));
                case CorInfoHelpFunc.CORINFO_HELP_GETCLASSFROMMETHODPARAM:
                    return _compilation.NodeFactory.MethodEntrypoint(_compilation.NodeFactory.TypeSystemContext.GetCoreLibEntryPoint("Internal.Runtime.CompilerHelpers"u8, "SharedCodeHelpers"u8, "GetClassHandleFromMethodParam"u8, null));

                case CorInfoHelpFunc.CORINFO_HELP_GVMLOOKUP_FOR_SLOT:
                    id = ReadyToRunHelper.GVMLookupForSlot;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_INTERFACELOOKUP_FOR_SLOT:
                    return _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("RhpResolveInterfaceMethodFast"u8));

                case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL:
                    id = ReadyToRunHelper.TypeHandleToRuntimeType;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_GETREFANY:
                    id = ReadyToRunHelper.GetRefAny;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL:
                    id = ReadyToRunHelper.TypeHandleToRuntimeTypeHandle;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_GETCURRENTMANAGEDTHREADID:
                    id = ReadyToRunHelper.GetCurrentManagedThreadId;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_VALIDATE_INDIRECT_CALL:
                    return _compilation.NodeFactory.ExternIndirectFunctionSymbol(new Utf8String("__guard_check_icall_fptr"u8));
                case CorInfoHelpFunc.CORINFO_HELP_DISPATCH_INDIRECT_CALL:
                    return _compilation.NodeFactory.ExternIndirectFunctionSymbol(new Utf8String("__guard_dispatch_icall_fptr"u8));

                default:
                    throw new NotImplementedException(ftnNum.ToString());
            }

            string mangledName;
            MethodDesc methodDesc;
            JitHelper.GetEntryPoint(_compilation.TypeSystemContext, id, out mangledName, out methodDesc);
            Debug.Assert(mangledName != null || methodDesc != null);

            ISymbolNode entryPoint;
            if (mangledName != null)
                entryPoint = _compilation.NodeFactory.ExternFunctionSymbol(new Utf8String(mangledName));
            else
                entryPoint = _compilation.NodeFactory.MethodEntrypoint(methodDesc);

            return entryPoint;
        }

        private void getFunctionEntryPoint(CORINFO_METHOD_STRUCT_* ftn, ref CORINFO_CONST_LOOKUP pResult, CORINFO_ACCESS_FLAGS accessFlags)
        {
            MethodDesc method = HandleToObject(ftn);

            // TODO: Implement MapMethodDeclToMethodImpl from CoreCLR
            if (method.IsVirtual &&
                method.OwningType is MetadataType mdType &&
                mdType.VirtualMethodImplsForType.Length > 0)
            {
                throw new NotImplementedException("getFunctionEntryPoint");
            }

            pResult = CreateConstLookupToSymbol(_compilation.NodeFactory.MethodEntrypoint(method));
        }

        private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* declaredCalleeHnd, CORINFO_METHOD_STRUCT_* exactCalleeHnd, bool fIsTailPrefix)
        {
            // Assume we can tail call unless proved otherwise
            bool result = true;

            if (!fIsTailPrefix)
            {
                MethodDesc caller = HandleToObject(callerHnd);

                if (caller.OwningType is EcmaType ecmaOwningType
                    && ecmaOwningType.Module.EntryPoint == caller)
                {
                    // Do not tailcall from the application entrypoint.
                    // We want Main to be visible in stack traces.
                    result = false;
                }

                if (caller.IsNoInlining)
                {
                    // Do not tailcall from methods that are marked as NoInlining (people often use no-inline
                    // to mean "I want to always see this method in stacktrace")
                    //
                    // NOTE: we don't have to handle NoOptimization here, because JIT is not expected
                    // to emit fast tail calls if optimizations are disabled.
                    result = false;
                }
            }

            return result;
        }

        private InfoAccessType constructStringLiteral(CORINFO_MODULE_STRUCT_* module, mdToken metaTok, ref void* ppValue)
        {
            MethodIL methodIL = (MethodIL)HandleToObject((void*)module);

            FrozenStringNode stringObject;
            if (metaTok == (mdToken)CorConstants.CorTokenType.mdtString)
            {
                stringObject = _compilation.NodeFactory.SerializedStringObject("");
            }
            else
            {
                object literal = methodIL.GetObject((int)metaTok);
                stringObject = _compilation.NodeFactory.SerializedStringObject((string)literal);
            }
            ppValue = (void*)ObjectToHandle(stringObject);
            return stringObject.RepresentsIndirectionCell ? InfoAccessType.IAT_PVALUE : InfoAccessType.IAT_VALUE;
        }

        private enum RhEHClauseKind
        {
            RH_EH_CLAUSE_TYPED = 0,
            RH_EH_CLAUSE_FAULT = 1,
            RH_EH_CLAUSE_FILTER = 2
        }

        private ObjectNode.ObjectData EncodeEHInfo()
        {
            var builder = default(ObjectDataBuilder);
            builder.RequireInitialAlignment(1);

            int totalClauses = _ehClauses.Length;

            // Count the number of special markers that will be needed
            for (int i = 1; i < _ehClauses.Length; i++)
            {
                ref CORINFO_EH_CLAUSE clause = ref _ehClauses[i];
                ref CORINFO_EH_CLAUSE previousClause = ref _ehClauses[i - 1];

                if ((previousClause.TryOffset == clause.TryOffset) &&
                    (previousClause.TryLength == clause.TryLength) &&
                    ((clause.Flags & CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_SAMETRY) == 0))
                {
                    totalClauses++;
                }
            }

            builder.EmitCompressedUInt((uint)totalClauses);

            for (int i = 0; i < _ehClauses.Length; i++)
            {
                ref CORINFO_EH_CLAUSE clause = ref _ehClauses[i];

                if (i > 0)
                {
                    ref CORINFO_EH_CLAUSE previousClause = ref _ehClauses[i - 1];

                    // If the previous clause has same try offset and length as the current clause,
                    // but belongs to a different try block (CORINFO_EH_CLAUSE_SAMETRY is not set),
                    // emit a special marker to allow runtime distinguish this case.
                    if ((previousClause.TryOffset == clause.TryOffset) &&
                        (previousClause.TryLength == clause.TryLength) &&
                        ((clause.Flags & CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_SAMETRY) == 0))
                    {
                        builder.EmitCompressedUInt(0);
                        builder.EmitCompressedUInt((uint)RhEHClauseKind.RH_EH_CLAUSE_FAULT);
                        builder.EmitCompressedUInt(0);
                    }
                }

                RhEHClauseKind clauseKind;

                if (((clause.Flags & CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_FAULT) != 0) ||
                    ((clause.Flags & CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_FINALLY) != 0))
                {
                    clauseKind = RhEHClauseKind.RH_EH_CLAUSE_FAULT;
                }
                else
                if ((clause.Flags & CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_FILTER) != 0)
                {
                    clauseKind = RhEHClauseKind.RH_EH_CLAUSE_FILTER;
                }
                else
                {
                    clauseKind = RhEHClauseKind.RH_EH_CLAUSE_TYPED;
                }

                builder.EmitCompressedUInt((uint)clause.TryOffset);

                // clause.TryLength returned by the JIT is actually end offset...
                // https://github.com/dotnet/runtime/issues/5282
                int tryLength = (int)clause.TryLength - (int)clause.TryOffset;
                builder.EmitCompressedUInt((uint)((tryLength << 2) | (int)clauseKind));

                switch (clauseKind)
                {
                    case RhEHClauseKind.RH_EH_CLAUSE_TYPED:
                        {
                            builder.EmitCompressedUInt(clause.HandlerOffset);

                            var methodIL = (MethodIL)HandleToObject((void*)_methodScope);
                            var type = (TypeDesc)methodIL.GetObject((int)clause.ClassTokenOrOffset);

                            var typeSymbol = _compilation.NecessaryTypeSymbolIfPossible(type);

                            RelocType rel = (_compilation.NodeFactory.Target.IsWindows) ?
                                RelocType.IMAGE_REL_BASED_ABSOLUTE :
                                RelocType.IMAGE_REL_BASED_RELPTR32;

                            builder.EmitReloc(typeSymbol, rel);
                        }
                        break;
                    case RhEHClauseKind.RH_EH_CLAUSE_FAULT:
                        builder.EmitCompressedUInt(clause.HandlerOffset);
                        break;
                    case RhEHClauseKind.RH_EH_CLAUSE_FILTER:
                        builder.EmitCompressedUInt(clause.HandlerOffset);
                        builder.EmitCompressedUInt(clause.ClassTokenOrOffset);
                        break;
                }
            }

            return builder.ToObjectData();
        }

        private void setVars(CORINFO_METHOD_STRUCT_* ftn, uint cVars, NativeVarInfo* vars)
        {
            var methodIL = (MethodIL)HandleToObject((void*)_methodScope);

            MethodSignature sig = methodIL.OwningMethod.Signature;
            int numLocals = methodIL.GetLocals().Length;

            ArrayBuilder<DebugVarRangeInfo>[] debugVarInfoBuilders = new ArrayBuilder<DebugVarRangeInfo>[(sig.IsStatic ? 0 : 1) + sig.Length + numLocals];

            for (uint i = 0; i < cVars; i++)
            {
                uint varNumber = vars[i].varNumber;
                if (varNumber < debugVarInfoBuilders.Length)
                    debugVarInfoBuilders[varNumber].Add(new DebugVarRangeInfo(vars[i].startOffset, vars[i].endOffset, vars[i].varLoc));
            }

            var debugVarInfos = default(ArrayBuilder<DebugVarInfo>);
            for (uint i = 0; i < debugVarInfoBuilders.Length; i++)
            {
                if (debugVarInfoBuilders[i].Count > 0)
                {
                    debugVarInfos.Add(new DebugVarInfo(i, debugVarInfoBuilders[i].ToArray()));
                }
            }

            _debugVarInfos = debugVarInfos.ToArray();

            // JIT gave the ownership of this to us, so need to free this.
            freeArray(vars);
        }

        /// <summary>
        /// Create a DebugLocInfo which is a table from native offset to source line.
        /// using native to il offset (pMap) and il to source line (_sequencePoints).
        /// </summary>
        private void setBoundaries(CORINFO_METHOD_STRUCT_* ftn, uint cMap, OffsetMapping* pMap)
        {
            Debug.Assert(_debugLocInfos == null);

            int largestILOffset = 0; // All epiloges point to the largest IL offset.
            for (int i = 0; i < cMap; i++)
            {
                OffsetMapping nativeToILInfo = pMap[i];
                int currentILOffset = (int)nativeToILInfo.ilOffset;
                if (currentILOffset > largestILOffset) // Special offsets are negative.
                {
                    largestILOffset = currentILOffset;
                }
            }

            ArrayBuilder<DebugLocInfo> debugLocInfos = default(ArrayBuilder<DebugLocInfo>);
            for (int i = 0; i < cMap; i++)
            {
                OffsetMapping* nativeToILInfo = &pMap[i];
                int ilOffset = (int)nativeToILInfo->ilOffset;
                switch (ilOffset)
                {
                    case (int)MappingTypes.PROLOG:
                        ilOffset = 0;
                        break;
                    case (int)MappingTypes.EPILOG:
                        ilOffset = largestILOffset;
                        break;
                    case (int)MappingTypes.NO_MAPPING:
                        continue;
                }
                // Ignore these; see WalkILOffsetsCallback in src/coreclr/vm/debugdebugger.cpp.
                if (nativeToILInfo->source == SourceTypes.CALL_INSTRUCTION)
                    continue;

                debugLocInfos.Add(new DebugLocInfo((int)nativeToILInfo->nativeOffset, ilOffset));
            }

            if (debugLocInfos.Count > 0)
            {
                _debugLocInfos = debugLocInfos.ToArray();
            }

            freeArray(pMap);
        }

        private void SetDebugInformation(IMethodNode methodCodeNodeNeedingCode, MethodIL methodIL)
        {
            _debugInfo = _compilation.GetDebugInfo(methodIL);
        }

        private ISymbolNode GetGenericLookupHelper(CORINFO_RUNTIME_LOOKUP_KIND runtimeLookupKind, ReadyToRunHelperId helperId, MethodDesc callerHandle, object helperArgument)
        {
            if (runtimeLookupKind == CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_THISOBJ
                || runtimeLookupKind == CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_CLASSPARAM)
            {
                return _compilation.NodeFactory.ReadyToRunHelperFromTypeLookup(helperId, helperArgument, callerHandle.OwningType);
            }

            Debug.Assert(runtimeLookupKind == CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_METHODPARAM);
            return _compilation.NodeFactory.ReadyToRunHelperFromDictionaryLookup(helperId, helperArgument, callerHandle);
        }

        private CorInfoHelpFunc getCastingHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool fThrowing)
        {
            TypeDesc type = HandleToObject(pResolvedToken.hClass);

            CorInfoHelpFunc helper;

            if (type.IsCanonicalDefinitionType(CanonicalFormKind.Any))
            {
                // In shared code just use the catch-all helper for type variables, as the same
                // code may be used for interface/array/class instantiations
                //
                // We may be able to take advantage of constraints to select a specialized helper.
                // This optimizations does not seem to be warranted at the moment.
                helper = CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY;
            }
            else if (type.HasVariance
                // The runtime considers generic interfaces that can be implemented by arrays variant
                || _compilation.TypeSystemContext.IsGenericArrayInterfaceType(type))
            {
                helper = CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY;
            }
            else if (type.IsInterface)
            {
                // If it is a non-variant interface, use the fast interface helper
                helper = CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFINTERFACE;
            }
            else if (type.IsArray)
            {
                // If it is an array, use the fast array helper
                helper = CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFARRAY;
            }
            else if (type.IsDefType)
            {
                helper = CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFCLASS;
            }
            else
            {
                // Otherwise, use the slow helper
                helper = CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY;
            }

            if (fThrowing)
            {
                int delta = CorInfoHelpFunc.CORINFO_HELP_CHKCASTANY - CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY;

                Debug.Assert(CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFINTERFACE + delta
                    == CorInfoHelpFunc.CORINFO_HELP_CHKCASTINTERFACE);
                Debug.Assert(CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFARRAY + delta
                    == CorInfoHelpFunc.CORINFO_HELP_CHKCASTARRAY);
                Debug.Assert(CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFCLASS + delta
                    == CorInfoHelpFunc.CORINFO_HELP_CHKCASTCLASS);

                helper += delta;
            }

            return helper;
        }

        private CorInfoHelpFunc getNewHelper(CORINFO_CLASS_STRUCT_* classHandle, ref bool pHasSideEffects)
        {
            TypeDesc type = HandleToObject(classHandle);

            Debug.Assert(!type.IsString && !type.IsArray && !type.IsCanonicalDefinitionType(CanonicalFormKind.Any));

            pHasSideEffects = type.HasFinalizer;

            if (type.RequiresAlign8())
            {
                if (type.HasFinalizer)
                    return CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_ALIGN8_FINALIZE;

                if (type.IsValueType)
                    return CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_ALIGN8_VC;

                return CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_ALIGN8;
            }

            if (type.HasFinalizer)
                return CorInfoHelpFunc.CORINFO_HELP_NEWSFAST_FINALIZE;

            return CorInfoHelpFunc.CORINFO_HELP_NEWSFAST;
        }

        private CorInfoHelpFunc getNewArrHelper(CORINFO_CLASS_STRUCT_* arrayCls)
        {
            TypeDesc type = HandleToObject(arrayCls);

            Debug.Assert(type.IsArray);

            TypeDesc elementType = ((ArrayType)type).ElementType;

            if (elementType.GetElementSize().AsInt == _compilation.TypeSystemContext.Target.PointerSize)
                return CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_PTR;

            if (type.RequiresAlign8())
                return CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_ALIGN8;

            return CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_VC;
        }

        private IMethodNode GetMethodEntrypoint(CORINFO_MODULE_STRUCT_* pScope, MethodDesc method)
        {
            bool isUnboxingThunk = method.IsUnboxingThunk();
            if (isUnboxingThunk)
            {
                method = method.GetUnboxedMethod();
            }

            if (method.HasInstantiation || method.OwningType.HasInstantiation)
            {
                MethodIL methodIL = (MethodIL)HandleToObject((void*)pScope);
                _compilation.DetectGenericCycles(methodIL.OwningMethod, method);
            }

            return _compilation.NodeFactory.MethodEntrypointOrTentativeMethod(method, isUnboxingThunk);
        }

        private static bool IsTypeSpecForTypicalInstantiation(TypeDesc t)
        {
            Instantiation inst = t.Instantiation;
            for (int i = 0; i < inst.Length; i++)
            {
                var arg = inst[i] as SignatureTypeVariable;
                if (arg == null || arg.Index != i)
                    return false;
            }
            return true;
        }

        private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_CALLINFO_FLAGS flags, CORINFO_CALL_INFO* pResult)
        {
#if DEBUG
            // In debug, write some bogus data to the struct to ensure we have filled everything
            // properly.
            NativeMemory.Fill(pResult, (nuint)sizeof(CORINFO_CALL_INFO), 0xcc);
#endif
            MethodDesc method = HandleToObject(pResolvedToken.hMethod);

            // Spec says that a callvirt lookup ignores static methods. Since static methods
            // can't have the exact same signature as instance methods, a lookup that found
            // a static method would have never found an instance method.
            if (method.Signature.IsStatic && (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0)
            {
                throw new BadImageFormatException();
            }

            TypeDesc exactType = HandleToObject(pResolvedToken.hClass);

            TypeDesc constrainedType = null;
            if (pConstrainedResolvedToken != null)
            {
                constrainedType = HandleToObject(pConstrainedResolvedToken->hClass);
            }

            bool resolvedConstraint = false;
            bool forceUseRuntimeLookup = false;
            bool targetIsFatFunctionPointer = false;
            bool useFatCallTransform = false;
            DefaultInterfaceMethodResolution staticResolution = default;

            MethodDesc methodAfterConstraintResolution = method;
            if (constrainedType == null)
            {
                pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM;
            }
            else
            {
                // We have a "constrained." call.  Try a partial resolve of the constraint call.  Note that this
                // will not necessarily resolve the call exactly, since we might be compiling
                // shared generic code - it may just resolve it to a candidate suitable for
                // JIT compilation, and require a runtime lookup for the actual code pointer
                // to call.
                if (constrainedType.IsEnum)
                {
                    // Optimize constrained calls to enum's GetHashCode method. TryResolveConstraintMethodApprox would return
                    // null since the virtual method resolves to System.Enum's implementation and that's a reference type.
                    // We can't do this for any other method since ToString and Equals have different semantics for enums
                    // and their underlying type.
                    if (method.OwningType.IsObject && method.Name.SequenceEqual("GetHashCode"u8))
                    {
                        constrainedType = constrainedType.UnderlyingType;
                    }
                }

                MethodDesc directMethod = constrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(exactType, method, out forceUseRuntimeLookup, ref staticResolution);
                if (directMethod != null)
                {
                    // Either
                    //    1. no constraint resolution at compile time (!directMethod)
                    // OR 2. no code sharing lookup in call
                    // OR 3. we have resolved to an instantiating stub

                    methodAfterConstraintResolution = directMethod;

                    Debug.Assert(!methodAfterConstraintResolution.OwningType.IsInterface
                        || methodAfterConstraintResolution.Signature.IsStatic);
                    resolvedConstraint = true;
                    pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM;

                    exactType = directMethod.OwningType;

                    _compilation.NodeFactory.MetadataManager.NoteOverridingMethod(method, directMethod);
                }
                else if (method.Signature.IsStatic)
                {
                    Debug.Assert(method.OwningType.IsInterface);
                    exactType = constrainedType;
                }
                else if (constrainedType.IsValueType)
                {
                    pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS;
                }
                else
                {
                    pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_DEREF_THIS;
                }
            }

            MethodDesc targetMethod = methodAfterConstraintResolution;

            //
            // Initialize callee context used for inlining and instantiation arguments
            //


            if (targetMethod.HasInstantiation)
            {
                pResult->contextHandle = contextFromMethod(targetMethod);
                pResult->exactContextNeedsRuntimeLookup = targetMethod.IsSharedByGenericInstantiations;
            }
            else
            {
                pResult->contextHandle = contextFromType(exactType);
                pResult->exactContextNeedsRuntimeLookup = exactType.IsCanonicalSubtype(CanonicalFormKind.Any);

                // Use main method as the context as long as the methods are called on the same type
                if (pResult->exactContextNeedsRuntimeLookup &&
                    pResolvedToken.tokenContext == contextFromMethodBeingCompiled() &&
                    constrainedType == null &&
                    exactType == MethodBeingCompiled.OwningType &&
                    // But don't allow inlining into generic methods since the generic context won't be the same.
                    // The scanner won't be able to predict such inlinig. See https://github.com/dotnet/runtimelab/pull/489
                    !MethodBeingCompiled.HasInstantiation)
                {
                    var methodIL = (MethodIL)HandleToObject((void*)pResolvedToken.tokenScope);
                    var rawMethod = (MethodDesc)methodIL.GetMethodILDefinition().GetObject((int)pResolvedToken.token);
                    if (IsTypeSpecForTypicalInstantiation(rawMethod.OwningType))
                    {
                        pResult->contextHandle = contextFromMethodBeingCompiled();
                    }
                }
            }

            //
            // Determine whether to perform direct call
            //

            bool directCall = false;
            bool resolvedCallVirt = false;

            if (targetMethod.Signature.IsStatic)
            {
                if (constrainedType != null && (!resolvedConstraint || forceUseRuntimeLookup))
                {
                    // Constrained call to static virtual interface method we didn't resolve statically
                    Debug.Assert(targetMethod.IsVirtual && targetMethod.OwningType.IsInterface);
                }
                else
                {
                    // Static methods are always direct calls
                    directCall = true;
                }
            }
            else if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) == 0 || resolvedConstraint)
            {
                directCall = true;
            }
            else
            {
                // We can devirtualize the callvirt if the method is not virtual to begin with
                bool canDevirt = targetMethod.IsCallEffectivelyDirect();

                // We might be able to devirt based on whole program view
                if (!canDevirt
                    // Do not devirt if devirtualization would need a generic dictionary entry that we didn't predict
                    // during scanning (i.e. compiling a shared method body and we need to call another shared body
                    // with a method generic dictionary argument).
                    && (!pResult->exactContextNeedsRuntimeLookup || !targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg()))
                {
                    canDevirt = _compilation.IsEffectivelySealed(targetMethod);
                }

                if (canDevirt)
                {
                    resolvedCallVirt = true;
                    directCall = true;
                }
            }

            pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup = false;

            bool allowInstParam = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_ALLOWINSTPARAM) != 0;

            if (directCall && targetMethod.IsAbstract)
            {
                ThrowHelper.ThrowBadImageFormatException();
            }

            if (directCall && resolvedConstraint && (pResult->exactContextNeedsRuntimeLookup || forceUseRuntimeLookup))
            {
                // We want to do a direct call to a shared method on a type. We need to provide
                // a generic context, but the JitInterface doesn't provide a way for us to do it from here.
                // So we do the next best thing and ask RyuJIT to look up a fat pointer.

                pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER;
                pResult->codePointerOrStubLookup.constLookup.accessType = InfoAccessType.IAT_VALUE;
                pResult->nullInstanceCheck = !targetMethod.Signature.IsStatic;

                TypeDesc runtimeDeterminedConstrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *pConstrainedResolvedToken);

                object targetOfLookup;
                ReadyToRunHelperId lookupHelper;
                if (forceUseRuntimeLookup)
                {
                    MethodDesc runtimeDeterminedInterfaceMethod = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
                    targetOfLookup = new ConstrainedCallInfo(runtimeDeterminedConstrainedType, runtimeDeterminedInterfaceMethod);
                    lookupHelper = ReadyToRunHelperId.ConstrainedDirectCall;
                }
                else
                {
                    // We have the canonical version of the method - find the runtime determined version.
                    // For now simplify it by assuming the resolved method is on the same type as the constraint.
                    Debug.Assert(targetMethod.OwningType.GetTypeDefinition() == constrainedType.GetTypeDefinition());

                    MethodDesc lookupMethod;
                    if (runtimeDeterminedConstrainedType.IsRuntimeDeterminedType)
                        lookupMethod = _compilation.TypeSystemContext.GetMethodForRuntimeDeterminedType(targetMethod.GetTypicalMethodDefinition(), (RuntimeDeterminedType)runtimeDeterminedConstrainedType);
                    else if (runtimeDeterminedConstrainedType.HasInstantiation)
                        lookupMethod = _compilation.TypeSystemContext.GetMethodForInstantiatedType(targetMethod.GetTypicalMethodDefinition(), (InstantiatedType)runtimeDeterminedConstrainedType);
                    else
                        lookupMethod = targetMethod.GetMethodDefinition();
                    if (lookupMethod.HasInstantiation)
                    {
                        var methodToGetInstantiation = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
                        lookupMethod = lookupMethod.MakeInstantiatedMethod(methodToGetInstantiation.Instantiation);
                    }
                    Debug.Assert(lookupMethod.GetCanonMethodTarget(CanonicalFormKind.Specific) == targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific));

                    targetOfLookup = lookupMethod;
                    lookupHelper = ReadyToRunHelperId.MethodEntry;
                }

                ComputeLookup(ref pResolvedToken,
                    targetOfLookup,
                    lookupHelper,
                    HandleToObject(callerHandle),
                    ref pResult->codePointerOrStubLookup);

                targetIsFatFunctionPointer = true;
                useFatCallTransform = true;
            }
            else if (directCall && !allowInstParam && targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstArg())
            {
                // JIT needs a single address to call this method but the method needs a hidden argument.
                // We need a fat function pointer for this that captures both things.
                targetIsFatFunctionPointer = true;

                // JIT won't expect fat function pointers unless this is e.g. delegate creation
                Debug.Assert((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0);

                pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER;

                if (pResult->exactContextNeedsRuntimeLookup)
                {
                    MethodDesc caller = HandleToObject(callerHandle);

                    pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup = true;
                    pResult->codePointerOrStubLookup.runtimeLookup.indirections = CORINFO.USEHELPER;
                    pResult->codePointerOrStubLookup.runtimeLookup.helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GENERIC_HANDLE;
                    pResult->codePointerOrStubLookup.lookupKind.runtimeLookupKind = GetGenericRuntimeLookupKind(caller);
                    object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
                    ISymbolNode helper = GetGenericLookupHelper(pResult->codePointerOrStubLookup.lookupKind.runtimeLookupKind, ReadyToRunHelperId.MethodEntry, caller, helperArg);
                    pResult->codePointerOrStubLookup.runtimeLookup.helperEntryPoint = CreateConstLookupToSymbol(helper);
                }
                else
                {
                    pResult->codePointerOrStubLookup.constLookup =
                        CreateConstLookupToSymbol(_compilation.NodeFactory.FatFunctionPointer(targetMethod));
                }
            }
            else if (directCall)
            {
                bool referencingArrayAddressMethod = false;

                if (targetMethod.IsIntrinsic)
                {
                    // If this is an intrinsic method with a callsite-specific expansion, this will replace
                    // the method with a method the intrinsic expands into. If it's not the special intrinsic,
                    // method stays unchanged.
                    var methodIL = (MethodIL)HandleToObject((void*)pResolvedToken.tokenScope);
                    targetMethod = _compilation.ExpandIntrinsicForCallsite(targetMethod, methodIL.OwningMethod);

                    // For multidim array Address method, we pretend the method requires a hidden instantiation argument
                    // (even though it doesn't need one). We'll actually swap the method out for a differnt one with
                    // a matching calling convention later. See ArrayMethod for a description.
                    referencingArrayAddressMethod = targetMethod.IsArrayAddressMethod();
                }

                pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL;

                TypeDesc owningType = targetMethod.OwningType;
                if (owningType.IsString && targetMethod.IsConstructor)
                {
                    // Calling a string constructor doesn't call the actual constructor.
                    pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(
                        _compilation.NodeFactory.StringAllocator(targetMethod)
                        );
                }
                else if (owningType.IsArray && targetMethod.IsConstructor)
                {
                    // Constructors on arrays are special and don't actually have entrypoints.
                    // That would be fine by itself and wouldn't need special casing. But
                    // constructors on SzArray have a weird property that causes them not to have canonical forms.
                    // int[][] has a .ctor(int32,int32) to construct the jagged array in one go, but its canonical
                    // form of __Canon[] doesn't have the two-parameter constructor. The canonical form would need
                    // to have an unlimited number of constructors to cover stuff like "int[][][][][][]..."
                    pResult->codePointerOrStubLookup.constLookup = default;
                }
                else if (pResult->exactContextNeedsRuntimeLookup)
                {
                    // Nothing to do... The generic handle lookup gets embedded in to the codegen
                    // during the jitting of the call.
                    // (Note: The generic lookup in R2R is performed by a call to a helper at runtime, not by
                    // codegen emitted at crossgen time)

                    targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);

                    Debug.Assert(!forceUseRuntimeLookup);
                    pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(
                        GetMethodEntrypoint(pResolvedToken.tokenScope, targetMethod)
                        );
                }
                else
                {
                    MethodDesc concreteMethod = targetMethod;
                    targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);

                    ISymbolNode instParam = null;

                    if (targetMethod.RequiresInstMethodDescArg())
                    {
                        instParam = _compilation.NodeFactory.MethodGenericDictionary(concreteMethod);
                    }
                    else if (targetMethod.RequiresInstMethodTableArg() || referencingArrayAddressMethod)
                    {
                        // Ask for a constructed type symbol because we need the vtable to get to the dictionary
                        instParam = _compilation.NodeFactory.ConstructedTypeSymbol(concreteMethod.OwningType);
                    }

                    if (instParam != null)
                    {
                        pResult->instParamLookup = CreateConstLookupToSymbol(instParam);
                    }

                    pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(
                        GetMethodEntrypoint(pResolvedToken.tokenScope, targetMethod)
                        );
                }

                pResult->nullInstanceCheck = resolvedCallVirt;
            }
            else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction)
            {
                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && constrainedType != null);

                ThrowHelper.ThrowBadImageFormatException();
            }
            else if (targetMethod.Signature.IsStatic)
            {
                // This should be an unresolved static virtual interface method call. Other static methods should
                // have been handled as a directCall above.
                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && constrainedType != null);

                pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER;

                TypeDesc runtimeDeterminedConstrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *pConstrainedResolvedToken);
                MethodDesc runtimeDeterminedInterfaceMethod = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);

                var constrainedCallInfo = new ConstrainedCallInfo(runtimeDeterminedConstrainedType, runtimeDeterminedInterfaceMethod);
                var constrainedHelperId = ReadyToRunHelperId.ConstrainedDirectCall;

                // Constant lookup doesn't make sense and we don't implement it. If we need constant lookup,
                // the method wasn't implemented. Don't crash on it.
                if (!_compilation.NeedsRuntimeLookup(constrainedHelperId, constrainedCallInfo))
                    ThrowHelper.ThrowTypeLoadException(constrainedType);

                ComputeLookup(ref pResolvedToken,
                    constrainedCallInfo,
                    constrainedHelperId,
                    HandleToObject(callerHandle),
                    ref pResult->codePointerOrStubLookup);

                targetIsFatFunctionPointer = true;
                useFatCallTransform = true;
                pResult->nullInstanceCheck = false;
            }
            else if (targetMethod.HasInstantiation)
            {
                // Generic virtual method call support
                pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_LDVIRTFTN;
                pResult->codePointerOrStubLookup.constLookup.accessType = InfoAccessType.IAT_VALUE;
                pResult->nullInstanceCheck = true;

                MethodDesc targetOfLookup = _compilation.GetTargetOfGenericVirtualMethodCall((MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken));

                _compilation.DetectGenericCycles(
                    ((MethodILScope)HandleToObject((void*)pResolvedToken.tokenScope)).OwningMethod,
                    targetOfLookup.GetCanonMethodTarget(CanonicalFormKind.Specific));

                ComputeLookup(ref pResolvedToken,
                    targetOfLookup,
                    ReadyToRunHelperId.MethodHandle,
                    HandleToObject(callerHandle),
                    ref pResult->codePointerOrStubLookup);

                // RyuJIT will assert if we report CORINFO_CALLCONV_PARAMTYPE for a result of a ldvirtftn
                // We don't need an instantiation parameter, so let's just not report it. Might be nice to
                // move that assert to some place later though.
                targetIsFatFunctionPointer = true;
            }
            else if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) == 0
                && targetMethod.OwningType.IsInterface)
            {
                pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_STUB;

                if (pResult->exactContextNeedsRuntimeLookup)
                {
                    ComputeLookup(ref pResolvedToken,
                        GetRuntimeDeterminedObjectForToken(ref pResolvedToken),
                        ReadyToRunHelperId.VirtualDispatchCell,
                        HandleToObject(callerHandle),
                        ref pResult->codePointerOrStubLookup);
                    Debug.Assert(pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup);
                }
                else
                {
                    pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup = false;
                    pResult->codePointerOrStubLookup.constLookup.accessType = InfoAccessType.IAT_PVALUE;
#pragma warning disable SA1001, SA1113, SA1115 // Commas should be spaced correctly
                    pResult->codePointerOrStubLookup.constLookup.addr = (void*)ObjectToHandle(
                        _compilation.NodeFactory.InterfaceDispatchCell(targetMethod
#if !SUPPORT_JIT
                        , _methodCodeNode
#endif
                        ));
#pragma warning restore SA1001, SA1113, SA1115 // Commas should be spaced correctly
                }

                pResult->nullInstanceCheck = false;
            }
            else if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) == 0)
            {
                pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_VTABLE;
                pResult->nullInstanceCheck = true;
            }
            else
            {
                pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_LDVIRTFTN;

                // If this is a non-interface call, we actually don't need a runtime lookup to find the target.
                // We don't even need to keep track of the runtime-determined method being called because the system ensures
                // that if e.g. Foo<__Canon>.GetHashCode is needed and we're generating a dictionary for Foo<string>,
                // Foo<string>.GetHashCode is needed too.
                if (pResult->exactContextNeedsRuntimeLookup && targetMethod.OwningType.IsInterface)
                {
                    // We need JitInterface changes to fully support this.
                    // If this is LDVIRTFTN of an interface method that is part of a verifiable delegate creation sequence,
                    // RyuJIT is not going to use this value.
                    pResult->exactContextNeedsRuntimeLookup = false;
                    pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("NYI_LDVIRTFTN"u8)));
                }
                else
                {
                    pResult->exactContextNeedsRuntimeLookup = false;
                    targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);

                    // Get the slot defining method to make sure our virtual method use tracking gets this right.
                    // For normal C# code the targetMethod will always be newslot.
                    MethodDesc slotDefiningMethod = targetMethod.IsNewSlot ?
                        targetMethod : MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(targetMethod);

                    pResult->codePointerOrStubLookup.constLookup =
                        CreateConstLookupToSymbol(
                            _compilation.NodeFactory.ReadyToRunHelper(ReadyToRunHelperId.ResolveVirtualFunction, slotDefiningMethod));
                }

                // The current NativeAOT ReadyToRun helpers do not handle null thisptr - ask the JIT to emit explicit null checks
                // TODO: Optimize this
                pResult->nullInstanceCheck = true;
            }

            pResult->hMethod = ObjectToHandle(targetMethod);

            pResult->accessAllowed = CorInfoIsAccessAllowedResult.CORINFO_ACCESS_ALLOWED;

            // We're pretty much done at this point.  Let's grab the rest of the information that the jit is going to
            // need.
            pResult->classFlags = getClassAttribsInternal(targetMethod.OwningType);

            pResult->methodFlags = getMethodAttribsInternal(targetMethod);

            targetIsFatFunctionPointer |= (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0 && !(pResult->kind == CORINFO_CALL_KIND.CORINFO_CALL);

            Get_CORINFO_SIG_INFO(targetMethod, &pResult->sig, scope: null, targetIsFatFunctionPointer);
            if (useFatCallTransform)
            {
                pResult->sig.flags |= CorInfoSigInfoFlags.CORINFO_SIGFLAG_FAT_CALL;
            }

            pResult->_wrapperDelegateInvoke = 0;
        }

        private CORINFO_CLASS_STRUCT_* embedClassHandle(CORINFO_CLASS_STRUCT_* handle, ref void* ppIndirection)
        {
            TypeDesc type = HandleToObject(handle);
            ISymbolNode typeHandleSymbol = _compilation.NecessaryTypeSymbolIfPossible(type);
            CORINFO_CLASS_STRUCT_* result = (CORINFO_CLASS_STRUCT_*)ObjectToHandle(typeHandleSymbol);

            if (typeHandleSymbol.RepresentsIndirectionCell)
            {
                ppIndirection = result;
                return null;
            }
            else
            {
                ppIndirection = null;
                return result;
            }
        }

        private void embedGenericHandle(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool fEmbedParent, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_GENERICHANDLE_RESULT pResult)
        {
#if DEBUG
            // In debug, write some bogus data to the struct to ensure we have filled everything
            // properly.
            fixed (CORINFO_GENERICHANDLE_RESULT* tmp = &pResult)
                NativeMemory.Fill(tmp, (nuint)sizeof(CORINFO_GENERICHANDLE_RESULT), 0xcc);
#endif
            ReadyToRunHelperId helperId = ReadyToRunHelperId.Invalid;
            object target = null;

            if (!fEmbedParent && pResolvedToken.hMethod != null)
            {
                MethodDesc md = HandleToObject(pResolvedToken.hMethod);
                TypeDesc td = HandleToObject(pResolvedToken.hClass);

                pResult.handleType = CorInfoGenericHandleType.CORINFO_HANDLETYPE_METHOD;

                Debug.Assert(md.OwningType == td);

                pResult.compileTimeHandle = (CORINFO_GENERIC_STRUCT_*)ObjectToHandle(md);

                if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Ldtoken)
                    helperId = ReadyToRunHelperId.MethodHandle;
                else
                {
                    Debug.Assert((pResolvedToken.tokenType & CorInfoTokenKind.CORINFO_TOKENKIND_Method) != 0);
                    helperId = ReadyToRunHelperId.MethodDictionary;
                }

                target = GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
            }
            else if (!fEmbedParent && pResolvedToken.hField != null)
            {
                FieldDesc fd = HandleToObject(pResolvedToken.hField);
                TypeDesc td = HandleToObject(pResolvedToken.hClass);

                pResult.handleType = CorInfoGenericHandleType.CORINFO_HANDLETYPE_FIELD;
                pResult.compileTimeHandle = (CORINFO_GENERIC_STRUCT_*)pResolvedToken.hField;

                Debug.Assert(pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Ldtoken);
                helperId = ReadyToRunHelperId.FieldHandle;
                target = GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
            }
            else
            {
                TypeDesc td = HandleToObject(pResolvedToken.hClass);

                pResult.handleType = CorInfoGenericHandleType.CORINFO_HANDLETYPE_CLASS;
                pResult.compileTimeHandle = (CORINFO_GENERIC_STRUCT_*)pResolvedToken.hClass;

                object obj = GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
                target = obj as TypeDesc;
                if (target == null)
                {
                    Debug.Assert(fEmbedParent);

                    if (obj is MethodDesc objAsMethod)
                    {
                        target = objAsMethod.OwningType;
                    }
                    else
                    {
                        Debug.Assert(obj is FieldDesc);
                        target = ((FieldDesc)obj).OwningType;
                    }
                }

                if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_NewObj
                        || pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Newarr
                        || pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Box
                        || pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Constrained
                        || pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Method)
                {
                    helperId = ReadyToRunHelperId.TypeHandle;
                }
                else if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Casting)
                {
                    helperId = ReadyToRunHelperId.TypeHandleForCasting;
                }
                else if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Ldtoken)
                {
                    if (((TypeDesc)target).IsRuntimeDeterminedSubtype)
                    {
                        // If we're in a generic context and the type is canonical, Compilation cannot give
                        // us a good answer on what handle kind to use because we don't track canonical forms.
                        // If we already have a fixed generic dictionary layout (a build with a scanner pass),
                        // ask for NecessaryTypeSymbol - it will get upgraded as needed.
                        // If we don't have a fixed dictionary layout, ask for a full type symbol because that's
                        // always correct.
                        MethodDesc callerContextMethod = HandleToObject(callerHandle);
                        DictionaryLayoutNode contextLayout = _compilation.NodeFactory.GenericDictionaryLayout(
                            callerContextMethod.RequiresInstMethodDescArg() ? callerContextMethod : callerContextMethod.OwningType);
                        if (contextLayout.HasFixedSlots)
                            helperId = ReadyToRunHelperId.NecessaryTypeHandle;
                        else
                            helperId = ReadyToRunHelperId.TypeHandle;
                    }
                    else
                    {
                        helperId = _compilation.GetLdTokenHelperForType(td);
                    }
                }
                else
                {
                    helperId = ReadyToRunHelperId.NecessaryTypeHandle;
                }
            }

            Debug.Assert(pResult.compileTimeHandle != null);

            ComputeLookup(ref pResolvedToken, target, helperId, HandleToObject(callerHandle), ref pResult.lookup);
        }

        private CORINFO_METHOD_STRUCT_* embedMethodHandle(CORINFO_METHOD_STRUCT_* handle, ref void* ppIndirection)
        {
            MethodDesc method = HandleToObject(handle);
            RuntimeMethodHandleNode methodHandleSymbol = _compilation.NodeFactory.RuntimeMethodHandle(method);
            CORINFO_METHOD_STRUCT_* result = (CORINFO_METHOD_STRUCT_*)ObjectToHandle(methodHandleSymbol);

            if (methodHandleSymbol.RepresentsIndirectionCell)
            {
                ppIndirection = result;
                return null;
            }
            else
            {
                ppIndirection = null;
                return result;
            }
        }

        private void getMethodVTableOffset(CORINFO_METHOD_STRUCT_* method, ref uint offsetOfIndirection, ref uint offsetAfterIndirection, ref bool isRelative)
        {
            MethodDesc methodDesc = HandleToObject(method);
            int pointerSize = _compilation.TypeSystemContext.Target.PointerSize;
            offsetOfIndirection = (uint)CORINFO_VIRTUALCALL_NO_CHUNK.Value;
            isRelative = false;

            // Normalize to the slot defining method. We don't have slot information for the overrides.
            methodDesc = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(methodDesc);
            Debug.Assert(!methodDesc.CanMethodBeInSealedVTable(_compilation.NodeFactory));

            // Avoid asking about slots on types like Foo<object, __Canon>. We might not have that information.
            // Canonically-equivalent types have the same slots, so ask for Foo<__Canon, __Canon>.
            methodDesc = methodDesc.GetCanonMethodTarget(CanonicalFormKind.Specific);

            TypeDesc owningType = methodDesc.OwningType;
            int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(_compilation.NodeFactory, methodDesc, owningType);
            if (slot == -1)
            {
                throw new InvalidOperationException(methodDesc.ToString());
            }

            if (_compilation.NeedsSlotUseTracking(owningType))
                (_additionalDependencies ??= new ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<NodeFactory>.DependencyList()).Add(_compilation.NodeFactory.VirtualMethodUse(methodDesc), "Virtual method call");

            offsetAfterIndirection = (uint)(EETypeNode.GetVTableOffset(pointerSize) + slot * pointerSize);
        }

        private void expandRawHandleIntrinsic(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_GENERICHANDLE_RESULT pResult)
        {
            // Resolved token as a potentially RuntimeDetermined object.
            MethodDesc method = (MethodDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);

            pResult.compileTimeHandle = null;

            switch (method.GetName())
            {
                case "Of":
                    ComputeLookup(ref pResolvedToken, method.Instantiation[0], ReadyToRunHelperId.TypeHandle, HandleToObject(callerHandle), ref pResult.lookup);
                    pResult.handleType = CorInfoGenericHandleType.CORINFO_HANDLETYPE_CLASS;
                    break;
                case "DefaultConstructorOf":
                    ComputeLookup(ref pResolvedToken, method.Instantiation[0], ReadyToRunHelperId.DefaultConstructor, HandleToObject(callerHandle), ref pResult.lookup);
                    pResult.handleType = CorInfoGenericHandleType.CORINFO_HANDLETYPE_METHOD;
                    break;
                case "AllocatorOf":
                    ComputeLookup(ref pResolvedToken, method.Instantiation[0], ReadyToRunHelperId.ObjectAllocator, HandleToObject(callerHandle), ref pResult.lookup);
                    pResult.handleType = CorInfoGenericHandleType.CORINFO_HANDLETYPE_UNKNOWN;
                    break;
                default:
                    Debug.Fail("Unexpected raw handle intrinsic");
                    break;
            }
        }

        private uint getMethodAttribs(CORINFO_METHOD_STRUCT_* ftn)
        {
            return getMethodAttribsInternal(HandleToObject(ftn));
        }

        private unsafe HRESULT allocPgoInstrumentationBySchema(CORINFO_METHOD_STRUCT_* ftnHnd, PgoInstrumentationSchema* pSchema, uint countSchemaItems, byte** pInstrumentationData)
        {
            throw new NotImplementedException("allocPgoInstrumentationBySchema");
        }

#pragma warning disable CA1822 // Mark members as static
        private CORINFO_CLASS_STRUCT_* getLikelyClass(CORINFO_METHOD_STRUCT_* ftnHnd, CORINFO_CLASS_STRUCT_* baseHnd, uint IlOffset, ref uint pLikelihood, ref uint pNumberOfClasses)
#pragma warning restore CA1822 // Mark members as static
        {
            return null;
        }

        private void getAddressOfPInvokeTarget(CORINFO_METHOD_STRUCT_* method, ref CORINFO_CONST_LOOKUP pLookup)
        {
            MethodDesc md = HandleToObject(method);

            Utf8String externName = new Utf8String(_compilation.PInvokeILProvider.GetDirectCallExternName(md));
            externName = _compilation.NodeFactory.NameMangler.NodeMangler.ExternMethod(externName, md);

            pLookup = CreateConstLookupToSymbol(_compilation.NodeFactory.ExternFunctionSymbol(externName));
        }

        private void getGSCookie(IntPtr* pCookieVal, IntPtr** ppCookieVal)
        {
            if (ppCookieVal != null)
            {
                *ppCookieVal = (IntPtr*)ObjectToHandle(_compilation.NodeFactory.ExternVariable(new Utf8String("__security_cookie"u8)));
                *pCookieVal = IntPtr.Zero;
            }
            else
            {
                throw new NotImplementedException("getGSCookie");
            }
        }

        private bool pInvokeMarshalingRequired(CORINFO_METHOD_STRUCT_* handle, CORINFO_SIG_INFO* callSiteSig)
        {
            // calli is covered by convertPInvokeCalliToCall
            if (handle == null)
            {
#if DEBUG
                MethodSignature methodSignature = (MethodSignature)HandleToObject((void*)callSiteSig->pSig);

                MethodDesc stub = _compilation.PInvokeILProvider.GetCalliStub(
                    methodSignature,
                    ((MetadataType)HandleToObject(callSiteSig->scope).OwningMethod.OwningType).Module);
                Debug.Assert(!IsPInvokeStubRequired(stub));
#endif

                return false;
            }

            MethodDesc method = HandleToObject(handle);

            if (method.IsRawPInvoke())
                return false;

            // Stub is required to trigger precise static constructor
            TypeDesc owningType = method.OwningType;
            if (_compilation.HasLazyStaticConstructor(owningType) && !((MetadataType)owningType).IsBeforeFieldInit)
                return true;

            // We could have given back the PInvoke stub IL to the JIT and let it inline it, without
            // checking whether there is any stub required. Save the JIT from doing the inlining by checking upfront.
            return IsPInvokeStubRequired(method);
        }

        private bool convertPInvokeCalliToCall(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool mustConvert)
        {
            var methodIL = (MethodIL)HandleToObject((void*)pResolvedToken.tokenScope);

            // Suppress recursive expansion of calli in marshaling stubs
            if (methodIL is Internal.IL.Stubs.PInvokeILStubMethodIL)
                return false;

            MethodSignature signature = (MethodSignature)methodIL.GetObject((int)pResolvedToken.token);
            if ((signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == 0)
                return false;

            MethodDesc stub = _compilation.PInvokeILProvider.GetCalliStub(
                signature,
                ((MetadataType)methodIL.OwningMethod.OwningType).Module);
            if (!mustConvert && !IsPInvokeStubRequired(stub))
                return false;

            pResolvedToken.hMethod = ObjectToHandle(stub);
            pResolvedToken.hClass = ObjectToHandle(stub.OwningType);
            return true;
        }

        private bool IsPInvokeStubRequired(MethodDesc method)
        {
            if (_compilation.GetMethodIL(method) is Internal.IL.Stubs.PInvokeILStubMethodIL stub)
                return stub.IsStubRequired;

            // This path is taken for PInvokes replaced by RemovingILProvider
            return true;
        }

        private int SizeOfPInvokeTransitionFrame
        {
            get
            {
                // struct PInvokeTransitionFrame:
                //  m_RIP (1)
                //  m_FramePointer (1)
                //  m_pThread
                //  m_Flags + align (no align for ARM64/LoongArch64/RiscV64 that has 64 bit m_Flags)
                //  m_PreservedRegs - RSP / R9 (2)
                //      No need to save other preserved regs because of the JIT ensures that there are
                //      no live GC references in callee saved registers around the PInvoke callsite.
                //
                // (1) On ARM32/ARM64/LoongArch64/RiscV64 the order of m_RIP and m_FramePointer is reverse
                // (2) R9 is saved for ARM32 because it needs to be preserved for methods with stackalloc
                int size = 5 * this.PointerSize;

                if (_compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.ARM)
                {
                    size += this.PointerSize; // R9 (REG_SAVED_LOCALLOC_SP)
                }

                return size;
            }
        }

#pragma warning disable CA1822 // Mark members as static
        private void classMustBeLoadedBeforeCodeIsRun(CORINFO_CLASS_STRUCT_* cls)
#pragma warning restore CA1822 // Mark members as static
        {
        }

        private void setEHcount(uint cEH)
        {
            _ehClauses = new CORINFO_EH_CLAUSE[cEH];
        }

        private void setEHinfo(uint EHnumber, ref CORINFO_EH_CLAUSE clause)
        {
            _ehClauses[EHnumber] = clause;
        }

#pragma warning disable CA1822 // Mark members as static
        private void beginInlining(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_METHOD_STRUCT_* inlineeHnd)
#pragma warning restore CA1822 // Mark members as static
        {
        }

#pragma warning disable CA1822 // Mark members as static
        private void reportInliningDecision(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_METHOD_STRUCT_* inlineeHnd, CorInfoInline inlineResult, byte* reason)
#pragma warning restore CA1822 // Mark members as static
        {
        }

#pragma warning disable CA1822 // Mark members as static
        private void updateEntryPointForTailCall(ref CORINFO_CONST_LOOKUP entryPoint)
#pragma warning restore CA1822 // Mark members as static
        {
        }

        private int* getAddrOfCaptureThreadGlobal(ref void* ppIndirection)
        {
            ppIndirection = null;
            return (int*)ObjectToHandle(_compilation.NodeFactory.ExternVariable(new Utf8String("RhpTrapThreads"u8)));
        }

        private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_ACCESS_FLAGS flags, CORINFO_FIELD_INFO* pResult)
        {
#if DEBUG
            // In debug, write some bogus data to the struct to ensure we have filled everything
            // properly.
            NativeMemory.Fill(pResult, (nuint)sizeof(CORINFO_FIELD_INFO), 0xcc);
#endif

            Debug.Assert(((int)flags & ((int)CORINFO_ACCESS_FLAGS.CORINFO_ACCESS_GET |
                                        (int)CORINFO_ACCESS_FLAGS.CORINFO_ACCESS_SET |
                                        (int)CORINFO_ACCESS_FLAGS.CORINFO_ACCESS_ADDRESS |
                                        (int)CORINFO_ACCESS_FLAGS.CORINFO_ACCESS_INIT_ARRAY)) != 0);

            var field = HandleToObject(pResolvedToken.hField);

            CORINFO_FIELD_ACCESSOR fieldAccessor;
            CORINFO_FIELD_FLAGS fieldFlags = (CORINFO_FIELD_FLAGS)0;
            uint fieldOffset = (field.IsStatic && field.HasRva ? 0xBAADF00D : (uint)field.Offset.AsInt);

            if (field.IsThreadStatic && field.OwningType is MetadataType mt)
            {
                fieldOffset += _compilation.NodeFactory.ThreadStaticBaseOffset(mt);
            }

            if (field.IsStatic)
            {
                fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_STATIC;

                if (field.HasRva)
                {
                    fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_UNMANAGED;

                    // TODO: Handle the case when the RVA is in the TLS range
                    fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_STATIC_RVA_ADDRESS;

                    ISymbolNode node = _compilation.GetFieldRvaData(field);
                    pResult->fieldLookup.addr = (void*)ObjectToHandle(node);
                    pResult->fieldLookup.accessType = node.RepresentsIndirectionCell ? InfoAccessType.IAT_PVALUE : InfoAccessType.IAT_VALUE;

                    // We are not going through a helper. The constructor has to be triggered explicitly.
                    if (_compilation.HasLazyStaticConstructor(field.OwningType))
                    {
                        fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_INITCLASS;
                    }
                }
                else if (field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any))
                {
                    // The JIT wants to know how to access a static field on a generic type. We need a runtime lookup.
                    fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_STATIC_READYTORUN_HELPER;
                    pResult->helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE;

                    MethodDesc contextMethod = HandleToObject(callerHandle);

                    FieldDesc runtimeDeterminedField = (FieldDesc)GetRuntimeDeterminedObjectForToken(ref pResolvedToken);

                    ReadyToRunHelperId helperId;

                    // Find out what kind of base do we need to look up.
                    if (field.IsThreadStatic)
                    {
                        helperId = ReadyToRunHelperId.GetThreadStaticBase;
                    }
                    else if (field.HasGCStaticBase)
                    {
                        helperId = ReadyToRunHelperId.GetGCStaticBase;
                    }
                    else
                    {
                        helperId = ReadyToRunHelperId.GetNonGCStaticBase;
                    }

                    // What generic context do we look up the base from.
                    ISymbolNode helper;
                    if (contextMethod.AcquiresInstMethodTableFromThis() || contextMethod.RequiresInstMethodTableArg())
                    {
                        helper = _compilation.NodeFactory.ReadyToRunHelperFromTypeLookup(
                            helperId, runtimeDeterminedField.OwningType, contextMethod.OwningType);
                    }
                    else
                    {
                        Debug.Assert(contextMethod.RequiresInstMethodDescArg());
                        helper = _compilation.NodeFactory.ReadyToRunHelperFromDictionaryLookup(
                            helperId, runtimeDeterminedField.OwningType, contextMethod);
                    }

                    pResult->fieldLookup = CreateConstLookupToSymbol(helper);
                }
                else
                {
                    fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER;
                    pResult->helper = CorInfoHelpFunc.CORINFO_HELP_UNDEF;

                    ReadyToRunHelperId helperId = ReadyToRunHelperId.Invalid;
                    CORINFO_FIELD_ACCESSOR intrinsicAccessor;
                    if (field.IsIntrinsic &&
                        (flags & CORINFO_ACCESS_FLAGS.CORINFO_ACCESS_GET) != 0 &&
                        (intrinsicAccessor = getFieldIntrinsic(field)) != (CORINFO_FIELD_ACCESSOR)(-1))
                    {
                        fieldAccessor = intrinsicAccessor;
                    }
                    else if (field.IsThreadStatic)
                    {
                        var target = MethodBeingCompiled.Context.Target;
                        if ((target.IsWindows && target.Architecture is TargetArchitecture.X64 or TargetArchitecture.ARM64) ||
                            ((target.OperatingSystem == TargetOS.Linux) &&
                            (target.Architecture is TargetArchitecture.X64 or TargetArchitecture.ARM64)))
                        {
                            ISortableSymbolNode index = _compilation.NodeFactory.TypeThreadStaticIndex(field.OwningType);
                            if (index is TypeThreadStaticIndexNode ti)
                            {
                                if (ti.IsInlined)
                                {
                                    fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_STATIC_TLS_MANAGED;
                                    if (_compilation.HasLazyStaticConstructor(field.OwningType))
                                    {
                                        fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_INITCLASS;
                                    }
                                }
                            }
                        }

                        pResult->helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_THREADSTATIC_BASE;
                        helperId = ReadyToRunHelperId.GetThreadStaticBase;
                    }
                    else if (!_compilation.HasLazyStaticConstructor(field.OwningType))
                    {
                        fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_STATIC_RELOCATABLE;
                        ISymbolNode baseAddr;
                        if (field.HasGCStaticBase)
                        {
                            pResult->fieldLookup.accessType = InfoAccessType.IAT_PVALUE;
                            baseAddr = _compilation.NodeFactory.TypeGCStaticsSymbol(field.OwningType);
                        }
                        else
                        {
                            pResult->fieldLookup.accessType = InfoAccessType.IAT_VALUE;
                            baseAddr = _compilation.NodeFactory.TypeNonGCStaticsSymbol(field.OwningType);
                        }
                        pResult->fieldLookup.addr = (void*)ObjectToHandle(baseAddr);
                    }
                    else
                    {
                        if (field.HasGCStaticBase)
                        {
                            pResult->helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GCSTATIC_BASE;
                            helperId = ReadyToRunHelperId.GetGCStaticBase;
                        }
                        else
                        {
                            pResult->helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NONGCSTATIC_BASE;
                            helperId = ReadyToRunHelperId.GetNonGCStaticBase;
                        }
                    }

                    if (helperId != ReadyToRunHelperId.Invalid)
                    {
                        pResult->fieldLookup = CreateConstLookupToSymbol(
                            _compilation.NodeFactory.ReadyToRunHelper(helperId, field.OwningType));
                    }
                }
            }
            else
            {
                fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INSTANCE;
            }

            if (_compilation.IsInitOnly(field))
                fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_FINAL;

            pResult->fieldAccessor = fieldAccessor;
            pResult->fieldFlags = fieldFlags;
            pResult->fieldType = getFieldType(pResolvedToken.hField, &pResult->structType, pResolvedToken.hClass);
            pResult->accessAllowed = CorInfoIsAccessAllowedResult.CORINFO_ACCESS_ALLOWED;
            pResult->offset = fieldOffset;

            // TODO: We need to implement access checks for fields and methods.  See JitInterface.cpp in mrtjit
            //       and STS::AccessCheck::CanAccess.
        }

        private bool CanNeverHaveInstanceOfSubclassOf(TypeDesc type)
        {
            // Don't try to optimize nullable
            if (type.IsNullable)
                return false;

            // We don't track unconstructable types very well and they are rare anyway
            if (!ConstructedEETypeNode.CreationAllowed(type))
                return false;

            // We don't track information that would allow us to deal with array variance
            if (_compilation.TypeSystemContext.IsArrayVariantCastable(type))
                return false;

            TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);

            // If we don't have a constructed MethodTable for the exact type or for its template,
            // this type or any of its subclasses can never be instantiated.
            return !_compilation.CanReferenceConstructedTypeOrCanonicalFormOfType(type)
                && (type == canonType || !_compilation.CanReferenceConstructedMethodTable(canonType));
        }

        private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
        {
            MetadataType type = HandleToObject(baseType) as MetadataType;
            if (type == null || type.HasVariance)
            {
                return -1;
            }

            if (CanNeverHaveInstanceOfSubclassOf(type))
            {
                return 0;
            }

            if (maxExactClasses == 0)
            {
                return -1;
            }

            // type is already sealed, return it
            if (_compilation.IsEffectivelySealed(type))
            {
                *exactClsRet = baseType;
                return 1;
            }

            TypeDesc[] implClasses = _compilation.GetImplementingClasses(type);
            if (implClasses == null || implClasses.Length > maxExactClasses)
            {
                return -1;
            }

            int index = 0;
            foreach (TypeDesc implClass in implClasses)
            {
                Debug.Assert(!implClass.IsCanonicalSubtype(CanonicalFormKind.Any));
                exactClsRet[index++] = ObjectToHandle(implClass);
            }

            Debug.Assert(index <= maxExactClasses);
            return index;
        }

        private bool getStaticFieldContent(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects)
        {
            Debug.Assert(fieldHandle != null);
            Debug.Assert(buffer != null);
            Debug.Assert(bufferSize > 0);
            Debug.Assert(valueOffset >= 0);

            FieldDesc field = HandleToObject(fieldHandle);
            Debug.Assert(field.IsStatic);


            if (!field.IsThreadStatic && _compilation.IsInitOnly(field) && field.OwningType is MetadataType owningType)
            {
                if (field.HasRva)
                {
                    return TryReadRvaFieldData(field, buffer, bufferSize, valueOffset);
                }

                PreinitializationManager preinitManager = _compilation.NodeFactory.PreinitializationManager;
                if (preinitManager.IsPreinitialized(owningType))
                {
                    TypePreinit.ISerializableValue value = preinitManager
                        .GetPreinitializationInfo(owningType).GetFieldValue(field);

                    int targetPtrSize = _compilation.TypeSystemContext.Target.PointerSize;

                    if (value == null)
                    {
                        if ((valueOffset == 0) && (bufferSize == targetPtrSize))
                        {
                            // Write "null" to buffer
                            new Span<byte>(buffer, targetPtrSize).Clear();
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }

                    if (value.GetRawData(_compilation.NodeFactory, out object data))
                    {
                        switch (data)
                        {
                            case byte[] bytes:
                                if (bytes.Length >= bufferSize && valueOffset <= bytes.Length - bufferSize)
                                {
                                    bytes.AsSpan(valueOffset, bufferSize).CopyTo(new Span<byte>(buffer, bufferSize));
                                    return true;
                                }
                                return false;

                            case FrozenObjectNode:
                                if ((valueOffset == 0) && (bufferSize == targetPtrSize))
                                {
                                    // save handle's value to buffer
                                    nint handle = ObjectToHandle(data);
                                    new Span<byte>(&handle, targetPtrSize).CopyTo(new Span<byte>(buffer, targetPtrSize));
                                    return true;
                                }
                                return false;
                        }
                    }
                }
                else if (!owningType.HasStaticConstructor)
                {
                    // (Effectively) read only field but no static constructor to set it: the value is default-initialized.
                    int size = field.FieldType.GetElementSize().AsInt;
                    if (size >= bufferSize && valueOffset <= size - bufferSize)
                    {
                        new Span<byte>(buffer, bufferSize).Clear();
                        return true;
                    }
                }
            }
            return false;
        }

        private bool getObjectContent(CORINFO_OBJECT_STRUCT_* objPtr, byte* buffer, int bufferSize, int valueOffset)
        {
            Debug.Assert(objPtr != null);
            Debug.Assert(buffer != null);
            Debug.Assert(bufferSize >= 0);
            Debug.Assert(valueOffset >= 0);

            FrozenObjectNode obj = HandleToObject(objPtr);
            if (obj is FrozenStringNode frozenStr)
            {
                // Only support reading the string data
                int strDataOffset = _compilation.TypeSystemContext.Target.PointerSize + sizeof(int); // 12 on 64bit
                if (valueOffset >= strDataOffset && (long)frozenStr.Data.Length * 2 >= (valueOffset - strDataOffset) + bufferSize)
                {
                    int offset = valueOffset - strDataOffset;
                    fixed (char* pStr = frozenStr.Data)
                    {
                        new Span<byte>((byte*)pStr + offset, bufferSize).CopyTo(
                            new Span<byte>(buffer, bufferSize));
                        return true;
                    }
                }
            }
            // TODO: handle FrozenObjectNode
            return false;
        }

        private CORINFO_CLASS_STRUCT_* getObjectType(CORINFO_OBJECT_STRUCT_* objPtr)
        {
            return ObjectToHandle(HandleToObject(objPtr).ObjectType);
        }

        private CORINFO_OBJECT_STRUCT_* getRuntimeTypePointer(CORINFO_CLASS_STRUCT_* cls)
        {
            TypeDesc type = HandleToObject(cls);
            if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
                return null;

            return ObjectToHandle(_compilation.NecessaryRuntimeTypeIfPossible(type));
        }

        private bool isObjectImmutable(CORINFO_OBJECT_STRUCT_* objPtr)
        {
            return HandleToObject(objPtr).IsKnownImmutable;
        }

        private bool getStringChar(CORINFO_OBJECT_STRUCT_* strObj, int index, ushort* value)
        {
            if (HandleToObject(strObj) is FrozenStringNode frozenStr && (uint)frozenStr.Data.Length > (uint)index)
            {
                *value = frozenStr.Data[index];
                return true;
            }
            return false;
        }

        private int getArrayOrStringLength(CORINFO_OBJECT_STRUCT_* objHnd)
        {
            return HandleToObject(objHnd).ArrayLength ?? -1;
        }

        private bool getIsClassInitedFlagAddress(CORINFO_CLASS_STRUCT_* cls, ref CORINFO_CONST_LOOKUP addr, ref int offset)
        {
            MetadataType type = (MetadataType)HandleToObject(cls);
            addr.addr = (void*)ObjectToHandle(_compilation.NodeFactory.TypeNonGCStaticsSymbol(type));
            addr.accessType = InfoAccessType.IAT_VALUE;
            offset = -NonGCStaticsNode.GetClassConstructorContextSize(_compilation.NodeFactory.Target);
            return true;
        }

        private bool getStaticBaseAddress(CORINFO_CLASS_STRUCT_* cls, bool isGc, ref CORINFO_CONST_LOOKUP addr)
        {
            MetadataType type = (MetadataType)HandleToObject(cls);
            if (isGc)
            {
                addr.accessType = InfoAccessType.IAT_PVALUE;
                addr.addr = (void*)ObjectToHandle(_compilation.NodeFactory.TypeGCStaticsSymbol(type));
            }
            else
            {
                addr.accessType = InfoAccessType.IAT_VALUE;
                addr.addr = (void*)ObjectToHandle(_compilation.NodeFactory.TypeNonGCStaticsSymbol(type));
            }
            return true;
        }

        private void getThreadLocalStaticInfo_NativeAOT(CORINFO_THREAD_STATIC_INFO_NATIVEAOT* pInfo)
        {
            pInfo->offsetOfThreadLocalStoragePointer = (uint)(11 * PointerSize); // Offset of ThreadLocalStoragePointer in the TEB
            pInfo->tlsIndexObject = CreateConstLookupToSymbol(_compilation.NodeFactory.ExternDataSymbol(new Utf8String("_tls_index"u8)));
            pInfo->tlsRootObject = CreateConstLookupToSymbol(_compilation.NodeFactory.TlsRoot);
            pInfo->threadStaticBaseSlow = CreateConstLookupToSymbol(_compilation.NodeFactory.HelperEntrypoint(HelperEntrypoint.GetInlinedThreadStaticBaseSlow));
            pInfo->tlsGetAddrFtnPtr = CreateConstLookupToSymbol(_compilation.NodeFactory.ExternFunctionSymbol(new Utf8String("__tls_get_addr"u8)));
        }

        private CORINFO_WASM_TYPE_SYMBOL_STRUCT_* getWasmTypeSymbol(CorInfoWasmType* types, nuint typesSize)
        {
            throw new NotImplementedException();
        }

#pragma warning disable CA1822 // Mark members as static
        private bool notifyMethodInfoUsage(CORINFO_METHOD_STRUCT_* ftn)
#pragma warning restore CA1822 // Mark members as static
        {
            return true;
        }

#pragma warning disable CA1822 // Mark members as static
        private void recordCallSite(uint instrOffset, CORINFO_SIG_INFO* callSig, CORINFO_METHOD_STRUCT_* methodHandle)
#pragma warning restore CA1822 // Mark members as static
        {
        }

#pragma warning disable CA1822 // Mark members as static
        private void recordWasmManagedCallSig(CORINFO_SIG_INFO* callSig)
#pragma warning restore CA1822 // Mark members as static
        {
        }
    }
}