Skip to content
Next Next commit
Support Swift CompileJob Caching
Add swift-driver support for compiler caching.
  • Loading branch information
cachemeifyoucan committed Jul 14, 2023
commit c4519f08fc76eae2c202b0b46767da5c6eb1b789
33 changes: 32 additions & 1 deletion Sources/CSwiftScan/include/swiftscan_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include <stdint.h>

#define SWIFTSCAN_VERSION_MAJOR 0
#define SWIFTSCAN_VERSION_MINOR 1
#define SWIFTSCAN_VERSION_MINOR 4

//=== Public Scanner Data Types -------------------------------------------===//

Expand Down Expand Up @@ -77,6 +77,18 @@ typedef struct {
typedef struct swiftscan_scan_invocation_s *swiftscan_scan_invocation_t;
typedef void *swiftscan_scanner_t;

//=== CAS/Caching Specification -------------------------------------------===//
typedef struct swiftscan_cas_s *swiftscan_cas_t;

typedef enum {
SWIFTSCAN_OUTPUT_TYPE_OBJECT = 0,
SWIFTSCAN_OUTPUT_TYPE_SWIFTMODULE = 1,
SWIFTSCAN_OUTPUT_TYPE_SWIFTINTERFACE = 2,
SWIFTSCAN_OUTPUT_TYPE_SWIFTPRIVATEINTERFACE = 3,
SWIFTSCAN_OUTPUT_TYPE_CLANG_MODULE = 4,
SWIFTSCAN_OUTPUT_TYPE_CLANG_PCH = 5
} swiftscan_output_kind_t;

//=== libSwiftScan Functions ------------------------------------------------===//

typedef struct {
Expand Down Expand Up @@ -117,13 +129,17 @@ typedef struct {
swiftscan_string_set_t *
(*swiftscan_swift_textual_detail_get_command_line)(swiftscan_module_details_t);
swiftscan_string_set_t *
(*swiftscan_swift_textual_detail_get_bridging_pch_command_line)(swiftscan_module_details_t);
swiftscan_string_set_t *
(*swiftscan_swift_textual_detail_get_extra_pcm_args)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_swift_textual_detail_get_context_hash)(swiftscan_module_details_t);
bool
(*swiftscan_swift_textual_detail_get_is_framework)(swiftscan_module_details_t);
swiftscan_string_set_t *
(*swiftscan_swift_textual_detail_get_swift_overlay_dependencies)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_swift_textual_detail_get_module_cache_key)(swiftscan_module_details_t);

//=== Swift Binary Module Details query APIs ------------------------------===//
swiftscan_string_ref_t
Expand All @@ -136,6 +152,8 @@ typedef struct {
(*swiftscan_swift_binary_detail_get_header_dependencies)(swiftscan_module_details_t);
bool
(*swiftscan_swift_binary_detail_get_is_framework)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_swift_binary_detail_get_module_cache_key)(swiftscan_module_details_t);

//=== Swift Placeholder Module Details query APIs -------------------------===//
swiftscan_string_ref_t
Expand All @@ -154,6 +172,8 @@ typedef struct {
(*swiftscan_clang_detail_get_command_line)(swiftscan_module_details_t);
swiftscan_string_set_t *
(*swiftscan_clang_detail_get_captured_pcm_args)(swiftscan_module_details_t);
swiftscan_string_ref_t
(*swiftscan_clang_detail_get_module_cache_key)(swiftscan_module_details_t);

//=== Batch Scan Input Functions ------------------------------------------===//
swiftscan_batch_scan_input_t *
Expand Down Expand Up @@ -253,6 +273,17 @@ typedef struct {
bool (*swiftscan_scanner_cache_load)(swiftscan_scanner_t scanner, const char * path);
void (*swiftscan_scanner_cache_reset)(swiftscan_scanner_t scanner);

//=== Scanner CAS Operations ----------------------------------------------===//
swiftscan_cas_t (*swiftscan_cas_create)(const char *path);
void (*swiftscan_cas_dispose)(swiftscan_cas_t cas);
swiftscan_string_ref_t (*swiftscan_cas_store)(swiftscan_cas_t cas,
uint8_t *data, unsigned size);
swiftscan_string_ref_t (*swiftscan_compute_cache_key)(swiftscan_cas_t cas,
int argc,
const char *argv,
const char *input,
swiftscan_output_kind_t);

} swiftscan_functions_t;

#endif // SWIFT_C_DEPENDENCY_SCAN_H
19 changes: 19 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ public struct Driver {
/// The working directory for the driver, if there is one.
let workingDirectory: AbsolutePath?

/// CacheKey for bridging header
var bridgingHeaderCacheKey: String? = nil

/// The set of input files
@_spi(Testing) public let inputFiles: [TypedVirtualPath]

Expand Down Expand Up @@ -263,6 +266,11 @@ public struct Driver {
/// Whether to consider incremental compilation.
let shouldAttemptIncrementalCompilation: Bool

/// CAS/Caching related options.
let enableCaching: Bool
let useClangIncludeTree: Bool
let casPath: String

/// Code & data for incremental compilation. Nil if not running in incremental mode.
/// Set during planning because needs the jobs to look at outputs.
@_spi(Testing) public private(set) var incrementalCompilationState: IncrementalCompilationState? = nil
Expand Down Expand Up @@ -571,6 +579,17 @@ public struct Driver {
diagnosticEngine: diagnosticsEngine,
compilerMode: compilerMode)

let cachingEnableOverride = parsedOptions.hasArgument(.driverExplicitModuleBuild) && env.keys.contains("SWIFT_ENABLE_CACHING")
self.enableCaching = parsedOptions.hasArgument(.cacheCompileJob) || cachingEnableOverride
self.useClangIncludeTree = enableCaching && env.keys.contains("SWIFT_CACHING_USE_INCLUDE_TREE")
if let casPathOpt = parsedOptions.getLastArgument(.casPath)?.asSingle {
self.casPath = casPathOpt.description
} else if let cacheEnv = env["CCHROOT"] {
self.casPath = cacheEnv
} else {
self.casPath = ""
}

// Compute the working directory.
workingDirectory = try parsedOptions.getLastArgument(.workingDirectory).map { workingDirectoryArg in
let cwd = fileSystem.currentWorkingDirectory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
/// Whether we are using the integrated driver via libSwiftDriver shared lib
private let integratedDriver: Bool
private let mainModuleName: String?
private let enableCAS: Bool
private let swiftScanOracle: InterModuleDependencyOracle

/// Clang PCM names contain a hash of the command-line arguments that were used to build them.
/// We avoid re-running the hash computation with the use of this cache
Expand All @@ -55,14 +57,18 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT

public init(dependencyGraph: InterModuleDependencyGraph,
toolchain: Toolchain,
dependencyOracle: InterModuleDependencyOracle,
integratedDriver: Bool = true,
supportsExplicitInterfaceBuild: Bool = false) throws {
supportsExplicitInterfaceBuild: Bool = false,
enableCAS: Bool = false) throws {
self.dependencyGraph = dependencyGraph
self.toolchain = toolchain
self.swiftScanOracle = dependencyOracle
self.integratedDriver = integratedDriver
self.mainModuleName = dependencyGraph.mainModuleName
self.reachabilityMap = try dependencyGraph.computeTransitiveClosure()
self.supportsExplicitInterfaceBuild = supportsExplicitInterfaceBuild
self.enableCAS = enableCAS
}

/// Generate build jobs for all dependencies of the main module.
Expand Down Expand Up @@ -136,7 +142,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
// Resolve all dependency module inputs for this Swift module
try resolveExplicitModuleDependencies(moduleId: moduleId,
inputs: &inputs,
commandLine: &commandLine)
commandLine: &commandLine,
isMainModule: false)

// Build the .swiftinterfaces file using a list of command line options specified in the
// `details` field.
Expand Down Expand Up @@ -192,7 +199,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT

// Resolve all dependency module inputs for this Clang module
try resolveExplicitModuleDependencies(moduleId: moduleId, inputs: &inputs,
commandLine: &commandLine)
commandLine: &commandLine,
isMainModule: false)

let moduleMapPath = moduleDetails.moduleMapPath.path
let modulePCMPath = moduleInfo.modulePath
Expand Down Expand Up @@ -221,7 +229,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
/// to use explicitly-built module dependencies.
private mutating func resolveExplicitModuleDependencies(moduleId: ModuleDependencyId,
inputs: inout [TypedVirtualPath],
commandLine: inout [Job.ArgTemplate]) throws {
commandLine: inout [Job.ArgTemplate],
isMainModule: Bool) throws {
// Prohibit the frontend from implicitly building textual modules into binary modules.
var swiftDependencyArtifacts: [SwiftModuleArtifactInfo] = []
var clangDependencyArtifacts: [ClangModuleArtifactInfo] = []
Expand Down Expand Up @@ -256,15 +265,24 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT

// Swift Main Module dependencies are passed encoded in a JSON file as described by
// SwiftModuleArtifactInfo
if moduleId.moduleName == mainModuleName {
let dependencyFile =
try serializeModuleDependencies(for: moduleId,
swiftDependencyArtifacts: swiftDependencyArtifacts,
clangDependencyArtifacts: clangDependencyArtifacts)
commandLine.appendFlag("-explicit-swift-module-map-file")
commandLine.appendPath(dependencyFile)
inputs.append(TypedVirtualPath(file: dependencyFile.intern(),
type: .jsonSwiftArtifacts))
if isMainModule {
if enableCAS {
let dependencyFile =
try serializeModuleDependenciesToCAS(for: moduleId,
swiftDependencyArtifacts: swiftDependencyArtifacts,
clangDependencyArtifacts: clangDependencyArtifacts)
commandLine.appendFlag("-explicit-swift-module-map-file")
commandLine.appendFlag(dependencyFile)
} else {
let dependencyFile =
try serializeModuleDependencies(for: moduleId,
swiftDependencyArtifacts: swiftDependencyArtifacts,
clangDependencyArtifacts: clangDependencyArtifacts)
commandLine.appendFlag("-explicit-swift-module-map-file")
commandLine.appendPath(dependencyFile)
inputs.append(TypedVirtualPath(file: dependencyFile.intern(),
type: .jsonSwiftArtifacts))
}
}
}

Expand All @@ -280,13 +298,15 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
let isFramework: Bool
swiftModulePath = .init(file: dependencyInfo.modulePath.path,
type: .swiftModule)
isFramework = try dependencyGraph.swiftModuleDetails(of: dependencyId).isFramework ?? false
let swiftModuleDetails = try dependencyGraph.swiftModuleDetails(of: dependencyId)
isFramework = swiftModuleDetails.isFramework ?? false
// Accumulate the required information about this dependency
// TODO: add .swiftdoc and .swiftsourceinfo for this module.
swiftDependencyArtifacts.append(
SwiftModuleArtifactInfo(name: dependencyId.moduleName,
modulePath: TextualVirtualPath(path: swiftModulePath.fileHandle),
isFramework: isFramework))
isFramework: isFramework,
moduleCacheKey: swiftModuleDetails.moduleCacheKey))
case .clang:
let dependencyInfo = try dependencyGraph.moduleInfo(of: dependencyId)
let dependencyClangModuleDetails =
Expand All @@ -295,7 +315,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
clangDependencyArtifacts.append(
ClangModuleArtifactInfo(name: dependencyId.moduleName,
modulePath: TextualVirtualPath(path: dependencyInfo.modulePath.path),
moduleMapPath: dependencyClangModuleDetails.moduleMapPath))
moduleMapPath: dependencyClangModuleDetails.moduleMapPath,
moduleCacheKey: dependencyClangModuleDetails.moduleCacheKey))
case .swiftPrebuiltExternal:
let prebuiltModuleDetails = try dependencyGraph.swiftPrebuiltDetails(of: dependencyId)
let compiledModulePath = prebuiltModuleDetails.compiledModulePath
Expand All @@ -308,7 +329,8 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
SwiftModuleArtifactInfo(name: dependencyId.moduleName,
modulePath: TextualVirtualPath(path: swiftModulePath.fileHandle),
headerDependencies: prebuiltModuleDetails.headerDependencyPaths,
isFramework: isFramework))
isFramework: isFramework,
moduleCacheKey: prebuiltModuleDetails.moduleCacheKey))
case .swiftPlaceholder:
fatalError("Unresolved placeholder dependencies at planning stage: \(dependencyId) of \(moduleId)")
}
Expand Down Expand Up @@ -354,24 +376,25 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
public mutating func resolveMainModuleDependencies(inputs: inout [TypedVirtualPath],
commandLine: inout [Job.ArgTemplate]) throws {
let mainModuleId: ModuleDependencyId = .swift(dependencyGraph.mainModuleName)

let mainModuleDetails = try dependencyGraph.swiftModuleDetails(of: mainModuleId)
if let additionalArgs = mainModuleDetails.commandLine {
additionalArgs.forEach { commandLine.appendFlag($0) }
}
commandLine.appendFlags("-disable-implicit-swift-modules",
"-Xcc", "-fno-implicit-modules",
"-Xcc", "-fno-implicit-module-maps")
try resolveExplicitModuleDependencies(moduleId: mainModuleId,
inputs: &inputs,
commandLine: &commandLine)
commandLine: &commandLine,
isMainModule: true)
}

