Skip to content

Commit a99d356

Browse files
[Caching] Handle emit module job correctly for swift caching
There are two situations when emiting modules are not correctly handled when swift caching is enabled: * When only `-emit-module` task is requested from driver, in this case, we are contructing a `CompileJob` but only generating module output. SwiftDriver still expects all swift inputs will have a cache key even only the first file is generating outputs and has a cache key. * When using `-experimental-emit-module-separately`, then swift-driver needs to construct a `EmitModuleJob`, which wasn't taught the concept of caching and not producing any cache key. rdar://127768967 (cherry picked from commit b5c3534)
1 parent 03d5083 commit a99d356

File tree

3 files changed

+129
-3
lines changed

3 files changed

+129
-3
lines changed

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,20 @@ extension Driver {
397397
} else {
398398
displayInputs = primaryInputs
399399
}
400-
// Only swift input files are contributing to the cache keys.
400+
// The cache key for compilation is created one per input file, and each cache key contains all the output
401+
// files for that specific input file. All the module level output files are attached to the cache key for
402+
// the first input file. Only the input files that produce the output will have a cache key. This behavior
403+
// needs to match the cache key creation logic in swift-frontend.
401404
let cacheContributingInputs = inputs.enumerated().reduce(into: [(TypedVirtualPath, Int)]()) { result, input in
402-
if input.element.type == .swift, displayInputs.contains(input.element) {
405+
guard input.element.type == .swift else { return }
406+
let singleInputKey = TypedVirtualPath(file: OutputFileMap.singleInputKey, type: .swift)
407+
if inputOutputMap[singleInputKey] != nil {
408+
// If singleInputKey exists, that means only the first swift file produces outputs.
409+
if result.isEmpty {
410+
result.append((input.element, input.offset))
411+
}
412+
} else if !inputOutputMap[input.element, default: []].isEmpty {
413+
// Otherwise, add all the inputs that produce output.
403414
result.append((input.element, input.offset))
404415
}
405416
}

Sources/SwiftDriver/Jobs/EmitModuleJob.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,21 @@ extension Driver {
119119
commandLine.appendPath(abiPath.file)
120120
outputs.append(abiPath)
121121
}
122+
let cacheContributingInputs = inputs.enumerated().reduce(into: [(TypedVirtualPath, Int)]()) { result, input in
123+
// only the first swift input contributes cache key to an emit module job.
124+
guard result.isEmpty, input.element.type == .swift else { return }
125+
result.append((input.element, input.offset))
126+
}
127+
let cacheKeys = try computeOutputCacheKeyForJob(commandLine: commandLine, inputs: cacheContributingInputs)
122128
return Job(
123129
moduleName: moduleOutputInfo.name,
124130
kind: .emitModule,
125131
tool: try toolchain.resolvedTool(.swiftCompiler),
126132
commandLine: commandLine,
127133
inputs: inputs,
128134
primaryInputs: [],
129-
outputs: outputs
135+
outputs: outputs,
136+
outputCacheKeys: cacheKeys
130137
)
131138
}
132139

Tests/SwiftDriverTests/CachingBuildTests.swift

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,114 @@ final class CachingBuildTests: XCTestCase {
333333
}
334334
}
335335

