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

#pragma warning disable IDE0005

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Help;
using System.CommandLine.Parsing;
using System.IO;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;

using Internal.IL;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

using ILCompiler.Dataflow;
using ILCompiler.DependencyAnalysis;
using ILLink.Shared;

using Debug = System.Diagnostics.Debug;
using InstructionSet = Internal.JitInterface.InstructionSet;

namespace ILCompiler
{
    internal sealed class Program
    {
        private readonly ILCompilerRootCommand _command;
        private static readonly char[] s_separator = new char[] { ',', ';', ' ' };

        public Program(ILCompilerRootCommand command)
        {
            _command = command;

            if (Get(command.WaitForDebugger))
            {
                Console.WriteLine("Waiting for debugger to attach. Press ENTER to continue");
                Console.ReadLine();
            }
        }

        private IReadOnlyCollection<MethodDesc> CreateInitializerList(CompilerTypeSystemContext context)
        {
            List<ModuleDesc> assembliesWithInitializers = new List<ModuleDesc>();

            // Build a list of assemblies that have an initializer that needs to run before
            // any user code runs.
            foreach (string initAssemblyName in Get(_command.InitAssemblies))
            {
                ModuleDesc assembly = context.ResolveAssembly(new AssemblyNameInfo(initAssemblyName), throwIfNotFound: true);
                assembliesWithInitializers.Add(assembly);
            }

            var libraryInitializers = new LibraryInitializers(context, assembliesWithInitializers);

            IReadOnlyCollection<MethodDesc> result = libraryInitializers.LibraryInitializerMethods;

            if (Get(_command.InstrumentReachability))
            {
                List<MethodDesc> instrumentedResult = new List<MethodDesc>(result);
                instrumentedResult.Add(ReachabilityInstrumentationProvider.CreateInitializerMethod(context));
                result = instrumentedResult;
            }
            return result;
        }

