Skip to content

Commit a065fa9

Browse files
authored
Merge pull request #236 from artemcm/BatchClangScanAgain
[Explicit Module Builds] Use the new batch scanning mode for versioned PCM clang modele re-scan
2 parents b3ff6c1 + b1b1d62 commit a065fa9

File tree

6 files changed

+164
-65
lines changed

6 files changed

+164
-65
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ add_library(SwiftDriver
1313
"Explicit Module Builds/ClangVersionedDependencyResolution.swift"
1414
"Explicit Module Builds/InterModuleDependencyGraph.swift"
1515
"Explicit Module Builds/ModuleDependencyScanning.swift"
16-
"Explicit Module Builds/ModuleArtifacts.swift"
16+
"Explicit Module Builds/SerializableModuleArtifacts.swift"
1717

1818
Driver/CompilerMode.swift
1919
Driver/DebugInfo.swift

Sources/SwiftDriver/Execution/DriverExecutor.swift

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,7 @@ extension DriverExecutor {
5151
recordedInputModificationDates: recordedInputModificationDates)
5252

5353
if (result.exitStatus != .terminated(code: EXIT_SUCCESS)) {
54-
let returnCode: Int
55-
switch result.exitStatus {
56-
case .terminated(let code):
57-
returnCode = Int(code)
58-
#if !os(Windows)
59-
case .signalled(let signal):
60-
returnCode = Int(signal)
61-
#endif
62-
}
54+
let returnCode = Self.computeReturnCode(exitStatus: result.exitStatus)
6355
throw JobExecutionError.jobFailedWithNonzeroExitCode(returnCode, try result.utf8stderrOutput())
6456
}
6557
guard let outputData = try? Data(result.utf8Output().utf8) else {
@@ -68,6 +60,19 @@ extension DriverExecutor {
6860

6961
return try JSONDecoder().decode(outputType, from: outputData)
7062
}
63+
64+
static func computeReturnCode(exitStatus: ProcessResult.ExitStatus) -> Int {
65+
var returnCode: Int
66+
switch exitStatus {
67+
case .terminated(let code):
68+
returnCode = Int(code)
69+
#if !os(Windows)
70+
case .signalled(let signal):
71+
returnCode = Int(signal)
72+
#endif
73+
}
74+
return returnCode
75+
}
7176
}
7277

7378
public protocol JobExecutionDelegate {

Sources/SwiftDriver/Explicit Module Builds/ClangVersionedDependencyResolution.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Foundation
14+
import TSCBasic
1415

1516
/// A map from a module identifier to a set of module dependency graphs
1617
/// Used to compute distinct graphs corresponding to different target versions for a given clang module
@@ -30,19 +31,31 @@ internal extension Driver {
3031
// to all Clang modules, and compute a set of distinct PCMArgs across all paths to a
3132
// given Clang module in the graph.
3233
let modulePCMArgsSetMap = try dependencyGraph.computePCMArgSetsForClangModules()
33-
var moduleVersionedGraphMap: [ModuleDependencyId: [InterModuleDependencyGraph]] = [:]
34-
for (moduleId, pcmArgSet) in modulePCMArgsSetMap {
35-
for pcmArgs in pcmArgSet {
36-
let pcmSpecificDepGraph = try scanClangModule(moduleId: moduleId,
37-
pcmArgs: pcmArgs)
38-
if moduleVersionedGraphMap[moduleId] != nil {
39-
moduleVersionedGraphMap[moduleId]!.append(pcmSpecificDepGraph)
40-
} else {
41-
moduleVersionedGraphMap[moduleId] = [pcmSpecificDepGraph]
34+
let temporaryDirectory = try determineTempDirectory()
35+
let batchScanInputList =
36+
try modulePCMArgsSetMap.compactMap { (moduleId, pcmArgsSet) throws -> [BatchScanModuleInfo] in
37+
var moduleInfos: [BatchScanModuleInfo] = []
38+
for pcmArgs in pcmArgsSet {
39+
var hasher = Hasher()
40+
pcmArgs.forEach { hasher.combine($0) }
41+
// Generate a filepath for the output dependency graph
42+
let moduleDependencyGraphPath =
43+
temporaryDirectory.appending(component: moduleId.moduleName +
44+
String(hasher.finalize()) +
45+
"-dependencies.json")
46+
let moduleBatchInfo =
47+
BatchScanModuleInfo.clang(
48+
BatchScanClangModuleInfo(moduleName: moduleId.moduleName,
49+
pcmArgs: pcmArgs.joined(separator: " "),
50+
outputPath: moduleDependencyGraphPath.description))
51+
moduleInfos.append(moduleBatchInfo)
4252
}
43-
}
44-
}
53+
return moduleInfos
54+
}.reduce([], +)
4555

56+
// Batch scan all clang modules for each discovered unique set of PCMArgs, per module
57+
let moduleVersionedGraphMap: [ModuleDependencyId: [InterModuleDependencyGraph]] =
58+
try performBatchDependencyScan(moduleInfos: batchScanInputList)
4659
try dependencyGraph.resolveVersionedClangModules(using: moduleVersionedGraphMap)
4760
}
4861
}

Sources/SwiftDriver/Explicit Module Builds/ModuleDependencyScanning.swift

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ extension Driver {
2424
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
2525
commandLine.appendFlag("-frontend")
2626
commandLine.appendFlag("-scan-dependencies")
27-
if parsedOptions.hasArgument(.parseStdlib) {
28-
commandLine.appendFlag(.disableObjcAttrRequiresFoundationModule)
29-
}
3027
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs,
3128
bridgingHeaderHandling: .precompiled,
3229
moduleDependencyGraphUse: .dependencyScan)
@@ -75,65 +72,105 @@ extension Driver {
7572
return placeholderMapFilePath
7673
}
7774

78-
/// Compute the dependencies for a given Clang module, by invoking the Clang dependency scanning action
79-
/// with the given module's name and a set of arguments (including the target version)
80-
mutating func clangDependencyScanningJob(moduleId: ModuleDependencyId,
81-
pcmArgs: [String]) throws -> Job {
75+
mutating func performBatchDependencyScan(moduleInfos: [BatchScanModuleInfo])
76+
throws -> [ModuleDependencyId: [InterModuleDependencyGraph]] {
77+
let batchScanningJob = try batchDependencyScanningJob(for: moduleInfos)
78+
let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles)
79+
let batchScanResult =
80+
try self.executor.execute(job: batchScanningJob,
81+
forceResponseFiles: forceResponseFiles,
82+
recordedInputModificationDates: recordedInputModificationDates)
83+
let success = batchScanResult.exitStatus == .terminated(code: EXIT_SUCCESS)
84+
guard success else {
85+
throw JobExecutionError.jobFailedWithNonzeroExitCode(
86+
SwiftDriverExecutor.computeReturnCode(exitStatus: batchScanResult.exitStatus),
87+
try batchScanResult.utf8stderrOutput())
88+
}
89+
90+
// Decode the resulting dependency graphs and build a dictionary from a moduleId to
91+
// a set of dependency graphs that were built for it
92+
let moduleVersionedGraphMap =
93+
try moduleInfos.reduce(into: [ModuleDependencyId: [InterModuleDependencyGraph]]()) {
94+
let moduleId: ModuleDependencyId
95+
let dependencyGraphPath: VirtualPath
96+
switch $1 {
97+
case .swift(let swiftModuleBatchScanInfo):
98+
moduleId = .swift(swiftModuleBatchScanInfo.swiftModuleName)
99+
dependencyGraphPath = try VirtualPath(path: swiftModuleBatchScanInfo.output)
100+
case .clang(let clangModuleBatchScanInfo):
101+
moduleId = .clang(clangModuleBatchScanInfo.clangModuleName)
102+
dependencyGraphPath = try VirtualPath(path: clangModuleBatchScanInfo.output)
103+
}
104+
let contents = try fileSystem.readFileContents(dependencyGraphPath)
105+
let decodedGraph = try JSONDecoder().decode(InterModuleDependencyGraph.self,
106+
from: Data(contents.contents))
107+
if $0[moduleId] != nil {
108+
$0[moduleId]!.append(decodedGraph)
109+
} else {
110+
$0[moduleId] = [decodedGraph]
111+
}
112+
}
113+
return moduleVersionedGraphMap
114+
}
115+
116+
/// Precompute the dependencies for a given collection of modules using swift frontend's batch scanning mode
117+
mutating func batchDependencyScanningJob(for moduleInfos: [BatchScanModuleInfo]) throws -> Job {
82118
var inputs: [TypedVirtualPath] = []
83119

84120
// Aggregate the fast dependency scanner arguments
85121
var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) }
86122
commandLine.appendFlag("-frontend")
87-
commandLine.appendFlag("-scan-clang-dependencies")
88-
123+
// The dependency scanner automatically operates in batch mode if -batch-scan-input-file
124+
// is present.
125+
commandLine.appendFlag("-scan-dependencies")
89126
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs,
90127
bridgingHeaderHandling: .precompiled,
91128
moduleDependencyGraphUse: .dependencyScan)
92129

