Skip to content

Commit 5b4f5dd

Browse files
committed
fix cross-module-optimization builds and add the option -disable-cmo
The driver splits the compile job into two jobs: one for creating the object file, one for emitting the swiftmodule. For the swiftmodule-job the driver adds -experimental-skip-non-inlinable-function-bodies-without-types. This breaks CMO, because in CMO, the inlinable-decision is not derived from the AST but done in the optimizer. As a quick hack we could disable -experimental-skip-non-inlinable-function-bodies-without-types. But for CMO, the two-job approach is highly problematic, because it risks the two compile jobs to get out of sync, e.g. by different command line options, indeterministic behavior, etc. This can result in unresolved symbol errors, which are very hard to debug. This change also adds a new option `-disable-cmo` to disable cross-module-optimization. It can be used to go back to the old behavior. rdar://89223981
1 parent 46d90b1 commit 5b4f5dd

File tree

5 files changed

+62
-14
lines changed

5 files changed

+62
-14
lines changed

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ extension Driver {
282282
try addCommonFrontendOptions(commandLine: &commandLine, inputs: &inputs)
283283
// FIXME: MSVC runtime flags
284284

285+
addDisableCMOOption(commandLine: &commandLine)
286+
285287
if parsedOptions.hasArgument(.parseAsLibrary, .emitLibrary) {
286288
commandLine.appendFlag(.parseAsLibrary)
287289
}

Sources/SwiftDriver/Jobs/EmitModuleJob.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ extension Driver {
9797

9898
addCommonModuleOptions(commandLine: &commandLine, outputs: &outputs, isMergeModule: false)
9999

100+
addDisableCMOOption(commandLine: &commandLine)
101+
100102
try commandLine.appendLast(.emitSymbolGraph, from: &parsedOptions)
101103
try commandLine.appendLast(.emitSymbolGraphDir, from: &parsedOptions)
102104
try commandLine.appendLast(.includeSpiSymbols, from: &parsedOptions)
@@ -143,6 +145,13 @@ extension Driver {
143145
default: true)
144146

145147
case .singleCompile:
148+
// Non library-evolution builds require a single job, because cross-module-optimization is enabled by default.
149+
if !parsedOptions.hasArgument(.enableLibraryEvolution),
150+
!parsedOptions.hasArgument(.disableCrossModuleOptimization),
151+
let opt = parsedOptions.getLast(in: .O), opt.option != .Onone {
152+
return false
153+
}
154+
146155
return parsedOptions.hasFlag(positive: .emitModuleSeparatelyWMO,
147156
negative: .noEmitModuleSeparatelyWMO,
148157
default: true) &&

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,14 @@ extension Driver {
300300
driver: self)
301301
}
302302

303+
/// Add options to disable cross-module-optimization.
304+
mutating func addDisableCMOOption(commandLine: inout [Job.ArgTemplate]) {
305+
if parsedOptions.hasArgument(.disableCrossModuleOptimization) {
306+
commandLine.appendFlag(.Xllvm)
307+
commandLine.appendFlag("-sil-disable-pass=cmo")
308+
}
309+
}
310+
303311
mutating func addFrontendSupplementaryOutputArguments(commandLine: inout [Job.ArgTemplate],
304312
primaryInputs: [TypedVirtualPath],
305313
inputsGeneratingCodeCount: Int,

Sources/SwiftOptions/Options.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ extension Option {
6161
public static let continueBuildingAfterErrors: Option = Option("-continue-building-after-errors", .flag, attributes: [.frontend, .doesNotAffectIncrementalBuild], helpText: "Continue building, even after errors are encountered")
6262
public static let coveragePrefixMap: Option = Option("-coverage-prefix-map", .separate, attributes: [.frontend], metaVar: "<prefix=replacement>", helpText: "Remap source paths in coverage info")
6363
public static let CrossModuleOptimization: Option = Option("-cross-module-optimization", .flag, attributes: [.helpHidden, .frontend], helpText: "Perform cross-module optimization")
64+
public static let disableCrossModuleOptimization: Option = Option("-disable-cmo", .flag, attributes: [.helpHidden, .frontend], helpText: "Disable cross-module optimization")
6465
public static let crosscheckUnqualifiedLookup: Option = Option("-crosscheck-unqualified-lookup", .flag, attributes: [.frontend, .noDriver], helpText: "Compare legacy DeclContext- to ASTScope-based unqualified name lookup (for debugging)")
6566
public static let c: Option = Option("-c", .flag, alias: Option.emitObject, attributes: [.frontend, .noInteractive], group: .modes)
6667
public static let debugAssertAfterParse: Option = Option("-debug-assert-after-parse", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Force an assertion failure after parsing", group: .debugCrash)
@@ -704,6 +705,7 @@ extension Option {
704705
Option.continueBuildingAfterErrors,
705706
Option.coveragePrefixMap,
706707
Option.CrossModuleOptimization,
708+
Option.disableCrossModuleOptimization,
707709
Option.crosscheckUnqualifiedLookup,
708710
Option.c,
709711
Option.debugAssertAfterParse,

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2691,12 +2691,11 @@ final class SwiftDriverTests: XCTestCase {
26912691
func testEmitModuleSeparatelyWMO() throws {
26922692
var envVars = ProcessEnv.vars
26932693
envVars["SWIFT_DRIVER_LD_EXEC"] = ld.nativePathString(escaped: false)
2694+
let root = try localFileSystem.currentWorkingDirectory.map { AbsolutePath("/foo/bar", relativeTo: $0) }
2695+
?? AbsolutePath(validating: "/foo/bar")
26942696

26952697
do {
2696-
let symbolGraphDir =
2697-
try localFileSystem.currentWorkingDirectory.map { AbsolutePath("/foo/bar", relativeTo: $0) }
2698-
?? AbsolutePath(validating: "/Foo/Bar")
2699-
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: symbolGraphDir), "-emit-symbol-graph", "-emit-symbol-graph-dir", symbolGraphDir.pathString, "-emit-library", "-target", "x86_64-apple-macosx10.15", "-wmo", "-emit-module-separately-wmo"],
2698+
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: root), "-emit-symbol-graph", "-emit-symbol-graph-dir", root.pathString, "-emit-library", "-target", "x86_64-apple-macosx10.15", "-wmo", "-emit-module-separately-wmo"],
27002699
env: envVars)
27012700

27022701
let abiFileCount = (driver.isFeatureSupported(.emit_abi_descriptor) && driver.targetTriple.isDarwin) ? 1 : 0
@@ -2715,21 +2714,18 @@ final class SwiftDriverTests: XCTestCase {
27152714
let emitModuleJob = plannedJobs.first(where: {$0.kind == .emitModule})!
27162715
XCTAssertTrue(emitModuleJob.tool.name.contains("swift"))
27172716
XCTAssertEqual(emitModuleJob.outputs.count, 3 + abiFileCount)
2718-
XCTAssertEqual(1, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.swiftmodule", at: symbolGraphDir)))}).count)
2719-
XCTAssertEqual(1, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.swiftdoc", at: symbolGraphDir)))}).count)
2720-
XCTAssertEqual(1, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.swiftsourceinfo", at: symbolGraphDir)))}).count)
2717+
XCTAssertEqual(1, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.swiftmodule", at: root)))}).count)
2718+
XCTAssertEqual(1, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.swiftdoc", at: root)))}).count)
2719+
XCTAssertEqual(1, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.swiftsourceinfo", at: root)))}).count)
27212720
if abiFileCount == 1 {
2722-
XCTAssertEqual(abiFileCount, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.abi.json", at: symbolGraphDir)))}).count)
2721+
XCTAssertEqual(abiFileCount, emitModuleJob.outputs.filter({$0.file == .absolute(AbsolutePath(rebase("Test.abi.json", at: root)))}).count)
27232722
}
27242723

27252724
// We don't know the output file of the symbol graph, just make sure the flag is passed along.
27262725
XCTAssertTrue(emitModuleJob.commandLine.contains(.flag("-emit-symbol-graph-dir")))
27272726
}
27282727

27292728
do {
2730-
let root = try localFileSystem.currentWorkingDirectory.map { AbsolutePath("/foo/bar", relativeTo: $0) }
2731-
?? AbsolutePath(validating: "/foo/bar")
2732-
27332729
// Ignore the `-emit-module-separately-wmo` flag when building only the module files to avoid duplicating outputs.
27342730
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: root), "-wmo", "-emit-module-separately-wmo"])
27352731
let abiFileCount = (driver.isFeatureSupported(.emit_abi_descriptor) && driver.targetTriple.isDarwin) ? 1 : 0
@@ -2750,9 +2746,6 @@ final class SwiftDriverTests: XCTestCase {
27502746
}
27512747

27522748
do {
2753-
let root = try localFileSystem.currentWorkingDirectory.map { AbsolutePath("/foo/bar", relativeTo: $0) }
2754-
?? AbsolutePath(validating: "/foo/bar")
2755-
27562749
// Specifying -no-emit-module-separately-wmo doesn't schedule the separate emit-module job.
27572750
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: root), "-emit-library", "-wmo", "-emit-module-separately-wmo", "-no-emit-module-separately-wmo" ])
27582751
let abiFileCount = (driver.isFeatureSupported(.emit_abi_descriptor) && driver.targetTriple.isDarwin) ? 1 : 0
@@ -2777,6 +2770,40 @@ final class SwiftDriverTests: XCTestCase {
27772770
}
27782771
}
27792772

2773+
#if os(Linux) || os(Android)
2774+
let autoLinkExtractJob = 1
2775+
#else
2776+
let autoLinkExtractJob = 0
2777+
#endif
2778+
2779+
do {
2780+
// non library-evolution builds require a single job, because cross-module-optimization is enabled by default.
2781+
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: root), "-c", "-o", rebase("test.o", at: root), "-wmo", "-O" ])
2782+
let plannedJobs = try driver.planBuild()
2783+
XCTAssertEqual(plannedJobs.count, 1 + autoLinkExtractJob)
2784+
}
2785+
2786+
do {
2787+
// library-evolution builds can emit the module in a separate job.
2788+
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: root), "-c", "-o", rebase("test.o", at: root), "-wmo", "-O", "-enable-library-evolution" ])
2789+
let plannedJobs = try driver.planBuild()
2790+
XCTAssertEqual(plannedJobs.count, 2 + autoLinkExtractJob)
2791+
}
2792+
2793+
do {
2794+
// When disabling cross-module-optimization, the module can be emitted in a separate job.
2795+
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: root), "-c", "-o", rebase("test.o", at: root), "-wmo", "-O", "-disable-cmo" ])
2796+
let plannedJobs = try driver.planBuild()
2797+
XCTAssertEqual(plannedJobs.count, 2 + autoLinkExtractJob)
2798+
}
2799+
2800+
do {
2801+
// non optimized builds can emit the module in a separate job.
2802+
var driver = try Driver(args: ["swiftc", "foo.swift", "bar.swift", "-module-name", "Test", "-emit-module-path", rebase("Test.swiftmodule", at: root), "-c", "-o", rebase("test.o", at: root), "-wmo" ])
2803+
let plannedJobs = try driver.planBuild()
2804+
XCTAssertEqual(plannedJobs.count, 2 + autoLinkExtractJob)
2805+
}
2806+
27802807
do {
27812808
// Don't use emit-module-separetely as a linker.
27822809
var driver = try Driver(args: ["swiftc", "foo.sil", "bar.sil", "-module-name", "Test", "-emit-module-path", "/foo/bar/Test.swiftmodule", "-emit-library", "-target", "x86_64-apple-macosx10.15", "-wmo", "-emit-module-separately-wmo"],

0 commit comments

Comments
 (0)