Skip to content

Commit 3f68e62

Browse files
author
David Ungar
authored
Merge pull request #664 from davidungar/replace-bijection-on-vacation
[NFC, Incremental] Refactoring to facilitate replacing the inputDependencySourceMap with the right thing
2 parents d963c8a + 0dc8704 commit 3f68e62

File tree

4 files changed

+162
-71
lines changed

4 files changed

+162
-71
lines changed

Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
226226
else {
227227
return buildInitialGraphFromSwiftDepsAndCollectInputsInvalidatedByChangedExternals()
228228
}
229-
guard graph.populateInputDependencySourceMap() else {
229+
guard graph.populateInputDependencySourceMap(for: .inputsAddedSincePriors) else {
230230
return nil
231231
}
232232
graph.dotFileWriter?.write(graph)
@@ -256,7 +256,8 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
256256
{
257257
let graph = ModuleDependencyGraph(self, .buildingWithoutAPrior)
258258
assert(outputFileMap.onlySourceFilesHaveSwiftDeps())
259-
guard graph.populateInputDependencySourceMap() else {
259+
260+
guard graph.populateInputDependencySourceMap(for: .buildingFromSwiftDeps) else {
260261
return nil
261262
}
262263

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 64 additions & 68 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,22 +54,20 @@ 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 {
57+
@_spi(Testing) public func source(requiredFor input: TypedVirtualPath,
58+
function: String = #function,
59+
file: String = #file,
60+
line: Int = #line) -> DependencySource {
61+
guard let source = inputDependencySourceMap.sourceIfKnown(for: input)
62+
else {
7963
fatalError("\(input.file.basename) not found in inputDependencySourceMap, \(file):\(line) in \(function)")
8064
}
8165
return source
8266
}
83-
@_spi(Testing) public func getInput(for source: DependencySource) -> TypedVirtualPath? {
84-
guard let input = inputDependencySourceMap[source] else {
67+
68+
@_spi(Testing) public func input(neededFor source: DependencySource) -> TypedVirtualPath? {
69+
guard let input = inputDependencySourceMap.input(ifKnownFor: source)
70+
else {
8571
info.diagnosticEngine.emit(warning: "Failed to find source file for '\(source.file.basename)', recovering with a full rebuild. Next build will be incremental.")
8672
return nil
8773
}
@@ -155,7 +141,7 @@ extension ModuleDependencyGraph {
155141
return TransitivelyInvalidatedInputSet()
156142
}
157143
return collectInputsRequiringCompilationAfterProcessing(
158-
dependencySource: getSource(for: input))
144+
dependencySource: source(requiredFor: input))
159145
}
160146
}
161147

@@ -177,17 +163,17 @@ extension ModuleDependencyGraph {
177163
/// speculatively scheduled in the first wave.
178164
func collectInputsInvalidatedBy(input: TypedVirtualPath
179165
) -> TransitivelyInvalidatedInputArray {
180-
let changedSource = getSource(for: input)
166+
let changedSource = source(requiredFor: input)
181167
let allDependencySourcesToRecompile =
182168
collectSwiftDepsUsing(dependencySource: changedSource)
183169

184170
return allDependencySourcesToRecompile.compactMap {
185171
depedencySource in
186172
guard depedencySource != changedSource else {return nil}
187-
let dependentSource = inputDependencySourceMap[depedencySource]
173+
let dependentInput = inputDependencySourceMap.input(ifKnownFor: depedencySource)
188174
info.reporter?.report(
189-
"Found dependent of \(input.file.basename):", dependentSource)
190-
return dependentSource
175+
"Found dependent of \(input.file.basename):", dependentInput)
176+
return dependentInput
191177
}
192178
}
193179

@@ -204,7 +190,7 @@ extension ModuleDependencyGraph {
204190
/// Does the graph contain any dependency nodes for a given source-code file?
205191
func containsNodes(forSourceFile file: TypedVirtualPath) -> Bool {
206192
precondition(file.type == .swift)
207-
guard let source = inputDependencySourceMap[file] else {
193+
guard let source = inputDependencySourceMap.sourceIfKnown(for: file) else {
208194
return false
209195
}
210196
return containsNodes(forDependencySource: source)
@@ -214,17 +200,46 @@ extension ModuleDependencyGraph {
214200
return nodeFinder.findNodes(for: source).map {!$0.isEmpty}
215201
?? false
216202
}
217-
218-
/// Return true on success
219-
func populateInputDependencySourceMap() -> Bool {
203+
204+
/// Returns: false on error
205+
func populateInputDependencySourceMap(
206+
for purpose: InputDependencySourceMap.AdditionPurpose
207+
) -> Bool {
220208
let ofm = info.outputFileMap
221-
let de = info.diagnosticEngine
222-
return info.inputFiles.reduce(true) { okSoFar, input in
223-
ofm.getDependencySource(for: input, diagnosticEngine: de)
224-
.map {source in addMapEntry(input, source); return okSoFar } ?? false
209+
let diags = info.diagnosticEngine
210+
var allFound = true
211+
for input in info.inputFiles {
212+
if let source = ofm.dependencySource(for: input, diagnosticEngine: diags) {
213+
inputDependencySourceMap.addEntry(input, source, for: purpose)
214+
} else {
215+
// Don't break in order to report all failures.
216+
allFound = false
217+
}
225218
}
219+
return allFound
226220
}
227221
}
222+
extension OutputFileMap {
223+
fileprivate func dependencySource(
224+
for sourceFile: TypedVirtualPath,
225+
diagnosticEngine: DiagnosticsEngine
226+
) -> DependencySource? {
227+
assert(sourceFile.type == FileType.swift)
228+
guard let swiftDepsPath = existingOutput(inputFile: sourceFile.fileHandle,
229+
outputType: .swiftDeps)
230+
else {
231+
// The legacy driver fails silently here.
232+
diagnosticEngine.emit(
233+
.remarkDisabled("\(sourceFile.file.basename) has no swiftDeps file")
234+
)
235+
return nil
236+
}
237+
assert(VirtualPath.lookup(swiftDepsPath).extension == FileType.swiftDeps.rawValue)
238+
let typedSwiftDepsFile = TypedVirtualPath(file: swiftDepsPath, type: .swiftDeps)
239+
return DependencySource(typedSwiftDepsFile)
240+
}
241+
}
242+
228243
// MARK: - Scheduling the 2nd wave
229244
extension ModuleDependencyGraph {
230245
/// After `source` has been compiled, figure out what other source files need compiling.
@@ -234,7 +249,7 @@ extension ModuleDependencyGraph {
234249
func collectInputsRequiringCompilation(byCompiling input: TypedVirtualPath
235250
) -> TransitivelyInvalidatedInputSet? {
236251
precondition(input.type == .swift)
237-
let dependencySource = getSource(for: input)
252+
let dependencySource = source(requiredFor: input)
238253
return collectInputsRequiringCompilationAfterProcessing(
239254
dependencySource: dependencySource)
240255
}
@@ -329,7 +344,7 @@ extension ModuleDependencyGraph {
329344
) -> TransitivelyInvalidatedInputSet? {
330345
var invalidatedInputs = TransitivelyInvalidatedInputSet()
331346
for invalidatedSwiftDeps in collectSwiftDepsUsingInvalidated(nodes: directlyInvalidatedNodes) {
332-
guard let invalidatedInput = getInput(for: invalidatedSwiftDeps) else {
347+
guard let invalidatedInput = input(neededFor: invalidatedSwiftDeps) else {
333348
return nil
334349
}
335350
invalidatedInputs.insert(invalidatedInput)
@@ -406,27 +421,6 @@ extension ModuleDependencyGraph {
406421
}
407422
}
408423

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

@@ -551,10 +545,11 @@ extension ModuleDependencyGraph {
551545
.record(def: dependencyKey, use: self.allNodes[useID])
552546
assert(isNewUse, "Duplicate use def-use arc in graph?")
553547
}
554-
for (input, source) in inputDependencySourceMap {
555-
graph.addMapEntry(input, source)
548+
for (input, dependencySource) in inputDependencySourceMap {
549+
graph.inputDependencySourceMap.addEntry(input,
550+
dependencySource,
551+
for: .readingPriors)
556552
}
557-
558553
return self.graph
559554
}
560555

@@ -850,7 +845,7 @@ extension ModuleDependencyGraph {
850845
}
851846
}
852847

853-
for (input, dependencySource) in graph.inputDependencySourceMap {
848+
graph.inputDependencySourceMap.enumerateToSerializePriors { input, dependencySource in
854849
self.addIdentifier(input.file.name)
855850
self.addIdentifier(dependencySource.file.name)
856851
}
@@ -995,7 +990,8 @@ extension ModuleDependencyGraph {
995990
}
996991
}
997992
}
998-
for (input, dependencySource) in graph.inputDependencySourceMap {
993+
graph.inputDependencySourceMap.enumerateToSerializePriors {
994+
input, dependencySource in
999995
serializer.stream.writeRecord(serializer.abbreviations[.mapNode]!) {
1000996
$0.append(RecordID.mapNode)
1001997
$0.append(serializer.lookupIdentifierCode(for: input.file.name))
@@ -1113,7 +1109,7 @@ extension Set where Element == ModuleDependencyGraph.Node {
11131109
}
11141110
}
11151111

1116-
extension BidirectionalMap where T1 == TypedVirtualPath, T2 == DependencySource {
1112+
extension InputDependencySourceMap {
11171113
fileprivate func matches(_ other: Self) -> Bool {
11181114
self == other
11191115
}
@@ -1131,6 +1127,6 @@ extension ModuleDependencyGraph {
11311127
_ mockInput: TypedVirtualPath,
11321128
_ mockDependencySource: DependencySource
11331129
) {
1134-
addMapEntry(mockInput, mockDependencySource)
1130+
inputDependencySourceMap.addEntry(mockInput, mockDependencySource, for: .mocking)
11351131
}
11361132
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
/// Maps input source files to- and from- `DependencySource`s containing the swiftdeps paths.
15+
/// Deliberately at the level of specific intention, for clarity and to facilitate restructuring later.
16+
@_spi(Testing) public struct InputDependencySourceMap: Equatable {
17+
18+
/// Maps input files (e.g. .swift) to and from the DependencySource object.
19+
///
20+
// FIXME: The map between swiftdeps and swift files is absolutely *not*
21+
// a bijection. In particular, more than one swiftdeps file can be encountered
22+
// in the course of deserializing priors *and* reading the output file map
23+
// *and* re-reading swiftdeps files after frontends complete
24+
// that correspond to the same swift file. These cause two problems:
25+
// - overwrites in this data structure that lose data and
26+
// - cache misses in `getInput(for:)` that cause the incremental build to
27+
// turn over when e.g. entries in the output file map change. This should be
28+
// replaced by a multi-map from swift files to dependency sources,
29+
// and a regular map from dependency sources to swift files -
30+
// since that direction really is one-to-one.
31+
32+
/// Holds the mapping for now. To be replaced later.
33+
public typealias BiMap = BidirectionalMap<TypedVirtualPath, DependencySource>
34+
@_spi(Testing) public var biMap = BiMap()
35+
}
36+
37+
// MARK: - Accessing
38+
extension InputDependencySourceMap {
39+
/// Find where the swiftdeps are stored for a given source file.
40+
///
41+
/// - Parameter input: A source file path
42+
/// - Returns: the corresponding `DependencySource`, or nil if none known.
43+
@_spi(Testing) public func sourceIfKnown(for input: TypedVirtualPath) -> DependencySource? {
44+
biMap[input]
45+
}
46+
47+
/// Find where the source file is for a given swiftdeps file.
48+
///
49+
/// - Parameter source: A `DependencySource` (containing a swiftdeps file)
50+
/// - Returns: the corresponding input source file, or nil if none known.
51+
@_spi(Testing) public func input(ifKnownFor source: DependencySource) -> TypedVirtualPath? {
52+
biMap[source]
53+
}
54+
55+
/// Enumerate the input <-> dependency source pairs to be serialized
56+
///
57+
/// - Parameter eachFn: a function to be called for each pair
58+
@_spi(Testing) public func enumerateToSerializePriors(
59+
_ eachFn: (TypedVirtualPath, DependencySource) -> Void
60+
) {
61+
biMap.forEach(eachFn)
62+
}
63+
}
64+
65+
// MARK: - Populating
66+
extension InputDependencySourceMap {
67+
/// For structural modifications-to-come, reify the various reasons to add an entry.
68+
public enum AdditionPurpose {
69+
/// For unit testing
70+
case mocking
71+
72+
/// When building a graph from swiftDeps files without priors
73+
case buildingFromSwiftDeps
74+
75+
/// Deserializing the map stored with the priors
76+
case readingPriors
77+
78+
/// After reading the priors, used to add entries for any inputs that might not have been in the priors.
79+
case inputsAddedSincePriors
80+
}
81+
82+
/// Add a mapping to & from input source file to swiftDeps file path
83+
///
84+
/// - Parameter input: the source file path
85+
/// - Parameter dependencySource: the dependency source (holding the swiftdeps path)
86+
/// - Parameter why: the purpose for this addition. Will be used for future restructuring.
87+
@_spi(Testing) public mutating func addEntry(_ input: TypedVirtualPath,
88+
_ dependencySource: DependencySource,
89+
for why: AdditionPurpose) {
90+
assert(input.type == .swift && dependencySource.typedFile.type == .swiftDeps)
91+
biMap[input] = dependencySource
92+
}
93+
}

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.input(ifKnownFor: source).map {
125+
input in
125126
"\(node.key) in \(input.file.basename)"
126127
}
127128
?? "\(node.key)"

0 commit comments

Comments
 (0)