diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b47777c1c7..4979b638f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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] @@ -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 diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index f5d74294f8f..33ac07e6bca 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -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] @@ -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 { @@ -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 `__main` entry point symbol to `_main` again. diff --git a/Sources/Commands/Options.swift b/Sources/Commands/Options.swift index 75635af7153..f822dc1e908 100644 --- a/Sources/Commands/Options.swift +++ b/Sources/Commands/Options.swift @@ -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 diff --git a/Sources/Commands/SwiftTool.swift b/Sources/Commands/SwiftTool.swift index cf9c4505699..8c3483406f6 100644 --- a/Sources/Commands/SwiftTool.swift +++ b/Sources/Commands/SwiftTool.swift @@ -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 ) }) diff --git a/Sources/SPMBuildCore/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters.swift index bcf0ab9085a..ab65c689ffa 100644 --- a/Sources/SPMBuildCore/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters.swift @@ -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( @@ -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) @@ -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 } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 1155f28218e..1f6b92e12e5 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -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, @@ -82,7 +83,8 @@ final class BuildPlanTests: XCTestCase { shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib, canRenameEntrypointFunctionName: canRenameEntrypointFunctionName, indexStoreMode: indexStoreMode, - useExplicitModuleBuild: useExplicitModuleBuild + useExplicitModuleBuild: useExplicitModuleBuild, + linkerDeadStrip: linkerDeadStrip ) } @@ -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", @@ -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"),