File: Compiler\ReadyToRunCodegenCompilation.cs
Web Access
Project: ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

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

using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysis.ReadyToRun;
using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.ReadyToRun;
using ILCompiler.Reflection.ReadyToRun;
using Internal.TypeSystem.Ecma;
using ILCompiler.ReadyToRun.TypeSystem;

namespace ILCompiler
{
    public abstract class Compilation : ICompilation, IDisposable
    {
        protected readonly DependencyAnalyzerBase<NodeFactory> _dependencyGraph;
        protected readonly NodeFactory _nodeFactory;
        protected readonly Logger _logger;
        private readonly DevirtualizationManager _devirtualizationManager;
        protected ILCache _methodILCache;
        private readonly HashSet<ModuleDesc> _modulesBeingInstrumented;


        public NameMangler NameMangler => _nodeFactory.NameMangler;
        public NodeFactory NodeFactory => _nodeFactory;
        public CompilerTypeSystemContext TypeSystemContext => NodeFactory.TypeSystemContext;
        public Logger Logger => _logger;

        public InstructionSetSupport InstructionSetSupport { get; }

        protected Compilation(
            DependencyAnalyzerBase<NodeFactory> dependencyGraph,
            NodeFactory nodeFactory,
            IEnumerable<ICompilationRootProvider> compilationRoots,
            ILProvider ilProvider,
            DevirtualizationManager devirtualizationManager,
            IEnumerable<ModuleDesc> modulesBeingInstrumented,
            Logger logger,
            InstructionSetSupport instructionSetSupport)
        {
            InstructionSetSupport = instructionSetSupport;
            _dependencyGraph = dependencyGraph;
            _nodeFactory = nodeFactory;
            _logger = logger;
            _devirtualizationManager = devirtualizationManager;
            _modulesBeingInstrumented = new HashSet<ModuleDesc>(modulesBeingInstrumented);

            _dependencyGraph.ComputeDependencyRoutine += ComputeDependencyNodeDependencies;
            NodeFactory.AttachToDependencyGraph(_dependencyGraph, ilProvider);


            var rootingService = new RootingServiceProvider(nodeFactory, _dependencyGraph.AddRoot);
            foreach (var rootProvider in compilationRoots)
                rootProvider.AddCompilationRoots(rootingService);

            _methodILCache = new ILCache((ReadyToRunILProvider)ilProvider, NodeFactory.CompilationModuleGroup);
        }

        public abstract void Dispose();
        public abstract void Compile(string outputFileName);
        public abstract void WriteDependencyLog(string outputFileName);

        protected abstract void ComputeDependencyNodeDependencies(List<DependencyNodeCore<NodeFactory>> obj);

        public bool CanInline(MethodDesc caller, MethodDesc callee)
        {
            if (JitConfigProvider.Instance.HasFlag(CorJitFlag.CORJIT_FLAG_DEBUG_CODE))
            {
                // If the callee wants debuggable code, don't allow it to be inlined
                return false;
            }

            if (callee.IsNoInlining || callee.IsNoOptimization)
            {
                // NoOptimization implies NoInlining
                return false;
            }

            // Check to see if the method requires a security object.  This means they call demand and
            // shouldn't be inlined.
            if (callee.RequireSecObject)
            {
                return false;
            }

            // If the method is MethodImpl'd by another method within the same type, then we have
            // an issue that the importer will import the wrong body. In this case, we'll just
            // disallow inlining because getFunctionEntryPoint will do the right thing.
            if (callee.IsVirtual)
            {
                MethodDesc calleeMethodImpl = callee.OwningType.FindVirtualFunctionTargetMethodOnObjectType(callee);
                if (calleeMethodImpl != callee)
                {
                    return false;
                }
            }

            _nodeFactory.DetectGenericCycles(caller, callee);

            return NodeFactory.CompilationModuleGroup.CanInline(caller, callee);
        }

        public virtual MethodIL GetMethodIL(MethodDesc method)
        {
            return _methodILCache.GetOrCreateValue(method).MethodIL;
        }

        public bool IsEffectivelySealed(TypeDesc type)
        {
            return _devirtualizationManager.IsEffectivelySealed(type);
        }

        public bool IsEffectivelySealed(MethodDesc method)
        {
            return _devirtualizationManager.IsEffectivelySealed(method);
        }

