@@ -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 build 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 {
@@ -199,18 +222,20 @@ extension IncrementalCompilationState {
199
222
/// Careful: job may not be primary.
200
223
public func collectJobsDiscoveredToBeNeededAfterFinishing(
201
224
job finishedJob: Job ) throws -> [ Job ] ? {
202
- // Find and deal with inputs that now need to be compiled
203
- let invalidatedInputs = collectInputsInvalidatedByRunning ( finishedJob)
204
- assert ( Set ( invalidatedInputs) . isDisjoint ( with: finishedJob. primaryInputs) ,
205
- " Primaries should not overlap secondaries. " )
206
-
207
- if let reporter = self . reporter {
208
- for input in invalidatedInputs {
209
- reporter. report (
210
- " Queuing because of dependencies discovered later: " , input)
225
+ return try self . confinementQueue. sync {
226
+ // Find and deal with inputs that now need to be compiled
227
+ let invalidatedInputs = collectInputsInvalidatedByRunning ( finishedJob)
228
+ assert ( Set ( invalidatedInputs) . isDisjoint ( with: finishedJob. primaryInputs) ,
229
+ " Primaries should not overlap secondaries. " )
230
+
231
+ if let reporter = self . reporter {
232
+ for input in invalidatedInputs {
233
+ reporter. report (
234
+ " Queuing because of dependencies discovered later: " , input)
235
+ }
211
236
}
237
+ return try getJobs ( for: invalidatedInputs)
212
238
}
213
- return try getJobs ( for: invalidatedInputs)
214
239
}
215
240
216
241
/// Needed for API compatibility, `result` will be ignored
@@ -222,6 +247,7 @@ extension IncrementalCompilationState {
222
247
223
248
/// After `job` finished find out which inputs must compiled that were not known to need compilation before
224
249
private func collectInputsInvalidatedByRunning( _ job: Job ) -> Set < TypedVirtualPath > {
250
+ dispatchPrecondition ( condition: . onQueue( self . confinementQueue) )
225
251
guard job. kind == . compile else {
226
252
return Set < TypedVirtualPath > ( )
227
253
}
@@ -231,8 +257,10 @@ extension IncrementalCompilationState {
231
257
. subtracting ( job. primaryInputs) // have already compiled these
232
258
}
233
259
234
- private func collectInputsInvalidated( byCompiling input: TypedVirtualPath )
235
- -> TransitivelyInvalidatedInputSet {
260
+ private func collectInputsInvalidated(
261
+ byCompiling input: TypedVirtualPath
262
+ ) -> TransitivelyInvalidatedInputSet {
263
+ dispatchPrecondition ( condition: . onQueue( self . confinementQueue) )
236
264
if let found = moduleDependencyGraph. collectInputsRequiringCompilation ( byCompiling: input) {
237
265
return found
238
266
}
@@ -242,8 +270,10 @@ extension IncrementalCompilationState {
242
270
}
243
271
244
272
/// Find the jobs that now must be run that were not originally known to be needed.
245
- private func getJobs( for invalidatedInputs: Set < TypedVirtualPath >
273
+ private func getJobs(
274
+ for invalidatedInputs: Set < TypedVirtualPath >
246
275
) throws -> [ Job ] {
276
+ dispatchPrecondition ( condition: . onQueue( self . confinementQueue) )
247
277
let unbatched = invalidatedInputs. flatMap { input -> [ Job ] in
248
278
if let group = skippedCompileGroups. removeValue ( forKey: input) {
249
279
let primaryInputs = group. compileJob. primaryInputs
@@ -264,12 +294,16 @@ extension IncrementalCompilationState {
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