        public int Run()
        {
            string outputFilePath = Get(_command.OutputFilePath);
            if (outputFilePath == null)
                throw new CommandLineException("Output filename must be specified (--out <file>)");

            var suppressedWarningCategories = new List<string>();
            if (Get(_command.NoTrimWarn))
                suppressedWarningCategories.Add(MessageSubCategory.TrimAnalysis);
            if (Get(_command.NoAotWarn))
                suppressedWarningCategories.Add(MessageSubCategory.AotAnalysis);

            ILProvider ilProvider = new NativeAotILProvider();

            Dictionary<int, bool> warningsAsErrors = new Dictionary<int, bool>();
            foreach (int warning in ProcessWarningCodes(Get(_command.WarningsAsErrorsEnable)))
            {
                warningsAsErrors[warning] = true;
            }
            foreach (int warning in ProcessWarningCodes(Get(_command.WarningsAsErrorsDisable)))
            {
                warningsAsErrors[warning] = false;
            }
            var logger = new Logger(Console.Out, ilProvider, Get(_command.IsVerbose), ProcessWarningCodes(Get(_command.SuppressedWarnings)),
                Get(_command.SingleWarn), Get(_command.SingleWarnEnabledAssemblies), Get(_command.SingleWarnDisabledAssemblies), suppressedWarningCategories,
                Get(_command.TreatWarningsAsErrors), warningsAsErrors, Get(_command.DisableGeneratedCodeHeuristics));

            // NativeAOT is full AOT and its pre-compiled methods can not be
            // thrown away at runtime if they mismatch in required ISAs or
            // computed layouts of structs. The worst case scenario is simply
            // that the image targets a higher machine than the user has and
            // it fails to launch. Thus we want to have usage of Vector<T>
            // directly encoded as part of the required ISAs.
            bool isVectorTOptimistic = false;

            TargetArchitecture targetArchitecture = Get(_command.TargetArchitecture);
            TargetOS targetOS = Get(_command.TargetOS);
            InstructionSetSupport instructionSetSupport = Helpers.ConfigureInstructionSetSupport(Get(_command.InstructionSet), Get(_command.MaxVectorTBitWidth), isVectorTOptimistic, targetArchitecture, targetOS,
                "Unrecognized instruction set {0}", "Unsupported combination of instruction sets: {0}/{1}", logger,
                allowOptimistic: _command.OptimizationMode != OptimizationMode.PreferSize,
                isReadyToRun: false);

            string systemModuleName = Get(_command.SystemModuleName);
            string reflectionData = Get(_command.ReflectionData);
            bool supportsReflection = reflectionData != "none" && systemModuleName == Helpers.DefaultSystemModule;

            //
            // Initialize type system context
            //

            SharedGenericsMode genericsMode = SharedGenericsMode.CanonicalReferenceTypes;

            var simdVectorLength = instructionSetSupport.GetVectorTSimdVector();
            var targetAbi = ILCompilerRootCommand.IsArmel ? TargetAbi.NativeAotArmel : TargetAbi.NativeAot;
            var targetDetails = new TargetDetails(targetArchitecture, targetOS, targetAbi, simdVectorLength);
            CompilerTypeSystemContext typeSystemContext =
                new CompilerTypeSystemContext(targetDetails, genericsMode, supportsReflection ? DelegateFeature.All : 0,
                    genericCycleDepthCutoff: Get(_command.MaxGenericCycleDepth),
                    genericCycleBreadthCutoff: Get(_command.MaxGenericCycleBreadth));

            typeSystemContext.InputFilePaths = _command.Result.GetValue(_command.InputFilePaths);
            typeSystemContext.ReferenceFilePaths = Get(_command.ReferenceFiles);
            if (!typeSystemContext.InputFilePaths.ContainsKey(systemModuleName)
                && !typeSystemContext.ReferenceFilePaths.ContainsKey(systemModuleName))
                throw new CommandLineException($"System module {systemModuleName} does not exists. Make sure that you specify --systemmodule");

            typeSystemContext.SetSystemModule(typeSystemContext.GetModuleForSimpleName(systemModuleName));

            if (typeSystemContext.InputFilePaths.Count == 0)
                throw new CommandLineException("No input files specified");

            ilProvider = new HardwareIntrinsicILProvider(
                instructionSetSupport,
                new ExternSymbolMappedField(typeSystemContext.GetWellKnownType(WellKnownType.Int32), "g_cpuFeatures"),
                ilProvider);

            SecurityMitigationOptions securityMitigationOptions = 0;
            string guard = Get(_command.Guard);
            if (StringComparer.OrdinalIgnoreCase.Equals(guard, "cf"))
            {
                if (targetOS != TargetOS.Windows)
                {
                    throw new CommandLineException($"Control flow guard only available on Windows");
                }

                securityMitigationOptions = SecurityMitigationOptions.ControlFlowGuardAnnotations;
            }
            else if (!string.IsNullOrEmpty(guard))
            {
                throw new CommandLineException($"Unrecognized mitigation option '{guard}'");
            }

            //
            // Initialize compilation group and compilation roots
            //

            // Single method mode?
            MethodDesc singleMethod = CheckAndParseSingleMethodModeArguments(typeSystemContext);

            CompilationModuleGroup compilationGroup;
            List<ICompilationRootProvider> compilationRoots = new List<ICompilationRootProvider>();
            TypeMapManager typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.Empty);
            bool multiFile = Get(_command.MultiFile);
            if (singleMethod != null)
            {
                // Compiling just a single method
                compilationGroup = new SingleMethodCompilationModuleGroup(singleMethod);
                compilationRoots.Add(new SingleMethodRootProvider(singleMethod));
                if (singleMethod.OwningType is MetadataType { Module.Assembly: EcmaAssembly assembly })
                {
                    typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.CreateFromAssembly(assembly, typeSystemContext.GeneratedAssembly, TypeMapAssemblyTargetsMode.Traverse));
                }
            }
            else
            {
                // Either single file, or multifile library, or multifile consumption.
                EcmaModule entrypointModule = null;
                foreach (var inputFile in typeSystemContext.InputFilePaths)
                {
                    EcmaModule module = typeSystemContext.GetModuleFromPath(inputFile.Value);

                    if (module.PEReader.PEHeaders.IsExe)
                    {
                        if (entrypointModule != null)
                            throw new Exception("Multiple EXE modules");
                        entrypointModule = module;
                    }

                    compilationRoots.Add(new UnmanagedEntryPointsRootProvider(module));
                }

                bool nativeLib = Get(_command.NativeLib);
                bool SplitExeInitialization = Get(_command.SplitExeInitialization);
                if (multiFile)
                {
                    List<EcmaModule> inputModules = new List<EcmaModule>();

                    foreach (var inputFile in typeSystemContext.InputFilePaths)
                    {
                        EcmaModule module = typeSystemContext.GetModuleFromPath(inputFile.Value);

                        if (entrypointModule == null)
                        {
                            // This is a multifile production build - we need to root all methods
                            compilationRoots.Add(new LibraryRootProvider(module));
                        }
                        inputModules.Add(module);
                    }

                    compilationGroup = new MultiFileSharedCompilationModuleGroup(typeSystemContext, inputModules);
                }
                else
                {
                    if (entrypointModule == null && (!nativeLib || SplitExeInitialization))
                        throw new Exception("No entrypoint module");

                    compilationGroup = new SingleFileCompilationModuleGroup();
                }

                const string settingsBlobName = "g_compilerEmbeddedSettingsBlob";
                const string knobsBlobName = "g_compilerEmbeddedKnobsBlob";
                string[] runtimeOptions = Get(_command.RuntimeOptions);
                string[] runtimeKnobs = Get(_command.RuntimeKnobs);
                if (nativeLib)
                {
                    // Set owning module of generated native library startup method to compiler generated module,
                    // to ensure the startup method is included in the object file during multimodule mode build
                    compilationRoots.Add(new NativeLibraryInitializerRootProvider(typeSystemContext.GeneratedAssembly, CreateInitializerList(typeSystemContext)));
                    compilationRoots.Add(new RuntimeConfigurationRootProvider(settingsBlobName, runtimeOptions));
                    compilationRoots.Add(new RuntimeConfigurationRootProvider(knobsBlobName, runtimeKnobs));
                    compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
                    if (SplitExeInitialization)
                    {
                        compilationRoots.Add(new MainMethodRootProvider(entrypointModule, CreateInitializerList(typeSystemContext), generateLibraryAndModuleInitializers: false));
                    }
                }
                else if (entrypointModule != null)
                {
                    compilationRoots.Add(new MainMethodRootProvider(entrypointModule, CreateInitializerList(typeSystemContext), generateLibraryAndModuleInitializers: !SplitExeInitialization));
                    compilationRoots.Add(new RuntimeConfigurationRootProvider(settingsBlobName, runtimeOptions));
                    compilationRoots.Add(new RuntimeConfigurationRootProvider(knobsBlobName, runtimeKnobs));
                    compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
                    if (SplitExeInitialization)
                    {
                        compilationRoots.Add(new NativeLibraryInitializerRootProvider(typeSystemContext.GeneratedAssembly, CreateInitializerList(typeSystemContext)));
                    }
                }

                compilationRoots.Add(new ManagedDataDescriptorProvider());

                string win32resourcesModule = Get(_command.Win32ResourceModuleName);
                if (typeSystemContext.Target.IsWindows && !string.IsNullOrEmpty(win32resourcesModule))
                {
                    EcmaModule module = typeSystemContext.GetModuleForSimpleName(win32resourcesModule);
                    compilationRoots.Add(new Win32ResourcesRootProvider(module));
                }

                foreach (var unmanagedEntryPointsAssemblyValue in Get(_command.UnmanagedEntryPointsAssemblies))
                {
                    const string hiddenSuffix = ",HIDDEN";
                    bool hidden = unmanagedEntryPointsAssemblyValue.EndsWith(hiddenSuffix, StringComparison.Ordinal);
                    string unmanagedEntryPointsAssembly = hidden ? unmanagedEntryPointsAssemblyValue[..^hiddenSuffix.Length] : unmanagedEntryPointsAssemblyValue;

                    if (typeSystemContext.InputFilePaths.ContainsKey(unmanagedEntryPointsAssembly))
                    {
                        // Skip adding UnmanagedEntryPointsRootProvider for modules that have been already registered as an input module
                        continue;
                    }
                    EcmaModule module = typeSystemContext.GetModuleForSimpleName(unmanagedEntryPointsAssembly);
                    compilationRoots.Add(new UnmanagedEntryPointsRootProvider(module, hidden));
                }

                foreach (var rdXmlFilePath in Get(_command.RdXmlFilePaths))
                {
                    compilationRoots.Add(new RdXmlRootProvider(typeSystemContext, rdXmlFilePath));
                }

                foreach (var linkTrimFilePath in Get(_command.LinkTrimFilePaths))
                {
                    if (!File.Exists(linkTrimFilePath))
                        throw new CommandLineException($"'{linkTrimFilePath}' doesn't exist");
                    compilationRoots.Add(new ILCompiler.DependencyAnalysis.TrimmingDescriptorNode(linkTrimFilePath));
                }

                // Get TypeMappingEntryAssembly from command-line option if specified
                string typeMappingEntryAssembly = Get(_command.TypeMapEntryAssembly);
                if (typeMappingEntryAssembly is not null)
                {
                    var typeMapEntryAssembly = (EcmaAssembly)typeSystemContext.ResolveAssembly(AssemblyNameInfo.Parse(typeMappingEntryAssembly), throwIfNotFound: true);
                    typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.CreateFromAssembly(typeMapEntryAssembly, typeSystemContext.GeneratedAssembly, TypeMapAssemblyTargetsMode.Traverse));
                }
                else if (entrypointModule is { Assembly: EcmaAssembly entryAssembly })
                {
                    // Fall back to entryassembly if not specified
                    typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.CreateFromAssembly(entryAssembly, typeSystemContext.GeneratedAssembly, TypeMapAssemblyTargetsMode.Traverse));
                }
            }

            // Root whatever assemblies were specified on the command line
            string[] rootedAssemblies = Get(_command.RootedAssemblies);
            foreach (var rootedAssembly in rootedAssemblies)
            {
                EcmaModule module = typeSystemContext.GetModuleForSimpleName(rootedAssembly);

                // We only root the module type. The rest will fall out because we treat rootedAssemblies
                // same as conditionally rooted ones and here we're fulfilling the condition ("something is used").
                compilationRoots.Add(
                    new GenericRootProvider<ModuleDesc>(module,
                    (ModuleDesc module, IRootingServiceProvider rooter) => rooter.AddReflectionRoot(module.GetGlobalModuleType(), "Command line root")));
            }

            // Unless explicitly opted in at the command line, we enable scanner for retail builds by default.
            // We also don't do this for multifile because scanner doesn't simulate inlining (this would be
            // fixable by using a CompilationGroup for the scanner that has a bigger worldview, but
            // let's cross that bridge when we get there).
            bool useScanner = Get(_command.UseScanner) ||
                (_command.OptimizationMode != OptimizationMode.None && !multiFile);

            useScanner &= !Get(_command.NoScanner);

            bool resilient = Get(_command.Resilient);
            if (resilient && useScanner)
            {
                // If we're in resilient mode (invalid IL doesn't crash the compiler) and using scanner,
                // assume invalid code is present. Scanner may not detect all invalid code that RyuJIT detect.
                // If they disagree, we won't know how the vtable of InvalidProgramException should look like
                // and that would be a compiler crash.
                MethodDesc throwInvalidProgramMethod = typeSystemContext.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowInvalidProgramException"u8);
                compilationRoots.Add(
                    new GenericRootProvider<MethodDesc>(throwInvalidProgramMethod,
                    (MethodDesc method, IRootingServiceProvider rooter) => rooter.AddCompilationRoot(method, "Invalid IL insurance")));
            }

            //
            // Compile
            //

            string compilationUnitPrefix = multiFile ? Path.GetFileNameWithoutExtension(outputFilePath) : "";
            var builder = new RyuJitCompilationBuilder(typeSystemContext, compilationGroup)
                .FileLayoutAlgorithms(Get(_command.MethodLayout), Get(_command.FileLayout))
                .UseSymbolOrder(Get(_command.OrderFile))
                .UseCompilationUnitPrefix(compilationUnitPrefix);

            string[] mibcFilePaths = Get(_command.MibcFilePaths);
            if (mibcFilePaths.Length > 0)
                ((RyuJitCompilationBuilder)builder).UseProfileData(mibcFilePaths);

            string jitPath = Get(_command.JitPath);
            if (!string.IsNullOrEmpty(jitPath))
                ((RyuJitCompilationBuilder)builder).UseJitPath(jitPath);

            PInvokeILEmitterConfiguration pinvokePolicy = new ConfigurablePInvokePolicy(typeSystemContext.Target,
                Get(_command.DirectPInvokes), Get(_command.DirectPInvokeLists));

            var featureSwitches = new Dictionary<string, bool>();
            foreach (var switchPair in Get(_command.FeatureSwitches))
            {
                string[] switchAndValue = switchPair.Split('=');
                if (switchAndValue.Length != 2
                    || !bool.TryParse(switchAndValue[1], out bool switchValue))
                    throw new CommandLineException($"Unexpected feature switch pair '{switchPair}'");
                featureSwitches[switchAndValue[0]] = switchValue;
            }

            BodyAndFieldSubstitutions substitutions = default;
            IReadOnlyDictionary<ModuleDesc, IReadOnlySet<string>> resourceBlocks = default;
            foreach (string substitutionFilePath in Get(_command.SubstitutionFilePaths))
            {
                using FileStream fs = File.OpenRead(substitutionFilePath);
                substitutions.AppendFrom(BodySubstitutionsParser.GetSubstitutions(
                    logger, typeSystemContext, XmlReader.Create(fs), substitutionFilePath, featureSwitches));

                fs.Seek(0, SeekOrigin.Begin);

                resourceBlocks = ManifestResourceBlockingPolicy.UnionBlockings(resourceBlocks,
                    ManifestResourceBlockingPolicy.SubstitutionsReader.GetSubstitutions(
                        logger, typeSystemContext, XmlReader.Create(fs), substitutionFilePath, featureSwitches));
            }

            CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger, Get(_command.DisableGeneratedCodeHeuristics));

            if (Get(_command.UseReachability) is string reachabilityInstrumentationFileName)
            {
                ilProvider = new ReachabilityInstrumentationFilter(reachabilityInstrumentationFileName, ilProvider);
            }

            SubstitutionProvider substitutionProvider = new SubstitutionProvider(logger, featureSwitches, substitutions);
            ILProvider unsubstitutedILProvider = ilProvider;
            ilProvider = new SubstitutedILProvider(ilProvider, substitutionProvider, new DevirtualizationManager());

            (bool emitStackTraceData, bool stackTraceLineNumbers) = Get(_command.StackTraceData) switch
            {
                null or "none" => (false, false),
                "frames" => (true, false),
                "lines" => (true, true),
                _ => throw new CommandLineException($"Unknown stack trace data: {Get(_command.StackTraceData)}"),
            };

            var stackTracePolicy = emitStackTraceData ?
                (StackTraceEmissionPolicy)new EcmaMethodStackTraceEmissionPolicy(stackTraceLineNumbers) : new NoStackTraceEmissionPolicy();

            MetadataBlockingPolicy mdBlockingPolicy;
            ManifestResourceBlockingPolicy resBlockingPolicy;
            UsageBasedMetadataGenerationOptions metadataGenerationOptions = default;
            if (supportsReflection)
            {
                mdBlockingPolicy = new NoMetadataBlockingPolicy();

                resBlockingPolicy = new ManifestResourceBlockingPolicy(logger, featureSwitches, resourceBlocks);

                if (Get(_command.CompleteTypesMetadata))
                    metadataGenerationOptions |= UsageBasedMetadataGenerationOptions.CompleteTypesOnly;
                if (Get(_command.ScanReflection))
                    metadataGenerationOptions |= UsageBasedMetadataGenerationOptions.ReflectionILScanning;
                if (reflectionData == "all")
                    metadataGenerationOptions |= UsageBasedMetadataGenerationOptions.CreateReflectableArtifacts;
                if (Get(_command.RootDefaultAssemblies))
                    metadataGenerationOptions |= UsageBasedMetadataGenerationOptions.RootDefaultAssemblies;
            }
            else
            {
                mdBlockingPolicy = new FullyBlockedMetadataBlockingPolicy();
                resBlockingPolicy = new FullyBlockedManifestResourceBlockingPolicy();
            }

            DynamicInvokeThunkGenerationPolicy invokeThunkGenerationPolicy = new DefaultDynamicInvokeThunkGenerationPolicy();

            var flowAnnotations = new ILLink.Shared.TrimAnalysis.FlowAnnotations(logger, ilProvider, compilerGeneratedState);

            MetadataManagerOptions metadataOptions = default;
            if (Get(_command.Dehydrate))
                metadataOptions |= MetadataManagerOptions.DehydrateData;

            MetadataManager metadataManager = new UsageBasedMetadataManager(
                    compilationGroup,
                    typeSystemContext,
                    mdBlockingPolicy,
                    resBlockingPolicy,
                    Get(_command.MetadataLogFileName),
                    stackTracePolicy,
                    invokeThunkGenerationPolicy,
                    flowAnnotations,
                    metadataGenerationOptions,
                    metadataOptions,
                    logger,
                    featureSwitches,
                    Get(_command.ConditionallyRootedAssemblies),
                    rootedAssemblies,
                    Get(_command.TrimmedAssemblies),
                    Get(_command.SatelliteFilePaths));

            InteropStateManager interopStateManager = new InteropStateManager(typeSystemContext.GeneratedAssembly);
            InteropStubManager interopStubManager = new UsageBasedInteropStubManager(interopStateManager, pinvokePolicy, logger);

            // Enable static data preinitialization in optimized builds.
            bool preinitStatics = Get(_command.PreinitStatics) ||
                (_command.OptimizationMode != OptimizationMode.None && !multiFile);
            preinitStatics &= !Get(_command.NoPreinitStatics);

            TypePreinit.TypePreinitializationPolicy preinitPolicy = preinitStatics ?
                new TypePreinit.TypeLoaderAwarePreinitializationPolicy() : new TypePreinit.DisabledPreinitializationPolicy();

            var preinitManager = new PreinitializationManager(typeSystemContext, compilationGroup, ilProvider, preinitPolicy, new StaticReadOnlyFieldPolicy(), flowAnnotations);
            builder
                .UseILProvider(ilProvider)
                .UsePreinitializationManager(preinitManager)
                .UseTypeMapManager(typeMapManager);