        public MethodDesc ResolveVirtualMethod(MethodDesc declMethod, TypeDesc implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail)
        {
            if (declMethod.OwningType.IsInterface)
            {
                // The virtual method resolution algorithm in the managed type system is not implemented to work correctly
                // in the presence of calling type equivalent interfaces.
                // Notably:
                // If the decl is to a interface equivalent to, but not equal to any interface implemented on the
                // owning type, then the logic for matching up methods by method index is not present.
                // AND
                // If the owningType implements multiple different type equivalent interfaces that are all mutually
                // equivalent, the implementation for finding the correct implementation method requires walking the
                // type hierarchy and searching for exact and equivalent matches at each level (much like variance)
                // This logic is also currently unimplemented.
                // NOTE: We do not currently have tests in the runtime suite which cover these cases
                if (declMethod.OwningType.HasTypeEquivalence)
                {
                    // To protect against this, require that the implType implement exactly the right interface, and
                    // no additional interfaces that are equivalent
                    bool foundExactMatch = false;
                    bool foundEquivalentMatch = false;
                    foreach (var @interface in implType.RuntimeInterfaces)
                    {
                        if (@interface == declMethod.OwningType)
                        {
                            foundExactMatch = true;
                            continue;
                        }
                        if (@interface.IsEquivalentTo(declMethod.OwningType))
                        {
                            foundEquivalentMatch = true;
                        }
                    }

                    if (!foundExactMatch || foundEquivalentMatch)
                    {
                        devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_TYPE_EQUIVALENCE;
                        return null;
                    }
                }
            }

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

        public bool IsModuleInstrumented(ModuleDesc module)
        {
            return _modulesBeingInstrumented.Contains(module);
        }

        public sealed class ILCache : LockFreeReaderHashtable<MethodDesc, ILCache.MethodILData>
        {
            public ReadyToRunILProvider ILProvider { get; }
            public int ExpectedILProviderVersion { get; }
            private readonly CompilationModuleGroup _compilationModuleGroup;

            public ILCache(ReadyToRunILProvider provider, CompilationModuleGroup compilationModuleGroup)
            {
                ILProvider = provider;
                ExpectedILProviderVersion = provider.Version;
                _compilationModuleGroup = compilationModuleGroup;
            }

            protected override int GetKeyHashCode(MethodDesc key)
            {
                return key.GetHashCode();
            }
            protected override int GetValueHashCode(MethodILData value)
            {
                return value.Method.GetHashCode();
            }
            protected override bool CompareKeyToValue(MethodDesc key, MethodILData value)
            {
                return Object.ReferenceEquals(key, value.Method);
            }
            protected override bool CompareValueToValue(MethodILData value1, MethodILData value2)
            {
                return Object.ReferenceEquals(value1.Method, value2.Method);
            }
            protected override MethodILData CreateValueFromKey(MethodDesc key)
            {
                MethodIL methodIL = ILProvider.GetMethodIL(key);
                if (methodIL == null
                    && key.IsPInvoke
                    && _compilationModuleGroup.GeneratesPInvoke(key))
                {
                    methodIL = PInvokeILEmitter.EmitIL(key);
                }

                return new MethodILData() { Method = key, MethodIL = methodIL };
            }

            public class MethodILData
            {
                public MethodDesc Method;
                public MethodIL MethodIL;
            }
        }

        private delegate void RootAdder(object o, string reason);

        private class RootingServiceProvider : IRootingServiceProvider
        {
            private readonly NodeFactory _factory;
            private readonly RootAdder _rootAdder;
            private readonly DeferredTillPhaseNode _deferredPhaseNode = new DeferredTillPhaseNode(1);

            public RootingServiceProvider(NodeFactory factory, RootAdder rootAdder)
            {
                _factory = factory;
                _rootAdder = rootAdder;
                _rootAdder(_deferredPhaseNode, "Deferred nodes");
            }

            private void AddCompilationRootHelper(DependencyNodeCore<NodeFactory> node, bool rootMinimalDependencies, string reason)
            {
                if (rootMinimalDependencies)
                {
                    _deferredPhaseNode.AddDependency(node);
                }
                else
                {
                    _rootAdder(node, reason);
                }
            }

            public void AddCompilationRoot(MethodDesc method, bool rootMinimalDependencies, string reason)
            {
                MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
                if (_factory.CompilationModuleGroup.ContainsMethodBody(canonMethod, false))
                {
                    MethodWithGCInfo methodEntryPoint = _factory.CompiledMethodNode(canonMethod);
                    AddCompilationRootHelper(methodEntryPoint, rootMinimalDependencies, reason);

                    // Process unbox stubs inclusion for methods that have all type args Canon. InheritedVirtualMethodsNode
                    // and GVMDependenciesNode are meant to deal with methods that have some of the type args instantiated
                    // with valuetypes.
                    if (_factory.NeedsUnboxingStub(canonMethod))
                    {
                        DependencyNodeCore<NodeFactory> unboxingStub = _factory.UnboxingStub(canonMethod);
                        AddCompilationRootHelper(unboxingStub, rootMinimalDependencies, reason);
                    }
                }
            }
        }
    }

    public interface ICompilation
    {
        void Compile(string outputFileName);
        void WriteDependencyLog(string outputFileName);
        void Dispose();
    }

    public sealed class ReadyToRunCodegenCompilation : Compilation
    {
        /// <summary>
        /// Input MSIL file names.
        /// </summary>
        private readonly IEnumerable<string> _inputFiles;

        private readonly string _compositeRootPath;

        private readonly bool _resilient;

        private readonly int _parallelism;

        private readonly bool _generateMapFile;
        private readonly bool _generateMapCsvFile;
        private readonly bool _generatePdbFile;
        private readonly string _pdbPath;
        private readonly bool _generatePerfMapFile;
        private readonly string _perfMapPath;
        private readonly int _perfMapFormatVersion;
        private readonly bool _generateProfileFile;
        private readonly Func<MethodDesc, string> _printReproInstructions;

        private readonly ProfileDataManager _profileData;
        private readonly FileLayoutOptimizer _fileLayoutOptimizer;
        private readonly HashSet<MethodDesc> _methodsWhichNeedMutableILBodies = new HashSet<MethodDesc>();
        private readonly HashSet<MethodWithGCInfo> _methodsToRecompile = new HashSet<MethodWithGCInfo>();

        public ProfileDataManager ProfileData => _profileData;

        public bool DeterminismCheckFailed { get; set; }

        public ReadyToRunSymbolNodeFactory SymbolNodeFactory { get; }
        public ReadyToRunCompilationModuleGroupBase CompilationModuleGroup { get; }
        private readonly int _customPESectionAlignment;
        private readonly ReadyToRunContainerFormat _format;

        /// <summary>
        /// Determining whether a type's layout is fixed is a little expensive and the question can be asked many times
        /// for the same type during compilation so preserve the computed value.
        /// </summary>
        private ConcurrentDictionary<TypeDesc, bool> _computedFixedLayoutTypes = new ConcurrentDictionary<TypeDesc, bool>();
        private Func<TypeDesc, bool> _computedFixedLayoutTypesUncached;

        private readonly ExternalReferenceTokenManager _tokenManager;

