File: IL\ILImporter.Scanner.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

using Internal.TypeSystem;
using Internal.ReadyToRunConstants;

using ILCompiler;
using ILCompiler.DependencyAnalysis;

using Debug = System.Diagnostics.Debug;
using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyList;
using CombinedDependencyList = System.Collections.Generic.List<ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry>;
using DependencyListEntry = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyListEntry;

#pragma warning disable IDE0060

namespace Internal.IL
{
    // Implements an IL scanner that scans method bodies to be compiled by the code generation
    // backend before the actual compilation happens to gain insights into the code.
    internal partial class ILImporter
    {
        private readonly MethodIL _methodIL;
        private readonly MethodIL _canonMethodIL;
        private readonly ILScanner _compilation;
        private readonly ILScanNodeFactory _factory;

        // True if we're scanning a throwing method body because scanning the real body failed.
        private readonly bool _isFallbackBodyCompilation;

        private readonly MethodDesc _canonMethod;

        private DependencyList _unconditionalDependencies = new DependencyList();

        private readonly byte[] _ilBytes;

        private TypeEqualityPatternAnalyzer _typeEqualityPatternAnalyzer;
        private IsInstCheckPatternAnalyzer _isInstCheckPatternAnalyzer;

        private sealed class BasicBlock
        {
            // Common fields
            public enum ImportState : byte
            {
                Unmarked,
                IsPending
            }

            public BasicBlock Next;

            public int StartOffset;
            public ImportState State = ImportState.Unmarked;

            public bool TryStart;
            public bool FilterStart;
            public bool HandlerStart;

            public object Condition;
            public DependencyList Dependencies;
        }

        private bool _isReadOnly;
        private TypeDesc _constrained;
        private int _currentInstructionOffset;
        private int _previousInstructionOffset;

        private DependencyList _dependencies;
        private BasicBlock _lateBasicBlocks;

        private bool _asyncDependenciesReported;

        private sealed class ExceptionRegion
        {
            public ILExceptionRegion ILRegion;
        }
        private ExceptionRegion[] _exceptionRegions;

        public ILImporter(ILScanner compilation, MethodDesc method, MethodIL methodIL = null)
        {
            if (methodIL == null)
            {
                methodIL = compilation.GetMethodIL(method);
            }
            else
            {
                _isFallbackBodyCompilation = true;
            }

            // This is e.g. an "extern" method in C# without a DllImport or InternalCall.
            if (methodIL == null)
            {
                ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramSpecific, method);
            }

            _compilation = compilation;
            _factory = (ILScanNodeFactory)compilation.NodeFactory;

            _ilBytes = methodIL.GetILBytes();

            _canonMethodIL = methodIL;

            // Get the runtime determined method IL so that this works right in shared code
            // and tokens in shared code resolve to runtime determined types.
            MethodIL uninstantiatiedMethodIL = methodIL.GetMethodILDefinition();
            if (methodIL != uninstantiatiedMethodIL)
            {
                MethodDesc sharedMethod = method.GetSharedRuntimeFormMethodTarget();
                _methodIL = new InstantiatedMethodIL(sharedMethod, uninstantiatiedMethodIL);
            }
            else
            {
                _methodIL = methodIL;
            }

            _canonMethod = method;

            var ilExceptionRegions = methodIL.GetExceptionRegions();
            _exceptionRegions = new ExceptionRegion[ilExceptionRegions.Length];
            for (int i = 0; i < ilExceptionRegions.Length; i++)
            {
                _exceptionRegions[i] = new ExceptionRegion() { ILRegion = ilExceptionRegions[i] };
            }

            _dependencies = _unconditionalDependencies;
        }

