File: IncrementalCompilationProtectedState.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 (173 lines) | stat: -rw-r--r-- 7,004 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
//===---------- IncrementalCompilationActor.swift - Incremental -----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
//
//===----------------------------------------------------------------------===//

import Dispatch
import SwiftOptions

import protocol TSCBasic.FileSystem

extension IncrementalCompilationState {
  /// Encapsulates the data necessary to make incremental build scheduling decisions and protects it from concurrent access.
  public struct ProtectedState {
    /// Keyed by primary input. As required compilations are discovered after the first wave, these shrink.
    ///
    /// This state is modified during the incremental build. All accesses must
    /// be protected by the confinement queue.
    fileprivate var skippedCompileGroups: [TypedVirtualPath: CompileJobGroup]

    /// Sadly, has to be `var` for formBatchedJobs
    ///
    /// After initialization, mutating accesses to the driver must be protected by
    /// the confinement queue.
    private var driver: Driver

    /// The oracle for deciding what depends on what. Applies to this whole module.
    /// fileprivate in order to control concurrency.
    fileprivate let moduleDependencyGraph: ModuleDependencyGraph

    fileprivate let jobCreatingPch: Job?
    fileprivate let reporter: Reporter?

    init(skippedCompileGroups: [TypedVirtualPath: CompileJobGroup],
         _ moduleDependencyGraph: ModuleDependencyGraph,
         _ jobCreatingPch: Job?,
         _ driver: inout Driver) {
      self.skippedCompileGroups = skippedCompileGroups
      self.moduleDependencyGraph = moduleDependencyGraph
      self.reporter = moduleDependencyGraph.info.reporter
      self.jobCreatingPch = jobCreatingPch
      self.driver = driver
    }
  }
}

extension IncrementalCompilationState.ProtectedState: IncrementalCompilationSynchronizer {
  public var incrementalCompilationQueue: DispatchQueue {
    moduleDependencyGraph.incrementalCompilationQueue
  }
}

// MARK: - 2nd wave
extension IncrementalCompilationState.ProtectedState {
  mutating func collectBatchedJobsDiscoveredToBeNeededAfterFinishing(
    job finishedJob: Job
  ) throws -> [Job]? {
    mutationSafetyPrecondition()
    // batch in here to protect the Driver from concurrent access
    return try collectUnbatchedJobsDiscoveredToBeNeededAfterFinishing(job: finishedJob)
      .map {try driver.formBatchedJobs($0, showJobLifecycle: driver.showJobLifecycle, jobCreatingPch: jobCreatingPch)}
  }

  /// Remember a job (group) that is before a compile or a compile itself.
  /// `job` just finished. Update state, and return the skipped compile job (groups) that are now known to be needed.
  /// If no more compiles are needed, return nil.
  /// Careful: job may not be primary.
  fileprivate mutating func collectUnbatchedJobsDiscoveredToBeNeededAfterFinishing(
    job finishedJob: Job) throws -> [Job]? {
      mutationSafetyPrecondition()
      // Find and deal with inputs that now need to be compiled
      let invalidatedInputs = collectInputsInvalidatedByRunning(finishedJob)
      assert(invalidatedInputs.isDisjoint(with: finishedJob.primarySwiftSourceFiles),
             "Primaries should not overlap secondaries.")

      if let reporter = self.reporter {
        for input in invalidatedInputs {
          reporter.report(
            "Queuing because of dependencies discovered later:", input)
        }
      }
      return try getUnbatchedJobs(for: invalidatedInputs)
    }

  /// After `job` finished find out which inputs must compiled that were not known to need compilation before
  fileprivate mutating func collectInputsInvalidatedByRunning(_ job: Job)-> Set<SwiftSourceFile> {
    mutationSafetyPrecondition()
    guard job.kind == .compile else {
      return Set<SwiftSourceFile>()
    }
    return job.primaryInputs.reduce(into: Set()) { invalidatedInputs, primaryInput in
      if let primary = SwiftSourceFile(ifSource: primaryInput) {
        invalidatedInputs.formUnion(collectInputsInvalidated(byCompiling: primary))
      }
    }
    .subtracting(job.primarySwiftSourceFiles) // have already compiled these
  }

  // "Mutating" because it mutates the graph, which may be a struct someday
  fileprivate mutating func collectInputsInvalidated(
    byCompiling input: SwiftSourceFile
  ) -> TransitivelyInvalidatedSwiftSourceFileSet {
    mutationSafetyPrecondition()
    if let found = moduleDependencyGraph.collectInputsRequiringCompilation(byCompiling: input) {
      return found
    }
    self.reporter?.report(
      "Failed to read some dependencies source; compiling everything", input)
    return TransitivelyInvalidatedSwiftSourceFileSet(skippedCompileGroups.keys.swiftSourceFiles)
  }

  /// Find the jobs that now must be run that were not originally known to be needed.
  fileprivate mutating func getUnbatchedJobs(
    for invalidatedInputs: Set<SwiftSourceFile>
  ) throws -> [Job] {
    mutationSafetyPrecondition()
    return invalidatedInputs.flatMap { input -> [Job] in
      if let group = skippedCompileGroups.removeValue(forKey: input.typedFile) {
        let primaryInputs = group.compileJob.primarySwiftSourceFiles
        assert(primaryInputs.count == 1)
        assert(primaryInputs[0] == input)
        self.reporter?.report("Scheduling invalidated", input)
        return group.allJobs()
      }
      else {
        self.reporter?.report("Tried to schedule invalidated input again", input)
        return []
      }
    }
  }
}


// MARK: - After the build
extension IncrementalCompilationState.ProtectedState {
  var skippedCompilationInputs: Set<TypedVirtualPath> {
    accessSafetyPrecondition()
    return Set(skippedCompileGroups.keys)
  }
  public var skippedJobs: [Job] {
    accessSafetyPrecondition()
    return skippedCompileGroups.values
      .sorted {$0.primaryInput.file.name < $1.primaryInput.file.name}
      .flatMap {$0.allJobs()}
  }

  func writeGraph(to path: VirtualPath,
                  on fs: FileSystem,
                  buildRecord: BuildRecord,
                  mockSerializedGraphVersion: Version? = nil
  ) throws {
    accessSafetyPrecondition()
    try moduleDependencyGraph.write(to: path, on: fs,
                                    buildRecord: buildRecord,
                                    mockSerializedGraphVersion: mockSerializedGraphVersion)
  }
}
// MARK: - Testing - (must be here to access graph safely)
extension IncrementalCompilationState.ProtectedState {
  /// Expose the protected ``ModuleDependencyGraph`` for testing
  @_spi(Testing) public mutating func testWithModuleDependencyGraph(
    _ fn: (ModuleDependencyGraph) throws -> Void
  ) rethrows {
    mutationSafetyPrecondition()
    try fn(moduleDependencyGraph)
  }
}