        internal ReadyToRunCodegenCompilation(
            DependencyAnalyzerBase<NodeFactory> dependencyGraph,
            NodeFactory nodeFactory,
            IEnumerable<ICompilationRootProvider> roots,
            ILProvider ilProvider,
            Logger logger,
            DevirtualizationManager devirtualizationManager,
            IEnumerable<string> inputFiles,
            string compositeRootPath,
            InstructionSetSupport instructionSetSupport,
            bool resilient,
            bool generateMapFile,
            bool generateMapCsvFile,
            bool generatePdbFile,
            Func<MethodDesc, string> printReproInstructions,
            string pdbPath,
            bool generatePerfMapFile,
            string perfMapPath,
            int perfMapFormatVersion,
            bool generateProfileFile,
            int parallelism,
            ProfileDataManager profileData,
            MethodLayoutAlgorithm methodLayoutAlgorithm,
            FileLayoutAlgorithm fileLayoutAlgorithm,
            int customPESectionAlignment,
            bool verifyTypeAndFieldLayout,
            ReadyToRunContainerFormat format)
            : base(
                  dependencyGraph,
                  nodeFactory,
                  roots,
                  ilProvider,
                  devirtualizationManager,
                  modulesBeingInstrumented: nodeFactory.CompilationModuleGroup.CompilationModuleSet,
                  logger,
                  instructionSetSupport)
        {
            _computedFixedLayoutTypesUncached = IsLayoutFixedInCurrentVersionBubbleInternal;
            _resilient = resilient;
            _parallelism = parallelism;
            _generateMapFile = generateMapFile;
            _generateMapCsvFile = generateMapCsvFile;
            _generatePdbFile = generatePdbFile;
            _pdbPath = pdbPath;
            _generatePerfMapFile = generatePerfMapFile;
            _perfMapPath = perfMapPath;
            _perfMapFormatVersion = perfMapFormatVersion;
            _generateProfileFile = generateProfileFile;
            _customPESectionAlignment = customPESectionAlignment;
            _format = format;
            SymbolNodeFactory = new ReadyToRunSymbolNodeFactory(nodeFactory, verifyTypeAndFieldLayout);
            _tokenManager = new ExternalReferenceTokenManager(_nodeFactory.ManifestMetadataTable._mutableModule, _nodeFactory.Resolver);
            if (nodeFactory.InstrumentationDataTable != null)
                nodeFactory.InstrumentationDataTable.Initialize(SymbolNodeFactory);
            if (nodeFactory.CrossModuleInlningInfo != null)
                nodeFactory.CrossModuleInlningInfo.Initialize(SymbolNodeFactory);
            if (nodeFactory.ImportReferenceProvider != null)
                nodeFactory.ImportReferenceProvider.Initialize(SymbolNodeFactory, _tokenManager);
            _inputFiles = inputFiles;
            _compositeRootPath = compositeRootPath;
            _printReproInstructions = printReproInstructions;
            CompilationModuleGroup = (ReadyToRunCompilationModuleGroupBase)nodeFactory.CompilationModuleGroup;

            // Generate baseline support specification for InstructionSetSupport. This will prevent usage of the generated
            // code if the runtime environment doesn't support the specified instruction set. Targets that cannot generate
            // code at runtime must not encode "must be absent" assertions, since a failing eager fixup is a fatal startup
            // error with no JIT fallback (see ToInstructionSetSupportString).
            bool targetAllowsRuntimeCodeGeneration = ((ReadyToRunCompilerContext)nodeFactory.TypeSystemContext).TargetAllowsRuntimeCodeGeneration;
            string instructionSetSupportString = ReadyToRunInstructionSetSupportSignature.ToInstructionSetSupportString(instructionSetSupport, emitExplicitlyUnsupported: targetAllowsRuntimeCodeGeneration);
            ReadyToRunInstructionSetSupportSignature instructionSetSupportSig = new ReadyToRunInstructionSetSupportSignature(instructionSetSupportString);
            _dependencyGraph.AddRoot(new Import(NodeFactory.EagerImports, instructionSetSupportSig), "Baseline instruction set support");

            _profileData = profileData;

            _fileLayoutOptimizer = new FileLayoutOptimizer(logger, methodLayoutAlgorithm, fileLayoutAlgorithm, profileData, _nodeFactory);
        }

        private readonly static string s_folderUpPrefix = ".." + Path.DirectorySeparatorChar;

