Skip to content

Commit 2f5e894

Browse files
author
David Ungar
authored
Merge pull request #50 from davidungar/incremental
Start of incremental logic
2 parents 8d8db42 + 484dfff commit 2f5e894

File tree

7 files changed

+227
-9
lines changed

7 files changed

+227
-9
lines changed

Sources/SwiftDriver/Driver/CompilerMode.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,19 @@ extension CompilerMode {
2525
}
2626
}
2727
}
28+
29+
extension CompilerMode: CustomStringConvertible {
30+
public var description: String {
31+
switch self {
32+
33+
case .standardCompile:
34+
return "standard compilation"
35+
case .singleCompile:
36+
return "whole module optimization"
37+
case .repl:
38+
return "read-eval-print-loop compilation"
39+
case .immediate:
40+
return "immediate compilation"
41+
}
42+
}
43+
}

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ public enum ModuleOutput: Equatable {
1818
return path
1919
}
2020
}
21+
22+
public var isTopLevel: Bool {
23+
switch self {
24+
case .topLevel: return true
25+
default: return false
26+
}
27+
}
2128
}
2229

2330
/// The Swift driver.
@@ -58,7 +65,7 @@ public struct Driver {
5865
public let inputFiles: [TypedVirtualPath]
5966

6067
/// The mapping from input files to output files for each kind.
61-
internal let outputFileMap: OutputFileMap
68+
internal let outputFileMap: OutputFileMap?
6269

6370
/// The mode in which the compiler will execute.
6471
public let compilerMode: CompilerMode
@@ -82,6 +89,9 @@ public struct Driver {
8289
/// \c nil if no module should be emitted.
8390
public let moduleOutput: ModuleOutput?
8491

92+
/// Code & data for incremental compilation
93+
public let incrementalCompilation: IncrementalCompilation
94+
8595
/// The name of the Swift module being built.
8696
public let moduleName: String
8797

@@ -187,14 +197,16 @@ public struct Driver {
187197
}
188198

189199
// Classify and collect all of the input files.
190-
self.inputFiles = try Self.collectInputFiles(&self.parsedOptions)
200+
let inputFiles = try Self.collectInputFiles(&self.parsedOptions)
201+
self.inputFiles = inputFiles
191202

192203
// Initialize an empty output file map, which will be populated when we start creating jobs.
193204
if let outputFileMapArg = parsedOptions.getLastArgument(.output_file_map)?.asSingle {
194205
let path = try AbsolutePath(validating: outputFileMapArg)
195206
self.outputFileMap = try .load(file: path, diagnosticEngine: diagnosticEngine)
196-
} else {
197-
self.outputFileMap = OutputFileMap()
207+
}
208+
else {
209+
self.outputFileMap = nil
198210
}
199211

200212
// Determine the compilation mode.
@@ -210,9 +222,21 @@ public struct Driver {
210222
(self.debugInfoLevel, self.debugInfoFormat) = Self.computeDebugInfo(&parsedOptions, diagnosticsEngine: diagnosticEngine)
211223

212224
// Determine the module we're building and whether/how the module file itself will be emitted.
213-
(self.moduleOutput, self.moduleName) = try Self.computeModuleInfo(
225+
let moduleOutput: ModuleOutput?
226+
(moduleOutput, self.moduleName) = try Self.computeModuleInfo(
214227
&parsedOptions, compilerOutputType: compilerOutputType, compilerMode: compilerMode, linkerOutputType: linkerOutputType,
215228
debugInfoLevel: debugInfoLevel, diagnosticsEngine: diagnosticEngine)
229+
self.moduleOutput = moduleOutput
230+
231+
// Determine the state for incremental compilation
232+
self.incrementalCompilation = IncrementalCompilation(
233+
&parsedOptions,
234+
compilerMode: compilerMode,
235+
outputFileMap: self.outputFileMap,
236+
compilerOutputType: self.compilerOutputType,
237+
moduleOutput: moduleOutput,
238+
inputFiles: inputFiles,
239+
diagnosticEngine: diagnosticEngine)
216240

217241
self.sdkPath = Self.computeSDKPath(&parsedOptions, compilerMode: compilerMode, toolchain: toolchain, diagnosticsEngine: diagnosticEngine)
218242

Sources/SwiftDriver/Driver/OutputFileMap.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public struct OutputFileMap {
1212
/// file type.
1313
public func getOutput(inputFile: VirtualPath, outputType: FileType) -> VirtualPath {
1414
// If we already have an output file, retrieve it.
15-
if let output = entries[inputFile]?[outputType] {
15+
if let output = existingOutput(inputFile: inputFile, outputType: outputType) {
1616
return output
1717
}
1818

@@ -35,6 +35,14 @@ public struct OutputFileMap {
3535
return .temporary(baseName.appendingFileTypeExtension(outputType))
3636
}
3737

38+
public func existingOutput(inputFile: VirtualPath, outputType: FileType) -> VirtualPath? {
39+
entries[inputFile]?[outputType]
40+
}
41+
42+
public func existingOutputForSingleInput(outputType: FileType) -> VirtualPath? {
43+
try! existingOutput(inputFile: VirtualPath(path: ""), outputType: outputType)
44+
}
45+
3846
/// Load the output file map at the given path.
3947
public static func load(
4048
file: AbsolutePath,
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import TSCBasic
2+
import TSCUtility
3+
import Foundation
4+
5+
// FIXME: rename to something like IncrementalCompilationInitialState
6+
public struct IncrementalCompilation {
7+
public let showIncrementalBuildDecisions: Bool
8+
public let shouldCompileIncrementally: Bool
9+
public let buildRecordPath: VirtualPath?
10+
public let outputBuildRecordForModuleOnlyBuild: Bool
11+
public let argsHash: String
12+
public let lastBuildTime: Date
13+
public let outOfDateMap: InputInfoMap?
14+
public let rebuildEverything: Bool
15+
16+
public init(_ parsedOptions: inout ParsedOptions,
17+
compilerMode: CompilerMode,
18+
outputFileMap: OutputFileMap?,
19+
compilerOutputType: FileType?,
20+
moduleOutput: ModuleOutput?,
21+
inputFiles: [TypedVirtualPath],
22+
diagnosticEngine: DiagnosticsEngine
23+
) {
24+
let showIncrementalBuildDecisions = Self.getShowIncrementalBuildDecisions(&parsedOptions)
25+
self.showIncrementalBuildDecisions = showIncrementalBuildDecisions
26+
27+
let shouldCompileIncrementally = Self.computeAndExplainShouldCompileIncrementally(
28+
&parsedOptions,
29+
showIncrementalBuildDecisions: showIncrementalBuildDecisions,
30+
compilerMode: compilerMode)
31+
32+
self.shouldCompileIncrementally = shouldCompileIncrementally
33+
34+
self.buildRecordPath = Self.computeBuildRecordPath(
35+
outputFileMap: outputFileMap,
36+
compilerOutputType: compilerOutputType,
37+
diagnosticEngine: shouldCompileIncrementally ? diagnosticEngine : nil)
38+
39+
// If we emit module along with full compilation, emit build record
40+
// file for '-emit-module' only mode as well.
41+
self.outputBuildRecordForModuleOnlyBuild = self.buildRecordPath != nil &&
42+
moduleOutput?.isTopLevel ?? false
43+
44+
let argsHash = Self.computeArgsHash(parsedOptions)
45+
self.argsHash = argsHash
46+
let lastBuildTime = Date.init()
47+
self.lastBuildTime = lastBuildTime
48+
49+
if let buRP = buildRecordPath, shouldCompileIncrementally {
50+
self.outOfDateMap = InputInfoMap.populateOutOfDateMap(
51+
argsHash: argsHash,
52+
lastBuildTime: lastBuildTime,
53+
inputFiles: inputFiles,
54+
buildRecordPath: buRP,
55+
showIncrementalBuildDecisions: showIncrementalBuildDecisions)
56+
}
57+
else {
58+
self.outOfDateMap = nil
59+
}
60+
// FIXME: Distinguish errors from "file removed", which is benign.
61+
self.rebuildEverything = outOfDateMap == nil
62+
}
63+
64+
private static func getShowIncrementalBuildDecisions(_ parsedOptions: inout ParsedOptions)
65+
-> Bool {
66+
parsedOptions.hasArgument(.driver_show_incremental)
67+
}
68+
69+
private static func computeAndExplainShouldCompileIncrementally(
70+
_ parsedOptions: inout ParsedOptions,
71+
showIncrementalBuildDecisions: Bool,
72+
compilerMode: CompilerMode
73+
)
74+
-> Bool
75+
{
76+
func explain(disabledBecause why: String) {
77+
stdoutStream <<< "Incremental compilation has been disabled, because it \(why).\n"
78+
stdoutStream.flush()
79+
}
80+
guard parsedOptions.hasArgument(.incremental) else {
81+
return false
82+
}
83+
guard compilerMode.supportsIncrementalCompilation else {
84+
explain(disabledBecause: "is not compatible with \(compilerMode)")
85+
return false
86+
}
87+
guard !parsedOptions.hasArgument(.embed_bitcode) else {
88+
explain(disabledBecause: "is not currently compatible with embedding LLVM IR bitcode")
89+
return false
90+
}
91+
return true
92+
}
93+
94+
private static func computeBuildRecordPath(
95+
outputFileMap: OutputFileMap?,
96+
compilerOutputType: FileType?,
97+
diagnosticEngine: DiagnosticsEngine?
98+
) -> VirtualPath? {
99+
// FIXME: This should work without an output file map. We should have
100+
// another way to specify a build record and where to put intermediates.
101+
guard let ofm = outputFileMap else {
102+
diagnosticEngine.map { $0.emit(.warning_incremental_requires_output_file_map) }
103+
return nil
104+
}
105+
guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps)
106+
else {
107+
diagnosticEngine.map { $0.emit(.warning_incremental_requires_build_record_entry) }
108+
return nil
109+
}
110+
// In 'emit-module' only mode, use build-record filename suffixed with
111+
// '~moduleonly'. So that module-only mode doesn't mess up build-record
112+
// file for full compilation.
113+
return try! compilerOutputType == .swiftModule
114+
? VirtualPath(path: partialBuildRecordPath.name + "~moduleonly")
115+
: partialBuildRecordPath
116+
}
117+
118+
static private func computeArgsHash(_ parsedOptionsArg: ParsedOptions) -> String {
119+
var parsedOptions = parsedOptionsArg
120+
let hashInput = parsedOptions
121+
.filter { $0.option.affectsIncrementalBuild && $0.option.kind != .input}
122+
.map {$0.option.spelling}
123+
.sorted()
124+
.joined()
125+
return SHA256(hashInput).digestString()
126+
}
127+
}
128+
129+
130+
fileprivate extension CompilerMode {
131+
var supportsIncrementalCompilation: Bool {
132+
switch self {
133+
case .standardCompile, .immediate, .repl: return true
134+
case .singleCompile: return false
135+
}
136+
}
137+
}
138+
139+
public extension Diagnostic.Message {
140+
static var warning_incremental_requires_output_file_map: Diagnostic.Message {
141+
.warning("ignoring -incremental (currently requires an output file map)")
142+
}
143+
static var warning_incremental_requires_build_record_entry: Diagnostic.Message {
144+
.warning(
145+
"ignoring -incremental; " +
146+
"output file map has no master dependencies entry under \(FileType.swiftDeps)"
147+
)
148+
}
149+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import TSCBasic
2+
import TSCUtility
3+
import Foundation
4+
5+
/// Holds the info about inputs needed to plan incremenal compilation
6+
public struct InputInfoMap {
7+
public static func populateOutOfDateMap(
8+
argsHash: String,
9+
lastBuildTime: Date,
10+
inputFiles: [TypedVirtualPath],
11+
buildRecordPath: VirtualPath,
12+
showIncrementalBuildDecisions: Bool
13+
) -> Self? {
14+
stderrStream <<< "WARNING: incremental compilation not implemented yet\n"
15+
stderrStream.flush()
16+
return nil
17+
}
18+
}

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ extension Driver {
4545

4646
// If there is a primary output, add it.
4747
if isPrimary, let compilerOutputType = compilerOutputType {
48-
primaryOutputs.append(TypedVirtualPath(file: outputFileMap.getOutput(inputFile: input, outputType: compilerOutputType),
49-
type: compilerOutputType))
48+
primaryOutputs.append(
49+
TypedVirtualPath(
50+
file: (outputFileMap ?? OutputFileMap()).getOutput(inputFile: input, outputType: compilerOutputType),
51+
type: compilerOutputType))
5052
}
5153
}
5254

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ extension Driver {
158158

159159
commandLine.appendFlag(flag)
160160

161-
let path = outputFileMap.getOutput(inputFile: input, outputType: outputType)
161+
let path = (outputFileMap ?? OutputFileMap())
162+
.getOutput(inputFile: input, outputType: outputType)
162163
commandLine.append(.path(path))
163164
outputs.append(TypedVirtualPath(file: path, type: outputType))
164165
}

0 commit comments

Comments
 (0)