Skip to content

Commit b990ca9

Browse files
author
Nathan Hawes
committed
Add driver support to specify an overriding output path to record in the index data
The frontend supports this via new options -index-unit-output-path and -index-unit-output-path-filelist that mirror -o and -output-filelist. These are intended to allow sharing index data across builds in separate directories (so different -o values) that are otherwise equivalent as far as the index data is concerned (e.g. an ASAN build and a non-ASAN build) by supplying the same -index-unit-output-path for both. This change updates the driver to add these new options to the frontend invocation 1) when a new "index-unit-output-path" entry is specified for one or more input files in the -output-file-map json or 2) if -index-file is specified, when a new -index-unit-output-path driver option is passed. Resolves rdar://problem/74816412
1 parent 56cba83 commit b990ca9

File tree

4 files changed

+218
-14
lines changed

4 files changed

+218
-14
lines changed

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ extension Driver {
2626
}
2727
}
2828

29+
mutating func computeIndexUnitOutput(for input: TypedVirtualPath, outputType: FileType, topLevel: Bool) -> TypedVirtualPath? {
30+
if let path = outputFileMap?.existingOutput(inputFile: input.file, outputType: .indexUnitOutputPath) {
31+
return TypedVirtualPath(file: path, type: outputType)
32+
}
33+
if topLevel {
34+
if let baseOutput = parsedOptions.getLastArgument(.indexUnitOutputPath)?.asSingle,
35+
let baseOutputPath = try? VirtualPath(path: baseOutput) {
36+
return TypedVirtualPath(file: baseOutputPath, type: outputType)
37+
}
38+
}
39+
return nil
40+
}
41+
2942
mutating func computePrimaryOutput(for input: TypedVirtualPath, outputType: FileType,
3043
isTopLevel: Bool) -> TypedVirtualPath {
3144
if let path = outputFileMap?.existingOutput(inputFile: input.file, outputType: outputType) {
@@ -79,19 +92,22 @@ extension Driver {
7992
case .swift, .image, .dSYM, .dependencies, .autolink, .swiftDocumentation, .swiftInterface,
8093
.privateSwiftInterface, .swiftSourceInfoFile, .diagnostics, .objcHeader, .swiftDeps,
8194
.remap, .tbd, .moduleTrace, .yamlOptimizationRecord, .bitstreamOptimizationRecord, .pcm,
82-
.pch, .clangModuleMap, .jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts, nil:
95+
.pch, .clangModuleMap, .jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts,
96+
.indexUnitOutputPath, nil:
8397
return false
8498
}
8599
}
86100

87101
/// Add the compiler inputs for a frontend compilation job, and return the
88-
/// corresponding primary set of outputs.
102+
/// corresponding primary set of outputs and, if not identical, the output
103+
/// paths to record in the index data (empty otherwise).
89104
mutating func addCompileInputs(primaryInputs: [TypedVirtualPath],
90105
indexFilePath: TypedVirtualPath?,
91106
inputs: inout [TypedVirtualPath],
92107
inputOutputMap: inout [TypedVirtualPath: TypedVirtualPath],
93108
outputType: FileType?,
94-
commandLine: inout [Job.ArgTemplate]) -> [TypedVirtualPath] {
109+
commandLine: inout [Job.ArgTemplate])
110+
-> ([TypedVirtualPath], [TypedVirtualPath]) {
95111
// Collect the set of input files that are part of the Swift compilation.
96112
let swiftInputFiles: [TypedVirtualPath] = inputFiles.filter { $0.type.isPartOfSwiftCompilation }
97113

@@ -136,6 +152,8 @@ extension Driver {
136152

137153
// Add each of the input files.
138154
var primaryOutputs: [TypedVirtualPath] = []
155+
var primaryIndexUnitOutputs: [TypedVirtualPath] = []
156+
var indexUnitOutputDiffers = false
139157
for input in swiftInputFiles {
140158
inputs.append(input)
141159

@@ -160,6 +178,13 @@ extension Driver {
160178
isTopLevel: isTopLevel)
161179
primaryOutputs.append(output)
162180
inputOutputMap[input] = output
181+
182+
if let indexUnitOut = computeIndexUnitOutput(for: input, outputType: outputType, topLevel: isTopLevel) {
183+
indexUnitOutputDiffers = true
184+
primaryIndexUnitOutputs.append(indexUnitOut)
185+
} else {
186+
primaryIndexUnitOutputs.append(output)
187+
}
163188
}
164189
}
165190

@@ -172,9 +197,22 @@ extension Driver {
172197
isTopLevel: isTopLevel)
173198
primaryOutputs.append(output)
174199
inputOutputMap[input] = output
200+
201+
if let indexUnitOut = computeIndexUnitOutput(for: input, outputType: outputType, topLevel: isTopLevel) {
202+
indexUnitOutputDiffers = true
203+
primaryIndexUnitOutputs.append(indexUnitOut)
204+
} else {
205+
primaryIndexUnitOutputs.append(output)
206+
}
207+
}
208+
209+
if !indexUnitOutputDiffers {
210+
primaryIndexUnitOutputs.removeAll()
211+
} else {
212+
assert(primaryOutputs.count == primaryIndexUnitOutputs.count)
175213
}
176214

177-
return primaryOutputs
215+
return (primaryOutputs, primaryIndexUnitOutputs)
178216
}
179217

