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

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;

using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysisFramework;

using Internal.Text;
using Internal.IL;
using Internal.IL.Stubs;
using Internal.JitInterface;
using Internal.TypeSystem;
using Internal.ReadyToRunConstants;

using Debug = System.Diagnostics.Debug;
using Internal.NativeFormat;

namespace ILCompiler
{
    /// <summary>
    /// IL scan analyzer of programs - this class analyzes what methods, types and other runtime artifact
    /// will need to be generated during a compilation. The result of analysis is a conservative superset of
    /// what methods will be compiled by the actual codegen backend.
    /// </summary>
    internal sealed class ILScanner : Compilation, IILScanner
    {
        private readonly int _parallelism;

        internal ILScanner(
            DependencyAnalyzerBase<NodeFactory> dependencyGraph,
            ILScanNodeFactory nodeFactory,
            IEnumerable<ICompilationRootProvider> roots,
            ILProvider ilProvider,
            DebugInformationProvider debugInformationProvider,
            Logger logger,
            int parallelism)
            : base(dependencyGraph, nodeFactory, roots, ilProvider, debugInformationProvider, nodeFactory.CompilationModuleGroup, logger)
        {
            _helperCache = new HelperCache(this);
            _parallelism = parallelism;
        }

        protected override void CompileInternal(string outputFile, ObjectDumper dumper)
        {
            // TODO: We should have a base class for compilation that doesn't implement ICompilation so that
            // we don't need this.
            throw new NotSupportedException();
        }

        protected override void ComputeDependencyNodeDependencies(List<DependencyNodeCore<NodeFactory>> obj)
        {
            // Determine the list of method we actually need to scan
            var methodsToCompile = new List<ScannedMethodNode>();
            var canonicalMethodsToCompile = new HashSet<MethodDesc>();

            foreach (DependencyNodeCore<NodeFactory> dependency in obj)
            {
                var methodCodeNodeNeedingCode = dependency as ScannedMethodNode;
                if (methodCodeNodeNeedingCode == null)
                {
                    // To compute dependencies of the shadow method that tracks dictionary
                    // dependencies we need to ensure there is code for the canonical method body.
                    var dependencyMethod = (ShadowMethodNode)dependency;
                    methodCodeNodeNeedingCode = (ScannedMethodNode)dependencyMethod.CanonicalMethodNode;
                }

                // We might have already queued this method for compilation
                MethodDesc method = methodCodeNodeNeedingCode.Method;
                if (method.IsCanonicalMethod(CanonicalFormKind.Any)
                    && !canonicalMethodsToCompile.Add(method))
                {
                    continue;
                }

                methodsToCompile.Add(methodCodeNodeNeedingCode);
            }

            if (_parallelism == 1)
            {
                CompileSingleThreaded(methodsToCompile);
            }
            else
            {
                CompileMultiThreaded(methodsToCompile);
            }
        }

        private void CompileMultiThreaded(List<ScannedMethodNode> methodsToCompile)
        {
            if (Logger.IsVerbose)
            {
                Logger.LogMessage($"Scanning {methodsToCompile.Count} methods...");
            }

            Parallel.ForEach(
                methodsToCompile,
                new ParallelOptions { MaxDegreeOfParallelism = _parallelism },
                CompileSingleMethod);
        }

        private void CompileSingleThreaded(List<ScannedMethodNode> methodsToCompile)
        {
            foreach (ScannedMethodNode methodCodeNodeNeedingCode in methodsToCompile)
            {
                if (Logger.IsVerbose)
                {
                    Logger.LogMessage($"Scanning {methodCodeNodeNeedingCode.Method}...");
                }

                CompileSingleMethod(methodCodeNodeNeedingCode);
            }
        }

        private void CompileSingleMethod(ScannedMethodNode methodCodeNodeNeedingCode)
        {
            MethodDesc method = methodCodeNodeNeedingCode.Method;

            try
            {
                var importer = new ILImporter(this, method);
                methodCodeNodeNeedingCode.InitializeDependencies(_nodeFactory, importer.Import());
            }
            catch (TypeSystemException ex)
            {
                // Try to compile the method again, but with a throwing method body this time.
                MethodIL throwingIL = TypeSystemThrowingILEmitter.EmitIL(method, ex);
                var importer = new ILImporter(this, method, throwingIL);
                methodCodeNodeNeedingCode.InitializeDependencies(_nodeFactory, importer.Import(), ex);
            }
            catch (Exception ex)
            {
                throw new CodeGenerationFailedException(method, ex);
            }
        }

