Skip to content

Commit fe7100a

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 fe7100a

File tree

6 files changed

+141
-86
lines changed

6 files changed

+141
-86
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: 64 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>()
@@ -64,34 +52,18 @@ import SwiftOptions
6452
: nil
6553
self.phase = phase
6654
self.creationPhase = phase
55+
self.inputDependencySourceMap = InputDependencySourceMap(simulateGetInputFailure: info.simulateGetInputFailure)
6756
}
6857

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]
58+
@_spi(Testing) public func sourceRequired(for input: TypedVirtualPath,
59+
function: String = #function,
60+
file: String = #file,
61+
line: Int = #line) -> DependencySource {
62+
guard let source = inputDependencySourceMap.sourceIfKnown(for: input)
8863
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
64+
fatalError("\(input.file.basename) not found in inputDependencySourceMap, \(file):\(line) in \(function)")
9365
}
94-
return input
66+
return source
9567
}
9668
}
9769

@@ -161,7 +133,7 @@ extension ModuleDependencyGraph {
161133
return TransitivelyInvalidatedInputSet()
162134
}
163135
return collectInputsRequiringCompilationAfterProcessing(
164-
dependencySource: getSource(for: input))
136+
dependencySource: sourceRequired(for: input))
165137
}
166138
}
167139