#if DEBUG
            List<TypeDesc> scannerConstructedTypes = null;
            List<MethodDesc> scannerCompiledMethods = null;
#endif

            int parallelism = Get(_command.Parallelism);
            if (useScanner)
            {
                // Run the scanner in a separate stack frame so that there's no dangling references to
                // it once we're done with it and it can be garbage collected.
                RunScanner();
            }

            [MethodImpl(MethodImplOptions.NoInlining)]
            void RunScanner()
            {
                ILScannerBuilder scannerBuilder = builder.GetILScannerBuilder()
                    .UseCompilationRoots(compilationRoots)
                    .UseMetadataManager(metadataManager)
                    .UseParallelism(parallelism)
                    .UseInteropStubManager(interopStubManager)
                    .UseTypeMapManager(typeMapManager)
                    .UseLogger(logger);

                string scanDgmlLogFileName = Get(_command.ScanDgmlLogFileName);
                if (scanDgmlLogFileName != null)
                    scannerBuilder.UseDependencyTracking(Get(_command.GenerateFullScanDgmlLog) ?
                            DependencyTrackingLevel.All : DependencyTrackingLevel.First);

                IILScanner scanner = scannerBuilder.ToILScanner();

                ILScanResults scanResults = scanner.Scan();

#if DEBUG
                scannerCompiledMethods = new List<MethodDesc>(scanResults.CompiledMethodBodies);
                scannerConstructedTypes = new List<TypeDesc>(scanResults.ConstructedEETypes);
#endif

                if (scanDgmlLogFileName != null)
                    scanResults.WriteDependencyLog(scanDgmlLogFileName);

                DevirtualizationManager devirtualizationManager = scanResults.GetDevirtualizationManager();

                metadataManager = ((UsageBasedMetadataManager)metadataManager).ToAnalysisBasedMetadataManager();

                builder.UseTypeMapManager(scanResults.GetTypeMapManager());

                interopStubManager = scanResults.GetInteropStubManager(interopStateManager, pinvokePolicy);

                ilProvider = new SubstitutedILProvider(unsubstitutedILProvider, substitutionProvider, devirtualizationManager, metadataManager, scanResults.GetAnalysisCharacteristics());

                // Use a more precise IL provider that uses whole program analysis for dead branch elimination
                builder.UseILProvider(ilProvider);

                // If we have a scanner, feed the vtable analysis results to the compilation.
                // This could be a command line switch if we really wanted to.
                builder.UseVTableSliceProvider(scanResults.GetVTableLayoutInfo());

                // If we have a scanner, feed the generic dictionary results to the compilation.
                // This could be a command line switch if we really wanted to.
                builder.UseGenericDictionaryLayoutProvider(scanResults.GetDictionaryLayoutInfo());

                // If we have a scanner, we can drive devirtualization using the information
                // we collected at scanning time (effectively sealing unsealed types if possible).
                // This could be a command line switch if we really wanted to.
                builder.UseDevirtualizationManager(devirtualizationManager);

                // If we use the scanner's result, we need to consult it to drive inlining.
                // This prevents e.g. devirtualizing and inlining methods on types that were
                // never actually allocated.
                builder.UseInliningPolicy(scanResults.GetInliningPolicy());

                // Use an error provider that prevents us from re-importing methods that failed
                // to import with an exception during scanning phase. We would see the same failure during
                // compilation, but before RyuJIT gets there, it might ask questions that we don't
                // have answers for because we didn't scan the entire method.
                builder.UseMethodImportationErrorProvider(scanResults.GetMethodImportationErrorProvider());

                // If we're doing preinitialization, use a new preinitialization manager that
                // has the whole program view.
                if (preinitStatics)
                {
                    var readOnlyFieldPolicy = scanResults.GetReadOnlyFieldPolicy();
                    preinitManager = new PreinitializationManager(typeSystemContext, compilationGroup, ilProvider, scanResults.GetPreinitializationPolicy(),
                        readOnlyFieldPolicy, flowAnnotations);
                    builder.UsePreinitializationManager(preinitManager)
                        .UseReadOnlyFieldPolicy(readOnlyFieldPolicy);
                }

                // If we have a scanner, we can inline threadstatics storage using the information we collected at scanning time.
                if (!Get(_command.NoInlineTls) &&
                    ((targetOS == TargetOS.Linux && targetArchitecture is TargetArchitecture.X64 or TargetArchitecture.ARM64) ||
                     (targetOS == TargetOS.Windows && targetArchitecture is TargetArchitecture.X64 or TargetArchitecture.ARM64)))
                {
                    builder.UseInlinedThreadStatics(scanResults.GetInlinedThreadStatics());
                }
            }

            if (Get(_command.InstrumentReachability))
            {
                ReachabilityInstrumentationProvider reachabilityProvider = new ReachabilityInstrumentationProvider(ilProvider);
                ilProvider = reachabilityProvider;
                builder.UseILProvider(ilProvider);
                compilationRoots.Add(reachabilityProvider);
            }

            string ilDump = Get(_command.IlDump);
            DebugInformationProvider debugInfoProvider = Get(_command.EnableDebugInfo) ?
                (ilDump == null ? new DebugInformationProvider() : new ILAssemblyGeneratingMethodDebugInfoProvider(ilDump, new EcmaOnlyDebugInformationProvider())) :
                new NullDebugInformationProvider();

            string dgmlLogFileName = Get(_command.DgmlLogFileName);
            DependencyTrackingLevel trackingLevel = dgmlLogFileName == null ?
                DependencyTrackingLevel.None : (Get(_command.GenerateFullDgmlLog) ?
                    DependencyTrackingLevel.All : DependencyTrackingLevel.First);

            compilationRoots.Add(metadataManager);
            compilationRoots.Add(interopStubManager);

            MethodBodyFoldingMode foldingMode = string.IsNullOrEmpty(Get(_command.MethodBodyFolding))
                ? MethodBodyFoldingMode.None
                : Enum.Parse<MethodBodyFoldingMode>(Get(_command.MethodBodyFolding), ignoreCase: true);

            builder
                .UseInstructionSetSupport(instructionSetSupport)
                .UseBackendOptions(Get(_command.CodegenOptions))
                .UseMethodBodyFolding(foldingMode)
                .UseParallelism(parallelism)
                .UseMetadataManager(metadataManager)
                .UseInteropStubManager(interopStubManager)
                .UseLogger(logger)
                .UseDependencyTracking(trackingLevel)
                .UseCompilationRoots(compilationRoots)
                .UseOptimizationMode(_command.OptimizationMode)
                .UseSecurityMitigationOptions(securityMitigationOptions)
                .UseDebugInfoProvider(debugInfoProvider)
                .UseDwarf5(Get(_command.UseDwarf5))
                .UseResilience(resilient);

            ICompilation compilation = builder.ToCompilation();

            string mapFileName = Get(_command.MapFileName);
            string mstatFileName = Get(_command.MstatFileName);
            string sourceLinkFileName = Get(_command.SourceLinkFileName);

            List<ObjectDumper> dumpers = new List<ObjectDumper>();

            if (mapFileName != null)
                dumpers.Add(new XmlObjectDumper(mapFileName));

            if (mstatFileName != null)
                dumpers.Add(new MstatObjectDumper(mstatFileName, typeSystemContext));

            if (sourceLinkFileName != null)
                dumpers.Add(new SourceLinkWriter(sourceLinkFileName));

            // Write to a temporary file and rename on success to avoid leaving partial files on failure
            string tempOutputFilePath = outputFilePath + ".tmp";
            CompilationResults compilationResults = compilation.Compile(tempOutputFilePath, ObjectDumper.Compose(dumpers));
            string exportsFile = Get(_command.ExportsFile);
            if (exportsFile != null)
            {
                ExportsFileWriter defFileWriter = new ExportsFileWriter(typeSystemContext, exportsFile, Get(_command.ExportDynamicSymbols));

                if (Get(_command.ExportUnmanagedEntryPoints))
                {
                    foreach (var compilationRoot in compilationRoots)
                    {
                        if (compilationRoot is UnmanagedEntryPointsRootProvider provider && !provider.Hidden)
                            defFileWriter.AddExportedMethods(provider.ExportedMethods);
                    }
                }

                defFileWriter.EmitExportedMethods();
            }

            typeSystemContext.LogWarnings(logger);

            if (dgmlLogFileName != null)
                compilationResults.WriteDependencyLog(dgmlLogFileName);