93-
// Ensure the `-target` option is inherited from the dependent Swift module's PCM args
94-
if let targetOptionIndex = pcmArgs.firstIndex(of: Option.target.spelling) {
95-
// PCM args are formulated as Clang command line options specified with:
96-
// -Xcc <option> -Xcc <option_value>
97-
assert(pcmArgs.count > targetOptionIndex + 1 && pcmArgs[targetOptionIndex + 1] == "-Xcc")
98-
let pcmArgTriple = Triple(pcmArgs[targetOptionIndex + 2])
99-
// Override the invocation's default target argument by appending the one extracted from
100-
// the pcmArgs
101-
commandLine.appendFlag(.target)
102-
commandLine.appendFlag(pcmArgTriple.triple)
103-
}
104-
105-
// Add the PCM args specific to this scan
106-
pcmArgs.forEach { commandLine.appendFlags($0) }
130+
let batchScanInputFilePath = try serializeBatchScanningModuleArtifacts(moduleInfos: moduleInfos)
131+
commandLine.appendFlag("-batch-scan-input-file")
132+
commandLine.appendPath(batchScanInputFilePath)
107133

108134
// This action does not require any input files, but all frontend actions require
109135
// at least one input so pick any input of the current compilation.
110136
let inputFile = inputFiles.first { $0.type == .swift }
111137
commandLine.appendPath(inputFile!.file)
112138
inputs.append(inputFile!)
113139