@@ -183,17 +155,16 @@ extension ModuleDependencyGraph {
183155
/// speculatively scheduled in the first wave.
184156
func collectInputsInvalidatedBy(input: TypedVirtualPath
185157
) -> TransitivelyInvalidatedInputArray {
186-
let changedSource = getSource(for: input)
158+
let changedSource = sourceRequired(for: input)
187159
let allDependencySourcesToRecompile =
188160
collectSwiftDepsUsing(dependencySource: changedSource)
189161

190162
return allDependencySourcesToRecompile.compactMap {
191163
dependencySource in
192164
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
165+
let inputToRecompile = inputDependencySourceMap.inputIfKnown(for: dependencySource)
166+
info.reporter?.report("Found dependent of \(input.file.basename):", inputToRecompile)
167+
return inputToRecompile
197168
}
198169
}
199170

@@ -210,7 +181,7 @@ extension ModuleDependencyGraph {
210181
/// Does the graph contain any dependency nodes for a given source-code file?
211182
func containsNodes(forSourceFile file: TypedVirtualPath) -> Bool {
212183
precondition(file.type == .swift)
213-
guard let source = inputDependencySourceMap[file] else {
184+
guard let source = inputDependencySourceMap.sourceIfKnown(for: file) else {
214185
return false
215186
}
216187
return containsNodes(forDependencySource: source)
@@ -220,17 +191,46 @@ extension ModuleDependencyGraph {
220191
return nodeFinder.findNodes(for: source).map {!$0.isEmpty}
221192
?? false
222193
}
223-
224-
/// Return true on success
225-
func populateInputDependencySourceMap() -> Bool {
194+
195+
/// - Returns: false on error
196+
func populateInputDependencySourceMap(
197+
`for` purpose: InputDependencySourceMap.AdditionPurpose
198+
) -> Bool {
226199
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
200+
let diags = info.diagnosticEngine
201+
var allFound = true
202+
for input in info.inputFiles {
203+
if let source = ofm.dependencySource(for: input, diagnosticEngine: diags) {
204+
inputDependencySourceMap.addEntry(input, source, for: purpose)
205+
} else {
206+
// Don't break in order to report all failures.
207+
allFound = false
208+
}
209+
}
210+
return allFound
211+
}
212+
}
213+
extension OutputFileMap {
214+
fileprivate func dependencySource(
215+
for sourceFile: TypedVirtualPath,
216+
diagnosticEngine: DiagnosticsEngine
217+
) -> DependencySource? {
218+
assert(sourceFile.type == FileType.swift)
219+
guard let swiftDepsPath = existingOutput(inputFile: sourceFile.fileHandle,
220+
outputType: .swiftDeps)
221+
else {
222+
// The legacy driver fails silently here.
223+
diagnosticEngine.emit(
224+
.remarkDisabled("\(sourceFile.file.basename) has no swiftDeps file")
225+
)
226+
return nil
231227
}
228+
assert(VirtualPath.lookup(swiftDepsPath).extension == FileType.swiftDeps.rawValue)
229+
let typedSwiftDepsFile = TypedVirtualPath(file: swiftDepsPath, type: .swiftDeps)
230+
return DependencySource(typedSwiftDepsFile)
232231
}
233232
}
233+
234234
// MARK: - Scheduling the 2nd wave
235235
extension ModuleDependencyGraph {
236236
/// After `source` has been compiled, figure out what other source files need compiling.
@@ -240,7 +240,7 @@ extension ModuleDependencyGraph {
240240
func collectInputsRequiringCompilation(byCompiling input: TypedVirtualPath
241241
) -> TransitivelyInvalidatedInputSet? {
242242
precondition(input.type == .swift)
243-
let dependencySource = getSource(for: input)
243+
let dependencySource = sourceRequired(for: input)
244244
return collectInputsRequiringCompilationAfterProcessing(
245245
dependencySource: dependencySource)
246246
}
@@ -327,8 +327,10 @@ extension ModuleDependencyGraph {
327327
) -> TransitivelyInvalidatedInputSet? {
328328
var invalidatedInputs = TransitivelyInvalidatedInputSet()
329329
for invalidatedSwiftDeps in collectSwiftDepsUsingInvalidated(nodes: directlyInvalidatedNodes) {
330-
guard let invalidatedInput = getInput(for: invalidatedSwiftDeps)
330+
guard let invalidatedInput = inputDependencySourceMap.inputIfKnown(for: invalidatedSwiftDeps)
331331
else {
332+
info.diagnosticEngine.emit(
333+
warning: "Failed to find source file for '\(invalidatedSwiftDeps.file.basename)', recovering with a full rebuild. Next build will be incremental.")
332334
return nil
333335
}
334336
invalidatedInputs.insert(invalidatedInput)
@@ -405,27 +407,6 @@ extension ModuleDependencyGraph {
405407
}
406408
}
407409

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-
429410
// MARK: - tracking traced nodes
430411
extension ModuleDependencyGraph {
431412

@@ -550,10 +531,11 @@ extension ModuleDependencyGraph {
550531
.record(def: dependencyKey, use: self.allNodes[useID])
551532
assert(isNewUse, "Duplicate use def-use arc in graph?")
552533
}
553-
for (input, source) in inputDependencySourceMap {
554-
graph.addMapEntry(input, source)
534+
for (input, dependencySource) in inputDependencySourceMap {
535+
graph.inputDependencySourceMap.addEntry(input,
536+
dependencySource,
537+
for: .readingPriors)
555538
}
556-
557539
return self.graph
558540
}
559541

@@ -849,7 +831,7 @@ extension ModuleDependencyGraph {
849831
}
850832
}
851833

852-
for (input, dependencySource) in graph.inputDependencySourceMap {
834+
graph.inputDependencySourceMap.enumerateToSerializePriors { input, dependencySource in
853835
self.addIdentifier(input.file.name)
854836
self.addIdentifier(dependencySource.file.name)
855837
}
@@ -994,7 +976,8 @@ extension ModuleDependencyGraph {
994976
}
995977
}
996978
}
997-
for (input, dependencySource) in graph.inputDependencySourceMap {
979+
graph.inputDependencySourceMap.enumerateToSerializePriors {
980+
input, dependencySource in
998981
serializer.stream.writeRecord(serializer.abbreviations[.mapNode]!) {
999982
$0.append(RecordID.mapNode)
1000983
$0.append(serializer.lookupIdentifierCode(for: input.file.name))
@@ -1112,7 +1095,7 @@ extension Set where Element == ModuleDependencyGraph.Node {
11121095
}
11131096
}
11141097

1115-
extension BidirectionalMap where T1 == TypedVirtualPath, T2 == DependencySource {
1098+
extension InputDependencySourceMap {
11161099
fileprivate func matches(_ other: Self) -> Bool {
11171100
self == other
11181101
}
@@ -1130,6 +1113,6 @@ extension ModuleDependencyGraph {
11301113
_ mockInput: TypedVirtualPath,
11311114
_ mockDependencySource: DependencySource
11321115
) {
1133-
addMapEntry(mockInput, mockDependencySource)
1116+
inputDependencySourceMap.addEntry(mockInput, mockDependencySource, for: .mocking)
11341117
}
11351118
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
private let simulateGetInputFailure: Bool
34+
35+
init(simulateGetInputFailure: Bool) {
36+
self.simulateGetInputFailure = simulateGetInputFailure
37+
}
38+
}
39+
40+
// MARK: - Accessing
41+
extension InputDependencySourceMap {
42+
@_spi(Testing) public func sourceIfKnown(for input: TypedVirtualPath) -> DependencySource? {
43+
biMap[input]
44+
}
45+
46+
@_spi(Testing) public func inputIfKnown(for source: DependencySource) -> TypedVirtualPath? {
47+
simulateGetInputFailure ? nil : biMap[source]
48+
}
49+
50+
@_spi(Testing) public func enumerateToSerializePriors(
51+
_ eachFn: (TypedVirtualPath, DependencySource) -> Void
52+
) {
53+
biMap.forEach(eachFn)
54+
}
55+
}
56+
57+
// MARK: - Populating
58+
extension InputDependencySourceMap {
59+
public enum AdditionPurpose {
60+
case mocking,
61+
buildingFromSwiftDeps,
62+
readingPriors,
63+
inputsAddedSincePriors }
64+
@_spi(Testing) public mutating func addEntry(_ input: TypedVirtualPath,
65+
_ dependencySource: DependencySource,
66+
`for` _ : AdditionPurpose) {
67+
assert(input.type == .swift && dependencySource.typedFile.type == .swiftDeps)
68+
biMap[input] = dependencySource
69+
}
70+
}

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)"

Tests/SwiftDriverTests/IncrementalCompilationTests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,8 @@ extension IncrementalCompilationTests {
513513
.remark("Finished Compiling main.swift"),
514514
.remark("Incremental compilation: Fingerprint changed for interface of source file main.swiftdeps in main.swiftdeps"),
515515
.remark("Incremental compilation: Fingerprint changed for implementation of source file main.swiftdeps in main.swiftdeps"),
516-
.remark("Incremental compilation: Traced: interface of source file main.swiftdeps in main.swift -> interface of top-level name 'foo' in main.swift -> implementation of source file other.swiftdeps in other.swift"),
516+
.remark("Incremental compilation: Traced: interface of source file main.swiftdeps -> interface of top-level name 'foo' -> implementation of source file other.swiftdeps"),
517517
.warning("Failed to find source file for '"),
518-
.remark("Simulating input not found in inputDependencySourceMap; created for: updatingFromAPrior, now: updatingAfterCompilation"),
519518
.remark("Incremental compilation: Failed to read some dependencies source; compiling everything {compile: main.o <= main.swift}"),
520519
.remark("Incremental compilation: Queuing because of dependencies discovered later: {compile: other.o <= other.swift}"),
521520
.remark("Incremental compilation: Scheduling invalidated {compile: other.o <= other.swift}"),

0 commit comments

Comments
 (0)