Skip to content

Commit f945f25

Browse files
committed
[Build] Add an option to emit Swift module separately
This can enable more parallelism since downstream targets can begin compiling without waiting for the entire module to finish building. <rdar://problem/56747877>
1 parent 60761e6 commit f945f25

File tree

4 files changed

+199
-6
lines changed

4 files changed

+199
-6
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ public struct BuildParameters: Encodable {
136136
/// `.swiftmodule`s.
137137
public let enableParseableModuleInterfaces: Bool
138138

139+
/// Emit Swift module separately from object files. This can enable more parallelism
140+
/// since downstream targets can begin compiling without waiting for the entire
141+
/// module to finish building.
142+
public let emitSwiftModuleSeparately: Bool
143+
139144
/// Checks if stdout stream is tty.
140145
fileprivate let isTTY: Bool = {
141146
guard let stream = stdoutStream.stream as? LocalFileOutputByteStream else {
@@ -165,7 +170,8 @@ public struct BuildParameters: Encodable {
165170
enableCodeCoverage: Bool = false,
166171
indexStoreMode: IndexStoreMode = .auto,
167172
enableParseableModuleInterfaces: Bool = false,
168-
enableTestDiscovery: Bool = false
173+
enableTestDiscovery: Bool = false,
174+
emitSwiftModuleSeparately: Bool = false
169175
) {
170176
self.dataPath = dataPath
171177
self.configuration = configuration
@@ -180,6 +186,7 @@ public struct BuildParameters: Encodable {
180186
self.indexStoreMode = indexStoreMode
181187
self.enableParseableModuleInterfaces = enableParseableModuleInterfaces
182188
self.enableTestDiscovery = enableTestDiscovery
189+
self.emitSwiftModuleSeparately = emitSwiftModuleSeparately
183190
}
184191

185192
/// Returns the compiler arguments for the index store, if enabled.
@@ -701,11 +708,148 @@ public final class SwiftTargetBuildDescription {
701708
return args
702709
}
703710

711+
/// Command-line for emitting just the Swift module.
712+
public func emitModuleCommandLine() -> [String] {
713+
assert(buildParameters.emitSwiftModuleSeparately)
714+
715+
var result: [String] = []
716+
result.append(buildParameters.toolchain.swiftCompiler.pathString)
717+
718+
result.append("-module-name")
719+
result.append(target.c99name)
720+
result.append("-emit-module")
721+
result.append("-emit-module-path")
722+
result.append(moduleOutputPath.pathString)
723+
result += buildParameters.toolchain.extraSwiftCFlags
724+
725+
result.append("-Xfrontend")
726+
result.append("-experimental-skip-non-inlinable-function-bodies")
727+
result.append("-force-single-frontend-invocation")
728+
729+
if target.type == .library || target.type == .test {
730+
result.append("-parse-as-library")
731+
}
732+
733+
// FIXME: Handle WMO
734+
735+
for source in target.sources.paths {
736+
result.append(source.pathString)
737+
}
738+
739+
result.append("-I")
740+
result.append(buildParameters.buildPath.pathString)
741+
742+
// FIXME: Maybe refactor these into "common args".
743+
result += buildParameters.targetTripleArgs(for: target)
744+
result += ["-swift-version", swiftVersion.rawValue]
745+
result += optimizationArguments
746+
result += ["-g"]
747+
result += ["-j\(ProcessInfo.processInfo.activeProcessorCount)"]
748+
result += activeCompilationConditions
749+
result += additionalFlags
750+
result += moduleCacheArgs
751+
result += self.buildSettingsFlags()
752+
753+
return result
754+
}
755+
756+
/// Command-line for emitting the object files.
757+
///
758+
/// Note: This doesn't emit the module.
759+
public func emitObjectsCommandLine() -> [String] {
760+
assert(buildParameters.emitSwiftModuleSeparately)
761+
762+
var result: [String] = []
763+
result.append(buildParameters.toolchain.swiftCompiler.pathString)
764+
765+
result.append("-module-name")
766+
result.append(target.c99name)
767+
result.append("-incremental")
768+
result.append("-emit-dependencies")
769+
770+
result.append("-output-file-map")
771+
// FIXME: Eliminate side effect.
772+
result.append(try! writeOutputFileMap().pathString)
773+
774+
if target.type == .library || target.type == .test {
775+
result.append("-parse-as-library")
776+
}
777+
// FIXME: Handle WMO
778+
779+
result.append("-c")
780+
for source in target.sources.paths {
781+
result.append(source.pathString)
782+
}
783+
784+
result.append("-I")
785+
result.append(buildParameters.buildPath.pathString)
786+
787+
result += buildParameters.targetTripleArgs(for: target)
788+
result += ["-swift-version", swiftVersion.rawValue]
789+
790+
result += buildParameters.indexStoreArguments
791+
result += buildParameters.toolchain.extraSwiftCFlags
792+
result += optimizationArguments
793+
result += ["-g"]
794+
result += ["-j\(ProcessInfo.processInfo.activeProcessorCount)"]
795+
result += activeCompilationConditions
796+
result += additionalFlags
797+
result += moduleCacheArgs
798+
result += buildParameters.sanitizers.compileSwiftFlags()
799+
result += ["-parseable-output"]
800+
result += self.buildSettingsFlags()
801+
result += buildParameters.swiftCompilerFlags
802+
return result
803+
}
804+
704805
/// Returns true if ObjC compatibility header should be emitted.
705806
private var shouldEmitObjCCompatibilityHeader: Bool {
706807
return buildParameters.triple.isDarwin() && target.type == .library
707808
}
708809

810+
private func writeOutputFileMap() throws -> AbsolutePath {
811+
let path = tempsPath.appending(component: "output-file-map.json")
812+
let stream = BufferedOutputByteStream()
813+
814+
stream <<< "{\n"
815+
816+
let masterDepsPath = tempsPath.appending(component: "master.swiftdeps")
817+
818+
stream <<< " \"\": {\n";
819+
// FIXME: Handle WMO
820+
stream <<< " \"swift-dependencies\": \"" <<< masterDepsPath.pathString <<< "\"\n";
821+
stream <<< " },\n";
822+
823+
// Write out the entries for each source file.
824+
let sources = target.sources.paths
825+
for (idx, source) in sources.enumerated() {
826+
let object = objects[idx]
827+
let objectDir = object.parentDirectory
828+
829+
let sourceFileName = source.basenameWithoutExt
830+
let partialModulePath = objectDir.appending(component: sourceFileName + "~partial.swiftmodule")
831+
832+
let swiftDepsPath = objectDir.appending(component: sourceFileName + ".swiftdeps")
833+
834+
stream <<< " \"" <<< source.pathString <<< "\": {\n"
835+
// FIXME: Handle WMO
836+
let depsPath = objectDir.appending(component: sourceFileName + ".d")
837+
stream <<< " \"dependencies\": \"" <<< depsPath.pathString <<< "\",\n"
838+
// FIXME: Need to record this deps file for processing it later.
839+
840+
stream <<< " \"object\": \"" <<< object.pathString <<< "\",\n"
841+
stream <<< " \"swiftmodule\": \"" <<< partialModulePath.pathString <<< "\",\n";
842+
stream <<< " \"swift-dependencies\": \"" <<< swiftDepsPath.pathString <<< "\"\n";
843+
stream <<< " }" <<< ((idx + 1) < sources.count ? "," : "") <<< "\n"
844+
}
845+
846+
stream <<< "}\n"
847+
848+
try localFileSystem.createDirectory(path.parentDirectory, recursive: true)
849+
try localFileSystem.writeFileContents(path, bytes: stream.bytes)
850+
return path
851+
}
852+
709853
/// Generates the module map for the Swift target and returns its path.
710854
private func generateModuleMap() throws -> AbsolutePath {
711855
let path = tempsPath.appending(component: moduleMapFilename)

Sources/Build/ManifestBuilder.swift

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,57 @@ extension LLBuildManifestBuilder {
149149
private func createSwiftCompileCommand(
150150
_ target: SwiftTargetBuildDescription
151151
) {
152+
// Inputs.
152153
let inputs = computeSwiftCompileCmdInputs(target)
153154

154-
let cmdName = target.target.getCommandName(config: buildConfig)
155+
// Outputs.
155156
let objectNodes = target.objects.map(Node.file)
156157
let moduleNode = Node.file(target.moduleOutputPath)
157158
let cmdOutputs = objectNodes + [moduleNode]
159+
160+
if buildParameters.emitSwiftModuleSeparately {
161+
addSwiftCmdsEmitSwiftModuleSeparately(target, inputs: inputs, objectNodes: objectNodes, moduleNode: moduleNode)
162+
} else {
163+
addCmdWithBuiltinSwiftTool(target, inputs: inputs, cmdOutputs: cmdOutputs)
164+
}
165+
166+
addTargetCmd(target, cmdOutputs: cmdOutputs)
167+
addModuleWrapCmd(target)
168+
}
169+
170+
private func addSwiftCmdsEmitSwiftModuleSeparately(
171+
_ target: SwiftTargetBuildDescription,
172+
inputs: [Node],
173+
objectNodes: [Node],
174+
moduleNode: Node
175+
) {
176+
// FIXME: We need to ingest the emitted dependencies.
177+
178+
manifest.addShellCmd(
179+
name: target.moduleOutputPath.pathString,
180+
description: "Emitting module for \(target.target.name)",
181+
inputs: inputs,
182+
outputs: [moduleNode],
183+
args: target.emitModuleCommandLine()
184+
)
185+
186+
let cmdName = target.target.getCommandName(config: buildConfig)
187+
manifest.addShellCmd(
188+
name: cmdName,
189+
description: "Compiling module \(target.target.name)",
190+
inputs: inputs,
191+
outputs: objectNodes,
192+
args: target.emitObjectsCommandLine()
193+
)
194+
}
195+
196+
private func addCmdWithBuiltinSwiftTool(
197+
_ target: SwiftTargetBuildDescription,
198+
inputs: [Node],
199+
cmdOutputs: [Node]
200+
) {
158201
let isLibrary = target.target.type == .library || target.target.type == .test
202+
let cmdName = target.target.getCommandName(config: buildConfig)
159203

160204
manifest.addSwiftCmd(
161205
name: cmdName,
@@ -172,9 +216,6 @@ extension LLBuildManifestBuilder {
172216
isLibrary: isLibrary,
173217
WMO: buildParameters.configuration == .release
174218
)
175-
176-
addTargetCmd(target, cmdOutputs: cmdOutputs)
177-
addModuleWrapCmd(target)
178219
}
179220

180221
private func computeSwiftCompileCmdInputs(

Sources/Commands/Options.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,8 @@ public class ToolOptions {
8686
/// Whether to enable llbuild manifest caching.
8787
public var enableBuildManifestCaching: Bool = false
8888

89+
/// Emit the Swift module separately from the object files.
90+
public var emitSwiftModuleSeparately: Bool = false
91+
8992
public required init() {}
9093
}

Sources/Commands/SwiftTool.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ public class SwiftTool<Options: ToolOptions> {
337337
option: parser.add(option: "--enable-build-manifest-caching", kind: Bool.self, usage: nil),
338338
to: { $0.enableBuildManifestCaching = $1 })
339339

340+
binder.bind(
341+
option: parser.add(option: "--emit-swift-module-separately", kind: Bool.self, usage: nil),
342+
to: { $0.emitSwiftModuleSeparately = $1 })
343+
340344
// Let subclasses bind arguments.
341345
type(of: self).defineArguments(parser: parser, binder: binder)
342346

@@ -739,7 +743,8 @@ public class SwiftTool<Options: ToolOptions> {
739743
enableCodeCoverage: options.shouldEnableCodeCoverage,
740744
indexStoreMode: options.indexStoreMode,
741745
enableParseableModuleInterfaces: options.shouldEnableParseableModuleInterfaces,
742-
enableTestDiscovery: options.enableTestDiscovery
746+
enableTestDiscovery: options.enableTestDiscovery,
747+
emitSwiftModuleSeparately: options.emitSwiftModuleSeparately
743748
)
744749
})
745750
}()

0 commit comments

Comments
 (0)