Skip to content

Commit b5c3534

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
1 parent 7d5368f commit b5c3534

File tree

3 files changed

+129
-3
lines changed

3 files changed

+129
-3
lines changed

Sources/SwiftDriver/Jobs/CompileJob.swift

+13-2
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

+8-1
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

+108
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,114 @@ final class CachingBuildTests: XCTestCase {
336336
}
337337
}
338338

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

0 commit comments

Comments
 (0)