File: Integrator.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (307 lines) | stat: -rw-r--r-- 12,106 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//===------------------ Integrator.swift ----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

extension ModuleDependencyGraph {

  // MARK: Integrator - state & creation

  /// Integrates a \c SourceFileDependencyGraph into a \c ModuleDependencyGraph. See ``Integrator/integrate(from:dependencySource:into:)``
  public struct Integrator {

    // Shorthands
    /*@_spi(Testing)*/ public typealias Graph = ModuleDependencyGraph
    typealias DefinitionLocation = Graph.DefinitionLocation

    public private(set) var invalidatedNodes = DirectlyInvalidatedNodeSet()

    /// If integrating from an .swift file in the build, refers to the .swift file
    /// Otherwise, refers to a .swiftmodule file
    let dependencySource: DependencySource

    /// the graph to be integrated
    let sourceGraph: SourceFileDependencyGraph

    /// the graph to be integrated into
    let destination: ModuleDependencyGraph

    /// Starts with all nodes in the `DependencySource` to be integrated.
    /// Then as nodes are found in this source, they are removed from here.
    /// After integration is complete, this dictionary contains the nodes that have disappeared from this `DependencySource`.
    var disappearedNodes = [DependencyKey: Graph.Node]()

    init(sourceGraph: SourceFileDependencyGraph,
         dependencySource: DependencySource,
         destination: ModuleDependencyGraph)
    {
      self.sourceGraph = sourceGraph
      self.dependencySource = dependencySource
      self.destination = destination
      self.disappearedNodes = destination.nodeFinder
      .findNodes(for: .known(dependencySource))
        ?? [:]
    }

    var reporter: IncrementalCompilationState.Reporter? {
      destination.info.reporter
    }

    var sourceType: FileType {
      dependencySource.typedFile.type
    }

    var isUpdating: Bool {
      destination.phase.isUpdating
    }
  }
}
// MARK: - integrate a graph
extension ModuleDependencyGraph.Integrator {
  /// Integrate a SourceFileDepGraph into the receiver.
  ///
  /// Integration happens when the driver needs to read SourceFileDepGraph.
  /// Common to scheduling both waves.
  /// - Parameters:
  ///   - g: the graph to be integrated from
  ///   - dependencySource: holds the .swift or .swifmodule file containing the dependencies to be integrated that were read into `g`
  ///   - destination: the graph to be integrated into
  /// - Returns: all nodes directly affected by the integration, plus nodes transitively affected by integrated external dependencies.
  /// Because external dependencies may have transitive effects not captured by the frontend, changes from them are always transitively closed.
  public static func integrate(
    from g: SourceFileDependencyGraph,
    dependencySource: DependencySource,
    into destination: Graph
  ) -> DirectlyInvalidatedNodeSet {
    precondition(g.internedStringTable === destination.internedStringTable)
    var integrator = Self(sourceGraph: g,
                          dependencySource: dependencySource,
                          destination: destination)
    integrator.integrate()

    if destination.info.verifyDependencyGraphAfterEveryImport {
      integrator.verifyAfterImporting()
    }
    destination.dotFileWriter?.write(g, for: dependencySource.typedFile,
                                     internedStringTable: destination.internedStringTable)
    destination.dotFileWriter?.write(destination)
    return integrator.invalidatedNodes
  }

  private mutating func integrate() {
    integrateEachSourceNode()
    handleDisappearedNodes()
    // Ensure transitive closure will get started.
    destination.ensureGraphWillRetrace(invalidatedNodes)
  }
  private mutating func integrateEachSourceNode() {
    sourceGraph.forEachNode { integrate(oneNode: $0) }
  }
  private mutating func handleDisappearedNodes() {
    for (_, node) in disappearedNodes {
      addDisappeared(node)
      destination.nodeFinder.remove(node)
    }
  }
}
// MARK: - integrate one node
extension ModuleDependencyGraph.Integrator {
  private mutating func integrate(
    oneNode integrand: SourceFileDependencyGraph.Node)
  {
    guard integrand.definitionVsUse == .definition else {
      // Uses are captured by recordWhatIsDependedUpon below.
      return
    }

    let integratedNode = destination.nodeFinder.findNodes(for: integrand.key)
      .flatMap {
        integrateWithNodeDefinedHere(    integrand, $0) ??
        integrateWithNodeDefinedNowhere( integrand, $0)
      }
    ?? integrateWithNewNode(integrand)

    recordDefsForThisUse(integrand, integratedNode)
  }

  /// If a node to be integrated corresponds to one already in the destination graph for the same source, integrate it.
  ///
  /// - Parameters:
  ///   - integrand: the node to be integrated
  ///   - nodesMatchingKey: all nodes in the destination graph with matching `DependencyKey`
  ///  - Returns: nil if a corresponding node did *not* already exist for the same source,
  ///  Otherwise, the integrated corresponding node.
  ///  If the integrated node was changed by the integration, it is added to ``invalidatedNodes``.
  private mutating func integrateWithNodeDefinedHere(
    _ integrand: SourceFileDependencyGraph.Node,
    _ nodesMatchingKey: [DefinitionLocation: Graph.Node]
  ) -> Graph.Node? {
    guard let matchHere = nodesMatchingKey[.known(dependencySource)] else {
      return nil
    }
    assert(matchHere.definitionLocation == .known(dependencySource))
    // Node was and still is. Do not remove it.
    disappearedNodes.removeValue(forKey: matchHere.key)
    enum FingerprintDisposition: String {
      case missing, changed, stable
      init(_ integrand: SourceFileDependencyGraph.Node,
           _ matchHere: ModuleDependencyGraph.Node) {
        switch (integrand.fingerprint, matchHere.fingerprint) {
        case (nil, _):
          self = .missing
        case (_?, nil):
          self = .changed
        case let (integrandFingerprint?, matchHereFingerprint?):
          self = integrandFingerprint == matchHereFingerprint
          ? .stable : .changed
        }
      }
    }
    let disposition = FingerprintDisposition(integrand, matchHere)
    switch disposition {
    case .stable:
      break
    case .missing:
      // Since we only put fingerprints in enums, structs, classes, etc.,
      // the driver really lacks the information to do much here.
      // Just report it.
      reporter?.report("Fingerprint \(disposition.rawValue) for existing \(matchHere.description(in: destination))")
      break
    case .changed:
      matchHere.setFingerprint(integrand.fingerprint)
      addChanged(matchHere)
      reporter?.report("Fingerprint \(disposition.rawValue) for existing \(matchHere.description(in: destination))")
    }
    return matchHere
  }