        public override void Compile(string outputFile)
        {
            _dependencyGraph.ComputeMarkedNodes();

            // Release single-threaded JIT state before object emission to reduce peak memory usage.
            _singleThreadedWorkerState = default;

            var nodes = _dependencyGraph.MarkedNodeList;

            nodes = _fileLayoutOptimizer.ApplyProfilerGuidedMethodSort(nodes);

            using (PerfEventSource.StartStopEvents.EmittingEvents())
            {
                NodeFactory.SetMarkingComplete();
                ReadyToRunObjectWriter.EmitObject(
                    outputFile,
                    componentModule: null,
                    inputFiles: _inputFiles,
                    nodes,
                    NodeFactory,
                    generateMapFile: _generateMapFile,
                    generateMapCsvFile: _generateMapCsvFile,
                    generatePdbFile: _generatePdbFile,
                    pdbPath: _pdbPath,
                    generatePerfMapFile: _generatePerfMapFile,
                    perfMapPath: _perfMapPath,
                    perfMapFormatVersion: _perfMapFormatVersion,
                    generateProfileFile: _generateProfileFile,
                    callChainProfile: _profileData.CallChainProfile,
                    _format,
                    _customPESectionAlignment,
                    _logger);
                CompilationModuleGroup moduleGroup = _nodeFactory.CompilationModuleGroup;

                if (moduleGroup.IsCompositeBuildMode)
                {
                    // In composite mode with standalone MSIL we rewrite all input MSIL assemblies to the
                    // output folder, adding a formal R2R header to them with forwarding information to
                    // the composite executable.
                    string outputDirectory = Path.GetDirectoryName(outputFile);
                    string ownerExecutableName = Path.GetFileName(outputFile);

                    if (_format == ReadyToRunContainerFormat.MachO)
                    {
                        // MachO composite images have the owner executable name stored with the dylib extension
                        ownerExecutableName = Path.ChangeExtension(ownerExecutableName, ".dylib");
                    }

                    HashSet<MethodDesc> compiledMethodDefs = null;
                    if (_nodeFactory.OptimizationFlags.StripILBodies)
                    {
                        compiledMethodDefs = _nodeFactory.BuildCompiledMethodDefsSet();
                    }

                    foreach (string inputFile in _inputFiles)
                    {
                        string relativeMsilPath = Path.GetRelativePath(_compositeRootPath, inputFile);
                        if (relativeMsilPath == inputFile || relativeMsilPath.StartsWith(s_folderUpPrefix, StringComparison.Ordinal))
                        {
                            // Input file not under the composite root, emit to root output folder
                            relativeMsilPath = Path.GetFileName(inputFile);
                        }
                        string standaloneMsilOutputFile = Path.Combine(outputDirectory, relativeMsilPath);
                        if (_format == ReadyToRunContainerFormat.Wasm)
                        {
                            // For wasm, component stubs are webcil-in-wasm modules loaded by name
                            // as "<assembly>.wasm" (matching the browser/wasi external-assembly probe).
                            standaloneMsilOutputFile = Path.ChangeExtension(standaloneMsilOutputFile, ".wasm");
                        }
                        RewriteComponentFile(inputFile: inputFile, outputFile: standaloneMsilOutputFile, ownerExecutableName: ownerExecutableName, compiledMethodDefs: compiledMethodDefs);
                    }
                }
            }
        }

        private void RewriteComponentFile(string inputFile, string outputFile, string ownerExecutableName, HashSet<MethodDesc> compiledMethodDefs)
        {
            EcmaModule inputModule = NodeFactory.TypeSystemContext.GetModuleFromPath(inputFile);

            Directory.CreateDirectory(Path.GetDirectoryName(outputFile));

            ReadyToRunFlags flags =
                ReadyToRunFlags.READYTORUN_FLAG_Component |
                ReadyToRunFlags.READYTORUN_FLAG_NonSharedPInvokeStubs;

            if (inputModule.IsPlatformNeutral || inputModule.PEReader.IsReadyToRunPlatformNeutralSource())
            {
                flags |= ReadyToRunFlags.READYTORUN_FLAG_PlatformNeutralSource;
            }
            bool automaticTypeValidation = _nodeFactory.OptimizationFlags.TypeValidation == TypeValidationRule.Automatic || _nodeFactory.OptimizationFlags.TypeValidation == TypeValidationRule.AutomaticWithLogging;
            if (_nodeFactory.OptimizationFlags.TypeValidation == TypeValidationRule.SkipTypeValidation)
            {
                flags |= ReadyToRunFlags.READYTORUN_FLAG_SkipTypeValidation;
            }

            NodeFactoryOptimizationFlags optimizationFlags = _nodeFactory.OptimizationFlags with { IsComponentModule = true, CompiledMethodDefs = compiledMethodDefs };

            if (optimizationFlags.StripILBodies)
            {
                flags |= ReadyToRunFlags.READYTORUN_FLAG_StrippedILBodies;
            }

            if (optimizationFlags.StripInliningInfo)
            {
                flags |= ReadyToRunFlags.READYTORUN_FLAG_StrippedInliningInfo;
            }

            if (optimizationFlags.StripDebugInfo)
            {
                flags |= ReadyToRunFlags.READYTORUN_FLAG_StrippedDebugInfo;
            }

            flags |= _nodeFactory.CompilationModuleGroup.GetReadyToRunFlags() & ReadyToRunFlags.READYTORUN_FLAG_MultiModuleVersionBubble;

            bool isNativeCompositeImage = false;
            if (NodeFactory.Target.IsWindows && NodeFactory.Format == ReadyToRunContainerFormat.PE)
            {
                isNativeCompositeImage = true;
            }
            else if (NodeFactory.Target.IsApplePlatform && NodeFactory.Format == ReadyToRunContainerFormat.MachO)
            {
                isNativeCompositeImage = true;
            }

            if (isNativeCompositeImage)
            {
                flags |= ReadyToRunFlags.READYTORUN_FLAG_PlatformNativeImage;
            }

            // Component (per-assembly forwarding) stubs are emitted as PE (even when the composite image is native),
            // except on wasm where we emit webcil-in-wasm stubs to match the browser/wasi loading model.
            // The PE/COFF writer does not support the Wasm32 architecture.
            ReadyToRunContainerFormat componentFormat =
                _format == ReadyToRunContainerFormat.Wasm ? ReadyToRunContainerFormat.Wasm : ReadyToRunContainerFormat.PE;
            CopiedCorHeaderNode copiedCorHeader = new CopiedCorHeaderNode(inputModule);
            // Re-written components shouldn't have any additional diagnostic information - only information about the forwards.
            // Even with all of this, we might be modifying the image in a silly manner - adding a directory when if didn't have one.
            DebugDirectoryNode debugDirectory = new DebugDirectoryNode(inputModule, outputFile, shouldAddNiPdb: false, shouldGeneratePerfmap: false, perfMapFormatVersion: 0);
            NodeFactory componentFactory = new NodeFactory(
                _nodeFactory.TypeSystemContext,
                _nodeFactory.CompilationModuleGroup,
                null,
                _nodeFactory.NameMangler,
                copiedCorHeader,
                debugDirectory,
                win32Resources: new Win32Resources.ResourceData(inputModule),
                flags: flags,
                nodeFactoryOptimizationFlags: optimizationFlags,
                format: componentFormat,
                imageBase: _nodeFactory.ImageBase,
                associatedModule: automaticTypeValidation ? inputModule : null,
                genericCycleDepthCutoff: -1, // We don't need generic cycle detection when rewriting component assemblies
                genericCycleBreadthCutoff: -1); // as we're not actually compiling anything

            IComparer<DependencyNodeCore<NodeFactory>> comparer = new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance);
            DependencyAnalyzerBase<NodeFactory> componentGraph = new DependencyAnalyzer<NoLogStrategy<NodeFactory>, NodeFactory>(componentFactory, comparer);

