Skip to content

Commit d538883

Browse files
author
David Ungar
committed
[NFC, Incremental] Create a domain-specific type for the inputDependencySourceMap
Will facilitate making it immutable
1 parent 1b03427 commit d538883

File tree

5 files changed

+133
-84
lines changed

5 files changed

+133
-84
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_library(SwiftDriver
3333
Execution/ParsableOutput.swift
3434
Execution/ProcessProtocol.swift
3535

36+
"IncrementalCompilation/ModuleDependencyGraphParts/InputDependencySourceMap.swift"
3637
"IncrementalCompilation/ModuleDependencyGraphParts/Integrator.swift"
3738
"IncrementalCompilation/ModuleDependencyGraphParts/Node.swift"
3839
"IncrementalCompilation/ModuleDependencyGraphParts/NodeFinder.swift"

Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
237237
else {
238238
return buildInitialGraphFromSwiftDepsAndCollectInputsInvalidatedByChangedExternals()
239239
}
240-
guard graph.populateInputDependencySourceMap() else {
240+
guard graph.populateInputDependencySourceMap(for: .inputsAddedSincePriors) else {
241241
return nil
242242
}
243243
graph.dotFileWriter?.write(graph)
@@ -266,7 +266,8 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
266266
{
267267
let graph = ModuleDependencyGraph(self, .buildingWithoutAPrior)
268268
assert(outputFileMap.onlySourceFilesHaveSwiftDeps())
269-
guard graph.populateInputDependencySourceMap() else {
269+
270+
guard graph.populateInputDependencySourceMap(for: .buildingFromSwiftDeps) else {
270271
return nil
271272
}
272273

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 63 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,7 @@ import SwiftOptions
2424
@_spi(Testing) public var nodeFinder = NodeFinder()
2525

2626
/// Maps input files (e.g. .swift) to and from the DependencySource object.
27-
///
28-
// FIXME: The map between swiftdeps and swift files is absolutely *not*
29-
// a bijection. In particular, more than one swiftdeps file can be encountered
30-
// in the course of deserializing priors *and* reading the output file map
31-
// *and* re-reading swiftdeps files after frontends complete
32-
// that correspond to the same swift file. These cause two problems:
33-
// - overwrites in this data structure that lose data and
34-
// - cache misses in `getInput(for:)` that cause the incremental build to
35-
// turn over when e.g. entries in the output file map change. This should be
36-
// replaced by a multi-map from swift files to dependency sources,
37-
// and a regular map from dependency sources to swift files -
38-
// since that direction really is one-to-one.
39-
@_spi(Testing) public private(set) var inputDependencySourceMap = BidirectionalMap<TypedVirtualPath, DependencySource>()
27+
@_spi(Testing) public private(set) var inputDependencySourceMap = InputDependencySourceMap()
4028

4129
// The set of paths to external dependencies known to be in the graph
4230
public internal(set) var fingerprintedExternalDependencies = Set<FingerprintedExternalDependency>()
@@ -66,32 +54,15 @@ import SwiftOptions
6654
self.creationPhase = phase
6755
}
6856

69-
private func addMapEntry(_ input: TypedVirtualPath, _ dependencySource: DependencySource) {
70-
assert(input.type == .swift && dependencySource.typedFile.type == .swiftDeps)
71-
inputDependencySourceMap[input] = dependencySource
72-
}
73-
74-
@_spi(Testing) public func getSource(for input: TypedVirtualPath,
75-
function: String = #function,
76-
file: String = #file,
77-
line: Int = #line) -> DependencySource {
78-
guard let source = inputDependencySourceMap[input] else {
79-
fatalError("\(input.file) not found in map: \(inputDependencySourceMap), \(file):\(line) in \(function)")
80-
}
81-
return source
82-
}
83-
84-
@_spi(Testing) public func getInput(for source: DependencySource) -> TypedVirtualPath? {
85-
guard let input =
86-
info.simulateGetInputFailure ? nil
87-
: inputDependencySourceMap[source]
57+
@_spi(Testing) public func sourceRequired(for input: TypedVirtualPath,
58+
function: String = #function,
59+
file: String = #file,
60+
line: Int = #line) -> DependencySource {
61+
guard let source = inputDependencySourceMap.sourceIfKnown(for: input)
8862
else {
89-
info.diagnosticEngine.emit(warning: "Failed to find source file for '\(source.file.basename)', recovering with a full rebuild. Next build will be incremental.")
90-
info.reporter?.report(
91-
"\(info.simulateGetInputFailure ? "Simulating i" : "I")nput not found in inputDependencySourceMap; created for: \(creationPhase), now: \(phase)")
92-
return nil
63+
fatalError("\(input.file.basename) not found in inputDependencySourceMap, \(file):\(line) in \(function)")
9364
}
94-
return input
65+
return source
9566
}
9667
}
9768

@@ -161,7 +132,7 @@ extension ModuleDependencyGraph {
161132
return TransitivelyInvalidatedInputSet()
162133
}
163134
return collectInputsRequiringCompilationAfterProcessing(
164-
dependencySource: getSource(for: input))
135+
dependencySource: sourceRequired(for: input))
165136
}
166137
}
167138