#if DEBUG
            if (scannerConstructedTypes != null)
            {
                // If the scanner and compiler don't agree on what to compile, the outputs of the scanner might not actually be usable.
                // We are going to check this two ways:
                // 1. The methods and types generated during compilation are a subset of method and types scanned
                // 2. The methods and types scanned are a subset of methods and types compiled (this has a chance to hold for unoptimized builds only).

                // Check that methods and types generated during compilation are a subset of method and types scanned
                bool scanningFail = false;
                DiffCompilationResults(ref scanningFail, compilationResults.CompiledMethodBodies, scannerCompiledMethods,
                    "Methods", "compiled", "scanned", method => !(method.GetTypicalMethodDefinition() is EcmaMethod) || IsRelatedToInvalidInput(method));
                DiffCompilationResults(ref scanningFail, compilationResults.ConstructedEETypes, scannerConstructedTypes,
                    "EETypes", "compiled", "scanned", type => !(type.GetTypeDefinition() is EcmaType));

                static bool IsRelatedToInvalidInput(MethodDesc method)
                {
                    // RyuJIT is more sensitive to invalid input and might detect cases that the scanner didn't have trouble with.
                    // If we find logic related to compiling fallback method bodies (methods that just throw) that got compiled
                    // but not scanned, it's usually fine. If it wasn't fine, we would probably crash before getting here.
                    return method.OwningType is MetadataType mdType
                        && mdType.Module == method.Context.SystemModule
                        && (mdType.Name.EndsWith("Exception"u8) || mdType.Namespace.StartsWith("Internal.Runtime"u8));
                }

                // If optimizations are enabled, the results will for sure not match in the other direction due to inlining, etc.
                // But there's at least some value in checking the scanner doesn't expand the universe too much in debug.
                if (_command.OptimizationMode == OptimizationMode.None)
                {
                    // Check that methods and types scanned are a subset of methods and types compiled

                    // If we find diffs here, they're not critical, but still might be causing a Size on Disk regression.
                    bool dummy = false;

                    // We additionally skip methods in SIMD module because there's just too many intrisics to handle and IL scanner
                    // doesn't expand them. They would show up as noisy diffs.
                    DiffCompilationResults(ref dummy, scannerCompiledMethods, compilationResults.CompiledMethodBodies,
                    "Methods", "scanned", "compiled", method => !(method.GetTypicalMethodDefinition() is EcmaMethod) || method.OwningType.IsIntrinsic);
                    DiffCompilationResults(ref dummy, scannerConstructedTypes, compilationResults.ConstructedEETypes,
                        "EETypes", "scanned", "compiled", type => !(type.GetTypeDefinition() is EcmaType));
                }

                if (scanningFail)
                    throw new Exception("Scanning failure");
            }
