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() {}