@@ -183,17 +154,16 @@ extension ModuleDependencyGraph {
183154
/// speculatively scheduled in the first wave.
184155
func collectInputsInvalidatedBy(input: TypedVirtualPath
185156
) -> TransitivelyInvalidatedInputArray {
186-
let changedSource = getSource(for: input)
157+
let changedSource = sourceRequired(for: input)
187158
let allDependencySourcesToRecompile =
188159
collectSwiftDepsUsing(dependencySource: changedSource)
189160

190161
return allDependencySourcesToRecompile.compactMap {
191162
dependencySource in
192163
guard dependencySource != changedSource else {return nil}
193-
let dependentSource = inputDependencySourceMap[dependencySource]
194-
info.reporter?.report(
195-
"Found dependent of \(input.file.basename):", dependentSource)
196-
return dependentSource
164+
let inputToRecompile = inputDependencySourceMap.inputIfKnown(for: dependencySource)
165+
info.reporter?.report("Found dependent of \(input.file.basename):", inputToRecompile)
166+
return inputToRecompile
197167
}
198168
}
199169

@@ -210,7 +180,7 @@ extension ModuleDependencyGraph {
210180
/// Does the graph contain any dependency nodes for a given source-code file?
211181
func containsNodes(forSourceFile file: TypedVirtualPath) -> Bool {
212182
precondition(file.type == .swift)
213-
guard let source = inputDependencySourceMap[file] else {
183+
guard let source = inputDependencySourceMap.sourceIfKnown(for: file) else {
214184
return false
215185
}
216186
return containsNodes(forDependencySource: source)
@@ -220,17 +190,46 @@ extension ModuleDependencyGraph {
220190
return nodeFinder.findNodes(for: source).map {!$0.isEmpty}
221191
?? false
222192
}
223-
224-
/// Return true on success
225-
func populateInputDependencySourceMap() -> Bool {
193+
194+
/// - Returns: false on error
195+
func populateInputDependencySourceMap(
196+
`for` purpose: InputDependencySourceMap.AdditionPurpose
197+
) -> Bool {
226198
let ofm = info.outputFileMap
227-
let de = info.diagnosticEngine
228-
return info.inputFiles.reduce(true) { okSoFar, input in
229-
ofm.getDependencySource(for: input, diagnosticEngine: de)
230-
.map {source in addMapEntry(input, source); return okSoFar } ?? false
199+
let diags = info.diagnosticEngine
200+
var allFound = true
201+
for input in info.inputFiles {
202+
if let source = ofm.dependencySource(for: input, diagnosticEngine: diags) {
203+
inputDependencySourceMap.addEntry(input, source, for: purpose)
204+
} else {
205+
// Don't break in order to report all failures.
206+
allFound = false
207+
}
208+
}
209+
return allFound
210+
}
211+
}
212+
extension OutputFileMap {
213+
fileprivate func dependencySource(
214+
for sourceFile: TypedVirtualPath,
215+
diagnosticEngine: DiagnosticsEngine
216+
) -> DependencySource? {
217+
assert(sourceFile.type == FileType.swift)
218+
guard let swiftDepsPath = existingOutput(inputFile: sourceFile.fileHandle,
219+
outputType: .swiftDeps)
220+
else {
221+
// The legacy driver fails silently here.
222+
diagnosticEngine.emit(
223+
.remarkDisabled("\(sourceFile.file.basename) has no swiftDeps file")
224+
)
225+
return nil
231226
}
227+
assert(VirtualPath.lookup(swiftDepsPath).extension == FileType.swiftDeps.rawValue)
228+
let typedSwiftDepsFile = TypedVirtualPath(file: swiftDepsPath, type: .swiftDeps)
229+
return DependencySource(typedSwiftDepsFile)
232230
}
233231
}
232+
234233
// MARK: - Scheduling the 2nd wave
235234
extension ModuleDependencyGraph {
236235
/// After `source` has been compiled, figure out what other source files need compiling.
@@ -240,7 +239,7 @@ extension ModuleDependencyGraph {
240239
func collectInputsRequiringCompilation(byCompiling input: TypedVirtualPath
241240
) -> TransitivelyInvalidatedInputSet? {
242241
precondition(input.type == .swift)
243-
let dependencySource = getSource(for: input)
242+
let dependencySource = sourceRequired(for: input)
244243
return collectInputsRequiringCompilationAfterProcessing(
245244
dependencySource: dependencySource)
246245
}
@@ -327,8 +326,10 @@ extension ModuleDependencyGraph {
327326
) -> TransitivelyInvalidatedInputSet? {
328327
var invalidatedInputs = TransitivelyInvalidatedInputSet()
329328
for invalidatedSwiftDeps in collectSwiftDepsUsingInvalidated(nodes: directlyInvalidatedNodes) {
330-
guard let invalidatedInput = getInput(for: invalidatedSwiftDeps)
329+
guard let invalidatedInput = inputDependencySourceMap.inputIfKnown(for: invalidatedSwiftDeps)
331330
else {
331+
info.diagnosticEngine.emit(
332+
warning: "Failed to find source file for '\(invalidatedSwiftDeps.file.basename)', recovering with a full rebuild. Next build will be incremental.")
332333
return nil
333334
}
334335
invalidatedInputs.insert(invalidatedInput)
@@ -405,27 +406,6 @@ extension ModuleDependencyGraph {
405406
}
406407
}
407408

408-
extension OutputFileMap {
409-
fileprivate func getDependencySource(
410-
for sourceFile: TypedVirtualPath,
411-
diagnosticEngine: DiagnosticsEngine
412-
) -> DependencySource? {
413-
assert(sourceFile.type == FileType.swift)
414-
guard let swiftDepsPath = existingOutput(inputFile: sourceFile.fileHandle,
415-
outputType: .swiftDeps)
416-
else {
417-
// The legacy driver fails silently here.
418-
diagnosticEngine.emit(
419-
.remarkDisabled("\(sourceFile.file.basename) has no swiftDeps file")
420-
)
421-
return nil
422-
}
423-
assert(VirtualPath.lookup(swiftDepsPath).extension == FileType.swiftDeps.rawValue)
424-
let typedSwiftDepsFile = TypedVirtualPath(file: swiftDepsPath, type: .swiftDeps)
425-
return DependencySource(typedSwiftDepsFile)
426-
}
427-
}
428-
429409
// MARK: - tracking traced nodes
430410
extension ModuleDependencyGraph {
431411

@@ -550,10 +530,11 @@ extension ModuleDependencyGraph {
550530
.record(def: dependencyKey, use: self.allNodes[useID])
551531
assert(isNewUse, "Duplicate use def-use arc in graph?")
552532
}
553-
for (input, source) in inputDependencySourceMap {
554-
graph.addMapEntry(input, source)
533+
for (input, dependencySource) in inputDependencySourceMap {
534+
graph.inputDependencySourceMap.addEntry(input,
535+
dependencySource,
536+
for: .readingPriors)
555537
}
556-
557538
return self.graph
558539
}
559540

@@ -849,7 +830,7 @@ extension ModuleDependencyGraph {
849830
}
850831
}
851832

852-
for (input, dependencySource) in graph.inputDependencySourceMap {
833+
graph.inputDependencySourceMap.enumerateToSerializePriors { input, dependencySource in
853834
self.addIdentifier(input.file.name)
854835
self.addIdentifier(dependencySource.file.name)
855836
}
@@ -994,7 +975,8 @@ extension ModuleDependencyGraph {
994975
}
995976
}
996977
}
997-
for (input, dependencySource) in graph.inputDependencySourceMap {
978+
graph.inputDependencySourceMap.enumerateToSerializePriors {
979+
input, dependencySource in
998980
serializer.stream.writeRecord(serializer.abbreviations[.mapNode]!) {
999981
$0.append(RecordID.mapNode)
1000982
$0.append(serializer.lookupIdentifierCode(for: input.file.name))
@@ -1112,7 +1094,7 @@ extension Set where Element == ModuleDependencyGraph.Node {
11121094
}
11131095
}
11141096

1115-
extension BidirectionalMap where T1 == TypedVirtualPath, T2 == DependencySource {
1097+
extension InputDependencySourceMap {
11161098
fileprivate func matches(_ other: Self) -> Bool {
11171099
self == other
11181100
}
@@ -1130,6 +1112,6 @@ extension ModuleDependencyGraph {
11301112
_ mockInput: TypedVirtualPath,
11311113
_ mockDependencySource: DependencySource
11321114
) {
1133-
addMapEntry(mockInput, mockDependencySource)
1115+
inputDependencySourceMap.addEntry(mockInput, mockDependencySource, for: .mocking)
11341116
}
11351117
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===------------- InputDependencySourceMap.swift ---------------- --------===//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
9+
//
10+
//===----------------------------------------------------------------------===//
11+
import Foundation
12+
import TSCBasic
13+
14+
@_spi(Testing) public struct InputDependencySourceMap: Equatable {
15+
16+
/// Maps input files (e.g. .swift) to and from the DependencySource object.
17+
///
18+
// FIXME: The map between swiftdeps and swift files is absolutely *not*
19+
// a bijection. In particular, more than one swiftdeps file can be encountered
20+
// in the course of deserializing priors *and* reading the output file map
21+
// *and* re-reading swiftdeps files after frontends complete
22+
// that correspond to the same swift file. These cause two problems:
23+
// - overwrites in this data structure that lose data and
24+
// - cache misses in `getInput(for:)` that cause the incremental build to
25+
// turn over when e.g. entries in the output file map change. This should be
26+
// replaced by a multi-map from swift files to dependency sources,
27+
// and a regular map from dependency sources to swift files -
28+
// since that direction really is one-to-one.
29+
30+
public typealias BiMap = BidirectionalMap<TypedVirtualPath, DependencySource>
31+
@_spi(Testing) public var biMap = BiMap()
32+
}
33+
34+
// MARK: - Accessing
35+
extension InputDependencySourceMap {
36+
@_spi(Testing) public func sourceIfKnown(for input: TypedVirtualPath) -> DependencySource? {
37+
biMap[input]
38+
}
39+
40+
@_spi(Testing) public func inputIfKnown(for source: DependencySource) -> TypedVirtualPath? {
41+
biMap[source]
42+
}
43+
44+
@_spi(Testing) public func enumerateToSerializePriors(
45+
_ eachFn: (TypedVirtualPath, DependencySource) -> Void
46+
) {
47+
biMap.forEach(eachFn)
48+
}
49+
}
50+
51+
// MARK: - Populating
52+
extension InputDependencySourceMap {
53+
public enum AdditionPurpose {
54+
case mocking,
55+
buildingFromSwiftDeps,
56+
readingPriors,
57+
inputsAddedSincePriors }
58+
@_spi(Testing) public mutating func addEntry(_ input: TypedVirtualPath,
59+
_ dependencySource: DependencySource,
60+
`for` _ : AdditionPurpose) {
61+
assert(input.type == .swift && dependencySource.typedFile.type == .swiftDeps)
62+
biMap[input] = dependencySource
63+
}
64+
}

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/Tracer.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ extension ModuleDependencyGraph.Tracer {
121121
path.compactMap { node in
122122
node.dependencySource.map {
123123
source in
124-
graph.inputDependencySourceMap[source].map { input in
124+
graph.inputDependencySourceMap.inputIfKnown(for: source).map {
125+
input in
125126
"\(node.key) in \(input.file.basename)"
126127
}
127128
?? "\(node.key)"

0 commit comments

Comments
 (0)