            componentGraph.AddRoot(componentFactory.Header, "Component module R2R header");
            OwnerCompositeExecutableNode ownerExecutableNode = new OwnerCompositeExecutableNode(ownerExecutableName);
            componentGraph.AddRoot(ownerExecutableNode, "Owner composite executable name");
            componentGraph.AddRoot(copiedCorHeader, "Copied COR header");
            componentGraph.AddRoot(debugDirectory, "Debug directory");
            if (componentFactory.Win32ResourcesNode != null)
            {
                componentGraph.AddRoot(componentFactory.Win32ResourcesNode, "Win32 resources");
            }
            componentGraph.ComputeMarkedNodes();
            componentFactory.Header.Add(Internal.Runtime.ReadyToRunSectionType.OwnerCompositeExecutable, ownerExecutableNode);
            componentFactory.SetMarkingComplete();
            ReadyToRunObjectWriter.EmitObject(
                outputFile,
                componentModule: inputModule,
                inputFiles: new string[] { inputFile },
                componentGraph.MarkedNodeList,
                componentFactory,
                generateMapFile: false,
                generateMapCsvFile: false,
                generatePdbFile: false,
                pdbPath: null,
                generatePerfMapFile: false,
                perfMapPath: null,
                perfMapFormatVersion: _perfMapFormatVersion,
                generateProfileFile: false,
                _profileData.CallChainProfile,
                componentFormat,
                customPESectionAlignment: 0,
                _logger);
        }

        public override void WriteDependencyLog(string outputFileName)
        {
            using (FileStream dgmlOutput = new FileStream(outputFileName, FileMode.Create))
            {
                DgmlWriter.WriteDependencyGraphToStream(dgmlOutput, _dependencyGraph, _nodeFactory);
                dgmlOutput.Flush();
            }
        }

        private bool IsLayoutFixedInCurrentVersionBubbleInternal(TypeDesc type)
        {
            // Primitive types and enums have fixed layout
            if (type.IsPrimitive || type.IsEnum)
            {
                return true;
            }

            if (type is not MetadataType defType)
            {
                // Non metadata backed types have layout defined in all version bubbles
                return true;
            }

            if (VectorOfTFieldLayoutAlgorithm.IsVectorOfTType(defType))
            {
                // Vector<T> always needs a layout check
                return false;
            }

            if (!NodeFactory.CompilationModuleGroup.VersionsWithModule(defType.Module))
            {
                // Valuetypes with non-versionable attribute are candidates for fixed layout. Reject the rest.
                return type is MetadataType metadataType && metadataType.IsNonVersionable();
            }

            // If the above condition passed, check that all instance fields have fixed layout as well. In particular,
            // it is important for generic types with non-versionable layout (e.g. Nullable<T>)
            foreach (var field in type.GetFields())
            {
                // Only instance fields matter here
                if (field.IsStatic)
                    continue;

                var fieldType = field.FieldType;
                if (!fieldType.IsValueType)
                    continue;

                if (!IsLayoutFixedInCurrentVersionBubble(fieldType))
                {
                    return false;
                }
            }

            return true;
        }

        public bool IsLayoutFixedInCurrentVersionBubble(TypeDesc type) =>
            _computedFixedLayoutTypes.GetOrAdd(type, _computedFixedLayoutTypesUncached);

        public bool IsInheritanceChainLayoutFixedInCurrentVersionBubble(TypeDesc type)
        {
            // This method is not expected to be called for value types
            Debug.Assert(!type.IsValueType);

            if (type.IsObject)
                return true;

            if (!IsLayoutFixedInCurrentVersionBubble(type))
            {
                return false;
            }

            type = type.BaseType;

            if (type != null)
            {
                // If there are multiple inexact compilation units in the layout of the type, then the exact offset
                // of a derived given field is unknown as there may or may not be alignment inserted between a type and its base
                if (CompilationModuleGroup.TypeLayoutCompilationUnits(type).HasMultipleInexactCompilationUnits)
                    return false;

                while (!type.IsObject)
                {
                    if (!IsLayoutFixedInCurrentVersionBubble(type))
                    {
                        return false;
                    }
                    type = type.BaseType;
                    Debug.Assert(type != null);
                }
            }

            return true;
        }

        // Compilation is broken into phases which interact with dependency analysis
        // Phase 0: All compilations which are driven by our standard heuristics and dependency expansion model
        // Phase 1: A helper phase which works in tandem with the DeferredTillPhaseNode to gather work to be done in phase 2
        // Phase 2: A phase where all compilations are not allowed to add dependencies that can trigger further compilations.
        // The _finishedFirstCompilationRunInPhase2 variable works in concert some checking to ensure that we don't violate any of this model
        private bool _finishedFirstCompilationRunInPhase2 = false;

        public void PrepareForCompilationRetry(MethodWithGCInfo methodToBeRecompiled, IEnumerable<MethodDesc> methodsThatNeedILBodies)
        {
            lock (_methodsToRecompile)
            {
                _methodsToRecompile.Add(methodToBeRecompiled);
                if (methodsThatNeedILBodies != null)
                {
                    foreach (var method in methodsThatNeedILBodies)
                    {
                        Debug.Assert(method.IsMethodDefinition);
                        _methodsWhichNeedMutableILBodies.Add(method);
                    }
                }
            }
        }