114-
commandLine.appendFlags("-module-name", moduleId.moduleName)
140+
// This job's outputs are defined as a set of dependency graph json files
141+
let outputs: [TypedVirtualPath] = try moduleInfos.map {
142+
switch $0 {
143+
case .swift(let swiftModuleBatchScanInfo):
144+
return TypedVirtualPath(file: try VirtualPath(path: swiftModuleBatchScanInfo.output),
145+
type: .jsonDependencies)
146+
case .clang(let clangModuleBatchScanInfo):
147+
return TypedVirtualPath(file: try VirtualPath(path: clangModuleBatchScanInfo.output),
148+
type: .jsonDependencies)
149+
}
150+
}
151+
115152
// Construct the scanning job.
116153
return Job(moduleName: moduleOutputInfo.name,
117-
kind: .scanClangDependencies,
154+
kind: .scanDependencies,
118155
tool: VirtualPath.absolute(try toolchain.getToolPath(.swiftCompiler)),
119156
commandLine: commandLine,
120157
displayInputs: inputs,
121158
inputs: inputs,
122-
outputs: [TypedVirtualPath(file: .standardOutput, type: .jsonDependencies)],
159+
outputs: outputs,
123160
supportsResponseFiles: true)
124161
}
125162

126-
mutating func scanClangModule(moduleId: ModuleDependencyId, pcmArgs: [String])
127-
throws -> InterModuleDependencyGraph {
128-
let clangDependencyScannerJob = try clangDependencyScanningJob(moduleId: moduleId,
129-
pcmArgs: pcmArgs)
130-
let forceResponseFiles = parsedOptions.hasArgument(.driverForceResponseFiles)
163+
/// Serialize a collection of modules into an input format expected by the batch module dependency scanner.
164+
func serializeBatchScanningModuleArtifacts(moduleInfos: [BatchScanModuleInfo])
165+
throws -> AbsolutePath {
166+
let temporaryDirectory = try determineTempDirectory()
167+
let batchScanInputFilePath =
168+
temporaryDirectory.appending(component: "\(moduleOutputInfo.name)-batch-module-scan.json")
131169

132-
let dependencyGraph =
133-
try self.executor.execute(job: clangDependencyScannerJob,
134-
capturingJSONOutputAs: InterModuleDependencyGraph.self,
135-
forceResponseFiles: forceResponseFiles,
136-
recordedInputModificationDates: recordedInputModificationDates)
137-
return dependencyGraph
170+
let encoder = JSONEncoder()
171+
encoder.outputFormatting = [.prettyPrinted]
172+
let contents = try encoder.encode(moduleInfos)
173+
try fileSystem.writeFileContents(batchScanInputFilePath, bytes: ByteString(contents))
174+
return batchScanInputFilePath
138175
}
139176
}

