Skip to content

Commit 7c4614a

Browse files
committed
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
1 parent c6a9226 commit 7c4614a

15 files changed

+372
-83
lines changed

include/swift/AST/IRGenRequests.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ struct IRGenDescriptor {
151151
const PrimarySpecificPaths &PSPs;
152152
StringRef PrivateDiscriminator;
153153
ArrayRef<std::string> parallelOutputFilenames;
154+
ArrayRef<std::string> parallelIROutputFilenames;
154155
llvm::GlobalVariable **outModuleHash;
155156
llvm::raw_pwrite_stream *out = nullptr;
156157

@@ -188,6 +189,7 @@ struct IRGenDescriptor {
188189
PSPs,
189190
PrivateDiscriminator,
190191
{},
192+
{},
191193
outModuleHash};
192194
}
193195

@@ -197,6 +199,7 @@ struct IRGenDescriptor {
197199
std::unique_ptr<SILModule> &&SILMod, StringRef ModuleName,
198200
const PrimarySpecificPaths &PSPs, SymsToEmit symsToEmit = std::nullopt,
199201
ArrayRef<std::string> parallelOutputFilenames = {},
202+
ArrayRef<std::string> parallelIROutputFilenames = {},
200203
llvm::GlobalVariable **outModuleHash = nullptr) {
201204
return IRGenDescriptor{M,
202205
symsToEmit,
@@ -209,6 +212,7 @@ struct IRGenDescriptor {
209212
PSPs,
210213
"",
211214
parallelOutputFilenames,
215+
parallelIROutputFilenames,
212216
outModuleHash};
213217
}
214218

include/swift/Basic/SupplementaryOutputPaths.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,9 @@ OUTPUT(YAMLOptRecordPath, TY_YAMLOptRecord)
166166

167167
/// The output path for bitstream optimization record file.
168168
OUTPUT(BitstreamOptRecordPath, TY_BitstreamOptRecord)
169+
170+
/// The output path to which we should output SIL as extra compilation output.
171+
OUTPUT(SILOutputPath, TY_SIL)
172+
173+
/// The output path to which we should output LLVM IR as extra compilation output.
174+
OUTPUT(LLVMIROutputPath, TY_LLVM_IR)

include/swift/FrontendTool/FrontendTool.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ int performFrontend(ArrayRef<const char *> args,
7979
FrontendObserver *observer = nullptr);
8080

8181
bool performCompileStepsPostSema(CompilerInstance &Instance, int &ReturnValue,
82-
FrontendObserver *observer);
82+
FrontendObserver *observer,
83+
ArrayRef<const char *> CommandLineArgs);
8384

8485
} // namespace swift
8586

include/swift/Option/FrontendOptions.td

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ def emit_module_semantic_info_path
6363
: Separate<["-"], "emit-module-semantic-info-path">, MetaVarName<"<path>">,
6464
HelpText<"Output semantic info of current module to <path>">;
6565

66+
def sil_output_path
67+
: Separate<["-"], "sil-output-path">, MetaVarName<"<path>">,
68+
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
69+
SupplementaryOutput, CacheInvariant]>,
70+
HelpText<"Output SIL to <path> as additional output during compilation">;
71+
72+
def ir_output_path
73+
: Separate<["-"], "ir-output-path">, MetaVarName<"<path>">,
74+
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
75+
SupplementaryOutput, CacheInvariant]>,
76+
HelpText<"Output LLVM IR to <path> as additional output during compilation">;
77+
6678
def diagnostic_documentation_path
6779
: Separate<["-"], "diagnostic-documentation-path">, MetaVarName<"<path>">,
6880
HelpText<"Path to diagnostic documentation resources">;
@@ -266,7 +278,7 @@ def serialize_dependency_scan_cache : Flag<["-"], "serialize-dependency-scan-cac
266278

267279
def reuse_dependency_scan_cache : Flag<["-"], "load-dependency-scan-cache">,
268280
HelpText<"For performing a dependency scan, deserialize the scanner's internal state from a prior scan.">;
269-
281+
270282
def validate_prior_dependency_scan_cache : Flag<["-"], "validate-prior-dependency-scan-cache">,
271283
HelpText<"For performing a dependency scan with a prior scanner state, validate module dependencies.">;
272284

