Skip to content

Commit e656eac

Browse files
author
David Ungar
authored
Merge pull request #283 from davidungar/module-dep-graph-10-1b
Cargo-culted, but then Swift-ified and cleaned-up ModuleDependencyGraph
2 parents 4982f8a + 8159a0d commit e656eac

20 files changed

+3060
-122
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,21 @@ add_library(SwiftDriver
2929
Execution/ParsableOutput.swift
3030
Execution/ProcessProtocol.swift
3131

32+
"Incremental Compilation/ModuleDependencyGraph Parts/Integrator.swift"
33+
"Incremental Compilation/ModuleDependencyGraph Parts/JobTracker.swift"
34+
"Incremental Compilation/ModuleDependencyGraph Parts/Node.swift"
35+
"Incremental Compilation/ModuleDependencyGraph Parts/NodeFinder.swift"
36+
"Incremental Compilation/ModuleDependencyGraph Parts/SwiftDeps.swift"
37+
"Incremental Compilation/ModuleDependencyGraph Parts/Tracer.swift"
38+
"Incremental Compilation/DependencyKey.swift"
39+
"Incremental Compilation/DictionaryOfDictionaries.swift"
3240
"Incremental Compilation/IncrementalCompilationState.swift"
3341
"Incremental Compilation/InputIInfoMap.swift"
3442
"Incremental Compilation/InputInfo.swift"
43+
"Incremental Compilation/ModuleDependencyGraph.swift"
44+
"Incremental Compilation/Multidictionary.swift"
3545
"Incremental Compilation/SourceFileDependencyGraph.swift"
46+
"Incremental Compilation/TwoDMap.swift"
3647

3748
Jobs/AutolinkExtractJob.swift
3849
Jobs/BackendJob.swift
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
2+
import Foundation
3+
4+
/// A filename from another module
5+
@_spi(Testing) public struct ExternalDependency: Hashable, CustomStringConvertible {
6+
let fileName: String
7+
8+
@_spi(Testing) public init(_ path: String) {
9+
self.fileName = path
10+
}
11+
12+
public var description: String {
13+
fileName.description
14+
}
15+
}
16+
17+
18+
19+
@_spi(Testing) public struct DependencyKey: Hashable {
20+
/// Instead of the status quo scheme of two kinds of "Depends", cascading and
21+
/// non-cascading this code represents each entity ("Provides" in the status
22+
/// quo), by a pair of nodes. One node represents the "implementation." If the
23+
/// implementation changes, users of the entity need not be recompiled. The
24+
/// other node represents the "interface." If the interface changes, any uses of
25+
/// that definition will need to be recompiled. The implementation always
26+
/// depends on the interface, since any change that alters the interface will
27+
/// require the implementation to be rebuilt. The interface does not depend on
28+
/// the implementation. In the dot files, interfaces are yellow and
29+
/// implementations white. Each node holds an instance variable describing which
30+
/// aspect of the entity it represents.
31+
32+
@_spi(Testing) public enum DeclAspect {
33+
case interface, implementation
34+
}
35+
36+
/// Encode the current sorts of dependencies as kinds of nodes in the dependency
37+
/// graph, splitting the current *member* into \ref member and \ref
38+
/// potentialMember and adding \ref sourceFileProvide.
39+
///
40+
@_spi(Testing) public enum Designator: Hashable {
41+
case
42+
topLevel(name: String),
43+
dynamicLookup(name: String),
44+
externalDepend(ExternalDependency),
45+
sourceFileProvide(name: String)
46+
47+
case
48+
nominal(context: String),
49+
potentialMember(context: String)
50+
51+
case
52+
member(context: String, name: String)
53+
54+
var externalDependency: ExternalDependency? {
55+
switch self {
56+
case let .externalDepend(externalDependency):
57+
return externalDependency
58+
default:
59+
return nil}
60+
}
61+
}
62+
63+
@_spi(Testing) public let aspect: DeclAspect
64+
@_spi(Testing) public let designator: Designator
65+
66+
67+
@_spi(Testing) public init(
68+
aspect: DeclAspect,
69+
designator: Designator)
70+
{
71+
self.aspect = aspect
72+
self.designator = designator
73+
}
74+
75+
76+
@_spi(Testing) public var correspondingImplementation: Self {
77+
assert(aspect == .interface)
78+
return Self(aspect: .implementation, designator: designator)
79+
}
80+
81+
@discardableResult
82+
func verify() -> Bool {
83+
// This space reserved for future use.
84+
return true
85+
}
86+
}
87+
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//===---------------- DictionaryOfDictionaries.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+
/// A dictionary of dictionaries that also behaves like dictionary of tuples
14+
/// It supports iterating over all 2nd-level pairs. See \code subscript(key: OuterKey)
15+
16+
import Foundation
17+
@_spi(Testing) public struct DictionaryOfDictionaries<OuterKey: Hashable, InnerKey: Hashable, Value>: Collection {
18+
public typealias InnerDict = [InnerKey: Value]
19+
public typealias OuterDict = [OuterKey: InnerDict]
20+
21+
public typealias Key = (OuterKey, InnerKey)
22+
public typealias Element = (Key, Value)
23+
24+
var outerDict = [OuterKey: [InnerKey: Value]]()
25+
}
26+
27+
// MARK: indices
28+
extension DictionaryOfDictionaries {
29+
public enum Index: Comparable {
30+
case end
31+
case notEnd(OuterDict.Index, InnerDict.Index)
32+
33+
public static func < (lhs: Self, rhs: Self)
34+
-> Bool
35+
{
36+
switch (lhs, rhs) {
37+
case (.end, .end): return false
38+
case (_, .end): return true
39+
case (.end, _): return false
40+
case let (.notEnd(lo, li), .notEnd(ro, ri)):
41+
switch (lo, ro, li, ri) {
42+
case let (lo, ro, _, _) where lo != ro: return lo < ro
43+
case let (_, _, li, ri): return li < ri
44+
}
45+
}
46+
}
47+
}
48+
49+
private func makeIndex(_ oi: OuterDict.Index, _ ii: InnerDict.Index) -> Index {
50+
assert(outerDict[oi].value.indices.contains(ii))
51+
return .notEnd(oi, ii)
52+
}
53+
54+
public var startIndex: Index {
55+
return outerDict.isEmpty
56+
? endIndex
57+
: makeIndex(outerDict.startIndex,
58+
outerDict.first!.value.startIndex)
59+
}
60+
public var endIndex: Index {
61+
return .end
62+
}
63+
64+
public func index(after i: Index) -> Index {
65+
switch i {
66+
case .end: fatalError("index at end")
67+
case let .notEnd(outerIndex, innerIndex):
68+
let innerDict = outerDict[outerIndex].value
69+
let nextInnerIndex = innerDict.index(after: innerIndex)
70+
if nextInnerIndex < innerDict.endIndex {
71+
return makeIndex(outerIndex, nextInnerIndex)
72+
}
73+
let nextOuterIndex = outerDict.index(after: outerIndex)
74+
if nextOuterIndex < outerDict.endIndex {
75+
return .notEnd(nextOuterIndex, outerDict[nextOuterIndex].value.startIndex)
76+
}
77+
return .end
78+
}
79+
}
80+
}
81+
82+
// MARK: - subscripting
83+
extension DictionaryOfDictionaries {
84+
public subscript(position: Index) -> Element {
85+
switch position {
86+
case .end: fatalError("index at end")
87+
case let .notEnd(outerIndex, innerIndex):
88+
let (outerKey, innerDict) = outerDict[outerIndex]
89+
let (innerKey, value) = innerDict[innerIndex]
90+
return (key: (outerKey, innerKey), value: value)
91+
}
92+
}
93+
94+
public subscript(key: Key) -> Value? {
95+
get {outerDict[key.0]?[key.1]}
96+
set {
97+
if let v = newValue { _ = updateValue(v, forKey: key) }
98+
else { _ = removeValue(forKey: key) }
99+
}
100+
}
101+
102+
public subscript(key: OuterKey) -> [InnerKey: Value]? {
103+
get {outerDict[key]}
104+
set {
105+
if let v = newValue { _ = outerDict.updateValue(v, forKey: key) }
106+
else { _ = outerDict.removeValue(forKey: key) }
107+
}
108+
}
109+
}
110+
111+
// MARK: - mutating
112+
extension DictionaryOfDictionaries {
113+
mutating func updateValue(_ v: Value, forKey keys : (OuterKey,InnerKey)
114+
) -> Value? {
115+
if var innerDict = outerDict[keys.0] {
116+
let old = innerDict.updateValue(v, forKey: keys.1)
117+
outerDict.updateValue(innerDict, forKey: keys.0)
118+
return old
119+
}
120+
outerDict.updateValue([keys.1: v], forKey: keys.0)
121+
return nil
122+
}
123+
124+
mutating func removeValue(forKey keys : (OuterKey,InnerKey)
125+
) -> Value? {
126+
guard var innerDict = outerDict[keys.0]
127+
else { return nil }
128+
let old = innerDict.removeValue(forKey: keys.1)
129+
if innerDict.isEmpty {
130+
outerDict.removeValue(forKey: keys.0)
131+
}
132+
else {
133+
outerDict.updateValue(innerDict, forKey: keys.0)
134+
}
135+
return old
136+
}
137+
}

Sources/SwiftDriver/Incremental Compilation/IncrementalCompilationState.swift

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,43 +22,43 @@ import SwiftOptions
2222
public let lastBuildTime: Date
2323
public let outOfDateMap: InputInfoMap?
2424
public var rebuildEverything: Bool { return outOfDateMap == nil }
25-
25+
2626
public init(_ parsedOptions: inout ParsedOptions,
27-
compilerMode: CompilerMode,
28-
outputFileMap: OutputFileMap?,
29-
compilerOutputType: FileType?,
30-
moduleOutput: ModuleOutputInfo.ModuleOutput?,
31-
fileSystem: FileSystem,
32-
inputFiles: [TypedVirtualPath],
33-
diagnosticEngine: DiagnosticsEngine,
34-
actualSwiftVersion: String?
27+
compilerMode: CompilerMode,
28+
outputFileMap: OutputFileMap?,
29+
compilerOutputType: FileType?,
30+
moduleOutput: ModuleOutputInfo.ModuleOutput?,
31+
fileSystem: FileSystem,
32+
inputFiles: [TypedVirtualPath],
33+
diagnosticEngine: DiagnosticsEngine,
34+
actualSwiftVersion: String?
3535
) {
3636
let showIncrementalBuildDecisions = Self.getShowIncrementalBuildDecisions(&parsedOptions)
3737
self.showIncrementalBuildDecisions = showIncrementalBuildDecisions
38-
38+
3939
let enableIncrementalBuild = Self.computeAndExplainShouldCompileIncrementally(
4040
&parsedOptions,
4141
showIncrementalBuildDecisions: showIncrementalBuildDecisions,
4242
compilerMode: compilerMode,
4343
diagnosticEngine: diagnosticEngine)
44-
44+
4545
self.enableIncrementalBuild = enableIncrementalBuild
46-
46+
4747
self.buildRecordPath = Self.computeBuildRecordPath(
4848
outputFileMap: outputFileMap,
4949
compilerOutputType: compilerOutputType,
5050
diagnosticEngine: enableIncrementalBuild ? diagnosticEngine : nil)
51-
51+
5252
// If we emit module along with full compilation, emit build record
5353
// file for '-emit-module' only mode as well.
5454
self.outputBuildRecordForModuleOnlyBuild = self.buildRecordPath != nil &&
5555
moduleOutput?.isTopLevel ?? false
56-
56+
5757
let argsHash = Self.computeArgsHash(parsedOptions)
5858
self.argsHash = argsHash
5959
let lastBuildTime = Date()
6060
self.lastBuildTime = lastBuildTime
61-
61+
6262
if let buRP = buildRecordPath, enableIncrementalBuild {
6363
let outOfDateMap = InputInfoMap.populateOutOfDateMap(
6464
argsHash: argsHash,
@@ -84,27 +84,25 @@ import SwiftOptions
8484
self.outOfDateMap = nil
8585
}
8686
}
87-
88-
private static func getShowIncrementalBuildDecisions(_ parsedOptions: inout ParsedOptions)
89-
-> Bool {
87+
88+
private static func getShowIncrementalBuildDecisions(
89+
_ parsedOptions: inout ParsedOptions) -> Bool {
9090
parsedOptions.hasArgument(.driverShowIncremental)
9191
}
92-
92+
9393
private static func computeAndExplainShouldCompileIncrementally(
9494
_ parsedOptions: inout ParsedOptions,
9595
showIncrementalBuildDecisions: Bool,
9696
compilerMode: CompilerMode,
9797
diagnosticEngine: DiagnosticsEngine
98-
)
99-
-> Bool
100-
{
98+
) -> Bool {
10199
guard parsedOptions.hasArgument(.incremental) else {
102100
return false
103101
}
104102
guard compilerMode.supportsIncrementalCompilation else {
105-
diagnosticEngine.emit(
106-
.remark_incremental_compilation_disabled(
107-
because: "it is not compatible with \(compilerMode)"))
103+
diagnosticEngine.emit(
104+
.remark_incremental_compilation_disabled(
105+
because: "it is not compatible with \(compilerMode)"))
108106
return false
109107
}
110108
guard !parsedOptions.hasArgument(.embedBitcode) else {
@@ -115,7 +113,7 @@ import SwiftOptions
115113
}
116114
return true
117115
}
118-
116+
119117
private static func computeBuildRecordPath(
120118
outputFileMap: OutputFileMap?,
121119
compilerOutputType: FileType?,
@@ -128,9 +126,9 @@ import SwiftOptions
128126
return nil
129127
}
130128
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)
131-
else {
132-
diagnosticEngine.map { $0.emit(.warning_incremental_requires_build_record_entry) }
133-
return nil
129+
else {
130+
diagnosticEngine.map { $0.emit(.warning_incremental_requires_build_record_entry) }
131+
return nil
134132
}
135133
// In 'emit-module' only mode, use build-record filename suffixed with
136134
// '~moduleonly'. So that module-only mode doesn't mess up build-record
@@ -139,8 +137,8 @@ import SwiftOptions
139137
? partialBuildRecordPath.appendingToBaseName("~moduleonly")
140138
: partialBuildRecordPath
141139
}
142-
143-
static private func computeArgsHash(_ parsedOptionsArg: ParsedOptions) -> String {
140+
141+
private static func computeArgsHash(_ parsedOptionsArg: ParsedOptions) -> String {
144142
var parsedOptions = parsedOptionsArg
145143
let hashInput = parsedOptions
146144
.filter { $0.option.affectsIncrementalBuild && $0.option.kind != .input}
@@ -168,7 +166,7 @@ extension Diagnostic.Message {
168166
static var warning_incremental_requires_build_record_entry: Diagnostic.Message {
169167
.warning(
170168
"ignoring -incremental; " +
171-
"output file map has no master dependencies entry under \(FileType.swiftDeps)"
169+
"output file map has no master dependencies entry under \(FileType.swiftDeps)"
172170
)
173171
}
174172
static func remark_incremental_compilation_disabled(because why: String) -> Diagnostic.Message {

0 commit comments

Comments
 (0)