Skip to content

Commit c54107d

Browse files
authored
Merge pull request #1995 from rjmansfield/sil-ir-outputdirs
Add -sil-output-dir and -ir-output-dir driver options
2 parents 97ab531 + 8bf76e3 commit c54107d

File tree

7 files changed

+403
-4
lines changed

7 files changed

+403
-4
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,12 @@ public struct Driver {
389389
/// Path to the Objective-C generated header.
390390
let objcGeneratedHeaderPath: VirtualPath.Handle?
391391

392+
/// Path to the SIL output file.
393+
let silOutputPath: VirtualPath.Handle?
394+
395+
/// Path to the LLVM IR output file.
396+
let llvmIROutputPath: VirtualPath.Handle?
397+
392398
/// Path to the loaded module trace file.
393399
let loadedModuleTracePath: VirtualPath.Handle?
394400

@@ -1296,6 +1302,11 @@ public struct Driver {
12961302
emitModuleSeparately: emitModuleSeparately,
12971303
outputFileMap: self.outputFileMap,
12981304
moduleName: moduleOutputInfo.name)
1305+
// SIL and IR outputs are handled through directory options and file maps
1306+
// rather than single output paths, so we set these to nil to enable
1307+
// the supplementary output path logic in FrontendJobHelpers
1308+
self.silOutputPath = nil
1309+
self.llvmIROutputPath = nil
12991310

13001311
if let loadedModuleTraceEnvVar = env["SWIFT_LOADED_MODULE_TRACE_FILE"] {
13011312
self.loadedModuleTracePath = try VirtualPath.intern(path: loadedModuleTraceEnvVar)
@@ -3871,6 +3882,18 @@ extension Driver {
38713882
return try VirtualPath.intern(path: moduleName.appendingFileTypeExtension(type))
38723883
}
38733884

3885+
static func hasFileMapEntry(outputFileMap: OutputFileMap?, fileType: FileType) -> Bool {
3886+
guard let outputFileMap = outputFileMap else { return false }
3887+
3888+
// Check if any input has this file type in the output file map
3889+
for inputFile in outputFileMap.entries.keys {
3890+
if outputFileMap.entries[inputFile]?[fileType] != nil {
3891+
return true
3892+
}
3893+
}
3894+
return false
3895+
}
3896+
38743897
/// Determine if the build system has created a Project/ directory for auxiliary outputs.
38753898
static func computeProjectDirectoryPath(moduleOutputPath: VirtualPath.Handle?,
38763899
fileSystem: FileSystem) -> VirtualPath.Handle? {

Sources/SwiftDriver/Driver/OutputFileMap.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ public struct OutputFileMap: Hashable, Codable {
172172
}
173173
return result
174174
}
175+
176+
/// Check if the output file map has any entries for the given file type
177+
public func hasEntries(for fileType: FileType) -> Bool {
178+
return entries.values.contains { $0[fileType] != nil }
179+
}
175180
}
176181

177182
/// Struct for loading the JSON file from disk.

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ extension Driver {
279279
moduleOutputInfo: self.moduleOutputInfo,
280280
moduleOutputPaths: self.moduleOutputPaths,
281281
includeModuleTracePath: emitModuleTrace,
282-
indexFilePaths: indexFilePaths)
282+
indexFilePaths: indexFilePaths,
283+
allInputs: inputs)
283284