include/swift/Subsystems.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,10 @@ namespace swift {
250250
GeneratedModule
251251
performIRGeneration(ModuleDecl *M, const IRGenOptions &Opts,
252252
const TBDGenOptions &TBDOpts,
253-
std::unique_ptr<SILModule> SILMod,
254-
StringRef ModuleName, const PrimarySpecificPaths &PSPs,
253+
std::unique_ptr<SILModule> SILMod, StringRef ModuleName,
254+
const PrimarySpecificPaths &PSPs,
255255
ArrayRef<std::string> parallelOutputFilenames,
256+
ArrayRef<std::string> parallelIROutputFilenames,
256257
llvm::GlobalVariable **outModuleHash = nullptr);
257258

258259
/// Turn the given Swift file into LLVM IR and return the generated module.

lib/Driver/ToolChains.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,12 @@ void ToolChain::JobContext::addFrontendSupplementaryOutputArguments(
953953
addOutputsOfType(arguments, Output, Args,
954954
file_types::TY_SwiftModuleSummaryFile,
955955
"-emit-module-summary-path");
956+
957+
// Add extra output paths for SIL and LLVM IR
958+
addOutputsOfType(arguments, Output, Args, file_types::TY_SIL,
959+
"-sil-output-path");
960+
addOutputsOfType(arguments, Output, Args, file_types::TY_LLVM_IR,
961+
"-ir-output-path");
956962
}
957963

958964
ToolChain::InvocationInfo
@@ -1237,6 +1243,12 @@ ToolChain::constructInvocation(const MergeModuleJobAction &job,
12371243
addOutputsOfType(Arguments, context.Output, context.Args, file_types::TY_TBD,
12381244
"-emit-tbd-path");
12391245

1246+
// Add extra output paths for SIL and LLVM IR
1247+
addOutputsOfType(Arguments, context.Output, context.Args, file_types::TY_SIL,
1248+
"-sil-output-path");
1249+
addOutputsOfType(Arguments, context.Output, context.Args,
1250+
file_types::TY_LLVM_IR, "-ir-output-path");
1251+
12401252
context.Args.AddLastArg(Arguments, options::OPT_emit_symbol_graph);
12411253
context.Args.AddLastArg(Arguments, options::OPT_emit_symbol_graph_dir);
12421254
context.Args.AddLastArg(Arguments, options::OPT_include_spi_symbols);

lib/Frontend/ArgsToFrontendOutputsConverter.cpp

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -275,15 +275,15 @@ SupplementaryOutputPathsComputer::computeOutputPaths() const {
275275

276276
if (InputsAndOutputs.hasPrimaryInputs())
277277
assert(OutputFiles.size() == pathsFromUser->size());
278-
else if (InputsAndOutputs.isSingleThreadedWMO())
279-
assert(OutputFiles.size() == pathsFromUser->size() &&
280-
pathsFromUser->size() == 1);
281278
else {
282-
// Multi-threaded WMO is the exception
283-
assert(OutputFiles.size() == InputsAndOutputs.inputCount() &&
284-
pathsFromUser->size() == (InputsAndOutputs.hasInputs() ? 1 : 0));
279+
if (!InputsAndOutputs.isSingleThreadedWMO()) {
280+
assert(OutputFiles.size() == InputsAndOutputs.inputCount());
281+
}
282+
assert(pathsFromUser->size() == 1 ||
283+
pathsFromUser->size() == InputsAndOutputs.inputCount());
285284
}
286285

286+
// For other cases, process the paths normally
287287
std::vector<SupplementaryOutputPaths> outputPaths;
288288
unsigned i = 0;
289289
bool hadError = InputsAndOutputs.forEachInputProducingSupplementaryOutput(
@@ -380,39 +380,78 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments()
380380
options::OPT_emit_module_semantic_info_path);
381381
auto optRecordOutput = getSupplementaryFilenamesFromArguments(
382382
options::OPT_save_optimization_record_path);
383+
auto silOutput =
384+
getSupplementaryFilenamesFromArguments(options::OPT_sil_output_path);
385+
auto irOutput =
386+
getSupplementaryFilenamesFromArguments(options::OPT_ir_output_path);
383387
if (!clangHeaderOutput || !moduleOutput || !moduleDocOutput ||
384388
!dependenciesFile || !referenceDependenciesFile ||
385389
!serializedDiagnostics || !loadedModuleTrace || !TBD ||
386-
!moduleInterfaceOutput || !privateModuleInterfaceOutput || !packageModuleInterfaceOutput ||
387-
!moduleSourceInfoOutput || !moduleSummaryOutput || !abiDescriptorOutput ||
388-
!moduleSemanticInfoOutput || !optRecordOutput) {
390+
!moduleInterfaceOutput || !privateModuleInterfaceOutput ||
391+
!packageModuleInterfaceOutput || !moduleSourceInfoOutput ||
392+
!moduleSummaryOutput || !abiDescriptorOutput ||
393+
!moduleSemanticInfoOutput || !optRecordOutput || !silOutput ||
394+
!irOutput) {
389395
return std::nullopt;
390396
}
391397
std::vector<SupplementaryOutputPaths> result;
392398

393-
const unsigned N =
394-
InputsAndOutputs.countOfFilesProducingSupplementaryOutput();
399+
// In WMO mode with multiple IR output paths, we need to create one
400+
// SupplementaryOutputPaths per input file, not just one for the module
401+
unsigned N = InputsAndOutputs.countOfFilesProducingSupplementaryOutput();
402+
if (!InputsAndOutputs.hasPrimaryInputs() && irOutput->size() > 1) {
403+
// WMO mode with multiple IR outputs: use input count instead of 1
404+
N = InputsAndOutputs.inputCount();
405+
}
406+
407+
// Find the index of SIL output path matching module name
408+
auto findSILIndexForModuleName = [&]() -> unsigned {
409+
if (!InputsAndOutputs.hasPrimaryInputs() && silOutput->size() > 1) {
410+
// In WMO mode with multiple SIL output paths, find the one whose matches
411+
// module name
412+
for (unsigned i = 0; i < silOutput->size(); ++i) {
413+
StringRef silPath = (*silOutput)[i];
414+
if (!silPath.empty()) {
415+
StringRef basename = llvm::sys::path::stem(silPath);
416+
if (basename == ModuleName) {
417+
return i;
418+
}
419+
}
420+
}
421+
// If no match found, fall back to first
422+
return 0;
423+
}
424+
return 0;
425+
};
426+
427+
unsigned silOutputIndex = findSILIndexForModuleName();
428+
395429
for (unsigned i = 0; i < N; ++i) {
396430
SupplementaryOutputPaths sop;
397-
sop.ClangHeaderOutputPath = (*clangHeaderOutput)[i];
398-
sop.ModuleOutputPath = (*moduleOutput)[i];
399-
sop.ModuleDocOutputPath = (*moduleDocOutput)[i];
400-
sop.DependenciesFilePath = (*dependenciesFile)[i];
401-
sop.ReferenceDependenciesFilePath = (*referenceDependenciesFile)[i];
402-
sop.SerializedDiagnosticsPath = (*serializedDiagnostics)[i];
403-
sop.LoadedModuleTracePath = (*loadedModuleTrace)[i];
404-
sop.TBDPath = (*TBD)[i];
405-
sop.ModuleInterfaceOutputPath = (*moduleInterfaceOutput)[i];
406-
sop.PrivateModuleInterfaceOutputPath = (*privateModuleInterfaceOutput)[i];
407-
sop.PackageModuleInterfaceOutputPath = (*packageModuleInterfaceOutput)[i];
408-
sop.ModuleSourceInfoOutputPath = (*moduleSourceInfoOutput)[i];
409-
sop.ModuleSummaryOutputPath = (*moduleSummaryOutput)[i];
410-
sop.ABIDescriptorOutputPath = (*abiDescriptorOutput)[i];
411-
sop.APIDescriptorOutputPath = (*apiDescriptorOutput)[i];
412-
sop.ConstValuesOutputPath = (*constValuesOutput)[i];
413-
sop.ModuleSemanticInfoOutputPath = (*moduleSemanticInfoOutput)[i];
414-
sop.YAMLOptRecordPath = (*optRecordOutput)[i];
415-
sop.BitstreamOptRecordPath = (*optRecordOutput)[i];
431+
// In multi-threaded WMO with multiple IR outputs, most supplementary outputs
432+
// are per-module (size 1), only IR is per-file. Use index 0 for module outputs.
433+
unsigned moduleIndex = (!InputsAndOutputs.hasPrimaryInputs() && irOutput->size() > 1) ? 0 : i;
434+
sop.ClangHeaderOutputPath = (*clangHeaderOutput)[moduleIndex];
435+
sop.ModuleOutputPath = (*moduleOutput)[moduleIndex];
436+
sop.ModuleDocOutputPath = (*moduleDocOutput)[moduleIndex];
437+
sop.DependenciesFilePath = (*dependenciesFile)[moduleIndex];
438+
sop.ReferenceDependenciesFilePath = (*referenceDependenciesFile)[moduleIndex];
439+
sop.SerializedDiagnosticsPath = (*serializedDiagnostics)[moduleIndex];
440+
sop.LoadedModuleTracePath = (*loadedModuleTrace)[moduleIndex];
441+
sop.TBDPath = (*TBD)[moduleIndex];
442+
sop.ModuleInterfaceOutputPath = (*moduleInterfaceOutput)[moduleIndex];
443+
sop.PrivateModuleInterfaceOutputPath = (*privateModuleInterfaceOutput)[moduleIndex];
444+
sop.PackageModuleInterfaceOutputPath = (*packageModuleInterfaceOutput)[moduleIndex];
445+
sop.ModuleSourceInfoOutputPath = (*moduleSourceInfoOutput)[moduleIndex];
446+
sop.ModuleSummaryOutputPath = (*moduleSummaryOutput)[moduleIndex];
447+
sop.ABIDescriptorOutputPath = (*abiDescriptorOutput)[moduleIndex];
448+
sop.APIDescriptorOutputPath = (*apiDescriptorOutput)[moduleIndex];
449+
sop.ConstValuesOutputPath = (*constValuesOutput)[moduleIndex];
450+
sop.ModuleSemanticInfoOutputPath = (*moduleSemanticInfoOutput)[moduleIndex];
451+
sop.YAMLOptRecordPath = (*optRecordOutput)[moduleIndex];
452+
sop.BitstreamOptRecordPath = (*optRecordOutput)[moduleIndex];
453+
sop.SILOutputPath = (*silOutput)[silOutputIndex];
454+
sop.LLVMIROutputPath = (*irOutput)[i];
416455
result.push_back(sop);
417456
}
418457
return result;
@@ -439,6 +478,15 @@ SupplementaryOutputPathsComputer::getSupplementaryFilenamesFromArguments(
439478
paths.emplace_back();
440479
return paths;
441480
}
481+
// Special handling for SIL and IR output paths: allow multiple paths per file
482+
// type
483+
else if ((pathID == options::OPT_sil_output_path ||
484+
pathID == options::OPT_ir_output_path) &&
485+
paths.size() > N) {
486+
// For parallel compilation, we can have multiple SIL/IR output paths
487+
// so return all the paths.
488+
return paths;
489+
}
442490

443491
if (paths.empty())
444492
return std::vector<std::string>(N, std::string());
@@ -613,6 +661,9 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput(
613661
file_types::TY_BitstreamOptRecord, "",
614662
defaultSupplementaryOutputPathExcludingExtension);
615663

664+
auto SILOutputPath = pathsFromArguments.SILOutputPath;
665+
auto LLVMIROutputPath = pathsFromArguments.LLVMIROutputPath;
666+
616667
SupplementaryOutputPaths sop;
617668
sop.ClangHeaderOutputPath = clangHeaderOutputPath;
618669
sop.ModuleOutputPath = moduleOutputPath;
@@ -635,6 +686,8 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput(
635686
sop.ModuleSemanticInfoOutputPath = ModuleSemanticInfoOutputPath;
636687
sop.YAMLOptRecordPath = YAMLOptRecordPath;
637688
sop.BitstreamOptRecordPath = bitstreamOptRecordPath;
689+
sop.SILOutputPath = SILOutputPath;
690+
sop.LLVMIROutputPath = LLVMIROutputPath;
638691
return sop;
639692
}
640693

@@ -741,18 +794,18 @@ createFromTypeToPathMap(const TypeToPathMap *map) {
741794

742795
std::optional<std::vector<SupplementaryOutputPaths>>
743796
SupplementaryOutputPathsComputer::readSupplementaryOutputFileMap() const {
744-
if (Arg *A = Args.getLastArg(options::OPT_emit_objc_header_path,
745-
options::OPT_emit_module_path,
746-
options::OPT_emit_module_doc_path,
747-
options::OPT_emit_dependencies_path,
748-
options::OPT_emit_reference_dependencies_path,
749-
options::OPT_serialize_diagnostics_path,
750-
options::OPT_emit_loaded_module_trace_path,
751-
options::OPT_emit_module_interface_path,
752-
options::OPT_emit_private_module_interface_path,
753-
options::OPT_emit_package_module_interface_path,
754-
options::OPT_emit_module_source_info_path,
755-
options::OPT_emit_tbd_path)) {
797+
if (Arg *A = Args.getLastArg(
798+
options::OPT_emit_objc_header_path, options::OPT_emit_module_path,
799+
options::OPT_emit_module_doc_path,
800+
options::OPT_emit_dependencies_path,
801+
options::OPT_emit_reference_dependencies_path,
802+
options::OPT_serialize_diagnostics_path,
803+
options::OPT_emit_loaded_module_trace_path,
804+
options::OPT_emit_module_interface_path,
805+
options::OPT_emit_private_module_interface_path,
806+
options::OPT_emit_package_module_interface_path,
807+
options::OPT_emit_module_source_info_path, options::OPT_emit_tbd_path,
808+
options::OPT_sil_output_path, options::OPT_ir_output_path)) {
756809
Diags.diagnose(SourceLoc(),
757810
diag::error_cannot_have_supplementary_outputs,
758811
A->getSpelling(), "-supplementary-output-file-map");

0 commit comments

Comments
 (0)