/// Resolve all module dependencies of the main module and add them to the lists of
/// inputs and command line flags.
public mutating func resolveBridgingHeaderDependencies(inputs: inout [TypedVirtualPath],
commandLine: inout [Job.ArgTemplate]) throws {
let mainModuleId: ModuleDependencyId = .swift(dependencyGraph.mainModuleName)
// Prohibit the frontend from implicitly building textual modules into binary modules.
commandLine.appendFlags("-disable-implicit-swift-modules",
"-Xcc", "-fno-implicit-modules",
"-Xcc", "-fno-implicit-module-maps")

var swiftDependencyArtifacts: [SwiftModuleArtifactInfo] = []
var clangDependencyArtifacts: [ClangModuleArtifactInfo] = []
let mainModuleDetails = try dependencyGraph.swiftModuleDetails(of: mainModuleId)
Expand Down Expand Up @@ -409,14 +432,34 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
inputs.append(clangModuleMapPath)
}

let dependencyFile =
try serializeModuleDependencies(for: mainModuleId,
swiftDependencyArtifacts: swiftDependencyArtifacts,
clangDependencyArtifacts: clangDependencyArtifacts)
commandLine.appendFlag("-explicit-swift-module-map-file")
commandLine.appendPath(dependencyFile)
inputs.append(TypedVirtualPath(file: dependencyFile.intern(),
type: .jsonSwiftArtifacts))
// Return if depscanner provided build commands.
if enableCAS, let scannerPCHArgs = mainModuleDetails.bridgingPchCommandLine {
scannerPCHArgs.forEach { commandLine.appendFlag($0) }
return
}

