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

using Internal.IL;
using Internal.IL.Stubs;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
using Internal.TypeSystem.Interop;
using Internal.CorConstants;
using Internal.Pgo;
using Internal.ReadyToRunConstants;

using ILCompiler;
using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysis.ReadyToRun;
using ILCompiler.DependencyAnalysis.Wasm;

using System.Text;
using System.Runtime.CompilerServices;
using ILCompiler.ReadyToRun.TypeSystem;

using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyList;

namespace Internal.JitInterface
{
    class InfiniteCompileStress
    {
        public static bool Enabled;
        static InfiniteCompileStress()
        {
            Enabled = JitConfigProvider.Instance.GetIntConfigValue("InfiniteCompileStress", 0) != 0;
        }
    }

    internal class RequiresRuntimeJitIfUsedSymbol
    {
        public RequiresRuntimeJitIfUsedSymbol(string message)
        {
            Message = message;
        }

        public string Message { get; }
    }

    public class FieldWithToken : IEquatable<FieldWithToken>
    {
        public readonly FieldDesc Field;
        public readonly ModuleToken Token;
        public readonly bool OwningTypeNotDerivedFromToken;
        private readonly bool _forceOwningTypeNotDerivedFromToken;

        public FieldWithToken(FieldDesc field, ModuleToken token, bool forceOwningTypeNotDerivedFromToken = false)
        {
            Field = field;
            Token = token;
            _forceOwningTypeNotDerivedFromToken = forceOwningTypeNotDerivedFromToken;
            if (token.TokenType == CorTokenType.mdtMemberRef)
            {
                var memberRef = token.MetadataReader.GetMemberReference((MemberReferenceHandle)token.Handle);
                switch (memberRef.Parent.Kind)
                {
                case HandleKind.TypeDefinition:
                case HandleKind.TypeReference:
                case HandleKind.TypeSpecification:
                    OwningTypeNotDerivedFromToken = token.Module.GetType(memberRef.Parent) != field.OwningType;
                    break;

                default:
                    break;
                }
            }
            if (_forceOwningTypeNotDerivedFromToken)
            {
                OwningTypeNotDerivedFromToken = true;
            }
        }

        public override bool Equals(object obj)
        {
            return obj is FieldWithToken fieldWithToken &&
                Equals(fieldWithToken);
        }

        public override int GetHashCode()
        {
            return Field.GetHashCode() ^ unchecked(17 * Token.GetHashCode());
        }

