Skip to content

Support Swift CompileJob Caching #1393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
24 changes: 24 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ public struct Driver {
/// The working directory for the driver, if there is one.
let workingDirectory: AbsolutePath?

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

/// CacheKey for swift interface
var swiftInterfaceCacheKey: String? = nil
/// CacheKey for private swift interface
var privateSwiftInterfaceCacheKey: String? = nil

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

Expand Down Expand Up @@ -263,6 +271,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 +584,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