Skip to content

Commit 0c71e8d

Browse files
committed
[Incremental Builds] Separately check whether we can skip 'emit-module' on an incremental module-only build
Resolves rdar://151626629
1 parent 974a26e commit 0c71e8d

File tree

2 files changed

+118
-7
lines changed

2 files changed

+118
-7
lines changed

Sources/SwiftDriver/IncrementalCompilation/FirstWaveComputer.swift

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,24 @@ extension IncrementalCompilationState.FirstWaveComputer {
134134
jobCreatingPch: jobCreatingPch)
135135

136136
// In the case where there are no compilation jobs to run on this build (no source-files were changed),
137-
// we can skip running `beforeCompiles` jobs if we also ensure that none of the `afterCompiles` jobs
138-
// have any dependencies on them.
139-
let skipAllJobs = batchedCompilationJobs.isEmpty ? !nonVerifyAfterCompileJobsDependOnBeforeCompileJobs() : false
140-
let beforeCompileJobs = skipAllJobs ? [] : jobsInPhases.beforeCompiles
141-
var skippedNonCompileJobs = skipAllJobs ? jobsInPhases.beforeCompiles : []
137+
// and the emit-module task does not need to be re-run, we can skip running `beforeCompiles` jobs if we
138+
// also ensure that none of the `afterCompiles` jobs have any dependencies on them.
139+
let skippingAllCompileJobs = batchedCompilationJobs.isEmpty
140+
let skipEmitModuleJobs = skippingAllCompileJobs ? try computeCanSkipEmitModuleTasks(moduleDependencyGraph, buildRecord) : false
141+
let skipAllJobs = skippingAllCompileJobs && skipEmitModuleJobs ? !nonVerifyAfterCompileJobsDependOnBeforeCompileJobs() : false
142+
143+
let beforeCompileJobs: [Job]
144+
var skippedNonCompileJobs: [Job] = []
145+
if skipAllJobs {
146+
beforeCompileJobs = []
147+
skippedNonCompileJobs = jobsInPhases.beforeCompiles
148+
} else if skipEmitModuleJobs {
149+
let emitModuleJobs = jobsInPhases.beforeCompiles.filter { $0.kind == .emitModule }
150+
beforeCompileJobs = jobsInPhases.beforeCompiles.filter { $0.kind != .emitModule }
151+
skippedNonCompileJobs.append(contentsOf: emitModuleJobs)
152+
} else {
153+
beforeCompileJobs = jobsInPhases.beforeCompiles
154+
}
142155

143156
// Schedule emitModule job together with verify module interface job.
144157
let afterCompileJobs = jobsInPhases.afterCompiles.compactMap { job -> Job? in
@@ -170,6 +183,38 @@ extension IncrementalCompilationState.FirstWaveComputer {
170183
}
171184
}
172185