180218
/// Form a compile job, which executes the Swift frontend to produce various outputs.
@@ -200,12 +238,13 @@ extension Driver {
200238
indexFilePath = nil
201239
}
202240

203-
let primaryOutputs = addCompileInputs(primaryInputs: primaryInputs,
204-
indexFilePath: indexFilePath,
205-
inputs: &inputs,
206-
inputOutputMap: &inputOutputMap,
207-
outputType: outputType,
208-
commandLine: &commandLine)
241+
let (primaryOutputs, primaryIndexUnitOutputs) =
242+
addCompileInputs(primaryInputs: primaryInputs,
243+
indexFilePath: indexFilePath,
244+
inputs: &inputs,
245+
inputOutputMap: &inputOutputMap,
246+
outputType: outputType,
247+
commandLine: &commandLine)
209248
outputs += primaryOutputs
210249

211250
// FIXME: optimization record arguments are added before supplementary outputs
@@ -265,6 +304,20 @@ extension Driver {
265304
}
266305
}
267306

307+
// Add index unit output paths if needed.
308+
if !primaryIndexUnitOutputs.isEmpty {
309+
if primaryOutputs.count > fileListThreshold {
310+
commandLine.appendFlag(.indexUnitOutputPathFilelist)
311+
let path = RelativePath(createTemporaryFileName(prefix: "index-outputs"))
312+
commandLine.appendPath(.fileList(path, .list(primaryIndexUnitOutputs.map { $0.file })))
313+
} else {
314+
for primaryIndexUnitOutput in primaryIndexUnitOutputs {
315+
commandLine.appendFlag(.indexUnitOutputPath)
316+
commandLine.appendPath(primaryIndexUnitOutput.file)
317+
}
318+
}
319+
}
320+
268321
try commandLine.appendLast(.embedBitcodeMarker, from: &parsedOptions)
269322

270323
// For `-index-file` mode add `-disable-typo-correction`, since the errors
@@ -380,7 +433,8 @@ extension FileType {
380433
case .swift, .dSYM, .autolink, .dependencies, .swiftDocumentation, .pcm,
381434
.diagnostics, .objcHeader, .image, .swiftDeps, .moduleTrace, .tbd,
382435
.yamlOptimizationRecord, .bitstreamOptimizationRecord, .swiftInterface,
383-
.privateSwiftInterface, .swiftSourceInfoFile, .clangModuleMap, .jsonSwiftArtifacts:
436+
.privateSwiftInterface, .swiftSourceInfoFile, .clangModuleMap, .jsonSwiftArtifacts,
437+
.indexUnitOutputPath:
384438
fatalError("Output type can never be a primary output")
385439
}
386440
}