284285
// Forward migrator flags.
285286
try commandLine.appendLast(.apiDiffDataFile, from: &parsedOptions)

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,16 +667,63 @@ extension Driver {
667667
moduleOutputInfo: ModuleOutputInfo,
668668
moduleOutputPaths: SupplementalModuleTargetOutputPaths,
669669
includeModuleTracePath: Bool,
670-
indexFilePaths: [TypedVirtualPath]) throws -> [TypedVirtualPath] {
670+
indexFilePaths: [TypedVirtualPath],
671+
allInputs: [TypedVirtualPath] = []) throws -> [TypedVirtualPath] {
671672
var flaggedInputOutputPairs: [(flag: String, input: TypedVirtualPath?, output: TypedVirtualPath)] = []
672673

674+
/// Generate directory-based output path for supplementary outputs
675+
func generateSupplementaryOutputPath(for input: TypedVirtualPath, outputType: FileType, directory: String) throws -> TypedVirtualPath {
676+
let inputBasename = input.file.basenameWithoutExt
677+
let fileExtension = outputType == .sil ? "sil" : "ll"
678+
let filename = "\(inputBasename).\(fileExtension)"
679+
let individualPath = try VirtualPath(path: directory).appending(component: filename)
680+
let outputPath = individualPath.intern()
681+
return TypedVirtualPath(file: outputPath, type: outputType)
682+
}
683+
684+
/// Process inputs for supplementary output generation (SIL/IR)
685+
func processInputsForSupplementaryOutput(inputs: [TypedVirtualPath], outputType: FileType, flag: String, directory: String?) throws {
686+
for inputFile in inputs {
687+
// Check output file map first, then fall back to directory-based generation
688+
if let outputFileMapPath = try outputFileMap?.existingOutput(inputFile: inputFile.fileHandle, outputType: outputType) {
689+
flaggedInputOutputPairs.append((flag: flag, input: inputFile, output: TypedVirtualPath(file: outputFileMapPath, type: outputType)))
690+
} else if let directory = directory {
691+
let outputPath = try generateSupplementaryOutputPath(for: inputFile, outputType: outputType, directory: directory)
692+
flaggedInputOutputPairs.append((flag: flag, input: inputFile, output: outputPath))
693+
} else if parsedOptions.hasArgument(.saveTemps) {
694+
// When using -save-temps without explicit directories, output to current directory
695+
let outputPath = try generateSupplementaryOutputPath(for: inputFile, outputType: outputType, directory: ".")
696+
flaggedInputOutputPairs.append((flag: flag, input: inputFile, output: outputPath))
697+
}
698+
}
699+
}
700+
673701
/// Add output of a particular type, if needed.
674702
func addOutputOfType(
675703
outputType: FileType,
676704
finalOutputPath: VirtualPath.Handle?,
677705
input: TypedVirtualPath?,
678706
flag: String
679707
) throws {
708+
// Handle directory-based options and file maps for SIL and LLVM IR when finalOutputPath is nil
709+
if finalOutputPath == nil && (outputType == .sil || outputType == .llvmIR) {
710+
let directoryOption: Option = outputType == .sil ? .silOutputDir : .irOutputDir
711+
let directory = parsedOptions.getLastArgument(directoryOption)?.asSingle
712+
let hasFileMapEntries = outputFileMap?.hasEntries(for: outputType) ?? false
713+
714+
if directory != nil || hasFileMapEntries || (parsedOptions.hasArgument(.saveTemps) && !hasFileMapEntries) {
715+
let inputsToProcess: [TypedVirtualPath]
716+
if compilerMode.usesPrimaryFileInputs {
717+
inputsToProcess = input.map { [$0] } ?? []
718+
} else {
719+
inputsToProcess = allInputs
720+
}
721+
722+
try processInputsForSupplementaryOutput(inputs: inputsToProcess, outputType: outputType, flag: flag, directory: directory)
723+
return
724+
}
725+
}
726+
680727
// If there is no final output, there's nothing to do.
681728
guard let finalOutputPath = finalOutputPath else { return }
682729

@@ -763,6 +810,41 @@ extension Driver {
763810
finalOutputPath: serializedDiagnosticsFilePath,
764811
input: input,
765812
flag: "-serialize-diagnostics-path")
813+
814+
// Add SIL and IR outputs when explicitly requested via directory options, file maps, or -save-temps
815+
let saveTempsWithoutFileMap = parsedOptions.hasArgument(.saveTemps) && outputFileMap == nil
816+
let hasSilFileMapEntries = outputFileMap?.hasEntries(for: .sil) ?? false
817+
let hasIrFileMapEntries = outputFileMap?.hasEntries(for: .llvmIR) ?? false
818+
819+
let silOutputPathSupported = Driver.isOptionFound("-sil-output-path", allOpts: supportedFrontendFlags)
820+
let irOutputPathSupported = Driver.isOptionFound("-ir-output-path", allOpts: supportedFrontendFlags)
821+
822+
if !silOutputPathSupported && (parsedOptions.hasArgument(.silOutputDir) || hasSilFileMapEntries) {
823+
diagnosticEngine.emit(.warning("frontend does not support -sil-output-path; SIL output will not be emitted"))
824+
}
825+
826+
if !irOutputPathSupported && (parsedOptions.hasArgument(.irOutputDir) || hasIrFileMapEntries) {
827+
diagnosticEngine.emit(.warning("frontend does not support -ir-output-path; IR output will not be emitted"))
828+
}
829+
830+
let shouldAddSilOutput = silOutputPathSupported && (parsedOptions.hasArgument(.silOutputDir) || saveTempsWithoutFileMap || hasSilFileMapEntries)
831+
let shouldAddIrOutput = irOutputPathSupported && (parsedOptions.hasArgument(.irOutputDir) || saveTempsWithoutFileMap || hasIrFileMapEntries)
832+
833+
if shouldAddSilOutput {
834+
try addOutputOfType(
835+
outputType: .sil,
836+
finalOutputPath: silOutputPath,
837+
input: input,
838+
flag: "-sil-output-path")
839+
}
840+
841+
if shouldAddIrOutput {
842+
try addOutputOfType(
843+
outputType: .llvmIR,
844+
finalOutputPath: llvmIROutputPath,
845+
input: input,
846+
flag: "-ir-output-path")
847+
}
766848
}
767849

768850
if compilerMode.usesPrimaryFileInputs {

Sources/SwiftOptions/Options.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ extension Option {
655655
public static let Isystem: Option = Option("-Isystem", .separate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to the system import search path")
656656
public static let I: Option = Option("-I", .joinedOrSeparate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to the import search path")
657657
public static let i: Option = Option("-i", .flag, group: .modes)
658+
public static let irOutputDir: Option = Option("-ir-output-dir", .separate, attributes: [.argumentIsPath, .supplementaryOutput, .cacheInvariant], metaVar: "<dir>", helpText: "Output LLVM IR files to directory <dir> as additional output during compilation")
658659
public static let json: Option = Option("-json", .flag, attributes: [.noDriver], helpText: "Print output in JSON format.")
659660
public static let json_: Option = Option("--json", .flag, alias: Option.json, attributes: [.noDriver], helpText: "Print output in JSON format.")
660661
public static let j: Option = Option("-j", .joinedOrSeparate, attributes: [.doesNotAffectIncrementalBuild], metaVar: "<n>", helpText: "Number of commands to execute in parallel")
@@ -864,6 +865,7 @@ extension Option {
864865
public static let silDebugSerialization: Option = Option("-sil-debug-serialization", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Do not eliminate functions in Mandatory Inlining/SILCombine dead functions. (for debugging only)")
865866
public static let silInlineCallerBenefitReductionFactor: Option = Option("-sil-inline-caller-benefit-reduction-factor", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<2>", helpText: "Controls the aggressiveness of performance inlining in -Osize mode by reducing the base benefits of a caller (lower value permits more inlining!)")
866867
public static let silInlineThreshold: Option = Option("-sil-inline-threshold", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<50>", helpText: "Controls the aggressiveness of performance inlining")
868+
public static let silOutputDir: Option = Option("-sil-output-dir", .separate, attributes: [.argumentIsPath, .supplementaryOutput, .cacheInvariant], metaVar: "<dir>", helpText: "Output SIL files to directory <dir> as additional output during compilation")
867869
public static let silOwnershipVerifyAll: Option = Option("-sil-ownership-verify-all", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Verify ownership after each transform")
868870
public static let silStopOptznsBeforeLoweringOwnership: Option = Option("-sil-stop-optzns-before-lowering-ownership", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Stop optimizing at SIL time before we lower ownership from SIL. Intended only for SIL ossa tests")
869871
public static let silUnrollThreshold: Option = Option("-sil-unroll-threshold", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<250>", helpText: "Controls the aggressiveness of loop unrolling")
@@ -1610,6 +1612,7 @@ extension Option {
16101612
Option.importPch,
16111613
Option.importPrescan,
16121614
Option.importUnderlyingModule,
1615+
Option.irOutputDir,
16131616
Option.inPlace,
16141617
Option.inProcessPluginServerPath,
16151618
Option.includeSpiSymbols,
@@ -1849,6 +1852,7 @@ extension Option {
18491852
Option.silDebugSerialization,
18501853
Option.silInlineCallerBenefitReductionFactor,
18511854
Option.silInlineThreshold,
1855+
Option.silOutputDir,
18521856
Option.silOwnershipVerifyAll,
18531857
Option.silStopOptznsBeforeLoweringOwnership,
18541858
Option.silUnrollThreshold,

Tests/SwiftDriverTests/JobExecutorTests.swift

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,12 @@ final class JobExecutorTests: XCTestCase {
536536
executor: executor)
537537
let jobs = try driver.planBuild()
538538
XCTAssertEqual(jobs.removingAutolinkExtractJobs().map(\.kind), [.compile, .link])
539-
XCTAssertEqual(jobs[0].outputs.count, 1)
540-
let compileOutput = jobs[0].outputs[0].file
539+
// With -save-temps, we now have additional SIL and IR outputs, so expect more outputs
540+
XCTAssertTrue(jobs[0].outputs.count >= 1, "Should have at least the object file output")
541+
// Find the main object file output
542+
let objectOutput = jobs[0].outputs.first { $0.type == .object }
543+
XCTAssertNotNil(objectOutput, "Should have object file output")
544+
let compileOutput = objectOutput!.file
541545
guard matchTemporary(compileOutput, "main.o") else {
542546
XCTFail("unexpected output")
543547
return
@@ -585,5 +589,53 @@ final class JobExecutorTests: XCTestCase {
585589
)
586590
}
587591
}
592+
593+
// Test that -save-temps also saves SIL and IR intermediate files
594+
do {
595+
// Skip test if frontend doesn't support these options
596+
let diags = DiagnosticsEngine()
597+
let executor = try SwiftDriverExecutor(diagnosticsEngine: diags,
598+
processSet: ProcessSet(),
599+
fileSystem: localFileSystem,
600+
env: ProcessEnv.block)
601+
let checkDriver = try Driver(args: ["swiftc", "foo.swift"] + getHostToolchainSdkArg(executor),
602+
envBlock: ProcessEnv.block,
603+
diagnosticsOutput: .engine(diags),
604+
fileSystem: localFileSystem,
605+
executor: executor)
606+
guard Driver.isOptionFound("-sil-output-path", allOpts: checkDriver.supportedFrontendFlags) &&
607+
Driver.isOptionFound("-ir-output-path", allOpts: checkDriver.supportedFrontendFlags) else {
608+
throw XCTSkip("Skipping: frontend does not support -sil-output-path or -ir-output-path")
609+
}
610+
611+
try withTemporaryDirectory { path in
612+
let main = path.appending(component: "main.swift")
613+
try localFileSystem.writeFileContents(main, bytes: "print(\"hello, world!\")")
614+
let diags = DiagnosticsEngine()
615+
let executor = try SwiftDriverExecutor(diagnosticsEngine: diags,
616+
processSet: ProcessSet(),
617+
fileSystem: localFileSystem,
618+
env: ProcessEnv.block)
619+
let outputPath = path.appending(component: "finalOutput")
620+
var driver = try Driver(args: ["swiftc", main.pathString,
621+
"-save-temps",
622+
"-o", outputPath.pathString] + getHostToolchainSdkArg(executor),
623+
envBlock: ProcessEnv.block,
624+
diagnosticsOutput: .engine(diags),
625+
fileSystem: localFileSystem,
626+
executor: executor)
627+
let jobs = try driver.planBuild()
628+
let compileJobs = jobs.removingAutolinkExtractJobs()
629+
XCTAssertEqual(compileJobs.map(\.kind), [.compile, .link])
630+
631+
let compileJob = compileJobs[0]
632+
633+
XCTAssertTrue(compileJob.commandLine.contains(.flag("-sil-output-path")))
634+
XCTAssertTrue(compileJob.commandLine.contains(.flag("-ir-output-path")))
635+
636+
let hasMultipleOutputs = compileJob.outputs.count > 1
637+
XCTAssertTrue(hasMultipleOutputs, "Should have additional SIL/IR outputs when using -save-temps")
638+
}
639+
}
588640
}
589641
}

0 commit comments

Comments
 (0)