#endif

            if (debugInfoProvider is IDisposable)
                ((IDisposable)debugInfoProvider).Dispose();

            preinitManager.LogStatistics(logger);

            // If errors were produced (including warnings treated as errors), delete the temporary file
            // and return error code to avoid misleading build systems into thinking the compilation succeeded.
            if (logger.HasLoggedErrors)
            {
                try
                {
                    File.Delete(tempOutputFilePath);
                }
                catch
                {
                    // If we can't delete the temp file, there's not much we can do.
                    // The compilation will still fail due to logged errors.
                }

                return 1;
            }

            // Rename the temporary file to the final output file
            File.Move(tempOutputFilePath, outputFilePath, overwrite: true);

            return 0;
        }

        private static void DiffCompilationResults<T>(ref bool result, IEnumerable<T> set1, IEnumerable<T> set2, string prefix,
            string set1name, string set2name, Predicate<T> filter)
        {
            HashSet<T> diff = new HashSet<T>(set1);
            diff.ExceptWith(set2);

            // TODO: move ownership of compiler-generated entities to CompilerTypeSystemContext.
            // https://github.com/dotnet/corert/issues/3873
            diff.RemoveWhere(filter);

            if (diff.Count > 0)
            {
                result = true;

                Console.WriteLine($"*** {prefix} {set1name} but not {set2name}:");

                foreach (var d in diff)
                {
                    Console.WriteLine(d.ToString());
                }
            }
        }

        private static TypeDesc FindType(CompilerTypeSystemContext context, string typeName)
        {
            ModuleDesc systemModule = context.SystemModule;

            TypeDesc foundType = systemModule.GetTypeByCustomAttributeTypeName(typeName, false,
                (module, typeDefName) => (MetadataType)module.Context.GetCanonType(typeDefName));

            if (foundType == null)
                throw new CommandLineException($"Type '{typeName}' not found");

            return foundType;
        }

        private MethodDesc CheckAndParseSingleMethodModeArguments(CompilerTypeSystemContext context)
        {
            string singleMethodName = Get(_command.SingleMethodName);
            string singleMethodTypeName = Get(_command.SingleMethodTypeName);
            string[] singleMethodGenericArgs = Get(_command.SingleMethodGenericArgs);

            if (singleMethodName == null && singleMethodTypeName == null && singleMethodGenericArgs.Length == 0)
                return null;

            if (singleMethodName == null || singleMethodTypeName == null)
                throw new CommandLineException("Both method name and type name are required parameters for single method mode");

            TypeDesc owningType = FindType(context, singleMethodTypeName);

            // TODO: allow specifying signature to distinguish overloads
            MethodDesc method = owningType.GetMethod(Encoding.UTF8.GetBytes(singleMethodName), null);
            if (method == null)
                throw new CommandLineException($"Method '{singleMethodName}' not found in '{singleMethodTypeName}'");

            if (method.Instantiation.Length != singleMethodGenericArgs.Length)
            {
                throw new CommandLineException(
                    $"Expected {method.Instantiation.Length} generic arguments for method '{singleMethodName}' on type '{singleMethodTypeName}'");
            }

            if (method.HasInstantiation)
            {
                List<TypeDesc> genericArguments = new List<TypeDesc>();
                foreach (var argString in singleMethodGenericArgs)
                    genericArguments.Add(FindType(context, argString));
                method = method.MakeInstantiatedMethod(genericArguments.ToArray());
            }

            return method;
        }

        private static IEnumerable<int> ProcessWarningCodes(IEnumerable<string> warningCodes)
        {
            foreach (string value in warningCodes)
            {
                string[] values = value.Split(s_separator, StringSplitOptions.RemoveEmptyEntries);
                foreach (string id in values)
                {
                    if (!id.StartsWith("IL", StringComparison.Ordinal) || !ushort.TryParse(id.AsSpan(2), out ushort code))
                        continue;

                    yield return code;
                }
            }
        }

        private T Get<T>(Option<T> option) => _command.Result.GetValue(option);

        private static int Main(string[] args) =>
            new ILCompilerRootCommand(args)
                .UseVersion()
                .UseExtendedHelp(ILCompilerRootCommand.PrintExtendedHelp)
                .Parse(args, new()
                {
                    ResponseFileTokenReplacer = Helpers.TryReadResponseFile,
                })
                .Invoke(new()
                {
                    EnableDefaultExceptionHandler = false
                });
    }
}