Skip to content

Commit 9b3e681

Browse files
author
David Ungar
committed
do not mutate
1 parent 2f08879 commit 9b3e681

File tree

5 files changed

+73
-112
lines changed

5 files changed

+73
-112
lines changed

Sources/SwiftDriver/IncrementalCompilation/BidirectionalMap.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212

1313
/// Like a two-way dictionary
1414
///
15-
// FIXME: The current use of this abstraction in the driver is
16-
// fundamentally broken. This data structure should be retired ASAP.
17-
// See the extended FIXME in its use in
18-
// `ModuleDependencyGraph.inputDependencySourceMap`
1915
public struct BidirectionalMap<T1: Hashable, T2: Hashable>: Equatable, Sequence {
2016
private var map1: [T1: T2] = [:]
2117
private var map2: [T2: T1] = [:]
@@ -74,6 +70,20 @@ public struct BidirectionalMap<T1: Hashable, T2: Hashable>: Equatable, Sequence
7470
public func contains(key: T2) -> Bool {
7571
map2.keys.contains(key)
7672
}
73+
74+
public mutating func updateValue(_ newValue: T2, forKey key: T1) -> T2? {
75+
let oldValue = map1.updateValue(newValue, forKey: key)
76+
_ = oldValue.map {map2.removeValue(forKey: $0)}
77+
map2[newValue] = key
78+
return oldValue
79+
}
80+
public mutating func updateValue(_ newValue: T1, forKey key: T2) -> T1? {
81+
let oldValue = map2.updateValue(newValue, forKey: key)
82+
_ = oldValue.map {map1.removeValue(forKey: $0)}
83+
map1[newValue] = key
84+
return oldValue
85+
}
86+
7787
public mutating func removeValue(forKey t1: T1) {
7888
if let t2 = map1[t1] {
7989
map2.removeValue(forKey: t2)

Sources/SwiftDriver/IncrementalCompilation/IncrementalDependencyAndInputSetup.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,6 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
227227
else {
228228
return buildInitialGraphFromSwiftDepsAndCollectInputsInvalidatedByChangedExternals()
229229
}
230-
guard graph.populateInputDependencySourceMap(for: .inputsAddedSincePriors) else {
231-
return nil
232-
}
233230
graph.dotFileWriter?.write(graph)
234231

235232
// Any externals not already in graph must be additions which should trigger
@@ -255,13 +252,11 @@ extension IncrementalCompilationState.IncrementalDependencyAndInputSetup {
255252
private func buildInitialGraphFromSwiftDepsAndCollectInputsInvalidatedByChangedExternals()
256253
-> (ModuleDependencyGraph, TransitivelyInvalidatedInputSet)?
257254
{
258-
let graph = ModuleDependencyGraph(self, .buildingWithoutAPrior)
259-
assert(outputFileMap.onlySourceFilesHaveSwiftDeps())
260-
261-
guard graph.populateInputDependencySourceMap(for: .buildingFromSwiftDeps) else {
255+
guard let graph = ModuleDependencyGraph(self, .buildingWithoutAPrior)
256+
else {
262257
return nil
263258
}
264-
259+
265260
var inputsInvalidatedByChangedExternals = TransitivelyInvalidatedInputSet()
266261
for input in sourceFiles.currentInOrder {
267262
guard let invalidatedInputs =

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +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-
@_spi(Testing) public private(set) var inputDependencySourceMap = InputDependencySourceMap()
27+
@_spi(Testing) public let inputDependencySourceMap: InputDependencySourceMap
2828

2929
// The set of paths to external dependencies known to be in the graph
3030
public internal(set) var fingerprintedExternalDependencies = Set<FingerprintedExternalDependency>()
@@ -43,7 +43,7 @@ import SwiftOptions
4343
/// Minimize the number of file system modification-time queries.
4444
private var externalDependencyModTimeCache = [ExternalDependency: Bool]()
4545

46-
public init(_ info: IncrementalCompilationState.IncrementalDependencyAndInputSetup,
46+
public init?(_ info: IncrementalCompilationState.IncrementalDependencyAndInputSetup,
4747
_ phase: Phase
4848
) {
4949
self.info = info
@@ -52,6 +52,11 @@ import SwiftOptions
5252
: nil
5353
self.phase = phase
5454
self.creationPhase = phase
55+
guard let inputDependencySourceMap = InputDependencySourceMap(info)
56+
else {
57+
return nil
58+
}
59+
self.inputDependencySourceMap = inputDependencySourceMap
5560
}
5661

5762
@_spi(Testing) public func sourceRequired(for input: TypedVirtualPath,
@@ -188,46 +193,9 @@ extension ModuleDependencyGraph {
188193
return nodeFinder.findNodes(for: source).map {!$0.isEmpty}
189194
?? false
190195
}
191-
192-
/// Returns: false on error
193-
func populateInputDependencySourceMap(
194-
`for` purpose: InputDependencySourceMap.AdditionPurpose
195-
) -> Bool {
196-
let ofm = info.outputFileMap
197-
let diags = info.diagnosticEngine
198-
var allFound = true
199-
for input in info.inputFiles {
200-
if let source = ofm.dependencySource(for: input, diagnosticEngine: diags) {
201-
inputDependencySourceMap.addEntry(input, source, for: purpose)
202-
} else {
203-
// Don't break in order to report all failures.
204-
allFound = false
205-
}
206-
}
207-
return allFound
208-
}
209-
}
210-
extension OutputFileMap {
211-
fileprivate func dependencySource(
212-
for sourceFile: TypedVirtualPath,
213-
diagnosticEngine: DiagnosticsEngine
214-
) -> DependencySource? {
215-
assert(sourceFile.type == FileType.swift)
216-
guard let swiftDepsPath = existingOutput(inputFile: sourceFile.fileHandle,
217-
outputType: .swiftDeps)
218-
else {
219-
// The legacy driver fails silently here.
220-
diagnosticEngine.emit(
221-
.remarkDisabled("\(sourceFile.file.basename) has no swiftDeps file in the output file map")
222-
)
223-
return nil
224-
}
225-
assert(VirtualPath.lookup(swiftDepsPath).extension == FileType.swiftDeps.rawValue)
226-
let typedSwiftDepsFile = TypedVirtualPath(file: swiftDepsPath, type: .swiftDeps)
227-
return DependencySource(typedSwiftDepsFile)
228-
}
229196
}
230197

198+
231199
// MARK: - Scheduling the 2nd wave
232200
extension ModuleDependencyGraph {
233201
/// After `source` has been compiled, figure out what other source files need compiling.
@@ -566,9 +534,13 @@ extension ModuleDependencyGraph {
566534
private var inputDependencySourceMap: [(TypedVirtualPath, DependencySource)] = []
567535
public private(set) var allNodes: [Node] = []
568536

569-
init(_ info: IncrementalCompilationState.IncrementalDependencyAndInputSetup) {
537+
init?(_ info: IncrementalCompilationState.IncrementalDependencyAndInputSetup) {
570538
self.fileSystem = info.fileSystem
571-
self.graph = ModuleDependencyGraph(info, .updatingFromAPrior)
539+
guard let graph = ModuleDependencyGraph(info, .updatingFromAPrior)
540+
else {
541+
return nil
542+
}
543+
self.graph = graph
572544
}
573545

574546
func finalizeGraph() -> ModuleDependencyGraph {
@@ -577,11 +549,6 @@ extension ModuleDependencyGraph {
577549
.record(def: dependencyKey, use: self.allNodes[useID])
578550
assert(isNewUse, "Duplicate use def-use arc in graph?")
579551
}
580-
for (input, dependencySource) in inputDependencySourceMap {
581-
graph.inputDependencySourceMap.addEntry(input,
582-
dependencySource,
583-
for: .readingPriors)
584-
}
585552
return self.graph
586553
}
587554

@@ -717,7 +684,9 @@ extension ModuleDependencyGraph {
717684
}
718685
}
719686

720-
var visitor = Visitor(info)
687+
guard var visitor = Visitor(info) else {
688+
return nil
689+
}
721690
try Bitcode.read(bytes: data, using: &visitor)
722691
guard let major = visitor.majorVersion,
723692
let minor = visitor.minorVersion,
@@ -1152,13 +1121,3 @@ extension Set where Element == FingerprintedExternalDependency {
11521121
self == other
11531122
}
11541123
}
1155-
1156-
/// This should be in a test file, but addMapEntry should be private.
1157-
extension ModuleDependencyGraph {
1158-
@_spi(Testing) public func mockMapEntry(
1159-
_ mockInput: TypedVirtualPath,
1160-
_ mockDependencySource: DependencySource
1161-
) {
1162-
inputDependencySourceMap.addEntry(mockInput, mockDependencySource, for: .mocking)
1163-
}
1164-
}

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/InputDependencySourceMap.swift

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,46 @@ import TSCBasic
1515

1616
/// Maps input files (e.g. .swift) to and from the DependencySource object.
1717
///
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.
18+
// This map caches the same information as in the `OutputFileMap`, but it
19+
// optimizes the reverse lookup, and includes path interning via `DependencySource`.
20+
// Once created, it does not change.
2921

3022
public typealias BiMap = BidirectionalMap<TypedVirtualPath, DependencySource>
31-
@_spi(Testing) public var biMap = BiMap()
23+
@_spi(Testing) public let biMap: BiMap
24+
25+
/// Create the reverse map to map swiftdeps -> input (source) file from the `OutputFileMap`
26+
///
27+
/// - Returns: the map, or nil if error
28+
init?(_ info: IncrementalCompilationState.IncrementalDependencyAndInputSetup) {
29+
let outputFileMap = info.outputFileMap
30+
let diagnosticEngine = info.diagnosticEngine
31+
32+
assert(outputFileMap.onlySourceFilesHaveSwiftDeps())
33+
var hadError = false
34+
self.biMap = info.inputFiles.reduce(into: BiMap()) { biMap, input in
35+
guard input.type == .swift else {return}
36+
guard let dependencySource = outputFileMap.getDependencySource(for: input)
37+
else {
38+
// The legacy driver fails silently here.
39+
diagnosticEngine.emit(
40+
.remarkDisabled("\(input.file.basename) has no swiftDeps file")
41+
)
42+
hadError = true
43+
// Don't stop at the first problem.
44+
return
45+
}
46+
if let sameSourceForInput = biMap.updateValue(dependencySource, forKey: input) {
47+
diagnosticEngine.emit(
48+
.remarkDisabled(
49+
"\(dependencySource) and \(sameSourceForInput) have the same input file in the output file map: \(input)")
50+
)
51+
hadError = true
52+
}
53+
}
54+
if hadError {
55+
return nil
56+
}
57+
}
3258
}
3359

3460
// MARK: - Accessing
@@ -45,20 +71,6 @@ extension InputDependencySourceMap {
4571
_ eachFn: (TypedVirtualPath, DependencySource) -> Void
4672
) {
4773
biMap.forEach(eachFn)
48-
49-
extension OutputFileMap {
50-
@_spi(Testing) public func getDependencySource(
51-
for sourceFile: TypedVirtualPath
52-
) -> DependencySource? {
53-
assert(sourceFile.type == FileType.swift)
54-
guard let swiftDepsPath = existingOutput(inputFile: sourceFile.fileHandle,
55-
outputType: .swiftDeps)
56-
else {
57-
return nil
58-
}
59-
assert(VirtualPath.lookup(swiftDepsPath).extension == FileType.swiftDeps.rawValue)
60-
let typedSwiftDepsFile = TypedVirtualPath(file: swiftDepsPath, type: .swiftDeps)
61-
return DependencySource(typedSwiftDepsFile)
6274
}
6375
}
6476

@@ -77,18 +89,3 @@ extension OutputFileMap {
7789
return DependencySource(typedSwiftDepsFile)
7890
}
7991
}
80-
81-
// MARK: - Populating
82-
extension InputDependencySourceMap {
83-
public enum AdditionPurpose {
84-
case mocking,
85-
buildingFromSwiftDeps,
86-
readingPriors,
87-
inputsAddedSincePriors }
88-
@_spi(Testing) public mutating func addEntry(_ input: TypedVirtualPath,
89-
_ dependencySource: DependencySource,
90-
`for` _ : AdditionPurpose) {
91-
assert(input.type == .swift && dependencySource.typedFile.type == .swiftDeps)
92-
biMap[input] = dependencySource
93-
}
94-
}

Tests/SwiftDriverTests/Helpers/MockingIncrementalCompilation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ struct MockModuleDependencyGraphCreator {
161161
}
162162

163163
func mockUpAGraph() -> ModuleDependencyGraph {
164-
ModuleDependencyGraph(info, .buildingWithoutAPrior)
164+
ModuleDependencyGraph(info, .buildingWithoutAPrior)!
165165
}
166166
}
167167

0 commit comments

Comments
 (0)