        private struct WorkerState
        {
            public CorInfoImpl CorInfoImpl;
            public int MethodsCompiled;
        }

        // SuperPMI collection runs crossgen2 with parallelism 1 and requires ObjectToHandle
        // handles to remain stable across compilation waves, so retain this state between calls.
        private WorkerState _singleThreadedWorkerState;
        private int _compilationSessionGeneratedColdCode;
        private bool _hasAddedAsyncReferences = false;

        protected override void ComputeDependencyNodeDependencies(List<DependencyNodeCore<NodeFactory>> obj)
        {
            bool generatedColdCode = false;

            using (PerfEventSource.StartStopEvents.JitEvents())
            {

                // Use only main thread to compile if parallelism is 1. This allows SuperPMI to rely on non-reuse of handles in ObjectToHandle
                if (Logger.IsVerbose)
                    Logger.Writer.WriteLine($"Processing {obj.Count} dependencies");

                // Ensure all methods being compiled have assigned tokens. This matters for code from modules from outside of the version bubble
                // as those tokens are dynamically assigned, and for instantiation which depend on tokens outside of the module
                var ilProvider = (ReadyToRunILProvider)_methodILCache.ILProvider;
                obj.MergeSortAllowDuplicates(new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance));
                foreach (var dependency in obj)
                {
                    if (dependency is MethodWithGCInfo methodCodeNodeNeedingCode)
                    {
                        var method = methodCodeNodeNeedingCode.Method;
                        var typicalDef = method.GetTypicalMethodDefinition();
                        if (typicalDef is EcmaMethod or AsyncMethodVariant or AsyncResumptionStub)
                        {
                            if (ilProvider.NeedsCrossModuleInlineableTokens(typicalDef) &&
                                !_methodsWhichNeedMutableILBodies.Contains(typicalDef) &&
                                CorInfoImpl.IsMethodCompilable(this, method))
                            {
                                _methodsWhichNeedMutableILBodies.Add(typicalDef);
                            }
                        }

                        bool shouldBeCompiled = !CorInfoImpl.ShouldCodeNotBeCompiledIntoFinalImage(InstructionSetSupport, method);
                        if (method.IsAsyncCall() && shouldBeCompiled)
                            AddNecessaryAsyncReferences(method);

                        if ((method.IsCompilerGeneratedILBodyForAsync() || ((CompilerTypeSystemContext)method.Context).IsUnboxingThunk(method)) && shouldBeCompiled)
                            EnsureGeneratedILTokensAreAvailable(method);

                        if (!_nodeFactory.CompilationModuleGroup.VersionsWithMethodBody(method))
                            EnsureInstantiationReferencesArePresentForExternalMethod(method);
                    }
                }

                ProcessMutableMethodBodiesList();
                ResetILCache();
                generatedColdCode |= CompileMethodList(obj);

                while (_methodsToRecompile.Count > 0)
                {
                    ProcessMutableMethodBodiesList();
                    ResetILCache();
                    MethodWithGCInfo[] methodsToRecompile = new MethodWithGCInfo[_methodsToRecompile.Count];
                    _methodsToRecompile.CopyTo(methodsToRecompile);
                    _methodsToRecompile.Clear();
                    Array.Sort(methodsToRecompile, new SortableDependencyNode.ObjectNodeComparer(CompilerComparer.Instance));

                    if (Logger.IsVerbose)
                        Logger.Writer.WriteLine($"Processing {methodsToRecompile.Length} recompiles");

                    generatedColdCode |= CompileMethodList(methodsToRecompile);
                }
            }

            ResetILCache();

            if (_nodeFactory.CompilationCurrentPhase == 2)
            {
                _finishedFirstCompilationRunInPhase2 = true;
            }

            if (generatedColdCode)
            {
                _nodeFactory.GenerateHotColdMap(_dependencyGraph);
            }

            void EnsureGeneratedILTokensAreAvailable(MethodDesc method)
            {
                if (!method.IsCompilerGeneratedILBodyForAsync() && !((CompilerTypeSystemContext)method.Context).IsUnboxingThunk(method))
                    return;
                MethodIL il = _methodILCache.ILProvider.GetMethodIL(method);
                if (il is null)
                    return;
                var bytes = il.GetILBytes();
                // Use ILTokenReplacer to iterate over tokens, not actually replace them
                ILTokenReplacer.Replace(bytes, tok =>
                {
                    switch (il.GetObject(tok))
                    {
                        case TypeSystemEntity tse:
                            _tokenManager.EnsureDefTokensAreAvailable(tse, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true);
                            break;
                        default:
                            // We don't need to worry about string handles
                            break;
                    }
                    return tok;
                });
                // ILTokenReplacer doesn't handle exception regions or local variable types, so handle those separately
                var exceptionRegions = (ILExceptionRegion[])il.GetExceptionRegions();
                for (int i = 0; i < exceptionRegions.Length; i++)
                {
                    var region = exceptionRegions[i];
                    if (region.Kind == ILExceptionRegionKind.Catch)
                    {
                        TypeSystemEntity catchType = (TypeSystemEntity)il.GetObject(region.ClassToken);
                        _tokenManager.EnsureDefTokensAreAvailable(catchType, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true);
                    }
                }
                foreach (var local in il.GetLocals())
                {
                    _tokenManager.EnsureDefTokensAreAvailable(local.Type, ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module, true);
                }
            }

