Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,12 @@ public struct Driver {
/// Path to the Objective-C generated header.
let objcGeneratedHeaderPath: VirtualPath.Handle?

/// Path to the SIL output file.
let silOutputPath: VirtualPath.Handle?

/// Path to the LLVM IR output file.
let llvmIROutputPath: VirtualPath.Handle?

/// Path to the loaded module trace file.
let loadedModuleTracePath: VirtualPath.Handle?

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

if let loadedModuleTraceEnvVar = env["SWIFT_LOADED_MODULE_TRACE_FILE"] {
self.loadedModuleTracePath = try VirtualPath.intern(path: loadedModuleTraceEnvVar)
Expand Down Expand Up @@ -3871,6 +3882,19 @@ extension Driver {
return try VirtualPath.intern(path: moduleName.appendingFileTypeExtension(type))
}

/// Check if output file map has entries for SIL/IR to enable file map support
static func hasFileMapEntry(outputFileMap: OutputFileMap?, fileType: FileType) -> Bool {
guard let outputFileMap = outputFileMap else { return false }

// Check if any input has this file type in the output file map
for inputFile in outputFileMap.entries.keys {
if outputFileMap.entries[inputFile]?[fileType] != nil {
return true
}
}
return false
}

/// Determine if the build system has created a Project/ directory for auxiliary outputs.
static func computeProjectDirectoryPath(moduleOutputPath: VirtualPath.Handle?,
fileSystem: FileSystem) -> VirtualPath.Handle? {
Expand Down
5 changes: 5 additions & 0 deletions Sources/SwiftDriver/Driver/OutputFileMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ public struct OutputFileMap: Hashable, Codable {
}
return result
}

/// Check if the output file map has any entries for the given file type
public func hasEntries(for fileType: FileType) -> Bool {
return entries.values.contains { $0[fileType] != nil }
}
}

/// Struct for loading the JSON file from disk.
Expand Down
3 changes: 2 additions & 1 deletion Sources/SwiftDriver/Jobs/CompileJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ extension Driver {
moduleOutputInfo: self.moduleOutputInfo,
moduleOutputPaths: self.moduleOutputPaths,
includeModuleTracePath: emitModuleTrace,
indexFilePaths: indexFilePaths)
indexFilePaths: indexFilePaths,
allInputs: inputs)