Sources/SwiftDriver/Explicit Module Builds/ModuleArtifacts.swift renamed to Sources/SwiftDriver/Explicit Module Builds/SerializableModuleArtifacts.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Foundation
1616
/// - Swift Module Path
1717
/// - Swift Doc Path
1818
/// - Swift Source Info Path
19-
public struct SwiftModuleArtifactInfo: Codable {
19+
@_spi(Testing) public struct SwiftModuleArtifactInfo: Codable {
2020
/// The module's name
2121
public let moduleName: String
2222
/// The path for the module's .swiftmodule file
@@ -42,7 +42,7 @@ public struct SwiftModuleArtifactInfo: Codable {
4242
/// - Clang Module (name)
4343
/// - Clang Module (PCM) Path
4444
/// - Clang Module Map Path
45-
public struct ClangModuleArtifactInfo {
45+
@_spi(Testing) public struct ClangModuleArtifactInfo: Codable {
4646
/// The module's name
4747
public let moduleName: String
4848
/// The path for the module's .pcm file
@@ -56,3 +56,46 @@ public struct ClangModuleArtifactInfo {
5656
self.moduleMapPath = moduleMapPath
5757
}
5858
}
59+
60+
/// Describes a given module's batch dependency scanning input info
61+
/// - Module Name
62+
/// - Extra PCM build arguments (for Clang modules only)
63+
/// - Dependency graph output path
64+
internal enum BatchScanModuleInfo: Encodable {
65+
case swift(BatchScanSwiftModuleInfo)
66+
case clang(BatchScanClangModuleInfo)
67+
}
68+
69+
internal struct BatchScanSwiftModuleInfo: Encodable {
70+
var swiftModuleName: String
71+
var output: String
72+
73+
init(moduleName: String, outputPath: String) {
74+
self.swiftModuleName = moduleName
75+
self.output = outputPath
76+
}
77+
}
78+
79+
internal struct BatchScanClangModuleInfo: Encodable {
80+
var clangModuleName: String
81+
var arguments: String
82+
var output: String
83+
84+
init(moduleName: String, pcmArgs: String, outputPath: String) {
85+
self.clangModuleName = moduleName
86+
self.arguments = pcmArgs
87+
self.output = outputPath
88+
}
89+
}
90+
91+
internal extension BatchScanModuleInfo {
92+
func encode(to encoder: Encoder) throws {
93+
var container = encoder.singleValueContainer()
94+
switch self {
95+
case .swift(let swiftInfo):
96+
try container.encode(swiftInfo)
97+
case .clang(let clangInfo):
98+
try container.encode(clangInfo)
99+
}
100+
}
101+
}

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ final class ExplicitModuleBuildTests: XCTestCase {
133133
func testModuleDependencyBuildCommandGeneration() throws {
134134
do {
135135
var driver = try Driver(args: ["swiftc", "-driver-print-module-dependencies-jobs",
136+
"-module-name", "testModuleDependencyBuildCommandGeneration",
136137
"test.swift"])
137138
let pcmArgs = ["-Xcc","-target","-Xcc","x86_64-apple-macosx10.15"]
138139
let moduleDependencyGraph =
@@ -250,7 +251,7 @@ final class ExplicitModuleBuildTests: XCTestCase {
250251
/// is invoked with -experimental-explicit-module-build
251252
func testExplicitModuleBuildJobs() throws {
252253
try withTemporaryDirectory { path in
253-
let main = path.appending(component: "main.swift")
254+
let main = path.appending(component: "testExplicitModuleBuildJobs.swift")
254255
try localFileSystem.writeFileContents(main) {
255256
$0 <<< "import C;"
256257
$0 <<< "import E;"
@@ -328,7 +329,7 @@ final class ExplicitModuleBuildTests: XCTestCase {
328329
case .relative(try pcmArgsEncodedRelativeModulePath(for: "SwiftShims", with: pcmArgsCurrent)):
329330
try checkExplicitModuleBuildJob(job: job, pcmArgs: pcmArgsCurrent, moduleId: .clang("SwiftShims"),
330331
moduleDependencyGraph: dependencyGraph)
331-
case .temporary(RelativePath("main.o")):
332+
case .temporary(RelativePath("testExplicitModuleBuildJobs.o")):
332333
XCTAssertTrue(driver.isExplicitMainModuleJob(job: job))
333334
guard case .swift(let mainModuleSwiftDetails) = dependencyGraph.mainModule.details else {
334335
XCTFail("Main module does not have Swift details field")
@@ -338,11 +339,11 @@ final class ExplicitModuleBuildTests: XCTestCase {
338339
try checkExplicitModuleBuildJobDependencies(job: job, pcmArgs: pcmArgs,
339340
moduleInfo: dependencyGraph.mainModule,
340341
moduleDependencyGraph: dependencyGraph)
341-
case .relative(RelativePath("main")):
342+
case .relative(RelativePath("testExplicitModuleBuildJobs")):
342343
XCTAssertTrue(driver.isExplicitMainModuleJob(job: job))
343344
XCTAssertEqual(job.kind, .link)
344345

345-
case .temporary(RelativePath("main.autolink")):
346+
case .temporary(RelativePath("testExplicitModuleBuildJobs.autolink")):
346347
XCTAssertTrue(driver.isExplicitMainModuleJob(job: job))
347348
XCTAssertEqual(job.kind, .autolinkExtract)
348349

@@ -359,7 +360,7 @@ final class ExplicitModuleBuildTests: XCTestCase {
359360
#if os(macOS)
360361
try withTemporaryDirectory { path in
361362
try localFileSystem.changeCurrentWorkingDirectory(to: path)
362-
let main = path.appending(component: "main.swift")
363+
let main = path.appending(component: "testExplicitModuleBuildEndToEnd.swift")
363364
try localFileSystem.writeFileContents(main) {
364365
$0 <<< "import C;"
365366
$0 <<< "import E;"

0 commit comments

Comments
 (0)