Skip to content

Commit e9f4a0a

Browse files
author
David Ungar
authored
Merge pull request #580 from davidungar/incremental-post-compile
2 parents 1430a1f + 52ed8e4 commit e9f4a0a

File tree

10 files changed

+151
-49
lines changed

10 files changed

+151
-49
lines changed

Sources/SwiftDriver/IncrementalCompilation/BuildRecord.swift

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,32 @@ public struct BuildRecord {
1919
public let swiftVersion: String
2020
/// When testing, the argsHash may be missing from the build record
2121
public let argsHash: String?
22-
public let buildTime: Date
22+
/// Next compile, will compare an input mod time against the start time of the previous build
23+
public let buildStartTime: Date
24+
/// Next compile, will compare an output mod time against the end time of the previous build
25+
public let buildEndTime: Date
2326
/// The date is the modification time of the main input file the last time the driver ran
2427
public let inputInfos: [VirtualPath: InputInfo]
2528

2629
public init(argsHash: String?,
2730
swiftVersion: String,
28-
buildTime: Date,
31+
buildStartTime: Date,
32+
buildEndTime: Date,
2933
inputInfos: [VirtualPath: InputInfo]) {
3034
self.argsHash = argsHash
3135
self.swiftVersion = swiftVersion
32-
self.buildTime = buildTime
36+
self.buildStartTime = buildStartTime
37+
self.buildEndTime = buildEndTime
3338
self.inputInfos = inputInfos
3439
}
3540

3641
private enum SectionName: String, CaseIterable {
3742
case swiftVersion = "version"
3843
case argsHash = "options"
39-
case buildTime = "build_time"
44+
// Implement this for a smoother transition
45+
case legacyBuildStartTime = "build_time"
46+
case buildStartTime = "build_start_time"
47+
case buildEndTime = "build_end_time"
4048
case inputInfos = "inputs"
4149

4250
var serializedName: String { rawValue }
@@ -59,7 +67,8 @@ public extension BuildRecord {
5967
var argsHash: String?
6068
var swiftVersion: String?
6169
// Legacy driver does not disable incremental if no buildTime field.
62-
var buildTime: Date = .distantPast
70+
var buildStartTime: Date = .distantPast
71+
var buildEndTime: Date = .distantFuture
6372
var inputInfos: [VirtualPath: InputInfo]?
6473
for (key, value) in sections {
6574
guard let k = key.string else {
@@ -80,14 +89,23 @@ public extension BuildRecord {
8089
return nil
8190
}
8291
argsHash = s
83-
case SectionName.buildTime.serializedName:
84-
guard let d = Self.decodeDate(value,
85-
forInputInfo: false,
86-
failedToReadOutOfDateMap)
87-
else {
88-
return nil
89-
}
90-
buildTime = d
92+
case SectionName.buildStartTime.serializedName,
93+
SectionName.legacyBuildStartTime.serializedName:
94+
guard let d = Self.decodeDate(value,
95+
forInputInfo: false,
96+
failedToReadOutOfDateMap)
97+
else {
98+
return nil
99+
}
100+
buildStartTime = d
101+
case SectionName.buildEndTime.serializedName:
102+
guard let d = Self.decodeDate(value,
103+
forInputInfo: false,
104+
failedToReadOutOfDateMap)
105+
else {
106+
return nil
107+
}
108+
buildEndTime = d
91109
case SectionName.inputInfos.serializedName:
92110
guard let ii = Self.decodeInputInfos(value, failedToReadOutOfDateMap) else {
93111
return nil
@@ -111,7 +129,8 @@ public extension BuildRecord {
111129
}
112130
self.init(argsHash: argsHash,
113131
swiftVersion: sv,
114-
buildTime: buildTime,
132+
buildStartTime: buildStartTime,
133+
buildEndTime: buildEndTime,
115134
inputInfos: iis)
116135
}
117136

@@ -181,7 +200,8 @@ extension BuildRecord {
181200
compilationInputModificationDates: [TypedVirtualPath: Date],
182201
actualSwiftVersion: String,
183202
argsHash: String!,
184-
timeBeforeFirstJob: Date
203+
timeBeforeFirstJob: Date,
204+
timeAfterLastJob: Date
185205
) {
186206
let jobResultsByInput = Dictionary(uniqueKeysWithValues:
187207
finishedJobResults.flatMap { entry in
@@ -197,7 +217,8 @@ extension BuildRecord {
197217
self.init(
198218
argsHash: argsHash,
199219
swiftVersion: actualSwiftVersion,
200-
buildTime: timeBeforeFirstJob,
220+
buildStartTime: timeBeforeFirstJob,
221+
buildEndTime: timeAfterLastJob,
201222
inputInfos: Dictionary(uniqueKeysWithValues: inputInfosArray)
202223
)
203224
}
@@ -216,10 +237,11 @@ extension BuildRecord {
216237
.map {(Yams.Node($0.0, .implicit, .doubleQuoted), Self.encode($0.1))}
217238
)
218239
let fieldNodes = [
219-
(SectionName.swiftVersion, Yams.Node(swiftVersion, .implicit, .doubleQuoted)),
220-
(SectionName.argsHash, Yams.Node(currentArgsHash, .implicit, .doubleQuoted)),
221-
(SectionName.buildTime, Self.encode(buildTime)),
222-
(SectionName.inputInfos, inputInfosNode )
240+
(SectionName.swiftVersion, Yams.Node(swiftVersion, .implicit, .doubleQuoted)),
241+
(SectionName.argsHash, Yams.Node(currentArgsHash, .implicit, .doubleQuoted)),
242+
(SectionName.buildStartTime, Self.encode(buildStartTime)),
243+
(SectionName.buildEndTime, Self.encode(buildEndTime)),
244+
(SectionName.inputInfos, inputInfosNode )
223245
] .map { (Yams.Node($0.0.serializedName), $0.1) }
224246

225247
let buildRecordNode = Yams.Node(fieldNodes, .implicit, .block)

Sources/SwiftDriver/IncrementalCompilation/BuildRecordInfo.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ import SwiftOptions
167167
compilationInputModificationDates: compilationInputModificationDates,
168168
actualSwiftVersion: actualSwiftVersion,
169169
argsHash: currentArgsHash,
170-
timeBeforeFirstJob: timeBeforeFirstJob)
170+
timeBeforeFirstJob: timeBeforeFirstJob,
171+
timeAfterLastJob: Date())
171172
}
172173

173174
guard let contents = buildRecord.encode(currentArgsHash: currentArgsHash,

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public final class IncrementalCompilationState {
4646
/// Jobs to run *after* the last compile, for instance, link-editing.
4747
public let jobsAfterCompiles: [Job]
4848

49+
/// Can compare input mod times against this.
50+
private let buildStartTime: Date
51+
52+
/// In order to decide while postCompile jobs to run, need to compare their outputs against the build time
53+
private let buildEndTime: Date
54+
55+
private let fileSystem: FileSystem
56+
4957
/// A high-priority confinement queue that must be used to protect the incremental compilation state.
5058
private let confinementQueue: DispatchQueue = DispatchQueue(label: "com.apple.swift-driver.incremental-compilation-state", qos: .userInteractive)
5159

@@ -124,6 +132,9 @@ public final class IncrementalCompilationState {
124132
self.mandatoryJobsInOrder = initial.mandatoryJobsInOrder
125133
self.jobsAfterCompiles = jobsInPhases.afterCompiles
126134
self.moduleDependencyGraph = initial.graph
135+
self.buildStartTime = initial.buildStartTime
136+
self.buildEndTime = initial.buildEndTime
137+
self.fileSystem = driver.fileSystem
127138
self.driver = driver
128139
}
129140
}
@@ -150,6 +161,10 @@ extension IncrementalCompilationState {
150161
/// All of the pre-compile or compilation job (groups) known to be required
151162
/// for the first wave to execute.
152163
let mandatoryJobsInOrder: [Job]
164+
/// The last time this compilation was started. Used to compare against e.g. input file mod dates.
165+
let buildStartTime: Date
166+
/// The last time this compilation finished. Used to compare against output file mod dates
167+
let buildEndTime: Date
153168
}
154169
}
155170

@@ -290,6 +305,14 @@ extension IncrementalCompilationState {
290305
return try driver.formBatchedJobs(unbatched, showJobLifecycle: driver.showJobLifecycle)
291306
}
292307
}
308+
// MARK: - Scheduling post-compile jobs
309+
extension IncrementalCompilationState {
310+
public func canSkipPostCompile(job: Job) -> Bool {
311+
job.outputs.allSatisfy {output in
312+
let fileModTime = (try? fileSystem.lastModificationTime(for: output.file)) ?? .distantFuture
313+
return fileModTime <= buildEndTime}
314+
}
315+
}
293316

294317
// MARK: - After the build
295318
extension IncrementalCompilationState {

Sources/SwiftDriver/IncrementalCompilation/InitialStateComputer.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ extension IncrementalCompilationState {
3838
@_spi(Testing) public let dependencyDotFilesIncludeExternals: Bool = true
3939
@_spi(Testing) public let dependencyDotFilesIncludeAPINotes: Bool = false
4040

41-
@_spi(Testing) public let buildTime: Date
41+
@_spi(Testing) public let buildStartTime: Date
42+
@_spi(Testing) public let buildEndTime: Date
4243

4344
@_spi(Testing) public init(
4445
_ options: IncrementalCompilationState.Options,
@@ -72,7 +73,8 @@ extension IncrementalCompilationState {
7273
self.isCrossModuleIncrementalBuildEnabled = options.contains(.enableCrossModuleIncrementalBuild)
7374
self.verifyDependencyGraphAfterEveryImport = options.contains(.verifyDependencyGraphAfterEveryImport)
7475
self.emitDependencyDotFileAfterEveryImport = options.contains(.emitDependencyDotFileAfterEveryImport)
75-
self.buildTime = maybeBuildRecord?.buildTime ?? .distantPast
76+
self.buildStartTime = maybeBuildRecord?.buildStartTime ?? .distantPast
77+
self.buildEndTime = maybeBuildRecord?.buildEndTime ?? .distantFuture
7678
}
7779

7880
func compute(batchJobFormer: inout Driver)
@@ -102,7 +104,9 @@ extension IncrementalCompilationState {
102104

103105
return InitialState(graph: graph,
104106
skippedCompileGroups: skippedCompileGroups,
105-
mandatoryJobsInOrder: mandatoryJobsInOrder)
107+
mandatoryJobsInOrder: mandatoryJobsInOrder,
108+
buildStartTime: buildStartTime,
109+
buildEndTime: buildEndTime)
106110
}
107111
}
108112
}

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ extension ModuleDependencyGraph {
330330
return true
331331
}
332332
let fileModTime = (try? info.fileSystem.lastModificationTime(for: depFile)) ?? .distantFuture
333-
let hasChanged = fileModTime >= info.buildTime
333+
let hasChanged = fileModTime >= info.buildStartTime
334334
externalDependencyModTimeCache[externalDependency] = hasChanged
335335
return hasChanged
336336
}

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/Integrator.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,6 @@ extension ModuleDependencyGraph.Integrator {
189189
}
190190
}
191191

192-
private var buildTime: Date { destination.info.buildTime }
193-
194192
// A `moduleGraphUseNode` is used by an externalDependency key being integrated.
195193
// Remember the dependency for later processing in externalDependencies, and
196194
// also return it in results.

Sources/SwiftDriverExecution/MultiJobExecutor.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,17 +390,30 @@ class ExecuteAllJobsRule: LLBuildRule {
390390
let subtaskSuccess = try DriverBuildValue(value).success
391391
// After all compilation jobs are done, we can schedule post-compilation jobs,
392392
// including merge module and linking jobs.
393-
if inputID == allCompilationId && !context.primaryIndices.isEmpty && subtaskSuccess {
394-
context.postCompileIndices.forEach {
395-
engine.taskNeedsInput(ExecuteJobRule.RuleKey(index: $0), inputID: $0)
396-
}
393+
if inputID == allCompilationId && subtaskSuccess {
394+
schedulePostCompileJobs(engine)
397395
}
398396
allInputsSucceeded = allInputsSucceeded && subtaskSuccess
399397
} catch {
400398
allInputsSucceeded = false
401399
}
402400
}
403401

402+
/// After all compilation jobs have run, figure which, for instance link, jobs must run
403+
private func schedulePostCompileJobs(_ engine: LLTaskBuildEngine) {
404+
for postCompileIndex in context.postCompileIndices {
405+
let job = context.jobs[postCompileIndex]
406+
/// If any compile jobs ran, skip the expensive mod-time checks
407+
if context.primaryIndices.isEmpty,
408+
let incrementalCompilationState = context.incrementalCompilationState,
409+
incrementalCompilationState.canSkipPostCompile(job: job) {
410+
continue
411+
}
412+
engine.taskNeedsInput(ExecuteJobRule.RuleKey(index: postCompileIndex),
413+
inputID: postCompileIndex)
414+
}
415+
}
416+
404417
override func inputsAvailable(_ engine: LLTaskBuildEngine) {
405418
engine.taskIsComplete(DriverBuildValue.jobExecution(success: allInputsSucceeded))
406419
}

Tests/SwiftDriverTests/Helpers/AssertDiagnostics.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,18 @@ import SwiftDriver
1515
import TSCBasic
1616
import TestUtilities
1717

18-
func assertDriverDiagnostics(
18+
@discardableResult
19+
func assertDriverDiagnostics<Result> (
1920
args: [String],
2021
env: [String: String] = ProcessEnv.vars,
2122
file: StaticString = #file, line: UInt = #line,
22-
do body: (inout Driver, DiagnosticVerifier) throws -> Void
23-
) throws {
23+
do body: (inout Driver, DiagnosticVerifier) throws -> Result
24+
) throws -> Result {
2425
let matcher = DiagnosticVerifier()
2526
defer { matcher.verify(file: file, line: line) }
2627

2728
var driver = try Driver(args: args, env: env, diagnosticsEngine: DiagnosticsEngine(handlers: [matcher.emit(_:)]))
28-
try body(&driver, matcher)
29+
return try body(&driver, matcher)
2930
}
3031

3132
/// Asserts that the `Driver` it instantiates will only emit warnings and errors

0 commit comments

Comments
 (0)