Skip to content

Commit 5cc1fb1

Browse files
Merge pull request #1825 from cachemeifyoucan/eng/PR-swift-driver-perf-for-caching
2 parents de736e4 + 5ea08fe commit 5cc1fb1

File tree

4 files changed

+112
-37
lines changed

4 files changed

+112
-37
lines changed

Sources/SwiftDriver/Jobs/CompileJob.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ extension Driver {
231231
outputType: FileType?,
232232
addJobOutputs: ([TypedVirtualPath]) -> Void,
233233
pchCompileJob: Job?,
234-
emitModuleTrace: Bool)
234+
emitModuleTrace: Bool,
235+
produceCacheKey: Bool)
235236
throws -> Job {
236237
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
237238
var inputs: [TypedVirtualPath] = []

Sources/SwiftDriver/Jobs/Planning.swift

+19-13
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,17 @@ extension Driver {
9090
incrementalCompilationState = nil
9191
}
9292

93-
return try (
94-
// For compatibility with swiftpm, the driver produces batched jobs
95-
// for every job, even when run in incremental mode, so that all jobs
96-
// can be returned from `planBuild`.
97-
// But in that case, don't emit lifecycle messages.
98-
formBatchedJobs(jobsInPhases.allJobs,
99-
showJobLifecycle: showJobLifecycle && incrementalCompilationState == nil,
100-
jobCreatingPch: jobsInPhases.allJobs.first(where: {$0.kind == .generatePCH})),
101-
incrementalCompilationState
102-
)
93+
let batchedJobs: [Job]
94+
// If the jobs are batched during the incremental build, reuse the computation rather than computing the batches again.
95+
if let incrementalState = incrementalCompilationState {
96+
batchedJobs = incrementalState.mandatoryJobsInOrder + incrementalState.jobsAfterCompiles
97+
} else {
98+
batchedJobs = try formBatchedJobs(jobsInPhases.allJobs,
99+
showJobLifecycle: showJobLifecycle,
100+
jobCreatingPch: jobsInPhases.allJobs.first(where: {$0.kind == .generatePCH}))
101+
}
102+
103+
return (batchedJobs, incrementalCompilationState)
103104
}
104105

105106
/// If performing an explicit module build, compute an inter-module dependency graph.
@@ -355,7 +356,8 @@ extension Driver {
355356
outputType: compilerOutputType,
356357
addJobOutputs: addJobOutputs,
357358
pchCompileJob: pchCompileJob,
358-
emitModuleTrace: emitModuleTrace)
359+
emitModuleTrace: emitModuleTrace,
360+
produceCacheKey: true)
359361
addJob(compile)
360362
return compile
361363
}
@@ -446,11 +448,14 @@ extension Driver {
446448
// We can skip the compile jobs if all we want is a module when it's
447449
// built separately.
448450
if parsedOptions.hasArgument(.driverExplicitModuleBuild), canSkipIfOnlyModule { return }
451+
// If we are in the batch mode, the constructed jobs here will be batched
452+
// later. There is no need to produce cache key for the job.
449453
let compile = try compileJob(primaryInputs: [primaryInput],
450454
outputType: compilerOutputType,
451455
addJobOutputs: addJobOutputs,
452456
pchCompileJob: pchCompileJob,
453-
emitModuleTrace: emitModuleTrace)
457+
emitModuleTrace: emitModuleTrace,
458+
produceCacheKey: !compilerMode.isBatchCompile)
454459
addCompileJob(compile)
455460
}
456461

@@ -872,7 +877,8 @@ extension Driver {
872877
outputType: compilerOutputType,
873878
addJobOutputs: {_ in },
874879
pchCompileJob: jobCreatingPch,
875-
emitModuleTrace: constituentsEmittedModuleTrace)
880+
emitModuleTrace: constituentsEmittedModuleTrace,
881+
produceCacheKey: true)
876882
}
877883
return batchedCompileJobs + noncompileJobs
878884
}

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

+39-14
Original file line numberDiff line numberDiff line change
@@ -674,20 +674,45 @@ private extension swiftscan_functions_t {
674674
}
675675
}
676676

