Skip to content

Commit 3e14a79

Browse files
authored
Add -dead_strip / --gc-sections for release builds (#4135)
motivation: In the past it was unsafe to pass -dead_strip because there was some swift metadata that could be stripped. It seems that at this point all of those cases are either fixed or marked as @llvm.used, so passing these flags should safely reduce binary size in some cases. If there are other cases where this isn't safe we should likely annotate them as @llvm.used anyways. Related: https://bugs.swift.org/browse/SR-521 #215 changes: * Add -dead_strip / --gc-sections for release builds * Add --disable-dead-strip flag * Add CHANGELOG entry
1 parent dee6c2c commit 3e14a79

File tree

6 files changed

+98
-3
lines changed

6 files changed

+98
-3
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Swift 5.7
1111

1212
Update to manifest API to make it impossible to create an invalid build setttings condition.
1313

14+
* [#4135]
15+
16+
Enable linker dead stripping for all platforms. This can be disabled with `--disable-dead-strip`
17+
1418
Swift 5.6
1519
-----------
1620
* [SE-0332]
@@ -222,4 +226,5 @@ Swift 3.0
222226
[#3942]: https://github.com/apple/swift-package-manager/pull/3942
223227
[#4119]: https://github.com/apple/swift-package-manager/pull/4119
224228
[#4131]: https://github.com/apple/swift-package-manager/pull/4131
229+
[#4135]: https://github.com/apple/swift-package-manager/pull/4135
225230

Sources/Build/BuildPlan.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,25 @@ public final class ProductBuildDescription {
12001200
return args.filter({ !invalidArguments.contains($0) })
12011201
}
12021202

1203+
private var deadStripArguments: [String] {
1204+
if !buildParameters.linkerDeadStrip {
1205+
return []
1206+
}
1207+
1208+
switch buildParameters.configuration {
1209+
case .debug:
1210+
return []
1211+
case .release:
1212+
if buildParameters.triple.isDarwin() {
1213+
return ["-Xlinker", "-dead_strip"]
1214+
} else if buildParameters.triple.isWindows() {
1215+
return ["-Xlinker", "/OPT:REF"]
1216+
} else {
1217+
return ["-Xlinker", "--gc-sections"]
1218+
}
1219+
}
1220+
}
1221+
12031222
/// The arguments to link and create this product.
12041223
public func linkArguments() throws -> [String] {
12051224
var args = [buildParameters.toolchain.swiftCompiler.pathString]
@@ -1246,12 +1265,14 @@ public final class ProductBuildDescription {
12461265
case .manifest:
12471266
args += ["-emit-executable"]
12481267
}
1268+
args += deadStripArguments
12491269
case .library(.dynamic):
12501270
args += ["-emit-library"]
12511271
if buildParameters.triple.isDarwin() {
12521272
let relativePath = "@rpath/\(buildParameters.binaryRelativePath(for: product).pathString)"
12531273
args += ["-Xlinker", "-install_name", "-Xlinker", relativePath]
12541274
}
1275+
args += deadStripArguments
12551276
case .executable, .snippet:
12561277
// Link the Swift stdlib statically, if requested.
12571278
if buildParameters.shouldLinkStaticSwiftStdlib {
@@ -1262,6 +1283,7 @@ public final class ProductBuildDescription {
12621283
}
12631284
}
12641285
args += ["-emit-executable"]
1286+
args += deadStripArguments
12651287

12661288
// If we're linking an executable whose main module is implemented in Swift,
12671289
// we rename the `_<modulename>_main` entry point symbol to `_main` again.

Sources/Commands/Options.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,12 @@ public struct SwiftToolOptions: ParsableArguments {
375375
@Option(name: .customLong("resolver-fingerprint-checking"))
376376
var resolverFingerprintCheckingMode: FingerprintCheckingMode = .warn
377377

378+
@Flag(
379+
name: .customLong("dead-strip"),
380+
inversion: .prefixedEnableDisable,
381+
help: "Disable/enable dead code stripping by the linker")
382+
var linkerDeadStrip: Bool = true
383+
378384
@Flag(name: .customLong("netrc"), help: .hidden)
379385
var _deprecated_netrc: Bool = false
380386

Sources/Commands/SwiftTool.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ public class SwiftTool {
842842
isXcodeBuildSystemEnabled: options.buildSystem == .xcode,
843843
printManifestGraphviz: options.printManifestGraphviz,
844844
forceTestDiscovery: options.enableTestDiscovery, // backwards compatibility, remove with --enable-test-discovery
845+
linkerDeadStrip: options.linkerDeadStrip,
845846
isTTY: isTTY
846847
)
847848
})

Sources/SPMBuildCore/BuildParameters.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ public struct BuildParameters: Encodable {
172172
// What strategy to use to discover tests
173173
public var testDiscoveryStrategy: TestDiscoveryStrategy
174174

175+
/// Whether to disable dead code stripping by the linker
176+
public var linkerDeadStrip: Bool
177+
175178
public var isTTY: Bool
176179

177180
public init(
@@ -200,6 +203,7 @@ public struct BuildParameters: Encodable {
200203
printManifestGraphviz: Bool = false,
201204
enableTestability: Bool? = nil,
202205
forceTestDiscovery: Bool = false,
206+
linkerDeadStrip: Bool = true,
203207
isTTY: Bool = false
204208
) {
205209
let triple = destinationTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompiler)
@@ -237,6 +241,7 @@ public struct BuildParameters: Encodable {
237241
self.enableTestability = enableTestability ?? (.debug == configuration)
238242
// decide if to enable the use of test manifests based on platform. this is likely to change in the future
239243
self.testDiscoveryStrategy = triple.isDarwin() ? .objectiveC : .manifest(generate: forceTestDiscovery)
244+
self.linkerDeadStrip = linkerDeadStrip
240245
self.isTTY = isTTY
241246
}
242247

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ final class BuildPlanTests: XCTestCase {
6969
canRenameEntrypointFunctionName: Bool = false,
7070
destinationTriple: TSCUtility.Triple = hostTriple,
7171
indexStoreMode: BuildParameters.IndexStoreMode = .off,
72-
useExplicitModuleBuild: Bool = false
72+
useExplicitModuleBuild: Bool = false,
73+
linkerDeadStrip: Bool = true
7374
) -> BuildParameters {
7475
return BuildParameters(
7576
dataPath: buildPath,
@@ -82,7 +83,8 @@ final class BuildPlanTests: XCTestCase {
8283
shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib,
8384
canRenameEntrypointFunctionName: canRenameEntrypointFunctionName,
8485
indexStoreMode: indexStoreMode,
85-
useExplicitModuleBuild: useExplicitModuleBuild
86+
useExplicitModuleBuild: useExplicitModuleBuild,
87+
linkerDeadStrip: linkerDeadStrip
8688
)
8789
}
8890

@@ -462,6 +464,60 @@ final class BuildPlanTests: XCTestCase {
462464
let exe = try result.target(for: "exe").swiftTarget().compileArguments()
463465
XCTAssertMatch(exe, ["-swift-version", "4", "-O", "-g", .equal(j), "-DSWIFT_PACKAGE", "-module-cache-path", "/path/to/build/release/ModuleCache", .anySequence])
464466

467+
#if os(macOS)
468+
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
469+
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
470+
"-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable",
471+
"-Xlinker", "-dead_strip", "-Xlinker", "-rpath", "-Xlinker", "@loader_path",
472+
"@/path/to/build/release/exe.product/Objects.LinkFileList",
473+
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
474+
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx",
475+
"-target", defaultTargetTriple,
476+
])
477+
#else
478+
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
479+
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
480+
"-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable",
481+
"-Xlinker", "--gc-sections", "-Xlinker", "-rpath=$ORIGIN",
482+
"@/path/to/build/release/exe.product/Objects.LinkFileList",
483+
"-target", defaultTargetTriple,
484+
])
485+
#endif
486+
}
487+
488+
func testBasicReleasePackageNoDeadStrip() throws {
489+
let fs = InMemoryFileSystem(emptyFiles:
490+
"/Pkg/Sources/exe/main.swift"
491+
)
492+
493+
let observability = ObservabilitySystem.makeForTesting()
494+
let graph = try loadPackageGraph(
495+
fs: fs,
496+
manifests: [
497+
Manifest.createRootManifest(
498+
name: "Pkg",
499+
path: .init("/Pkg"),
500+
targets: [
501+
TargetDescription(name: "exe", dependencies: []),
502+
]),
503+
],
504+
observabilityScope: observability.topScope
505+
)
506+
XCTAssertNoDiagnostics(observability.diagnostics)
507+
508+
let result = try BuildPlanResult(plan: BuildPlan(
509+
buildParameters: mockBuildParameters(config: .release, linkerDeadStrip: false),
510+
graph: graph,
511+
fileSystem: fs,
512+
observabilityScope: observability.topScope
513+
))
514+
515+
result.checkProductsCount(1)
516+
result.checkTargetsCount(1)
517+
518+
let exe = try result.target(for: "exe").swiftTarget().compileArguments()
519+
XCTAssertMatch(exe, ["-swift-version", "4", "-O", "-g", .equal(j), "-DSWIFT_PACKAGE", "-module-cache-path", "/path/to/build/release/ModuleCache", .anySequence])
520+
465521
#if os(macOS)
466522
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
467523
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
@@ -1029,7 +1085,7 @@ final class BuildPlanTests: XCTestCase {
10291085
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
10301086
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
10311087
"-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable",
1032-
"-Xlinker", "-rpath", "-Xlinker", "@loader_path",
1088+
"-Xlinker", "-dead_strip", "-Xlinker", "-rpath", "-Xlinker", "@loader_path",
10331089
"@/path/to/build/release/exe.product/Objects.LinkFileList",
10341090
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
10351091
"-target", hostTriple.tripleString(forPlatformVersion: "12.0"),

0 commit comments

Comments
 (0)