            void ProcessMutableMethodBodiesList()
            {
                MethodDesc[] mutableMethodBodyNeedList = new MethodDesc[_methodsWhichNeedMutableILBodies.Count];
                _methodsWhichNeedMutableILBodies.CopyTo(mutableMethodBodyNeedList);
                _methodsWhichNeedMutableILBodies.Clear();
                TypeSystemComparer comparer = TypeSystemComparer.Instance;
                Comparison<MethodDesc> comparison = (MethodDesc a, MethodDesc b) => comparer.Compare(a, b);
                Array.Sort(mutableMethodBodyNeedList, comparison);
                var ilProvider = (ReadyToRunILProvider)_methodILCache.ILProvider;

                foreach (var method in mutableMethodBodyNeedList)
                    ilProvider.CreateCrossModuleInlineableTokensForILBody(method);
            }

            void ResetILCache()
            {
                if (_methodILCache.Count > 1000 || _methodILCache.ILProvider.Version != _methodILCache.ExpectedILProviderVersion)
                    _methodILCache = new ILCache(_methodILCache.ILProvider, NodeFactory.CompilationModuleGroup);
            }
        }

        private bool CompileMethodList(IReadOnlyList<DependencyNodeCore<NodeFactory>> methodList)
        {
            _compilationSessionGeneratedColdCode = 0;

            NodeFactory.ManifestMetadataTable._mutableModule.DisableNewTokens = true;
            try
            {
                if (_parallelism == 1)
                {
                    foreach (DependencyNodeCore<NodeFactory> dependency in methodList)
                    {
                        CompileOneMethod(dependency, ref _singleThreadedWorkerState);
                    }
                }
                else
                {
                    Parallel.ForEach<DependencyNodeCore<NodeFactory>, WorkerState>(
                        // Method compilation costs vary widely, so avoid buffering work into imbalanced partitions.
                        Partitioner.Create(methodList, EnumerablePartitionerOptions.NoBuffering),
                        new ParallelOptions { MaxDegreeOfParallelism = _parallelism },
                        static () => default,
                        CompileOneMethodInParallel,
                        static _ => { });
                }

                return Volatile.Read(ref _compilationSessionGeneratedColdCode) != 0;
            }
            finally
            {
                NodeFactory.ManifestMetadataTable._mutableModule.DisableNewTokens = false;
            }
        }

        private WorkerState CompileOneMethodInParallel(
            DependencyNodeCore<NodeFactory> dependency,
            ParallelLoopState _,
            WorkerState workerState)
        {
            CompileOneMethod(dependency, ref workerState);
            return workerState;
        }

        private void CompileOneMethod(DependencyNodeCore<NodeFactory> dependency, ref WorkerState workerState)
        {
            MethodWithGCInfo methodCodeNodeNeedingCode = dependency as MethodWithGCInfo;
            if (methodCodeNodeNeedingCode == null)
            {
                if (dependency is DeferredTillPhaseNode deferredPhaseNode)
                {
                    if (Logger.IsVerbose)
                        _logger.Writer.WriteLine($"Moved to phase {_nodeFactory.CompilationCurrentPhase}");
                    deferredPhaseNode.NotifyCurrentPhase(_nodeFactory.CompilationCurrentPhase);
                    return;
                }
            }

            Debug.Assert((_nodeFactory.CompilationCurrentPhase == 0) || ((_nodeFactory.CompilationCurrentPhase == 2) && !_finishedFirstCompilationRunInPhase2));

            MethodDesc method = methodCodeNodeNeedingCode.Method;

            if (Logger.IsVerbose)
            {
                string methodName = method.ToString();
                Logger.Writer.WriteLine("Compiling " + methodName);
            }

            if (_nodeFactory.OptimizationFlags.PrintReproArgs)
            {
                Logger.Writer.WriteLine($"Single method repro args:{GetReproInstructions(method)}");
            }

            try
            {
                using (PerfEventSource.StartStopEvents.JitMethodEvents())
                {
                    workerState.MethodsCompiled++;
                    if (workerState.CorInfoImpl is null ||
                        (_parallelism != 1 && (workerState.MethodsCompiled % 3000) == 0))
                    {
                        // Periodically create a new CorInfoImpl to clear out stale caches. For single-threaded
                        // compilation, reuse one instance so SuperPMI can rely on non-reuse of ObjectToHandle handles.
                        workerState.CorInfoImpl = new CorInfoImpl(this);
                    }

                    CorInfoImpl corInfoImpl = workerState.CorInfoImpl;
                    corInfoImpl.CompileMethod(methodCodeNodeNeedingCode, Logger);
                    if (corInfoImpl.HasColdCode)
                    {
                        Volatile.Write(ref _compilationSessionGeneratedColdCode, 1);
                    }
                }
            }
            catch (TypeSystemException ex)
            {
                // If compilation fails, don't emit code for this method. It will be Jitted at runtime
                if (Logger.IsVerbose)
                    Logger.Writer.WriteLine($"Warning: Method `{method}` was not compiled because: {ex.Message}");
            }
            catch (RequiresRuntimeJitException ex)
            {
                if (Logger.IsVerbose)
                    Logger.Writer.WriteLine($"Info: Method `{method}` was not compiled because `{ex.Message}` requires runtime JIT");
            }
            catch (CodeGenerationFailedException ex) when (_resilient)
            {
                if (Logger.IsVerbose)
                    Logger.Writer.WriteLine($"Warning: Method `{method}` was not compiled because `{ex.Message}` requires runtime JIT");
            }
        }

        private void EnsureInstantiationReferencesArePresentForExternalMethod(MethodDesc method)
        {
            // Validate that the typedef tokens for all of the instantiation parameters of the method
            // have tokens.
            var moduleForNewReferences = ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
            foreach (var type in method.Instantiation)
                _tokenManager.EnsureDefTokensAreAvailable(type, moduleForNewReferences, false);
            foreach (var type in method.OwningType.Instantiation)
                _tokenManager.EnsureDefTokensAreAvailable(type, moduleForNewReferences, false);
        }