Sources/SwiftDriver/Utilities/FileType.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ public enum FileType: String, Hashable, CaseIterable, Codable {
114114
/// The extension isn't real.
115115
case indexData
116116

117+
/// Output path to record in the indexing data store
118+
///
119+
/// This is only needed for use as a key in the output file map.
120+
case indexUnitOutputPath
121+
117122
/// Optimization record.
118123
case yamlOptimizationRecord = "opt.yaml"
119124

@@ -185,6 +190,9 @@ extension FileType: CustomStringConvertible {
185190
case .indexData:
186191
return "index-data"
187192

193+
case .indexUnitOutputPath:
194+
return "index-unit-output-path"
195+
188196
case .yamlOptimizationRecord:
189197
return "yaml-opt-record"
190198

@@ -209,7 +217,8 @@ extension FileType {
209217
.swiftDocumentation, .pcm, .diagnostics, .objcHeader, .image,
210218
.swiftDeps, .moduleTrace, .tbd, .yamlOptimizationRecord, .bitstreamOptimizationRecord,
211219
.swiftInterface, .privateSwiftInterface, .swiftSourceInfoFile, .jsonDependencies,
212-
.clangModuleMap, .jsonTargetInfo, .jsonCompilerFeatures, .jsonSwiftArtifacts:
220+
.clangModuleMap, .jsonTargetInfo, .jsonCompilerFeatures, .jsonSwiftArtifacts,
221+
.indexUnitOutputPath:
213222
return false
214223
}
215224
}
@@ -300,6 +309,8 @@ extension FileType {
300309
return "bitstream-opt-record"
301310
case .diagnostics:
302311
return "diagnostics"
312+
case .indexUnitOutputPath:
313+
return "index-unit-output-path"
303314
}
304315
}
305316
}
@@ -315,7 +326,8 @@ extension FileType {
315326
return true
316327
case .image, .object, .dSYM, .pch, .sib, .raw_sib, .swiftModule,
317328
.swiftDocumentation, .swiftSourceInfoFile, .llvmBitcode, .diagnostics,
318-
.pcm, .swiftDeps, .remap, .indexData, .bitstreamOptimizationRecord:
329+
.pcm, .swiftDeps, .remap, .indexData, .bitstreamOptimizationRecord,
330+
.indexUnitOutputPath:
319331
return false
320332
}
321333
}
@@ -331,7 +343,7 @@ extension FileType {
331343
.swiftSourceInfoFile, .raw_sil, .raw_sib, .diagnostics, .objcHeader, .swiftDeps, .remap,
332344
.importedModules, .tbd, .moduleTrace, .indexData, .yamlOptimizationRecord,
333345
.bitstreamOptimizationRecord, .pcm, .pch, .jsonDependencies, .clangModuleMap,
334-
.jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts:
346+
.jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts, .indexUnitOutputPath:
335347
return false
336348
}
337349
}

