Skip to content

Commit e32c869

Browse files
authored
Merge pull request #666 from CodaFi/an-integral-part
[NFC] Refactor External Integration A Bit
2 parents 3f68e62 + cae0be5 commit e32c869

File tree

5 files changed

+105
-111
lines changed

5 files changed

+105
-111
lines changed

Sources/SwiftDriver/IncrementalCompilation/DependencyKey.swift

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,8 @@ extension DependencyKey.Designator: Comparable {
372372
extension ExternalDependency {
373373
/// When explaining incremental decisions, it helps to know why a particular external dependency
374374
/// was investigated.
375-
public enum Why: String, CustomStringConvertible {
376-
/// An `Import` of this file was added to the source code.
375+
public enum InvalidationReason: String, CustomStringConvertible {
376+
/// An `import` of this file was added to the source code.
377377
case added
378378

379379
/// The imported file has changed.
@@ -382,49 +382,6 @@ extension ExternalDependency {
382382
/// Used when testing invalidation
383383
case testing
384384

385-
/// Figure out the reason to invalidate or process a dependency.
386-
/// Even if invalidation won't be reported to the caller, a new or added
387-
/// incremental external dependency may require integration in order to
388-
/// transitively close them, (e.g. if an imported module imports a module).
389-
init?(should fed: FingerprintedExternalDependency,
390-
whichIsNewToTheGraph isNewToTheGraph: Bool,
391-
closeOverSwiftModulesIn graph: ModuleDependencyGraph) {
392-
guard graph.info.isCrossModuleIncrementalBuildEnabled
393-
else { return nil }
394-
self.init(should: fed,
395-
whichIsSignificantlyNew: isNewToTheGraph,
396-
beInvestigatedIn: graph)
397-
}
398-
399-
400-
init?(shouldUsesOf fed: FingerprintedExternalDependency,
401-
whichIsNewToTheGraph isNewToTheGraph: Bool,
402-
beInvalidatedIn graph: ModuleDependencyGraph) {
403-
if graph.phase.isCompilingAllInputsNoMatterWhat {
404-
// going to compile every input anyway, less work for callers
405-
return nil
406-
}
407-
let isSignificantlyNew = graph.phase.shouldNewExternalDependenciesTriggerInvalidation && isNewToTheGraph
408-
self.init(should: fed,
409-
whichIsSignificantlyNew: isSignificantlyNew,
410-
beInvestigatedIn: graph)
411-
}
412-
413-
/// Figure out the reason to invalidate or process a dependency
414-
private init?(should fed: FingerprintedExternalDependency,
415-
whichIsSignificantlyNew isSignificantlyNew: Bool,
416-
beInvestigatedIn graph: ModuleDependencyGraph) {
417-
if isSignificantlyNew {
418-
self = .added
419-
return
420-
}
421-
if graph.hasFileChanged(of: fed.externalDependency) {
422-
self = .changed
423-
return
424-
}
425-
return nil
426-
}
427-
428385
public var description: String { rawValue }
429386
}
430387
}

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ extension IncrementalCompilationState {
432432
func reportInvalidated<Nodes: Sequence>(
433433
_ nodes: Nodes,
434434
by externalDependency: ExternalDependency,
435-
_ why: ExternalDependency.Why
435+
_ why: ExternalDependency.InvalidationReason
436436
)
437437
where Nodes.Element == ModuleDependencyGraph.Node
438438
{

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraph.swift

Lines changed: 86 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,7 @@ extension ModuleDependencyGraph {
150150
func collectNodesInvalidatedByChangedOrAddedExternals() -> DirectlyInvalidatedNodeSet {
151151
fingerprintedExternalDependencies.reduce(into: DirectlyInvalidatedNodeSet()) {
152152
invalidatedNodes, fed in
153-
invalidatedNodes.formUnion (
154-
self.collectNodesInvalidatedByProcessing(fingerprintedExternalDependency: fed,
155-
isPresentInTheGraph: true))
153+
invalidatedNodes.formUnion(self.integrateExternal(.known(fed)))
156154
}
157155
}
158156
}
@@ -286,9 +284,9 @@ extension ModuleDependencyGraph {
286284
/// Given an external dependency & its fingerprint, find any nodes directly using that dependency.
287285
/// As an optimization, only return the nodes that have not been already traced, because the traced nodes
288286
/// will have already been used to schedule jobs to run.
289-
/*@_spi(Testing)*/ public func collectUntracedNodesUsing(
290-
_ why: ExternalDependency.Why,
291-
_ fingerprintedExternalDependency: FingerprintedExternalDependency
287+
/*@_spi(Testing)*/ public func collectUntracedNodes(
288+
from fingerprintedExternalDependency: FingerprintedExternalDependency,
289+
_ why: ExternalDependency.InvalidationReason
292290
) -> DirectlyInvalidatedNodeSet {
293291
// These nodes will depend on the *interface* of the external Decl.
294292
let key = DependencyKey(
@@ -353,45 +351,91 @@ extension ModuleDependencyGraph {
353351
}
354352
}
355353

356-
// MARK: - processing external dependencies
354+
// MARK: - Integrating External Dependencies
355+
357356
extension ModuleDependencyGraph {
357+
/// The kinds of external dependencies available to integrate.
358+
enum ExternalIntegrand {
359+
/// A `known` integrand is known to be present in the graph and requires
360+
/// only a mod-time check to determine if it is up to date.
361+
case known(FingerprintedExternalDependency)
362+
/// An `unknown` integrand is not, up to this point, known to the dependency
363+
/// graph. This models the addition of an import that is discovered during
364+
/// the incremental build.
365+
case unknown(FingerprintedExternalDependency)
366+
367+
var externalDependency: FingerprintedExternalDependency {
368+
switch self {
369+
case .known(let fed): return fed
370+
case .unknown(let fed): return fed
371+
}
372+
}
358373

359-
/// Process a possibly-fingerprinted external dependency by reading and integrating, if applicable.
360-
/// Return the nodes thus invalidated.
361-
/// But always integrate, in order to detect future changes.
362-
/// This function does not to the transitive closure; that is left to the callers
363-
func collectNodesInvalidatedByProcessing(
364-
fingerprintedExternalDependency fed: FingerprintedExternalDependency,
365-
isPresentInTheGraph: Bool?)
366-
-> DirectlyInvalidatedNodeSet {
367-
368-
/// Compute this up front as an optimization.
369-
let isNewToTheGraph = isPresentInTheGraph != true && fingerprintedExternalDependencies.insert(fed).inserted
370-
371-
let whyIntegrateForClosure = ExternalDependency.Why(
372-
should: fed,
373-
whichIsNewToTheGraph: isNewToTheGraph,
374-
closeOverSwiftModulesIn: self)
375-
376-
let invalidatedNodesFromIncrementalExternal = whyIntegrateForClosure.flatMap { why in
377-
collectNodesInvalidatedByAttemptingToProcess(why, fed)
374+
var isKnown: Bool {
375+
switch self {
376+
case .known(_): return true
377+
case .unknown(_): return false
378+
}
378379
}
380+
}
379381

380-
guard let whyInvalidate = ExternalDependency.Why(
381-
shouldUsesOf: fed,
382-
whichIsNewToTheGraph: isNewToTheGraph,
383-
beInvalidatedIn: self)
384-
else {
382+
/// Collects the nodes invalidated by a change to the given external
383+
/// dependency after integrating it into the dependency graph.
384+
///
385+
/// This function does not to the transitive closure; that is left to the
386+
/// callers.
387+
///
388+
/// - Parameters:
389+
/// - integrand: The external dependency to integrate.
390+
/// - isKnown: If `true`, the caller is aware of this node and
391+
/// integration should assume it is a known external.
392+
/// If `false`, and the external has not been
393+
/// integrated before, it is treated as a freshly-
394+
/// added external dependency.
395+
/// - Returns: The set of module dependency graph nodes invalidated by integration.
396+
func integrateExternal(
397+
_ integrand: ExternalIntegrand
398+
) -> DirectlyInvalidatedNodeSet {
399+
guard let whyInvalidate = self.invalidationReason(for: integrand) else {
385400
return DirectlyInvalidatedNodeSet()
386401
}
387402

388-
// If there was an error integrating the external dependency, or if it was not an incremental one,
389-
// return anything that uses that dependency.
390-
return invalidatedNodesFromIncrementalExternal ?? collectUntracedNodesUsing(whyInvalidate, fed)
403+
if self.info.isCrossModuleIncrementalBuildEnabled {
404+
if let ii = integrateIncrementalImport(of: integrand.externalDependency, whyInvalidate) {
405+
return ii
406+
}
407+
}
408+
409+
// If we're compiling everything anyways, there's no need to trace.
410+
// FIXME: Seems like
411+
// 1) We could set this flag a lot earlier in some cases
412+
// 2) It should apply to incremental imports as well.
413+
guard !self.phase.isCompilingAllInputsNoMatterWhat else {
414+
return DirectlyInvalidatedNodeSet()
415+
}
416+
return collectUntracedNodes(from: integrand.externalDependency, whyInvalidate)
391417
}
392418

393-
func hasFileChanged(of externalDependency: ExternalDependency
394-
) -> Bool {
419+
/// Figure out the reason to invalidate or process a dependency.
420+
///
421+
/// Even if invalidation won't be reported to the caller, a new or added
422+
/// incremental external dependencies may require integration in order to
423+
/// transitively close them, (e.g. if an imported module imports a module).
424+
private func invalidationReason(
425+
for fed: ExternalIntegrand
426+
) -> ExternalDependency.InvalidationReason? {
427+
let isNewToTheGraph = !fed.isKnown && fingerprintedExternalDependencies.insert(fed.externalDependency).inserted
428+
if self.phase.shouldNewExternalDependenciesTriggerInvalidation && isNewToTheGraph {
429+
return .added
430+
}
431+
432+
if self.hasFileChanged(fed.externalDependency.externalDependency) {
433+
return .changed
434+
}
435+
return nil
436+
}
437+
438+
private func hasFileChanged(_ externalDependency: ExternalDependency) -> Bool {
395439
if let hasChanged = externalDependencyModTimeCache[externalDependency] {
396440
return hasChanged
397441
}
@@ -406,12 +450,13 @@ extension ModuleDependencyGraph {
406450

407451
/// Try to read and integrate an external dependency.
408452
/// Return nil if it's not incremental, or if an error occurs.
409-
private func collectNodesInvalidatedByAttemptingToProcess(
410-
_ why: ExternalDependency.Why,
411-
_ fed: FingerprintedExternalDependency
453+
private func integrateIncrementalImport(
454+
of fed: FingerprintedExternalDependency,
455+
_ why: ExternalDependency.InvalidationReason
412456
) -> DirectlyInvalidatedNodeSet? {
413-
guard let source = fed.incrementalDependencySource,
414-
let unserializedDepGraph = source.read(in: info.fileSystem, reporter: info.reporter)
457+
guard
458+
let source = fed.incrementalDependencySource,
459+
let unserializedDepGraph = source.read(in: info.fileSystem, reporter: info.reporter)
415460
else {
416461
return nil
417462
}

Sources/SwiftDriver/IncrementalCompilation/ModuleDependencyGraphParts/Integrator.swift

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,9 @@ extension ModuleDependencyGraph.Integrator {
160160
_ integrand: SourceFileDependencyGraph.Node
161161
) -> Graph.Node {
162162
precondition(integrand.isProvides, "Dependencies are arcs in the module graph")
163-
let newNode = Graph.Node(
164-
key: integrand.key,
165-
fingerprint: integrand.fingerprint,
166-
dependencySource: sourceGraph.dependencySource)
163+
let newNode = Graph.Node(key: integrand.key,
164+
fingerprint: integrand.fingerprint,
165+
dependencySource: sourceGraph.dependencySource)
167166
let oldNode = destination.nodeFinder.insert(newNode)
168167
assert(oldNode == nil, "Should be new!")
169168
addNew(newNode)
@@ -178,39 +177,32 @@ extension ModuleDependencyGraph.Integrator {
178177
_ moduleUseNode: Graph.Node
179178
) {
180179
sourceGraph.forEachDefDependedUpon(by: sourceFileUseNode) { def in
181-
let isNewUse = destination.nodeFinder.record(def: def.key,
182-
use: moduleUseNode)
183-
if let externalDependency = def.key.designator.externalDependency,
184-
isNewUse {
185-
collectNodesInvalidatedByUsing(
186-
externalDependency: FingerprintedExternalDependency(externalDependency, def.fingerprint),
187-
moduleFileGraphUseNode: moduleUseNode)
180+
guard
181+
destination.nodeFinder.record(def: def.key, use: moduleUseNode),
182+
let externalDependency = def.key.designator.externalDependency
183+
else {
184+
return
188185
}
186+
187+
recordInvalidations(
188+
from: FingerprintedExternalDependency(externalDependency, def.fingerprint))
189189
}
190190
}
191191

192192
// A `moduleGraphUseNode` is used by an externalDependency key being integrated.
193193
// Remember the dependency for later processing in externalDependencies, and
194194
// also return it in results.
195195
// Also the use node has changed.
196-
private mutating func collectNodesInvalidatedByUsing(
197-
externalDependency fingerprintedExternalDependency: FingerprintedExternalDependency,
198-
moduleFileGraphUseNode moduleUseNode: Graph.Node
196+
private mutating func recordInvalidations(
197+
from externalDependency: FingerprintedExternalDependency
199198
) {
200-
let invalidated = destination.collectNodesInvalidatedByProcessing(
201-
fingerprintedExternalDependency: fingerprintedExternalDependency,
202-
isPresentInTheGraph: nil)
203-
collectUsesOfSomeExternal(invalidated)
199+
let invalidated = destination.integrateExternal(.unknown(externalDependency))
200+
invalidatedNodes.formUnion(invalidated)
204201
}
205202
}
206203

207204
// MARK: - Results {
208205
extension ModuleDependencyGraph.Integrator {
209-
/*@_spi(Testing)*/
210-
mutating func collectUsesOfSomeExternal(_ invalidated: DirectlyInvalidatedNodeSet)
211-
{
212-
invalidatedNodes.formUnion(invalidated)
213-
}
214206
mutating func addDisappeared(_ node: Graph.Node) {
215207
assert(isUpdating)
216208
invalidatedNodes.insert(node)

Tests/SwiftDriverTests/ModuleDependencyGraphTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ extension ModuleDependencyGraph {
10141014
on fingerprintedExternalDependency: FingerprintedExternalDependency
10151015
) -> [DependencySource] {
10161016
var foundSources = [DependencySource]()
1017-
for dependent in collectUntracedNodesUsing(.testing, fingerprintedExternalDependency) {
1017+
for dependent in collectUntracedNodes(from: fingerprintedExternalDependency, .testing) {
10181018
let dependencySource = dependent.dependencySource!
10191019
foundSources.append(dependencySource)
10201020
// findSwiftDepsToRecompileWhenWholeSwiftDepChanges is reflexive

0 commit comments

Comments
 (0)