Skip to content

Commit 1780e77

Browse files
author
David Ungar
authored
[DNM Incremental] Start of cross-module incrementality (#458)
* Generalize DependencySource * Add comments * add isCrossModuleIncrementalBuildEnabled, but do nothing with it yet Fix tests after extra message * Generalize read interface to SourceFileDepGraph * Rm warning, improve a diagnostic * Redo Reporter interface * Add more incremental reporting * fix tests
1 parent 9962b82 commit 1780e77

11 files changed

+358
-131
lines changed

Sources/SwiftDriver/IncrementalCompilation/BuildRecordInfo.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,14 @@ final class BuildRecordInfo {
176176
do {
177177
contents = try fileSystem.readFileContents(buildRecordPath).cString
178178
} catch {
179-
reporter?.report("Incremental compilation could not read build record at \(buildRecordPath)")
179+
reporter?.report("Incremental compilation could not read build record at ", buildRecordPath)
180180
reporter?.reportDisablingIncrementalBuild("could not read build record")
181181
return nil
182182
}
183183
func failedToReadOutOfDateMap(_ reason: String? = nil) {
184184
let why = "malformed build record file\(reason.map {" " + $0} ?? "")"
185185
reporter?.report(
186-
"Incremental compilation has been disabled due to \(why) '\(buildRecordPath)'")
186+
"Incremental compilation has been disabled due to \(why)", buildRecordPath)
187187
reporter?.reportDisablingIncrementalBuild(why)
188188
}
189189
guard let outOfDateBuildRecord = BuildRecord(contents: contents,

Sources/SwiftDriver/IncrementalCompilation/DependencyKey.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ public struct DependencyKey: Hashable, CustomStringConvertible {
152152
/// available from this node.
153153
case incrementalExternalDependency(ExternalDependency)
154154

155-
var externalDependency: ExternalDependency? {
155+
var externalDependency: (ExternalDependency, isIncremental: Bool)? {
156156
switch self {
157157
case let .externalDepend(externalDependency):
158-
return externalDependency
158+
return (externalDependency, isIncremental: false)
159+
case let .incrementalExternalDependency(externalDependency):
160+
return (externalDependency, isIncremental: true)
159161
default:
160162
return nil}
161163
}

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState.swift

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import TSCBasic
1313
import Foundation
1414
import SwiftOptions
1515
public class IncrementalCompilationState {
16+
/// Whether cross-module incrementality is enabled
17+
private let isCrossModuleIncrementalBuildEnabled: Bool
18+
1619
/// The oracle for deciding what depends on what. Applies to this whole module.
1720
private let moduleDependencyGraph: ModuleDependencyGraph
1821

@@ -56,6 +59,11 @@ public class IncrementalCompilationState {
5659
self.reporter = nil
5760
}
5861

62+
self.isCrossModuleIncrementalBuildEnabled =
63+
driver.parsedOptions.contains(.enableExperimentalCrossModuleIncrementalBuild)
64+
reporter?.report(
65+
"\(self.isCrossModuleIncrementalBuildEnabled ? "Enabling" : "Disabling") incremental cross-module building")
66+
5967

6068
guard let (outputFileMap, buildRecordInfo, outOfDateBuildRecord)
6169
= try driver.getBuildInfo(self.reporter)
@@ -66,13 +74,13 @@ public class IncrementalCompilationState {
6674
guard let (
6775
moduleDependencyGraph,
6876
inputsHavingMalformedDependencySources: inputsHavingMalformedDependencySources
69-
) =
70-
Self.computeModuleDependencyGraph(
71-
buildRecordInfo,
72-
outOfDateBuildRecord,
73-
outputFileMap,
74-
&driver,
75-
self.reporter)
77+
) = Self.computeModuleDependencyGraph(
78+
buildRecordInfo,
79+
outOfDateBuildRecord,
80+
outputFileMap,
81+
&driver,
82+
self.reporter,
83+
isCrossModuleIncrementalBuildEnabled: isCrossModuleIncrementalBuildEnabled)
7684
else {
7785
return nil
7886
}
@@ -99,15 +107,16 @@ public class IncrementalCompilationState {
99107
_ outOfDateBuildRecord: BuildRecord,
100108
_ outputFileMap: OutputFileMap,
101109
_ driver: inout Driver,
102-
_ reporter: Reporter?
110+
_ reporter: Reporter?,
111+
isCrossModuleIncrementalBuildEnabled: Bool
103112
)
104113
-> (ModuleDependencyGraph,
105114
inputsHavingMalformedDependencySources: [TypedVirtualPath])?
106115
{
107116
let diagnosticEngine = driver.diagnosticEngine
108117
guard let (
109118
moduleDependencyGraph,
110-
inputsAndMalformedDependencySources: inputsAndMalformedDependencySources
119+
inputsAndMalformedSwiftDeps: inputsAndMalformedSwiftDeps
111120
) =
112121
ModuleDependencyGraph.buildInitialGraph(
113122
diagnosticEngine: diagnosticEngine,
@@ -117,20 +126,22 @@ public class IncrementalCompilationState {
117126
parsedOptions: &driver.parsedOptions,
118127
remarkDisabled: Diagnostic.Message.remark_incremental_compilation_has_been_disabled,
119128
reporter: reporter,
120-
fileSystem: driver.fileSystem)
129+
fileSystem: driver.fileSystem,
130+
isCrossModuleIncrementalBuildEnabled: isCrossModuleIncrementalBuildEnabled
131+
)
121132
else {
122133
return nil
123134
}
124135
// Preserve legacy behavior,
125136
// but someday, just ensure inputsAndMalformedDependencySources are compiled
126-
if let badDependencySource = inputsAndMalformedDependencySources.first?.1 {
137+
if let badSwiftDeps = inputsAndMalformedSwiftDeps.first?.1 {
127138
diagnosticEngine.emit(
128139
.remark_incremental_compilation_has_been_disabled(
129-
because: "malformed dependencies file '\(badDependencySource)'")
140+
because: "malformed dependencies file '\(badSwiftDeps)'")
130141
)
131142
return nil
132143
}
133-
let inputsHavingMalformedDependencySources = inputsAndMalformedDependencySources.map {$0.0}
144+
let inputsHavingMalformedDependencySources = inputsAndMalformedSwiftDeps.map {$0.0}
134145
return (moduleDependencyGraph,
135146
inputsHavingMalformedDependencySources: inputsHavingMalformedDependencySources)
136147
}
@@ -282,6 +293,7 @@ extension IncrementalCompilationState {
282293
alwaysRebuildDependents: Bool,
283294
reporter: IncrementalCompilationState.Reporter?
284295
) -> Set<TypedVirtualPath> {
296+
// Input == source file
285297
let changedInputs = Self.computeChangedInputs(
286298
groups: allGroups,
287299
buildRecordInfo: buildRecordInfo,
@@ -290,7 +302,15 @@ extension IncrementalCompilationState {
290302
fileSystem: fileSystem,
291303
reporter: reporter)
292304

293-
let externalDependents = computeExternallyDependentInputs(
305+
let externallyChangedInputs = computeExternallyChangedInputs(
306+
forIncrementalExternalDependencies: false,
307+
buildTime: outOfDateBuildRecord.buildTime,
308+
fileSystem: fileSystem,
309+
moduleDependencyGraph: moduleDependencyGraph,
310+
reporter: moduleDependencyGraph.reporter)
311+
312+
let incrementallyExternallyChangedInputs = computeExternallyChangedInputs(
313+
forIncrementalExternalDependencies: true,
294314
buildTime: outOfDateBuildRecord.buildTime,
295315
fileSystem: fileSystem,
296316
moduleDependencyGraph: moduleDependencyGraph,
@@ -304,12 +324,13 @@ extension IncrementalCompilationState {
304324

305325
// Combine to obtain the inputs that definitely must be recompiled.
306326
let definitelyRequiredInputs =
307-
Set(changedInputs.map({ $0.filePath }) + externalDependents +
327+
Set(changedInputs.map({ $0.filePath }) +
328+
externallyChangedInputs + incrementallyExternallyChangedInputs +
308329
inputsHavingMalformedDependencySources
309330
+ inputsMissingOutputs)
310331
if let reporter = reporter {
311332
for scheduledInput in definitelyRequiredInputs.sorted(by: {$0.file.name < $1.file.name}) {
312-
reporter.report("Queuing (initial):", path: scheduledInput)
333+
reporter.report("Queuing (initial):", scheduledInput)
313334
}
314335
}
315336

@@ -319,7 +340,7 @@ extension IncrementalCompilationState {
319340
// as each first wave job finished.
320341
let speculativeInputs = computeSpeculativeInputs(
321342
changedInputs: changedInputs,
322-
externalDependents: externalDependents,
343+
externalDependents: externallyChangedInputs,
323344
inputsMissingOutputs: Set(inputsMissingOutputs),
324345
moduleDependencyGraph: moduleDependencyGraph,
325346
alwaysRebuildDependents: alwaysRebuildDependents,
@@ -328,7 +349,7 @@ extension IncrementalCompilationState {
328349

329350
if let reporter = reporter {
330351
for dependent in speculativeInputs.sorted(by: {$0.file.name < $1.file.name}) {
331-
reporter.report("Queuing because of the initial set:", path: dependent)
352+
reporter.report("Queuing because of the initial set:", dependent)
332353
}
333354
}
334355
let immediatelyCompiledInputs = definitelyRequiredInputs.union(speculativeInputs)
@@ -337,7 +358,7 @@ extension IncrementalCompilationState {
337358
.subtracting(immediatelyCompiledInputs)
338359
if let reporter = reporter {
339360
for skippedInput in skippedInputs.sorted(by: {$0.file.name < $1.file.name}) {
340-
reporter.report("Skipping input:", path: skippedInput)
361+
reporter.report("Skipping input:", skippedInput)
341362
}
342363
}
343364
return skippedInputs
@@ -383,17 +404,17 @@ extension IncrementalCompilationState {
383404

384405
switch previousCompilationStatus {
385406
case .upToDate where datesMatch:
386-
reporter?.report("May skip current input:", path: input)
407+
reporter?.report("May skip current input:", input)
387408
return nil
388409

389410
case .upToDate:
390-
reporter?.report("Scheduing changed input", path: input)
411+
reporter?.report("Scheduing changed input", input)
391412
case .newlyAdded:
392-
reporter?.report("Scheduling new", path: input)
413+
reporter?.report("Scheduling new", input)
393414
case .needsCascadingBuild:
394-
reporter?.report("Scheduling cascading build", path: input)
415+
reporter?.report("Scheduling cascading build", input)
395416
case .needsNonCascadingBuild:
396-
reporter?.report("Scheduling noncascading build", path: input)
417+
reporter?.report("Scheduling noncascading build", input)
397418
}
398419
return ChangedInput(filePath: input,
399420
status: previousCompilationStatus,
@@ -402,25 +423,28 @@ extension IncrementalCompilationState {
402423
}
403424

404425
/// Any files dependent on modified files from other modules must be compiled, too.
405-
private static func computeExternallyDependentInputs(
426+
private static func computeExternallyChangedInputs(
427+
forIncrementalExternalDependencies: Bool,
406428
buildTime: Date,
407429
fileSystem: FileSystem,
408430
moduleDependencyGraph: ModuleDependencyGraph,
409431
reporter: IncrementalCompilationState.Reporter?
410432
) -> [TypedVirtualPath] {
411433
var externalDependencySources = Set<ModuleDependencyGraph.DependencySource>()
412-
for extDep in moduleDependencyGraph.externalDependencies {
413-
let extModTime = extDep.file.flatMap {
414-
try? fileSystem.getFileInfo($0).modTime}
434+
let extDeps = forIncrementalExternalDependencies
435+
? moduleDependencyGraph.incrementalExternalDependencies
436+
: moduleDependencyGraph.externalDependencies
437+
for extDep in extDeps {
438+
let extModTime = extDep.file.flatMap {try? fileSystem.getFileInfo($0).modTime}
415439
?? Date.distantFuture
416440
if extModTime >= buildTime {
417-
for dependent in moduleDependencyGraph.untracedDependents(of: extDep) {
441+
for dependent in moduleDependencyGraph.untracedDependents(of: extDep, isIncremental: forIncrementalExternalDependencies) {
418442
guard let dependencySource = dependent.dependencySource else {
419-
fatalError("Dependent \(dependent) does not have dependencies source file!")
443+
fatalError("Dependent \(dependent) does not have dependencies file!")
420444
}
421445
reporter?.report(
422-
"Queuing because of external dependency on newer \(extDep.file?.basename ?? "extDep?")",
423-
path: TypedVirtualPath(file: dependencySource.file, type: .swiftDeps))
446+
"Queuing because of \(forIncrementalExternalDependencies ? "incremental " : "")external dependency on newer \(extDep.file?.basename ?? "extDep?")",
447+
dependencySource.typedFile)
424448
externalDependencySources.insert(dependencySource)
425449
}
426450
}
@@ -457,7 +481,7 @@ extension IncrementalCompilationState {
457481
for dep in dependentsOfOneFile where !cascadingFileSet.contains(dep) {
458482
if dependentFiles.insert(dep).0 {
459483
reporter?.report(
460-
"Immediately scheduling dependent on \(cascadingFile.file.basename)", path: dep)
484+
"Immediately scheduling dependent on \(cascadingFile.file.basename)", dep)
461485
}
462486
}
463487
}
@@ -536,7 +560,8 @@ extension IncrementalCompilationState {
536560

537561
if let reporter = self.reporter {
538562
for input in discoveredInputs {
539-
reporter.report("Queuing because of dependencies discovered later:", path: input)
563+
reporter.report(
564+
"Queuing because of dependencies discovered later:", input)
540565
}
541566
}
542567
let newJobs = try getJobsFor(discoveredCompilationInputs: discoveredInputs)
@@ -561,7 +586,8 @@ extension IncrementalCompilationState {
561586
if let found = moduleDependencyGraph.findSourcesToCompileAfterCompiling(input, on: self.driver.fileSystem) {
562587
return found
563588
}
564-
self.reporter?.report("Failed to read some dependencies source; compiling everything", path: input)
589+
self.reporter?.report(
590+
"Failed to read some dependencies source; compiling everything", input)
565591
return Array(skippedCompileGroups.keys)
566592
}
567593
)
@@ -579,11 +605,11 @@ extension IncrementalCompilationState {
579605
let primaryInputs = group.compileJob.primaryInputs
580606
assert(primaryInputs.count == 1)
581607
assert(primaryInputs[0] == input)
582-
self.reporter?.report("Scheduling discovered", path: input)
608+
self.reporter?.report("Scheduling discovered", input)
583609
return group.allJobs()
584610
}
585611
else {
586-
self.reporter?.report("Tried to schedule discovered input again", path: input)
612+
self.reporter?.report("Tried to schedule discovered input again", input)
587613
return []
588614
}
589615
}
@@ -627,20 +653,38 @@ extension IncrementalCompilationState {
627653
///
628654
/// - Parameters:
629655
/// - message: The message to emit in the remark.
630-
/// - path: If non-nil, the path of an output for an incremental job.
631-
func report(_ message: String, path: TypedVirtualPath? = nil) {
632-
guard let outputFileMap = outputFileMap,
633-
let path = path,
656+
/// - path: If non-nil, the path of some file. If the output for an incremental job, will print out the
657+
/// source and object files.
658+
func report(_ message: String, _ path: TypedVirtualPath?) {
659+
guard let path = path,
660+
let outputFileMap = outputFileMap,
634661
let input = path.type == .swift ? path.file : outputFileMap.getInput(outputFile: path.file)
635662
else {
636-
diagnosticEngine.emit(.remark_incremental_compilation(because: message))
663+
report(message, path?.file)
637664
return
638665
}
639666
let output = outputFileMap.getOutput(inputFile: path.file, outputType: .object)
640667
let compiling = " {compile: \(output.basename) <= \(input.basename)}"
641668
diagnosticEngine.emit(.remark_incremental_compilation(because: "\(message) \(compiling)"))
642669
}
643670

671+
/// Entry point for a simple path, won't print the compile job, path could be anything.
672+
func report(_ message: String, _ path: VirtualPath?) {
673+
guard let path = path
674+
else {
675+
report(message)
676+
diagnosticEngine.emit(.remark_incremental_compilation(because: message))
677+
return
678+
}
679+
diagnosticEngine.emit(.remark_incremental_compilation(because: "\(message) '\(path.name)'"))
680+
}
681+
682+
/// Entry point if no path.
683+
func report(_ message: String) {
684+
diagnosticEngine.emit(.remark_incremental_compilation(because: message))
685+
}
686+
687+
644688
// Emits a remark indicating incremental compilation has been disabled.
645689
func reportDisablingIncrementalBuild(_ why: String) {
646690
report("Disabling incremental build: \(why)")

0 commit comments

Comments
 (0)