Skip to content

Commit c3cc47d

Browse files
[Caching] Direct cache replay support
Teach swift-driver to replay cached compilation result using libSwiftScan APIs directly. Jobs constructed from swift-driver is going to have a new field for all the cache keys associated with the job. The Executor can choose to query and replay using those cache keys if possible, instead of executing the build command.
1 parent ea3ec7f commit c3cc47d

20 files changed

+747
-144
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <stdint.h>
1919

2020
#define SWIFTSCAN_VERSION_MAJOR 0
21-
#define SWIFTSCAN_VERSION_MINOR 5
21+
#define SWIFTSCAN_VERSION_MINOR 6
2222

2323
//=== Public Scanner Data Types -------------------------------------------===//
2424

@@ -78,18 +78,45 @@ typedef struct swiftscan_scan_invocation_s *swiftscan_scan_invocation_t;
7878
typedef void *swiftscan_scanner_t;
7979

8080
//=== CAS/Caching Specification -------------------------------------------===//
81-
typedef struct swiftscan_cas_s *swiftscan_cas_t;
8281
typedef struct swiftscan_cas_options_s *swiftscan_cas_options_t;
82+
typedef struct swiftscan_cas_s *swiftscan_cas_t;
83+
typedef struct swiftscan_cached_compilation_s *swiftscan_cached_compilation_t;
84+
typedef struct swiftscan_cache_replay_instance_s
85+
*swiftscan_cache_replay_instance_t;
86+
typedef struct swiftscan_cache_cancellation_token_s
87+
*swiftscan_cache_cancellation_token_t;
8388

8489
typedef enum {
8590
SWIFTSCAN_OUTPUT_TYPE_OBJECT = 0,
8691
SWIFTSCAN_OUTPUT_TYPE_SWIFTMODULE = 1,
8792
SWIFTSCAN_OUTPUT_TYPE_SWIFTINTERFACE = 2,
8893
SWIFTSCAN_OUTPUT_TYPE_SWIFTPRIVATEINTERFACE = 3,
8994
SWIFTSCAN_OUTPUT_TYPE_CLANG_MODULE = 4,
90-
SWIFTSCAN_OUTPUT_TYPE_CLANG_PCH = 5
95+
SWIFTSCAN_OUTPUT_TYPE_CLANG_PCH = 5,
96+
SWIFTSCAN_OUTPUT_TYPE_CLANG_HEADER = 6,
97+
SWIFTSCAN_OUTPUT_TYPE_SWIFT_SOURCE_INFO = 7,
98+
SWIFTSCAN_OUTPUT_TYPE_SWIFT_MODULE_DOC = 8,
99+
SWIFTSCAN_OUTPUT_TYPE_DEPENDENCIES = 9,
100+
SWIFTSCAN_OUTPUT_TYPE_SWIFT_DEPS = 10,
101+
SWIFTSCAN_OUTPUT_TYPE_MODULE_TRACE = 11,
102+
SWIFTSCAN_OUTPUT_TYPE_TBD = 12,
103+
SWIFTSCAN_OUTPUT_TYPE_SWIFT_MODULE_SUMMARY = 13,
104+
SWIFTSCAN_OUTPUT_TYPE_SWIFT_ABI_DESCRIPTOR = 14,
105+
SWIFTSCAN_OUTPUT_TYPE_SWIFT_API_DESCRIPTOR = 15,
106+
SWIFTSCAN_OUTPUT_TYPE_CONST_VALUE = 16,
107+
SWIFTSCAN_OUTPUT_TYPE_MODULE_SEMANTIC_INFO = 17,
108+
SWIFTSCAN_OUTPUT_TYPE_YAML_OPT_RECORD = 18,
109+
SWIFTSCAN_OUTPUT_TYPE_BITSTREAM_OPT_RECORD = 19,
110+
SWIFTSCAN_OUTPUT_TYPE_CACHED_DIAGNOSTICS = 20,
111+
SWIFTSCAN_OUTPUT_TYPE_LAST = SWIFTSCAN_OUTPUT_TYPE_CACHED_DIAGNOSTICS
91112
} swiftscan_output_kind_t;
92113