        private void AddNecessaryAsyncReferences(MethodDesc method)
        {
            if (_hasAddedAsyncReferences)
                return;

            // Keep in sync with CorInfoImpl.getAsyncInfo()
            DefType continuation = TypeSystemContext.ContinuationType;
            TypeDesc asyncHelpers = TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8);
            TypeDesc[] requiredTypes = [asyncHelpers, continuation];
            FieldDesc[] requiredFields =
            [
                // For CorInfoImpl.getAsyncInfo
                continuation.GetKnownField("Next"u8),
                continuation.GetKnownField("ResumeInfo"u8),
                continuation.GetKnownField("State"u8),
                continuation.GetKnownField("Flags"u8),
            ];
            // The signature types for the TransparentAwait overloads used by
            // CorInfoImpl.getAwaitReturnCall (kept in sync with that method).
            TypeDesc voidType = TypeSystemContext.GetWellKnownType(WellKnownType.Void);
            TypeDesc taskType = TypeSystemContext.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task"u8);
            TypeDesc valueTaskType = TypeSystemContext.SystemModule.GetKnownType("System.Threading.Tasks"u8, "ValueTask"u8);
            MetadataType taskOfTType = TypeSystemContext.SystemModule.GetKnownType("System.Threading.Tasks"u8, "Task`1"u8);
            MetadataType valueTaskOfTType = TypeSystemContext.SystemModule.GetKnownType("System.Threading.Tasks"u8, "ValueTask`1"u8);
            TypeDesc methodVar = TypeSystemContext.GetSignatureVariable(0, method: true);
            MethodDesc[] requiredMethods =
            [
                // For CorInfoImpl.getAsyncInfo
                asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null),
                asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null),
                asyncHelpers.GetKnownMethod("CaptureContexts"u8, null),
                asyncHelpers.GetKnownMethod("RestoreContexts"u8, null),
                asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null),
                asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null),
                asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null),
                asyncHelpers.GetKnownMethod("RestoreInlinedFrameContexts"u8, null),
                asyncHelpers.GetKnownMethod("CaptureInlinedFrameTransitionWithContinuationContext"u8, null),
                asyncHelpers.GetKnownMethod("CaptureInlinedFrameTransitionNoContinuationContext"u8, null),
                asyncHelpers.GetKnownMethod("CaptureInlinedFrameTransitionContinueOnThreadPool"u8, null),

                // R2R Helpers
                asyncHelpers.GetKnownMethod("AllocContinuation"u8, null),
                asyncHelpers.GetKnownMethod("AllocContinuationClass"u8, null),
                asyncHelpers.GetKnownMethod("AllocContinuationMethod"u8, null),

                // For CorInfoImpl.getAwaitReturnCall. The JIT synthesizes calls to these overloads, so they
                // have no IL token in the caller and their manifest tokens must be pre-seeded here.
                asyncHelpers.GetKnownMethod("TransparentAwait"u8, new MethodSignature(MethodSignatureFlags.Static, 0, voidType, [taskType])),
                asyncHelpers.GetKnownMethod("TransparentAwait"u8, new MethodSignature(MethodSignatureFlags.Static, 0, voidType, [valueTaskType])),
                asyncHelpers.GetKnownMethod("TransparentAwait"u8, new MethodSignature(MethodSignatureFlags.Static, 1, methodVar, [taskOfTType.MakeInstantiatedType(methodVar)])),
                asyncHelpers.GetKnownMethod("TransparentAwait"u8, new MethodSignature(MethodSignatureFlags.Static, 1, methodVar, [valueTaskOfTType.MakeInstantiatedType(methodVar)])),

                // For CorInfoImpl.getAwaitAwaiterInContinuationCall. Same as above: the JIT rewrites calls
                // to AsyncHelpers.AwaitAwaiter/UnsafeAwaitAwaiter into calls to these, so nothing in the
                // caller's IL refers to them and their manifest tokens must be pre-seeded here.
                //
                // Only the typical definitions need tokens. The method fixup signature emits the method's
                // def/ref token and encodes the instantiation separately as type signatures (see
                // SignatureBuilder.EmitMethodSpecificationSignature), so no MethodSpec token is required.
                // The instantiation argument is the awaiter type, which the caller already refers to in its
                // own IL, so that is guaranteed to be encodable as well.
                asyncHelpers.GetKnownMethod("AwaitAwaiterInContinuation"u8, null),
                asyncHelpers.GetKnownMethod("UnsafeAwaitAwaiterInContinuation"u8, null),
            ];
            var moduleForNewReferences = ((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
            _tokenManager.EnsureDefTokensAreAvailable([..requiredMethods, ..requiredTypes, ..requiredFields], moduleForNewReferences, true);
            _hasAddedAsyncReferences = true;
        }

        public ISymbolNode GetFieldRvaData(FieldDesc field)
        {
            if (!CompilationModuleGroup.ContainsType(field.OwningType.GetTypeDefinition()))
            {
                // TODO: cross-bubble RVA field
                throw new RequiresRuntimeJitException($"GetFieldRvaData({field})");
            }

            return NodeFactory.CopiedFieldRva(field);
        }

        public override void Dispose()
        {
            _singleThreadedWorkerState = default;

            // Workaround for https://github.com/dotnet/runtime/issues/23103.
            // ManifestMetadataTable.Dispose() allows to break circular reference
            // ConcurrentBag<EcmaModule> -> EcmaModule -> EcmaAssembly -> ReadyToRunCompilerContext -> ... -> ConcurrentBag<EcmaModule>.
            // This circular reference along with #23103 prevents objects from being collected by GC.
            _nodeFactory.ManifestMetadataTable.Dispose();
        }

        public string GetReproInstructions(MethodDesc method)
        {
            return _printReproInstructions(method);
        }

    }
}