From ba0ce8aea603b76e585c1ba775b534949cea2f27 Mon Sep 17 00:00:00 2001 From: Ryan Mansfield Date: Thu, 18 Sep 2025 15:53:31 -0400 Subject: [PATCH] Add frontend options to write SIL and LLVM IR as additional compilation output. This commit adds -sil-output-path and -ir-output-path frontend options that allow generating SIL and LLVM IR files as supplementary outputs during normal compilation. These options can be useful for debugging and analysis tools workflows that need access to intermediate compilation artifacts without requiring separate compiler invocations. Expected behaviour: Primary File mode: - SIL: Generates one .sil file per source file - IR: Generates one .ll file per source file Single-threaded WMO mode: - SIL: Generates one .sil file for the entire module - IR: Generates one .ll file for the entire module Multi-threaded WMO mode: - SIL: Generates one .sil file for the entire module - IR: Generates separate .ll files per source file File Maps with WMO: - Both SIL and IR outputs using first entry's naming, which is consistent with the behaviour of other supplementary outputs. rdar://160297898 --- include/swift/AST/IRGenRequests.h | 4 + .../swift/Basic/SupplementaryOutputPaths.def | 6 + include/swift/FrontendTool/FrontendTool.h | 3 +- include/swift/Option/FrontendOptions.td | 14 +- include/swift/Subsystems.h | 5 +- lib/Driver/ToolChains.cpp | 12 ++ .../ArgsToFrontendOutputsConverter.cpp | 137 ++++++++++++------ lib/FrontendTool/FrontendTool.cpp | 126 +++++++++++----- lib/IDETool/CompileInstance.cpp | 3 +- lib/IRGen/IRGen.cpp | 34 ++++- lib/Immediate/SwiftMaterializationUnit.cpp | 3 +- test/Frontend/ir-output-path.swift | 14 ++ test/Frontend/output-file-map-sil-ir.swift | 46 ++++++ test/Frontend/sil-output-path.swift | 12 ++ test/Frontend/wmo-supplementary-outputs.swift | 36 +++++ 15 files changed, 372 insertions(+), 83 deletions(-) create mode 100644 test/Frontend/ir-output-path.swift create mode 100644 test/Frontend/output-file-map-sil-ir.swift create mode 100644 test/Frontend/sil-output-path.swift create mode 100644 test/Frontend/wmo-supplementary-outputs.swift diff --git a/include/swift/AST/IRGenRequests.h b/include/swift/AST/IRGenRequests.h index 7285a7370f95f..7e4228fe56839 100644 --- a/include/swift/AST/IRGenRequests.h +++ b/include/swift/AST/IRGenRequests.h @@ -151,6 +151,7 @@ struct IRGenDescriptor { const PrimarySpecificPaths &PSPs; StringRef PrivateDiscriminator; ArrayRef parallelOutputFilenames; + ArrayRef parallelIROutputFilenames; llvm::GlobalVariable **outModuleHash; llvm::raw_pwrite_stream *out = nullptr; @@ -188,6 +189,7 @@ struct IRGenDescriptor { PSPs, PrivateDiscriminator, {}, + {}, outModuleHash}; } @@ -197,6 +199,7 @@ struct IRGenDescriptor { std::unique_ptr &&SILMod, StringRef ModuleName, const PrimarySpecificPaths &PSPs, SymsToEmit symsToEmit = std::nullopt, ArrayRef parallelOutputFilenames = {}, + ArrayRef parallelIROutputFilenames = {}, llvm::GlobalVariable **outModuleHash = nullptr) { return IRGenDescriptor{M, symsToEmit, @@ -209,6 +212,7 @@ struct IRGenDescriptor { PSPs, "", parallelOutputFilenames, + parallelIROutputFilenames, outModuleHash}; } diff --git a/include/swift/Basic/SupplementaryOutputPaths.def b/include/swift/Basic/SupplementaryOutputPaths.def index 8a0db85b02109..00dd7ad1b5420 100644 --- a/include/swift/Basic/SupplementaryOutputPaths.def +++ b/include/swift/Basic/SupplementaryOutputPaths.def @@ -166,3 +166,9 @@ OUTPUT(YAMLOptRecordPath, TY_YAMLOptRecord) /// The output path for bitstream optimization record file. OUTPUT(BitstreamOptRecordPath, TY_BitstreamOptRecord) + +/// The output path to which we should output SIL as extra compilation output. +OUTPUT(SILOutputPath, TY_SIL) + +/// The output path to which we should output LLVM IR as extra compilation output. +OUTPUT(LLVMIROutputPath, TY_LLVM_IR) diff --git a/include/swift/FrontendTool/FrontendTool.h b/include/swift/FrontendTool/FrontendTool.h index 184e61969182b..2c17c8e697262 100644 --- a/include/swift/FrontendTool/FrontendTool.h +++ b/include/swift/FrontendTool/FrontendTool.h @@ -79,7 +79,8 @@ int performFrontend(ArrayRef args, FrontendObserver *observer = nullptr); bool performCompileStepsPostSema(CompilerInstance &Instance, int &ReturnValue, - FrontendObserver *observer); + FrontendObserver *observer, + ArrayRef CommandLineArgs); } // namespace swift diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 3d034ee45263c..d81d5795e8e65 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -63,6 +63,18 @@ def emit_module_semantic_info_path : Separate<["-"], "emit-module-semantic-info-path">, MetaVarName<"">, HelpText<"Output semantic info of current module to ">; +def sil_output_path + : Separate<["-"], "sil-output-path">, MetaVarName<"">, + Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath, + SupplementaryOutput, CacheInvariant]>, + HelpText<"Output SIL to as additional output during compilation">; + +def ir_output_path + : Separate<["-"], "ir-output-path">, MetaVarName<"">, + Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath, + SupplementaryOutput, CacheInvariant]>, + HelpText<"Output LLVM IR to as additional output during compilation">; + def diagnostic_documentation_path : Separate<["-"], "diagnostic-documentation-path">, MetaVarName<"">, HelpText<"Path to diagnostic documentation resources">; @@ -266,7 +278,7 @@ def serialize_dependency_scan_cache : Flag<["-"], "serialize-dependency-scan-cac def reuse_dependency_scan_cache : Flag<["-"], "load-dependency-scan-cache">, HelpText<"For performing a dependency scan, deserialize the scanner's internal state from a prior scan.">; - + def validate_prior_dependency_scan_cache : Flag<["-"], "validate-prior-dependency-scan-cache">, HelpText<"For performing a dependency scan with a prior scanner state, validate module dependencies.">; diff --git a/include/swift/Subsystems.h b/include/swift/Subsystems.h index 635c306601c54..a40041ceb3f38 100644 --- a/include/swift/Subsystems.h +++ b/include/swift/Subsystems.h @@ -250,9 +250,10 @@ namespace swift { GeneratedModule performIRGeneration(ModuleDecl *M, const IRGenOptions &Opts, const TBDGenOptions &TBDOpts, - std::unique_ptr SILMod, - StringRef ModuleName, const PrimarySpecificPaths &PSPs, + std::unique_ptr SILMod, StringRef ModuleName, + const PrimarySpecificPaths &PSPs, ArrayRef parallelOutputFilenames, + ArrayRef parallelIROutputFilenames, llvm::GlobalVariable **outModuleHash = nullptr); /// Turn the given Swift file into LLVM IR and return the generated module. diff --git a/lib/Driver/ToolChains.cpp b/lib/Driver/ToolChains.cpp index 2d2deec00e082..6741f2e58b8a2 100644 --- a/lib/Driver/ToolChains.cpp +++ b/lib/Driver/ToolChains.cpp @@ -953,6 +953,12 @@ void ToolChain::JobContext::addFrontendSupplementaryOutputArguments( addOutputsOfType(arguments, Output, Args, file_types::TY_SwiftModuleSummaryFile, "-emit-module-summary-path"); + + // Add extra output paths for SIL and LLVM IR + addOutputsOfType(arguments, Output, Args, file_types::TY_SIL, + "-sil-output-path"); + addOutputsOfType(arguments, Output, Args, file_types::TY_LLVM_IR, + "-ir-output-path"); } ToolChain::InvocationInfo @@ -1237,6 +1243,12 @@ ToolChain::constructInvocation(const MergeModuleJobAction &job, addOutputsOfType(Arguments, context.Output, context.Args, file_types::TY_TBD, "-emit-tbd-path"); + // Add extra output paths for SIL and LLVM IR + addOutputsOfType(Arguments, context.Output, context.Args, file_types::TY_SIL, + "-sil-output-path"); + addOutputsOfType(Arguments, context.Output, context.Args, + file_types::TY_LLVM_IR, "-ir-output-path"); + context.Args.AddLastArg(Arguments, options::OPT_emit_symbol_graph); context.Args.AddLastArg(Arguments, options::OPT_emit_symbol_graph_dir); context.Args.AddLastArg(Arguments, options::OPT_include_spi_symbols); diff --git a/lib/Frontend/ArgsToFrontendOutputsConverter.cpp b/lib/Frontend/ArgsToFrontendOutputsConverter.cpp index 31cfd92649ebb..f3c8e936da53c 100644 --- a/lib/Frontend/ArgsToFrontendOutputsConverter.cpp +++ b/lib/Frontend/ArgsToFrontendOutputsConverter.cpp @@ -275,15 +275,15 @@ SupplementaryOutputPathsComputer::computeOutputPaths() const { if (InputsAndOutputs.hasPrimaryInputs()) assert(OutputFiles.size() == pathsFromUser->size()); - else if (InputsAndOutputs.isSingleThreadedWMO()) - assert(OutputFiles.size() == pathsFromUser->size() && - pathsFromUser->size() == 1); else { - // Multi-threaded WMO is the exception - assert(OutputFiles.size() == InputsAndOutputs.inputCount() && - pathsFromUser->size() == (InputsAndOutputs.hasInputs() ? 1 : 0)); + if (!InputsAndOutputs.isSingleThreadedWMO()) { + assert(OutputFiles.size() == InputsAndOutputs.inputCount()); + } + assert(pathsFromUser->size() == 1 || + pathsFromUser->size() == InputsAndOutputs.inputCount()); } + // For other cases, process the paths normally std::vector outputPaths; unsigned i = 0; bool hadError = InputsAndOutputs.forEachInputProducingSupplementaryOutput( @@ -380,39 +380,78 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments() options::OPT_emit_module_semantic_info_path); auto optRecordOutput = getSupplementaryFilenamesFromArguments( options::OPT_save_optimization_record_path); + auto silOutput = + getSupplementaryFilenamesFromArguments(options::OPT_sil_output_path); + auto irOutput = + getSupplementaryFilenamesFromArguments(options::OPT_ir_output_path); if (!clangHeaderOutput || !moduleOutput || !moduleDocOutput || !dependenciesFile || !referenceDependenciesFile || !serializedDiagnostics || !loadedModuleTrace || !TBD || - !moduleInterfaceOutput || !privateModuleInterfaceOutput || !packageModuleInterfaceOutput || - !moduleSourceInfoOutput || !moduleSummaryOutput || !abiDescriptorOutput || - !moduleSemanticInfoOutput || !optRecordOutput) { + !moduleInterfaceOutput || !privateModuleInterfaceOutput || + !packageModuleInterfaceOutput || !moduleSourceInfoOutput || + !moduleSummaryOutput || !abiDescriptorOutput || + !moduleSemanticInfoOutput || !optRecordOutput || !silOutput || + !irOutput) { return std::nullopt; } std::vector result; - const unsigned N = - InputsAndOutputs.countOfFilesProducingSupplementaryOutput(); + // In WMO mode with multiple IR output paths, we need to create one + // SupplementaryOutputPaths per input file, not just one for the module + unsigned N = InputsAndOutputs.countOfFilesProducingSupplementaryOutput(); + if (!InputsAndOutputs.hasPrimaryInputs() && irOutput->size() > 1) { + // WMO mode with multiple IR outputs: use input count instead of 1 + N = InputsAndOutputs.inputCount(); + } + + // Find the index of SIL output path matching module name + auto findSILIndexForModuleName = [&]() -> unsigned { + if (!InputsAndOutputs.hasPrimaryInputs() && silOutput->size() > 1) { + // In WMO mode with multiple SIL output paths, find the one whose matches + // module name + for (unsigned i = 0; i < silOutput->size(); ++i) { + StringRef silPath = (*silOutput)[i]; + if (!silPath.empty()) { + StringRef basename = llvm::sys::path::stem(silPath); + if (basename == ModuleName) { + return i; + } + } + } + // If no match found, fall back to first + return 0; + } + return 0; + }; + + unsigned silOutputIndex = findSILIndexForModuleName(); + for (unsigned i = 0; i < N; ++i) { SupplementaryOutputPaths sop; - sop.ClangHeaderOutputPath = (*clangHeaderOutput)[i]; - sop.ModuleOutputPath = (*moduleOutput)[i]; - sop.ModuleDocOutputPath = (*moduleDocOutput)[i]; - sop.DependenciesFilePath = (*dependenciesFile)[i]; - sop.ReferenceDependenciesFilePath = (*referenceDependenciesFile)[i]; - sop.SerializedDiagnosticsPath = (*serializedDiagnostics)[i]; - sop.LoadedModuleTracePath = (*loadedModuleTrace)[i]; - sop.TBDPath = (*TBD)[i]; - sop.ModuleInterfaceOutputPath = (*moduleInterfaceOutput)[i]; - sop.PrivateModuleInterfaceOutputPath = (*privateModuleInterfaceOutput)[i]; - sop.PackageModuleInterfaceOutputPath = (*packageModuleInterfaceOutput)[i]; - sop.ModuleSourceInfoOutputPath = (*moduleSourceInfoOutput)[i]; - sop.ModuleSummaryOutputPath = (*moduleSummaryOutput)[i]; - sop.ABIDescriptorOutputPath = (*abiDescriptorOutput)[i]; - sop.APIDescriptorOutputPath = (*apiDescriptorOutput)[i]; - sop.ConstValuesOutputPath = (*constValuesOutput)[i]; - sop.ModuleSemanticInfoOutputPath = (*moduleSemanticInfoOutput)[i]; - sop.YAMLOptRecordPath = (*optRecordOutput)[i]; - sop.BitstreamOptRecordPath = (*optRecordOutput)[i]; + // In multi-threaded WMO with multiple IR outputs, most supplementary outputs + // are per-module (size 1), only IR is per-file. Use index 0 for module outputs. + unsigned moduleIndex = (!InputsAndOutputs.hasPrimaryInputs() && irOutput->size() > 1) ? 0 : i; + sop.ClangHeaderOutputPath = (*clangHeaderOutput)[moduleIndex]; + sop.ModuleOutputPath = (*moduleOutput)[moduleIndex]; + sop.ModuleDocOutputPath = (*moduleDocOutput)[moduleIndex]; + sop.DependenciesFilePath = (*dependenciesFile)[moduleIndex]; + sop.ReferenceDependenciesFilePath = (*referenceDependenciesFile)[moduleIndex]; + sop.SerializedDiagnosticsPath = (*serializedDiagnostics)[moduleIndex]; + sop.LoadedModuleTracePath = (*loadedModuleTrace)[moduleIndex]; + sop.TBDPath = (*TBD)[moduleIndex]; + sop.ModuleInterfaceOutputPath = (*moduleInterfaceOutput)[moduleIndex]; + sop.PrivateModuleInterfaceOutputPath = (*privateModuleInterfaceOutput)[moduleIndex]; + sop.PackageModuleInterfaceOutputPath = (*packageModuleInterfaceOutput)[moduleIndex]; + sop.ModuleSourceInfoOutputPath = (*moduleSourceInfoOutput)[moduleIndex]; + sop.ModuleSummaryOutputPath = (*moduleSummaryOutput)[moduleIndex]; + sop.ABIDescriptorOutputPath = (*abiDescriptorOutput)[moduleIndex]; + sop.APIDescriptorOutputPath = (*apiDescriptorOutput)[moduleIndex]; + sop.ConstValuesOutputPath = (*constValuesOutput)[moduleIndex]; + sop.ModuleSemanticInfoOutputPath = (*moduleSemanticInfoOutput)[moduleIndex]; + sop.YAMLOptRecordPath = (*optRecordOutput)[moduleIndex]; + sop.BitstreamOptRecordPath = (*optRecordOutput)[moduleIndex]; + sop.SILOutputPath = (*silOutput)[silOutputIndex]; + sop.LLVMIROutputPath = (*irOutput)[i]; result.push_back(sop); } return result; @@ -439,6 +478,15 @@ SupplementaryOutputPathsComputer::getSupplementaryFilenamesFromArguments( paths.emplace_back(); return paths; } + // Special handling for SIL and IR output paths: allow multiple paths per file + // type + else if ((pathID == options::OPT_sil_output_path || + pathID == options::OPT_ir_output_path) && + paths.size() > N) { + // For parallel compilation, we can have multiple SIL/IR output paths + // so return all the paths. + return paths; + } if (paths.empty()) return std::vector(N, std::string()); @@ -613,6 +661,9 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput( file_types::TY_BitstreamOptRecord, "", defaultSupplementaryOutputPathExcludingExtension); + auto SILOutputPath = pathsFromArguments.SILOutputPath; + auto LLVMIROutputPath = pathsFromArguments.LLVMIROutputPath; + SupplementaryOutputPaths sop; sop.ClangHeaderOutputPath = clangHeaderOutputPath; sop.ModuleOutputPath = moduleOutputPath; @@ -635,6 +686,8 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput( sop.ModuleSemanticInfoOutputPath = ModuleSemanticInfoOutputPath; sop.YAMLOptRecordPath = YAMLOptRecordPath; sop.BitstreamOptRecordPath = bitstreamOptRecordPath; + sop.SILOutputPath = SILOutputPath; + sop.LLVMIROutputPath = LLVMIROutputPath; return sop; } @@ -741,18 +794,18 @@ createFromTypeToPathMap(const TypeToPathMap *map) { std::optional> SupplementaryOutputPathsComputer::readSupplementaryOutputFileMap() const { - if (Arg *A = Args.getLastArg(options::OPT_emit_objc_header_path, - options::OPT_emit_module_path, - options::OPT_emit_module_doc_path, - options::OPT_emit_dependencies_path, - options::OPT_emit_reference_dependencies_path, - options::OPT_serialize_diagnostics_path, - options::OPT_emit_loaded_module_trace_path, - options::OPT_emit_module_interface_path, - options::OPT_emit_private_module_interface_path, - options::OPT_emit_package_module_interface_path, - options::OPT_emit_module_source_info_path, - options::OPT_emit_tbd_path)) { + if (Arg *A = Args.getLastArg( + options::OPT_emit_objc_header_path, options::OPT_emit_module_path, + options::OPT_emit_module_doc_path, + options::OPT_emit_dependencies_path, + options::OPT_emit_reference_dependencies_path, + options::OPT_serialize_diagnostics_path, + options::OPT_emit_loaded_module_trace_path, + options::OPT_emit_module_interface_path, + options::OPT_emit_private_module_interface_path, + options::OPT_emit_package_module_interface_path, + options::OPT_emit_module_source_info_path, options::OPT_emit_tbd_path, + options::OPT_sil_output_path, options::OPT_ir_output_path)) { Diags.diagnose(SourceLoc(), diag::error_cannot_have_supplementary_outputs, A->getSpelling(), "-supplementary-output-file-map"); diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index d91148fe0051f..40515b9462076 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -111,6 +111,10 @@ using namespace swift; +static std::vector +collectSupplementaryOutputPaths(ArrayRef Args, + options::ID OptionID); + static std::string displayName(StringRef MainExecutablePath) { std::string Name = llvm::sys::path::stem(MainExecutablePath).str(); Name += " -frontend"; @@ -724,16 +728,14 @@ static bool writeAPIDescriptorIfNeeded(CompilerInstance &Instance) { Instance.getOutputBackend()); } -static bool performCompileStepsPostSILGen(CompilerInstance &Instance, - std::unique_ptr SM, - ModuleOrSourceFile MSF, - const PrimarySpecificPaths &PSPs, - int &ReturnValue, - FrontendObserver *observer); +static bool performCompileStepsPostSILGen( + CompilerInstance &Instance, std::unique_ptr SM, + ModuleOrSourceFile MSF, const PrimarySpecificPaths &PSPs, int &ReturnValue, + FrontendObserver *observer, ArrayRef CommandLineArgs); -bool swift::performCompileStepsPostSema(CompilerInstance &Instance, - int &ReturnValue, - FrontendObserver *observer) { +bool swift::performCompileStepsPostSema( + CompilerInstance &Instance, int &ReturnValue, FrontendObserver *observer, + ArrayRef CommandLineArgs) { const auto &Invocation = Instance.getInvocation(); const FrontendOptions &opts = Invocation.getFrontendOptions(); @@ -778,7 +780,8 @@ bool swift::performCompileStepsPostSema(CompilerInstance &Instance, auto SM = performASTLowering(mod, Instance.getSILTypes(), SILOpts, &irgenOpts); return performCompileStepsPostSILGen(Instance, std::move(SM), mod, PSPs, - ReturnValue, observer); + ReturnValue, observer, + CommandLineArgs); } @@ -797,7 +800,7 @@ bool swift::performCompileStepsPostSema(CompilerInstance &Instance, SILOpts, &irgenOpts); result |= performCompileStepsPostSILGen(Instance, std::move(SM), PrimaryFile, PSPs, ReturnValue, - observer); + observer, CommandLineArgs); } return result; @@ -814,7 +817,8 @@ bool swift::performCompileStepsPostSema(CompilerInstance &Instance, SILOptions SILOpts = getSILOptions(PSPs, emptyAuxPSPs); auto SM = performASTLowering(*SASTF, Instance.getSILTypes(), SILOpts); result |= performCompileStepsPostSILGen(Instance, std::move(SM), mod, - PSPs, ReturnValue, observer); + PSPs, ReturnValue, observer, + CommandLineArgs); } } @@ -1230,9 +1234,9 @@ static bool performParseOnly(ModuleDecl &MainModule) { return MainModule.getASTContext().hadError(); } -static bool performAction(CompilerInstance &Instance, - int &ReturnValue, - FrontendObserver *observer) { +static bool performAction(CompilerInstance &Instance, int &ReturnValue, + FrontendObserver *observer, + ArrayRef CommandLineArgs) { const auto &opts = Instance.getInvocation().getFrontendOptions(); switch (Instance.getInvocation().getFrontendOptions().RequestedAction) { // MARK: Trivial Actions @@ -1322,7 +1326,8 @@ static bool performAction(CompilerInstance &Instance, Instance, observer, [&](CompilerInstance &Instance) { assert(FrontendOptions::doesActionGenerateSIL(opts.RequestedAction) && "All actions not requiring SILGen must have been handled!"); - return performCompileStepsPostSema(Instance, ReturnValue, observer); + return performCompileStepsPostSema(Instance, ReturnValue, observer, + CommandLineArgs); }); } case FrontendOptions::ActionType::EmitSILGen: @@ -1342,7 +1347,8 @@ static bool performAction(CompilerInstance &Instance, Instance, observer, [&](CompilerInstance &Instance) { assert(FrontendOptions::doesActionGenerateSIL(opts.RequestedAction) && "All actions not requiring SILGen must have been handled!"); - return performCompileStepsPostSema(Instance, ReturnValue, observer); + return performCompileStepsPostSema(Instance, ReturnValue, observer, + CommandLineArgs); }); } @@ -1702,9 +1708,9 @@ static bool generateReproducer(CompilerInstance &Instance, /// \param Instance Will be reset after performIRGeneration when the verifier /// mode is NoVerify and there were no errors. /// \returns true on error -static bool performCompile(CompilerInstance &Instance, - int &ReturnValue, - FrontendObserver *observer) { +static bool performCompile(CompilerInstance &Instance, int &ReturnValue, + FrontendObserver *observer, + ArrayRef CommandLineArgs) { const auto &Invocation = Instance.getInvocation(); const auto &opts = Invocation.getFrontendOptions(); const FrontendOptions::ActionType Action = opts.RequestedAction; @@ -1730,7 +1736,8 @@ static bool performCompile(CompilerInstance &Instance, return true; }() && "Only supports parsing .swift files"); - bool hadError = performAction(Instance, ReturnValue, observer); + bool hadError = + performAction(Instance, ReturnValue, observer, CommandLineArgs); auto canIgnoreErrorForExit = [&Instance, &opts]() { return opts.AllowModuleWithCompilerErrors || (opts.isTypeCheckAction() && Instance.downgradeInterfaceVerificationErrors()); @@ -1777,11 +1784,11 @@ static bool serializeModuleSummary(SILModule *SM, static GeneratedModule generateIR(const IRGenOptions &IRGenOpts, const TBDGenOptions &TBDOpts, - std::unique_ptr SM, - const PrimarySpecificPaths &PSPs, + std::unique_ptr SM, const PrimarySpecificPaths &PSPs, StringRef OutputFilename, ModuleOrSourceFile MSF, llvm::GlobalVariable *&HashGlobal, - ArrayRef parallelOutputFilenames) { + ArrayRef parallelOutputFilenames, + ArrayRef parallelIROutputFilenames) { if (auto *SF = MSF.dyn_cast()) { return performIRGeneration(SF, IRGenOpts, TBDOpts, std::move(SM), OutputFilename, PSPs, @@ -1790,7 +1797,8 @@ generateIR(const IRGenOptions &IRGenOpts, const TBDGenOptions &TBDOpts, } else { return performIRGeneration(cast(MSF), IRGenOpts, TBDOpts, std::move(SM), OutputFilename, PSPs, - parallelOutputFilenames, &HashGlobal); + parallelOutputFilenames, + parallelIROutputFilenames, &HashGlobal); } } @@ -1972,12 +1980,10 @@ static bool generateCode(CompilerInstance &Instance, StringRef OutputFilename, Instance.getStatsReporter()); } -static bool performCompileStepsPostSILGen(CompilerInstance &Instance, - std::unique_ptr SM, - ModuleOrSourceFile MSF, - const PrimarySpecificPaths &PSPs, - int &ReturnValue, - FrontendObserver *observer) { +static bool performCompileStepsPostSILGen( + CompilerInstance &Instance, std::unique_ptr SM, + ModuleOrSourceFile MSF, const PrimarySpecificPaths &PSPs, int &ReturnValue, + FrontendObserver *observer, ArrayRef CommandLineArgs) { const auto &Invocation = Instance.getInvocation(); const auto &opts = Invocation.getFrontendOptions(); FrontendOptions::ActionType Action = opts.RequestedAction; @@ -2102,6 +2108,14 @@ static bool performCompileStepsPostSILGen(CompilerInstance &Instance, if (Action == FrontendOptions::ActionType::EmitSIL) return writeSIL(*SM, PSPs, Instance, Invocation.getSILOptions()); + // Write extra SIL output if requested + if (!PSPs.SupplementaryOutputs.SILOutputPath.empty()) { + if (writeSIL(*SM, Instance.getMainModule(), Invocation.getSILOptions(), + PSPs.SupplementaryOutputs.SILOutputPath, + Instance.getOutputBackend())) + return true; + } + assert(Action >= FrontendOptions::ActionType::Immediate && "All actions not requiring IRGen must have been handled!"); assert(Action != FrontendOptions::ActionType::REPL && @@ -2141,10 +2155,28 @@ static bool performCompileStepsPostSILGen(CompilerInstance &Instance, StringRef OutputFilename = PSPs.OutputFilename; std::vector ParallelOutputFilenames = opts.InputsAndOutputs.copyOutputFilenames(); + + // Collect IR output paths from command line arguments + std::vector ParallelIROutputFilenames = + collectSupplementaryOutputPaths(CommandLineArgs, + options::OPT_ir_output_path); + llvm::GlobalVariable *HashGlobal; auto IRModule = generateIR(IRGenOpts, Invocation.getTBDGenOptions(), std::move(SM), PSPs, - OutputFilename, MSF, HashGlobal, ParallelOutputFilenames); + OutputFilename, MSF, HashGlobal, ParallelOutputFilenames, + ParallelIROutputFilenames); + + // Write extra LLVM IR output if requested + if (IRModule && !PSPs.SupplementaryOutputs.LLVMIROutputPath.empty()) { + if (withOutputPath(Instance.getDiags(), Instance.getOutputBackend(), + PSPs.SupplementaryOutputs.LLVMIROutputPath, + [&](raw_ostream &out) -> bool { + IRModule.getModule()->print(out, nullptr); + return false; + })) + return true; + } // Cancellation check after IRGen. if (Instance.isCancellationRequested()) @@ -2265,6 +2297,32 @@ swift_ASTGen_printStaticBuildConfiguration(BridgedLangOptions cLangOpts); #pragma clang diagnostic pop #endif +static std::vector +collectSupplementaryOutputPaths(ArrayRef Args, + options::ID OptionID) { + std::vector paths; + + for (size_t i = 0; i < Args.size(); ++i) { + StringRef arg = Args[i]; + StringRef optionName; + + if (OptionID == options::OPT_sil_output_path) { + optionName = "-sil-output-path"; + } else if (OptionID == options::OPT_ir_output_path) { + optionName = "-ir-output-path"; + } else { + continue; + } + + if (arg == optionName && i + 1 < Args.size()) { + paths.push_back(Args[i + 1]); + ++i; + } + } + + return paths; +} + int swift::performFrontend(ArrayRef Args, const char *Argv0, void *MainAddr, FrontendObserver *observer) { @@ -2468,13 +2526,13 @@ int swift::performFrontend(ArrayRef Args, // Run the first time without observer and discard return value; int ReturnValueTest = 0; (void)performCompile(*VerifyInstance, ReturnValueTest, - /*observer*/ nullptr); + /*observer*/ nullptr, Args); // Get the hashing output backend and free the compiler instance. HashBackend = VerifyInstance->getHashingBackend(); } int ReturnValue = 0; - bool HadError = performCompile(*Instance, ReturnValue, observer); + bool HadError = performCompile(*Instance, ReturnValue, observer, Args); if (verifierEnabled) { DiagnosticEngine &diags = Instance->getDiags(); diff --git a/lib/IDETool/CompileInstance.cpp b/lib/IDETool/CompileInstance.cpp index 8392591793243..d1467790041d1 100644 --- a/lib/IDETool/CompileInstance.cpp +++ b/lib/IDETool/CompileInstance.cpp @@ -377,7 +377,8 @@ bool CompileInstance::performCompile( CI->addDiagnosticConsumer(DiagC); SWIFT_DEFER { CI->removeDiagnosticConsumer(DiagC); }; int ReturnValue = 0; - return performCompileStepsPostSema(*CI, ReturnValue, /*observer=*/nullptr); + return performCompileStepsPostSema(*CI, ReturnValue, /*observer=*/nullptr, + Args); } bool CompileInstance::shouldCheckDependencies() const { diff --git a/lib/IRGen/IRGen.cpp b/lib/IRGen/IRGen.cpp index 2566667e69027..9543db5115133 100644 --- a/lib/IRGen/IRGen.cpp +++ b/lib/IRGen/IRGen.cpp @@ -1773,6 +1773,37 @@ static void performParallelIRGeneration(IRGenDescriptor desc) { } } + // Write IR outputs if requested + // In WMO mode, IR generation creates separate modules per source file + auto irOutputFilenames = desc.parallelIROutputFilenames; + + if (!irOutputFilenames.empty()) { + auto IRIter = irOutputFilenames.begin(); + + for (auto *File : M->getFiles()) { + auto nextSF = dyn_cast(File); + if (!nextSF) + continue; + + // Write IR output for this source file + if (IRIter != irOutputFilenames.end()) { + auto irOutputPath = *IRIter++; + if (!irOutputPath.empty()) { + CurrentIGMPtr IGM = irgen.getGenModule(nextSF); + std::error_code EC; + llvm::raw_fd_ostream irOut(irOutputPath, EC); + if (!EC) { + IGM->getModule()->print(irOut, nullptr); + irOut.close(); + } else { + Ctx.Diags.diagnose(SourceLoc(), diag::error_opening_output, + irOutputPath, EC.message()); + } + } + } + } + } + // Bail out if there are any errors. if (Ctx.hadError()) return; @@ -1804,6 +1835,7 @@ GeneratedModule swift::performIRGeneration( const TBDGenOptions &TBDOpts, std::unique_ptr SILMod, StringRef ModuleName, const PrimarySpecificPaths &PSPs, ArrayRef parallelOutputFilenames, + ArrayRef parallelIROutputFilenames, llvm::GlobalVariable **outModuleHash) { // Get a pointer to the SILModule to avoid a potential use-after-move. const auto *SILModPtr = SILMod.get(); @@ -1811,7 +1843,7 @@ GeneratedModule swift::performIRGeneration( auto desc = IRGenDescriptor::forWholeModule( M, Opts, TBDOpts, SILOpts, SILModPtr->Types, std::move(SILMod), ModuleName, PSPs, /*symsToEmit*/ std::nullopt, parallelOutputFilenames, - outModuleHash); + parallelIROutputFilenames, outModuleHash); if (Opts.shouldPerformIRGenerationInParallel() && !parallelOutputFilenames.empty() && diff --git a/lib/Immediate/SwiftMaterializationUnit.cpp b/lib/Immediate/SwiftMaterializationUnit.cpp index 0465188f2b270..b22f26b59f3ca 100644 --- a/lib/Immediate/SwiftMaterializationUnit.cpp +++ b/lib/Immediate/SwiftMaterializationUnit.cpp @@ -262,7 +262,8 @@ generateModule(const CompilerInstance &CI, std::unique_ptr SM) { // Lower the SIL module to LLVM IR auto GenModule = performIRGeneration( swiftModule, IRGenOpts, TBDOpts, std::move(SM), - swiftModule->getName().str(), PSPs, ArrayRef()); + swiftModule->getName().str(), PSPs, ArrayRef(), + /*parallelIROutputFilenames*/ ArrayRef()); if (Context.hadError()) { return std::nullopt; diff --git a/test/Frontend/ir-output-path.swift b/test/Frontend/ir-output-path.swift new file mode 100644 index 0000000000000..b1d852501a2a5 --- /dev/null +++ b/test/Frontend/ir-output-path.swift @@ -0,0 +1,14 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-object -ir-output-path %t/test.ll %s -o %t/test.o +// RUN: %FileCheck -input-file %t/test.ll %s --check-prefix=IR-CHECK +// RUN: test -f %t/test.o + +// Test that -ir-output-path produces LLVM IR output alongside normal compilation + +func testFunction() -> Int { + return 42 +} + +let _ = testFunction() + +// IR-CHECK: @"$s4test0A8FunctionSiyF" diff --git a/test/Frontend/output-file-map-sil-ir.swift b/test/Frontend/output-file-map-sil-ir.swift new file mode 100644 index 0000000000000..205977fae6ed0 --- /dev/null +++ b/test/Frontend/output-file-map-sil-ir.swift @@ -0,0 +1,46 @@ +// RUN: %empty-directory(%t) + +// Test that SIL and IR files can be requested via output file map + +// Test primary file compilation with both SIL and IR in file map +// RUN: echo '{"%/s": {"sil": "%/t/primary.sil", "llvm-ir": "%/t/primary.ll"}, "%/S/../Driver/Inputs/main.swift": {"sil": "%/t/main.sil", "llvm-ir": "%/t/main.ll"}}' > %t/multi-map.json +// RUN: %target-swift-frontend -emit-object -supplementary-output-file-map %t/multi-map.json -primary-file %/s %/S/../Driver/Inputs/main.swift -o %t/primary.o -module-name test +// RUN: test -f %t/primary.sil && test -f %t/primary.ll && test -f %t/primary.o +// RUN: test ! -f %t/main.sil && test ! -f %t/main.ll +// RUN: %FileCheck -input-file %t/primary.sil %s --check-prefix=SIL-CHECK +// RUN: %FileCheck -input-file %t/primary.ll %s --check-prefix=IR-CHECK + +// Test switching primary files - same map, different primary file +// RUN: %target-swift-frontend -emit-object -supplementary-output-file-map %t/multi-map.json -primary-file %/S/../Driver/Inputs/main.swift %/s -o %t/main-primary.o -module-name test +// RUN: test -f %t/main.sil && test -f %t/main.ll && test -f %t/main-primary.o +// RUN: %FileCheck -input-file %t/main.sil %s --check-prefix=MAIN-SIL-CHECK +// RUN: %FileCheck -input-file %t/main.ll %s --check-prefix=MAIN-IR-CHECK + +// Test partial file maps: SIL-only and IR-only in one test +// RUN: echo '{"%/s": {"sil": "%/t/partial.sil", "llvm-ir": "%/t/partial.ll"}}' > %t/partial-map.json +// RUN: %target-swift-frontend -emit-object -supplementary-output-file-map %t/partial-map.json %/s -o %t/partial.o -module-name test +// RUN: test -f %t/partial.sil && test -f %t/partial.ll && test -f %t/partial.o +// RUN: %FileCheck -input-file %t/partial.sil %s --check-prefix=SIL-CHECK +// RUN: %FileCheck -input-file %t/partial.ll %s --check-prefix=IR-CHECK + +func testFunction() -> Int { + return 42 +} + +func runTest() { + _ = testFunction() +} + +// Function expected by main.swift +func libraryFunction() {} + +// For module-qualified access +struct ThisModule { + static func libraryFunction() {} +} + +// SIL-CHECK: sil hidden @$s4test0A8FunctionSiyF : $@convention(thin) () -> Int +// IR-CHECK: @"$s4test0A8FunctionSiyF" + +// MAIN-SIL-CHECK: sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 +// MAIN-IR-CHECK: define{{.*}} i32 @main( diff --git a/test/Frontend/sil-output-path.swift b/test/Frontend/sil-output-path.swift new file mode 100644 index 0000000000000..2f110ddf10254 --- /dev/null +++ b/test/Frontend/sil-output-path.swift @@ -0,0 +1,12 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-object -sil-output-path %t/test.sil %s -o %t/test.o +// RUN: %FileCheck -input-file %t/test.sil %s --check-prefix=SIL-CHECK +// RUN: test -f %t/test.o + +// Test that -sil-output-path produces SIL output alongside normal compilation + +func testFunction() -> Int { + return 42 +} + +// SIL-CHECK: sil hidden @$s4test0A8FunctionSiyF : $@convention(thin) () -> Int diff --git a/test/Frontend/wmo-supplementary-outputs.swift b/test/Frontend/wmo-supplementary-outputs.swift new file mode 100644 index 0000000000000..8b8bc4907a004 --- /dev/null +++ b/test/Frontend/wmo-supplementary-outputs.swift @@ -0,0 +1,36 @@ +// RUN: %empty-directory(%t) + +// Test WMO supplementary output functionality + +// Test SIL consolidates, IR separates in multi-threaded WMO +// RUN: %target-swift-frontend -wmo -num-threads 4 %S/../Driver/Inputs/main.swift %s -module-name=ThisModule -c -o %t/main.o -o %t/multi-threaded.o -sil-output-path %t/mt-wmo.sil -ir-output-path %t/main.ll -ir-output-path %t/multi-threaded.ll +// RUN: test -f %t/mt-wmo.sil && test -f %t/main.ll && test -f %t/multi-threaded.ll && test -f %t/main.o && test -f %t/multi-threaded.o +// RUN: %FileCheck -input-file %t/mt-wmo.sil %s --check-prefix=SIL-CHECK +// RUN: %FileCheck -input-file %t/main.ll %s --check-prefix=IR-CHECK-MAIN +// RUN: %FileCheck -input-file %t/multi-threaded.ll %s --check-prefix=IR-CHECK + +// MARK: Single-threaded WMO tests - Both SIL and IR consolidate + +// Test single-threaded WMO: both SIL and IR produce consolidated output +// RUN: %target-swift-frontend -wmo %S/../Driver/Inputs/main.swift %s -module-name=ThisModule -c -o %t/st-main.o -sil-output-path %t/st-wmo.sil -ir-output-path %t/st-wmo.ll +// RUN: test -f %t/st-wmo.sil && test -f %t/st-wmo.ll && test -f %t/st-main.o +// RUN: %FileCheck -input-file %t/st-wmo.sil %s --check-prefix=SIL-CHECK +// RUN: %FileCheck -input-file %t/st-wmo.ll %s --check-prefix=IR-CHECK + +// MARK: WMO with supplementary output file maps - First entry consolidation + +// Test file map consolidation: both SIL and IR use first entry naming with consolidated content +// RUN: echo '{"%/S/../Driver/Inputs/main.swift": {"sil": "%/t/map.sil", "llvm-ir": "%/t/map.ll"}, "%/s": {"sil": "%/t/unused.sil", "llvm-ir": "%/t/unused.ll"}}' > %t/map.json +// RUN: %target-swift-frontend -wmo %/S/../Driver/Inputs/main.swift %/s -module-name=ThisModule -c -o %t/map.o -supplementary-output-file-map %t/map.json +// RUN: test -f %t/map.sil && test -f %t/map.ll && test -f %t/map.o +// RUN: test ! -f %t/unused.sil && test ! -f %t/unused.ll +// RUN: %FileCheck -input-file %t/map.sil %s --check-prefix=SIL-CHECK +// RUN: %FileCheck -input-file %t/map.ll %s --check-prefix=IR-CHECK + +// SIL-CHECK: sil {{.*}} @$s10ThisModule15libraryFunctionyyF + +// IR-CHECK: @"$s10ThisModule15libraryFunctionyyF" + +// IR-CHECK-MAIN: define{{.*}} i32 @main( + +func libraryFunction() {}