Skip to content

Add -dead_strip / --gc-sections for release builds #4135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Swift 5.7

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

* [#4135]

Enable linker dead stripping for all platforms. This can be disabled with `--disable-dead-strip`

Swift 5.6
-----------
* [SE-0332]
Expand Down Expand Up @@ -222,4 +226,5 @@ Swift 3.0
[#3942]: https://github.com/apple/swift-package-manager/pull/3942
[#4119]: https://github.com/apple/swift-package-manager/pull/4119
[#4131]: https://github.com/apple/swift-package-manager/pull/4131
[#4135]: https://github.com/apple/swift-package-manager/pull/4135

22 changes: 22 additions & 0 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,25 @@ public final class ProductBuildDescription {
return args.filter({ !invalidArguments.contains($0) })
}

private var deadStripArguments: [String] {
if !buildParameters.linkerDeadStrip {
return []
}

switch buildParameters.configuration {
case .debug:
return []
case .release:
if buildParameters.triple.isDarwin() {
return ["-Xlinker", "-dead_strip"]
} else if buildParameters.triple.isWindows() {
return ["-Xlinker", "/OPT:REF"]
} else {
return ["-Xlinker", "--gc-sections"]
}
}
}

/// The arguments to link and create this product.
public func linkArguments() throws -> [String] {
var args = [buildParameters.toolchain.swiftCompiler.pathString]
Expand Down Expand Up @@ -1246,12 +1265,14 @@ public final class ProductBuildDescription {
case .manifest:
args += ["-emit-executable"]
}
args += deadStripArguments
case .library(.dynamic):
args += ["-emit-library"]
if buildParameters.triple.isDarwin() {
let relativePath = "@rpath/\(buildParameters.binaryRelativePath(for: product).pathString)"
args += ["-Xlinker", "-install_name", "-Xlinker", relativePath]
}
args += deadStripArguments
case .executable, .snippet:
// Link the Swift stdlib statically, if requested.
if buildParameters.shouldLinkStaticSwiftStdlib {
Expand All @@ -1262,6 +1283,7 @@ public final class ProductBuildDescription {
}
}
args += ["-emit-executable"]
args += deadStripArguments

// If we're linking an executable whose main module is implemented in Swift,
// we rename the `_<modulename>_main` entry point symbol to `_main` again.
Expand Down
6 changes: 6 additions & 0 deletions Sources/Commands/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,12 @@ public struct SwiftToolOptions: ParsableArguments {
@Option(name: .customLong("resolver-fingerprint-checking"))
var resolverFingerprintCheckingMode: FingerprintCheckingMode = .warn

@Flag(
name: .customLong("dead-strip"),
inversion: .prefixedEnableDisable,
help: "Disable/enable dead code stripping by the linker")
var linkerDeadStrip: Bool = true

@Flag(name: .customLong("netrc"), help: .hidden)
var _deprecated_netrc: Bool = false

Expand Down
1 change: 1 addition & 0 deletions Sources/Commands/SwiftTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ public class SwiftTool {
isXcodeBuildSystemEnabled: options.buildSystem == .xcode,
printManifestGraphviz: options.printManifestGraphviz,
forceTestDiscovery: options.enableTestDiscovery, // backwards compatibility, remove with --enable-test-discovery
linkerDeadStrip: options.linkerDeadStrip,
isTTY: isTTY
)
})
Expand Down
5 changes: 5 additions & 0 deletions Sources/SPMBuildCore/BuildParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ public struct BuildParameters: Encodable {
// What strategy to use to discover tests
public var testDiscoveryStrategy: TestDiscoveryStrategy

/// Whether to disable dead code stripping by the linker
public var linkerDeadStrip: Bool

public var isTTY: Bool

public init(
Expand Down Expand Up @@ -200,6 +203,7 @@ public struct BuildParameters: Encodable {
printManifestGraphviz: Bool = false,
enableTestability: Bool? = nil,
forceTestDiscovery: Bool = false,
linkerDeadStrip: Bool = true,
isTTY: Bool = false
) {
let triple = destinationTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompiler)
Expand Down Expand Up @@ -237,6 +241,7 @@ public struct BuildParameters: Encodable {
self.enableTestability = enableTestability ?? (.debug == configuration)
// decide if to enable the use of test manifests based on platform. this is likely to change in the future
self.testDiscoveryStrategy = triple.isDarwin() ? .objectiveC : .manifest(generate: forceTestDiscovery)
self.linkerDeadStrip = linkerDeadStrip
self.isTTY = isTTY
}

Expand Down
62 changes: 59 additions & 3 deletions Tests/BuildTests/BuildPlanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ final class BuildPlanTests: XCTestCase {
canRenameEntrypointFunctionName: Bool = false,
destinationTriple: TSCUtility.Triple = hostTriple,
indexStoreMode: BuildParameters.IndexStoreMode = .off,
useExplicitModuleBuild: Bool = false
useExplicitModuleBuild: Bool = false,
linkerDeadStrip: Bool = true
) -> BuildParameters {
return BuildParameters(
dataPath: buildPath,
Expand All @@ -82,7 +83,8 @@ final class BuildPlanTests: XCTestCase {
shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib,
canRenameEntrypointFunctionName: canRenameEntrypointFunctionName,
indexStoreMode: indexStoreMode,
useExplicitModuleBuild: useExplicitModuleBuild
useExplicitModuleBuild: useExplicitModuleBuild,
linkerDeadStrip: linkerDeadStrip
)
}

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

#if os(macOS)
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
"-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable",
"-Xlinker", "-dead_strip", "-Xlinker", "-rpath", "-Xlinker", "@loader_path",
"@/path/to/build/release/exe.product/Objects.LinkFileList",
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx",
"-target", defaultTargetTriple,
])
#else
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
"-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable",
"-Xlinker", "--gc-sections", "-Xlinker", "-rpath=$ORIGIN",
"@/path/to/build/release/exe.product/Objects.LinkFileList",
"-target", defaultTargetTriple,
])
#endif
}

func testBasicReleasePackageNoDeadStrip() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Pkg/Sources/exe/main.swift"
)

let observability = ObservabilitySystem.makeForTesting()
let graph = try loadPackageGraph(
fs: fs,
manifests: [
Manifest.createRootManifest(
name: "Pkg",
path: .init("/Pkg"),
targets: [
TargetDescription(name: "exe", dependencies: []),
]),
],
observabilityScope: observability.topScope
)
XCTAssertNoDiagnostics(observability.diagnostics)

let result = try BuildPlanResult(plan: BuildPlan(
buildParameters: mockBuildParameters(config: .release, linkerDeadStrip: false),
graph: graph,
fileSystem: fs,
observabilityScope: observability.topScope
))

result.checkProductsCount(1)
result.checkTargetsCount(1)

let exe = try result.target(for: "exe").swiftTarget().compileArguments()
XCTAssertMatch(exe, ["-swift-version", "4", "-O", "-g", .equal(j), "-DSWIFT_PACKAGE", "-module-cache-path", "/path/to/build/release/ModuleCache", .anySequence])

#if os(macOS)
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
Expand Down Expand Up @@ -1029,7 +1085,7 @@ final class BuildPlanTests: XCTestCase {
XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [
"/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release",
"-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable",
"-Xlinker", "-rpath", "-Xlinker", "@loader_path",
"-Xlinker", "-dead_strip", "-Xlinker", "-rpath", "-Xlinker", "@loader_path",
"@/path/to/build/release/exe.product/Objects.LinkFileList",
"-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx",
"-target", hostTriple.tripleString(forPlatformVersion: "12.0"),
Expand Down