        public bool Equals(FieldWithToken fieldWithToken)
        {
            if (fieldWithToken == null)
                return false;

            return Field == fieldWithToken.Field
                && Token.Equals(fieldWithToken.Token)
                && _forceOwningTypeNotDerivedFromToken == fieldWithToken._forceOwningTypeNotDerivedFromToken;
        }

        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.GetMangledFieldName(Field));
            sb.Append("; "u8);
            sb.Append(Token.ToString());
        }

        public override string ToString()
        {
            StringBuilder debuggingName = new StringBuilder();
            debuggingName.Append(Field.ToString());

            debuggingName.Append("; ");
            debuggingName.Append(Token.ToString());

            return debuggingName.ToString();
        }

        public int CompareTo(FieldWithToken other, TypeSystemComparer comparer)
        {
            int result;

            result = comparer.Compare(Field, other.Field);
            if (result != 0)
                return result;
            result = Token.CompareTo(other.Token);
            if (result != 0)
                return result;
            return _forceOwningTypeNotDerivedFromToken.CompareTo(other._forceOwningTypeNotDerivedFromToken);
        }
    }

    public class MethodWithToken
    {
        public readonly MethodDesc Method;
        public readonly ModuleToken Token;
        public readonly TypeDesc ConstrainedType;
        public readonly bool Unboxing;
        public readonly bool OwningTypeNotDerivedFromToken;
        private readonly bool _forceOwningTypeNotDerivedFromToken;
        public readonly TypeDesc OwningType;

        public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, TypeSystemEntity genericContextObject, TypeDesc devirtualizedMethodOwner = null, bool forceOwningTypeFromMethodDesc = false)
        {
            Debug.Assert(!method.IsUnboxingThunk());
            Debug.Assert(genericContextObject is null or MethodDesc or TypeDesc);
            Method = method;
            Token = token;
            ConstrainedType = constrainedType;
            Unboxing = unboxing;
            _forceOwningTypeNotDerivedFromToken = forceOwningTypeFromMethodDesc;
            if (!_forceOwningTypeNotDerivedFromToken)
            {
                OwningType = GetMethodTokenOwningType(this, constrainedType, genericContextObject, devirtualizedMethodOwner, out OwningTypeNotDerivedFromToken);
            }
            else
            {
                OwningType = method.OwningType;
                OwningTypeNotDerivedFromToken = true;
            }
        }

        private static TypeDesc GetMethodTokenOwningType(MethodWithToken methodToken, TypeDesc constrainedType, TypeSystemEntity genericContextObject, TypeDesc devirtualizedMethodOwner, out bool owningTypeNotDerivedFromToken)
        {
            ModuleToken moduleToken = methodToken.Token;
            owningTypeNotDerivedFromToken = false;

            // Strip off method spec details. The owning type is only associated with a MethodDef or a MemberRef
            if (moduleToken.TokenType == CorTokenType.mdtMethodSpec)
            {
                var methodSpecification = moduleToken.MetadataReader.GetMethodSpecification((MethodSpecificationHandle)moduleToken.Handle);
                moduleToken = new ModuleToken(moduleToken.Module, methodSpecification.Method);
            }

            if (moduleToken.TokenType == CorTokenType.mdtMethodDef)
            {
                var methodDefinition = moduleToken.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)moduleToken.Handle);
                return HandleContext(moduleToken.Module, methodDefinition.GetDeclaringType(), methodToken.Method.OwningType, constrainedType, genericContextObject, devirtualizedMethodOwner, ref owningTypeNotDerivedFromToken);
            }

            // At this point moduleToken must point at a MemberRef.
            Debug.Assert(moduleToken.TokenType == CorTokenType.mdtMemberRef);
            var memberRef = moduleToken.MetadataReader.GetMemberReference((MemberReferenceHandle)moduleToken.Handle);
            switch (memberRef.Parent.Kind)
            {
                case HandleKind.TypeDefinition:
                case HandleKind.TypeReference:
                case HandleKind.TypeSpecification:
                    {
                        Debug.Assert(devirtualizedMethodOwner == null); // Devirtualization is expected to always use a methoddef token
                        return HandleContext(moduleToken.Module, memberRef.Parent, methodToken.Method.OwningType, constrainedType, genericContextObject, null, ref owningTypeNotDerivedFromToken);
                    }

                default:
                    return methodToken.Method.OwningType;
            }

            static TypeDesc HandleContext(IEcmaModule module, EntityHandle handle, TypeDesc methodTargetOwner, TypeDesc constrainedType, TypeSystemEntity genericContextObject, TypeDesc devirtualizedMethodOwner, ref bool owningTypeNotDerivedFromToken)
            {
                var tokenOnlyOwningType = module.GetType(handle);
                TypeDesc actualOwningType;

                if (genericContextObject == null)
                {
                    actualOwningType = methodTargetOwner;
                }
                else
                {
                    Instantiation typeInstantiation;
                    Instantiation methodInstantiation = new Instantiation();

                    if (genericContextObject is MethodDesc methodContext)
                    {
                        typeInstantiation = methodContext.OwningType.Instantiation;
                        methodInstantiation = methodContext.Instantiation;
                    }
                    else
                    {
                        TypeDesc typeContext = (TypeDesc)genericContextObject;
                        typeInstantiation = typeContext.Instantiation;
                    }

                    TypeDesc instantiatedOwningType = null;

                    if (devirtualizedMethodOwner != null)
                    {
                        // We might be in a situation where we use the passed in type (devirtualization scenario)
                        // Check to see if devirtualizedMethodOwner actually is a type derived from the type definition in some way.
                        bool derivesFromTypeDefinition = false;
                        TypeDesc currentType = devirtualizedMethodOwner;
                        do
                        {
                            derivesFromTypeDefinition = currentType.GetTypeDefinition() == tokenOnlyOwningType;
                            currentType = currentType.BaseType;
                        } while (currentType != null && !derivesFromTypeDefinition);

                        if (derivesFromTypeDefinition)
                        {
                            instantiatedOwningType = devirtualizedMethodOwner;
                        }
                        else
                        {
                            Debug.Assert(false); // This is expected to fire if and only if we implement devirtualization to default interface methods
                            throw new RequiresRuntimeJitException(methodTargetOwner.ToString());
                        }
                    }

                    if (instantiatedOwningType == null)
                    {
                        instantiatedOwningType = tokenOnlyOwningType.InstantiateSignature(typeInstantiation, methodInstantiation);
                    }

                    var canonicalizedOwningType = instantiatedOwningType.ConvertToCanonForm(CanonicalFormKind.Specific);
                    if ((instantiatedOwningType == canonicalizedOwningType) || (constrainedType != null))
                    {
                        actualOwningType = instantiatedOwningType;
                    }
                    else
                    {
                        actualOwningType = ComputeActualOwningType(methodTargetOwner, instantiatedOwningType, canonicalizedOwningType);

                        // Implement via a helper function, so that managing the loop escape behavior is easier to read
                        TypeDesc ComputeActualOwningType(TypeDesc methodTargetOwner, TypeDesc instantiatedOwningType, TypeDesc canonicalizedOwningType)
                        {
                            // Pick between Canonical and Exact OwningTypes.
                            //
                            // If the canonicalizedOwningType is the OwningType (or parent type) of the associated method
                            //   Then return canonicalizedOwningType
                            // Else If the Exact Owning type is the OwningType (or parent type) of the associated method
                            //   Then return actualOwningType
                            // Else If the canonicallized owningType (or canonicalized parent type) of the associated method
                            //   Return the canonicalizedOwningType
                            // Else
                            //   Fail, unexpected behavior
                            var currentType = canonicalizedOwningType;
                            while (currentType != null)
                            {
                                if (currentType == methodTargetOwner)
                                    return canonicalizedOwningType;
                                currentType = currentType.BaseType;
                            }

                            currentType = instantiatedOwningType;
                            while (currentType != null)
                            {
                                if (currentType == methodTargetOwner)
                                    return instantiatedOwningType;
                                currentType = currentType.BaseType;
                            }

                            currentType = canonicalizedOwningType;
                            while (currentType != null)
                            {
                                currentType = currentType.ConvertToCanonForm(CanonicalFormKind.Specific);
                                if (currentType == methodTargetOwner)
                                    return canonicalizedOwningType;
                                currentType = currentType.BaseType;
                            }

                            foreach (DefType interfaceType in methodTargetOwner.RuntimeInterfaces)
                            {
                                if (interfaceType == instantiatedOwningType ||
                                    interfaceType.ConvertToCanonForm(CanonicalFormKind.Specific) == canonicalizedOwningType)
                                {
                                    return methodTargetOwner;
                                }
                            }

                            Debug.Assert(false);
                            throw new Exception();
                        }
                    }
                }

                if (actualOwningType != tokenOnlyOwningType)
                {
                    owningTypeNotDerivedFromToken = true;
                }
                return actualOwningType;
            }
        }

        public override bool Equals(object obj)
        {
            return obj is MethodWithToken methodWithToken &&
                Equals(methodWithToken);
        }

        public override int GetHashCode()
        {
            return Method.GetHashCode() ^ unchecked(17 * Token.GetHashCode()) ^ unchecked(39 * (ConstrainedType?.GetHashCode() ?? 0));
        }

        public bool Equals(MethodWithToken methodWithToken)
        {
            if (methodWithToken == null)
                return false;

            bool equals = Method == methodWithToken.Method
                && Token.Equals(methodWithToken.Token)
                && OwningType == methodWithToken.OwningType
                && ConstrainedType == methodWithToken.ConstrainedType
                && Unboxing == methodWithToken.Unboxing
                && _forceOwningTypeNotDerivedFromToken == methodWithToken._forceOwningTypeNotDerivedFromToken;

            return equals;
        }

        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.GetMangledMethodName(Method));
            if (ConstrainedType != null)
            {
                sb.Append(" @ "u8);
                sb.Append(nameMangler.GetMangledTypeName(ConstrainedType));
            }
            sb.Append("; "u8);
            sb.Append(Token.ToString());
            if (OwningTypeNotDerivedFromToken)
            {
                sb.Append("; OWNINGTYPE"u8);
                sb.Append(nameMangler.GetMangledTypeName(OwningType));
                sb.Append("; "u8);
            }
            if (Unboxing)
                sb.Append("; UNBOXING"u8);
            if (Method.IsAsyncVariant())
                sb.Append("; ASYNC"u8);
            if (Method is AsyncResumptionStub)
                sb.Append("; RESUME"u8);
        }

        public override string ToString()
        {
            StringBuilder debuggingName = new StringBuilder();
            debuggingName.Append(Method.ToString());
            if (ConstrainedType != null)
            {
                debuggingName.Append(" @ ");
                debuggingName.Append(ConstrainedType.ToString());
            }

            debuggingName.Append("; ");
            debuggingName.Append(Token.ToString());
            if (Unboxing)
                debuggingName.Append("; UNBOXING");
            if (Method.IsAsyncVariant())
                debuggingName.Append("; ASYNC");

            return debuggingName.ToString();
        }

        public int CompareTo(MethodWithToken other, TypeSystemComparer comparer)
        {
            int result;
            if (ConstrainedType != null || other.ConstrainedType != null)
            {
                if (ConstrainedType == null)
                    return -1;
                else if (other.ConstrainedType == null)
                    return 1;

                result = comparer.Compare(ConstrainedType, other.ConstrainedType);
                if (result != 0)
                    return result;
            }

            result = comparer.Compare(Method, other.Method);
            if (result != 0)
                return result;

            result = Token.CompareTo(other.Token);
            if (result != 0)
                return result;

            result = Unboxing.CompareTo(other.Unboxing);
            if (result != 0)
                return result;

            result = _forceOwningTypeNotDerivedFromToken.CompareTo(other._forceOwningTypeNotDerivedFromToken);
            if (result != 0)
                return result;

            return comparer.Compare(OwningType, other.OwningType);
        }
    }

    public struct GenericContext : IEquatable<GenericContext>
    {
        public readonly TypeSystemEntity Context;

        public TypeDesc ContextType { get { return (Context is MethodDesc contextAsMethod ? contextAsMethod.OwningType : (TypeDesc)Context); } }

        public MethodDesc ContextMethod { get { return (MethodDesc)Context; } }

        public GenericContext(TypeSystemEntity context)
        {
            Context = context;
        }

        public bool Equals(GenericContext other) => Context == other.Context;

        public override bool Equals(object obj) => obj is GenericContext other && Context == other.Context;

        public override int GetHashCode() => Context.GetHashCode();

        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            if (Context is MethodDesc contextAsMethod)
            {
                sb.Append(nameMangler.GetMangledMethodName(contextAsMethod));
            }
            else
            {
                sb.Append(nameMangler.GetMangledTypeName(ContextType));
            }
        }
    }

    public class RequiresRuntimeJitException : Exception
    {
        public RequiresRuntimeJitException(object reason)
            : base(reason.ToString())
        {
        }
    }

    unsafe partial class CorInfoImpl
    {
        private const CORINFO_RUNTIME_ABI TargetABI = CORINFO_RUNTIME_ABI.CORINFO_CORECLR_ABI;

        private uint OffsetOfDelegateFirstTarget => (uint)(3 * PointerSize); // Delegate._methodPtr

        private readonly ReadyToRunCodegenCompilation _compilation;
        private MethodWithGCInfo _methodCodeNode;
        private MethodColdCodeNode _methodColdCodeNode;
        private OffsetMapping[] _debugLocInfos;
        private NativeVarInfo[] _debugVarInfos;
        private HashSet<MethodDesc> _inlinedMethods;
        private UnboxingMethodDescFactory _unboxingThunkFactory = new UnboxingMethodDescFactory();
        private List<ISymbolNode> _precodeFixups;
        private List<MethodDesc> _ilBodiesNeeded;
        private Dictionary<TypeDesc, bool> _preInitedTypes = new Dictionary<TypeDesc, bool>();
        private HashSet<MethodDesc> _synthesizedPgoDependencies;
        public bool HasColdCode { get; private set; }

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

        private void AddPrecodeFixup(ISymbolNode node)
        {
            _precodeFixups = _precodeFixups ?? new List<ISymbolNode>();
            _precodeFixups.Add(node);
        }

        private void AddAdditionalDependency(ISymbolNode node, string reason)
        {
            _additionalDependencies ??= new DependencyList();
            _additionalDependencies.Add(node, reason);
        }

        private void AddResumptionStubFixup(MethodWithGCInfo compiledStubNode)
        {
            AddPrecodeFixup(_compilation.SymbolNodeFactory.ResumptionStubEntryPoint(compiledStubNode));
        }

        private CORINFO_METHOD_STRUCT_* getAsyncResumptionStub(ref void* entryPoint)
        {
            MethodDesc asyncResumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(MethodBeingCompiled, MethodBeingCompiled.OwningType);
            AddResumptionStubFixup(_compilation.NodeFactory.CompiledMethodNode(asyncResumptionStub));

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

        private static mdToken FindGenericMethodArgTypeSpec(EcmaModule module)
        {
            // Find the TypeSpec for "!!0"
            MetadataReader reader = module.MetadataReader;
            int numTypeSpecs = reader.GetTableRowCount(TableIndex.TypeSpec);
            for (int i = 1; i < numTypeSpecs + 1; i++)
            {
                TypeSpecificationHandle handle = MetadataTokens.TypeSpecificationHandle(i);
                BlobHandle typeSpecSigHandle = reader.GetTypeSpecification(handle).Signature;
                BlobReader typeSpecSig = reader.GetBlobReader(typeSpecSigHandle);
                SignatureTypeCode typeCode = typeSpecSig.ReadSignatureTypeCode();
                if (typeCode == SignatureTypeCode.GenericMethodParameter)
                {
                    if (typeSpecSig.ReadByte() == 0)
                    {
                        return (mdToken)MetadataTokens.GetToken(handle);
                    }
                }
            }

            // Should be unreachable - couldn't find a TypeSpec.
            // Are we still compiling CoreLib?
            throw new NotSupportedException();
        }

        public static bool ShouldSkipCompilation(InstructionSetSupport instructionSetSupport, MethodDesc methodNeedingCode, ReadyToRunCodegenCompilation compilation = null)
        {
            bool targetAllowsRuntimeCodeGeneration = ((ReadyToRunCompilerContext)methodNeedingCode.Context).TargetAllowsRuntimeCodeGeneration;
            if (methodNeedingCode.IsAggressiveOptimization && targetAllowsRuntimeCodeGeneration)
            {
                return true;
            }

            // On platforms where we can JIT, we will rarely need the hardware intrinsic fallback implementations.
            // On platforms where we cannot JIT, we need to ensure that we have a fallback implementation pre-compiled
            // so any code that uses hardware intrinsics and is interpreted has an implementation to use.
            // This allows us to avoid the high cost of manually implementing intrinsics in the interpreter.
            if (HardwareIntrinsicHelpers.IsHardwareIntrinsic(methodNeedingCode) && targetAllowsRuntimeCodeGeneration)
            {
                return true;
            }

            if (methodNeedingCode.IsAbstract)
            {
                return true;
            }
            if (methodNeedingCode.IsInternalCall)
            {
                return true;
            }
            if (methodNeedingCode.OwningType.IsDelegate && (
                methodNeedingCode.IsConstructor ||
                methodNeedingCode.Name.SequenceEqual("BeginInvoke"u8) ||
                methodNeedingCode.Name.SequenceEqual("Invoke"u8) ||
                methodNeedingCode.Name.SequenceEqual("EndInvoke"u8)))
            {
                // Special methods on delegate types
                return true;
            }

            if (ShouldCodeNotBeCompiledIntoFinalImage(instructionSetSupport, methodNeedingCode))
            {
                return true;
            }

            return false;
        }

        public static bool ShouldCodeNotBeCompiledIntoFinalImage(InstructionSetSupport instructionSetSupport, MethodDesc method)
        {
            EcmaMethod ecmaMethod = (EcmaMethod)(method.GetPrimaryMethodDesc().GetTypicalMethodDefinition());
            var metadataReader = ecmaMethod.MetadataReader;
            var stringComparer = metadataReader.StringComparer;

            var handle = ecmaMethod.Handle;

            List<TypeDesc> compExactlyDependsOnList = null;
            bool hasCompHasFallback = false;

            foreach (var attributeHandle in metadataReader.GetMethodDefinition(handle).GetCustomAttributes())
            {
                StringHandle namespaceHandle, nameHandle;
                if (!metadataReader.GetAttributeNamespaceAndName(attributeHandle, out namespaceHandle, out nameHandle))
                    continue;

                if (metadataReader.StringComparer.Equals(namespaceHandle, "System.Runtime"))
                {
                    if (metadataReader.StringComparer.Equals(nameHandle, "BypassReadyToRunAttribute"))
                    {
                        return true;
                    }
                }
                else if (metadataReader.StringComparer.Equals(namespaceHandle, "System.Runtime.CompilerServices"))
                {
                    if (metadataReader.StringComparer.Equals(nameHandle, "CompExactlyDependsOnAttribute"))
                    {
                        var customAttribute = metadataReader.GetCustomAttribute(attributeHandle);
                        var typeProvider = new CustomAttributeTypeProvider(ecmaMethod.Module);
                        var fixedArguments = customAttribute.DecodeValue(typeProvider).FixedArguments;
                        if (fixedArguments.Length < 1)
                            continue;

                        TypeDesc typeForBypass = fixedArguments[0].Value as TypeDesc;
                        if (typeForBypass != null)
                        {
                            if (compExactlyDependsOnList == null)
                                compExactlyDependsOnList = new List<TypeDesc>();

                            compExactlyDependsOnList.Add(typeForBypass);
                        }
                    }
                    else if (metadataReader.StringComparer.Equals(nameHandle, "CompHasFallbackAttribute"))
                    {
                        hasCompHasFallback = true;
                    }
                }
            }

            if (compExactlyDependsOnList != null && compExactlyDependsOnList.Count > 0)
            {
                bool anySupported = false;

                foreach (var intrinsicType in compExactlyDependsOnList)
                {
                    InstructionSet instructionSet = InstructionSetParser.LookupPlatformIntrinsicInstructionSet(intrinsicType.Context.Target.Architecture, intrinsicType);
                    // If the instruction set is ILLEGAL, it means it is never supported by the current architecture so the behavior at runtime is known
                    if (instructionSet != InstructionSet.ILLEGAL)
                    {
                        if (instructionSetSupport.IsInstructionSetSupported(instructionSet))
                        {
                            anySupported = true;
                        }
                        else if (!instructionSetSupport.IsInstructionSetExplicitlyUnsupported(instructionSet))
                        {
                            // If we reach here this is an instruction set generally supported on this platform, but we don't know what the behavior will be at runtime
                            return true;
                        }
                    }
                }

                if (!anySupported && !hasCompHasFallback)
                {
                    // If none of the instruction sets are supported (all are either illegal or explicitly unsupported),
                    // skip compilation unless the method has a functional fallback path
                    return true;
                }
            }

            // No reason to bypass compilation and code generation.
            return false;
        }

        private static bool FunctionJustThrows(MethodIL ilBody)
        {
            try
            {
                if (ilBody.GetExceptionRegions().Length != 0)
                    return false;

                ILReader reader = new ILReader(ilBody.GetILBytes());

                while (reader.HasNext)
                {
                    var ilOpcode = reader.ReadILOpcode();
                    if (ilOpcode == ILOpcode.throw_)
                        return true;
                    if (ilOpcode.IsBranch() || ilOpcode == ILOpcode.switch_)
                        return false;
                    reader.Skip(ilOpcode);
                }
            }
            catch
            { }

            return false;
        }

        class DeterminismData
        {
            public int Iterations = 0;
            public int HashCode = 0;
        }

        private static ConditionalWeakTable<MethodDesc, DeterminismData> _determinismTable = new ConditionalWeakTable<MethodDesc, DeterminismData>();

        partial void DetermineIfCompilationShouldBeRetried(ref CompilationResult result)
        {
            if ((_ilBodiesNeeded == null) && _compilation.NodeFactory.OptimizationFlags.DeterminismStress > 0)
            {
                HashCode hashCode = default(HashCode);
                hashCode.AddBytes(_code);
                hashCode.AddBytes(_roData);
                int functionOutputHashCode = hashCode.ToHashCode();

                lock (_determinismTable)
                {
                    DeterminismData data = _determinismTable.GetOrCreateValue(MethodBeingCompiled);
                    if (data.Iterations == 0)
                    {
                        data.Iterations = 1;
                        data.HashCode = functionOutputHashCode;
                    }
                    else
                    {
                        data.Iterations++;
                    }

                    if (data.HashCode != functionOutputHashCode)
                    {
                        _compilation.DeterminismCheckFailed = true;
                        _compilation.Logger.LogMessage($"ERROR: Determinism check compiling method '{MethodBeingCompiled}' failed. Use '{_compilation.GetReproInstructions(MethodBeingCompiled)}' on command line to reproduce the failure.");
                    }
                    else if (data.Iterations <= _compilation.NodeFactory.OptimizationFlags.DeterminismStress)
                    {
                        result = CompilationResult.CompilationRetryRequested;
                    }
                }
            }

            // If any il bodies need to be recomputed, force recompilation
            if ((_ilBodiesNeeded != null) || InfiniteCompileStress.Enabled || result == CompilationResult.CompilationRetryRequested)
            {
                _compilation.PrepareForCompilationRetry(_methodCodeNode, _ilBodiesNeeded);
                result = CompilationResult.CompilationRetryRequested;
            }
        }

        // At the moment, cross module code embedding does not support embedding cross module references within the EH clauses of a method
        // Obviously this could be fixed, but it is not necessary to demonstrate significant value from the feature as written now.
        private static bool FunctionHasNonReferenceableTypedILCatchClause(MethodIL methodIL, CompilationModuleGroup compilationGroup)
        {
            if (!compilationGroup.VersionsWithMethodBody(methodIL.OwningMethod.GetTypicalMethodDefinition()))
            {
                foreach (var clause in methodIL.GetExceptionRegions())
                {
                    if (clause.Kind == ILExceptionRegionKind.Catch)
                        return true;
                }
            }
            return false;
        }

        public static bool IsMethodCompilable(Compilation compilation, MethodDesc method)
        {
            // This logic must mirror the logic in CompileMethod used to get to the point of calling CompileMethodInternal
            if (ShouldSkipCompilation(compilation.InstructionSetSupport, method) || MethodSignatureIsUnstable(method.Signature, out var _))
                return false;

            MethodIL methodIL = compilation.GetMethodIL(method);
            if (methodIL == null)
                return false;

            if (FunctionJustThrows(methodIL))
                return false;

            if (FunctionHasNonReferenceableTypedILCatchClause(methodIL, compilation.NodeFactory.CompilationModuleGroup))
                return false;

            return true;
        }

        public void CompileMethod(MethodWithGCInfo methodCodeNodeNeedingCode, Logger logger)
        {
            bool codeGotPublished = false;
            _methodCodeNode = methodCodeNodeNeedingCode;

            try
            {
                if (ShouldSkipCompilation(_compilation.InstructionSetSupport, MethodBeingCompiled, _compilation))
                {
                    if (logger.IsVerbose)
                        logger.Writer.WriteLine($"Info: Method `{MethodBeingCompiled}` was not compiled because it is skipped.");
                    return;
                }

                if (MethodSignatureIsUnstable(MethodBeingCompiled.Signature, out var _))
                {
                    if (logger.IsVerbose)
                        logger.Writer.WriteLine($"Info: Method `{MethodBeingCompiled}` was not compiled because it has an non version resilient signature.");
                    return;
                }
                MethodIL methodIL = _compilation.GetMethodIL(MethodBeingCompiled);
                if (methodIL == null)
                {
                    if (logger.IsVerbose)
                        logger.Writer.WriteLine($"Info: Method `{MethodBeingCompiled}` was not compiled because IL code could not be found for the method.");
                    return;
                }

                if (FunctionJustThrows(methodIL))
                {
                    if (logger.IsVerbose)
                        logger.Writer.WriteLine($"Info: Method `{MethodBeingCompiled}` was not compiled because it always throws an exception");
                    return;
                }

                if (FunctionHasNonReferenceableTypedILCatchClause(methodIL, _compilation.NodeFactory.CompilationModuleGroup))
                {
                    if (logger.IsVerbose)
                        logger.Writer.WriteLine($"Info: Method `{MethodBeingCompiled}` was not compiled because it has a non referenceable catch clause");
                    return;
                }

                var typicalDef = MethodBeingCompiled.GetTypicalMethodDefinition();
                if (typicalDef is EcmaMethod or AsyncMethodVariant)
                {
                    var ecmaMethod = (EcmaMethod)typicalDef.GetPrimaryMethodDesc();
                    if ((methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL && _compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && ecmaMethod.Module == typicalDef.Context.SystemModule) ||
                        (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(MethodBeingCompiled) &&
                            _compilation.NodeFactory.CompilationModuleGroup.CrossModuleInlineable(MethodBeingCompiled)))
                    {
                        ISymbolNode ilBodyNode = _compilation.SymbolNodeFactory.CheckILBodyFixupSignature(typicalDef);
                        AddPrecodeFixup(ilBodyNode);
                    }

                    // Harvest the method being compiled for the purpose of populating the type resolver
                    var resolver = _compilation.NodeFactory.Resolver;
                    if (_compilation.CompilationModuleGroup.VersionsWithModule(ecmaMethod.Module))
                    {
                        resolver.AddModuleTokenForMethod(MethodBeingCompiled, new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle));
                    }
                    else
                    {
                        // Cross module optimization scenario. If we aren't able to come up with a token for the method being compiled, then force it to be
                        // computed.
                        // NOTE: Technically, we could arrange to require only the set of tokens necessary to describe the type that owns this method
                        // as well as the parameters and return value to the method. The token for the actual method is not required.
                        EntityHandle? handle = _compilation.NodeFactory.ManifestMetadataTable._mutableModule.TryGetExistingEntityHandle(ecmaMethod);
                        if (handle.HasValue)
                        {
                            resolver.AddModuleTokenForMethod(MethodBeingCompiled, new ModuleToken(_compilation.NodeFactory.ManifestMetadataTable._mutableModule, handle.Value));
                        }
                        else
                        {
                            _ilBodiesNeeded = _ilBodiesNeeded ?? new List<MethodDesc>();
                            _ilBodiesNeeded.Add(typicalDef);
                        }
                    }
                }

                // For managed methods on Wasm, add an interpreter-to-R2R thunk so the
                // interpreter can call into this R2R-compiled function.
                if (_compilation.NodeFactory.Target.IsWasm && !MethodBeingCompiled.IsUnmanagedCallersOnly)
                {
                    WasmSignature wasmSig = WasmLowering.GetSignature(MethodBeingCompiled);
                    AddAdditionalDependency(
                        _compilation.NodeFactory.WasmInterpreterToR2RThunk(wasmSig),
                        "Interpreter-to-R2R thunk for compiled method");
                }

                var compilationResult = CompileMethodInternal(methodCodeNodeNeedingCode, methodIL);
                codeGotPublished = true;

                if (compilationResult == CompilationResult.CompilationRetryRequested && logger.IsVerbose)
                {
                    logger.Writer.WriteLine($"Info: Method `{MethodBeingCompiled}` triggered recompilation to acquire stable tokens for cross module inline.");
                }
            }
            finally
            {
                if (!codeGotPublished)
                {
                    PublishEmptyCode();
                }

                HasColdCode = (_methodColdCodeNode != null);
                CompileMethodCleanup();
            }
        }

        private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CorInfoHelpFunc id, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_CONST_LOOKUP pLookup)
        {
            if (_compilation.NodeFactory.Target.IsWasm)
            {
                // WebAssembly doesn't use the compact ReadyToRun helpers, so disable them here.
                return false;
            }

            switch (id)
            {
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NEW:
                    {
                        var type = HandleToObject(pResolvedToken.hClass);
                        Debug.Assert(type.IsDefType);
                        if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
                            return false;

                        pLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.NewHelper, type));
                    }
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NEWARR_1:
                    {
                        var type = HandleToObject(pResolvedToken.hClass);
                        Debug.Assert(type.IsSzArray);
                        if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
                            return false;

                        pLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.NewArr1, type));
                    }
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_ISINSTANCEOF:
                    {
                        var type = HandleToObject(pResolvedToken.hClass);
                        if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
                            return false;

                        // ECMA-335 III.4.3:  If typeTok is a nullable type, Nullable<T>, it is interpreted as "boxed" T
                        if (type.IsNullable)
                            type = type.Instantiation[0];

                        pLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.IsInstanceOf, type));
                    }
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_CHKCAST:
                    {
                        var type = HandleToObject(pResolvedToken.hClass);
                        if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
                            return false;

                        // ECMA-335 III.4.3:  If typeTok is a nullable type, Nullable<T>, it is interpreted as "boxed" T
                        if (type.IsNullable)
                            type = type.Instantiation[0];

                        pLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.CastClass, type));
                    }
                    break;
                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.SymbolNodeFactory.CreateReadyToRunHelper(helperId, type));
                    }
                    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

            TypeDesc delegateTypeDesc = HandleToObject(delegateType);
            MethodDesc targetMethodDesc = HandleToObject(pTargetMethod.hMethod);
            Debug.Assert(!targetMethodDesc.IsUnboxingThunk());

            TypeSystemEntity typeOrMethodContext = (TypeSystemEntity)((pTargetMethod.tokenContext == contextFromMethodBeingCompiled()) ?
                MethodBeingCompiled : HandleToObject((void*)pTargetMethod.tokenContext));

            TypeDesc constrainedType = null;
            if (targetConstraint != 0)
            {
                MethodIL methodIL = _compilation.GetMethodIL(MethodBeingCompiled);
                constrainedType = (TypeDesc)ResolveTokenInScope(methodIL, typeOrMethodContext, targetConstraint);
            }

            MethodWithToken targetMethod = new MethodWithToken(
                targetMethodDesc,
                HandleToModuleToken(ref pTargetMethod, out bool strippedInstantiation),
                constrainedType: constrainedType,
                unboxing: false,
                genericContextObject: typeOrMethodContext,
                forceOwningTypeFromMethodDesc: strippedInstantiation);

            // runtime lookup is not needed, callerHandle is unused
            pLookup.lookupKind.needsRuntimeLookup = false;
            pLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.DelegateCtor(delegateTypeDesc, targetMethod));
        }

        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_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_ASSIGN_REF:
                    id = ReadyToRunHelper.WriteBarrier;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF:
                    id = ReadyToRunHelper.CheckedWriteBarrier;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_BYREF:
                    id = ReadyToRunHelper.ByRefWriteBarrier;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_BULK_WRITEBARRIER:
                    id = ReadyToRunHelper.BulkWriteBarrier;
                    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_GET_GCSTATIC_BASE:
                    id = ReadyToRunHelper.GenericGcStaticBase;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_GET_NONGCSTATIC_BASE:
                    id = ReadyToRunHelper.GenericNonGcStaticBase;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_GET_GCTHREADSTATIC_BASE:
                    id = ReadyToRunHelper.GenericGcTlsBase;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_GET_NONGCTHREADSTATIC_BASE:
                    id = ReadyToRunHelper.GenericNonGcTlsBase;
                    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_METHODDESC_TO_STUBRUNTIMEMETHOD:
                    id = ReadyToRunHelper.GetRuntimeMethodHandle;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD:
                    id = ReadyToRunHelper.GetRuntimeFieldHandle;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE:
                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_NEWFAST:
                    id = ReadyToRunHelper.NewObject;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_DIRECT:
                    id = ReadyToRunHelper.NewArray;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_MAYBEFROZEN:
                    id = ReadyToRunHelper.NewMaybeFrozenArray;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_NEWFAST_MAYBEFROZEN:
                    id = ReadyToRunHelper.NewMaybeFrozenObject;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_VIRTUAL_FUNC_PTR:
                    id = ReadyToRunHelper.VirtualFuncPtr;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR:
                    id = ReadyToRunHelper.VirtualFuncPtr;
                    break;
                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_LNG2FLT:
                    id = ReadyToRunHelper.Lng2Flt;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ULNG2FLT:
                    id = ReadyToRunHelper.ULng2Flt;
                    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_FLTREM:
                    id = ReadyToRunHelper.FltRem;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_DBLREM:
                    id = ReadyToRunHelper.DblRem;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_CHKCASTANY:
                    id = ReadyToRunHelper.CheckCastAny;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY:
                    id = ReadyToRunHelper.CheckInstanceAny;
                    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_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_ESI:
                    id = ReadyToRunHelper.WriteBarrier_ESI;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF_EDI:
                    id = ReadyToRunHelper.WriteBarrier_EDI;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF_EBP:
                    id = ReadyToRunHelper.WriteBarrier_EBP;
                    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_ESI:
                    id = ReadyToRunHelper.CheckedWriteBarrier_ESI;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF_EDI:
                    id = ReadyToRunHelper.CheckedWriteBarrier_EDI;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_CHECKED_ASSIGN_REF_EBP:
                    id = ReadyToRunHelper.CheckedWriteBarrier_EBP;
                    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_STACK_PROBE:
                    id = ReadyToRunHelper.StackProbe;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_POLL_GC:
                    id = ReadyToRunHelper.GCPoll;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_GETCURRENTMANAGEDTHREADID:
                    id = ReadyToRunHelper.GetCurrentManagedThreadId;
                    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_ALLOC_CONTINUATION:
                    id = ReadyToRunHelper.AllocContinuation;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_ALLOC_CONTINUATION_METHOD:
                    id = ReadyToRunHelper.AllocContinuationMethod;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_ALLOC_CONTINUATION_CLASS:
                    id = ReadyToRunHelper.AllocContinuationClass;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_INITCLASS:
                    id = ReadyToRunHelper.InitClass;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_INITINSTCLASS:
                    id = ReadyToRunHelper.InitInstClass;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_THROW_ARGUMENTEXCEPTION:
                    id = ReadyToRunHelper.ThrowArgument;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROW_ARGUMENTOUTOFRANGEEXCEPTION:
                    id = ReadyToRunHelper.ThrowArgumentOutOfRange;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED:
                    id = ReadyToRunHelper.ThrowPlatformNotSupported;
                    break;
                case CorInfoHelpFunc.CORINFO_HELP_THROW_NOT_IMPLEMENTED:
                    id = ReadyToRunHelper.ThrowNotImplemented;
                    break;

                case CorInfoHelpFunc.CORINFO_HELP_GETSYNCFROMCLASSHANDLE:
                case CorInfoHelpFunc.CORINFO_HELP_GETCLASSFROMMETHODPARAM:
                case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL:
                case CorInfoHelpFunc.CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL:
                case CorInfoHelpFunc.CORINFO_HELP_GETREFANY:
                case CorInfoHelpFunc.CORINFO_HELP_NEW_MDARR_RARE:
                // For x86 tailcall where helper is required we need runtime JIT.
                case CorInfoHelpFunc.CORINFO_HELP_TAILCALL:
                // DirectOnThreadLocalData helper is used at runtime during R2R fixup resolution, not during R2R compilation
                case CorInfoHelpFunc.CORINFO_HELP_GETDIRECTONTHREADLOCALDATA_NONGCTHREADSTATIC_BASE:
                    throw new RequiresRuntimeJitException(ftnNum.ToString());

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

            return _compilation.NodeFactory.GetReadyToRunHelperCell(id);
        }

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

            var entrypoint = _compilation.NodeFactory.MethodEntrypoint(
                new MethodWithToken(
                    method,
                    _compilation.NodeFactory.Resolver.GetModuleTokenForMethod(method, true, true),
                    null,
                    false,
                    MethodBeingCompiled),
                false,
                false,
                false);
            pResult = CreateConstLookupToSymbol(entrypoint);
        }

        private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* declaredCalleeHnd, CORINFO_METHOD_STRUCT_* exactCalleeHnd, bool fIsTailPrefix)
        {
            if (!fIsTailPrefix)
            {
                MethodDesc caller = HandleToObject(callerHnd);

                // Do not tailcall out of the entry point as it results in a confusing debugger experience.
                if (caller is EcmaMethod em && em.Module.EntryPoint == caller)
                {
                    return false;
                }

                // 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")
                if (caller.IsNoInlining)
                {
                    // NOTE: we don't have to handle NoOptimization here, because JIT is not expected
                    // to emit fast tail calls if optimizations are disabled.
                    return false;
                }

                // Methods with StackCrawlMark depend on finding their caller on the stack.
                // If we tail call one of these guys, they get confused.  For lack of
                // a better way of identifying them, we use DynamicSecurity attribute to identify
                // them.
                //
                MethodDesc callee = exactCalleeHnd == null ? null : HandleToObject(exactCalleeHnd);
                if (callee != null && callee.RequireSecObject)
                {
                    return false;
                }
            }

            return true;
        }

        private FieldWithToken ComputeFieldWithToken(FieldDesc field, ref CORINFO_RESOLVED_TOKEN pResolvedToken)
        {
            ModuleToken token = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation);
            return new FieldWithToken(field, token, strippedInstantiation);
        }

        private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing)
        {
            ModuleToken token = _HandleToModuleToken(ref pResolvedToken, method, out TypeSystemEntity context, ref constrainedType, out bool strippedInstantiation);

            TypeDesc devirtualizedMethodOwner = null;
            if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod)
            {
                devirtualizedMethodOwner = HandleToObject(pResolvedToken.hClass);
            }

            // For crossgen-generated IL stubs, we may only have access to the method token for the typical method definition.
            // This means that the owning type of the method may not be encoded in the method token.
            // bool methodTokenMightNotEncodeOwningType = MethodBeingCompiled.IsCompilerGeneratedILBodyForAsync();
            return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, genericContextObject: context, devirtualizedMethodOwner: devirtualizedMethodOwner, forceOwningTypeFromMethodDesc: strippedInstantiation);

            ModuleToken _HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, MethodDesc methodDesc, out TypeSystemEntity context, ref TypeDesc constrainedType, out bool strippedInstantiation)
            {
                if (methodDesc != null && (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(methodDesc)
                    || (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod)
                    || methodDesc.IsPInvoke))
                {
                    if ((CorTokenType)(unchecked((uint)pResolvedToken.token) & 0xFF000000u) == CorTokenType.mdtMethodDef &&
                        methodDesc?.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod)
                    {
                        mdToken token = (mdToken)MetadataTokens.GetToken(ecmaMethod.Handle);

                        // This is used for de-virtualization of non-generic virtual methods, and should be treated
                        // as a the methodDesc parameter defining the exact OwningType, not doing resolution through the token.
                        context = null;
                        constrainedType = null;
                        strippedInstantiation = false;

                        return new ModuleToken(ecmaMethod.Module, token);
                    }
                }

                if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_ResolvedStaticVirtualMethod)
                {
                    context = null;
                }
                else
                {
                    context = entityFromContext(pResolvedToken.tokenContext);
                }

                return HandleToModuleToken(ref pResolvedToken, out strippedInstantiation);
            }
        }

        private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken, out bool strippedInstantiation)
        {
            mdToken token = pResolvedToken.token;
            var methodIL = HandleToObject(pResolvedToken.tokenScope);
            IEcmaModule module;

            // If the method body is synthetized by the compiler (the definition of the MethodIL is not
            // an EcmaMethodIL), the tokens in the MethodIL are not actual tokens: they're just
            // "per-MethodIL unique cookies". For ready to run, we need to be able to get to an actual
            // token to refer to the result of token lookup in the R2R fixups; we replace the token
            // token to refer to the result of token lookup in the R2R fixups.
            //
            // We replace the token with the token of the ECMA entity. This only works for **types/members
            // within the current version bubble**, but this happens to be good enough because
            // we only do this replacement within CoreLib to replace method bodies in places
            // that we cannot express in C# right now and for p/invokes in large version bubbles).
            MethodILScope methodILDef = methodIL.GetMethodILScopeDefinition();
            bool isFauxMethodIL = !(methodILDef is IEcmaMethodIL);
            if (isFauxMethodIL)
            {
                object resultDef = methodILDef.GetObject((int)pResolvedToken.token);

                if (resultDef is MethodDesc resultMethod)
                {
                    // It's okay to strip the instantiation away because we don't need a MethodSpec
                    // token - SignatureBuilder will generate the generic method signature
                    // using instantiation parameters from the MethodDesc entity.
                    // However, we need to make note of this to indicate that the OwningType of the
                    // method should be inferred from the MethodDesc entity rather than the token.
                    var primaryMethod = resultMethod.GetPrimaryMethodDesc();
                    resultMethod = primaryMethod.GetTypicalMethodDefinition();
                    strippedInstantiation = resultMethod != primaryMethod;

                    if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(resultMethod.OwningType))
                    {
                        ModuleToken result = _compilation.NodeFactory.Resolver.GetModuleTokenForMethod(resultMethod, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
                        return result;
                    }
                    Debug.Assert(resultMethod is EcmaMethod);
                    token = (mdToken)MetadataTokens.GetToken(((EcmaMethod)resultMethod).Handle);
                    module = ((EcmaMethod)resultMethod).Module;
                }
                else if (resultDef is FieldDesc resultField)
                {
                    // It's okay to strip the instantiation away because we don't need the
                    // instantiated MemberRef token - SignatureBuilder will generate the generic
                    // field signature using instantiation parameters from the FieldDesc entity.
                    resultField = resultField.GetTypicalFieldDefinition();
                    strippedInstantiation = resultField != resultDef;

                    if (!(_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(resultField.OwningType) || resultField.OwningType.IsNonVersionable()))
                    {
                        ModuleToken result = _compilation.NodeFactory.Resolver.GetModuleTokenForField(resultField, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
                        return result;
                    }
                    Debug.Assert(resultField is EcmaField);
                    token = (mdToken)MetadataTokens.GetToken(((EcmaField)resultField).Handle);
                    module = ((EcmaField)resultField).Module;
                }
                else
                {
                    // We don't strip the instantiation for instantiated types in GetModuleTokenForType
                    strippedInstantiation = false;
                    return GetModuleTokenForType((TypeSystemEntity)resultDef);
                }
            }
            else
            {
                strippedInstantiation = false;
                module = ((IEcmaMethodIL)methodILDef).Module;
            }

            return new ModuleToken(module, token);

            ModuleToken GetModuleTokenForType(TypeSystemEntity resultDef)
            {
                switch (resultDef)
                {
                    case SignatureMethodVariable sigMethod:
                        Debug.Assert(sigMethod.Index == 0);
                        module = (EcmaModule)((MetadataType)methodILDef.OwningMethod.OwningType).Module;
                        token = FindGenericMethodArgTypeSpec((EcmaModule)module);
                        return new ModuleToken(module, token);
                    case ParameterizedType paramType:
                        return  _compilation.NodeFactory.Resolver.GetModuleTokenForType(paramType, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
                    case EcmaType ecmaType:
                        if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(ecmaType))
                        {
                            return  _compilation.NodeFactory.Resolver.GetModuleTokenForType(ecmaType, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
                        }
                        token = (mdToken)MetadataTokens.GetToken(ecmaType.Handle);
                        module = ecmaType.Module;
                        return new ModuleToken(module, token);
                    case TypeDesc typeDesc:
                        return _compilation.NodeFactory.Resolver.GetModuleTokenForType(typeDesc, allowDynamicallyCreatedReference: true, throwIfNotFound: true);
                    default:
                        throw new NotImplementedException($"Unsupported token resolution for {resultDef.GetType()}");
                }
            }
        }

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

            Debug.Assert(methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL
                // We may be calling this from emptyStringLiteral for a method inlined into a thunk
                || metaTok == (mdToken)CorTokenType.mdtString);
            if (metaTok == (mdToken)CorTokenType.mdtString)
            {
                ISymbolNode str = _compilation.SymbolNodeFactory.StringLiteral(
                    new ModuleToken(_compilation.NodeFactory.ManifestMetadataTable._mutableModule, metaTok));
                ppValue = (void*)ObjectToHandle(str);
                return InfoAccessType.IAT_PPVALUE;
            }

            IEcmaModule metadataModule = ((IEcmaMethodIL)methodIL.GetMethodILScopeDefinition()).Module;
            ISymbolNode stringObject = _compilation.SymbolNodeFactory.StringLiteral(
                new ModuleToken(metadataModule, metaTok));
            ppValue = (void*)ObjectToHandle(stringObject);
            return InfoAccessType.IAT_PPVALUE;
        }

        enum EHInfoFields
        {
            Flags = 0,
            TryOffset = 1,
            TryEnd = 2,
            HandlerOffset = 3,
            HandlerEnd = 4,
            ClassTokenOrOffset = 5,

            Length
        }

        private ObjectNode.ObjectData EncodeEHInfo()
        {
            int totalClauses = _ehClauses.Length;
            byte[] ehInfoData = new byte[(int)EHInfoFields.Length * sizeof(uint) * totalClauses];

            for (int i = 0; i < totalClauses; i++)
            {
                ref CORINFO_EH_CLAUSE clause = ref _ehClauses[i];
                int clauseOffset = (int)EHInfoFields.Length * sizeof(uint) * i;
                CORINFO_EH_CLAUSE_FLAGS flags = clause.Flags;
                uint classTokenOrOffset = clause.ClassTokenOrOffset;
                if (flags == CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_NONE
                    && MethodBeingCompiled.IsCompilerGeneratedILBodyForAsync()
                    && ((TypeDesc)ResolveTokenInScope(_compilation.GetMethodIL(MethodBeingCompiled), MethodBeingCompiled, (mdToken)classTokenOrOffset)).IsWellKnownType(WellKnownType.Exception))
                {
                    flags |= CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_R2R_SYSTEM_EXCEPTION;
                    classTokenOrOffset = 0;
                }
                Array.Copy(BitConverter.GetBytes((uint)flags), 0, ehInfoData, clauseOffset + (int)EHInfoFields.Flags * sizeof(uint), sizeof(uint));
                Array.Copy(BitConverter.GetBytes((uint)clause.TryOffset), 0, ehInfoData, clauseOffset + (int)EHInfoFields.TryOffset * sizeof(uint), sizeof(uint));
                // JIT in fact returns the end offset in the length field
                Array.Copy(BitConverter.GetBytes((uint)(clause.TryLength)), 0, ehInfoData, clauseOffset + (int)EHInfoFields.TryEnd * sizeof(uint), sizeof(uint));
                Array.Copy(BitConverter.GetBytes((uint)clause.HandlerOffset), 0, ehInfoData, clauseOffset + (int)EHInfoFields.HandlerOffset * sizeof(uint), sizeof(uint));
                Array.Copy(BitConverter.GetBytes((uint)(clause.HandlerLength)), 0, ehInfoData, clauseOffset + (int)EHInfoFields.HandlerEnd * sizeof(uint), sizeof(uint));
                Array.Copy(BitConverter.GetBytes(classTokenOrOffset), 0, ehInfoData, clauseOffset + (int)EHInfoFields.ClassTokenOrOffset * sizeof(uint), sizeof(uint));
            }
            return new ObjectNode.ObjectData(ehInfoData, Array.Empty<Relocation>(), alignment: 1, definedSymbols: Array.Empty<ISymbolDefinitionNode>());
        }

        /// <summary>
        /// Create a NativeVarInfo array; a table from native code range to variable location
        /// on the stack / in a specific register.
        /// </summary>
        private void setVars(CORINFO_METHOD_STRUCT_* ftn, uint cVars, NativeVarInfo* vars)
        {
            Debug.Assert(_debugVarInfos == null);

            if (cVars == 0)
                return;

            _debugVarInfos = new NativeVarInfo[cVars];
            for (int i = 0; i < cVars; i++)
            {
                _debugVarInfos[i] = vars[i];
            }

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

        /// <summary>
        /// Create a DebugLocInfo array; a table from native offset to IL offset.
        /// </summary>
        private void setBoundaries(CORINFO_METHOD_STRUCT_* ftn, uint cMap, OffsetMapping* pMap)
        {
            Debug.Assert(_debugLocInfos == null);

            _debugLocInfos = new OffsetMapping[cMap];
            for (int i = 0; i < cMap; i++)
            {
                _debugLocInfos[i] = pMap[i];
            }

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

        private void PublishEmptyCode()
        {
            _methodCodeNode.SetCode(new ObjectNode.ObjectData(Array.Empty<byte>(), null, 1, Array.Empty<ISymbolDefinitionNode>()));
            _methodCodeNode.InitializeFrameInfos(Array.Empty<FrameInfo>());
            _methodCodeNode.InitializeColdFrameInfos(Array.Empty<FrameInfo>());
        }

        private CorInfoHelpFunc getCastingHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool fThrowing)
        {
            return fThrowing ? CorInfoHelpFunc.CORINFO_HELP_CHKCASTANY : CorInfoHelpFunc.CORINFO_HELP_ISINSTANCEOFANY;
        }

        private CorInfoHelpFunc getNewHelper(CORINFO_CLASS_STRUCT_* classHandle, ref bool pHasSideEffects)
        {
            TypeDesc type = HandleToObject(classHandle);
            MetadataType metadataType = type as MetadataType;
            if (metadataType != null && metadataType.IsAbstract)
            {
                ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramDefault);
            }

            pHasSideEffects = type.HasFinalizer;

            // If the type isn't within the version bubble, it could gain a finalizer. Always treat it as if it has a finalizer
            if (!pHasSideEffects && !_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(type))
                pHasSideEffects = true;

            return CorInfoHelpFunc.CORINFO_HELP_NEWFAST;
        }

        private CorInfoHelpFunc getNewArrHelper(CORINFO_CLASS_STRUCT_* arrayCls)
        {
            return CorInfoHelpFunc.CORINFO_HELP_NEWARR_1_DIRECT;
        }

        private bool IsClassPreInited(TypeDesc type)
        {
            if (type.IsGenericDefinition)
            {
                return true;
            }

            var uninstantiatedType = type.GetTypeDefinition();

            if (_preInitedTypes.TryGetValue(uninstantiatedType, out bool preInited))
            {
                return preInited;
            }

            preInited = ComputeIsClassPreInited(type);
            _preInitedTypes.Add(uninstantiatedType, preInited);
            return preInited;

            static bool ComputeIsClassPreInited(TypeDesc type)
            {
                if (type.IsGenericDefinition)
                {
                    return true;
                }

                if (type.HasStaticConstructor)
                {
                    return false;
                }
                if (IsDynamicStaticsOrHasBoxedRegularStatics(type))
                {
                    return false;
                }
                return true;
            }

            static bool IsDynamicStaticsOrHasBoxedRegularStatics(TypeDesc type)
            {
                bool typeHasInstantiation = type.HasInstantiation;
                foreach (FieldDesc field in type.GetFields())
                {
                    if (!field.IsStatic || field.IsLiteral)
                        continue;

                    if (typeHasInstantiation)
                        return true; // Dynamic statics

                    if (!field.HasRva &&
                        !field.IsThreadStatic &&
                        field.FieldType.IsValueType &&
                        !field.FieldType.UnderlyingType.IsPrimitive)
                    {
                        return true; // HasBoxedRegularStatics
                    }
                }

                return false;
            }
        }

        private bool IsGenericTooDeeplyNested(Instantiation instantiation, int nestingLevel)
        {
            const int MaxInstatiationNesting = 10;

            if (nestingLevel == MaxInstatiationNesting)
            {
                return true;
            }

            foreach (TypeDesc instantiationType in instantiation)
            {
                if (instantiationType.HasInstantiation && IsGenericTooDeeplyNested(instantiationType.Instantiation, nestingLevel + 1))
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsGenericTooDeeplyNested(Instantiation instantiation)
        {
            return IsGenericTooDeeplyNested(instantiation, 0);
        }

        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);
            MethodDesc callerMethod = HandleToObject(callerHandle);

            if (field.Offset.IsIndeterminate)
                throw new RequiresRuntimeJitException(field);

            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.IsStatic)
            {
                fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_STATIC;

                if (field.FieldType.IsValueType && field.HasGCStaticBase && !field.HasRva)
                {
                    // statics of struct types are stored as implicitly boxed in CoreCLR i.e.
                    // we need to modify field access flags appropriately
                    fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_STATIC_IN_HEAP;
                }

                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_RELOCATABLE;
                    fieldOffset = 0;
                    pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.RvaFieldAddress(ComputeFieldWithToken(field, ref pResolvedToken)));

                    // We are not going through a helper. The constructor has to be triggered explicitly.
                    if (!IsClassPreInited(field.OwningType))
                    {
                        fieldFlags |= CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_INITCLASS;
                    }
                }
                else if (field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any) || _compilation.NodeFactory.Target.IsWasm)
                {
                    // 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_GENERICS_STATIC_HELPER;
                    if (field.IsThreadStatic)
                    {
                        pResult->helper = (field.HasGCStaticBase ?
                            CorInfoHelpFunc.CORINFO_HELP_GET_GCTHREADSTATIC_BASE :
                            CorInfoHelpFunc.CORINFO_HELP_GET_NONGCTHREADSTATIC_BASE);
                    }
                    else
                    {
                        pResult->helper = (field.HasGCStaticBase ?
                            CorInfoHelpFunc.CORINFO_HELP_GET_GCSTATIC_BASE :
                            CorInfoHelpFunc.CORINFO_HELP_GET_NONGCSTATIC_BASE);
                    }

                    if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && (fieldOffset <= FieldFixupSignature.MaxCheckableOffset))
                    {
                        // ENCODE_CHECK_FIELD_OFFSET
                        AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                    }
                }
                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)
                    {
                        if (field.HasGCStaticBase)
                        {
                            pResult->helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_THREADSTATIC_BASE;
                            helperId = ReadyToRunHelperId.GetThreadStaticBase;
                        }
                        else
                        {
                            pResult->helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NONGCTHREADSTATIC_BASE;
                            helperId = ReadyToRunHelperId.GetThreadNonGcStaticBase;
                        }
                    }
                    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 (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(field.OwningType) &&
                        fieldAccessor == CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER)
                    {
                        PreventRecursiveFieldInlinesOutsideVersionBubble(field, callerMethod);

                        // Static fields outside of the version bubble need to be accessed using the ENCODE_FIELD_ADDRESS
                        // helper in accordance with ZapInfo::getFieldInfo in CoreCLR.
                        pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.FieldAddress(ComputeFieldWithToken(field, ref pResolvedToken)));

                        fieldFlags &= ~CORINFO_FIELD_FLAGS.CORINFO_FLG_FIELD_STATIC_IN_HEAP; // The dynamic helper takes care of the unboxing
                        fieldOffset = 0;
                    }
                    else
                    if (helperId != ReadyToRunHelperId.Invalid)
                    {
                        if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && (fieldOffset <= FieldFixupSignature.MaxCheckableOffset))
                        {
                            // ENCODE_CHECK_FIELD_OFFSET
                            AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                        }

                        pResult->fieldLookup = CreateConstLookupToSymbol(
                            _compilation.SymbolNodeFactory.CreateReadyToRunHelper(helperId, field.OwningType)
                            );
                    }
                }
            }
            else
            {
                fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INSTANCE;
            }

            if (field.IsInitOnly)
                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;

            EncodeFieldBaseOffset(field, ref pResolvedToken, pResult, callerMethod);

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

        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 ceeInfoGetCallInfo(
            ref CORINFO_RESOLVED_TOKEN pResolvedToken,
            CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken,
            CORINFO_METHOD_STRUCT_* callerHandle,
            CORINFO_CALLINFO_FLAGS flags,
            CORINFO_CALL_INFO* pResult,
            out MethodDesc methodToCall,
            out MethodDesc targetMethod,
            out TypeDesc constrainedType,
            out MethodDesc originalMethod,
            out TypeDesc exactType,
            out MethodDesc callerMethod,
            out EcmaModule callerModule,
            out bool useInstantiatingStub)
        {
#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
            pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup = false;

            originalMethod = HandleToObject(pResolvedToken.hMethod);
            TypeDesc type = HandleToObject(pResolvedToken.hClass);

            if (type.IsGenericDefinition)
            {
                ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramSpecific, HandleToObject(callerHandle));
            }

            // This formula roughly corresponds to CoreCLR CEEInfo::resolveToken when calling GetMethodDescFromMethodSpec
            // (that always winds up by calling FindOrCreateAssociatedMethodDesc) at
            // https://github.com/dotnet/runtime/blob/17154bd7b8f21d6d8d6fca71b89d7dcb705ec32b/src/coreclr/vm/jitinterface.cpp#L1054
            // Its basic meaning is that shared generic methods always need instantiating
            // stubs as the shared generic code needs the method dictionary parameter that cannot
            // be provided by other means.
            useInstantiatingStub = originalMethod.OwningType.IsArray || originalMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstMethodDescArg();

            callerMethod = HandleToObject(callerHandle);

            if (originalMethod.HasInstantiation && IsGenericTooDeeplyNested(originalMethod.Instantiation))
            {
                throw new RequiresRuntimeJitException(callerMethod.ToString() + " -> " + originalMethod.ToString());
            }

            if (originalMethod.OwningType.HasInstantiation && IsGenericTooDeeplyNested(originalMethod.OwningType.Instantiation))
            {
                throw new RequiresRuntimeJitException(callerMethod.ToString() + " -> " + originalMethod.ToString());
            }

            if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(callerMethod) &&
                !_compilation.NodeFactory.CompilationModuleGroup.CrossModuleInlineable(callerMethod))
            {
                // We must abort inline attempts calling from outside of the version bubble being compiled
                // because we have no way to remap the token relative to the external module to the current version bubble.
                //
                // Unless this is a call made while compiling a CrossModuleInlineable method
                throw new RequiresRuntimeJitException(callerMethod.ToString() + " -> " + originalMethod.ToString());
            }

            callerModule = ((EcmaMethod)callerMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
            bool isCallVirt = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0;
            bool isLdftn = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0;
            bool isStaticVirtual = (originalMethod.Signature.IsStatic && originalMethod.IsVirtual);

            // 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 (originalMethod.Signature.IsStatic && isCallVirt)
            {
                ThrowHelper.ThrowMissingMethodException("?");
            }

            exactType = type;

            constrainedType = (pConstrainedResolvedToken != null ? HandleToObject(pConstrainedResolvedToken->hClass) : null);

            bool resolvedConstraint = false;
            bool forceUseRuntimeLookup = false;

            MethodDesc methodAfterConstraintResolution = originalMethod;
            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 && originalMethod.Name.SequenceEqual("GetHashCode"u8))
                {
                    MethodDesc methodOnUnderlyingType = constrainedType.UnderlyingType.FindVirtualFunctionTargetMethodOnObjectType(originalMethod);
                    Debug.Assert(methodOnUnderlyingType != null);

                    constrainedType = constrainedType.UnderlyingType;
                    originalMethod = methodOnUnderlyingType;
                }

                var dimResolution = DefaultInterfaceMethodResolution.None;
                MethodDesc directMethod = constrainedType.TryResolveConstraintMethodApprox(exactType, originalMethod, out forceUseRuntimeLookup, ref dimResolution);
                if (isStaticVirtual && directMethod != null)
                {
                    if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(directMethod))
                    {
                        directMethod = null;
                    }
                    else if (dimResolution == DefaultInterfaceMethodResolution.DefaultImplementation)
                    {
                        // For static virtual calls resolved through a default interface method,
                        // emit a Check_VirtualFunctionOverride fixup so that the R2R code is
                        // rejected at runtime if the resolution changes (e.g. a DIM override
                        // is added in an assembly outside the version bubble).
                        MethodWithToken declMethodWithToken = new MethodWithToken(originalMethod, HandleToModuleToken(ref pResolvedToken, out _), null, false, null);

                        ModuleToken moduleToken = _compilation.NodeFactory.Resolver.GetModuleTokenForMethod(directMethod, false, false);
                        Debug.Assert(!moduleToken.IsNull);
                        MethodWithToken implMethodWithToken = new MethodWithToken(directMethod, moduleToken, null, false, null);

                        AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckVirtualFunctionOverride(
                            declMethodWithToken, constrainedType, implMethodWithToken, true));
                    }
                }

                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

                    // This check for introducing an instantiation stub comes from the logic in
                    // MethodTable::TryResolveConstraintMethodApprox at
                    // https://github.com/dotnet/runtime/blob/17154bd7b8f21d6d8d6fca71b89d7dcb705ec32b/src/coreclr/vm/methodtable.cpp#L8628
                    // Its meaning is that, for direct method calls on value types, instantiating
                    // stubs are always needed in the presence of generic arguments as the generic
                    // dictionary cannot be passed through "this->method table".
                    useInstantiatingStub = directMethod.OwningType.IsValueType;

                    methodAfterConstraintResolution = directMethod;
                    // When resolved to a default interface method, the owning type is the interface
                    Debug.Assert(!methodAfterConstraintResolution.OwningType.IsInterface || (isStaticVirtual && methodAfterConstraintResolution.Signature.IsStatic));
                    resolvedConstraint = true;
                    pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM;

                    exactType = (isStaticVirtual && dimResolution == DefaultInterfaceMethodResolution.DefaultImplementation)
                        ? directMethod.OwningType
                        : constrainedType;
                    if (isStaticVirtual)
                    {
                        pResolvedToken.tokenType = CorInfoTokenKind.CORINFO_TOKENKIND_ResolvedStaticVirtualMethod;
                        constrainedType = null;
                    }
                }
                else if (isStaticVirtual)
                {
                    pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM;
                }
                else if (constrainedType.IsValueType)
                {
                    pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS;
                }
                else
                {
                    pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_DEREF_THIS;
                }
            }

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

            targetMethod = methodAfterConstraintResolution;
            bool constrainedTypeNeedsRuntimeLookup = (constrainedType != null && constrainedType.IsCanonicalSubtype(CanonicalFormKind.Any));

            if (targetMethod.HasInstantiation)
            {
                pResult->contextHandle = contextFromMethod(targetMethod);
                pResult->exactContextNeedsRuntimeLookup = constrainedTypeNeedsRuntimeLookup || targetMethod.IsSharedByGenericInstantiations;
            }
            else
            {
                pResult->contextHandle = contextFromType(exactType);
                pResult->exactContextNeedsRuntimeLookup = constrainedTypeNeedsRuntimeLookup || 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)
                {
                    var methodIL = HandleToObject(pResolvedToken.tokenScope);
                    var rawMethod = (MethodDesc)methodIL.GetMethodILScopeDefinition().GetObject((int)pResolvedToken.token);
                    if (IsTypeSpecForTypicalInstantiation(rawMethod.OwningType))
                    {
                        pResult->contextHandle = contextFromMethodBeingCompiled();
                    }
                }
            }

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

            bool directCall = false;
            bool resolvedCallVirt = false;
            bool callVirtCrossingVersionBubble = false;

            if (isStaticVirtual && !resolvedConstraint)
            {
                // Don't use direct calls for static virtual method calls unresolved at compile time
            }
            else if (isLdftn)
            {
                PrepareForUseAsAFunctionPointer(targetMethod);
                directCall = true;
            }
            else if (targetMethod.Signature.IsStatic)
            {
                // Static methods are always direct calls
                directCall = true;
            }
            else if (!isCallVirt || resolvedConstraint)
            {
                directCall = true;
            }
            else
            {
                bool devirt;

                // Check For interfaces before the bubble check
                // since interface methods shouldnt change from non-virtual to virtual between versions
                if (targetMethod.OwningType.IsInterface)
                {
                    // Handle interface methods specially because the Sealed bit has no meaning on interfaces.
                    devirt = !targetMethod.IsVirtual;
                }
                // if we are generating version resilient code
                // AND
                //    caller/callee are in different version bubbles
                // we have to apply more restrictive rules
                // These rules are related to the "inlining rules" as far as the
                // boundaries of a version bubble are concerned.
                // This check is different between CG1 and CG2. CG1 considers two types in the same version bubble
                // if their assemblies are in the same bubble, or if the NonVersionableTypeAttribute is present on the type.
                // CG2 checks a method cache that it builds with a bunch of new code.
                else if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(callerMethod) ||
                    // check the Typical TargetMethod, not the Instantiation
                    !_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(targetMethod.GetTypicalMethodDefinition()))
                {
                    // For version resiliency we won't de-virtualize all final/sealed method calls.  Because during a
                    // servicing event it is legal to unseal a method or type.
                    //
                    // Note that it is safe to devirtualize in the following cases, since a servicing event cannot later modify it
                    //  1) Callvirt on a virtual final method of a value type - since value types are sealed types as per ECMA spec
                    //  2) Delegate.Invoke() - since a Delegate is a sealed class as per ECMA spec
                    //  3) JIT intrinsics - since they have pre-defined behavior
                    devirt = targetMethod.OwningType.IsValueType ||
                        (targetMethod.OwningType.IsDelegate && targetMethod.Name.SequenceEqual("Invoke"u8)) ||
                        (targetMethod.OwningType.IsObject && targetMethod.Name.SequenceEqual("GetType"u8));

                    callVirtCrossingVersionBubble = true;
                }
                else
                {
                    devirt = !targetMethod.IsVirtual || targetMethod.IsFinal || targetMethod.OwningType.IsSealed();
                }

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

            methodToCall = targetMethod;
            bool isArrayConstructor = targetMethod.OwningType.IsArray && targetMethod.IsConstructor;
            MethodDesc canonMethod = (isArrayConstructor ? null : targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific));

            if (directCall)
            {
                bool isVirtualBehaviorUnresolved = (isCallVirt && !resolvedCallVirt || isStaticVirtual && !resolvedConstraint);

                // Direct calls to abstract methods are not allowed
                if (targetMethod.IsAbstract &&
                    // Compensate for always treating delegates as direct calls above
                    !(isLdftn && isVirtualBehaviorUnresolved))
                {
                    ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramSpecific, targetMethod);
                }

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

                // If the target method is resolved via constrained static virtual dispatch
                // And it requires an instParam, we do not have the generic dictionary infrastructure
                // to load the correct generic context arg via EmbedGenericHandle.
                // Instead, force the call to go down the CORINFO_CALL_CODE_POINTER code path
                // which should have somewhat inferior performance. This should only actually happen in the case
                // of shared generic code calling a shared generic implementation method, which should be rare.
                //
                // An alternative design would be to add a new generic dictionary entry kind to hold the MethodDesc
                // of the constrained target instead, and use that in some circumstances; however, implementation of
                // that design requires refactoring variuos parts of the JIT interface as well as
                // TryResolveConstraintMethodApprox. In particular we would need to be abled to embed a constrained lookup
                // via EmbedGenericHandle, as well as decide in TryResolveConstraintMethodApprox if the call can be made
                // via a single use of CORINFO_CALL_CODE_POINTER, or would be better done with a CORINFO_CALL + embedded
                // constrained generic handle, or if there is a case where we would want to use both a CORINFO_CALL and
                // embedded constrained generic handle. Given the current expected high performance use case of this feature
                // which is generic numerics which will always resolve to exact valuetypes, it is not expected that
                // the complexity involved would be worth the risk. Other scenarios are not expected to be as performance
                // sensitive.
                if (isStaticVirtual && pResult->exactContextNeedsRuntimeLookup)
                {
                    allowInstParam = false;
                }

                if (!allowInstParam && canonMethod != null && canonMethod.RequiresInstArg())
                {
                    useInstantiatingStub = true;
                }

                // We don't allow a JIT to call the code directly if a runtime lookup is
                // needed. This is the case if
                //     1. the scan of the call token indicated that it involves code sharing
                // AND 2. the method is an instantiating stub
                //
                // In these cases the correct instantiating stub is only found via a runtime lookup.
                //
                // Note that most JITs don't call instantiating stubs directly if they can help it -
                // they call the underlying shared code and provide the type context parameter
                // explicitly. However
                //    (a) some JITs may call instantiating stubs (it makes the JIT simpler) and
                //    (b) if the method is a remote stub then the EE will force the
                //        call through an instantiating stub and
                //    (c) constraint calls that require runtime context lookup are never resolved
                //        to underlying shared generic code

                const CORINFO_CALLINFO_FLAGS LdVirtFtnMask = CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN | CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT;
                bool unresolvedLdVirtFtn = ((flags & LdVirtFtnMask) == LdVirtFtnMask) && !resolvedCallVirt;

                if (isArrayConstructor)
                {
                    // 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->kind = CORINFO_CALL_KIND.CORINFO_CALL;
                }
                else if ((pResult->exactContextNeedsRuntimeLookup && useInstantiatingStub && (!allowInstParam || resolvedConstraint)) || forceUseRuntimeLookup)
                {
                    if (unresolvedLdVirtFtn)
                    {
                        // Compensate for always treating delegates as direct calls above.
                        // Dictionary lookup is computed in embedGenericHandle as part of the LDVIRTFTN code sequence
                        pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_LDVIRTFTN;
                    }
                    else
                    {
                        pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER;

                        // For reference types, the constrained type does not affect instance virtual method resolution
                        DictionaryEntryKind entryKind = (constrainedType != null && (constrainedType.IsValueType || !isCallVirt)
                            ? DictionaryEntryKind.ConstrainedMethodEntrySlot
                            : DictionaryEntryKind.MethodEntrySlot);

                        if (isStaticVirtual && exactType.HasInstantiation)
                        {
                            useInstantiatingStub = true;
                        }

                        ComputeRuntimeLookupForSharedGenericToken(entryKind, ref pResolvedToken, pConstrainedResolvedToken, originalMethod, HandleToObject(callerHandle), ref pResult->codePointerOrStubLookup);
                    }
                }
                else
                {
                    if (allowInstParam)
                    {
                        useInstantiatingStub = false;
                        methodToCall = canonMethod ?? methodToCall;
                    }

                    pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL;

                    // Compensate for always treating delegates as direct calls above
                    if (unresolvedLdVirtFtn)
                    {
                        pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_LDVIRTFTN;
                    }
                }
                pResult->nullInstanceCheck = resolvedCallVirt;
            }
            else if (isStaticVirtual)
            {
                pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL;
                pResult->nullInstanceCheck = false;

                // Always use an instantiating stub for unresolved constrained SVM calls as we cannot
                // always tell at compile time that a given SVM resolves to a method on a generic base
                // class and not requesting the instantiating stub makes the runtime transform the
                // owning type to its canonical equivalent that would need different codegen
                // (supplying the instantiation argument).
                if (!resolvedConstraint)
                {
                    if (pResult->exactContextNeedsRuntimeLookup)
                    {
                        throw new RequiresRuntimeJitException("EmbedGenericHandle currently doesn't support propagation of RUNTIME_LOOKUP or pConstrainedResolvedToken from ComputeRuntimeLookupForSharedGenericToken");
                        // ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind.DispatchStubAddrSlot, ref pResolvedToken, pConstrainedResolvedToken, originalMethod, ref pResult->codePointerOrStubLookup);
                        // useInstantiatingStub = false;
                    }
                    else
                    {
                        throw new RequiresRuntimeJitException("CanInline currently doesn't support propagation of constrained type so that we cannot reliably tell whether a SVM call can be inlined");
                        // Even if we decided to support SVMs unresolved at compile time, we'd still need to force the use of instantiating stub
                        // as we can't tell in advance whether the method will be runtime-resolved to a canonical representation.
                        // useInstantiatingStub = true;
                    }
                }
            }
            // All virtual calls which take method instantiations must
            // currently be implemented by an indirect call via a runtime-lookup
            // function pointer
            else if (targetMethod.HasInstantiation || _compilation.NodeFactory.Target.IsWasm) // WASM doesn't currently support the stub dispatch path
            {
                pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_LDVIRTFTN;  // stub dispatch can't handle generic method calls yet
                pResult->nullInstanceCheck = true;
            }
            // Non-interface dispatches go through the vtable.
            else if (!targetMethod.OwningType.IsInterface)
            {
                pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_STUB;
                pResult->nullInstanceCheck = true;

                // We'll special virtual calls to target methods in the corelib assembly when compiling in R2R mode, and generate fragile-NI-like callsites for improved performance. We
                // can do that because today we'll always service the corelib assembly and the runtime in one bundle. Any caller in the corelib version bubble can benefit from this
                // performance optimization.
                /* TODO-PERF, GitHub issue# 7168: uncommenting the conditional statement below enables
                ** VTABLE-based calls for Corelib (and maybe a larger framework version bubble in the
                ** future). Making it work requires construction of the method table in managed code
                ** matching the CoreCLR algorithm (MethodTableBuilder).
                if (MethodInSystemVersionBubble(callerMethod) && MethodInSystemVersionBubble(targetMethod))
                {
                    pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_VTABLE;
                }
                */
            }
            else
            {
                // At this point, we knew it is a virtual call to targetMethod,
                // If it is also a default interface method call, it should go through instantiating stub.
                useInstantiatingStub = useInstantiatingStub || (targetMethod.OwningType.IsInterface && !originalMethod.IsAbstract);
                // Insert explicit null checks for cross-version bubble non-interface calls.
                // It is required to handle null checks properly for non-virtual <-> virtual change between versions
                pResult->nullInstanceCheck = callVirtCrossingVersionBubble && !targetMethod.OwningType.IsInterface;
                pResult->kind = CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_STUB;

                // We can't make stub calls when we need exact information
                // for interface calls from shared code.

                if (pResult->exactContextNeedsRuntimeLookup)
                {
                    ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind.DispatchStubAddrSlot, ref pResolvedToken, null, originalMethod, HandleToObject(callerHandle), ref pResult->codePointerOrStubLookup);
                }
                else
                {
                    // We use an indirect call
                    pResult->codePointerOrStubLookup.constLookup.accessType = InfoAccessType.IAT_PVALUE;
                    pResult->codePointerOrStubLookup.constLookup.addr = null;
                }
            }

            pResult->hMethod = ObjectToHandle(methodToCall);

            // TODO: access checks
            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(type);

            pResult->methodFlags = getMethodAttribsInternal(methodToCall);

            pResult->wrapperDelegateInvoke = false;

            Get_CORINFO_SIG_INFO(methodToCall, &pResult->sig, scope: null, useInstantiatingStub);
        }

        private uint getMethodAttribs(CORINFO_METHOD_STRUCT_* ftn)
        {
            MethodDesc method = HandleToObject(ftn);
            return getMethodAttribsInternal(method);
            // OK, if the EE said we're not doing a stub dispatch then just return the kind to
        }

        private void PrepareForUseAsAFunctionPointer(MethodDesc method)
        {
            foreach (TypeDesc type in method.Signature)
            {
                if (type.IsValueType)
                {
                    classMustBeLoadedBeforeCodeIsRun(type);
                }
            }
        }

        private void classMustBeLoadedBeforeCodeIsRun(CORINFO_CLASS_STRUCT_* cls)
        {
            TypeDesc type = HandleToObject(cls);
            classMustBeLoadedBeforeCodeIsRun(type);
        }

        private void classMustBeLoadedBeforeCodeIsRun(TypeDesc type)
        {
            if (!type.IsPrimitive)
            {
                ISymbolNode node = _compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeHandle, type);
                AddPrecodeFixup(node);
            }
        }

        private static bool MethodSignatureIsUnstable(MethodSignature methodSig, out string unstableMessage)
        {
            foreach (TypeDesc t in methodSig)
            {
                DefType defType = t as DefType;

                if (defType != null)
                {
                    if (!defType.LayoutAbiStable)
                    {
                        unstableMessage = $"Abi unstable type {defType}";
                        return true;
                    }
                }
            }
            unstableMessage = null;
            return false;
        }

        private void UpdateConstLookupWithRequiresRuntimeJitSymbolIfNeeded(ref CORINFO_CONST_LOOKUP constLookup, MethodDesc method)
        {
            if (MethodSignatureIsUnstable(method.Signature, out string unstableMessage))
            {
                constLookup.addr = (void*)ObjectToHandle(new RequiresRuntimeJitIfUsedSymbol(unstableMessage + " calling " + method));
                constLookup.accessType = InfoAccessType.IAT_PVALUE;
            }
        }

        private void VerifyMethodSignatureIsStable(MethodSignature methodSig)
        {
            if (MethodSignatureIsUnstable(methodSig, out var unstableMessage))
            {
                throw new RequiresRuntimeJitException(unstableMessage);
            }
        }

        private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_CALLINFO_FLAGS flags, CORINFO_CALL_INFO* pResult)
        {
            MethodDesc methodToCall;
            MethodDesc targetMethod;
            TypeDesc constrainedType;
            MethodDesc originalMethod;
            TypeDesc exactType;
            MethodDesc callerMethod;
            EcmaModule callerModule;
            bool useInstantiatingStub;
            ceeInfoGetCallInfo(
                ref pResolvedToken,
                pConstrainedResolvedToken,
                callerHandle,
                flags,
                pResult,
                out methodToCall,
                out targetMethod,
                out constrainedType,
                out originalMethod,
                out exactType,
                out callerMethod,
                out callerModule,
                out useInstantiatingStub);

            if (callerMethod.HasInstantiation || callerMethod.OwningType.HasInstantiation)
            {
                _compilation.NodeFactory.DetectGenericCycles(callerMethod, methodToCall);
            }

            if (pResult->thisTransform == CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS)
            {
                // READYTORUN: FUTURE: Optionally create boxing stub at runtime
                // We couldn't resolve the constrained call into a valuetype instance method and we're asking the JIT
                // to box and do a virtual dispatch. If we were to allow the boxing to happen now, it could break future code
                // when the user adds a method to the valuetype that makes it possible to avoid boxing (if there is state
                // mutation in the method).

                // We allow this at least for primitives and enums because we control them
                // and we know there's no state mutation.
                //
                // We allow this for constrained calls to non-virtual methods, as its extremely unlikely they will become virtual (verified by fixup)
                //
                // TODO in the future we should consider allowing this for regular virtuals as well with a fixup that verifies the method wasn't implemented
                if (getTypeForPrimitiveValueClass(pConstrainedResolvedToken->hClass) != CorInfoType.CORINFO_TYPE_UNDEF)
                {
                    // allowed
                }
                else if (!originalMethod.IsVirtual && originalMethod.Context.GetWellKnownType(WellKnownType.Object) == originalMethod.OwningType)
                {
                    // alllowed, non-virtual method's on Object will never become virtual, and will also always trigger a BOX_THIS pattern
                }
                else if (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(constrainedType))
                {
                    // The constrained value type is within the version bubble, so target method will always require boxing
                }
                else
                {
                    throw new RequiresRuntimeJitException(pResult->thisTransform.ToString());
                }
            }

            // We validate the safety of the signature here, as it could have been adjusted
            // by virtual resolution during getCallInfo (virtual resolution could find a result using type equivalence)
            ValidateSafetyOfUsingTypeEquivalenceInSignature(targetMethod.GetTypicalMethodDefinition().Signature);

            // OK, if the EE said we're not doing a stub dispatch then just return the kind to
            // the caller.  No other kinds of virtual calls have extra information attached.
            switch (pResult->kind)
            {
                case CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_STUB:
                    {
                        if (pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup)
                        {
                            return;
                        }

                        pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(
                            _compilation.SymbolNodeFactory.InterfaceDispatchCell(
                                ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: null, unboxing: false),
                                MethodBeingCompiled));

                        // If the abi of the method isn't stable, this will cause a usage of the RequiresRuntimeJitSymbol, which will trigger a RequiresRuntimeJitException
                        UpdateConstLookupWithRequiresRuntimeJitSymbolIfNeeded(ref pResult->codePointerOrStubLookup.constLookup, targetMethod);
                    }
                    break;


                case CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER:
                    Debug.Assert(pResult->codePointerOrStubLookup.lookupKind.needsRuntimeLookup);
                    // Eagerly check abi stability here as no symbol usage can be used to delay the check
                    VerifyMethodSignatureIsStable(targetMethod.Signature);

                    // There is no easy way to detect method referenced via generic lookups in generated code.
                    // Report this method reference unconditionally.
                    // TODO: m_pImage->m_pPreloader->MethodReferencedByCompiledCode(pResult->hMethod);
                    return;

                case CORINFO_CALL_KIND.CORINFO_CALL:
                    {
                        // Constrained token is not interesting with this transforms
                        if (pResult->thisTransform != CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM)
                            constrainedType = null;

                        MethodDesc nonUnboxingMethod = methodToCall;
                        bool isUnboxingStub = methodToCall.IsUnboxingThunk();
                        if (isUnboxingStub)
                        {
                            nonUnboxingMethod = methodToCall.GetUnboxedMethod();
                        }
                        if (nonUnboxingMethod is IL.Stubs.PInvokeTargetNativeMethod rawPinvoke)
                        {
                            nonUnboxingMethod = rawPinvoke.Target;
                        }

                        if (methodToCall.OwningType.IsArray && methodToCall.IsConstructor)
                        {
                            pResult->codePointerOrStubLookup.constLookup = default;
                        }
                        else if (MethodBeingCompiled is AsyncResumptionStub resumptionStub
                            && nonUnboxingMethod == resumptionStub.TargetMethod)
                        {
                            // Async resumption stubs must call the exact code version that created
                            // the continuation, since the continuation layout is coupled to the
                            // compilation. Use a direct call to the compiled method body so tiering
                            // backpatching cannot redirect this call to a different code version.
                            MethodDesc compilableTarget = nonUnboxingMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                            MethodWithGCInfo targetCodeNode = _compilation.NodeFactory.CompiledMethodNode(compilableTarget);
                            pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(targetCodeNode);
                        }
                        else
                        {
                            // READYTORUN: FUTURE: Direct calls if possible
                            pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(
                                _compilation.NodeFactory.MethodEntrypoint(
                                    ComputeMethodWithToken(nonUnboxingMethod, ref pResolvedToken, constrainedType, unboxing: isUnboxingStub),
                                    isInstantiatingStub: useInstantiatingStub,
                                    isPrecodeImportRequired: (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0,
                                    isJumpableImportRequired: false));
                        }

                        // If the abi of the method isn't stable, this will cause a usage of the RequiresRuntimeJitSymbol, which will trigger a RequiresRuntimeJitException
                        UpdateConstLookupWithRequiresRuntimeJitSymbolIfNeeded(ref pResult->codePointerOrStubLookup.constLookup, targetMethod);
                    }
                    break;

                case CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_VTABLE:
                    // Only calls within the CoreLib version bubble support fragile NI codegen with vtable based calls, for better performance (because
                    // CoreLib and the runtime will always be updated together anyways - this is a special case)

                    // Eagerly check abi stability here as no symbol usage can be used to delay the check
                    VerifyMethodSignatureIsStable(targetMethod.Signature);
                    break;

                case CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_LDVIRTFTN:
                    if (!pResult->exactContextNeedsRuntimeLookup)
                    {
                        pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(
                            _compilation.NodeFactory.DynamicHelperCell(
                                ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: null, unboxing: false),
                                useInstantiatingStub));

                        Debug.Assert(!pResult->sig.hasTypeArg());
                    }
                    break;

                default:
                    throw new NotImplementedException(pResult->kind.ToString());
            }

            if (pResult->sig.hasTypeArg())
            {
                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)
                }
                else
                {
                    MethodDesc canonMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                    if (canonMethod.RequiresInstMethodDescArg())
                    {
                        pResult->instParamLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(
                            ReadyToRunHelperId.MethodDictionary,
                            ComputeMethodWithToken(targetMethod, ref pResolvedToken, constrainedType: constrainedType, unboxing: false)));
                    }
                    else
                    {
                        pResult->instParamLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.CreateReadyToRunHelper(
                            ReadyToRunHelperId.TypeDictionary,
                            exactType));
                    }
                }
            }
        }

        private void ComputeRuntimeLookupForSharedGenericToken(
            DictionaryEntryKind entryKind,
            ref CORINFO_RESOLVED_TOKEN pResolvedToken,
            CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken,
            MethodDesc templateMethod,
            MethodDesc callerHandle,
            ref CORINFO_LOOKUP pResultLookup)
        {
            Debug.Assert(callerHandle != null);

            pResultLookup.lookupKind.needsRuntimeLookup = true;

            ref CORINFO_RUNTIME_LOOKUP pResult = ref pResultLookup.runtimeLookup;
            pResult.signature = null;

            pResult.indirectFirstOffset = false;
            pResult.indirectSecondOffset = false;

            // Unless we decide otherwise, just do the lookup via a helper function
            pResult.indirections = CORINFO.USEHELPER;
            pResult.helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GENERIC_HANDLE;
            pResult.sizeOffset = CORINFO.CORINFO_NO_SIZE_CHECK;

            MethodDesc contextMethod = callerHandle;

            // There is a pathological case where invalid IL refereces __Canon type directly, but there is no dictionary availabled to store the lookup.
            if (!contextMethod.IsSharedByGenericInstantiations)
            {
                ThrowHelper.ThrowInvalidProgramException();
            }

            if (contextMethod.RequiresInstMethodDescArg())
            {
                pResultLookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_METHODPARAM;
            }
            else
            {
                if (contextMethod.RequiresInstMethodTableArg())
                    pResultLookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_CLASSPARAM;
                else
                    pResultLookup.lookupKind.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_THISOBJ;
            }

            ReadyToRunHelperId helperId;
            TypeDesc constrainedType = null;
            switch (entryKind)
            {
                case DictionaryEntryKind.DeclaringTypeHandleSlot:
                    Debug.Assert(templateMethod != null);
                    helperId = ReadyToRunHelperId.DeclaringTypeHandle;
                    break;

                case DictionaryEntryKind.TypeHandleSlot:
                    helperId = ReadyToRunHelperId.TypeHandle;
                    break;

                case DictionaryEntryKind.MethodDescSlot:
                case DictionaryEntryKind.MethodEntrySlot:
                case DictionaryEntryKind.ConstrainedMethodEntrySlot:
                case DictionaryEntryKind.DispatchStubAddrSlot:
                {
                    if (entryKind == DictionaryEntryKind.MethodDescSlot)
                    {
                        helperId = ReadyToRunHelperId.MethodHandle;
                    }
                    else if (entryKind == DictionaryEntryKind.MethodEntrySlot || entryKind == DictionaryEntryKind.ConstrainedMethodEntrySlot)
                    {
                        if (pConstrainedResolvedToken != null)
                        {
                            constrainedType = (TypeDesc)GetRuntimeDeterminedObjectForToken(ref *pConstrainedResolvedToken);
                            _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, constrainedType);
                        }
                        helperId = ReadyToRunHelperId.MethodEntry;
                    }
                    else
                    {
                        helperId = ReadyToRunHelperId.VirtualDispatchCell;
                    }
                    break;
                }

                case DictionaryEntryKind.FieldDescSlot:
                    helperId = ReadyToRunHelperId.FieldHandle;
                    break;

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

            object helperArg = GetRuntimeDeterminedObjectForToken(ref pResolvedToken);
            if (helperArg is MethodDesc methodDesc)
            {
                var methodIL = HandleToObject(pResolvedToken.tokenScope);
                MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget();
                // We shouldn't be needing shared generics in resumption stubs - generics info should all be stored in the continuation
                Debug.Assert(MethodBeingCompiled is not AsyncResumptionStub);
                _compilation.NodeFactory.DetectGenericCycles(MethodBeingCompiled, sharedMethod);
                helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation), constrainedType, unboxing: false, genericContextObject: sharedMethod, forceOwningTypeFromMethodDesc: strippedInstantiation);
            }
            else if (helperArg is FieldDesc fieldDesc)
            {
                ModuleToken fieldToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation);
                Debug.Assert(!strippedInstantiation);
                helperArg = new FieldWithToken(fieldDesc, fieldToken, forceOwningTypeNotDerivedFromToken: strippedInstantiation);
            }

            var methodContext = new GenericContext(callerHandle);
            ISymbolNode helper = _compilation.SymbolNodeFactory.GenericLookupHelper(
                pResultLookup.lookupKind.runtimeLookupKind,
                helperId,
                helperArg,
                methodContext);

            pResultLookup.runtimeLookup.helperEntryPoint = CreateConstLookupToSymbol(helper);

            // For R2R compilations, we don't generate the dictionary lookup signatures (dictionary lookups are done in a
            // different way that is more version resilient... plus we can't have pointers to existing MTs/MDs in the sigs)
        }

        private void ceeInfoEmbedGenericHandle(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

            bool runtimeLookup = false;
            MethodDesc templateMethod = 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);
                templateMethod = md;

                // Runtime lookup is only required for stubs. Regular entrypoints are always the same shared MethodDescs.
                runtimeLookup = md.IsSharedByGenericInstantiations;
            }
            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;

                runtimeLookup = fd.IsStatic && td.IsCanonicalSubtype(CanonicalFormKind.Specific);
            }
            else
            {
                TypeDesc td = HandleToObject(pResolvedToken.hClass);

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

                if (fEmbedParent && pResolvedToken.hMethod != null)
                {
                    MethodDesc declaringMethod = HandleToObject(pResolvedToken.hMethod);
                    if (declaringMethod.OwningType.GetClosestDefType() != td.GetClosestDefType())
                    {
                        //
                        // The method type may point to a sub-class of the actual class that declares the method.
                        // It is important to embed the declaring type in this case.
                        //

                        templateMethod = declaringMethod;
                        pResult.compileTimeHandle = (CORINFO_GENERIC_STRUCT_*)ObjectToHandle(declaringMethod.OwningType);
                    }
                }

                // IsSharedByGenericInstantiations would not work here. The runtime lookup is required
                // even for standalone generic variables that show up as __Canon here.
                runtimeLookup = td.IsCanonicalSubtype(CanonicalFormKind.Specific);
            }

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

            if (runtimeLookup)
            {
                DictionaryEntryKind entryKind = DictionaryEntryKind.EmptySlot;
                switch (pResult.handleType)
                {
                    case CorInfoGenericHandleType.CORINFO_HANDLETYPE_CLASS:
                        entryKind = (templateMethod != null ? DictionaryEntryKind.DeclaringTypeHandleSlot : DictionaryEntryKind.TypeHandleSlot);
                        break;
                    case CorInfoGenericHandleType.CORINFO_HANDLETYPE_METHOD:
                        entryKind = DictionaryEntryKind.MethodDescSlot;
                        break;
                    case CorInfoGenericHandleType.CORINFO_HANDLETYPE_FIELD:
                        entryKind = DictionaryEntryKind.FieldDescSlot;
                        break;
                    default:
                        throw new NotImplementedException(pResult.handleType.ToString());
                }

                ComputeRuntimeLookupForSharedGenericToken(entryKind, ref pResolvedToken, pConstrainedResolvedToken: null, templateMethod, HandleToObject(callerHandle), ref pResult.lookup);
            }
            else
            {
                // If the target is not shared then we've already got our result and
                // can simply do a static look up
                pResult.lookup.lookupKind.needsRuntimeLookup = false;

                pResult.lookup.constLookup.handle = pResult.compileTimeHandle;
                pResult.lookup.constLookup.accessType = InfoAccessType.IAT_VALUE;
            }
        }

        private CORINFO_CLASS_STRUCT_* embedClassHandle(CORINFO_CLASS_STRUCT_* handle, ref void* ppIndirection)
        {
            TypeDesc type = HandleToObject(handle);
            // Continuations require special handling to encode the layout of the type.
            // The TypeSignature format used in TypeHandles is not sufficient for encoding specific continuation layouts
            // PrecodeHelper(
            //   TypeHandle(ReadyToRunFixupKind.ContinuationLayout)
            // )
            if (type is AsyncContinuationType act)
            {
                Import import = (Import)_compilation.SymbolNodeFactory.ContinuationTypeSymbol(act);
                ppIndirection = (void*)ObjectToHandle(import);
                return null;
            }
            if (!_compilation.CompilationModuleGroup.VersionsWithType(type))
                throw new RequiresRuntimeJitException(type.ToString());

            Import typeHandleImport = (Import)_compilation.SymbolNodeFactory.CreateReadyToRunHelper(ReadyToRunHelperId.TypeHandle, type);
            Debug.Assert(typeHandleImport.RepresentsIndirectionCell);
            ppIndirection = (void*)ObjectToHandle(typeHandleImport);
            return null;
        }

        private void embedGenericHandle(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool fEmbedParent, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_GENERICHANDLE_RESULT pResult)
        {
            ceeInfoEmbedGenericHandle(ref pResolvedToken, fEmbedParent, callerHandle, ref pResult);

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

            if (pResult.lookup.lookupKind.needsRuntimeLookup)
            {
                if (pResult.handleType == CorInfoGenericHandleType.CORINFO_HANDLETYPE_METHOD)
                {
                    // There is no easy way to detect method referenced via generic lookups in generated code.
                    // Report this method reference unconditionally.
                    // TODO: m_pImage->m_pPreloader->MethodReferencedByCompiledCode((CORINFO_METHOD_HANDLE)pResult->compileTimeHandle);
                }
            }
            else
            {
                ISymbolNode symbolNode;

                switch (pResult.handleType)
                {
                    case CorInfoGenericHandleType.CORINFO_HANDLETYPE_CLASS:
                        symbolNode = _compilation.SymbolNodeFactory.CreateReadyToRunHelper(
                            ReadyToRunHelperId.TypeHandle,
                            HandleToObject(pResolvedToken.hClass));
                        break;

                    case CorInfoGenericHandleType.CORINFO_HANDLETYPE_METHOD:
                        {
                            MethodDesc md = HandleToObject(pResolvedToken.hMethod);
                            TypeDesc td = HandleToObject(pResolvedToken.hClass);

                            bool unboxingStub = false;
                            //
                            // This logic should be kept in sync with MethodTableBuilder::NeedsTightlyBoundUnboxingStub
                            // Essentially all ValueType virtual methods will require an Unboxing Stub
                            //
                            if ((td.IsValueType) && !md.Signature.IsStatic
                                && md.IsVirtual)
                            {
                                unboxingStub = true;
                            }

                            symbolNode = _compilation.SymbolNodeFactory.CreateReadyToRunHelper(
                                ReadyToRunHelperId.MethodHandle,
                                ComputeMethodWithToken(md, ref pResolvedToken, constrainedType: null, unboxing: unboxingStub));
                        }
                        break;

                    case CorInfoGenericHandleType.CORINFO_HANDLETYPE_FIELD:
                        symbolNode = _compilation.SymbolNodeFactory.CreateReadyToRunHelper(
                            ReadyToRunHelperId.FieldHandle,
                            ComputeFieldWithToken(HandleToObject(pResolvedToken.hField), ref pResolvedToken));
                        break;

                    default:
                        throw new NotImplementedException(pResult.handleType.ToString());
                }

                pResult.lookup.constLookup = CreateConstLookupToSymbol(symbolNode);
            }
        }

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

        private CORINFO_METHOD_STRUCT_* embedMethodHandle(CORINFO_METHOD_STRUCT_* handle, ref void* ppIndirection)
        {
            // TODO: READYTORUN FUTURE: Handle this case correctly
            MethodDesc methodDesc = HandleToObject(handle);
            throw new RequiresRuntimeJitException("embedMethodHandle: " + methodDesc.ToString());
        }

        private bool NeedsTypeLayoutCheck(TypeDesc type)
        {
            if (!type.IsDefType)
                return false;

            if (!type.IsValueType)
                return false;

            return !_compilation.IsLayoutFixedInCurrentVersionBubble(type) || (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && !((MetadataType)type).IsNonVersionable());
        }

        /// <summary>
        /// Throws if the JIT inlines a method outside the current version bubble and that inlinee accesses
        /// fields also outside the version bubble. ReadyToRun currently cannot encode such references.
        /// </summary>
        private void PreventRecursiveFieldInlinesOutsideVersionBubble(FieldDesc field, MethodDesc callerMethod)
        {
            if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(callerMethod) && !_compilation.NodeFactory.CompilationModuleGroup.CrossModuleInlineable(callerMethod))
            {
                // Prevent recursive inline attempts where an inlined method outside of the version bubble is
                // referencing fields outside the version bubble.
                throw new RequiresRuntimeJitException(callerMethod.ToString() + " -> " + field.ToString());
            }
        }

        private void EncodeFieldBaseOffset(FieldDesc field, ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_FIELD_INFO* pResult, MethodDesc callerMethod)
        {
            TypeDesc pMT = field.OwningType;

            if (pResult->fieldAccessor != CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INSTANCE)
            {
                // No-op except for instance fields
            }
            else if (!_compilation.IsLayoutFixedInCurrentVersionBubble(pMT))
            {
                if (pMT.IsValueType)
                {
                    // ENCODE_CHECK_FIELD_OFFSET
                    if (pResult->offset > FieldFixupSignature.MaxCheckableOffset)
                        throw new RequiresRuntimeJitException(callerMethod.ToString() + " -> " + field.ToString());

                    AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                    // No-op other than generating the check field offset fixup
                }
                else
                {
                    PreventRecursiveFieldInlinesOutsideVersionBubble(field, callerMethod);

                    // ENCODE_FIELD_OFFSET
                    pResult->offset = 0;
                    pResult->fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INSTANCE_WITH_BASE;
                    pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.FieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                }
            }
            else if (pMT.IsValueType)
            {
                if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && !callerMethod.IsNonVersionable() && (pResult->offset <= FieldFixupSignature.MaxCheckableOffset))
                {
                    // ENCODE_CHECK_FIELD_OFFSET
                    if (_compilation.CompilationModuleGroup.VersionsWithType(field.OwningType)) // Only verify versions with types
                        AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                }
                // ENCODE_NONE
            }
            else if (_compilation.IsInheritanceChainLayoutFixedInCurrentVersionBubble(pMT.BaseType))
            {
                if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && !callerMethod.IsNonVersionable() && (pResult->offset <= FieldFixupSignature.MaxCheckableOffset))
                {
                    // ENCODE_CHECK_FIELD_OFFSET
                    if (_compilation.CompilationModuleGroup.VersionsWithType(field.OwningType)) // Only verify versions with types
                        AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                }
                // ENCODE_NONE
            }
            else if (TypeCannotUseBasePlusOffsetEncoding(pMT.BaseType as MetadataType))
            {
                // ENCODE_CHECK_FIELD_OFFSET
                AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                // No-op other than generating the check field offset fixup
            }
            else
            {
                PreventRecursiveFieldInlinesOutsideVersionBubble(field, callerMethod);

                if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && !callerMethod.IsNonVersionable() && (pResult->offset <= FieldFixupSignature.MaxCheckableOffset))
                {
                    // ENCODE_CHECK_FIELD_OFFSET
                    if (_compilation.CompilationModuleGroup.VersionsWithType(field.OwningType)) // Only verify versions with types
                        AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken)));
                }

                // ENCODE_FIELD_BASE_OFFSET
                int fieldBaseOffset = ((MetadataType)pMT).FieldBaseOffset().AsInt;
                Debug.Assert(pResult->offset >= (uint)fieldBaseOffset);
                pResult->offset -= (uint)fieldBaseOffset;
                pResult->fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INSTANCE_WITH_BASE;
                pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.FieldBaseOffset(field.OwningType));
            }
        }

        private bool TypeCannotUseBasePlusOffsetEncoding(MetadataType type)
        {
            if (type == null)
                return true;

            // Types which are encoded with sequential or explicit layout do not support an aligned base offset, and so we just encode with the exact offset
            // of the field, and if that offset is incorrect, the method cannot be used.
            if (type.IsSequentialLayout || type.IsExplicitLayout)
                return true;
            else if (type.BaseType is MetadataType metadataType)
            {
                return TypeCannotUseBasePlusOffsetEncoding(metadataType);
            }
            return false;
        }

        private void getGSCookie(IntPtr* pCookieVal, IntPtr** ppCookieVal)
        {
            *pCookieVal = IntPtr.Zero;
            *ppCookieVal = (IntPtr *)ObjectToHandle(_compilation.NodeFactory.GetReadyToRunHelperCell(ReadyToRunHelper.GSCookie));
        }

        private int* getAddrOfCaptureThreadGlobal(ref void* ppIndirection)
        {
            ppIndirection = (void*)ObjectToHandle(_compilation.NodeFactory.GetReadyToRunHelperCell(ReadyToRunHelper.IndirectTrapThreads));
            return null;
        }

        private void getMethodVTableOffset(CORINFO_METHOD_STRUCT_* method, ref uint offsetOfIndirection, ref uint offsetAfterIndirection, ref bool isRelative)
        { throw new NotImplementedException("getMethodVTableOffset"); }
        private void expandRawHandleIntrinsic(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_GENERICHANDLE_RESULT pResult)
        { throw new NotImplementedException("expandRawHandleIntrinsic"); }

        private byte[] _bbCounts;

        partial void findKnownBBCountBlock(ref BlockType blockType, void* location, ref int offset)
        {
            if (_bbCounts != null)
            {
                fixed (byte* pBBCountData = _bbCounts)
                {
                    if (pBBCountData <= (byte*)location && (byte*)location < (pBBCountData + _bbCounts.Length))
                    {
                        offset = (int)((byte*)location - pBBCountData);
                        blockType = BlockType.BBCounts;
                        return;
                    }
                }
            }
            blockType = BlockType.Unknown;
        }

        private unsafe HRESULT allocPgoInstrumentationBySchema(CORINFO_METHOD_STRUCT_* ftnHnd, PgoInstrumentationSchema* pSchema, uint countSchemaItems, byte** pInstrumentationData)
        {
            CORJIT_FLAGS flags = default(CORJIT_FLAGS);
            getJitFlags(ref flags, 0);
            *pInstrumentationData = null;

            if (flags.IsSet(CorJitFlag.CORJIT_FLAG_IL_STUB))
            {
                return HRESULT.E_NOTIMPL;
            }

            // Methods without ecma metadata are not instrumented
            EcmaMethod ecmaMethod = _methodCodeNode.Method.GetTypicalMethodDefinition() as EcmaMethod;
            if (ecmaMethod == null)
            {
                return HRESULT.E_NOTIMPL;
            }

            // Only allocation of PGO data for the current method is supported.
            if (_methodCodeNode.Method != HandleToObject(ftnHnd))
            {
                return HRESULT.E_NOTIMPL;
            }

            if (!_compilation.IsModuleInstrumented(ecmaMethod.Module))
            {
                return HRESULT.E_NOTIMPL;
            }

            // Validate that each schema item is only used for a basic block count
            for (uint iSchema = 0; iSchema < countSchemaItems; iSchema++)
            {
                if (pSchema[iSchema].InstrumentationKind != PgoInstrumentationKind.BasicBlockIntCount)
                    return HRESULT.E_NOTIMPL;
                if (pSchema[iSchema].Count != 1)
                    return HRESULT.E_NOTIMPL;
            }

            BlockCounts* blockCounts = (BlockCounts*)GetPin(_bbCounts = new byte[countSchemaItems * sizeof(BlockCounts)]);
            *pInstrumentationData = (byte*)blockCounts;

            for (uint iSchema = 0; iSchema < countSchemaItems; iSchema++)
            {
                // Update schema have correct offsets
                pSchema[iSchema].Offset = new IntPtr((byte*)&blockCounts[iSchema].ExecutionCount - (byte*)blockCounts);
                // Insert IL Offsets into block data to match schema
                blockCounts[iSchema].ILOffset = (uint)pSchema[iSchema].ILOffset;
            }

            return 0;
        }

        private void getAddressOfPInvokeTarget(CORINFO_METHOD_STRUCT_* method, ref CORINFO_CONST_LOOKUP pLookup)
        {
            MethodDesc methodDesc = HandleToObject(method);
            if (methodDesc is IL.Stubs.PInvokeTargetNativeMethod rawPInvoke)
                methodDesc = rawPInvoke.Target;
            Debug.Assert(_compilation.CompilationModuleGroup.VersionsWithMethodBody(methodDesc));
            EcmaMethod ecmaMethod = (EcmaMethod)methodDesc;
            ModuleToken moduleToken = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle);
            MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null, unboxing: false, genericContextObject: null);

            if ((ecmaMethod.GetPInvokeMethodCallingConventions() & UnmanagedCallingConventions.IsSuppressGcTransition) != 0)
            {
                pLookup.addr = (void*)ObjectToHandle(_compilation.SymbolNodeFactory.GetPInvokeTargetNode(methodWithToken));
                pLookup.accessType = InfoAccessType.IAT_PVALUE;
            }
            else
            {
                pLookup.addr = (void*)ObjectToHandle(_compilation.SymbolNodeFactory.GetIndirectPInvokeTargetNode(methodWithToken));
                pLookup.accessType = InfoAccessType.IAT_PPVALUE;
            }
        }

        private bool pInvokeMarshalingRequired(CORINFO_METHOD_STRUCT_* handle, CORINFO_SIG_INFO* callSiteSig)
        {
            if (handle != null)
            {
                var method = HandleToObject(handle);
                if (method.IsRawPInvoke())
                {
                    return false;
                }

                // If this method is in another versioning unit, then the compilation cannot inline the pinvoke (as we aren't currently
                // able to construct a token correctly to refer to the pinvoke method.
                if (!_compilation.CompilationModuleGroup.VersionsWithMethodBody(method))
                {
                    return true;
                }

                MethodIL stubIL = null;
                try
                {
                    stubIL = _compilation.GetMethodIL(method);
                    if (stubIL == null)
                    {
                        // This is the case of a PInvoke method that requires marshallers, which we can't use in this compilation
                        Debug.Assert(!_compilation.NodeFactory.CompilationModuleGroup.GeneratesPInvoke(method));
                        return true;
                    }
                }
                catch (RequiresRuntimeJitException)
                {
                    // The PInvoke IL emitter will throw for known unsupported scenario. We cannot propagate the exception here since
                    // this interface call might be used to check if a certain pinvoke can be inlined in the caller. Throwing means that the
                    // caller will not get compiled. Instead, we'll return true to let the JIT know that it cannot inline the pinvoke, and
                    // the actual pinvoke call will be handled by a stub that we create and compile in the runtime.
                    return true;
                }

                return ((PInvokeILStubMethodIL)stubIL).IsMarshallingRequired;
            }
            else
            {
                var sig = HandleToObject(callSiteSig->methodSignature);
                return Marshaller.IsMarshallingRequired(sig, Array.Empty<ParameterMetadata>(), ((MetadataType)HandleToObject(callSiteSig->scope).OwningMethod.OwningType).Module);
            }
        }

        private bool convertPInvokeCalliToCall(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool mustConvert)
        {
            throw new NotImplementedException();
        }

        private int SizeOfPInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_PInvokeTransitionFrameSizeInPointerUnits * PointerSize;
        private int SizeOfReversePInvokeTransitionFrame
        {
            get
            {
                if (_compilation.NodeFactory.Target.Architecture == TargetArchitecture.X86)
                    return ReadyToRunRuntimeConstants.READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits_X86 * PointerSize;
                else
                    return ReadyToRunRuntimeConstants.READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits_Universal * PointerSize;
            }
        }

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

        private void setEHinfo(uint EHnumber, ref CORINFO_EH_CLAUSE clause)
        {
            // Filters, finallys, and faults don't have class token in the clause.ClassTokenOrOffset
            if ((clause.Flags & (CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_FILTER | CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_FINALLY | CORINFO_EH_CLAUSE_FLAGS.CORINFO_EH_CLAUSE_FAULT)) == 0)
            {
                if (clause.ClassTokenOrOffset != 0)
                {
                    MethodIL methodIL = _compilation.GetMethodIL(MethodBeingCompiled);
                    mdToken classToken = (mdToken)clause.ClassTokenOrOffset;
                    TypeDesc clauseType = (TypeDesc)ResolveTokenInScope(methodIL, MethodBeingCompiled, classToken);

                    CORJIT_FLAGS flags = default(CORJIT_FLAGS);
                    getJitFlags(ref flags, 0);

                    if (flags.IsSet(CorJitFlag.CORJIT_FLAG_IL_STUB))
                    {
                        // IL stub tokens are 'private' and do not resolve correctly in their parent module's metadata.

                        // Currently, the only place we are using a token here is for a COM-to-CLR exception-to-HRESULT
                        // mapping catch clause.  We want this catch clause to catch all exceptions, so we override the
                        // token to be mdTypeRefNil, which used by the EH system to mean catch (...)
                        Debug.Assert(clauseType.IsObject);
                        clause.ClassTokenOrOffset = 0;
                    }
                    else
                    {
                        // For all clause types add fixup to ensure the types are loaded before the code of the method
                        // containing the catch blocks is executed. This ensures that a failure to load the types would
                        // not happen when the exception handling is in progress and it is looking for a catch handler.
                        // At that point, we could only fail fast.
                        classMustBeLoadedBeforeCodeIsRun(clauseType);
                    }
                }
            }

            _ehClauses[EHnumber] = clause;
        }

        private readonly Stack<List<ISymbolNode>> _stashedPrecodeFixups = new Stack<List<ISymbolNode>>();
        private readonly Stack<HashSet<MethodDesc>> _stashedInlinedMethods = new Stack<HashSet<MethodDesc>>();

        private void beginInlining(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_METHOD_STRUCT_* inlineeHnd)
        {
            _stashedPrecodeFixups.Push(_precodeFixups);
            _precodeFixups = null;
            _stashedInlinedMethods.Push(_inlinedMethods);
            _inlinedMethods = null;
        }

        private void reportInliningDecision(CORINFO_METHOD_STRUCT_* inlinerHnd, CORINFO_METHOD_STRUCT_* inlineeHnd, CorInfoInline inlineResult, byte* reason)
        {
            if (inlineResult == CorInfoInline.INLINE_PASS)
            {
                // We deliberately ignore inlinerHnd because we have no interest to track intermediate links now.
                MethodDesc inlinee = HandleToObject(inlineeHnd);

                // If during inlining we found Precode fixups, then only if the inline was successful, add them to the set of
                // fixups that will be used for the entire method
                List<ISymbolNode> previouslyStashedFixups = _stashedPrecodeFixups.Pop();

                if (_precodeFixups != null)
                {
                    previouslyStashedFixups = previouslyStashedFixups ?? new List<ISymbolNode>();
                    previouslyStashedFixups.AddRange(_precodeFixups);
                }
                _precodeFixups = previouslyStashedFixups;

                // If during inlining we found new inlinees, then if the inline was successful, add them to the set of fixups
                // for the entire method.
                HashSet<MethodDesc> previouslyStashedInlinees = _stashedInlinedMethods.Pop();
                if (_inlinedMethods != null)
                {
                    previouslyStashedInlinees = previouslyStashedInlinees ?? new HashSet<MethodDesc>();
                    foreach (var inlineeInHashSet in _inlinedMethods)
                        previouslyStashedInlinees.Add(inlineeInHashSet);
                }
                _inlinedMethods = previouslyStashedInlinees;

                // Then add the fact we inlined this method.
                _inlinedMethods = _inlinedMethods ?? new HashSet<MethodDesc>();
                _inlinedMethods.Add(inlinee);

                var typicalMethod = inlinee.GetTypicalMethodDefinition();

                MethodIL methodIL = _compilation.GetMethodIL(typicalMethod);
                if (methodIL is ILStubMethodIL ilStubMethodIL && ilStubMethodIL.StubILHasGeneratedTokens)
                {
                    // If a method is implemented by an IL Stub, then we may need to defer creation of the IL body that
                    // we can really emit into the final file.
                    _ilBodiesNeeded = _ilBodiesNeeded ?? new List<MethodDesc>();
                    Debug.Assert(typicalMethod.IsMethodDefinition);
                    _ilBodiesNeeded.Add(typicalMethod);
                }
                else if (!_compilation.CompilationModuleGroup.VersionsWithMethodBody(typicalMethod)
                    && typicalMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition() is EcmaMethod ecmaMethod)
                {
                    Debug.Assert(_compilation.CompilationModuleGroup.CrossModuleInlineable(ecmaMethod) ||
                                 _compilation.CompilationModuleGroup.IsNonVersionableWithILTokensThatDoNotNeedTranslation(ecmaMethod));
                    bool needsTokenTranslation = !_compilation.CompilationModuleGroup.IsNonVersionableWithILTokensThatDoNotNeedTranslation(ecmaMethod);

                    if (needsTokenTranslation)
                    {
                        // This can happen if the method is marked as NonVersionable if there are tokens of interest
                        // or if we are working with a full cross module inline
                        // The CheckILBodyFixupSignature will use the exact IL in metadata, not what is used by the ReadyToRunILProvider,
                        // which can be different for runtime-async methods (the ILProvider returns the Task-returning thunk for runtime-async EcmaMethods).
                        ISymbolNode ilBodyNode = _compilation.SymbolNodeFactory.CheckILBodyFixupSignature(typicalMethod);
                        AddPrecodeFixup(ilBodyNode);
                    }

                    // For cross module inlines, we will compile in a potentially 2 step process
                    // 1. Compile the method using the set of il bodies available at the start of a multi-threaded compilation run
                    // 2. If at any time, the set of methods that are inlined includes a method which has an IL body without
                    //    tokens that are useable in compilation, record that information, and once the multi-threaded portion
                    //    of the build finishes, it will then compute the IL bodies for those methods, then run the compilation again.

                    if (needsTokenTranslation && !(methodIL is IMethodTokensAreUseableInCompilation)
                        && (methodIL is EcmaMethodIL || methodIL is ReadyToRunILProvider.AsyncEcmaMethodIL))
                    {
                        // We may have already acquired the right type of MethodIL here, or be working with a method that is an IL Intrinsic.
                        // Add the typicalMethod (which may be an AsyncMethodVariant) so that
                        // CreateCrossModuleInlineableTokensForILBody stores under the correct key.
                        _ilBodiesNeeded = _ilBodiesNeeded ?? new List<MethodDesc>();
                        Debug.Assert(typicalMethod.IsMethodDefinition);
                        _ilBodiesNeeded.Add(typicalMethod);
                    }
                }
            }
            else
            {
                // If we didn't succeed on the inline, just pop back to the state before inlining
                _precodeFixups = _stashedPrecodeFixups.Pop();
                _inlinedMethods = _stashedInlinedMethods.Pop();
            }
        }

        private void updateEntryPointForTailCall(ref CORINFO_CONST_LOOKUP entryPoint)
        {
            // In x64 we normally use a return address to find the indirection cell for delay load.
            // For tailcalls we instead expect the JIT to leave the indirection in rax.
            if (_compilation.TypeSystemContext.Target.Architecture != TargetArchitecture.X64)
                return;

            object node = HandleToObject(entryPoint.addr);
            if (node is not DelayLoadMethodImport imp)
                return;

            Debug.Assert(imp.GetType() == typeof(DelayLoadMethodImport));
            IMethodNode newEntryPoint =
                _compilation.NodeFactory.MethodEntrypoint(
                    imp.MethodWithToken,
                    ((MethodFixupSignature)imp.ImportSignature.Target).IsInstantiatingStub,
                    isPrecodeImportRequired: false,
                    isJumpableImportRequired: true);

            entryPoint = CreateConstLookupToSymbol(newEntryPoint);
        }

        private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses, CORINFO_CLASS_STRUCT_** exactClsRet)
        {
            // Not implemented for R2R yet
            return -1;
        }

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

            // For crossgen2 we only support RVA fields
            if (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(field.OwningType) && field.HasRva)
            {
                return TryReadRvaFieldData(field, buffer, bufferSize, valueOffset);
            }
            return false;
        }

        private bool getObjectContent(CORINFO_OBJECT_STRUCT_* obj, byte* buffer, int bufferSize, int valueOffset)
        {
            throw new NotSupportedException();
        }

        private CORINFO_CLASS_STRUCT_* getObjectType(CORINFO_OBJECT_STRUCT_* objPtr)
        {
            throw new NotSupportedException();
        }

        private bool isObjectImmutable(CORINFO_OBJECT_STRUCT_* objPtr)
        {
            throw new NotSupportedException();
        }

        private bool getStringChar(CORINFO_OBJECT_STRUCT_* strObj, int index, ushort* value)
        {
            return false;
        }

        private CORINFO_OBJECT_STRUCT_* getRuntimeTypePointer(CORINFO_CLASS_STRUCT_* cls)
        {
            return null;
        }

        private int getArrayOrStringLength(CORINFO_OBJECT_STRUCT_* objHnd)
        {
            return -1;
        }

        private bool getIsClassInitedFlagAddress(CORINFO_CLASS_STRUCT_* cls, ref CORINFO_CONST_LOOKUP addr, ref int offset)
        {
            // Implemented for JIT and NativeAOT only for now.
            return false;
        }

        private bool getStaticBaseAddress(CORINFO_CLASS_STRUCT_* cls, bool isGc, ref CORINFO_CONST_LOOKUP addr)
        {
            // Implemented for JIT and NativeAOT only for now.
            return false;
        }

        private void ValidateSafetyOfUsingTypeEquivalenceInSignature(MethodSignature signature)
        {
            // Type equivalent valuetypes not in the current version bubble are problematic, and cannot be referred to in our current token
            // scheme except through type references. So we need to detect them, and if they aren't referred to by type reference from a module
            // in the current build, then we need to fallback to runtime jit.
            ValidateSafetyOfUsingTypeEquivalenceOfType(signature.ReturnType);
            foreach (var type in signature)
            {
                ValidateSafetyOfUsingTypeEquivalenceOfType(type);
            }
        }

        void ValidateSafetyOfUsingTypeEquivalenceOfType(TypeDesc type)
        {
            if (type.IsValueType && type.IsTypeDefEquivalent && !_compilation.CompilationModuleGroup.VersionsWithTypeReference(type))
            {
                // Technically this is a bit pickier than needed, as cross module inlineable cases will be hit by this, but type equivalence is a
                // rarely used feature, and we can fix that if we need to.
                throw new RequiresRuntimeJitException($"Type equivalent valuetype '{type}' not directly referenced from member reference");
            }
        }

        private bool notifyMethodInfoUsage(CORINFO_METHOD_STRUCT_* ftn)
        {
            MethodDesc method = HandleToObject(ftn);
            Debug.Assert(method != null);
            // If ftn isn't within the current version bubble we can't rely on methodInfo being
            // stable e.g. mark calls as no-return if their IL has no rets.
            return _compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(method);
        }

        private CORINFO_WASM_TYPE_SYMBOL_STRUCT_* getWasmTypeSymbol(CorInfoWasmType* types, nuint typesSize)
        {
            CorInfoWasmType[] typeArray = new ReadOnlySpan<CorInfoWasmType>(types, (int)typesSize).ToArray();

            WasmTypeNode typeNode = _compilation.NodeFactory.WasmTypeNode(typeArray);
            return (CORINFO_WASM_TYPE_SYMBOL_STRUCT_*)ObjectToHandle(typeNode);
        }