336+
func testModuleOnlyJob() throws {
337+
try withTemporaryDirectory { path in
338+
let main = path.appending(component: "testModuleOnlyJob.swift")
339+
try localFileSystem.writeFileContents(main) {
340+
$0.send("import C;import E;")
341+
}
342+
let other = path.appending(component: "testModuleOnlyJob2.swift")
343+
try localFileSystem.writeFileContents(other) {
344+
$0.send("import G;")
345+
}
346+
let swiftModuleInterfacesPath: AbsolutePath =
347+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
348+
.appending(component: "Swift")
349+
let cHeadersPath: AbsolutePath =
350+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
351+
.appending(component: "CHeaders")
352+
let casPath = path.appending(component: "cas")
353+
let swiftInterfacePath: AbsolutePath = path.appending(component: "testModuleOnlyJob.swiftinterface")
354+
let privateSwiftInterfacePath: AbsolutePath = path.appending(component: "testModuleOnlyJob.private.swiftinterface")
355+
let modulePath: AbsolutePath = path.appending(component: "testModuleOnlyJob.swiftmodule")
356+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
357+
var driver = try Driver(args: ["swiftc",
358+
"-target", "x86_64-apple-macosx11.0",
359+
"-module-name", "Test",
360+
"-I", cHeadersPath.nativePathString(escaped: true),
361+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
362+
"-emit-module-interface-path", swiftInterfacePath.nativePathString(escaped: true),
363+
"-emit-private-module-interface-path", privateSwiftInterfacePath.nativePathString(escaped: true),
364+
"-explicit-module-build", "-emit-module-separately-wmo", "-disable-cmo", "-Rcache-compile-job",
365+
"-enable-library-evolution", "-O", "-whole-module-optimization",
366+
"-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true),
367+
"-emit-module", "-o", modulePath.nativePathString(escaped: true),
368+
main.nativePathString(escaped: true), other.nativePathString(escaped: true)] + sdkArgumentsForTesting,
369+
env: ProcessEnv.vars,
370+
interModuleDependencyOracle: dependencyOracle)
371+
let jobs = try driver.planBuild()
372+
try driver.run(jobs: jobs)
373+
for job in jobs {
374+
XCTAssertFalse(job.outputCacheKeys.isEmpty)
375+
}
376+
XCTAssertFalse(driver.diagnosticEngine.hasErrors)
377+
378+
let scanLibPath = try XCTUnwrap(driver.getSwiftScanLibPath())
379+
try dependencyOracle.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
380+
swiftScanLibPath: scanLibPath)
381+
382+
let cas = try dependencyOracle.getOrCreateCAS(pluginPath: nil, onDiskPath: casPath, pluginOptions: [])
383+
if let driverCAS = driver.cas {
384+
XCTAssertEqual(cas, driverCAS, "CAS should only be created once")
385+
} else {
386+
XCTFail("Cached compilation doesn't have a CAS")
387+
}
388+
try checkCASForResults(jobs: jobs, cas: cas, fs: driver.fileSystem)
389+
}
390+
}
391+
392+
func testSeparateModuleJob() throws {
393+
try withTemporaryDirectory { path in
394+
let main = path.appending(component: "testSeparateModuleJob.swift")
395+
try localFileSystem.writeFileContents(main) {
396+
$0.send("import C;import E;")
397+
}
398+
let swiftModuleInterfacesPath: AbsolutePath =
399+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
400+
.appending(component: "Swift")
401+
let cHeadersPath: AbsolutePath =
402+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
403+
.appending(component: "CHeaders")
404+
let casPath = path.appending(component: "cas")
405+
let swiftInterfacePath: AbsolutePath = path.appending(component: "testSeparateModuleJob.swiftinterface")
406+
let privateSwiftInterfacePath: AbsolutePath = path.appending(component: "testSeparateModuleJob.private.swiftinterface")
407+
let modulePath: AbsolutePath = path.appending(component: "testSeparateModuleJob.swiftmodule")
408+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
409+
var driver = try Driver(args: ["swiftc",
410+
"-target", "x86_64-apple-macosx11.0",
411+
"-module-name", "Test",
412+
"-I", cHeadersPath.nativePathString(escaped: true),
413+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
414+
"-emit-module-path", modulePath.nativePathString(escaped: true),
415+
"-emit-module-interface-path", swiftInterfacePath.nativePathString(escaped: true),
416+
"-emit-private-module-interface-path", privateSwiftInterfacePath.nativePathString(escaped: true),
417+
"-explicit-module-build", "-experimental-emit-module-separately", "-Rcache-compile-job",
418+
"-enable-library-evolution", "-O",
419+
"-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true),
420+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
421+
env: ProcessEnv.vars,
422+
interModuleDependencyOracle: dependencyOracle)
423+
let jobs = try driver.planBuild()
424+
for job in jobs {
425+
XCTAssertFalse(job.outputCacheKeys.isEmpty)
426+
}
427+
try driver.run(jobs: jobs)
428+
XCTAssertFalse(driver.diagnosticEngine.hasErrors)
429+
430+
let scanLibPath = try XCTUnwrap(driver.getSwiftScanLibPath())
431+
try dependencyOracle.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
432+
swiftScanLibPath: scanLibPath)
433+
434+
let cas = try dependencyOracle.getOrCreateCAS(pluginPath: nil, onDiskPath: casPath, pluginOptions: [])
435+
if let driverCAS = driver.cas {
436+
XCTAssertEqual(cas, driverCAS, "CAS should only be created once")
437+
} else {
438+
XCTFail("Cached compilation doesn't have a CAS")
439+
}
440+
try checkCASForResults(jobs: jobs, cas: cas, fs: driver.fileSystem)
441+
}
442+
}
443+
336444
/// Test generation of explicit module build jobs for dependency modules when the driver
337445
/// is invoked with -explicit-module-build, -verify-emitted-module-interface and -enable-library-evolution.
338446
func testExplicitModuleVerifyInterfaceJobs() throws {

0 commit comments

Comments
 (0)