Sources/SwiftOptions/Options.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ extension Option {
308308
public static let indexIgnoreSystemModules: Option = Option("-index-ignore-system-modules", .flag, attributes: [.noInteractive], helpText: "Avoid indexing system modules")
309309
public static let indexStorePath: Option = Option("-index-store-path", .separate, attributes: [.frontend, .argumentIsPath], metaVar: "<path>", helpText: "Store indexing data to <path>")
310310
public static let indexSystemModules: Option = Option("-index-system-modules", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Emit index data for imported serialized swift system modules")
311+
public static let indexUnitOutputPath: Option = Option("-index-unit-output-path", .separate, attributes: [.frontend, .noInteractive, .argumentIsPath], metaVar: "<file>", helpText: "Record <file> as the output file in the produced index data")
312+
public static let indexUnitOutputPathFilelist: Option = Option("-index-unit-output-path-filelist", .separate, attributes: [.frontend, .noDriver], helpText: "Specify unit index output paths in a file rather than on the command line")
311313
public static let interpret: Option = Option("-interpret", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Immediate mode", group: .modes)
312314
public static let I: Option = Option("-I", .joinedOrSeparate, attributes: [.frontend, .argumentIsPath], helpText: "Add directory to the import search path")
313315
public static let i: Option = Option("-i", .flag, group: .modes)
@@ -811,6 +813,8 @@ extension Option {
811813
Option.indexIgnoreSystemModules,
812814
Option.indexStorePath,
813815
Option.indexSystemModules,
816+
Option.indexUnitOutputPath,
817+
Option.indexUnitOutputPathFilelist,
814818
Option.interpret,
815819
Option.I,
816820
Option.i,

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,140 @@ final class SwiftDriverTests: XCTestCase {
614614
}
615615
}
616616

617+
func testOutputFileMapLoadingIndexUnitOutputPath() throws {
618+
let contents = """
619+
{
620+
"/tmp/main.swift": {
621+
"object": "/tmp/build1/main.o",
622+
"index-unit-output-path": "/tmp/build2/main.o",
623+
},
624+
"/tmp/second.swift": {
625+
"object": "/tmp/build1/second.o",
626+
"index-unit-output-path": "/tmp/build2/second.o",
627+
}
628+
}
629+
"""
630+
631+
func getFileListElements(for filelistOpt: String, job: Job) -> [VirtualPath] {
632+
let optIndex = job.commandLine.firstIndex(of: .flag(filelistOpt))!
633+
let value = job.commandLine[job.commandLine.index(after: optIndex)]
634+
guard case let .path(.fileList(_, valueFileList)) = value else {
635+
XCTFail("Argument wasn't a filelist")
636+
return []
637+
}
638+
guard case let .list(inputs) = valueFileList else {
639+
XCTFail("FileList wasn't List")
640+
return []
641+
}
642+
return inputs
643+
}
644+
645+
try withTemporaryFile { file in
646+
try assertNoDiagnostics { diags in
647+
try localFileSystem.writeFileContents(file.path) { $0 <<< contents }
648+
649+
// 1. Incremental mode (single primary file)
650+
// a) without filelists
651+
var driver = try Driver(args: [
652+
"swiftc", "-c",
653+
"-output-file-map", file.path.pathString,
654+
"-module-name", "test", "/tmp/second.swift", "/tmp/main.swift"
655+
])
656+
var jobs = try driver.planBuild()
657+
print(jobs[0].commandLine)
658+
print(jobs[1].commandLine)
659+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-o", .path(.absolute(.init("/tmp/build1/second.o")))]))
660+
XCTAssertTrue(jobs[1].commandLine.contains(subsequence: ["-o", .path(.absolute(.init("/tmp/build1/main.o")))]))
661+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-index-unit-output-path", .path(.absolute(.init("/tmp/build2/second.o")))]))
662+
XCTAssertTrue(jobs[1].commandLine.contains(subsequence: ["-index-unit-output-path", .path(.absolute(.init("/tmp/build2/main.o")))]))
663+
664+
// b) with filelists
665+
driver = try Driver(args: [
666+
"swiftc", "-c", "-driver-filelist-threshold=0",
667+
"-output-file-map", file.path.pathString,
668+
"-module-name", "test", "/tmp/second.swift", "/tmp/main.swift"
669+
])
670+
jobs = try driver.planBuild()
671+
XCTAssertEqual(getFileListElements(for: "-output-filelist", job: jobs[0]),
672+
[.absolute(.init("/tmp/build1/second.o"))])
673+
XCTAssertEqual(getFileListElements(for: "-index-unit-output-path-filelist", job: jobs[0]),
674+
[.absolute(.init("/tmp/build2/second.o"))])
675+
XCTAssertEqual(getFileListElements(for: "-output-filelist", job: jobs[1]),
676+
[.absolute(.init("/tmp/build1/main.o"))])
677+
XCTAssertEqual(getFileListElements(for: "-index-unit-output-path-filelist", job: jobs[1]),
678+
[.absolute(.init("/tmp/build2/main.o"))])
679+
680+
681+
// 2. Batch mode (two primary files)
682+
// a) without filelists
683+
driver = try Driver(args: [
684+
"swiftc", "-c", "-enable-batch-mode", "-driver-batch-count", "1",
685+
"-output-file-map", file.path.pathString,
686+
"-module-name", "test", "/tmp/second.swift", "/tmp/main.swift"
687+
])
688+
jobs = try driver.planBuild()
689+
print(jobs[0].commandLine)
690+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-o", .path(.absolute(.init("/tmp/build1/second.o")))]))
691+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-o", .path(.absolute(.init("/tmp/build1/main.o")))]))
692+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-index-unit-output-path", .path(.absolute(.init("/tmp/build2/second.o")))]))
693+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-index-unit-output-path", .path(.absolute(.init("/tmp/build2/main.o")))]))
694+
695+
// b) with filelists
696+
driver = try Driver(args: [
697+
"swiftc", "-c", "-driver-filelist-threshold=0",
698+
"-enable-batch-mode", "-driver-batch-count", "1",
699+
"-output-file-map", file.path.pathString,
700+
"-module-name", "test", "/tmp/second.swift", "/tmp/main.swift"
701+
])
702+
jobs = try driver.planBuild()
703+
XCTAssertEqual(getFileListElements(for: "-output-filelist", job: jobs[0]),
704+
[.absolute(.init("/tmp/build1/second.o")), .absolute(.init("/tmp/build1/main.o"))])
705+
XCTAssertEqual(getFileListElements(for: "-index-unit-output-path-filelist", job: jobs[0]),
706+
[.absolute(.init("/tmp/build2/second.o")), .absolute(.init("/tmp/build2/main.o"))])
707+
708+
// 3. Multi-threaded WMO
709+
// a) without filelists
710+
driver = try Driver(args: [
711+
"swiftc", "-c", "-whole-module-optimization", "-num-threads", "2",
712+
"-output-file-map", file.path.pathString,
713+
"-module-name", "test", "/tmp/second.swift", "/tmp/main.swift"
714+
])
715+
jobs = try driver.planBuild()
716+
print(jobs[0].commandLine)
717+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-o", .path(.absolute(.init("/tmp/build1/second.o")))]))
718+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-index-unit-output-path", .path(.absolute(.init("/tmp/build2/second.o")))]))
719+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-o", .path(.absolute(.init("/tmp/build1/main.o")))]))
720+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-index-unit-output-path", .path(.absolute(.init("/tmp/build2/main.o")))]))
721+
722+
// b) with filelists
723+
driver = try Driver(args: [
724+
"swiftc", "-c", "-driver-filelist-threshold=0",
725+
"-whole-module-optimization", "-num-threads", "2",
726+
"-output-file-map", file.path.pathString,
727+
"-module-name", "test", "/tmp/second.swift", "/tmp/main.swift"
728+
])
729+
jobs = try driver.planBuild()
730+
XCTAssertEqual(getFileListElements(for: "-output-filelist", job: jobs[0]),
731+
[.absolute(.init("/tmp/build1/second.o")), .absolute(.init("/tmp/build1/main.o"))])
732+
XCTAssertEqual(getFileListElements(for: "-index-unit-output-path-filelist", job: jobs[0]),
733+
[.absolute(.init("/tmp/build2/second.o")), .absolute(.init("/tmp/build2/main.o"))])
734+
735+
// 4. Index-file (single primary)
736+
driver = try Driver(args: [
737+
"swiftc", "-c", "-enable-batch-mode", "-driver-batch-count", "1",
738+
"-module-name", "test", "/tmp/second.swift", "/tmp/main.swift",
739+
"-index-file", "-index-file-path", "/tmp/second.swift",
740+
"-disable-batch-mode", "-o", "/tmp/build1/second.o",
741+
"-index-unit-output-path", "/tmp/build2/second.o"
742+
])
743+
jobs = try driver.planBuild()
744+
print(jobs[0].commandLine)
745+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-o", .path(.absolute(.init("/tmp/build1/second.o")))]))
746+
XCTAssertTrue(jobs[0].commandLine.contains(subsequence: ["-index-unit-output-path", .path(.absolute(.init("/tmp/build2/second.o")))]))
747+
}
748+
}
749+
}
750+
617751
func testMergeModuleEmittingDependencies() throws {
618752
var driver1 = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Foo", "-emit-dependencies", "-emit-module", "-serialize-diagnostics", "-driver-filelist-threshold=9999"])
619753
let plannedJobs = try driver1.planBuild().removingAutolinkExtractJobs()

0 commit comments

Comments
 (0)