Skip to content

Commit d405e35

Browse files
author
David Ungar
committed
Interleave waves
1 parent a4b7055 commit d405e35

File tree

11 files changed

+457
-296
lines changed

11 files changed

+457
-296
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -880,29 +880,16 @@ extension Driver {
880880
allJobs: [Job],
881881
forceResponseFiles: Bool
882882
) throws {
883-
// Create and use the tool execution delegate if one is not provided explicitly.
884-
let executorDelegate = createToolExecutionDelegate()
885-
886-
func execute(jobs: [Job]) throws {
887-
try executor.execute(jobs: jobs,
888-
delegate: executorDelegate,
889-
numParallelJobs: numParallelJobs ?? 1,
890-
forceResponseFiles: forceResponseFiles,
891-
recordedInputModificationDates: recordedInputModificationDates)
892-
}
893-
894-
guard let incrementalCompilationState = incrementalCompilationState else {
895-
try execute(jobs: allJobs)
896-
return
897-
}
898-
while let jobs = incrementalCompilationState.preOrCompileJobs.dequeue() {
899-
try execute(jobs: formBatchedJobs(jobs, forIncremental: true))
900-
}
901-
guard let postCompileJobs = incrementalCompilationState.postCompileJobs
902-
else {
903-
fatalError("planning must have finished by now")
883+
var numParallelJobs: Int {
884+
self.numParallelJobs ?? 1
904885
}
905-
try execute(jobs: postCompileJobs)
886+
try executor.execute(
887+
jobs: allJobs,
888+
incrementalCompilationState: incrementalCompilationState,
889+
delegate: createToolExecutionDelegate(),
890+
numParallelJobs: numParallelJobs,
891+
forceResponseFiles: forceResponseFiles,
892+
recordedInputModificationDates: recordedInputModificationDates)
906893
}
907894

908895
private func printBindings(_ job: Job) {

Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ struct ToolExecutionDelegate: JobExecutionDelegate {
7373
}
7474

7575
buildRecordInfo?.jobFinished(job: job, result: result)
76-
incrementalCompilationState?.jobFinished(job: job, result: result)
7776

7877
switch mode {
7978
case .regular, .verbose:

Sources/SwiftDriver/Execution/DriverExecutor.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,23 @@ public protocol DriverExecutor {
2323
recordedInputModificationDates: [TypedVirtualPath: Date]) throws -> ProcessResult
2424

2525
/// Execute multiple jobs, tracking job status using the provided execution delegate.
26+
/// Pass in the `IncrementalCompilationState` to allow for incremental compilation.
2627
func execute(jobs: [Job],
28+
incrementalCompilationState: IncrementalCompilationState?,
2729
delegate: JobExecutionDelegate,
2830
numParallelJobs: Int,
2931
forceResponseFiles: Bool,
3032
recordedInputModificationDates: [TypedVirtualPath: Date]
3133
) throws
32-
34+
35+
/// Execute multiple jobs, tracking job status using the provided execution delegate.
36+
func execute(jobs: [Job],
37+
delegate: JobExecutionDelegate,
38+
numParallelJobs: Int,
39+
forceResponseFiles: Bool,
40+
recordedInputModificationDates: [TypedVirtualPath: Date]
41+
) throws
42+
3343
/// Launch a process with the given command line and report the result.
3444
@discardableResult
3545
func checkNonZeroExit(args: String..., environment: [String: String]) throws -> String
@@ -70,6 +80,22 @@ extension DriverExecutor {
7080
}
7181
}
7282

83+
public func execute(
84+
jobs: [Job],
85+
delegate: JobExecutionDelegate,
86+
numParallelJobs: Int,
87+
forceResponseFiles: Bool,
88+
recordedInputModificationDates: [TypedVirtualPath: Date]
89+
) throws {
90+
try execute(
91+
jobs: jobs,
92+
incrementalCompilationState: nil,
93+
delegate: delegate,
94+
numParallelJobs: numParallelJobs,
95+
forceResponseFiles: forceResponseFiles,
96+
recordedInputModificationDates: recordedInputModificationDates)
97+
}
98+
7399
static func computeReturnCode(exitStatus: ProcessResult.ExitStatus) -> Int {
74100
var returnCode: Int
75101
switch exitStatus {

Sources/SwiftDriver/Incremental Compilation/IncrementalCompilationState.swift

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,21 @@
1212
import TSCBasic
1313
import Foundation
1414
import SwiftOptions
15-
@_spi(Testing) public class IncrementalCompilationState {
15+
public class IncrementalCompilationState {
1616

1717
/// The oracle for deciding what depends on what. Applies to this whole module.
18-
public let moduleDependencyGraph: ModuleDependencyGraph
18+
private let moduleDependencyGraph: ModuleDependencyGraph
1919

2020
/// If non-null outputs information for `-driver-show-incremental` for input path
2121
public let reportIncrementalDecision: ((String, TypedVirtualPath?) -> Void)?
2222

23+
/// All of the compilation job (groups) known to be required, preserving planning order
24+
public private (set) var primaryJobsInOrder = [Job]()
25+
26+
/// All the compilation job (groups) known to be required, which have not finished yet.
27+
/// (Changes as jobs complete.)
28+
private var unfinishedPrimaryJobs = Set<Job>()
29+
2330
/// Inputs that must be compiled, and swiftDeps processed.
2431
/// When empty, the compile phase is done.
2532
private var pendingInputs = Set<TypedVirtualPath>()
@@ -33,19 +40,15 @@ import SwiftOptions
3340
/// treated as a unit.
3441
private var skippedCompileGroups = [TypedVirtualPath: [Job]]()
3542

36-
/// Accumulates jobs to be run through compilation
37-
public var preOrCompileJobs = SynchronizedQueue<[Job]?>()
38-
3943
/// Jobs to run after the last compile
40-
/// Nonnil means planning has informed me
41-
internal private(set) var postCompileJobs: [Job]? = nil
44+
public private(set) var tertiaryJobs = [Job]()
4245

4346
/// A check for reentrancy.
4447
private var amHandlingJobCompletion = false
4548

4649
// MARK: - Creating IncrementalCompilationState if possible
4750
/// Return nil if not compiling incrementally
48-
public init?(
51+
@_spi(Testing) public init?(
4952
buildRecordInfo: BuildRecordInfo?,
5053
compilerMode: CompilerMode,
5154
diagnosticEngine: DiagnosticsEngine,
@@ -358,41 +361,42 @@ extension IncrementalCompilationState {
358361
// MARK: - Scheduling
359362
extension IncrementalCompilationState {
360363
/// Decide if a job can be skipped, and register accordingly
361-
func addPreOrCompileJobGroups(_ groups: [[Job]]) {
362-
var wereAnyJobsScheduled = false
364+
func addPrimaryOrSkippedJobGroups(_ groups: [[Job]]) {
363365
for group in groups {
364366
if let firstJob = group.first, isSkipped(firstJob) {
365367
recordSkippedGroup(group)
366368
}
367369
else {
368-
schedule(group: group)
369-
wereAnyJobsScheduled = true
370+
schedulePrimary(group: group)
370371
}
371372
}
372-
if !wereAnyJobsScheduled {
373-
finishedWithCompilations()
373+
}
374+
375+
/// Remember that `group` (a compilation and possibly bitcode generation)
376+
/// must definitely be executed.
377+
func schedulePrimary(group: [Job]) {
378+
if let report = reportIncrementalDecision {
379+
for job in group {
380+
report("Queuing \(job.descriptionForLifecycle)", nil)
381+
}
374382
}
383+
primaryJobsInOrder.append(contentsOf: group)
384+
unfinishedPrimaryJobs.formUnion(group)
385+
let primaryCompilationInputs = group
386+
.flatMap {$0.kind == .compile ? $0.primaryInputs : []}
387+
pendingInputs.formUnion(primaryCompilationInputs)
375388
}
376389

390+
/// Decide if this job does not need to run, unless some yet-to-be-discovered dependency changes.
377391
func isSkipped(_ job: Job) -> Bool {
378392
guard job.kind == .compile else {
379393
return false
380394
}
381-
func isInputSkipped(_ p: TypedVirtualPath) -> Bool {
382-
skippedCompilationInputs.contains(p)
383-
}
384-
guard let jobCanBeSkipped = job.primaryInputs.first.map(isInputSkipped)
385-
else {
386-
return false
387-
}
388-
// Should only be one primary here, but check anyway
389-
assert(
390-
job.primaryInputs.dropFirst().allSatisfy {
391-
isInputSkipped($0) == jobCanBeSkipped}
392-
)
393-
return jobCanBeSkipped
395+
assert(job.primaryInputs.count <= 1, "Jobs should not be batched here.")
396+
return job.primaryInputs.first.map(skippedCompilationInputs.contains) ?? false
394397
}
395398

399+
/// Remember that this job-group will be skipped (but may be needed later)
396400
func recordSkippedGroup(_ group: [Job]) {
397401
let job = group.first!
398402
for input in job.primaryInputs {
@@ -402,26 +406,9 @@ extension IncrementalCompilationState {
402406
}
403407
}
404408

405-
func schedule(group: [Job]) {
406-
schedule(preOrCompileJobs: group)
407-
}
408-
/// Put job in queue for execution
409-
func schedule(preOrCompileJobs jobs: [Job]) {
410-
if let report = reportIncrementalDecision {
411-
for job in jobs {
412-
report("Queuing \(job.descriptionForLifecycle)", nil)
413-
}
414-
}
415-
let primaryCompilationInputs = jobs
416-
.flatMap {$0.kind == .compile ? $0.primaryInputs : []}
417-
pendingInputs.formUnion(primaryCompilationInputs)
418-
preOrCompileJobs.enqueue(jobs)
419-
}
420-
421-
/// Remember a job that runs after all compile jobs
422-
func addPostCompileJobs(_ jobs: [Job]) {
423-
assert(postCompileJobs == nil, "Should only be called once")
424-
postCompileJobs = jobs
409+
/// Remember a job that runs after all compile jobs, e.g., ld
410+
func addTertiaryJobs(_ jobs: [Job]) {
411+
self.tertiaryJobs = jobs
425412
for job in jobs {
426413
if let report = reportIncrementalDecision {
427414
for input in job.primaryInputs {
@@ -431,31 +418,46 @@ extension IncrementalCompilationState {
431418
}
432419
}
433420

434-
/// Update the incremental build state when a job finishes:
435-
/// Read it's swiftDeps files and queue up any required discovered jobs.
436-
func jobFinished(job finishedJob: Job, result: ProcessResult) {
421+
/// `job` just finished. Update state, and return the skipped compile job (groups) that are now known to be needed.
422+
/// If no more compiles are needed, return nil.
423+
/// Careful: job may not be primary.
424+
public func getSecondaryJobsAfterFinishing(job finishedJob: Job, result: ProcessResult) -> [Job]? {
437425
defer {
438426
amHandlingJobCompletion = false
439427
}
440428
assert(!amHandlingJobCompletion, "was reentered, need to synchronize")
441429
amHandlingJobCompletion = true
442430

431+
unfinishedPrimaryJobs.remove(finishedJob)
432+
if finishedJob.kind == .compile {
433+
finishedJob.primaryInputs.forEach {
434+
if pendingInputs.remove($0) == nil {
435+
fatalError("should have been pending")
436+
}
437+
}
438+
}
439+
440+
// Find and deal with inputs that how need to be compiled
443441
let discoveredInputs = collectInputsDiscovered(from: finishedJob)
442+
assert(Set(discoveredInputs).isDisjoint(with: finishedJob.primaryInputs),
443+
"Primaries should not overlap secondaries.")
444+
skippedCompilationInputs.subtract(discoveredInputs)
445+
pendingInputs.formUnion(discoveredInputs)
446+
444447
if let report = reportIncrementalDecision {
445448
for input in discoveredInputs {
446449
report("Queuing because of dependencies discovered later:", input)
447450
}
448451
}
449-
schedule(compilationInputs: discoveredInputs)
450-
finishedJob.primaryInputs.forEach {pendingInputs.remove($0)}
451-
if pendingInputs.isEmpty {
452-
finishedWithCompilations()
452+
if pendingInputs.isEmpty && unfinishedPrimaryJobs.isEmpty {
453+
// no more compilations are possible
454+
return nil
453455
}
456+
return getSecondaryJobsFor(discoveredCompilationInputs: discoveredInputs)
454457
}
455458

456-
private func collectInputsDiscovered(
457-
from job: Job
458-
) -> [TypedVirtualPath] {
459+
/// After `job` finished find out which inputs must compiled that were not known to need compilation before
460+
private func collectInputsDiscovered(from job: Job) -> [TypedVirtualPath] {
459461
Array(
460462
Set(
461463
job.primaryInputs.flatMap {
@@ -467,11 +469,15 @@ extension IncrementalCompilationState {
467469
.sorted {$0.file.name < $1.file.name}
468470
}
469471

470-
private func schedule(compilationInputs inputs: [TypedVirtualPath]) {
471-
let jobs = inputs.flatMap { input -> [Job] in
472+
/// Find the jobs that now must be run that were not originally known to be needed.
473+
private func getSecondaryJobsFor(
474+
discoveredCompilationInputs inputs: [TypedVirtualPath]
475+
) -> [Job] {
476+
inputs.flatMap { input -> [Job] in
472477
if let group = skippedCompileGroups.removeValue(forKey: input) {
473478
let primaryInputs = group.first!.primaryInputs
474-
skippedCompilationInputs.subtract(primaryInputs)
479+
assert(primaryInputs.count == 1)
480+
assert(primaryInputs[0] == input)
475481
reportIncrementalDecision?("Scheduling discovered", input)
476482
return group
477483
}
@@ -480,11 +486,5 @@ extension IncrementalCompilationState {
480486
return []
481487
}
482488
}
483-
schedule(preOrCompileJobs: jobs)
484-
}
485-
486-
func finishedWithCompilations() {
487-
preOrCompileJobs.enqueue(nil)
488489
}
489490
}
490-

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ extension Driver {
6565
debugInfo: debugInfo,
6666
addJob: addPostCompileJob)
6767

68-
incrementalCompilationState?.addPreOrCompileJobGroups(preAndCompileJobGroups)
69-
incrementalCompilationState?.addPostCompileJobs(postCompileJobs)
68+
incrementalCompilationState?.addPrimaryOrSkippedJobGroups(preAndCompileJobGroups)
69+
incrementalCompilationState?.addTertiaryJobs(postCompileJobs)
7070

7171
return try formBatchedJobs(allJobs, forIncremental: false)
7272
}

0 commit comments

Comments
 (0)