114+
typedef enum {
115+
SWIFTSCAN_CACHE_RESULT_SUCCESS = 0,
116+
SWIFTSCAN_CACHE_RESULT_NOT_FOUND = 1,
117+
SWIFTSCAN_CACHE_RESULT_ERROR = 2,
118+
} swiftscan_cache_result_t;
119+
93120
//=== libSwiftScan Functions ------------------------------------------------===//
94121

95122
typedef struct {
@@ -290,9 +317,64 @@ typedef struct {
290317
swiftscan_string_ref_t (*swiftscan_cas_store)(swiftscan_cas_t cas,
291318
uint8_t *data, unsigned size,
292319
swiftscan_string_ref_t *error);
293-
swiftscan_string_ref_t (*swiftscan_compute_cache_key)(
294-
swiftscan_cas_t cas, int argc, const char *argv, const char *input,
295-
swiftscan_output_kind_t, swiftscan_string_ref_t *error);
320+
swiftscan_string_ref_t (*swiftscan_cache_compute_key)(
321+
swiftscan_cas_t cas, int argc, const char **argv, const char *input,
322+
swiftscan_string_ref_t *error);
323+
324+
//=== Scanner Caching Query/Replay Operations -----------------------------===//
325+
swiftscan_cached_compilation_t (*swiftscan_cache_query)(
326+
swiftscan_cas_t cas, const char *key, bool globally,
327+
swiftscan_string_ref_t *error);
328+
void (*swiftscan_cache_query_async)(
329+
swiftscan_cas_t cas, const char *key, bool globally, void *ctx,
330+
void (*callback)(void *ctx, swiftscan_cache_result_t,
331+
swiftscan_cached_compilation_t,
332+
swiftscan_string_ref_t error),
333+
swiftscan_cache_cancellation_token_t *);
334+
335+
void (*swiftscan_cached_compilation_dispose)(swiftscan_cached_compilation_t);
336+
337+
swiftscan_cache_result_t (*swiftscan_cache_load_cached_compilation)(
338+
swiftscan_cas_t cas, swiftscan_cached_compilation_t,
339+
swiftscan_string_ref_t *error);
340+
void (*swiftscan_cache_load_cached_compilation_async)(
341+
swiftscan_cas_t cas, swiftscan_cached_compilation_t, void *ctx,
342+
void (*callback)(void *ctx, swiftscan_cache_result_t,
343+
swiftscan_string_ref_t error),
344+
swiftscan_cache_cancellation_token_t *);
345+
346+
bool (*swiftscan_cache_compilation_is_loaded)(swiftscan_cached_compilation_t);
347+
unsigned (*swiftscan_cache_get_num_cached_outputs)(
348+
swiftscan_cached_compilation_t, swiftscan_string_ref_t *error);
349+
bool (*swiftscan_cache_compilation_has_output_kind)(
350+
swiftscan_cached_compilation_t, swiftscan_output_kind_t,
351+
swiftscan_string_ref_t *error);
352+
353+
void (*swiftscan_cache_make_global_async)(
354+
swiftscan_cas_t cas, const char *key, void *ctx,
355+
void (*callback)(void *ctx, swiftscan_string_ref_t error),
356+
swiftscan_cache_cancellation_token_t *);
357+
358+
void (*swiftscan_cache_action_cancel)(swiftscan_cache_cancellation_token_t);
359+
void (*swiftscan_cache_cancellation_token_dispose)(
360+
swiftscan_cache_cancellation_token_t);
361+
362+
swiftscan_cache_replay_instance_t (*swiftscan_cache_replay_instance_create)(
363+
int argc, const char **argv, swiftscan_string_ref_t *error);
364+
void (*swiftscan_cache_replay_instance_dispose)(
365+
swiftscan_cache_replay_instance_t);
366+
367+
swiftscan_cache_result_t (*swiftscan_cache_replay_compilation)(
368+
swiftscan_cas_t cas, swiftscan_cache_replay_instance_t,
369+
swiftscan_cached_compilation_t, swiftscan_string_ref_t *error);
370+
void (*swiftscan_cache_replay_compilation_async)(
371+
swiftscan_cas_t cas, swiftscan_cache_replay_instance_t,
372+
swiftscan_cached_compilation_t, void *ctx,
373+
void (*callback)(void *ctx, swiftscan_cache_result_t,
374+
swiftscan_string_ref_t std_out,
375+
swiftscan_string_ref_t std_err,
376+
swiftscan_string_ref_t error),
377+
swiftscan_cache_cancellation_token_t *);
296378

297379
} swiftscan_functions_t;
298380

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_library(SwiftDriver
1818
SwiftScan/DependencyGraphBuilder.swift
1919
SwiftScan/Loader.swift
2020
SwiftScan/SwiftScan.swift
21+
SwiftScan/SwiftScanCAS.swift
2122

2223
Driver/CompilerMode.swift
2324
Driver/DebugInfo.swift

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ public struct Driver {
272272
let enableCaching: Bool
273273
let useClangIncludeTree: Bool
274274

275+
var cas: SwiftScanCAS?
276+
275277
/// Scanner prefix mapping.
276278
let scannerPrefixMap: [AbsolutePath: AbsolutePath]
277279
let scannerPrefixMapSDK: AbsolutePath?
@@ -473,6 +475,14 @@ public struct Driver {
473475
return supportedFrontendFeatures.contains(feature.rawValue)
474476
}
475477

478+
@_spi(Testing)
479+
public func getCAS() throws -> SwiftScanCAS {
480+
guard let cas = self.cas else {
481+
throw DependencyScanningError.casError("CAS is not initialized but requested")
482+
}
483+
return cas
484+
}
485+
476486
@_spi(Testing)
477487
public static func findBlocklists(RelativeTo execDir: AbsolutePath) throws -> [AbsolutePath] {
478488
// Expect to find all blocklists in such dir:
@@ -1604,7 +1614,8 @@ extension Driver {
16041614
buildRecordInfo: buildRecordInfo,
16051615
showJobLifecycle: showJobLifecycle,
16061616
argsResolver: executor.resolver,
1607-
diagnosticEngine: diagnosticEngine)
1617+
diagnosticEngine: diagnosticEngine,
1618+
cas: cas)
16081619
}
16091620

16101621
private mutating func performTheBuild(

Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,21 @@ import var TSCBasic.stdoutStream
5656
private var nextBatchQuasiPID: Int
5757
private let argsResolver: ArgsResolver
5858
private var batchJobInputQuasiPIDMap = TwoLevelMap<Job, TypedVirtualPath, Int>()
59+
private var cas: SwiftScanCAS?
5960

6061
@_spi(Testing) public init(mode: ToolExecutionDelegate.Mode,
6162
buildRecordInfo: BuildRecordInfo?,
6263
showJobLifecycle: Bool,
6364
argsResolver: ArgsResolver,
64-
diagnosticEngine: DiagnosticsEngine) {
65+
diagnosticEngine: DiagnosticsEngine,
66+
cas: SwiftScanCAS? = nil) {
6567
self.mode = mode
6668
self.buildRecordInfo = buildRecordInfo
6769
self.showJobLifecycle = showJobLifecycle
6870
self.diagnosticEngine = diagnosticEngine
6971
self.argsResolver = argsResolver
7072
self.nextBatchQuasiPID = ToolExecutionDelegate.QUASI_PID_START
73+
self.cas = cas
7174
}
7275

7376
public func jobStarted(job: Job, arguments: [String], pid: Int) {
@@ -95,6 +98,19 @@ import var TSCBasic.stdoutStream
9598

9699
buildRecordInfo?.jobFinished(job: job, result: result)
97100

101+
do {
102+
if let cas = self.cas {
103+
for (input, key) in job.outputCacheKeys {
104+
guard let _ = try cas.queryCacheKey(key, globally: false) else {
105+
throw DependencyScanningError.casError("cannot find outputs for input \(input.file.name): \(key)")
106+
}
107+
}
108+
}
109+
} catch {
110+
diagnosticEngine.emit(.error("failured to lookup output from CAS: \(error)"))
111+
}
112+
113+
98114
#if os(Windows)
99115
if case .abnormal = result.exitStatus {
100116
anyJobHadAbnormalExit = true

Sources/SwiftDriver/Execution/ArgsResolver.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,16 @@ public final class ArgsResolver {
6565
public func resolveArgumentList(for job: Job, useResponseFiles: ResponseFileHandling = .heuristic)
6666
throws -> ([String], usingResponseFile: Bool) {
6767
let tool = try resolve(.path(job.tool))
68-
var arguments = [tool] + (try job.commandLine.map { try resolve($0) })
68+
var arguments = [tool] + (try resolveArgumentList(for: job.commandLine))
6969
let usingResponseFile = try createResponseFileIfNeeded(for: job, resolvedArguments: &arguments,
7070
useResponseFiles: useResponseFiles)
7171
return (arguments, usingResponseFile)
7272
}
7373

74+
public func resolveArgumentList(for commandLine: [Job.ArgTemplate]) throws -> [String] {
75+
return try commandLine.map { try resolve($0) }
76+
}
77+
7478
@available(*, deprecated, message: "use resolveArgumentList(for:,useResponseFiles:,quotePaths:)")
7579
public func resolveArgumentList(for job: Job, forceResponseFiles: Bool,
7680
quotePaths: Bool = false) throws -> [String] {

Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
4545
/// Whether we are using the integrated driver via libSwiftDriver shared lib
4646
private let integratedDriver: Bool
4747
private let mainModuleName: String?
48-
private let enableCAS: Bool
48+
private let cas: SwiftScanCAS?
4949
private let swiftScanOracle: InterModuleDependencyOracle
5050

5151
/// Clang PCM names contain a hash of the command-line arguments that were used to build them.
@@ -60,15 +60,15 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
6060
dependencyOracle: InterModuleDependencyOracle,
6161
integratedDriver: Bool = true,
6262
supportsExplicitInterfaceBuild: Bool = false,
63-
enableCAS: Bool = false) throws {
63+
cas: SwiftScanCAS? = nil) throws {
6464
self.dependencyGraph = dependencyGraph
6565
self.toolchain = toolchain
6666
self.swiftScanOracle = dependencyOracle
6767
self.integratedDriver = integratedDriver
6868
self.mainModuleName = dependencyGraph.mainModuleName
6969
self.reachabilityMap = try dependencyGraph.computeTransitiveClosure()
7070
self.supportsExplicitInterfaceBuild = supportsExplicitInterfaceBuild
71-
self.enableCAS = enableCAS
71+
self.cas = cas
7272
}
7373

7474
/// Supports resolving bridging header pch command from swiftScan.
@@ -136,9 +136,6 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
136136
for moduleId in swiftDependencies {
137137
let moduleInfo = try dependencyGraph.moduleInfo(of: moduleId)
138138
var inputs: [TypedVirtualPath] = []
139-
let outputs: [TypedVirtualPath] = [
140-
TypedVirtualPath(file: moduleInfo.modulePath.path, type: .swiftModule)
141-
]
142139
var commandLine: [Job.ArgTemplate] = []
143140
// First, take the command line options provided in the dependency information
144141
let moduleDetails = try dependencyGraph.swiftModuleDetails(of: moduleId)
@@ -155,9 +152,18 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
155152
throw Driver.Error.malformedModuleDependency(moduleId.moduleName,
156153
"no `moduleInterfacePath` object")
157154
}
158-
inputs.append(TypedVirtualPath(file: moduleInterfacePath.path,
159-
type: .swiftInterface))
160155

156+
let inputInterfacePath = TypedVirtualPath(file: moduleInterfacePath.path, type: .swiftInterface)
157+
inputs.append(inputInterfacePath)
158+
let outputModulePath = TypedVirtualPath(file: moduleInfo.modulePath.path, type: .swiftModule)
159+
let outputs = [outputModulePath]
160+
161+
let cacheKeys : [TypedVirtualPath : String]
162+
if let key = moduleDetails.moduleCacheKey {
163+
cacheKeys = [inputInterfacePath: key]
164+
} else {
165+
cacheKeys = [:]
166+
}
161167
// Add precompiled module candidates, if present
162168
if let compiledCandidateList = moduleDetails.compiledModuleCandidates {
163169
for compiledCandidate in compiledCandidateList {
@@ -173,7 +179,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
173179
commandLine: commandLine,
174180
inputs: inputs,
175181
primaryInputs: [],
176-
outputs: outputs
182+
outputs: outputs,
183+
outputCacheKeys: cacheKeys
177184
))
178185
}
179186
return jobs
@@ -205,15 +212,20 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
205212
try resolveExplicitModuleDependencies(moduleId: moduleId, inputs: &inputs,
206213
commandLine: &commandLine)
207214

208-
let moduleMapPath = moduleDetails.moduleMapPath.path
209-
let modulePCMPath = moduleInfo.modulePath
210-
outputs.append(TypedVirtualPath(file: modulePCMPath.path, type: .pcm))
215+
let moduleMapPath = TypedVirtualPath(file: moduleDetails.moduleMapPath.path, type: .clangModuleMap)
216+
let modulePCMPath = TypedVirtualPath(file: moduleInfo.modulePath.path, type: .pcm)
217+
outputs.append(modulePCMPath)
211218

212219
// The only required input is the .modulemap for this module.
213220
// Command line options in the dependency scanner output will include the
214221
// required modulemap, so here we must only add it to the list of inputs.
215-
inputs.append(TypedVirtualPath(file: moduleMapPath,
216-
type: .clangModuleMap))
222+
inputs.append(moduleMapPath)
223+
let cacheKeys : [TypedVirtualPath : String]
224+
if let key = moduleDetails.moduleCacheKey {
225+
cacheKeys = [moduleMapPath: key]
226+
} else {
227+
cacheKeys = [:]
228+
}
217229

218230
jobs.append(Job(
219231
moduleName: moduleId.moduleName,
@@ -222,7 +234,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
222234
commandLine: commandLine,
223235
inputs: inputs,
224236
primaryInputs: [],
225-
outputs: outputs
237+
outputs: outputs,
238+
outputCacheKeys: cacheKeys
226239
))
227240
}
228241
return jobs
@@ -247,7 +260,7 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
247260
type: .swiftModule))
248261

249262
let prebuiltHeaderDependencyPaths = dependencyModule.prebuiltHeaderDependencyPaths ?? []
250-
if enableCAS && !prebuiltHeaderDependencyPaths.isEmpty {
263+
if cas != nil && !prebuiltHeaderDependencyPaths.isEmpty {
251264
throw Driver.Error.unsupportedConfigurationForCaching("module \(dependencyModule.moduleName) has prebuilt header dependency")
252265
}
253266

@@ -277,9 +290,9 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
277290
try serializeModuleDependencies(for: moduleId,
278291
swiftDependencyArtifacts: swiftDependencyArtifacts,
279292
clangDependencyArtifacts: clangDependencyArtifacts)
280-
if enableCAS {
293+
if let cas = self.cas {
281294
// When using a CAS, write JSON into CAS and pass the ID on command-line.
282-
let casID = try swiftScanOracle.store(data: dependencyFileContent)
295+
let casID = try cas.store(data: dependencyFileContent)
283296
commandLine.appendFlag("-explicit-swift-module-map-file")
284297
commandLine.appendFlag(casID)
285298
} else {
@@ -445,7 +458,7 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
445458
return
446459
}
447460

448-
assert(!enableCAS, "Caching build should always return command-line from scanner")
461+
assert(cas == nil, "Caching build should always return command-line from scanner")
449462
// Prohibit the frontend from implicitly building textual modules into binary modules.
450463
commandLine.appendFlags("-disable-implicit-swift-modules",
451464
"-Xcc", "-fno-implicit-modules",

0 commit comments

Comments
 (0)