Skip to content

Commit 57c9789

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 57c9789

File tree

4 files changed

+218
-13
lines changed

4 files changed

+218
-13
lines changed

Sources/SwiftDriver/Jobs/CompileJob.swift

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

29+
mutating func computeIndexUnitOutput(for input: TypedVirtualPath, outputType: FileType) -> TypedVirtualPath? {
30+
guard outputType == .indexData || outputType == .object else { return nil }
31+
32+
if let path = outputFileMap?.existingOutput(inputFile: input.file, outputType: .indexUnitOutputPath) {
33+
return TypedVirtualPath(file: path, type: outputType)
34+
}
35+
if outputType == .indexData {
36+
if let baseOutput = parsedOptions.getLastArgument(.indexUnitOutputPath)?.asSingle,
37+
let baseOutputPath = try? VirtualPath(path: baseOutput) {
38+
return TypedVirtualPath(file: baseOutputPath, type: outputType)
39+
}
40+
}
41+
return nil
42+
}
43+
2944
mutating func computePrimaryOutput(for input: TypedVirtualPath, outputType: FileType,
3045
isTopLevel: Bool) -> TypedVirtualPath {
3146
if let path = outputFileMap?.existingOutput(inputFile: input.file, outputType: outputType) {
@@ -79,7 +94,8 @@ extension Driver {
7994
case .swift, .image, .dSYM, .dependencies, .autolink, .swiftDocumentation, .swiftInterface,
8095
.privateSwiftInterface, .swiftSourceInfoFile, .diagnostics, .objcHeader, .swiftDeps,
8196
.remap, .tbd, .moduleTrace, .yamlOptimizationRecord, .bitstreamOptimizationRecord, .pcm,
82-
.pch, .clangModuleMap, .jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts, nil:
97+
.pch, .clangModuleMap, .jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts,
98+
.indexUnitOutputPath, nil:
8399
return false
84100
}
85101
}
@@ -91,7 +107,8 @@ extension Driver {
91107
inputs: inout [TypedVirtualPath],
92108
inputOutputMap: inout [TypedVirtualPath: TypedVirtualPath],
93109
outputType: FileType?,
94-
commandLine: inout [Job.ArgTemplate]) -> [TypedVirtualPath] {
110+
commandLine: inout [Job.ArgTemplate])
111+
-> ([TypedVirtualPath], [TypedVirtualPath]) {
95112
// Collect the set of input files that are part of the Swift compilation.
96113
let swiftInputFiles: [TypedVirtualPath] = inputFiles.filter { $0.type.isPartOfSwiftCompilation }
97114

@@ -136,6 +153,8 @@ extension Driver {
136153

137154
// Add each of the input files.
138155
var primaryOutputs: [TypedVirtualPath] = []
156+
var primaryIndexUnitOutputs: [TypedVirtualPath] = []
157+
var indexUnitOutputDiffers = false
139158
for input in swiftInputFiles {
140159
inputs.append(input)
141160

@@ -160,6 +179,13 @@ extension Driver {
160179
isTopLevel: isTopLevel)
161180
primaryOutputs.append(output)
162181
inputOutputMap[input] = output
182+
183+
if let indexUnitOut = computeIndexUnitOutput(for: input, outputType: outputType) {
184+
indexUnitOutputDiffers = true
185+
primaryIndexUnitOutputs.append(indexUnitOut)
186+
} else {
187+
primaryIndexUnitOutputs.append(output)
188+
}
163189
}
164190
}
165191

@@ -172,9 +198,22 @@ extension Driver {
172198
isTopLevel: isTopLevel)
173199
primaryOutputs.append(output)
174200
inputOutputMap[input] = output
201+
202+
if let indexUnitOut = computeIndexUnitOutput(for: input, outputType: outputType) {
203+
indexUnitOutputDiffers = true
204+
primaryIndexUnitOutputs.append(indexUnitOut)
205+
} else {
206+
primaryIndexUnitOutputs.append(output)
207+
}
208+
}
209+
210+
if !indexUnitOutputDiffers {
211+
primaryIndexUnitOutputs.removeAll()
212+
} else {
213+
assert(primaryOutputs.count == primaryIndexUnitOutputs.count)
175214
}
176215

177-
return primaryOutputs
216+
return (primaryOutputs, primaryIndexUnitOutputs)
178217
}
179218

180219
/// Form a compile job, which executes the Swift frontend to produce various outputs.
@@ -200,12 +239,13 @@ extension Driver {
200239
indexFilePath = nil
201240
}
202241

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

211251
// FIXME: optimization record arguments are added before supplementary outputs
@@ -265,6 +305,20 @@ extension Driver {
265305
}
266306
}
267307

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

270324
// For `-index-file` mode add `-disable-typo-correction`, since the errors
@@ -380,7 +434,8 @@ extension FileType {
380434
case .swift, .dSYM, .autolink, .dependencies, .swiftDocumentation, .pcm,
381435
.diagnostics, .objcHeader, .image, .swiftDeps, .moduleTrace, .tbd,
382436
.yamlOptimizationRecord, .bitstreamOptimizationRecord, .swiftInterface,
383-
.privateSwiftInterface, .swiftSourceInfoFile, .clangModuleMap, .jsonSwiftArtifacts:
437+
.privateSwiftInterface, .swiftSourceInfoFile, .clangModuleMap, .jsonSwiftArtifacts,
438+
.indexUnitOutputPath:
384439
fatalError("Output type can never be a primary output")
385440
}
386441
}

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)