@@ -13,7 +13,25 @@ import TSCBasic
13
13
import Foundation
14
14
import SwiftOptions
15
15
16
- public class IncrementalCompilationState {
16
+ /// An instance of `IncrementalCompilationState` encapsulates the data necessary
17
+ /// to make incremental builds scheduling decisions.
18
+ ///
19
+ /// The primary form of interaction with the incremental compilation state is
20
+ /// using it as an oracle to discover the jobs to execute as the incremental
21
+ /// build progresses. After a job completes, call
22
+ /// `IncrementalCompilationState.collectJobsDiscoveredToBeNeededAfterFinishing(job:)`
23
+ /// to both update the incremental state and recieve an array of jobs that
24
+ /// need to be executed in response.
25
+ ///
26
+ /// Jobs become "unstuck" as their inputs become available, or may be discovered
27
+ /// by this class as fresh dependency information is integrated.
28
+ ///
29
+ /// Threading Considerations
30
+ /// ========================
31
+ ///
32
+ /// The public API surface of this class is thread safe, but not re-entrant.
33
+ /// FIXME: This should be an actor.
34
+ public final class IncrementalCompilationState {
17
35
18
36
/// The oracle for deciding what depends on what. Applies to this whole module.
19
37
@_spi ( Testing) public let moduleDependencyGraph : ModuleDependencyGraph
@@ -25,15 +43,24 @@ public class IncrementalCompilationState {
25
43
/// Already batched, and in order of input files.
26
44
public let mandatoryJobsInOrder : [ Job ]
27
45
46
+ /// Jobs to run *after* the last compile, for instance, link-editing.
47
+ public let jobsAfterCompiles : [ Job ]
48
+
49
+ /// A high-priority confinement queue that must be used to protect the incremental compilation state.
50
+ private let confinementQueue : DispatchQueue = DispatchQueue ( label: " com.apple.swift-driver.incremental-compilation-state " , qos: . userInteractive)
51
+
28
52
/// Sadly, has to be `var` for formBatchedJobs
53
+ ///
54
+ /// After initialization, mutating accesses to the driver must be protected by
55
+ /// the confinement queue.
29
56
private var driver : Driver
30
57
31
58
/// Keyed by primary input. As required compilations are discovered after the first wave, these shrink.
59
+ ///
60
+ /// This state is modified during the incremental build. All accesses must
61
+ /// be protected by the confinement queue.
32
62
private var skippedCompileGroups = [ TypedVirtualPath: CompileJobGroup] ( )
33
63
34
- /// Jobs to run *after* the last compile, for instance, link-editing.
35
- public let jobsAfterCompiles : [ Job ]
36
-
37
64
// MARK: - Creating IncrementalCompilationState if possible
38
65
/// Return nil if not compiling incrementally
39
66
init ? (
@@ -124,10 +151,6 @@ extension IncrementalCompilationState {
124
151
/// for the first wave to execute.
125
152
let mandatoryJobsInOrder : [ Job ]
126
153
}
127
-
128
-
129
-
130
-
131
154
}
132
155
133
156
extension Driver {
@@ -203,7 +226,7 @@ extension IncrementalCompilationState {
203
226
let invalidatedInputs = collectInputsInvalidatedByRunning ( finishedJob)
204
227
assert ( Set ( invalidatedInputs) . isDisjoint ( with: finishedJob. primaryInputs) ,
205
228
" Primaries should not overlap secondaries. " )
206
-
229
+
207
230
if let reporter = self . reporter {
208
231
for input in invalidatedInputs {
209
232
reporter. report (
@@ -222,17 +245,21 @@ extension IncrementalCompilationState {
222
245
223
246
/// After `job` finished find out which inputs must compiled that were not known to need compilation before
224
247
private func collectInputsInvalidatedByRunning( _ job: Job ) -> Set < TypedVirtualPath > {
225
- guard job. kind == . compile else {
226
- return Set < TypedVirtualPath > ( )
227
- }
228
- return job. primaryInputs. reduce ( into: Set ( ) ) { invalidatedInputs, primaryInput in
229
- invalidatedInputs. formUnion ( collectInputsInvalidated ( byCompiling: primaryInput) )
248
+ return self . confinementQueue. sync {
249
+ guard job. kind == . compile else {
250
+ return Set < TypedVirtualPath > ( )
251
+ }
252
+ return job. primaryInputs. reduce ( into: Set ( ) ) { invalidatedInputs, primaryInput in
253
+ invalidatedInputs. formUnion ( collectInputsInvalidated ( byCompiling: primaryInput) )
254
+ }
255
+ . subtracting ( job. primaryInputs) // have already compiled these
230
256
}
231
- . subtracting ( job. primaryInputs) // have already compiled these
232
257
}
233
258
234
- private func collectInputsInvalidated( byCompiling input: TypedVirtualPath )
235
- -> TransitivelyInvalidatedInputSet {
259
+ private func collectInputsInvalidated(
260
+ byCompiling input: TypedVirtualPath
261
+ ) -> TransitivelyInvalidatedInputSet {
262
+ dispatchPrecondition ( condition: . onQueue( self . confinementQueue) )
236
263
if let found = moduleDependencyGraph. collectInputsRequiringCompilation ( byCompiling: input) {
237
264
return found
238
265
}
@@ -242,34 +269,41 @@ extension IncrementalCompilationState {
242
269
}
243
270
244
271
/// Find the jobs that now must be run that were not originally known to be needed.
245
- private func getJobs( for invalidatedInputs: Set < TypedVirtualPath >
272
+ private func getJobs(
273
+ for invalidatedInputs: Set < TypedVirtualPath >
246
274
) throws -> [ Job ] {
247
- let unbatched = invalidatedInputs. flatMap { input -> [ Job ] in
248
- if let group = skippedCompileGroups. removeValue ( forKey: input) {
249
- let primaryInputs = group. compileJob. primaryInputs
250
- assert ( primaryInputs. count == 1 )
251
- assert ( primaryInputs [ 0 ] == input)
252
- self . reporter? . report ( " Scheduling invalidated " , input)
253
- return group. allJobs ( )
254
- }
255
- else {
256
- self . reporter? . report ( " Tried to schedule invalidated input again " , input)
257
- return [ ]
275
+ return try self . confinementQueue. sync {
276
+ let unbatched = invalidatedInputs. flatMap { input -> [ Job ] in
277
+ if let group = skippedCompileGroups. removeValue ( forKey: input) {
278
+ let primaryInputs = group. compileJob. primaryInputs
279
+ assert ( primaryInputs. count == 1 )
280
+ assert ( primaryInputs [ 0 ] == input)
281
+ self . reporter? . report ( " Scheduling invalidated " , input)
282
+ return group. allJobs ( )
283
+ }
284
+ else {
285
+ self . reporter? . report ( " Tried to schedule invalidated input again " , input)
286
+ return [ ]
287
+ }
258
288
}
289
+ return try driver. formBatchedJobs ( unbatched, showJobLifecycle: driver. showJobLifecycle)
259
290
}
260
- return try driver. formBatchedJobs ( unbatched, showJobLifecycle: driver. showJobLifecycle)
261
291
}
262
292
}
263
293
264
294
// MARK: - After the build
265
295
extension IncrementalCompilationState {
266
296
var skippedCompilationInputs : Set < TypedVirtualPath > {
267
- Set ( skippedCompileGroups. keys)
297
+ return self . confinementQueue. sync {
298
+ Set ( skippedCompileGroups. keys)
299
+ }
268
300
}
269
301
public var skippedJobs : [ Job ] {
270
- skippedCompileGroups. values
271
- . sorted { $0. primaryInput. file. name < $1. primaryInput. file. name}
272
- . flatMap { $0. allJobs ( ) }
302
+ return self . confinementQueue. sync {
303
+ skippedCompileGroups. values
304
+ . sorted { $0. primaryInput. file. name < $1. primaryInput. file. name}
305
+ . flatMap { $0. allJobs ( ) }
306
+ }
273
307
}
274
308
}
275
309
0 commit comments