Skip to content

Commit fb9a032

Browse files
author
David Ungar
authored
[Incremental] Fix fingerprint integration bug, add a test, and add debugging affordances. (#505)
* Fix fingerprint integration bug, add a test, and add debugging affordances. * Add a warning
1 parent 8f1d770 commit fb9a032

File tree

7 files changed

+86
-15
lines changed

7 files changed

+86
-15
lines changed

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,8 @@ extension ModuleDependencyGraph {
307307
fed.incrementalDependencySource?
308308
.read(in: info.fileSystem, reporter: info.reporter)
309309
.map { unserializedDepGraph in
310-
Integrator.integrate(
310+
info.reporter?.report("Integrating changes from", fed.externalDependency.file)
311+
return Integrator.integrate(
311312
from: unserializedDepGraph,
312313
into: self,
313314
includeAddedExternals: includeAddedExternals)

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/Integrator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ extension ModuleDependencyGraph.Integrator {
136136
// Node was and still is. Do not remove it.
137137
disappearedNodes.removeValue(forKey: matchHere.key)
138138
if matchHere.fingerprint != integrand.fingerprint {
139+
matchHere.setFingerprint(integrand.fingerprint)
139140
results.allInvalidatedNodes.insert(matchHere)
140141
}
141142
return matchHere

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/Node.swift

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,21 @@ extension ModuleDependencyGraph {
2323
/// (Cargo-culted and modified from the legacy driver.)
2424
///
2525
/// Use a class, not a struct because otherwise it would be duplicated for each thing it uses
26+
///
27+
/// Neither the `fingerprint`, nor the `isTraced` value is part of the node's identity.
28+
/// Neither of these must be considered for equality testing or hashing because their
29+
/// value is subject to change during integration and tracing.
2630

2731
public final class Node {
2832

2933
/*@_spi(Testing)*/ public typealias Graph = ModuleDependencyGraph
3034

3135
/// Hold these where an invariant can be checked.
32-
let keyAndFingerprint: KeyAndFingerprintHolder
36+
/// Must be able to change the fingerprint
37+
private(set) var keyAndFingerprint: KeyAndFingerprintHolder
3338

3439
var key: DependencyKey { keyAndFingerprint.key }
35-
var fingerprint: String? { keyAndFingerprint.fingerprint }
40+
/*@_spi(Testing)*/ public var fingerprint: String? { keyAndFingerprint.fingerprint }
3641

3742
/// The dependencySource file that holds this entity iff the entities .swiftdeps (or in future, .swiftmodule) is known.
3843
/// If more than one source file has the same DependencyKey, then there
@@ -59,23 +64,28 @@ extension ModuleDependencyGraph {
5964
}
6065
}
6166

67+
// MARK: - Setting fingerprint
68+
extension ModuleDependencyGraph.Node {
69+
func setFingerprint(_ newFP: String?) {
70+
keyAndFingerprint = try! KeyAndFingerprintHolder(key, newFP)
71+
}
72+
}
73+
6274
// MARK: - trace status
6375
extension ModuleDependencyGraph.Node {
6476
var isUntraced: Bool { !isTraced }
6577
func setTraced() { isTraced = true }
66-
func setUntraced() { isTraced = false }
78+
@_spi(Testing) public func setUntraced() { isTraced = false }
6779
}
6880

6981
// MARK: - comparing, hashing
7082
extension ModuleDependencyGraph.Node: Equatable, Hashable {
71-
public static func == (lhs: Graph.Node, rhs: Graph.Node) -> Bool {
72-
lhs.key == rhs.key && lhs.fingerprint == rhs.fingerprint
73-
&& lhs.dependencySource == rhs.dependencySource
83+
public static func ==(lhs: ModuleDependencyGraph.Node, rhs: ModuleDependencyGraph.Node) -> Bool {
84+
lhs.keyAndFingerprint.key == rhs.keyAndFingerprint.key &&
85+
lhs.dependencySource == rhs.dependencySource
7486
}
75-
7687
public func hash(into hasher: inout Hasher) {
77-
hasher.combine(key)
78-
hasher.combine(fingerprint)
88+
hasher.combine(keyAndFingerprint.key)
7989
hasher.combine(dependencySource)
8090
}
8191
}

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/NodeFinder.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ extension ModuleDependencyGraph.NodeFinder {
173173
let replacement = Graph.Node(key: original.key,
174174
fingerprint: newFingerprint,
175175
dependencySource: newDependencySource)
176+
assert(original.isExpat, "Would have to search every use in usesByDef if original could be a use.")
176177
if usesByDef.removeValue(original, forKey: original.key) != nil {
177178
usesByDef.insertValue(replacement, forKey: original.key)
178179
}

Sources/SwiftDriver/IncrementalCompilation/SourceFileDependencyGraph.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import TSCUtility
5858
}
5959

6060
extension SourceFileDependencyGraph {
61-
public struct Node {
61+
public struct Node: CustomStringConvertible {
6262
public let keyAndFingerprint: KeyAndFingerprintHolder
6363
public var key: DependencyKey { keyAndFingerprint.key }
6464
public var fingerprint: String? { keyAndFingerprint.fingerprint }
@@ -92,6 +92,17 @@ extension SourceFileDependencyGraph {
9292
}
9393
}
9494
}
95+
96+
public var description: String {
97+
[
98+
key.description,
99+
fingerprint.map {"fingerprint: \($0.description)"},
100+
isProvides ? "provides" : "depends",
101+
defsIDependUpon.isEmpty ? nil : "depends on \(defsIDependUpon.count)"
102+
]
103+
.compactMap{$0}
104+
.joined(separator: ", ")
105+
}
95106
}
96107
}
97108

Tests/SwiftDriverTests/Helpers/MockingIncrementalCompilation.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@ extension ModuleDependencyGraph {
2929
}
3030
return false
3131
}
32+
33+
func setUntraced() {
34+
nodeFinder.forEachNode {
35+
$0.setUntraced()
36+
}
37+
}
38+
39+
func ensureIsSerializable() {
40+
var nodeIDs = Set<Node>()
41+
nodeFinder.forEachNode { node in
42+
nodeIDs.insert(node)
43+
}
44+
for key in nodeFinder.usesByDef.keys {
45+
for use in nodeFinder.usesByDef[key, default: []] {
46+
XCTAssertTrue(nodeIDs.contains(use), "Node ID was not registered! \(use), \(String(describing: use.fingerprint))")
47+
}
48+
}
49+
}
3250
}
3351

3452
// MARK: - mocking

Tests/SwiftDriverTests/ModuleDependencyGraphTests.swift

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ class ModuleDependencyGraphTests: XCTestCase {
752752

753753
func testLoadPassesWithFingerprint() {
754754
let graph = ModuleDependencyGraph(mock: de)
755-
_ = graph.getInvalidatedSourcesForSimulatedLoad(
755+
_ = graph.getInvalidatedNodesForSimulatedLoad(
756756
0,
757757
[MockDependencyKind.nominal: ["A@1"]],
758758
includeAddedExternals: false)
@@ -781,6 +781,35 @@ class ModuleDependencyGraphTests: XCTestCase {
781781
}
782782
}
783783

784+
func testUseFingerprintsPingPong() {
785+
let graph = ModuleDependencyGraph(mock: de)
786+
// Because of the cross-type dependency, A->B,
787+
// when A changes, only B is dirtied in 1.
788+
789+
graph.simulateLoad(0, [.nominal: ["A@1"]])
790+
graph.simulateLoad(1, [.nominal: ["B", "A->B"]])
791+
graph.ensureIsSerializable()
792+
793+
do {
794+
let swiftDeps = graph.simulateReload(0, [.nominal: ["A@2"]]).sorted()
795+
XCTAssertEqual(swiftDeps, [0, 1])
796+
graph.ensureIsSerializable()
797+
}
798+
799+
do {
800+
// In the real world, we would have a graph with untraced nodes from the
801+
// priors, remembering the fingerprints from before.
802+
graph.setUntraced()
803+
// When the driver integrates a node that preexists but has a new fingerprint
804+
// it must change that fingerprint in the node in the graph.
805+
// If it does not, and subsequently integrates the old fingerprint,
806+
// the change will be missed.
807+
let swiftDeps = graph.simulateReload(0, [.nominal: ["A@1"]]).sorted()
808+
XCTAssertEqual(swiftDeps, [0, 1])
809+
graph.ensureIsSerializable()
810+
}
811+
}
812+
784813
func testCrossTypeDependencyBaseline() {
785814
let graph = ModuleDependencyGraph(mock: de)
786815
graph.simulateLoad(0, [.nominal: ["A"]])
@@ -894,7 +923,7 @@ extension ModuleDependencyGraph {
894923
includePrivateDeps: Bool = true,
895924
hadCompilationError: Bool = false)
896925
{
897-
_ = getInvalidatedSourcesForSimulatedLoad(
926+
_ = getInvalidatedNodesForSimulatedLoad(
898927
swiftDepsIndex, dependencyDescriptions,
899928
includeAddedExternals: false,
900929
interfaceHash,
@@ -909,7 +938,7 @@ extension ModuleDependencyGraph {
909938
hadCompilationError: Bool = false)
910939
-> [Int]
911940
{
912-
let invalidatedNodes = getInvalidatedSourcesForSimulatedLoad(
941+
let invalidatedNodes = getInvalidatedNodesForSimulatedLoad(
913942
swiftDepsIndex,
914943
dependencyDescriptions,
915944
includeAddedExternals: true,
@@ -922,7 +951,7 @@ extension ModuleDependencyGraph {
922951
}
923952

924953

925-
func getInvalidatedSourcesForSimulatedLoad(
954+
func getInvalidatedNodesForSimulatedLoad(
926955
_ swiftDepsIndex: Int,
927956
_ dependencyDescriptions: [MockDependencyKind: [String]],
928957
includeAddedExternals: Bool,

0 commit comments

Comments
 (0)