        ILScanResults IILScanner.Scan()
        {
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.BulkWriteBarrier), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.MemCpy), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.MemSet), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.MemZero), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastAny), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastInterface), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastClass), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckCastClassSpecial), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckInstanceAny), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckInstanceInterface), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.CheckInstanceClass), "Not tracked by scanner");
            _dependencyGraph.AddRoot(GetHelperEntrypoint(ReadyToRunHelper.IsInstanceOfException), "Not tracked by scanner");

            _dependencyGraph.ComputeMarkedNodes();

            _nodeFactory.SetMarkingComplete();

            return new ILScanResults(_dependencyGraph, _nodeFactory);
        }

        public ISymbolNode GetHelperEntrypoint(ReadyToRunHelper helper)
        {
            return _helperCache.GetOrCreateValue(helper).Symbol;
        }

        private sealed class Helper
        {
            public ReadyToRunHelper HelperID { get; }
            public ISymbolNode Symbol { get; }

            public Helper(ReadyToRunHelper id, ISymbolNode symbol)
            {
                HelperID = id;
                Symbol = symbol;
            }
        }

        private HelperCache _helperCache;

        private sealed class HelperCache : LockFreeReaderHashtable<ReadyToRunHelper, Helper>
        {
            private Compilation _compilation;

            public HelperCache(Compilation compilation)
            {
                _compilation = compilation;
            }

            protected override bool CompareKeyToValue(ReadyToRunHelper key, Helper value) => key == value.HelperID;
            protected override bool CompareValueToValue(Helper value1, Helper value2) => value1.HelperID == value2.HelperID;
            protected override int GetKeyHashCode(ReadyToRunHelper key) => (int)key;
            protected override int GetValueHashCode(Helper value) => (int)value.HelperID;
            protected override Helper CreateValueFromKey(ReadyToRunHelper key)
            {
                string mangledName;
                MethodDesc methodDesc;
                JitHelper.GetEntryPoint(_compilation.TypeSystemContext, key, out mangledName, out methodDesc);
                Debug.Assert(mangledName != null || methodDesc != null);

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

                return new Helper(key, entryPoint);
            }

        }
    }

    public interface IILScanner
    {
        ILScanResults Scan();
    }

    internal sealed class ScannerFailedException : InternalCompilerErrorException
    {
        public ScannerFailedException(string message)
            : base(message + " " + "You can work around by running the compilation with scanner disabled.")
        {
        }
    }

    public class ILScanResults : CompilationResults
    {
        internal ILScanResults(DependencyAnalyzerBase<NodeFactory> graph, NodeFactory factory)
            : base(graph, factory)
        {
        }

        public AnalysisBasedInteropStubManager GetInteropStubManager(InteropStateManager stateManager, PInvokeILEmitterConfiguration pinvokePolicy)
        {
            return new AnalysisBasedInteropStubManager(stateManager, pinvokePolicy,
                _factory.MetadataManager.GetTypesWithStructMarshalling(),
                _factory.MetadataManager.GetTypesWithDelegateMarshalling());
        }

        public VTableSliceProvider GetVTableLayoutInfo()
        {
            return new ScannedVTableProvider(MarkedNodes);
        }

        public DictionaryLayoutProvider GetDictionaryLayoutInfo()
        {
            return new ScannedDictionaryLayoutProvider(_factory, MarkedNodes);
        }

        public DevirtualizationManager GetDevirtualizationManager()
        {
            return new ScannedDevirtualizationManager(_factory, MarkedNodes);
        }

        public IInliningPolicy GetInliningPolicy()
        {
            return new ScannedInliningPolicy(_factory.CompilationModuleGroup, MarkedNodes);
        }

        public MethodImportationErrorProvider GetMethodImportationErrorProvider()
        {
            return new ScannedMethodImportationErrorProvider(MarkedNodes);
        }

        public TypePreinit.TypePreinitializationPolicy GetPreinitializationPolicy()
        {
            return new ScannedPreinitializationPolicy(_factory.PreinitializationManager, MarkedNodes);
        }

        public InlinedThreadStatics GetInlinedThreadStatics()
        {
            return new ScannedInlinedThreadStatics(_factory, MarkedNodes);
        }

        public ReadOnlyFieldPolicy GetReadOnlyFieldPolicy()
        {
            return new ScannedReadOnlyPolicy(MarkedNodes);
        }

        public TypeMapManager GetTypeMapManager()
        {
            return new ScannedTypeMapManager(_factory);
        }

        public IEnumerable<string> GetAnalysisCharacteristics()
        {
            foreach (DependencyNodeCore<NodeFactory> n in MarkedNodes)
            {
                if (n is AnalysisCharacteristicNode acn)
                    yield return acn.Characteristic;
            }
        }

        private sealed class ScannedVTableProvider : VTableSliceProvider
        {
            private readonly Dictionary<TypeDesc, MethodDesc[]> _vtableSlices = new Dictionary<TypeDesc, MethodDesc[]>();

            public ScannedVTableProvider(ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                foreach (var node in markedNodes)
                {
                    var vtableSliceNode = node as VTableSliceNode;
                    if (vtableSliceNode != null)
                    {
                        ArrayBuilder<MethodDesc> usedSlots = default;

                        for (int i = 0; i < vtableSliceNode.Slots.Count; i++)
                        {
                            MethodDesc slot = vtableSliceNode.Slots[i];
                            if (vtableSliceNode.IsSlotUsed(slot))
                                usedSlots.Add(slot);
                        }

                        _vtableSlices.Add(vtableSliceNode.Type, usedSlots.ToArray());
                    }
                }
            }

            internal override VTableSliceNode GetSlice(TypeDesc type)
            {
                // TODO: move ownership of compiler-generated entities to CompilerTypeSystemContext.
                // https://github.com/dotnet/corert/issues/3873
                if (type.GetTypeDefinition() is Internal.TypeSystem.Ecma.EcmaType)
                {
                    if (!_vtableSlices.TryGetValue(type, out MethodDesc[] slots))
                    {
                        // If we couldn't find the vtable slice information for this type, it's because the scanner
                        // didn't correctly predict what will be needed.
                        // To troubleshoot, compare the dependency graph of the scanner and the compiler.
                        // Follow the path from the node that requested this node to the root.
                        // On the path, you'll find a node that exists in both graphs, but it's predecessor
                        // only exists in the compiler's graph. That's the place to focus the investigation on.
                        // Use the ILCompiler-DependencyGraph-Viewer tool to investigate.
                        string typeName = ExceptionTypeNameFormatter.Instance.FormatName(type);
                        throw new ScannerFailedException($"VTable of type '{typeName}' not computed by the IL scanner.");
                    }
                    return new LazilyBuiltVTableSliceNode(type, slots);
                }
                else
                    return new LazilyBuiltVTableSliceNode(type);
            }
        }

        private sealed class ScannedDictionaryLayoutProvider : DictionaryLayoutProvider
        {
            private Dictionary<TypeSystemEntity, (GenericLookupResult[] Slots, GenericLookupResult[] DiscardedSlots)> _layouts = new();
            private HashSet<TypeSystemEntity> _entitiesWithForcedLazyLookups = new HashSet<TypeSystemEntity>();

            public ScannedDictionaryLayoutProvider(NodeFactory factory, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                foreach (var node in markedNodes)
                {
                    if (node is DictionaryLayoutNode layoutNode)
                    {
                        TypeSystemEntity owningMethodOrType = layoutNode.OwningMethodOrType;
                        GenericLookupResult[] layout = OptimizeSlots(factory, layoutNode.Entries, out GenericLookupResult[] discarded);
                        _layouts.Add(owningMethodOrType, (layout, discarded));
                    }
                    else if (node is ReadyToRunGenericHelperNode genericLookup
                        && genericLookup.HandlesInvalidEntries(factory))
                    {
                        // If a dictionary layout has an associated lookup helper that contains handling of broken slots
                        // (because one of our precomputed dictionaries contained an uncompilable entry)
                        // we won't hand out a precomputed dictionary and keep using the lookup helpers.
                        // The inlined lookups using the precomputed dictionary wouldn't handle the broken slots.
                        _entitiesWithForcedLazyLookups.Add(genericLookup.DictionaryOwner);
                    }
                }
            }

            private static GenericLookupResult[] OptimizeSlots(NodeFactory factory, IEnumerable<GenericLookupResult> slots, out GenericLookupResult[] discarded)
            {
                ArrayBuilder<GenericLookupResult> slotBuilder = default;
                ArrayBuilder<GenericLookupResult> discardedBuilder = default;

                // Find all constructed and metadata type lookups. We'll use this for deduplication.
                var constructedTypeLookups = new HashSet<TypeDesc>();
                var metadataTypeLookups = new HashSet<TypeDesc>();
                foreach (GenericLookupResult lookupResult in slots)
                {
                    if (lookupResult is TypeHandleGenericLookupResult thLookup)
                        constructedTypeLookups.Add(thLookup.Type);
                    else if (lookupResult is MetadataTypeHandleGenericLookupResult mdthLookup)
                        metadataTypeLookups.Add(mdthLookup.Type);
                }

                // We go over all slots in the layout, looking for references to method dictionaries
                // that are going to be empty.
                // Set those slots aside so that we can avoid generating the references to such dictionaries.
                // We do this for methods only because method dictionaries have a high overhead (they
                // get prefixed with a pointer-padded 32-bit hashcode and might end up in various
                // summary tables as well).

                foreach (GenericLookupResult lookupResult in slots)
                {
                    if (lookupResult is MethodDictionaryGenericLookupResult methodDictLookup)
                    {
                        MethodDesc targetMethod = methodDictLookup.Method.GetCanonMethodTarget(CanonicalFormKind.Specific);
                        DictionaryLayoutNode targetLayout = factory.GenericDictionaryLayout(targetMethod);
                        if (targetLayout.IsEmpty)
                        {
                            discardedBuilder.Add(lookupResult);
                            continue;
                        }
                    }
                    else if (lookupResult is NecessaryTypeHandleGenericLookupResult thLookup)
                    {
                        if (constructedTypeLookups.Contains(thLookup.Type) || metadataTypeLookups.Contains(thLookup.Type))
                            continue;
                    }
                    else if (lookupResult is MetadataTypeHandleGenericLookupResult mdthLookup)
                    {
                        if (constructedTypeLookups.Contains(mdthLookup.Type))
                            continue;
                    }

                    slotBuilder.Add(lookupResult);
                }

                discarded = discardedBuilder.ToArray();
                return slotBuilder.ToArray();
            }

            private PrecomputedDictionaryLayoutNode GetPrecomputedLayout(TypeSystemEntity methodOrType)
            {
                if (!_layouts.TryGetValue(methodOrType, out var layout))
                {
                    // If we couldn't find the dictionary layout information for this, it's because the scanner
                    // didn't correctly predict what will be needed.
                    // To troubleshoot, compare the dependency graph of the scanner and the compiler.
                    // Follow the path from the node that requested this node to the root.
                    // On the path, you'll find a node that exists in both graphs, but it's predecessor
                    // only exists in the compiler's graph. That's the place to focus the investigation on.
                    // Use the ILCompiler-DependencyGraph-Viewer tool to investigate.
                    throw new ScannerFailedException($"Dictionary layout of '{methodOrType}' was not computed by the IL scanner.");
                }
                return new PrecomputedDictionaryLayoutNode(methodOrType, layout.Slots, layout.DiscardedSlots);
            }

            public override DictionaryLayoutNode GetLayout(TypeSystemEntity methodOrType)
            {
                if (_entitiesWithForcedLazyLookups.Contains(methodOrType))
                {
                    return new LazilyBuiltDictionaryLayoutNode(methodOrType);
                }

                if (methodOrType is TypeDesc type)
                {
                    // TODO: move ownership of compiler-generated entities to CompilerTypeSystemContext.
                    // https://github.com/dotnet/corert/issues/3873
                    if (type.GetTypeDefinition() is Internal.TypeSystem.Ecma.EcmaType)
                        return GetPrecomputedLayout(type);
                    else
                        return new LazilyBuiltDictionaryLayoutNode(type);
                }
                else
                {
                    Debug.Assert(methodOrType is MethodDesc);
                    MethodDesc method = (MethodDesc)methodOrType;

                    // TODO: move ownership of compiler-generated entities to CompilerTypeSystemContext.
                    // https://github.com/dotnet/corert/issues/3873
                    if (method.GetTypicalMethodDefinition() is Internal.TypeSystem.Ecma.EcmaMethod)
                        return GetPrecomputedLayout(method);
                    else
                        return new LazilyBuiltDictionaryLayoutNode(method);
                }
            }
        }

        private sealed class ScannedDevirtualizationManager : DevirtualizationManager
        {
            private CompilerTypeSystemContext _context;
            private HashSet<TypeDesc> _constructedMethodTables = new HashSet<TypeDesc>();
            private HashSet<TypeDesc> _metadataMethodTables = new HashSet<TypeDesc>();
            private HashSet<TypeDesc> _reflectionVisibleGenericDefinitionMethodTables = new HashSet<TypeDesc>();
            private HashSet<TypeDesc> _canonConstructedMethodTables = new HashSet<TypeDesc>();
            private HashSet<TypeDesc> _canonConstructedTypes = new HashSet<TypeDesc>();
            private HashSet<TypeDesc> _unsealedTypes = new HashSet<TypeDesc>();
            private Dictionary<TypeDesc, HashSet<TypeDesc>> _implementators = new();
            private HashSet<TypeDesc> _disqualifiedTypes = new();
            private HashSet<MethodDesc> _overriddenMethods = new();
            private HashSet<MethodDesc> _generatedVirtualMethods = new();
            private readonly bool _canHaveDynamicInterfaceImplementations;

            public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                _context = factory.TypeSystemContext;

                // Do not try to optimize around continuation types, we don't keep good track of them.
                // Allow CoreLib not to have this type.
                if (_context.SystemModule.GetType("System.Runtime.CompilerServices"u8, "Continuation"u8, throwIfNotFound: false) is MetadataType continuationType)
                {
                    _unsealedTypes.Add(continuationType);
                    _disqualifiedTypes.Add(continuationType);
                }

                var vtables = new Dictionary<TypeDesc, List<MethodDesc>>();
                var dynamicInterfaceCastableImplementationTargets = new HashSet<TypeDesc>();

                foreach (var node in markedNodes)
                {
                    // Collect all non-generic virtual method bodies we compiled
                    if (node is IMethodBodyNode { Method.IsVirtual: true, Method.HasInstantiation: false } virtualMethodBody)
                    {
                        _generatedVirtualMethods.Add(virtualMethodBody.Method);
                    }

                    if (node is ReflectionVisibleGenericDefinitionEETypeNode reflectionVisibleMT)
                    {
                        _reflectionVisibleGenericDefinitionMethodTables.Add(reflectionVisibleMT.Type);
                    }

                    if (node is MetadataEETypeNode metadataMT)
                    {
                        _metadataMethodTables.Add(metadataMT.Type);
                    }

                    TypeDesc type = (node as ConstructedEETypeNode)?.Type;

                    if (type != null)
                    {
                        _constructedMethodTables.Add(type);
                        TypeDesc canonForm = type.ConvertToCanonForm(CanonicalFormKind.Specific);
                        if (canonForm != type)
                        {
                            _canonConstructedMethodTables.Add(canonForm);
                        }

                        if (type.IsInterface)
                        {
                            if (((MetadataType)type).IsDynamicInterfaceCastableImplementation())
                            {
                                foreach (DefType baseInterface in type.RuntimeInterfaces)
                                {
                                    // If the interface is implemented through IDynamicInterfaceCastable, there might be
                                    // no real upper bound on the number of actual classes implementing it.
                                    if (CanAssumeWholeProgramViewOnTypeUse(factory, type, baseInterface))
                                        dynamicInterfaceCastableImplementationTargets.Add(baseInterface);
                                }
                            }
                        }
                        else
                        {
                            //
                            // We collect this information:
                            //
                            // 1. What types got allocated
                            // 2. What types are the base types of other types
                            //    This is needed for optimizations. We use this information to effectively
                            //    seal types that are not base types for any other type.
                            // 3. What types implement interfaces for which use we can assume whole
                            //    program view.
                            //

                            if (type is not MetadataType { IsAbstract: true })
                            {
                                // Record all interfaces this class implements to _implementators
                                foreach (DefType baseInterface in type.RuntimeInterfaces)
                                {
                                    if (CanAssumeWholeProgramViewOnTypeUse(factory, type, baseInterface))
                                    {
                                        RecordImplementation(baseInterface, type);
                                    }
                                }

                                // Record all base types of this class
                                for (DefType @base = type.BaseType; @base != null; @base = @base.BaseType)
                                {
                                    if (CanAssumeWholeProgramViewOnTypeUse(factory, type, @base))
                                    {
                                        RecordImplementation(@base, type);
                                    }
                                }
                            }

                            if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
                            {
                                // If the interface is implemented on a template type, there might be
                                // no real upper bound on the number of actual classes implementing it
                                // due to MakeGenericType.
                                foreach (DefType baseInterface in type.RuntimeInterfaces)
                                {
                                    _disqualifiedTypes.Add(baseInterface);
                                }

                                // Same for base classes
                                for (DefType @base = type.BaseType; @base != null; @base = @base.BaseType)
                                {
                                    _disqualifiedTypes.Add(@base);
                                }
                            }

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

                            if (canonType is not MetadataType { IsAbstract: true })
                            {
                                _canonConstructedTypes.Add(canonType.GetClosestDefType());
                                baseType = canonType.BaseType;
                                while (baseType != null)
                                {
                                    baseType = baseType.ConvertToCanonForm(CanonicalFormKind.Specific);
                                    if (!_canonConstructedTypes.Add(baseType))
                                        break;
                                    baseType = baseType.BaseType;
                                }
                            }

                            baseType = canonType.BaseType;
                            while (baseType != null)
                            {
                                baseType = baseType.ConvertToCanonForm(CanonicalFormKind.Specific);
                                if (!_unsealedTypes.Add(baseType))
                                    break;
                                baseType = baseType.BaseType;
                            }

                            static List<MethodDesc> BuildVTable(NodeFactory factory, TypeDesc currentType, TypeDesc implType, List<MethodDesc> vtable)
                            {
                                if (currentType == null)
                                    return vtable;

                                BuildVTable(factory, currentType.BaseType, implType, vtable);

                                IReadOnlyList<MethodDesc> slice = factory.VTable(currentType).Slots;
                                foreach (MethodDesc decl in slice)
                                {
                                    vtable.Add(implType.GetClosestDefType()
                                        .FindVirtualFunctionTargetMethodOnObjectType(decl));
                                }

                                return vtable;
                            }

                            baseType = type.BaseType;
                            if (!type.IsArray && baseType != null)
                            {
                                if (!vtables.TryGetValue(baseType, out List<MethodDesc> baseVtable))
                                    vtables.Add(baseType, baseVtable = BuildVTable(factory, baseType, baseType, new List<MethodDesc>()));

                                if (!vtables.TryGetValue(type, out List<MethodDesc> vtable))
                                    vtables.Add(type, vtable = BuildVTable(factory, type, type, new List<MethodDesc>()));

                                for (int i = 0; i < baseVtable.Count; i++)
                                {
                                    if (baseVtable[i] != vtable[i])
                                        _overriddenMethods.Add(baseVtable[i].GetCanonMethodTarget(CanonicalFormKind.Specific));
                                }
                            }

                            _canHaveDynamicInterfaceImplementations |= type.IsIDynamicInterfaceCastable;
                        }
                    }
                }

                if (_canHaveDynamicInterfaceImplementations)
                {
                    _disqualifiedTypes.UnionWith(dynamicInterfaceCastableImplementationTargets);
                }
            }

            private static bool CanAssumeWholeProgramViewOnTypeUse(NodeFactory factory, TypeDesc implementingType, DefType baseType)
            {
                if (!baseType.HasInstantiation)
                {
                    return true;
                }

                // If there are variance considerations, bail
                if (baseType.IsInterface
                    && VariantInterfaceMethodUseNode.IsVariantInterfaceImplementation(factory, implementingType, baseType))
                {
                    return false;
                }

                if (baseType.IsCanonicalSubtype(CanonicalFormKind.Any)
                    || baseType.ConvertToCanonForm(CanonicalFormKind.Specific) != baseType)
                {
                    // If the interface has a canonical form, we might not have a full view of all implementers.
                    // E.g. if we have:
                    // class Fooer<T> : IFooable<T> { }
                    // class Doer<T> : IFooable<T> { }
                    // And we instantiated Fooer<string>, but not Doer<string>. But we do have code for Doer<__Canon>.
                    // We might think we can devirtualize IFooable<string> to Fooer<string>, but someone could
                    // typeof(Doer<>).MakeGenericType(typeof(string)) and break our whole program view.
                    // This is only a problem if canonical form of the interface exists.
                    return false;
                }

                return true;
            }

            private void RecordImplementation(TypeDesc type, TypeDesc implType)
            {
                Debug.Assert(!implType.IsInterface);

                HashSet<TypeDesc> implList;
                if (!_implementators.TryGetValue(type, out implList))
                {
                    implList = new();
                    _implementators[type] = implList;
                }
                implList.Add(implType);
            }

            public override bool IsEffectivelySealed(TypeDesc type)
            {
                // If we know we scanned a type that derives from this one, this for sure can't be reported as sealed.
                TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
                if (_unsealedTypes.Contains(canonType))
                    return false;

                // Don't report __Canon as sealed or it can cause trouble
                // (E.g. RyuJIT might think it's okay to omit array element type checks for __Canon[].)
                if (type.IsCanonicalDefinitionType(CanonicalFormKind.Any))
                    return false;

                if (type is MetadataType metadataType)
                {
                    // Due to how the compiler is structured, we might see "constructed" EETypes for things
                    // that never got allocated (doing a typeof() on a class that is otherwise never used is
                    // a good example of when that happens). This can put us into a position where we could
                    // report `sealed` on an `abstract` class, but that doesn't lead to anything good.
                    return !metadataType.IsAbstract;
                }

                // Everything else can be considered sealed.
                return true;
            }

            public override bool IsEffectivelySealed(MethodDesc method)
            {
                // First try to answer using metadata
                if (method.IsFinal || method.OwningType.IsSealed())
                    return true;

                // Now let's see if we can seal through whole program view

                // Sealing abstract methods or methods on interface can't lead to anything good
                if (method.IsAbstract || method.OwningType.IsInterface)
                    return false;

                // If we want to make something final, we better have a method body for it.
                // Sometimes we might have optimized it away so don't let codegen make direct calls.
                // NOTE: this check naturally also rejects generic virtual methods since we don't track them.
                MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
                if (!_generatedVirtualMethods.Contains(canonMethod))
                    return false;

                // If we haven't seen any other method override this, this method is sealed
                return !_overriddenMethods.Contains(canonMethod);
            }

            protected override MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail)
            {
                // If we would resolve into a type that wasn't seen as allocated, don't allow devirtualization.
                // It would go past what we scanned in the scanner and that doesn't lead to good things.
                if (!_canonConstructedTypes.Contains(implType.ConvertToCanonForm(CanonicalFormKind.Specific)))
                {
                    // FAILED_BUBBLE_IMPL_NOT_REFERENCEABLE is close enough...
                    devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_BUBBLE_IMPL_NOT_REFERENCEABLE;
                    return null;
                }

                return base.ResolveVirtualMethod(declMethod, implType, out devirtualizationDetail);
            }

            public override bool CanReferenceConstructedMethodTable(TypeDesc type)
            {
                Debug.Assert(type.NormalizeInstantiation() == type);
                Debug.Assert(ConstructedEETypeNode.CreationAllowed(type));
                return _constructedMethodTables.Contains(type);
            }

            public override bool CanReferenceMetadataMethodTable(TypeDesc type)
            {
                Debug.Assert(type.NormalizeInstantiation() == type);
                Debug.Assert(ConstructedEETypeNode.CreationAllowed(type));
                return _metadataMethodTables.Contains(type);
            }

            public override bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type)
            {
                Debug.Assert(type.NormalizeInstantiation() == type);
                Debug.Assert(ConstructedEETypeNode.CreationAllowed(type));
                return _constructedMethodTables.Contains(type) || _canonConstructedMethodTables.Contains(type);
            }

            public override bool IsGenericDefinitionMethodTableReflectionVisible(TypeDesc type)
            {
                Debug.Assert(type.IsGenericDefinition);
                return _reflectionVisibleGenericDefinitionMethodTables.Contains(type);
            }

            public override TypeDesc[] GetImplementingClasses(TypeDesc type)
            {
                if (_disqualifiedTypes.Contains(type))
                    return null;

                if (_context.IsArrayVariantCastable(type))
                    return null;

                if (_implementators.TryGetValue(type, out HashSet<TypeDesc> implementations))
                {
                    TypeDesc[] types;
                    int index = 0;
                    if (!type.IsInterface && type is not MetadataType { IsAbstract: true })
                    {
                        types = new TypeDesc[implementations.Count + 1];
                        types[index++] = type;
                    }
                    else
                    {
                        types = new TypeDesc[implementations.Count];
                    }

                    foreach (TypeDesc implementation in implementations)
                    {
                        types[index++] = implementation;
                    }
                    return types;
                }
                return null;
            }

            public override bool CanHaveDynamicInterfaceImplementations(TypeDesc type) => _canHaveDynamicInterfaceImplementations;
        }

        private sealed class ScannedInliningPolicy : IInliningPolicy
        {
            private readonly HashSet<TypeDesc> _constructedTypes = new HashSet<TypeDesc>();
            private readonly CompilationModuleGroup _baseGroup;

            public ScannedInliningPolicy(CompilationModuleGroup baseGroup, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                _baseGroup = baseGroup;

                foreach (var node in markedNodes)
                {
                    if (node is ConstructedEETypeNode eetypeNode)
                    {
                        TypeDesc type = eetypeNode.Type;
                        _constructedTypes.Add(type);

                        // It's convenient to also see Array<T> as constructed for each T[]
                        DefType closestDefType = type.GetClosestDefType();
                        if (closestDefType != type)
                            _constructedTypes.Add(closestDefType);
                    }
                }
            }

            public bool CanInline(MethodDesc caller, MethodDesc callee)
            {
                if (_baseGroup.CanInline(caller, callee))
                {
                    // Since the scanner doesn't look at instance methods whose owning type
                    // wasn't allocated (done through TentativeInstanceMethodNode),
                    // we need to disallow inlining these methods. They could
                    // bring in dependencies that we didn't look at.
                    if (callee.NotCallableWithoutOwningEEType())
                    {
                        return _constructedTypes.Contains(callee.OwningType);
                    }
                    return true;
                }

                return false;
            }
        }

        private sealed class ScannedMethodImportationErrorProvider : MethodImportationErrorProvider
        {
            private readonly Dictionary<MethodDesc, TypeSystemException> _importationErrors = new Dictionary<MethodDesc, TypeSystemException>();

            public ScannedMethodImportationErrorProvider(ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                foreach (var markedNode in markedNodes)
                {
                    if (markedNode is ScannedMethodNode scannedMethod
                        && scannedMethod.Exception != null)
                    {
                        _importationErrors.Add(scannedMethod.Method, scannedMethod.Exception);
                    }
                }
            }

            public override TypeSystemException GetCompilationError(MethodDesc method)
                => _importationErrors.TryGetValue(method, out var exception) ? exception : null;
        }

        private sealed class ScannedInlinedThreadStatics : InlinedThreadStatics
        {
            private readonly List<MetadataType> _types;
            private readonly Dictionary<MetadataType, int> _offsets;
            private readonly int _size;

            internal override bool IsComputed() => true;
            internal override List<MetadataType> GetTypes() => _types;
            internal override Dictionary<MetadataType, int> GetOffsets() => _offsets;
            internal override int GetSize() => _size;

            public ScannedInlinedThreadStatics(NodeFactory factory, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                List<ThreadStaticsNode> threadStaticNodes = new List<ThreadStaticsNode>();
                foreach (var markedNode in markedNodes)
                {
                    if (markedNode is ThreadStaticsNode threadStaticNode)
                    {
                        threadStaticNodes.Add(threadStaticNode);
                    }
                }

                // skip MT pointer
                int nextDataOffset = factory.Target.PointerSize;

                List<MetadataType> types = new List<MetadataType>();
                Dictionary<MetadataType, int> offsets = new Dictionary<MetadataType, int>();

                if (threadStaticNodes.Count > 0)
                {
                    threadStaticNodes.Sort(CompilerComparer.Instance);
                    for (int i = 0; i < threadStaticNodes.Count; i++)
                    {
                        ThreadStaticsNode threadStaticNode = threadStaticNodes[i];
                        MetadataType t = threadStaticNode.Type;

                        // do not inline storage for shared generics
                        if (t.ConvertToCanonForm(CanonicalFormKind.Specific) != t)
                            continue;

                        types.Add(t);

                        // N.B. for ARM32, we would need to deal with > PointerSize alignments. We
                        // currently don't support inlined thread statics on ARM32, regular GCStaticEEType
                        // handles this with RequiresAlign8Flag
                        Debug.Assert(t.ThreadGcStaticFieldAlignment.AsInt <= factory.Target.PointerSize);
                        nextDataOffset = nextDataOffset.AlignUp(t.ThreadGcStaticFieldAlignment.AsInt);

                        // reported offset is from the MT pointer, adjust for that
                        offsets.Add(t, nextDataOffset - factory.Target.PointerSize);

                        // ThreadGcStaticFieldSize includes MT pointer, we will not need space for it
                        int dataSize = t.ThreadGcStaticFieldSize.AsInt - factory.Target.PointerSize;
                        nextDataOffset += dataSize;
                    }
                }

                _types = types;
                _offsets = offsets;
                // the size is at least MIN_OBJECT_SIZE
                _size = Math.Max(nextDataOffset, factory.Target.PointerSize * 3);
            }
        }

        private sealed class ScannedPreinitializationPolicy : TypePreinit.TypePreinitializationPolicy
        {
            private readonly HashSet<TypeDesc> _canonFormsWithCctorChecks = new HashSet<TypeDesc>();

            public ScannedPreinitializationPolicy(PreinitializationManager preinitManager, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                foreach (var markedNode in markedNodes)
                {
                    // If there's a type loader template for a type, we can create new instances
                    // at runtime that will not be preinitialized.
                    // This makes sure accessing static bases of template-constructed types
                    // goes through a cctor check.
                    if (markedNode is NativeLayoutTemplateTypeLayoutVertexNode typeTemplate)
                    {
                        _canonFormsWithCctorChecks.Add(typeTemplate.CanonType);
                    }

                    // If there's a type for which we have a canonical form that requires
                    // a cctor check, make sure accessing the static base from a shared generic context
                    // will trigger the cctor.
                    // This makes sure that "static object Read<T>() => SomeType<T>.StaticField" will do
                    // a cctor check if any of the canonically-equivalent SomeType instantiations required
                    // a cctor check.
                    if (markedNode is NonGCStaticsNode nonGCStatics
                        && nonGCStatics.Type.ConvertToCanonForm(CanonicalFormKind.Specific) != nonGCStatics.Type
                        && nonGCStatics.HasLazyStaticConstructor)
                    {
                        _canonFormsWithCctorChecks.Add(nonGCStatics.Type.ConvertToCanonForm(CanonicalFormKind.Specific));
                    }

                    // Also look at EETypes to cover the cases when the non-GC static base wasn't generated.
                    // This makes assert around CanPreinitializeAllConcreteFormsForCanonForm happy.
                    if (markedNode is EETypeNode eeType
                        && eeType.Type.ConvertToCanonForm(CanonicalFormKind.Specific) != eeType.Type
                        && preinitManager.HasLazyStaticConstructor(eeType.Type))
                    {
                        _canonFormsWithCctorChecks.Add(eeType.Type.ConvertToCanonForm(CanonicalFormKind.Specific));
                    }
                }
            }

            public override bool CanPreinitialize(DefType type) => true;

            public override bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type)
            {
                // The form we're asking about should be canonical, but may not be normalized
                Debug.Assert(type.IsCanonicalSubtype(CanonicalFormKind.Any));
                return !_canonFormsWithCctorChecks.Contains(type.NormalizeInstantiation());
            }
        }

        private sealed class ScannedReadOnlyPolicy : ReadOnlyFieldPolicy
        {
            private HashSet<FieldDesc> _writtenFields = new();

            public ScannedReadOnlyPolicy(ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
            {
                foreach (var node in markedNodes)
                {
                    if (node is NotReadOnlyFieldNode writtenField)
                    {
                        _writtenFields.Add(writtenField.Field);
                    }
                }
            }

            public override bool IsReadOnly(FieldDesc field)
            {
                FieldDesc typicalField = field.GetTypicalFieldDefinition();
                if (field != typicalField)
                {
                    DefType owningType = field.OwningType;
                    var canonOwningType = (InstantiatedType)owningType.ConvertToCanonForm(CanonicalFormKind.Specific);
                    if (owningType != canonOwningType)
                        field = field.Context.GetFieldForInstantiatedType(typicalField, canonOwningType);
                }

                return !_writtenFields.Contains(field);
            }
        }

        private sealed class ScannedTypeMapManager : TypeMapManager
        {
            private ImmutableArray<IExternalTypeMapNode> _externalTypeMapNodes;
            private ImmutableArray<IProxyTypeMapNode> _proxyTypeMapNodes;

            public ScannedTypeMapManager(NodeFactory factory)
            {
                ImmutableArray<IExternalTypeMapNode>.Builder externalTypeMapNodes = ImmutableArray.CreateBuilder<IExternalTypeMapNode>();
                ImmutableArray<IProxyTypeMapNode>.Builder proxyTypeMapNodes = ImmutableArray.CreateBuilder<IProxyTypeMapNode>();
                foreach (var externalTypeMapNode in factory.TypeMapManager.GetExternalTypeMaps())
                {
                    externalTypeMapNodes.Add(externalTypeMapNode.ToAnalysisBasedNode(factory));
                }

                foreach (var proxyTypeMapNode in factory.TypeMapManager.GetProxyTypeMaps())
                {
                    proxyTypeMapNodes.Add(proxyTypeMapNode.ToAnalysisBasedNode(factory));
                }

                _externalTypeMapNodes = externalTypeMapNodes.ToImmutable();
                _proxyTypeMapNodes = proxyTypeMapNodes.ToImmutable();
            }

            protected override bool IsEmpty => _externalTypeMapNodes.Length == 0 && _proxyTypeMapNodes.Length == 0;

            public override void AddCompilationRoots(IRootingServiceProvider rootProvider)
            {
                const string reason = "Used Type Map Group";

                foreach (IExternalTypeMapNode externalTypeMap in _externalTypeMapNodes)
                {
                    rootProvider.AddCompilationRoot(externalTypeMap, reason);
                }

                foreach (IProxyTypeMapNode proxyTypeMap in _proxyTypeMapNodes)
                {
                    rootProvider.AddCompilationRoot(proxyTypeMap, reason);
                }
            }

            internal override IEnumerable<IExternalTypeMapNode> GetExternalTypeMaps() => _externalTypeMapNodes;
            internal override IEnumerable<IProxyTypeMapNode> GetProxyTypeMaps() => _proxyTypeMapNodes;
        }
    }
}