Skip to content

Commit 474fc4e

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 e200d72 commit 474fc4e

19 files changed

+708
-150
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 68 additions & 14 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,17 +78,15 @@ 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;
83-
84-
typedef enum {
85-
SWIFTSCAN_OUTPUT_TYPE_OBJECT = 0,
86-
SWIFTSCAN_OUTPUT_TYPE_SWIFTMODULE = 1,
87-
SWIFTSCAN_OUTPUT_TYPE_SWIFTINTERFACE = 2,
88-
SWIFTSCAN_OUTPUT_TYPE_SWIFTPRIVATEINTERFACE = 3,
89-
SWIFTSCAN_OUTPUT_TYPE_CLANG_MODULE = 4,
90-
SWIFTSCAN_OUTPUT_TYPE_CLANG_PCH = 5
91-
} swiftscan_output_kind_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_cached_output_s *swiftscan_cached_output_t;
85+
typedef struct swiftscan_cache_replay_instance_s
86+
*swiftscan_cache_replay_instance_t;
87+
typedef struct swiftscan_cache_replay_result_s *swiftscan_cache_replay_result_t;
88+
typedef struct swiftscan_cache_cancellation_token_s
89+
*swiftscan_cache_cancellation_token_t;
9290

9391
//=== libSwiftScan Functions ------------------------------------------------===//
9492

