Skip to content

Commit 00fada4

Browse files
authored
Merge pull request #486 from davidungar/holder-enforcer
[Incremental] Centralize fingerprint/swiftmodule invariant, catch bad paths earlier
2 parents 8126c2f + 4f6e7a7 commit 00fada4

File tree

11 files changed

+225
-171
lines changed

11 files changed

+225
-171
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ add_library(SwiftDriver
4343
"IncrementalCompilation/BuildRecordInfo.swift"
4444
"IncrementalCompilation/DependencyKey.swift"
4545
"IncrementalCompilation/DictionaryOfDictionaries.swift"
46+
"IncrementalCompilation/ExternalDependencyAndFingerprintEnforcer.swift"
4647
"IncrementalCompilation/IncrementalCompilationState.swift"
4748
"IncrementalCompilation/InputInfo.swift"
48-
"IncrementalCompilation/KeyAndFingerprintEnforcer.swift"
49+
"IncrementalCompilation/KeyAndFingerprintHolder.swift"
4950
"IncrementalCompilation/ModuleDependencyGraph.swift"
5051
"IncrementalCompilation/Multidictionary.swift"
5152
"IncrementalCompilation/SourceFileDependencyGraph.swift"

Sources/SwiftDriver/IncrementalCompilation/DependencyKey.swift

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,47 @@
11

22
import Foundation
3+
import TSCBasic
34

45
/// A filename from another module
56
/*@_spi(Testing)*/ public struct ExternalDependency: Hashable, Comparable, CustomStringConvertible {
6-
let fileName: String
7-
let fingerprint: String?
7+
let file: VirtualPath
88

9-
/*@_spi(Testing)*/ public init(_ fileName: String, fingerprint: String? = nil) {
10-
self.fileName = fileName
11-
self.fingerprint = fingerprint
9+
/// Cache this
10+
let isSwiftModule: Bool
11+
12+
/*@_spi(Testing)*/ public init(_ fileName: String)
13+
throws {
14+
self.file = try VirtualPath(path: fileName)
15+
self.isSwiftModule = file.extension == FileType.swiftModule.rawValue
1216
}
1317

14-
var file: VirtualPath? {
15-
try? VirtualPath(path: fileName)
18+
func modTime(_ fileSystem: FileSystem) -> Date? {
19+
try? fileSystem.getFileInfo(file).modTime
1620
}
1721

1822
public var description: String {
19-
fileName.description
23+
file.name
2024
}
2125

2226
public static func < (lhs: Self, rhs: Self) -> Bool {
23-
lhs.fileName < rhs.fileName
27+
lhs.file.name < rhs.file.name
2428
}
2529
}
2630

2731
/// Since the integration surfaces all externalDependencies to be processed later,
2832
/// a combination of the dependency and fingerprint are needed.
29-
public struct FingerprintedExternalDependency: Hashable, Equatable {
33+
public struct FingerprintedExternalDependency: Hashable, Equatable, ExternalDependencyAndFingerprintEnforcer {
3034
let externalDependency: ExternalDependency
3135
let fingerprint: String?
3236
init(_ externalDependency: ExternalDependency, _ fingerprint: String?) {
3337
self.externalDependency = externalDependency
3438
self.fingerprint = fingerprint
39+
assert(verifyExternalDependencyAndFingerprint())
40+
}
41+
var externalDependencyToCheck: ExternalDependency? { externalDependency }
42+
var isIncremental: Bool {
43+
fingerprint != nil && externalDependency.isSwiftModule
3544
}
36-
var isIncremental: Bool { fingerprint != nil }
3745
}
3846

3947
/// A `DependencyKey` carries all of the information necessary to uniquely
@@ -184,7 +192,7 @@ public struct DependencyKey: Hashable, CustomStringConvertible {
184192
case let .dynamicLookup(name: name):
185193
return "AnyObject member '\(name)'"
186194
case let .externalDepend(externalDependency):
187-
return "module '\(externalDependency)'"
195+
return "import '\(externalDependency)'"
188196
case let .sourceFileProvide(name: name):
189197
return "source file \((try? VirtualPath(path: name).basename) ?? name)"
190198
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===------- FingerprintedExternalHolder.swift ----------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Central place to check an invariant: If an externalDependency has a fingerprint, it should point
14+
/// to a swiftmodule file that contains dependency information.
15+
16+
protocol ExternalDependencyAndFingerprintEnforcer {
17+
var externalDependencyToCheck: ExternalDependency? {get}
18+
var fingerprint: String? {get}
19+
}
20+
extension ExternalDependencyAndFingerprintEnforcer {
21+
func verifyExternalDependencyAndFingerprint() -> Bool {
22+
if let fingerprint = self.fingerprint,
23+
let externalDependency = externalDependencyToCheck,
24+
!externalDependency.isSwiftModule {
25+
fatalError("An external dependency with a fingerprint (\(fingerprint)) must point to a swiftmodule file: \(externalDependency)")
26+
}
27+
return true
28+
}
29+
}

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,15 +564,14 @@ extension IncrementalCompilationState {
564564
var externalDependencySources = Set<DependencySource>()
565565
for extDepAndPrint in moduleDependencyGraph.fingerprintedExternalDependencies {
566566
let extDep = extDepAndPrint.externalDependency
567-
let extModTime = extDep.file.flatMap {try? fileSystem.getFileInfo($0).modTime}
568-
?? Date.distantFuture
567+
let extModTime = extDep.modTime(fileSystem) ?? Date.distantFuture
569568
if extModTime >= buildTime {
570569
for dependent in moduleDependencyGraph.untracedDependents(of: extDepAndPrint) {
571570
guard let dependencySource = dependent.dependencySource else {
572571
fatalError("Dependent \(dependent) does not have dependencies file!")
573572
}
574573
reporter?.report(
575-
"Queuing because of external dependency on newer \(extDep.file?.basename ?? "extDep?")",
574+
"Queuing because of external dependency on newer \(extDep.file.basename)",
576575
dependencySource.typedFile)
577576
externalDependencySources.insert(dependencySource)
578577
}

Sources/SwiftDriver/IncrementalCompilation/KeyAndFingerprintEnforcer.swift

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//===--------- KeyAndFingerprintHolder.swift ------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
16+
/// Encapsulates the invariant required for anything with a DependencyKey and an fingerprint
17+
public struct KeyAndFingerprintHolder: ExternalDependencyAndFingerprintEnforcer {
18+
/// Def->use arcs go by DependencyKey. There may be >1 node for a given key.
19+
let key: DependencyKey
20+
21+
/// The frontend records in the fingerprint, all of the information about an
22+
/// entity, such that any uses need be rebuilt only if the fingerprint
23+
/// changes.
24+
/// When the driver reloads a dependency graph (after a frontend job has run),
25+
/// it can use the fingerprint to determine if the entity has changed and thus
26+
/// if uses need to be recompiled.
27+
///
28+
/// However, at present, the frontend does not record this information for
29+
/// every Decl; it only records it for the source-file-as-a-whole in the
30+
/// interface hash. The inteface hash is a product of all the tokens that are
31+
/// not inside of function bodies. Thus, if there is no fingerprint, when the
32+
/// frontend creates an interface node,
33+
/// it adds a dependency to it from the implementation source file node (which
34+
/// has the intefaceHash as its fingerprint).
35+
let fingerprint: String?
36+
37+
init(_ key: DependencyKey, _ fingerprint: String?) throws {
38+
self.key = key
39+
self.fingerprint = fingerprint
40+
assert(verifyKeyAndFingerprint())
41+
}
42+
var externalDependencyToCheck: ExternalDependency? {
43+
key.designator.externalDependency
44+
}
45+
private func verifyKeyAndFingerprint() -> Bool {
46+
assert(verifyExternalDependencyAndFingerprint())
47+
48+
if let externalDependency = externalDependencyToCheck, key.aspect != .interface {
49+
fatalError("Aspect of external dependency must be interface: \(externalDependency)")
50+
}
51+
return true
52+
}
53+
}

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ extension ModuleDependencyGraph {
213213
guard externalDependency.isIncremental else {
214214
return Integrator.Results()
215215
}
216-
let file = externalDependency.externalDependency.file!
216+
let file = externalDependency.externalDependency.file
217217
let dependencySource = DependencySource(file)
218218
reporter?.report("integrating incremental external dependency",
219219
dependencySource.typedFile)
@@ -596,7 +596,7 @@ extension ModuleDependencyGraph {
596596
let hasFingerprint = Int(record.fields[1]) != 0
597597
let fingerprint = hasFingerprint ? fingerprintStr : nil
598598
self.graph.fingerprintedExternalDependencies.insert(
599-
FingerprintedExternalDependency(ExternalDependency(path), fingerprint))
599+
FingerprintedExternalDependency(try ExternalDependency(path), fingerprint))
600600
case .identifierNode:
601601
guard record.fields.count == 0,
602602
case .blob(let identifierBlob) = record.payload,
@@ -768,7 +768,7 @@ extension ModuleDependencyGraph {
768768
}
769769

770770
for edF in graph.fingerprintedExternalDependencies {
771-
self.addIdentifier(edF.externalDependency.fileName)
771+
self.addIdentifier(edF.externalDependency.file.name)
772772
}
773773

774774
for str in self.identifiersToWrite {
@@ -905,7 +905,7 @@ extension ModuleDependencyGraph {
905905
serializer.stream.writeRecord(serializer.abbreviations[.externalDepNode]!, {
906906
$0.append(RecordID.externalDepNode)
907907
$0.append(serializer.lookupIdentifierCode(
908-
for: fingerprintedExternalDependency.externalDependency.fileName))
908+
for: fingerprintedExternalDependency.externalDependency.file.name))
909909
$0.append((fingerprintedExternalDependency.fingerprint != nil) ? UInt32(1) : UInt32(0))
910910
},
911911
blob: (fingerprintedExternalDependency.fingerprint ?? ""))
@@ -963,7 +963,7 @@ fileprivate extension DependencyKey.Designator {
963963
self = .dynamicLookup(name: name)
964964
case 5:
965965
try mustBeEmpty(context)
966-
self = .externalDepend(ExternalDependency(name))
966+
self = .externalDepend(try ExternalDependency(name))
967967
case 6:
968968
try mustBeEmpty(context)
969969
self = .sourceFileProvide(name: name)
@@ -1016,7 +1016,7 @@ fileprivate extension DependencyKey.Designator {
10161016
case .dynamicLookup(name: let name):
10171017
return name
10181018
case .externalDepend(let path):
1019-
return path.fileName
1019+
return path.file.name
10201020
case .sourceFileProvide(name: let name):
10211021
return name
10221022
case .member(context: _, name: let name):

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/Node.swift

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,16 @@ extension ModuleDependencyGraph {
2525
/// Use a class, not a struct because otherwise it would be duplicated for each thing it uses
2626

2727
/*@_spi(Testing)*/
28-
public final class Node: KeyAndFingerprintEnforcer {
28+
public final class Node {
2929

3030
/*@_spi(Testing)*/ public typealias Graph = ModuleDependencyGraph
3131

32-
/// Def->use arcs go by DependencyKey. There may be >1 node for a given key.
33-
let key: DependencyKey
32+
/// Hold these where an invariant can be checked.
33+
let keyAndFingerprint: KeyAndFingerprintHolder
34+
35+
var key: DependencyKey { keyAndFingerprint.key }
36+
var fingerprint: String? { keyAndFingerprint.fingerprint }
3437

35-
/// The frontend records in the fingerprint, all of the information about an
36-
/// entity, such that any uses need be rebuilt only if the fingerprint
37-
/// changes.
38-
/// When the driver reloads a dependency graph (after a frontend job has run),
39-
/// it can use the fingerprint to determine if the entity has changed and thus
40-
/// if uses need to be recompiled.
41-
///
42-
/// However, at present, the frontend does not record this information for
43-
/// every Decl; it only records it for the source-file-as-a-whole in the
44-
/// interface hash. The inteface hash is a product of all the tokens that are
45-
/// not inside of function bodies. Thus, if there is no fingerprint, when the
46-
/// frontend creates an interface node,
47-
/// it adds a dependency to it from the implementation source file node (which
48-
/// has the intefaceHash as its fingerprint).
49-
let fingerprint: String?
5038

5139

5240
/// The dependencySource file that holds this entity iff the entities .swiftdeps (or in future, .swiftmodule) is known.
@@ -62,11 +50,8 @@ extension ModuleDependencyGraph {
6250
/// SourceFileDependencyGraph or the DependencyKeys
6351
init(key: DependencyKey, fingerprint: String?,
6452
dependencySource: DependencySource?) {
65-
self.key = key
66-
self.fingerprint = fingerprint
53+
self.keyAndFingerprint = try! KeyAndFingerprintHolder(key, fingerprint)
6754
self.dependencySource = dependencySource
68-
69-
try! verifyKeyAndFingerprint()
7055
}
7156
}
7257
}

0 commit comments

Comments
 (0)