// Forward migrator flags.
try commandLine.appendLast(.apiDiffDataFile, from: &parsedOptions)
Expand Down
73 changes: 72 additions & 1 deletion Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -667,16 +667,63 @@ extension Driver {
moduleOutputInfo: ModuleOutputInfo,
moduleOutputPaths: SupplementalModuleTargetOutputPaths,
includeModuleTracePath: Bool,
indexFilePaths: [TypedVirtualPath]) throws -> [TypedVirtualPath] {
indexFilePaths: [TypedVirtualPath],
allInputs: [TypedVirtualPath] = []) throws -> [TypedVirtualPath] {
var flaggedInputOutputPairs: [(flag: String, input: TypedVirtualPath?, output: TypedVirtualPath)] = []

/// Generate directory-based output path for supplementary outputs
func generateSupplementaryOutputPath(for input: TypedVirtualPath, outputType: FileType, directory: String) throws -> TypedVirtualPath {
let inputBasename = input.file.basenameWithoutExt
let fileExtension = outputType == .sil ? "sil" : "ll"
let filename = "\(inputBasename).\(fileExtension)"
let individualPath = try VirtualPath(path: directory).appending(component: filename)
let outputPath = individualPath.intern()
return TypedVirtualPath(file: outputPath, type: outputType)
}

/// Process inputs for supplementary output generation (SIL/IR)
func processInputsForSupplementaryOutput(inputs: [TypedVirtualPath], outputType: FileType, flag: String, directory: String?) throws {
for inputFile in inputs {
// Check output file map first, then fall back to directory-based generation
if let outputFileMapPath = try outputFileMap?.existingOutput(inputFile: inputFile.fileHandle, outputType: outputType) {
flaggedInputOutputPairs.append((flag: flag, input: inputFile, output: TypedVirtualPath(file: outputFileMapPath, type: outputType)))
} else if let directory = directory {
let outputPath = try generateSupplementaryOutputPath(for: inputFile, outputType: outputType, directory: directory)
flaggedInputOutputPairs.append((flag: flag, input: inputFile, output: outputPath))
} else if parsedOptions.hasArgument(.saveTemps) {
// When using -save-temps without explicit directories, output to current directory
let outputPath = try generateSupplementaryOutputPath(for: inputFile, outputType: outputType, directory: ".")
flaggedInputOutputPairs.append((flag: flag, input: inputFile, output: outputPath))
}
}
}

/// Add output of a particular type, if needed.
func addOutputOfType(
outputType: FileType,
finalOutputPath: VirtualPath.Handle?,
input: TypedVirtualPath?,
flag: String
) throws {
// Handle directory-based options and file maps for SIL and LLVM IR when finalOutputPath is nil
if finalOutputPath == nil && (outputType == .sil || outputType == .llvmIR) {
let directoryOption: Option = outputType == .sil ? .silOutputDir : .irOutputDir
let directory = parsedOptions.getLastArgument(directoryOption)?.asSingle
let hasFileMapEntries = outputFileMap?.hasEntries(for: outputType) ?? false

if directory != nil || hasFileMapEntries || (parsedOptions.hasArgument(.saveTemps) && !hasFileMapEntries) {
let inputsToProcess: [TypedVirtualPath]
if compilerMode.usesPrimaryFileInputs {
inputsToProcess = input.map { [$0] } ?? []
} else {
inputsToProcess = allInputs
}

try processInputsForSupplementaryOutput(inputs: inputsToProcess, outputType: outputType, flag: flag, directory: directory)
return
}
}

// If there is no final output, there's nothing to do.
guard let finalOutputPath = finalOutputPath else { return }

Expand Down Expand Up @@ -763,6 +810,30 @@ extension Driver {
finalOutputPath: serializedDiagnosticsFilePath,
input: input,
flag: "-serialize-diagnostics-path")

// Add SIL and IR outputs when explicitly requested via directory options, file maps, or -save-temps
let saveTempsWithoutFileMap = parsedOptions.hasArgument(.saveTemps) && outputFileMap == nil
let hasSilFileMapEntries = outputFileMap?.hasEntries(for: .sil) ?? false
let hasIrFileMapEntries = outputFileMap?.hasEntries(for: .llvmIR) ?? false

let shouldAddSilOutput = parsedOptions.hasArgument(.silOutputDir) || saveTempsWithoutFileMap || hasSilFileMapEntries
let shouldAddIrOutput = parsedOptions.hasArgument(.irOutputDir) || saveTempsWithoutFileMap || hasIrFileMapEntries

if shouldAddSilOutput {
try addOutputOfType(
outputType: .sil,
finalOutputPath: silOutputPath,
input: input,
flag: "-sil-output-path")
}

if shouldAddIrOutput {
try addOutputOfType(
outputType: .llvmIR,
finalOutputPath: llvmIROutputPath,
input: input,
flag: "-ir-output-path")
}
}

if compilerMode.usesPrimaryFileInputs {
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftOptions/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ extension Option {
public static let Isystem: Option = Option("-Isystem", .separate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to the system import search path")
public static let I: Option = Option("-I", .joinedOrSeparate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to the import search path")
public static let i: Option = Option("-i", .flag, group: .modes)
public static let irOutputDir: Option = Option("-ir-output-dir", .separate, attributes: [.frontend, .argumentIsPath, .supplementaryOutput, .cacheInvariant], metaVar: "<dir>", helpText: "Output LLVM IR files to directory <dir> as additional output during compilation")
public static let json: Option = Option("-json", .flag, attributes: [.noDriver], helpText: "Print output in JSON format.")
public static let json_: Option = Option("--json", .flag, alias: Option.json, attributes: [.noDriver], helpText: "Print output in JSON format.")
public static let j: Option = Option("-j", .joinedOrSeparate, attributes: [.doesNotAffectIncrementalBuild], metaVar: "<n>", helpText: "Number of commands to execute in parallel")
Expand Down Expand Up @@ -864,6 +865,7 @@ extension Option {
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)")
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!)")
public static let silInlineThreshold: Option = Option("-sil-inline-threshold", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<50>", helpText: "Controls the aggressiveness of performance inlining")
public static let silOutputDir: Option = Option("-sil-output-dir", .separate, attributes: [.frontend, .argumentIsPath, .supplementaryOutput, .cacheInvariant], metaVar: "<dir>", helpText: "Output SIL files to directory <dir> as additional output during compilation")
public static let silOwnershipVerifyAll: Option = Option("-sil-ownership-verify-all", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Verify ownership after each transform")
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")
public static let silUnrollThreshold: Option = Option("-sil-unroll-threshold", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<250>", helpText: "Controls the aggressiveness of loop unrolling")
Expand Down Expand Up @@ -1610,6 +1612,7 @@ extension Option {
Option.importPch,
Option.importPrescan,
Option.importUnderlyingModule,
Option.irOutputDir,
Option.inPlace,
Option.inProcessPluginServerPath,
Option.includeSpiSymbols,
Expand Down Expand Up @@ -1849,6 +1852,7 @@ extension Option {
Option.silDebugSerialization,
Option.silInlineCallerBenefitReductionFactor,
Option.silInlineThreshold,
Option.silOutputDir,
Option.silOwnershipVerifyAll,
Option.silStopOptznsBeforeLoweringOwnership,
Option.silUnrollThreshold,
Expand Down
41 changes: 39 additions & 2 deletions Tests/SwiftDriverTests/JobExecutorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,12 @@ final class JobExecutorTests: XCTestCase {
executor: executor)
let jobs = try driver.planBuild()
XCTAssertEqual(jobs.removingAutolinkExtractJobs().map(\.kind), [.compile, .link])
XCTAssertEqual(jobs[0].outputs.count, 1)
let compileOutput = jobs[0].outputs[0].file
// With -save-temps, we now have additional SIL and IR outputs, so expect more outputs
XCTAssertTrue(jobs[0].outputs.count >= 1, "Should have at least the object file output")
// Find the main object file output
let objectOutput = jobs[0].outputs.first { $0.type == .object }
XCTAssertNotNil(objectOutput, "Should have object file output")
let compileOutput = objectOutput!.file
guard matchTemporary(compileOutput, "main.o") else {
XCTFail("unexpected output")
return
Expand Down Expand Up @@ -585,5 +589,38 @@ final class JobExecutorTests: XCTestCase {
)
}
}

// Test that -save-temps also saves SIL and IR intermediate files
do {
try withTemporaryDirectory { path in
let main = path.appending(component: "main.swift")
try localFileSystem.writeFileContents(main, bytes: "print(\"hello, world!\")")
let diags = DiagnosticsEngine()
let executor = try SwiftDriverExecutor(diagnosticsEngine: diags,
processSet: ProcessSet(),
fileSystem: localFileSystem,
env: ProcessEnv.block)
let outputPath = path.appending(component: "finalOutput")
var driver = try Driver(args: ["swiftc", main.pathString,
"-save-temps",
"-o", outputPath.pathString] + getHostToolchainSdkArg(executor),
envBlock: ProcessEnv.block,
diagnosticsOutput: .engine(diags),
fileSystem: localFileSystem,
executor: executor)
let jobs = try driver.planBuild()
let compileJobs = jobs.removingAutolinkExtractJobs()
XCTAssertEqual(compileJobs.map(\.kind), [.compile, .link])

let compileJob = compileJobs[0]

XCTAssertTrue(compileJob.commandLine.contains(.flag("-sil-output-path")))
XCTAssertTrue(compileJob.commandLine.contains(.flag("-ir-output-path")))

// Verify the compile job has additional outputs for SIL and IR
let hasMultipleOutputs = compileJob.outputs.count > 1
XCTAssertTrue(hasMultipleOutputs, "Should have additional SIL/IR outputs when using -save-temps")
}
}
}
}
Loading