@@ -290,9 +288,65 @@ typedef struct {
290288
swiftscan_string_ref_t (*swiftscan_cas_store)(swiftscan_cas_t cas,
291289
uint8_t *data, unsigned size,
292290
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);
291+
swiftscan_string_ref_t (*swiftscan_cache_compute_key)(
292+
swiftscan_cas_t cas, int argc, const char **argv, const char *input,
293+
swiftscan_string_ref_t *error);
294+
295+
//=== Scanner Caching Query/Replay Operations -----------------------------===//
296+
swiftscan_cached_compilation_t (*swiftscan_cache_query)(
297+
swiftscan_cas_t cas, const char *key, bool globally,
298+
swiftscan_string_ref_t *error);
299+
void (*swiftscan_cache_query_async)(
300+
swiftscan_cas_t cas, const char *key, bool globally, void *ctx,
301+
void (*callback)(void *ctx, swiftscan_cached_compilation_t,
302+
swiftscan_string_ref_t error),
303+
swiftscan_cache_cancellation_token_t *);
304+
305+
306+
unsigned (*swiftscan_cached_compilation_get_num_outputs)(
307+
swiftscan_cached_compilation_t);
308+
swiftscan_string_ref_t (*swiftscan_cached_compilation_get_output_kind_name)(
309+
swiftscan_cached_compilation_t, unsigned idx);
310+
swiftscan_cached_output_t (*swiftscan_cached_compilation_get_output)(
311+
swiftscan_cached_compilation_t, unsigned idx);
312+
bool (*swiftscan_cached_compilation_is_uncacheable)(
313+
swiftscan_cached_compilation_t);
314+
void (*swiftscan_cached_compilation_make_global_async)(
315+
swiftscan_cas_t cas, swiftscan_cached_compilation_t, void *ctx,
316+
void (*callback)(void *ctx, swiftscan_string_ref_t error),
317+
swiftscan_cache_cancellation_token_t *);
318+
void (*swiftscan_cached_compilation_dispose)(swiftscan_cached_compilation_t);
319+
320+
bool (*swiftscan_cached_output_load)(swiftscan_cached_output_t,
321+
swiftscan_string_ref_t *error);
322+
void (*swiftscan_cached_output_load_async)(
323+
swiftscan_cached_output_t, void *ctx,
324+
void (*callback)(void *ctx, bool success, swiftscan_string_ref_t error),
325+
swiftscan_cache_cancellation_token_t *);
326+
bool (*swiftscan_cached_output_is_materialized)(swiftscan_cached_output_t);
327+
swiftscan_string_ref_t (*swiftscan_cached_output_get_casid)(
328+
swiftscan_cached_output_t);
329+
void (*swiftscan_cached_output_dispose)(swiftscan_cached_output_t);
330+
331+
void (*swiftscan_cache_action_cancel)(swiftscan_cache_cancellation_token_t);
332+
void (*swiftscan_cache_cancellation_token_dispose)(
333+
swiftscan_cache_cancellation_token_t);
334+
335+
swiftscan_cache_replay_instance_t (*swiftscan_cache_replay_instance_create)(
336+
int argc, const char **argv, swiftscan_string_ref_t *error);
337+
void (*swiftscan_cache_replay_instance_dispose)(
338+
swiftscan_cache_replay_instance_t);
339+
340+
swiftscan_cache_replay_result_t (*swiftscan_cache_replay_compilation)(
341+
swiftscan_cache_replay_instance_t, swiftscan_cached_compilation_t,
342+
swiftscan_string_ref_t *error);
343+
344+
swiftscan_string_ref_t (*swiftscan_cache_replay_result_get_stdout)(
345+
swiftscan_cache_replay_result_t);
346+
swiftscan_string_ref_t (*swiftscan_cache_replay_result_get_stderr)(
347+
swiftscan_cache_replay_result_t);
348+
void (*swiftscan_cache_replay_result_dispose)(
349+
swiftscan_cache_replay_result_t);
296350

297351
} swiftscan_functions_t;
298352

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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ public struct Driver {
274274
let enableCaching: Bool
275275
let useClangIncludeTree: Bool
276276

277+
var cas: SwiftScanCAS?
278+
277279
/// Scanner prefix mapping.
278280
let scannerPrefixMap: [AbsolutePath: AbsolutePath]
279281
let scannerPrefixMapSDK: AbsolutePath?
@@ -475,6 +477,14 @@ public struct Driver {
475477
return supportedFrontendFeatures.contains(feature.rawValue)
476478
}
477479

480+
@_spi(Testing)
481+
public func getCAS() throws -> SwiftScanCAS {
482+
guard let cas = self.cas else {
483+
throw DependencyScanningError.casError("CAS is not initialized but requested")
484+
}
485+
return cas
486+
}
487+
478488
@_spi(Testing)
479489
public static func findBlocklists(RelativeTo execDir: AbsolutePath) throws -> [AbsolutePath] {
480490
// Expect to find all blocklists in such dir:

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",

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import protocol TSCBasic.FileSystem
1414
import struct TSCBasic.AbsolutePath
1515
import struct Foundation.Data
16+
import var TSCBasic.localFileSystem
1617

1718
import Dispatch
1819

@@ -152,6 +153,13 @@ public class InterModuleDependencyOracle {
152153
return swiftScan.supportsCaching
153154
}
154155

156+
@_spi(Testing) public func supportsCacheReplay() throws -> Bool {
157+
guard let swiftScan = swiftScanLibInstance else {
158+
fatalError("Attempting to query supported scanner API with no scanner instance.")
159+
}
160+
return swiftScan.supportsCacheReplay
161+
}
162+
155163
@_spi(Testing) public func supportsBridgingHeaderPCHCommand() throws -> Bool {
156164
guard let swiftScan = swiftScanLibInstance else {
157165
fatalError("Attempting to query supported scanner API with no scanner instance.")
@@ -171,26 +179,11 @@ public class InterModuleDependencyOracle {
171179
return diags.isEmpty ? nil : diags
172180
}
173181

174-
public func createCAS(pluginPath: AbsolutePath?, onDiskPath: AbsolutePath?, pluginOptions: [(String, String)]) throws {
175-
guard let swiftScan = swiftScanLibInstance else {
176-
fatalError("Attempting to reset scanner cache with no scanner instance.")
177-
}
178-
try swiftScan.createCAS(pluginPath: pluginPath?.pathString, onDiskPath: onDiskPath?.pathString, pluginOptions: pluginOptions)
179-
}
180-
181-
public func store(data: Data) throws -> String {
182-
guard let swiftScan = swiftScanLibInstance else {
183-
fatalError("Attempting to reset scanner cache with no scanner instance.")
184-
}
185-
return try swiftScan.store(data:data)
186-
}
187-
188-
public func computeCacheKeyForOutput(kind: FileType, commandLine: [Job.ArgTemplate], input: VirtualPath.Handle?) throws -> String {
182+
@_spi(Testing) public func createCAS(pluginPath: AbsolutePath?, onDiskPath: AbsolutePath?, pluginOptions: [(String, String)]) throws -> SwiftScanCAS {
189183
guard let swiftScan = swiftScanLibInstance else {
190184
fatalError("Attempting to reset scanner cache with no scanner instance.")
191185
}
192-
let inputPath = input?.description ?? ""
193-
return try swiftScan.computeCacheKeyForOutput(kind: kind, commandLine: commandLine.stringArray, input: inputPath)
186+
return try swiftScan.createCAS(pluginPath: pluginPath?.pathString, onDiskPath: onDiskPath?.pathString, pluginOptions: pluginOptions)
194187
}
195188

196189
private var hasScannerInstance: Bool { self.swiftScanLibInstance != nil }

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,9 @@ public extension Driver {
174174
}
175175
}
176176
if !fallbackToFrontend && enableCaching {
177-
try interModuleDependencyOracle.createCAS(pluginPath: try getCASPluginPath(),
178-
onDiskPath: try getOnDiskCASPath(),
179-
pluginOptions: try getCASPluginOptions())
177+
self.cas = try interModuleDependencyOracle.createCAS(pluginPath: try getCASPluginPath(),
178+
onDiskPath: try getOnDiskCASPath(),
179+
pluginOptions: try getCASPluginOptions())
180180
}
181181
return fallbackToFrontend
182182
}

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ extension Driver {
9797
.moduleTrace, .yamlOptimizationRecord, .bitstreamOptimizationRecord, .pcm, .pch,
9898
.clangModuleMap, .jsonCompilerFeatures, .jsonTargetInfo, .jsonSwiftArtifacts,
9999
.indexUnitOutputPath, .modDepCache, .jsonAPIBaseline, .jsonABIBaseline,
100-
.swiftConstValues, .jsonAPIDescriptor, nil:
100+
.swiftConstValues, .jsonAPIDescriptor, .moduleSummary, .moduleSemanticInfo,
101+
.cachedDiagnostics, nil:
101102
return false
102103
}
103104
}
@@ -398,6 +399,9 @@ extension Driver {
398399
} else {
399400
displayInputs = primaryInputs
400401
}
402+
// Only swift input files are contributing to the cache keys.
403+
let cacheContributingInputs = displayInputs.filter() { $0.type == .swift }
404+
let cacheKeys = try computeOutputCacheKeyForJob(commandLine: commandLine, inputs: cacheContributingInputs)
401405

402406
return Job(
403407
moduleName: moduleOutputInfo.name,
@@ -408,6 +412,7 @@ extension Driver {
408412
inputs: inputs,
409413
primaryInputs: primaryInputs,
410414
outputs: outputs,
415+
outputCacheKeys: cacheKeys,
411416
inputOutputMap: inputOutputMap
412417
)
413418
}
@@ -470,7 +475,8 @@ extension FileType {
470475
.bitstreamOptimizationRecord, .swiftInterface, .privateSwiftInterface,
471476
.swiftSourceInfoFile, .clangModuleMap, .jsonSwiftArtifacts,
472477
.indexUnitOutputPath, .modDepCache, .jsonAPIBaseline, .jsonABIBaseline,
473-
.swiftConstValues, .jsonAPIDescriptor:
478+
.swiftConstValues, .jsonAPIDescriptor, .moduleSummary, .moduleSemanticInfo,
479+
.cachedDiagnostics:
474480
fatalError("Output type can never be a primary output")
475481
}
476482
}

0 commit comments

Comments
 (0)