  /// If a node to be integrated correspnds with a node in the graph belonging to no dependency source read as yet, integrate it.
  /// The node to be integrated represents the definition of a declaration whose uses have already been seen.
  /// The existing node is "moved" to its proper place in the graph, corresponding to the location of the definition of the declaration.
  ///
  /// - Parameters:
  ///   - integrand: the node to be integrated
  ///   - nodesMatchingKey: all nodes in the destination graph with matching `DependencyKey`
  /// - Returns: nil if a corresponding node *did* have a definition location, or the integrated corresponding node if it did not.
  ///  If the integrated node was changed by the integration, it is added to ``invalidatedNodes``.
  private mutating func integrateWithNodeDefinedNowhere(
    _ integrand: SourceFileDependencyGraph.Node,
    _ nodesMatchingKey: [DefinitionLocation: Graph.Node]
  ) -> Graph.Node? {
    guard let nodeWithNoDefinitionLocation = nodesMatchingKey[.unknown] else {
      return nil
    }
    assert(nodesMatchingKey.count == 1,
           "The graph never holds more than one node for a given key that has no definition location")
    let integratedNode = destination.nodeFinder
      .replace(nodeWithNoDefinitionLocation,
               newDependencySource: self.dependencySource,
               newFingerprint: integrand.fingerprint)
    addPatriated(integratedNode)
    return integratedNode
  }

  /// Integrate a node that correspnds with no known node.
  ///
  /// - Parameters:
  ///   - integrand: the node to be integrated
  /// - Returns: the integrated node
  /// Since the integrated nodeis a change, it is added to ``invalidatedNodes``.
 private mutating func integrateWithNewNode(
    _ integrand: SourceFileDependencyGraph.Node
  ) -> Graph.Node {
    precondition(integrand.definitionVsUse == .definition,
                 "Dependencies are arcs in the module graph")
    let newNode = Graph.Node(
      key: integrand.key,
      fingerprint: integrand.fingerprint,
      definitionLocation: .known(dependencySource))
    let oldNode = destination.nodeFinder.insert(newNode)
    assert(oldNode == nil, "Should be new!")
    addNew(newNode)
    return newNode
  }

  /// Find the keys of nodes used by this node, and record the def-use links.
  /// Also see if any of those keys are external dependencies, and if such is a new dependency,
  /// record the external dependency, and record the node as changed.
  private mutating func recordDefsForThisUse(
    _ sourceFileUseNode: SourceFileDependencyGraph.Node,
    _ moduleUseNode: Graph.Node
  ) {
    sourceGraph.forEachDefDependedUpon(by: sourceFileUseNode) { def in
      let isNewUse = destination.nodeFinder.record(def: def.key,
                                                   use: moduleUseNode)
      guard
        isNewUse,
        let externalDependency = def.key.designator.externalDependency
      else {
        return
      }

      recordInvalidations(
        from: FingerprintedExternalDependency(externalDependency, def.fingerprint))
    }
  }

  // A `moduleGraphUseNode` is used by an externalDependency key being integrated.
  // Remember the dependency for later processing in externalDependencies, and
  // also return it in results.
  // Also the use node has changed.
  private mutating func recordInvalidations(
    from externalDependency: FingerprintedExternalDependency
  ) {
    let integrand = ModuleDependencyGraph.ExternalIntegrand(externalDependency, in: destination)
    let invalidated = destination.findNodesInvalidated(by: integrand)
    recordUsesOfSomeExternal(invalidated)
  }
}

// MARK: - Results {
extension ModuleDependencyGraph.Integrator {
  /*@_spi(Testing)*/
    mutating func recordUsesOfSomeExternal(_ invalidated: DirectlyInvalidatedNodeSet)
    {
      invalidatedNodes.formUnion(invalidated)
    }
  mutating func addDisappeared(_ node: Graph.Node) {
    assert(isUpdating)
    invalidatedNodes.insert(node)
  }
  mutating func addChanged(_ node: Graph.Node) {
    assert(isUpdating)
    invalidatedNodes.insert(node)
  }
  mutating func addPatriated(_ node: Graph.Node) {
    if isUpdating {
      reporter?.report("Discovered a definition for \(node.description(in: destination))")
      invalidatedNodes.insert(node)
    }
  }
  mutating func addNew(_ node: Graph.Node) {
    if isUpdating {
      reporter?.report("New definition: \(node.description(in: destination))")
      invalidatedNodes.insert(node)
    }
  }
}

// MARK: - verification
extension ModuleDependencyGraph.Integrator {
  @discardableResult
  func verifyAfterImporting() -> Bool {
    guard let nodesInFile = destination.nodeFinder.findNodes(for: .known(dependencySource)),
          !nodesInFile.isEmpty
    else {
      fatalError("Just imported \(dependencySource), should have nodes")
    }
    return destination.verifyGraph()
  }
}