#pragma warning disable CA1822 // Mark members as static
        private void getThreadLocalStaticInfo_NativeAOT(CORINFO_THREAD_STATIC_INFO_NATIVEAOT* pInfo)
        {
            // Implemented for NativeAOT only for now.
        }
#pragma warning restore CA1822 // Mark members as static

#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
        {
            if ((callSig != null) && _compilation.NodeFactory.Target.IsWasm)
            {
                var sig = HandleToObject(callSig->methodSignature);

                WasmLowering.LoweringFlags flags = 0;
                if (callSig->hasTypeArg())
                {
                    flags |= WasmLowering.LoweringFlags.HasGenericContextArg;
                }
                if (callSig->isAsyncCall())
                {
                    flags |= WasmLowering.LoweringFlags.IsAsyncCall;
                }
                if (((int)callSig->getCallConv() & 0xF) != 0)
                {
                    flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly;
                }

                WasmSignature wasmSig = WasmLowering.GetSignature(sig, flags);

                // Only create R2R-to-interpreter thunks for managed calls.
                // Unmanaged calls don't go through the interpreter transition.
                if (!flags.HasFlag(WasmLowering.LoweringFlags.IsUnmanagedCallersOnly))
                {
                    AddAdditionalDependency(_compilation.NodeFactory.WasmR2RToInterpreterThunk(wasmSig), "R2R-to-interpreter thunk for call site");
                }
            }
        }

        private void recordWasmManagedCallSig(CORINFO_SIG_INFO* callSig)
        {
            if ((callSig != null) && _compilation.NodeFactory.Target.IsWasm)
            {
                var sig = HandleToObject(callSig->methodSignature);

                WasmLowering.LoweringFlags flags = 0;
                if (callSig->hasTypeArg())
                {
                    flags |= WasmLowering.LoweringFlags.HasGenericContextArg;
                }
                if (callSig->isAsyncCall())
                {
                    flags |= WasmLowering.LoweringFlags.IsAsyncCall;
                }
                if (((int)callSig->getCallConv() & 0xF) != 0)
                {
                    flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly;
                }

                WasmSignature wasmSig = WasmLowering.GetSignature(sig, flags);

                // Only create R2R-to-interpreter thunks for managed calls.
                // Unmanaged calls don't go through the interpreter transition.
                if (!flags.HasFlag(WasmLowering.LoweringFlags.IsUnmanagedCallersOnly))
                {
                    AddAdditionalDependency(_compilation.NodeFactory.WasmR2RToInterpreterThunk(wasmSig), "R2R-to-interpreter thunk for call site");
                }
            }
        }
    }
}