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
This commit is contained in:
Ryan Mansfield
2025-09-18 15:53:31 -04:00
parent 5b4c5ebed4
commit ba0ce8aea6
15 changed files with 372 additions and 83 deletions

View File

@@ -151,6 +151,7 @@ struct IRGenDescriptor {
const PrimarySpecificPaths &PSPs;
StringRef PrivateDiscriminator;
ArrayRef<std::string> parallelOutputFilenames;
ArrayRef<std::string> parallelIROutputFilenames;
llvm::GlobalVariable **outModuleHash;
llvm::raw_pwrite_stream *out = nullptr;
@@ -188,6 +189,7 @@ public:
PSPs,
PrivateDiscriminator,
{},
{},
outModuleHash};
}
@@ -197,6 +199,7 @@ public:
std::unique_ptr<SILModule> &&SILMod, StringRef ModuleName,
const PrimarySpecificPaths &PSPs, SymsToEmit symsToEmit = std::nullopt,
ArrayRef<std::string> parallelOutputFilenames = {},
ArrayRef<std::string> parallelIROutputFilenames = {},
llvm::GlobalVariable **outModuleHash = nullptr) {
return IRGenDescriptor{M,
symsToEmit,
@@ -209,6 +212,7 @@ public:
PSPs,
"",
parallelOutputFilenames,
parallelIROutputFilenames,
outModuleHash};
}

View File

@@ -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)

View File

@@ -79,7 +79,8 @@ int performFrontend(ArrayRef<const char *> args,
FrontendObserver *observer = nullptr);
bool performCompileStepsPostSema(CompilerInstance &Instance, int &ReturnValue,
FrontendObserver *observer);
FrontendObserver *observer,
ArrayRef<const char *> CommandLineArgs);
} // namespace swift

View File

@@ -63,6 +63,18 @@ def emit_module_semantic_info_path
: Separate<["-"], "emit-module-semantic-info-path">, MetaVarName<"<path>">,
HelpText<"Output semantic info of current module to <path>">;
def sil_output_path
: Separate<["-"], "sil-output-path">, MetaVarName<"<path>">,
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
SupplementaryOutput, CacheInvariant]>,
HelpText<"Output SIL to <path> as additional output during compilation">;
def ir_output_path
: Separate<["-"], "ir-output-path">, MetaVarName<"<path>">,
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
SupplementaryOutput, CacheInvariant]>,
HelpText<"Output LLVM IR to <path> as additional output during compilation">;
def diagnostic_documentation_path
: Separate<["-"], "diagnostic-documentation-path">, MetaVarName<"<path>">,
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.">;

View File

@@ -250,9 +250,10 @@ namespace swift {
GeneratedModule
performIRGeneration(ModuleDecl *M, const IRGenOptions &Opts,
const TBDGenOptions &TBDOpts,
std::unique_ptr<SILModule> SILMod,
StringRef ModuleName, const PrimarySpecificPaths &PSPs,
std::unique_ptr<SILModule> SILMod, StringRef ModuleName,
const PrimarySpecificPaths &PSPs,
ArrayRef<std::string> parallelOutputFilenames,
ArrayRef<std::string> parallelIROutputFilenames,
llvm::GlobalVariable **outModuleHash = nullptr);
/// Turn the given Swift file into LLVM IR and return the generated module.

View File

@@ -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);

View File

@@ -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<SupplementaryOutputPaths> 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<SupplementaryOutputPaths> 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<std::string>(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<std::vector<SupplementaryOutputPaths>>
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");

View File

@@ -111,6 +111,10 @@
using namespace swift;
static std::vector<std::string>
collectSupplementaryOutputPaths(ArrayRef<const char *> 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<SILModule> SM,
ModuleOrSourceFile MSF,
const PrimarySpecificPaths &PSPs,
int &ReturnValue,
FrontendObserver *observer);
static bool performCompileStepsPostSILGen(
CompilerInstance &Instance, std::unique_ptr<SILModule> SM,
ModuleOrSourceFile MSF, const PrimarySpecificPaths &PSPs, int &ReturnValue,
FrontendObserver *observer, ArrayRef<const char *> CommandLineArgs);
bool swift::performCompileStepsPostSema(CompilerInstance &Instance,
int &ReturnValue,
FrontendObserver *observer) {
bool swift::performCompileStepsPostSema(
CompilerInstance &Instance, int &ReturnValue, FrontendObserver *observer,
ArrayRef<const char *> 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<const char *> 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<const char *> 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<SILModule> SM,
const PrimarySpecificPaths &PSPs,
std::unique_ptr<SILModule> SM, const PrimarySpecificPaths &PSPs,
StringRef OutputFilename, ModuleOrSourceFile MSF,
llvm::GlobalVariable *&HashGlobal,
ArrayRef<std::string> parallelOutputFilenames) {
ArrayRef<std::string> parallelOutputFilenames,
ArrayRef<std::string> parallelIROutputFilenames) {
if (auto *SF = MSF.dyn_cast<SourceFile *>()) {
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<ModuleDecl *>(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<SILModule> SM,
ModuleOrSourceFile MSF,
const PrimarySpecificPaths &PSPs,
int &ReturnValue,
FrontendObserver *observer) {
static bool performCompileStepsPostSILGen(
CompilerInstance &Instance, std::unique_ptr<SILModule> SM,
ModuleOrSourceFile MSF, const PrimarySpecificPaths &PSPs, int &ReturnValue,
FrontendObserver *observer, ArrayRef<const char *> 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<std::string> ParallelOutputFilenames =
opts.InputsAndOutputs.copyOutputFilenames();
// Collect IR output paths from command line arguments
std::vector<std::string> 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<std::string>
collectSupplementaryOutputPaths(ArrayRef<const char *> Args,
options::ID OptionID) {
std::vector<std::string> 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<const char *> Args,
const char *Argv0, void *MainAddr,
FrontendObserver *observer) {
@@ -2468,13 +2526,13 @@ int swift::performFrontend(ArrayRef<const char *> 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();

View File

@@ -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 {

View File

@@ -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<SourceFile>(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<SILModule> SILMod,
StringRef ModuleName, const PrimarySpecificPaths &PSPs,
ArrayRef<std::string> parallelOutputFilenames,
ArrayRef<std::string> 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() &&

View File

@@ -262,7 +262,8 @@ generateModule(const CompilerInstance &CI, std::unique_ptr<SILModule> SM) {
// Lower the SIL module to LLVM IR
auto GenModule = performIRGeneration(
swiftModule, IRGenOpts, TBDOpts, std::move(SM),
swiftModule->getName().str(), PSPs, ArrayRef<std::string>());
swiftModule->getName().str(), PSPs, ArrayRef<std::string>(),
/*parallelIROutputFilenames*/ ArrayRef<std::string>());
if (Context.hadError()) {
return std::nullopt;

View File

@@ -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"

View File

@@ -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<Optional<UnsafeMutablePointer<Int8>>>) -> Int32
// MAIN-IR-CHECK: define{{.*}} i32 @main(

View File

@@ -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

View File

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