        public (DependencyList, CombinedDependencyList) Import()
        {
            TypeDesc owningType = _canonMethod.OwningType;
            if (_compilation.HasLazyStaticConstructor(owningType))
            {
                // Don't trigger cctor if this is a fallback compilation (bad cctor could have been the reason for fallback).
                // Otherwise follow the rules from ECMA-335 I.8.9.5.
                if (!_isFallbackBodyCompilation &&
                    (_canonMethod.Signature.IsStatic || _canonMethod.IsConstructor || owningType.IsValueType || owningType.IsInterface))
                {
                    // For beforefieldinit, we can wait for field access.
                    if (!((MetadataType)owningType).IsBeforeFieldInit)
                    {
                        MethodDesc method = _methodIL.OwningMethod;
                        if (method.OwningType.IsRuntimeDeterminedSubtype)
                        {
                            _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.GetNonGCStaticBase, method.OwningType), "Owning type cctor");
                        }
                        else
                        {
                            _dependencies.Add(_factory.ReadyToRunHelper(ReadyToRunHelperId.GetNonGCStaticBase, method.OwningType), "Owning type cctor");
                        }
                    }
                }
            }

            if (_canonMethod.IsSynchronized)
            {
                const string reason = "Synchronized method";
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.MonitorEnter), reason);
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.MonitorExit), reason);
                if (_canonMethod.Signature.IsStatic)
                {
                    _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(_compilation.NodeFactory.TypeSystemContext.GetCoreLibEntryPoint("System"u8, "Type"u8, "GetTypeFromMethodTable"u8, null)), reason);

                    MethodDesc method = _methodIL.OwningMethod;
                    if (method.OwningType.IsRuntimeDeterminedSubtype)
                    {
                        _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.NecessaryTypeHandle, method.OwningType), reason);
                    }
                    else
                    {
                        _dependencies.Add(_factory.NecessaryTypeSymbol(method.OwningType), reason);
                    }

                    if (_canonMethod.IsCanonicalMethod(CanonicalFormKind.Any))
                    {
                        if (_canonMethod.RequiresInstMethodDescArg())
                            _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(_compilation.NodeFactory.TypeSystemContext.GetCoreLibEntryPoint("Internal.Runtime.CompilerHelpers"u8, "SharedCodeHelpers"u8, "GetClassHandleFromMethodParam"u8, null)), reason);
                    }
                }
            }

            if (_canonMethod.IsAsyncCall())
            {
                const string reason = "Async state machine";
                DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8);
                _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContexts"u8, null)), reason);
                _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContexts"u8, null)), reason);
            }

            FindBasicBlocks();
            ImportBasicBlocks();

            CombinedDependencyList conditionalDependencies = null;
            foreach (BasicBlock bb in _basicBlocks)
            {
                if (bb?.Condition == null)
                    continue;

                conditionalDependencies ??= new CombinedDependencyList();
                foreach (DependencyListEntry dep in bb.Dependencies)
                    conditionalDependencies.Add(new(dep.Node, bb.Condition, dep.Reason));
            }

            CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _unconditionalDependencies, _factory, _canonMethod, _canonMethodIL);
            CodeBasedDependencyAlgorithm.AddConditionalDependenciesDueToMethodCodePresence(ref conditionalDependencies, _factory, _canonMethod);

            return (_unconditionalDependencies, conditionalDependencies);
        }

        private ISymbolNode GetGenericLookupHelper(ReadyToRunHelperId helperId, object helperArgument)
        {
            GenericDictionaryLookup lookup = _compilation.ComputeGenericLookup(_canonMethod, helperId, helperArgument);
            Debug.Assert(lookup.UseHelper);

            if (_canonMethod.RequiresInstMethodDescArg())
            {
                return _compilation.NodeFactory.ReadyToRunHelperFromDictionaryLookup(lookup.HelperId, lookup.HelperObject, _canonMethod);
            }
            else
            {
                Debug.Assert(_canonMethod.RequiresInstArg() || _canonMethod.AcquiresInstMethodTableFromThis());
                return _compilation.NodeFactory.ReadyToRunHelperFromTypeLookup(lookup.HelperId, lookup.HelperObject, _canonMethod.OwningType);
            }
        }

        private ISymbolNode GetHelperEntrypoint(ReadyToRunHelper helper)
        {
            return _compilation.GetHelperEntrypoint(helper);
        }

        private static void MarkInstructionBoundary() { }

        private void EndImportingBasicBlock(BasicBlock basicBlock)
        {
            if (_pendingBasicBlocks == null)
            {
                _pendingBasicBlocks = _lateBasicBlocks;
                _lateBasicBlocks = null;
            }
        }

        private void StartImportingBasicBlock(BasicBlock basicBlock)
        {
            _dependencies = basicBlock.Condition != null ? basicBlock.Dependencies : _unconditionalDependencies;

            // Import all associated EH regions
            foreach (ExceptionRegion ehRegion in _exceptionRegions)
            {
                ILExceptionRegion region = ehRegion.ILRegion;
                if (region.TryOffset == basicBlock.StartOffset)
                {
                    ImportBasicBlockEdge(basicBlock, _basicBlocks[region.HandlerOffset]);
                    if (region.Kind == ILExceptionRegionKind.Filter)
                        ImportBasicBlockEdge(basicBlock, _basicBlocks[region.FilterOffset]);

                    if (region.Kind == ILExceptionRegionKind.Catch)
                    {
                        TypeDesc catchType = (TypeDesc)_methodIL.GetObject(region.ClassToken);

                        // EH tables refer to this type
                        if (catchType.IsRuntimeDeterminedSubtype)
                        {
                            _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandleForCasting, catchType), "EH");
                        }
                        else
                        {
                            _dependencies.Add(_compilation.ComputeConstantLookup(ReadyToRunHelperId.TypeHandleForCasting, catchType), "EH");
                        }
                    }
                }
            }

            _typeEqualityPatternAnalyzer = default;
            _isInstCheckPatternAnalyzer = default;
            _currentInstructionOffset = 0;
            _previousInstructionOffset = -1;
        }

        private void StartImportingInstruction()
        {
            _currentInstructionOffset = _currentOffset;
        }

        partial void StartImportingInstruction(ILOpcode opcode)
        {
            _typeEqualityPatternAnalyzer.Advance(opcode, new ILReader(_ilBytes, _currentOffset), _methodIL);
            _isInstCheckPatternAnalyzer.Advance(opcode, new ILReader(_ilBytes, _currentOffset), _methodIL);
        }

        private void EndImportingInstruction()
        {
            // The instruction should have consumed any prefixes.
            _constrained = null;
            _isReadOnly = false;

            _previousInstructionOffset = _currentInstructionOffset;
        }

        private void ImportCasting(ILOpcode opcode, int token)
        {
            TypeDesc type = (TypeDesc)_methodIL.GetObject(token);

            if (type.IsRuntimeDeterminedSubtype)
            {
                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandleForCasting, type), "IsInst/CastClass");
            }
            else
            {
                _dependencies.Add(_compilation.ComputeConstantLookup(ReadyToRunHelperId.TypeHandleForCasting, type), "IsInst/CastClass");
            }
        }

        private IMethodNode GetMethodEntrypoint(MethodDesc method)
        {
            if (method.HasInstantiation || method.OwningType.HasInstantiation)
            {
                _compilation.DetectGenericCycles(_canonMethod, method);
            }

            return _factory.MethodEntrypointOrTentativeMethod(method);
        }

        // Check if a method call starts a task await pattern that can be
        // optimized for runtime async.
        // Roughly corresponds to impMatchTaskAwaitPattern in RyuJIT codebase
        private bool MatchTaskAwaitPattern()
        {
            // We look for the following code patterns in runtime async methods:
            //
            //    call[virt] <Method>
            //    [ OPTIONAL ]
            //    {
            //       [ OPTIONAL ]
            //       {
            //         stloc X;
            //         ldloca X
            //       }
            //       ldc.i4.0 / ldc.i4.1
            //       call[virt] <ConfigureAwait>
            //    }
            //    call       <Await>

            // Find where this basic block ends
            int nextBBOffset = _currentOffset;
            while (nextBBOffset < _basicBlocks.Length && _basicBlocks[nextBBOffset] == null)
                nextBBOffset++;

            // Create ILReader for what's left in the basic block
            var reader = new ILReader(new ReadOnlySpan<byte>(_ilBytes, _currentOffset, nextBBOffset - _currentOffset));

            if (!reader.HasNext)
                return false;

            ILOpcode opcode;

            // If we can read at least two call tokens + an ldc, this could be ConfigureAwait
            // so check for that.
            if (reader.Size > 2 * (1 + sizeof(int)))
            {
                opcode = reader.ReadILOpcode();

                // ConfigureAwait on a ValueTask will start with stloc/ldloca.
                int stlocNum = opcode switch
                {
                    >= ILOpcode.stloc_0 and <= ILOpcode.stloc_3 => opcode - ILOpcode.stloc_0,
                    ILOpcode.stloc => reader.ReadILUInt16(),
                    ILOpcode.stloc_s => reader.ReadILByte(),
                    _ => -1,
                };

                // if it was a stloc, check for matching ldloca
                if (stlocNum != -1)
                {
                    opcode = reader.ReadILOpcode();
                    int ldlocaNum = opcode switch
                    {
                        ILOpcode.ldloca_s => reader.ReadILByte(),
                        ILOpcode.ldloca => reader.ReadILUInt16(),
                        _ => -1,
                    };

                    if (stlocNum != ldlocaNum)
                        return false;

                    opcode = reader.ReadILOpcode();
                }

                if (opcode is (not ILOpcode.ldc_i4_0) and (not ILOpcode.ldc_i4_1))
                {
                    if (stlocNum != -1)
                    {
                        // we had stloc/ldloca, we must see ConfigAwait
                        return false;
                    }

                    goto checkForAwait;
                }

                opcode = reader.ReadILOpcode();
                if (opcode is (not ILOpcode.call) and (not ILOpcode.callvirt)
                    || !IsTaskConfigureAwait((MethodDesc)_methodIL.GetObject(reader.ReadILToken()))
                    || !reader.HasNext)
                {
                    return false;
                }
            }

            opcode = reader.ReadILOpcode();

        checkForAwait:

            return opcode == ILOpcode.call
                && IsAsyncHelpersAwait((MethodDesc)_methodIL.GetObject(reader.ReadILToken()));
        }

        private void ImportCall(ILOpcode opcode, int token)
        {
            // We get both the canonical and runtime determined form - JitInterface mostly operates
            // on the canonical form.
            var runtimeDeterminedMethod = (MethodDesc)_methodIL.GetObject(token);
            var method = (MethodDesc)_canonMethodIL.GetObject(token);

            _compilation.TypeSystemContext.EnsureLoadableMethod(method);
            if ((method.Signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == MethodSignatureFlags.CallingConventionVarargs)
                ThrowHelper.ThrowBadImageFormatException();

            _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, method);

            if (method.IsRawPInvoke())
            {
                // Raw P/invokes don't have any dependencies.
                return;
            }

            // Are we scanning a call within a state machine?
            if (opcode is ILOpcode.call or ILOpcode.callvirt
                && _canonMethod.IsAsyncCall())
            {
                // Add dependencies on infra to do suspend/resume. We only need to do this once per method scanned.
                if (!_asyncDependenciesReported && method.IsAsync)
                {
                    _asyncDependenciesReported = true;

                    const string asyncReason = "Async state machine";

                    AsyncResumptionStub resumptionStub = _compilation.TypeSystemContext.GetAsyncResumptionStub(_canonMethod, _compilation.TypeSystemContext.GeneratedAssembly.GetGlobalModuleType());
                    _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(resumptionStub), asyncReason);

                    _dependencies.Add(_factory.ConstructedTypeSymbol(_compilation.TypeSystemContext.ContinuationType), asyncReason);

                    DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8);

                    _dependencies.Add(_compilation.GetHelperEntrypoint(ReadyToRunHelper.AllocContinuation), asyncReason);
                    _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)), asyncReason);
                    _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)), asyncReason);
                    _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)), asyncReason);
                    _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)), asyncReason);
                    _dependencies.Add(_factory.MethodEntrypoint(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)), asyncReason);
                }

                // If this is the task await pattern, the JIT will first resolve the call to the
                // async variant, then may switch back to the original if the async variant is just
                // a thunk (for non-runtime-async methods). Report both variants as dependencies such
                // that the JIT can pick either one.

                // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T).
                // we cannot resolve to an Async variant in such case.
                bool allowAsyncVariant = method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask();

                // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either.
                allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate;

                if (allowAsyncVariant && MatchTaskAwaitPattern())
                {
                    MethodDesc asyncVariantMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(method);
                    MethodDesc asyncVariantRuntimeDeterminedMethod = _factory.TypeSystemContext.GetAsyncVariantMethod(runtimeDeterminedMethod);
                    ImportCall(opcode, asyncVariantMethod, asyncVariantRuntimeDeterminedMethod);
                }
            }

            ImportCall(opcode, method, runtimeDeterminedMethod);
        }

        private void ImportCall(ILOpcode opcode, MethodDesc method, MethodDesc runtimeDeterminedMethod)
        {
            string reason = null;
            switch (opcode)
            {
                case ILOpcode.newobj:
                    reason = "newobj"; break;
                case ILOpcode.call:
                    reason = "call"; break;
                case ILOpcode.callvirt:
                    reason = "callvirt"; break;
                case ILOpcode.ldftn:
                    reason = "ldftn"; break;
                case ILOpcode.ldvirtftn:
                    reason = "ldvirtftn"; break;
                default:
                    Debug.Assert(false); break;
            }

            if (opcode == ILOpcode.newobj)
            {
                TypeDesc owningType = runtimeDeterminedMethod.OwningType;
                if (owningType.IsString)
                {
                    // String .ctor handled specially below
                }
                else if (owningType.IsGCPointer)
                {
                    if (owningType.IsRuntimeDeterminedSubtype)
                    {
                        _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, owningType), reason);
                    }
                    else
                    {
                        _dependencies.Add(_factory.ConstructedTypeSymbol(owningType), reason);
                    }

                    if (owningType.IsArray)
                    {
                        // RyuJIT is going to call the "MdArray" creation helper even if this is an SzArray,
                        // hence the IsArray check above. Note that the MdArray helper can handle SzArrays.
                        if (((ArrayType)owningType).Rank == 1)
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArrRare), reason);
                        else
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArr), reason);
                        return;
                    }
                    else
                    {
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewObject), reason);
                    }
                }
            }

            if (method.OwningType.IsDelegate && method.Name.SequenceEqual("Invoke"u8) &&
                opcode != ILOpcode.ldftn && opcode != ILOpcode.ldvirtftn)
            {
                // This call is expanded as an intrinsic; it's not an actual function call.
                // Before codegen realizes this is an intrinsic, it might still ask questions about
                // the vtable of this virtual method, so let's make sure it's marked in the scanner's
                // dependency graph.
                _dependencies.Add(_factory.VTable(method.OwningType), reason);
                return;
            }

            if (method.IsIntrinsic)
            {
                if (IsActivatorDefaultConstructorOf(method))
                {
                    if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod)
                    {
                        _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DefaultConstructor, runtimeDeterminedMethod.Instantiation[0]), reason);
                    }
                    else
                    {
                        TypeDesc type = method.Instantiation[0];
                        MethodDesc ctor = Compilation.GetConstructorForCreateInstanceIntrinsic(type);
                        _dependencies.Add(type.IsValueType ? _factory.ExactCallableAddress(ctor) : _factory.CanonicalEntrypoint(ctor), reason);
                    }

                    return;
                }

                if (IsActivatorAllocatorOf(method))
                {
                    if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod)
                    {
                        _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ObjectAllocator, runtimeDeterminedMethod.Instantiation[0]), reason);
                    }
                    else
                    {
                        _dependencies.Add(_compilation.ComputeConstantLookup(ReadyToRunHelperId.ObjectAllocator, method.Instantiation[0]), reason);
                    }

                    return;
                }

                if (IsEETypePtrOf(method))
                {
                    if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod)
                    {
                        _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.Instantiation[0]), reason);
                    }
                    else
                    {
                        _dependencies.Add(_factory.ConstructedTypeSymbol(method.Instantiation[0]), reason);
                    }
                    return;
                }

                if (opcode != ILOpcode.ldftn)
                {
                    if (IsRuntimeHelpersIsReferenceOrContainsReferences(method))
                    {
                        return;
                    }

                    if (IsMemoryMarshalGetArrayDataReference(method))
                    {
                        return;
                    }
                }
            }

            TypeDesc exactType = method.OwningType;

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

            MethodDesc methodAfterConstraintResolution = method;
            if (_constrained != null)
            {
                // 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.

                TypeDesc constrained = _constrained;
                if (constrained.IsRuntimeDeterminedSubtype)
                    constrained = constrained.ConvertToCanonForm(CanonicalFormKind.Specific);

                if (constrained.IsEnum)
                {
                    // Optimize constrained calls to enum's GetHashCode method. TryResolveConstraintMethodApprox would return
                    // null since the virtual method resolves to System.Enum's implementation and that's a reference type.
                    // We can't do this for any other method since ToString and Equals have different semantics for enums
                    // and their underlying type.
                    if (method.OwningType.IsObject && method.Name.SequenceEqual("GetHashCode"u8))
                    {
                        constrained = constrained.UnderlyingType;
                    }
                }

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

                    methodAfterConstraintResolution = directMethod;

                    Debug.Assert(!methodAfterConstraintResolution.OwningType.IsInterface
                        || methodAfterConstraintResolution.Signature.IsStatic);
                    resolvedConstraint = true;

                    exactType = directMethod.OwningType;

                    _factory.MetadataManager.NoteOverridingMethod(method, directMethod);
                }
                else if (method.Signature.IsStatic)
                {
                    Debug.Assert(method.OwningType.IsInterface);
                    exactType = constrained;
                }
                else if (constrained.IsValueType)
                {
                    // We'll need to box `this`. Note we use _constrained here, because the other one is canonical.
                    AddBoxingDependencies(_constrained, reason);
                }
            }

            MethodDesc targetMethod = methodAfterConstraintResolution;

            bool exactContextNeedsRuntimeLookup;
            if (targetMethod.HasInstantiation)
            {
                exactContextNeedsRuntimeLookup = targetMethod.IsSharedByGenericInstantiations;
            }
            else
            {
                exactContextNeedsRuntimeLookup = exactType.IsCanonicalSubtype(CanonicalFormKind.Any);
            }

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

            bool directCall = false;

            if (targetMethod.Signature.IsStatic)
            {
                if (_constrained != null && (!resolvedConstraint || forceUseRuntimeLookup))
                {
                    // Constrained call to static virtual interface method we didn't resolve statically
                    Debug.Assert(targetMethod.IsVirtual && targetMethod.OwningType.IsInterface);
                }
                else
                {
                    // Static methods are always direct calls
                    directCall = true;
                }
            }
            else if ((opcode != ILOpcode.callvirt && opcode != ILOpcode.ldvirtftn) || resolvedConstraint)
            {
                directCall = true;
            }
            else
            {
                if (targetMethod.IsCallEffectivelyDirect())
                {
                    directCall = true;
                }
            }

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

            MethodDesc targetForDelegate = !resolvedConstraint || forceUseRuntimeLookup ? runtimeDeterminedMethod : targetMethod;
            TypeDesc constraintForDelegate = !resolvedConstraint || forceUseRuntimeLookup ? _constrained : null;
            int numDependenciesBeforeTargetDetermination = _dependencies.Count;

            bool allowInstParam = opcode != ILOpcode.ldvirtftn && opcode != ILOpcode.ldftn;

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

                if (forceUseRuntimeLookup)
                {
                    var constrainedCallInfo = new ConstrainedCallInfo(_constrained, runtimeDeterminedMethod);
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.ConstrainedDirectCall, constrainedCallInfo), reason);
                }
                else
                {
                    // We have the canonical version of the method - find the runtime determined version.
                    Debug.Assert(targetMethod.OwningType.IsValueType || targetMethod.Signature.IsStatic);

                    MethodDesc targetOfLookup;
                    if (_constrained.IsRuntimeDeterminedType)
                        targetOfLookup = _compilation.TypeSystemContext.GetMethodForRuntimeDeterminedType(targetMethod.GetTypicalMethodDefinition(), (RuntimeDeterminedType)_constrained);
                    else if (_constrained.HasInstantiation)
                        targetOfLookup = _compilation.TypeSystemContext.GetMethodForInstantiatedType(targetMethod.GetTypicalMethodDefinition(), (InstantiatedType)_constrained);
                    else
                        targetOfLookup = targetMethod.GetMethodDefinition();
                    if (targetOfLookup.HasInstantiation)
                    {
                        targetOfLookup = targetOfLookup.MakeInstantiatedMethod(runtimeDeterminedMethod.Instantiation);
                    }
                    Debug.Assert(targetOfLookup.GetCanonMethodTarget(CanonicalFormKind.Specific) == targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific));
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodEntry, targetOfLookup), reason);

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

                if (exactContextNeedsRuntimeLookup)
                {
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodEntry, runtimeDeterminedMethod), reason);
                }
                else
                {
                    _dependencies.Add(_factory.FatFunctionPointer(targetMethod), reason);
                }
            }
            else if (directCall)
            {
                bool referencingArrayAddressMethod = false;

                if (targetMethod.IsIntrinsic)
                {
                    // If this is an intrinsic method with a callsite-specific expansion, this will replace
                    // the method with a method the intrinsic expands into. If it's not the special intrinsic,
                    // method stays unchanged.
                    targetMethod = _compilation.ExpandIntrinsicForCallsite(targetMethod, _canonMethod);

                    // Array address method requires special dependency tracking.
                    referencingArrayAddressMethod = targetMethod.IsArrayAddressMethod();
                }

                MethodDesc concreteMethod = targetMethod;
                targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);

                if (targetMethod.IsConstructor && targetMethod.OwningType.IsString)
                {
                    _dependencies.Add(_factory.StringAllocator(targetMethod), reason);
                }
                else if (exactContextNeedsRuntimeLookup)
                {
                    if (targetMethod.IsSharedByGenericInstantiations && !resolvedConstraint && !referencingArrayAddressMethod)
                    {
                        ISymbolNode instParam = null;

                        if (!_canonMethod.IsSharedByGenericInstantiations)
                        {
                            // Some handemitted IL helpers will call __Canon methods directly from unshared context.
                            // This is fine, we just don't report any dependencies for the exact instantiation.
                        }
                        else if (targetMethod.RequiresInstMethodDescArg())
                        {
                            instParam = GetGenericLookupHelper(ReadyToRunHelperId.MethodDictionary, runtimeDeterminedMethod);
                        }
                        else if (targetMethod.RequiresInstMethodTableArg())
                        {
                            instParam = GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.OwningType);
                        }
                        else
                        {
                            Debug.Assert(targetMethod.AcquiresInstMethodTableFromThis());
                            _dependencies.Add(_factory.ShadowNonConcreteMethod(concreteMethod), reason);
                        }

                        if (instParam != null)
                        {
                            _dependencies.Add(instParam, reason);
                        }

                        _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason);
                    }
                    else
                    {
                        Debug.Assert(!forceUseRuntimeLookup);
                        _dependencies.Add(GetMethodEntrypoint(targetMethod), reason);

                        if (targetMethod.RequiresInstMethodTableArg() && resolvedConstraint)
                        {
                            if (_constrained.IsRuntimeDeterminedSubtype)
                                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, _constrained), reason);
                            else
                                _dependencies.Add(_factory.ConstructedTypeSymbol(_constrained), reason);
                        }

                        if (referencingArrayAddressMethod && !_isReadOnly)
                        {
                            // Address method is special - it expects an instantiation argument, unless a readonly prefix was applied.
                            _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.OwningType), reason);
                        }
                    }
                }
                else
                {
                    ISymbolNode instParam = null;

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

                    if (instParam != null)
                    {
                        _dependencies.Add(instParam, reason);
                    }

                    _dependencies.Add(GetMethodEntrypoint(targetMethod), reason);
                }
            }
            else if (staticResolution is DefaultInterfaceMethodResolution.Diamond or DefaultInterfaceMethodResolution.Reabstraction)
            {
                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && _constrained != null);

                // TODO: https://github.com/dotnet/runtime/issues/72589
                ThrowHelper.ThrowBadImageFormatException();
            }
            else if (method.Signature.IsStatic)
            {
                // This should be an unresolved static virtual interface method call. Other static methods should
                // have been handled as a directCall above.
                Debug.Assert(targetMethod.OwningType.IsInterface && targetMethod.IsVirtual && _constrained != null);

                var constrainedCallInfo = new ConstrainedCallInfo(_constrained, runtimeDeterminedMethod);
                var constrainedHelperId = ReadyToRunHelperId.ConstrainedDirectCall;

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

                _dependencies.Add(GetGenericLookupHelper(constrainedHelperId, constrainedCallInfo), reason);
            }
            else if (method.HasInstantiation)
            {
                // Generic virtual method call

                MethodDesc methodToLookup = _compilation.GetTargetOfGenericVirtualMethodCall(runtimeDeterminedMethod);

                _compilation.DetectGenericCycles(
                        _canonMethod,
                        methodToLookup.GetCanonMethodTarget(CanonicalFormKind.Specific));

                if (exactContextNeedsRuntimeLookup)
                {
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodHandle, methodToLookup), reason);
                }
                else
                {
                    _dependencies.Add(_factory.RuntimeMethodHandle(methodToLookup), reason);
                }

                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GVMLookupForSlot), reason);
            }
            else if (method.OwningType.IsInterface)
            {
                if (exactContextNeedsRuntimeLookup)
                {
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.VirtualDispatchCell, runtimeDeterminedMethod), reason);
                }
                else
                {
                    _dependencies.Add(_factory.InterfaceDispatchCell(method), reason);
                }
            }
            else if (_compilation.NeedsSlotUseTracking(method.OwningType))
            {
                MethodDesc slotDefiningMethod = targetMethod.IsNewSlot ?
                        targetMethod : MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(targetMethod);
                _dependencies.Add(_factory.VirtualMethodUse(slotDefiningMethod), reason);
            }

            // Is this a verifiable delegate creation sequence (load function pointer followed by newobj)?
            if ((opcode == ILOpcode.ldftn || opcode == ILOpcode.ldvirtftn)
                && _currentOffset + 5 < _ilBytes.Length
                && _basicBlocks[_currentOffset] == null
                && _ilBytes[_currentOffset] == (byte)ILOpcode.newobj)
            {
                // TODO: for ldvirtftn we need to also check for the `dup` instruction
                int ctorToken = ReadILTokenAt(_currentOffset + 1);
                var ctorMethod = (MethodDesc)_methodIL.GetObject(ctorToken);
                if (ctorMethod.OwningType.IsDelegate)
                {
                    // Yep, verifiable delegate creation

                    // Drop any dependencies we inserted so far - the delegate construction helper is the only dependency
                    while (_dependencies.Count > numDependenciesBeforeTargetDetermination)
                        _dependencies.RemoveAt(_dependencies.Count - 1);

                    TypeDesc canonDelegateType = ctorMethod.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific);
                    DelegateCreationInfo info = _compilation.GetDelegateCtor(canonDelegateType, targetForDelegate, constraintForDelegate, opcode == ILOpcode.ldvirtftn);

                    if (info.NeedsRuntimeLookup)
                        _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DelegateCtor, info), reason);
                    else
                        _dependencies.Add(_factory.ReadyToRunHelper(ReadyToRunHelperId.DelegateCtor, info), reason);
                }
            }
        }

        private void ImportLdFtn(int token, ILOpcode opCode)
        {
            ImportCall(opCode, token);
        }

        private void ImportJmp(int token)
        {
            // JMP is kind of like a tail call (with no arguments pushed on the stack).
            ImportCall(ILOpcode.call, token);
        }

        private void ImportCalli(int token)
        {
            MethodSignature signature = (MethodSignature)_methodIL.GetObject(token);

            // Managed calli
            if ((signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == 0)
                return;

            // Calli in marshaling stubs
            if (_methodIL is Internal.IL.Stubs.PInvokeILStubMethodIL)
                return;

            MethodDesc stub = _compilation.PInvokeILProvider.GetCalliStub(
                signature,
                ((MetadataType)_methodIL.OwningMethod.OwningType).Module);

            _dependencies.Add(_factory.CanonicalEntrypoint(stub), "calli");
        }

        private void ImportBranch(ILOpcode opcode, BasicBlock target, BasicBlock fallthrough)
        {
            object condition = null;

            if (opcode == ILOpcode.brfalse
                && _typeEqualityPatternAnalyzer.IsTypeEqualityBranch
                && !_typeEqualityPatternAnalyzer.IsTwoTokens
                && !_typeEqualityPatternAnalyzer.IsInequality)
            {
                TypeDesc typeEqualityCheckType = (TypeDesc)_canonMethodIL.GetObject(_typeEqualityPatternAnalyzer.Token1);
                if (!typeEqualityCheckType.IsGenericDefinition
                    && ConstructedEETypeNode.CreationAllowed(typeEqualityCheckType)
                    && !typeEqualityCheckType.ConvertToCanonForm(CanonicalFormKind.Specific).IsCanonicalSubtype(CanonicalFormKind.Any))
                {
                    // If the type could generate metadata, we set the condition to the presence of the metadata.
                    // This covers situations where the typeof is compared against metadata-only types.
                    // Note this assumes a constructed MethodTable always implies having metadata.
                    // This will likely remain true because anyone can call Object.GetType on a constructed type.
                    // If the type cannot generate metadata, we only condition on the MethodTable itself.
                    if (!_factory.MetadataManager.IsReflectionBlocked(typeEqualityCheckType)
                        && typeEqualityCheckType.GetTypeDefinition() is MetadataType typeEqualityCheckMetadataType)
                        condition = _factory.TypeMetadata(typeEqualityCheckMetadataType);
                    else
                        condition = _factory.MaximallyConstructableType(typeEqualityCheckType);
                }
            }

            if (opcode == ILOpcode.brfalse && _isInstCheckPatternAnalyzer.IsIsInstBranch)
            {
                TypeDesc isinstCheckType = (TypeDesc)_canonMethodIL.GetObject(_isInstCheckPatternAnalyzer.Token);
                if (ConstructedEETypeNode.CreationAllowed(isinstCheckType)
                    // Below makes sure we don't need to worry about variance
                    && !isinstCheckType.ConvertToCanonForm(CanonicalFormKind.Specific).IsCanonicalSubtype(CanonicalFormKind.Any)
                    // However, we still need to worry about variant-by-size casting with arrays
                    && !_factory.TypeSystemContext.IsArrayVariantCastable(isinstCheckType))
                {
                    condition = _factory.MaximallyConstructableType(isinstCheckType);
                }
            }

            if (opcode == ILOpcode.brfalse && _previousInstructionOffset >= 0)
            {
                var reader = new ILReader(_ilBytes, _previousInstructionOffset);
                if (reader.ReadILOpcode() == ILOpcode.call
                    && _methodIL.GetObject(reader.ReadILToken()) is MethodDesc { IsIntrinsic: true } intrinsicMethod
                    && intrinsicMethod.HasCustomAttribute("System.Runtime.CompilerServices", "AnalysisCharacteristicAttribute"))
                {
                    condition = _factory.AnalysisCharacteristic(intrinsicMethod.GetName());
                }
            }

            ImportFallthrough(target);

            if (fallthrough != null)
                ImportFallthrough(fallthrough, condition);
        }

        private void ImportSwitchJump(int jmpBase, int[] jmpDelta, BasicBlock fallthrough)
        {
            for (int i = 0; i < jmpDelta.Length; i++)
            {
                BasicBlock target = _basicBlocks[jmpBase + jmpDelta[i]];
                ImportFallthrough(target);
            }

            if (fallthrough != null)
                ImportFallthrough(fallthrough);
        }

        private void ImportUnbox(int token, ILOpcode opCode)
        {
            TypeDesc type = (TypeDesc)_methodIL.GetObject(token);

            if (!type.IsValueType)
            {
                if (opCode == ILOpcode.unbox_any)
                {
                    // When applied to a reference type, unbox_any has the same effect as castclass.
                    ImportCasting(ILOpcode.castclass, token);
                }
                return;
            }

            if (type.IsRuntimeDeterminedSubtype)
            {
                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.NecessaryTypeHandle, type), "Unbox");
            }
            else
            {
                _dependencies.Add(_factory.NecessaryTypeSymbol(type), "Unbox");
            }

            ReadyToRunHelper helper;
            if (opCode == ILOpcode.unbox)
            {
                helper = ReadyToRunHelper.Unbox;
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Unbox_TypeTest), "Unbox");
            }
            else
            {
                Debug.Assert(opCode == ILOpcode.unbox_any);
                helper = ReadyToRunHelper.Unbox_Nullable;
            }

            _dependencies.Add(GetHelperEntrypoint(helper), "Unbox");
        }

        private void ImportRefAnyVal(int token)
        {
            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRefAny), "refanyval");
            ImportTypedRefOperationDependencies(token, "refanyval");
        }

        private void ImportMkRefAny(int token)
        {
            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.TypeHandleToRuntimeType), "mkrefany");
            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.TypeHandleToRuntimeTypeHandle), "mkrefany");
            _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token));
            ImportTypedRefOperationDependencies(token, "mkrefany");
        }

        private void ImportTypedRefOperationDependencies(int token, string reason)
        {
            var type = (TypeDesc)_methodIL.GetObject(token);
            if (type.IsRuntimeDeterminedSubtype)
            {
                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, type), reason);
            }
            else
            {
                _dependencies.Add(_factory.MaximallyConstructableType(type), reason);
            }
        }

        private void ImportLdToken(int token)
        {
            object obj = _methodIL.GetObject(token);

            if (obj is TypeDesc type)
            {
                // We might also be able to optimize this a little if this is a ldtoken/GetTypeFromHandle/Equals sequence.
                var helperId = ReadyToRunHelperId.MetadataTypeHandle;
                TypeEqualityPatternAnalyzer analyzer = _typeEqualityPatternAnalyzer;
                ILReader reader = new ILReader(_ilBytes, _currentOffset);
                while (!analyzer.IsDefault)
                {
                    ILOpcode opcode = reader.ReadILOpcode();
                    analyzer.Advance(opcode, reader, _methodIL);
                    reader.Skip(opcode);

                    if (analyzer.IsTypeEqualityCheck)
                    {
                        helperId = ReadyToRunHelperId.NecessaryTypeHandle;
                        break;
                    }
                }

                _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token));

                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeTypeHandle), "ldtoken");
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeType), "ldtoken");

                ISymbolNode reference;
                if (type.IsRuntimeDeterminedSubtype)
                {
                    reference = GetGenericLookupHelper(helperId, type);
                }
                else
                {
                    if (type.IsCanonicalDefinitionType(CanonicalFormKind.Any))
                    {
                        Debug.Assert(_methodIL.OwningMethod.Name.SequenceEqual("GetCanonType"u8));
                        helperId = ReadyToRunHelperId.NecessaryTypeHandle;
                    }

                    reference = _compilation.ComputeConstantLookup(helperId, type);
                }
                _dependencies.Add(reference, "ldtoken");
            }
            else if (obj is MethodDesc method)
            {
                _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (MethodDesc)_canonMethodIL.GetObject(token));

                if (method.IsRuntimeDeterminedExactMethod)
                {
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodHandle, method), "ldtoken");
                }
                else
                {
                    _dependencies.Add(_factory.RuntimeMethodHandle(method), "ldtoken");
                }

                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeMethodHandle), "ldtoken");
            }
            else
            {
                var field = (FieldDesc)obj;

                _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (FieldDesc)_canonMethodIL.GetObject(token));

                if (field.OwningType.IsRuntimeDeterminedSubtype)
                {
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.FieldHandle, field), "ldtoken");
                }
                else
                {
                    _dependencies.Add(_factory.RuntimeFieldHandle(field), "ldtoken");
                }

                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeFieldHandle), "ldtoken");
            }
        }

        private static void ImportRefAnyType()
        {
            // TODO
        }

        private static void ImportArgList()
        {
        }

        private void ImportConstrainedPrefix(int token)
        {
            _constrained = (TypeDesc)_methodIL.GetObject(token);
        }

        private void ImportReadOnlyPrefix()
        {
            _isReadOnly = true;
        }

        private void ImportFieldAccess(int token, bool isStatic, bool? write, string reason)
        {
            var field = (FieldDesc)_methodIL.GetObject(token);
            var canonField = (FieldDesc)_canonMethodIL.GetObject(token);

            if (field.IsLiteral)
                ThrowHelper.ThrowMissingFieldException(field.OwningType, field.GetName());

            _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _compilation.NodeFactory, _canonMethodIL, canonField);

            // `write` will be null for ld(s)flda. Consider address loads write unless they were
            // for initonly static fields. We'll trust the initonly that this is not a write.
            write ??= !field.IsInitOnly || !field.IsStatic;

            if (write.Value)
            {
                bool isInitOnlyWrite = field.OwningType == _methodIL.OwningMethod.OwningType
                    && ((field.IsStatic && _methodIL.OwningMethod.IsStaticConstructor)
                        || (!field.IsStatic && _methodIL.OwningMethod.IsConstructor));

                if (!isInitOnlyWrite)
                {
                    FieldDesc fieldToReport = canonField;
                    DefType fieldOwningType = canonField.OwningType;
                    TypeDesc canonFieldOwningType = fieldOwningType.ConvertToCanonForm(CanonicalFormKind.Specific);
                    if (fieldOwningType != canonFieldOwningType)
                        fieldToReport = _factory.TypeSystemContext.GetFieldForInstantiatedType(fieldToReport.GetTypicalFieldDefinition(), (InstantiatedType)canonFieldOwningType);

                    _dependencies.Add(_factory.NotReadOnlyField(fieldToReport), "Field written outside initializer");
                }
            }

            // Covers both ldsfld/ldsflda and ldfld/ldflda with a static field
            if (isStatic || field.IsStatic)
            {
                // ldsfld/ldsflda with an instance field is invalid IL
                if (isStatic && !field.IsStatic)
                    ThrowHelper.ThrowInvalidProgramException();

                // References to literal fields from IL body should never resolve.
                // The CLR would throw a MissingFieldException while jitting and so should we.
                if (field.IsLiteral)
                    ThrowHelper.ThrowMissingFieldException(field.OwningType, field.GetName());

                ReadyToRunHelperId helperId;
                if (field.HasRva)
                {
                    // We don't care about field RVA data for the usual cases, but if this is one of the
                    // magic fields the compiler synthetized, the data blob might bring more dependencies
                    // and we need to scan those.
                    _dependencies.Add(_compilation.GetFieldRvaData(field), reason);
                    if (_compilation.HasLazyStaticConstructor(canonField.OwningType))
                    {
                        helperId = ReadyToRunHelperId.GetNonGCStaticBase;
                        goto addBase;
                    }
                    return;
                }

                if (field.IsThreadStatic)
                {
                    helperId = ReadyToRunHelperId.GetThreadStaticBase;
                }
                else if (field.HasGCStaticBase)
                {
                    helperId = ReadyToRunHelperId.GetGCStaticBase;
                }
                else
                {
                    helperId = ReadyToRunHelperId.GetNonGCStaticBase;
                }

            addBase:
                TypeDesc owningType = field.OwningType;
                if (owningType.IsRuntimeDeterminedSubtype)
                {
                    _dependencies.Add(GetGenericLookupHelper(helperId, owningType), reason);
                }
                else
                {
                    _dependencies.Add(_factory.ReadyToRunHelper(helperId, owningType), reason);
                }
            }
        }

        private void ImportLoadField(int token, bool isStatic)
        {
            ImportFieldAccess(token, isStatic, write: false, isStatic ? "ldsfld" : "ldfld");
        }

        private void ImportAddressOfField(int token, bool isStatic)
        {
            ImportFieldAccess(token, isStatic, write: null, isStatic ? "ldsflda" : "ldflda");
        }

        private void ImportStoreField(int token, bool isStatic)
        {
            ImportFieldAccess(token, isStatic, write: true, isStatic ? "stsfld" : "stfld");
        }

        private void ImportLoadString(int token)
        {
            _dependencies.Add(_factory.SerializedStringObject((string)_methodIL.GetObject(token)), "ldstr");
        }

        private void ImportBox(int token)
        {
            var type = (TypeDesc)_methodIL.GetObject(token);

            // There are some sequences of box with ByRefLike types that are allowed
            // per the extension to the ECMA-335 specification.
            // Everything else is invalid.
            if (!type.IsRuntimeDeterminedType && type.IsByRefLike)
            {
                ILReader reader = new ILReader(_ilBytes, _currentOffset);
                ILOpcode nextOpcode = reader.ReadILOpcode();

                // box ; br_true/false
                if (nextOpcode is ILOpcode.brtrue or ILOpcode.brtrue_s or ILOpcode.brfalse or ILOpcode.brfalse_s)
                    return;

                if (nextOpcode is ILOpcode.unbox_any or ILOpcode.isinst)
                {
                    var type2 = (TypeDesc)_methodIL.GetObject(reader.ReadILToken());
                    if (type == type2)
                    {
                        // box ; unbox_any with same token
                        if (nextOpcode == ILOpcode.unbox_any)
                            return;

                        nextOpcode = reader.ReadILOpcode();

                        // box ; isinst ; br_true/false
                        if (nextOpcode is ILOpcode.brtrue or ILOpcode.brtrue_s or ILOpcode.brfalse or ILOpcode.brfalse_s)
                            return;

                        // box ; isinst ; unbox_any
                        if (nextOpcode == ILOpcode.unbox_any)
                        {
                            type2 = (TypeDesc)_methodIL.GetObject(reader.ReadILToken());
                            if (type2 == type)
                                return;
                        }
                    }
                }

                ThrowHelper.ThrowInvalidProgramException();
            }

            AddBoxingDependencies(type, "Box");
        }

        private void AddBoxingDependencies(TypeDesc type, string reason)
        {
            Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Any));

            // Generic code will have BOX instructions when referring to T - the instruction is a no-op
            // if the substitution wasn't a value type.
            if (!type.IsValueType)
                return;

            TypeDesc typeForAccessCheck = type.IsRuntimeDeterminedSubtype ? type.ConvertToCanonForm(CanonicalFormKind.Specific) : type;
            _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, typeForAccessCheck);

            if (type.IsRuntimeDeterminedSubtype)
            {
                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, type), reason);
            }
            else
            {
                _dependencies.Add(_factory.ConstructedTypeSymbol(type), reason);
            }

            if (type.IsNullable)
            {
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Box_Nullable), reason);
            }
            else
            {
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Box), reason);
            }
        }

        private void ImportLeave(BasicBlock target)
        {
            ImportFallthrough(target);
        }

        private void ImportNewArray(int token)
        {
            var elementType = (TypeDesc)_methodIL.GetObject(token);
            _factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token));
            if (elementType.IsRuntimeDeterminedSubtype)
            {
                _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, elementType.MakeArrayType()), "newarr");
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewArray), "newarr");
            }
            else
            {
                if (elementType.IsVoid)
                    ThrowHelper.ThrowInvalidProgramException();
                _dependencies.Add(_factory.ConstructedTypeSymbol(elementType.MakeArrayType()), "newarr");
            }
        }

        private void ImportLoadElement(int token)
        {
            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.RngChkFail), "ldelem");
        }

        private void ImportLoadElement(TypeDesc elementType)
        {
            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.RngChkFail), "ldelem");
        }

        private void ImportStoreElement(int token)
        {
            ImportStoreElement((TypeDesc)_methodIL.GetObject(token));
        }

        private void ImportStoreElement(TypeDesc elementType)
        {
            if (elementType == null || elementType.IsGCPointer)
            {
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Stelem_Ref), "stelem");
            }

            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.RngChkFail), "stelem");
        }

        private void ImportAddressOfElement(int token)
        {
            TypeDesc elementType = (TypeDesc)_methodIL.GetObject(token);
            if (elementType.IsGCPointer && !_isReadOnly)
            {
                if (elementType.IsRuntimeDeterminedSubtype)
                    _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.NecessaryTypeHandle, elementType), "ldelema");
                else
                    _dependencies.Add(_factory.NecessaryTypeSymbol(elementType), "ldelema");

                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Ldelema_Ref), "ldelema");
            }

            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.RngChkFail), "ldelema");
        }

        private void ImportBinaryOperation(ILOpcode opcode)
        {
            switch (opcode)
            {
                case ILOpcode.add_ovf:
                case ILOpcode.add_ovf_un:
                case ILOpcode.sub_ovf:
                case ILOpcode.sub_ovf_un:
                    _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Overflow), "_ovf");
                    break;
                case ILOpcode.mul_ovf:
                case ILOpcode.mul_ovf_un:
                    if (_compilation.TypeSystemContext.Target.PointerSize == 4)
                    {
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.LMulOfv), "_lmulovf");
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.ULMulOvf), "_ulmulovf");
                    }

                    _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Overflow), "_ovf");
                    break;
                case ILOpcode.div:
                case ILOpcode.div_un:
                    if (_compilation.TypeSystemContext.Target.Architecture is TargetArchitecture.ARM or TargetArchitecture.X86)
                    {
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.ULDiv), "_uldiv");
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.LDiv), "_ldiv");

                        if (_compilation.TypeSystemContext.Target.Architecture is TargetArchitecture.ARM)
                        {
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.UDiv), "_udiv");
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Div), "_div");
                        }
                    }
                    else if (_compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.ARM64)
                    {
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.ThrowDivZero), "_divbyzero");
                        if (opcode == ILOpcode.div)
                        {
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Overflow), "_ovf");
                        }
                    }
                    break;
                case ILOpcode.rem:
                case ILOpcode.rem_un:
                    if (_compilation.TypeSystemContext.Target.Architecture is TargetArchitecture.ARM or TargetArchitecture.X86)
                    {
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.ULMod), "_ulmod");
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.LMod), "_lmod");
                        if (_compilation.TypeSystemContext.Target.Architecture is TargetArchitecture.ARM)
                        {
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.UMod), "_umod");
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Mod), "_mod");
                        }
                    }
                    else if (_compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.ARM64)
                    {
                        _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.ThrowDivZero), "_divbyzero");
                        if (opcode == ILOpcode.rem)
                        {
                            _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Overflow), "_ovf");
                        }
                    }

                    _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.DblRem), "rem");
                    _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.FltRem), "rem");
                    break;
            }
        }

        private void ImportConvert(WellKnownType wellKnownType, bool checkOverflow, bool unsigned)
        {
            if (checkOverflow)
            {
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2IntOvf), "_dbl2intovf");
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2UIntOvf), "_dbl2uintovf");
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2LngOvf), "_dbl2lngovf");
                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Dbl2ULngOvf), "_dbl2ulngovf");

                _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.Overflow), "_ovf");
            }
        }

        private void ImportBasicBlockEdge(BasicBlock source, BasicBlock next, object condition = null)
        {
            // We don't track multiple conditions; if the source basic block is only reachable under a condition,
            // this condition will be used for the next basic block, irrespective if we could make it more narrow.
            object effectiveCondition = source.Condition ?? condition;

            // Did we already look at this basic block?
            if (next.State != BasicBlock.ImportState.Unmarked)
            {
                // If next is not conditioned, it stays not conditioned.
                // If it was conditioned on something else, it needs to become unconditional.
                // If the conditions match, it stays conditioned on the same thing.
                if (next.Condition != null && next.Condition != effectiveCondition)
                {
                    // Now we need to make `next` not conditioned. We move all of its dependencies to
                    // unconditional dependencies, and do this for all basic blocks that are reachable
                    // from it.
                    // TODO-SIZE: below doesn't do it for all basic blocks reachable from `next`, but
                    // for all basic blocks with the same conditon. This is a shortcut. It likely
                    // doesn't matter in practice.
                    object conditionToRemove = next.Condition;
                    foreach (BasicBlock bb in _basicBlocks)
                    {
                        if (bb?.Condition == conditionToRemove)
                        {
                            _unconditionalDependencies.AddRange(bb.Dependencies);
                            bb.Dependencies = null;
                            bb.Condition = null;
                        }
                    }
                }
            }
            else
            {
                if (effectiveCondition != null)
                {
                    next.Condition = effectiveCondition;
                    next.Dependencies = new DependencyList();
                    MarkBasicBlock(next, ref _lateBasicBlocks);
                }
                else
                {
                    MarkBasicBlock(next);
                }
            }
        }

        private void ImportFallthrough(BasicBlock next, object condition = null)
        {
            ImportBasicBlockEdge(_currentBasicBlock, next, condition);
        }

        private int ReadILTokenAt(int ilOffset)
        {
            return (int)(_ilBytes[ilOffset]
                + (_ilBytes[ilOffset + 1] << 8)
                + (_ilBytes[ilOffset + 2] << 16)
                + (_ilBytes[ilOffset + 3] << 24));
        }

        private static void ReportInvalidBranchTarget(int targetOffset)
        {
            ThrowHelper.ThrowInvalidProgramException();
        }

        private static void ReportFallthroughAtEndOfMethod()
        {
            ThrowHelper.ThrowInvalidProgramException();
        }

        private static void ReportMethodEndInsideInstruction()
        {
            ThrowHelper.ThrowInvalidProgramException();
        }

        private static void ReportInvalidInstruction(ILOpcode opcode)
        {
            ThrowHelper.ThrowInvalidProgramException();
        }

        private static bool IsTypeGetTypeFromHandle(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("GetTypeFromHandle"u8))
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    return owningType.Name.SequenceEqual("Type"u8) && owningType.Namespace.SequenceEqual("System"u8);
                }
            }

            return false;
        }

        private static bool IsActivatorDefaultConstructorOf(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("DefaultConstructorOf"u8) && method.Instantiation.Length == 1)
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    return owningType.Name.SequenceEqual("Activator"u8) && owningType.Namespace.SequenceEqual("System"u8);
                }
            }

            return false;
        }

        private static bool IsActivatorAllocatorOf(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("AllocatorOf"u8) && method.Instantiation.Length == 1)
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    return owningType.Name.SequenceEqual("Activator"u8) && owningType.Namespace.SequenceEqual("System"u8);
                }
            }

            return false;
        }

        private static bool IsEETypePtrOf(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("Of"u8) && method.Instantiation.Length == 1)
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    return owningType.Name.SequenceEqual("MethodTable"u8) && owningType.Namespace.SequenceEqual("Internal.Runtime"u8);
                }
            }

            return false;
        }

        private static bool IsRuntimeHelpersIsReferenceOrContainsReferences(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("IsReferenceOrContainsReferences"u8) && method.Instantiation.Length == 1)
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    return owningType.Name.SequenceEqual("RuntimeHelpers"u8) && owningType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8);
                }
            }

            return false;
        }

        private static bool IsMemoryMarshalGetArrayDataReference(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("GetArrayDataReference"u8) && method.Instantiation.Length == 1)
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    return owningType.Name.SequenceEqual("MemoryMarshal"u8) && owningType.Namespace.SequenceEqual("System.Runtime.InteropServices"u8);
                }
            }

            return false;
        }

        private static bool IsAsyncHelpersAwait(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("Await"u8))
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    return owningType.Module == method.Context.SystemModule
                        && owningType.Name.SequenceEqual("AsyncHelpers"u8)
                        && owningType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8);
                }
            }

            return false;
        }

        private static bool IsTaskConfigureAwait(MethodDesc method)
        {
            if (method.IsIntrinsic && method.Name.SequenceEqual("ConfigureAwait"u8))
            {
                MetadataType owningType = method.OwningType as MetadataType;
                if (owningType != null)
                {
                    ReadOnlySpan<byte> typeName = owningType.Name;
                    return owningType.Module == method.Context.SystemModule
                        && owningType.Namespace.SequenceEqual("System.Threading.Tasks"u8)
                        && (typeName.SequenceEqual("Task"u8)
                            || typeName.SequenceEqual("Task`1"u8)
                            || typeName.SequenceEqual("ValueTask"u8)
                            || typeName.SequenceEqual("ValueTask`1"u8));
                }
            }

            return false;
        }

        private DefType GetWellKnownType(WellKnownType wellKnownType)
        {
            return _compilation.TypeSystemContext.GetWellKnownType(wellKnownType);
        }

        private static void ImportNop() { }
        private static void ImportBreak() { }
        private static void ImportLoadVar(int index, bool argument) { }
        private static void ImportStoreVar(int index, bool argument) { }
        private static void ImportAddressOfVar(int index, bool argument) { }
        private static void ImportDup() { }
        private static void ImportPop() { }
        private static void ImportLoadNull() { }
        private static void ImportReturn() { }
        private static void ImportLoadInt(long value, StackValueKind kind) { }
        private static void ImportLoadFloat(double value) { }
        private static void ImportLoadIndirect(int token) { }
        private static void ImportLoadIndirect(TypeDesc type) { }
        private static void ImportStoreIndirect(int token) { }
        private static void ImportStoreIndirect(TypeDesc type) { }
        private static void ImportShiftOperation(ILOpcode opcode) { }
        private static void ImportCompareOperation(ILOpcode opcode) { }
        private static void ImportUnaryOperation(ILOpcode opCode) { }
        private static void ImportCpOpj(int token) { }
        private static void ImportCkFinite() { }
        private static void ImportLocalAlloc() { }
        private static void ImportEndFilter() { }
        private static void ImportCpBlk() { }
        private static void ImportInitBlk() { }
        private static void ImportRethrow() { }
        private static void ImportSizeOf(int token) { }
        private static void ImportUnalignedPrefix(byte alignment) { }
        private static void ImportVolatilePrefix() { }
        private static void ImportTailPrefix() { }
        private static void ImportNoPrefix(byte mask) { }
        private static void ImportThrow() { }
        private static void ImportInitObj(int token) { }
        private static void ImportLoadLength() { }
        private static void ImportEndFinally() { }
    }
}