677-
// TODO: Move to TSC?
678-
/// Perform an `action` passing it a `const char **` constructed out of `[String]`
679-
@_spi(Testing) public func withArrayOfCStrings<T>(_ strings: [String],
680-
_ action: (UnsafeMutablePointer<UnsafePointer<Int8>?>?) -> T) -> T
681-
{
682-
#if os(Windows)
683-
let cstrings = strings.map { _strdup($0) } + [nil]
684-
#else
685-
let cstrings = strings.map { strdup($0) } + [nil]
686-
#endif
687-
let unsafeCStrings = cstrings.map { UnsafePointer($0) }
688-
let result = unsafeCStrings.withUnsafeBufferPointer {
689-
action(UnsafeMutablePointer(mutating: $0.baseAddress))
677+
// TODO: Move the following functions to TSC?
678+
/// Helper function to scan a sequence type to help generate pointers for C String Arrays.
679+
func scan<
680+
S: Sequence, U
681+
>(_ seq: S, _ initial: U, _ combine: (U, S.Element) -> U) -> [U] {
682+
var result: [U] = []
683+
result.reserveCapacity(seq.underestimatedCount)
684+
var runningResult = initial
685+
for element in seq {
686+
runningResult = combine(runningResult, element)
687+
result.append(runningResult)
690688
}
691-
for ptr in cstrings { if let ptr = ptr { free(ptr) } }
692689
return result
693690
}
691+
692+
/// Perform an `action` passing it a `const char **` constructed out of `[String]`
693+
@_spi(Testing) public func withArrayOfCStrings<T>(
694+
_ args: [String],
695+
_ body: (UnsafeMutablePointer<UnsafePointer<Int8>?>?) -> T
696+
) -> T {
697+
let argsCounts = Array(args.map { $0.utf8.count + 1 })
698+
let argsOffsets = [0] + scan(argsCounts, 0, +)
699+
let argsBufferSize = argsOffsets.last!
700+
var argsBuffer: [UInt8] = []
701+
argsBuffer.reserveCapacity(argsBufferSize)
702+
for arg in args {
703+
argsBuffer.append(contentsOf: arg.utf8)
704+
argsBuffer.append(0)
705+
}
706+
return argsBuffer.withUnsafeMutableBufferPointer {
707+
(argsBuffer) in
708+
let ptr = UnsafeRawPointer(argsBuffer.baseAddress!).bindMemory(
709+
to: Int8.self, capacity: argsBuffer.count)
710+
var cStrings: [UnsafePointer<Int8>?] = argsOffsets.map { ptr + $0 }
711+
cStrings[cStrings.count - 1] = nil
712+
return cStrings.withUnsafeMutableBufferPointer {
713+
let unsafeString = UnsafeMutableRawPointer($0.baseAddress!).bindMemory(
714+
to: UnsafePointer<Int8>?.self, capacity: $0.count)
715+
return body(unsafeString)
716+
}
717+
}
718+
}

Tests/SwiftDriverTests/CachingBuildTests.swift

+52-9
Original file line numberDiff line numberDiff line change
@@ -986,17 +986,60 @@ final class CachingBuildTests: XCTestCase {
986986
XCTFail("Cached compilation doesn't have a CAS")
987987
}
988988
try checkCASForResults(jobs: jobs, cas: cas, fs: driver.fileSystem)
989+
}
990+
}
989991

990-
// try replan the job and make sure some key command-line options are generated.
991-
let rebuildJobs = try driver.planBuild()
992-
for job in rebuildJobs {
993-
if job.kind == .compile || job.kind == .emitModule {
994-
XCTAssertTrue(job.commandLine.contains(.flag(String("-disable-implicit-swift-modules"))))
995-
XCTAssertTrue(job.commandLine.contains(.flag(String("-cache-compile-job"))))
996-
XCTAssertTrue(job.commandLine.contains(.flag(String("-cas-path"))))
997-
XCTAssertTrue(job.commandLine.contains(.flag(String("-bridging-header-pch-key"))))
998-
}
992+
func testCacheBatchBuildPlan() throws {
993+
try withTemporaryDirectory { path in
994+
try localFileSystem.changeCurrentWorkingDirectory(to: path)
995+
let moduleCachePath = path.appending(component: "ModuleCache")
996+
let casPath = path.appending(component: "cas")
997+
try localFileSystem.createDirectory(moduleCachePath)
998+
let main = path.appending(component: "testCachingBuild.swift")
999+
let mainFileContent = "import C;import E;import G;"
1000+
try localFileSystem.writeFileContents(main) {
1001+
$0.send(mainFileContent)
9991002
}
1003+
let ofm = path.appending(component: "ofm.json")
1004+
let inputPathsAndContents: [(AbsolutePath, String)] = [(main, mainFileContent)]
1005+
OutputFileMapCreator.write(
1006+
module: "Test", inputPaths: inputPathsAndContents.map {$0.0},
1007+
derivedData: path, to: ofm, excludeMainEntry: false)
1008+
1009+
let cHeadersPath: AbsolutePath =
1010+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
1011+
.appending(component: "CHeaders")
1012+
let swiftModuleInterfacesPath: AbsolutePath =
1013+
try testInputsPath.appending(component: "ExplicitModuleBuilds")
1014+
.appending(component: "Swift")
1015+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
1016+
let bridgingHeaderpath: AbsolutePath =
1017+
cHeadersPath.appending(component: "Bridging.h")
1018+
var driver = try Driver(args: ["swiftc",
1019+
"-I", cHeadersPath.nativePathString(escaped: true),
1020+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
1021+
"-explicit-module-build", "-Rcache-compile-job", "-incremental",
1022+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
1023+
"-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true),
1024+
"-import-objc-header", bridgingHeaderpath.nativePathString(escaped: true),
1025+
"-output-file-map", ofm.nativePathString(escaped: true),
1026+
"-working-directory", path.nativePathString(escaped: true),
1027+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
1028+
interModuleDependencyOracle: dependencyOracle)
1029+
let jobs = try driver.planBuild()
1030+
try driver.run(jobs: jobs)
1031+
XCTAssertFalse(driver.diagnosticEngine.hasErrors)
1032+
1033+
let scanLibPath = try XCTUnwrap(driver.getSwiftScanLibPath())
1034+
try dependencyOracle.verifyOrCreateScannerInstance(swiftScanLibPath: scanLibPath)
1035+
1036+
let cas = try dependencyOracle.getOrCreateCAS(pluginPath: nil, onDiskPath: casPath, pluginOptions: [])
1037+
if let driverCAS = driver.cas {
1038+
XCTAssertEqual(cas, driverCAS, "CAS should only be created once")
1039+
} else {
1040+
XCTFail("Cached compilation doesn't have a CAS")
1041+
}
1042+
try checkCASForResults(jobs: jobs, cas: cas, fs: driver.fileSystem)
10001043
}
10011044
}
10021045

0 commit comments

Comments
 (0)