186+
/// Figure out if the emit-module tasks are *not* mandatory.
187+
/// This functionality only runs if there are not actual
188+
/// compilation tasks to be run in this build, for example on an
189+
/// emit-module-only build.
190+
private func computeCanSkipEmitModuleTasks(
191+
_ moduleDependencyGraph: ModuleDependencyGraph,
192+
_ buildRecord: BuildRecord
193+
) throws -> Bool {
194+
guard let emitModuleJob = jobsInPhases.beforeCompiles.first(where: { $0.kind == .emitModule }) else {
195+
return true
196+
}
197+
198+
var oldestOutputModTime: TimePoint = .distantFuture
199+
for output in emitModuleJob.outputs {
200+
if try !fileSystem.exists(output.file) {
201+
return false
202+
}
203+
let modTime = try fileSystem.lastModificationTime(for: output.file)
204+
if modTime < oldestOutputModTime {
205+
oldestOutputModTime = modTime
206+
}
207+
}
208+
209+
for swiftInput in emitModuleJob.inputs.swiftSourceFiles {
210+
if try fileSystem.lastModificationTime(for: swiftInput.typedFile.file) > oldestOutputModTime {
211+
return false
212+
}
213+
}
214+
215+
return true
216+
}
217+
173218
/// Figure out which compilation inputs are *not* mandatory at the start
174219
private func computeInitiallySkippedCompilationInputs(
175220
inputsInvalidatedByExternals: TransitivelyInvalidatedSwiftSourceFileSet,
@@ -178,7 +223,7 @@ extension IncrementalCompilationState.FirstWaveComputer {
178223
) -> Set<TypedVirtualPath> {
179224
let allCompileJobs = jobsInPhases.compileJobs
180225
// Input == source file
181-
let changedInputs = computeChangedInputs(moduleDependencyGraph, buildRecord)
226+
let changedInputs = computeChangedInputs(buildRecord)
182227

183228
if let reporter = reporter {
184229
for input in inputsInvalidatedByExternals {
@@ -274,7 +319,6 @@ extension IncrementalCompilationState.FirstWaveComputer {
274319

275320
// Find the inputs that have changed since last compilation, or were marked as needed a build
276321
private func computeChangedInputs(
277-
_ moduleDependencyGraph: ModuleDependencyGraph,
278322
_ outOfDateBuildRecord: BuildRecord
279323
) -> [ChangedInput] {
280324
jobsInPhases.compileJobs.compactMap { job in

Tests/SwiftDriverTests/IncrementalCompilationTests.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,55 @@ extension IncrementalCompilationTests {
645645
linking
646646
}
647647
}
648+
649+
func testExplicitIncrementalEmitModuleOnly() throws {
650+
guard let sdkArgumentsForTesting = try Driver.sdkArgumentsForTesting()
651+
else {
652+
throw XCTSkip("Cannot perform this test on this host")
653+
}
654+
655+
let args = [
656+
"swiftc",
657+
"-module-name", module,
658+
"-emit-module", "-emit-module-path",
659+
derivedDataPath.appending(component: module + ".swiftmodule").pathString,
660+
"-incremental",
661+
"-driver-show-incremental",
662+
"-driver-show-job-lifecycle",
663+
"-save-temps",
664+
"-output-file-map", OFM.pathString,
665+
"-no-color-diagnostics"
666+
] + inputPathsAndContents.map {$0.0.pathString}.sorted() + explicitBuildArgs + sdkArgumentsForTesting
667+
668+
// Initial build
669+
_ = try doABuildWithoutExpectations(arguments: args)
670+
671+
// Subsequent build, ensure module does not get re-emitted since inputs have not changed
672+
_ = try doABuild(
673+
whenAutolinking: autolinkLifecycleExpectedDiags,
674+
arguments: args
675+
) {
676+
readGraph
677+
explicitIncrementalScanReuseCache(serializedDepScanCachePath.pathString)
678+
explicitIncrementalScanCacheSerialized(serializedDepScanCachePath.pathString)
679+
queuingInitial("main", "other")
680+
}
681+
682+
touch("main")
683+
touch("other")
684+
// Subsequent build, ensure module re-emitted since inputs changed
685+
_ = try doABuild(
686+
whenAutolinking: autolinkLifecycleExpectedDiags,
687+
arguments: args
688+
) {
689+
readGraph
690+
explicitIncrementalScanReuseCache(serializedDepScanCachePath.pathString)
691+
explicitIncrementalScanCacheSerialized(serializedDepScanCachePath.pathString)
692+
queuingInitial("main", "other")
693+
emittingModule(module)
694+
schedulingPostCompileJobs
695+
}
696+
}
648697
}
649698

650699
extension IncrementalCompilationTests {
@@ -1770,6 +1819,14 @@ extension IncrementalCompilationTests {
17701819
}
17711820
}
17721821

1822+
private func doABuild(
1823+
whenAutolinking autolinkExpectedDiags: [Diagnostic.Message],
1824+
arguments: [String],
1825+
@DiagsBuilder expecting expectedDiags: () -> [Diagnostic.Message]
1826+
) throws -> Driver {
1827+
try doABuild(whenAutolinking: autolinkExpectedDiags, expecting: expectedDiags(), arguments: arguments)
1828+
}
1829+
17731830
private func doABuildWithoutExpectations(arguments: [String]) throws -> Driver {
17741831
// If not checking, print out the diagnostics
17751832
let diagnosticEngine = DiagnosticsEngine(handlers: [
@@ -1859,6 +1916,16 @@ extension DiagVerifiable {
18591916
@DiagsBuilder func explicitDependencyModuleOlderThanInput(_ dependencyModuleName: String) -> [Diagnostic.Message] {
18601917
"Dependency module \(dependencyModuleName) is older than input file"
18611918
}
1919+
@DiagsBuilder func startEmitModule(_ moduleName: String) -> [Diagnostic.Message] {
1920+
"Starting Emitting module for \(moduleName)"
1921+
}
1922+
@DiagsBuilder func finishEmitModule(_ moduleName: String) -> [Diagnostic.Message] {
1923+
"Finished Emitting module for \(moduleName)"
1924+
}
1925+
@DiagsBuilder func emittingModule(_ moduleName: String) -> [Diagnostic.Message] {
1926+
startEmitModule(moduleName)
1927+
finishEmitModule(moduleName)
1928+
}
18621929
@DiagsBuilder func startCompilingExplicitClangDependency(_ dependencyModuleName: String) -> [Diagnostic.Message] {
18631930
"Starting Compiling Clang module \(dependencyModuleName)"
18641931
}

0 commit comments

Comments
 (0)