// Prohibit the frontend from implicitly building textual modules into binary modules.
commandLine.appendFlags("-disable-implicit-swift-modules",
"-Xcc", "-fno-implicit-modules",
"-Xcc", "-fno-implicit-module-maps")

if enableCAS {
let dependencyFile =
try serializeModuleDependenciesToCAS(for: mainModuleId,
swiftDependencyArtifacts: swiftDependencyArtifacts,
clangDependencyArtifacts: clangDependencyArtifacts)
commandLine.appendFlag("-explicit-swift-module-map-file")
commandLine.appendFlag(dependencyFile)
} else {
let dependencyFile =
try serializeModuleDependencies(for: mainModuleId,
swiftDependencyArtifacts: swiftDependencyArtifacts,
clangDependencyArtifacts: clangDependencyArtifacts)
commandLine.appendFlag("-explicit-swift-module-map-file")
commandLine.appendPath(dependencyFile)
inputs.append(TypedVirtualPath(file: dependencyFile.intern(),
type: .jsonSwiftArtifacts))
}
}

/// Store the output file artifacts for a given module in a JSON file, return the file's path.
Expand All @@ -433,6 +476,22 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
return VirtualPath.createUniqueTemporaryFileWithKnownContents(.init("\(moduleId.moduleName)-dependencies.json"), contents)
}

private func serializeModuleDependenciesToCAS(for moduleId: ModuleDependencyId,
swiftDependencyArtifacts: [SwiftModuleArtifactInfo],
clangDependencyArtifacts: [ClangModuleArtifactInfo]
) throws -> String {
// The module dependency map in CAS needs to be stable.
// Sort the dependencies by name.
let allDependencyArtifacts: [ModuleDependencyArtifactInfo] =
swiftDependencyArtifacts.sorted().map {ModuleDependencyArtifactInfo.swift($0)} +
clangDependencyArtifacts.sorted().map {ModuleDependencyArtifactInfo.clang($0)}
let encoder = JSONEncoder()
// Use sorted key to ensure the order of the keys is stable.
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let contents = try encoder.encode(allDependencyArtifacts)
return try swiftScanOracle.store(data: contents)
}

private func getPCMHashParts(pcmArgs: [String], contextHash: String) -> [String] {
var results: [String] = []
results.append(contextHash)
Expand Down
Loading