diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index 610ffca8514..06fd47cd4a1 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -294,6 +294,17 @@ public final class ClangTargetBuildDescription { args += self.buildParameters.flags.cxxCompilerFlags } + // rdar://117578677 + // Pass -fno-omit-frame-pointer to support backtraces + // this can be removed once the backtracer uses DWARF instead of frame pointers + if let omitFramePointers = self.buildParameters.omitFramePointers { + if omitFramePointers { + args += ["-fomit-frame-pointer"] + } else { + args += ["-fno-omit-frame-pointer"] + } + } + // Pass default include paths from the toolchain. for includeSearchPath in self.buildParameters.toolchain.includeSearchPaths { args += ["-I", includeSearchPath.pathString] diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index 2091ef4394c..f1843de849a 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -530,6 +530,17 @@ public final class SwiftTargetBuildDescription { args += self.packageNameArgumentIfSupported(with: self.package, packageAccess: self.target.packageAccess) args += try self.macroArguments() + + // rdar://117578677 + // Pass -fno-omit-frame-pointer to support backtraces + // this can be removed once the backtracer uses DWARF instead of frame pointers + if let omitFramePointers = self.buildParameters.omitFramePointers { + if omitFramePointers { + args += ["-Xcc", "-fomit-frame-pointer"] + } else { + args += ["-Xcc", "-fno-omit-frame-pointer"] + } + } return args } diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index f834b479369..ee0288c587d 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -470,6 +470,11 @@ public struct BuildOptions: ParsableArguments { ) public var testEntryPointPath: AbsolutePath? + // Whether to omit frame pointers + // this can be removed once the backtracer uses DWARF instead of frame pointers + @Flag(inversion: .prefixedNo, help: .hidden) + public var omitFramePointers: Bool? = nil + // @Flag works best when there is a default value present // if true, false aren't enough and a third state is needed // nil should not be the goto. Instead create an enum diff --git a/Sources/CoreCommands/SwiftTool.swift b/Sources/CoreCommands/SwiftTool.swift index 14c41c865f5..a83e7ffe834 100644 --- a/Sources/CoreCommands/SwiftTool.swift +++ b/Sources/CoreCommands/SwiftTool.swift @@ -695,6 +695,7 @@ public final class SwiftTool { isXcodeBuildSystemEnabled: options.build.buildSystem == .xcode, forceTestDiscovery: options.build.enableTestDiscovery, // backwards compatibility, remove with --enable-test-discovery testEntryPointPath: options.build.testEntryPointPath, + omitFramePointers: options.build.omitFramePointers, explicitTargetDependencyImportCheckingMode: options.build.explicitTargetDependencyImportCheck.modeParameter, linkerDeadStrip: options.linker.linkerDeadStrip, verboseOutput: self.logLevel <= .info diff --git a/Sources/SPMBuildCore/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters.swift index 6120d93369e..0f0e930617c 100644 --- a/Sources/SPMBuildCore/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters.swift @@ -215,6 +215,9 @@ public struct BuildParameters: Encodable { /// The style of test product to produce. public var testProductStyle: TestProductStyle + /// Whether to omit or preserver frame pointers + public var omitFramePointers: Bool? + /// Whether to disable dead code stripping by the linker public var linkerDeadStrip: Bool @@ -247,6 +250,7 @@ public struct BuildParameters: Encodable { enableTestability: Bool? = nil, forceTestDiscovery: Bool = false, testEntryPointPath: AbsolutePath? = nil, + omitFramePointers: Bool? = nil, explicitTargetDependencyImportCheckingMode: TargetDependencyImportCheckingMode = .none, linkerDeadStrip: Bool = true, colorizedOutput: Bool = false, @@ -287,6 +291,7 @@ public struct BuildParameters: Encodable { explicitlyEnabledDiscovery: forceTestDiscovery, explicitlySpecifiedPath: testEntryPointPath ) + self.omitFramePointers = omitFramePointers ?? (triple.isLinux() ? false : nil) self.explicitTargetDependencyImportCheckingMode = explicitTargetDependencyImportCheckingMode self.linkerDeadStrip = linkerDeadStrip self.colorizedOutput = colorizedOutput diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index e0a2b423e93..9ad7208badc 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1190,6 +1190,11 @@ final class BuildPlanTests: XCTestCase { #if !os(Windows) // FIXME(5473) - modules flags on Windows dropped args += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] #endif + + if hostTriple.isLinux() { + args += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try ext.basicArguments(isCXX: false), args) XCTAssertEqual(try ext.objects, [buildPath.appending(components: "extlib.build", "extlib.c.o")]) XCTAssertEqual(ext.moduleMap, buildPath.appending(components: "extlib.build", "module.modulemap")) @@ -1222,6 +1227,11 @@ final class BuildPlanTests: XCTestCase { #if !os(Windows) // FIXME(5473) - modules flags on Windows dropped args += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] #endif + + if hostTriple.isLinux() { + args += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try exe.basicArguments(isCXX: false), args) XCTAssertEqual(try exe.objects, [buildPath.appending(components: "exe.build", "main.c.o")]) XCTAssertEqual(exe.moduleMap, nil) @@ -1453,7 +1463,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(contents, .contains(#"-std=c++1z","-c","\#(Pkg.appending(components: "Sources", "lib", "libx.cpp").escapedPathString())"#)) let swiftInteropLib = try result.target(for: "swiftInteropLib").swiftTarget().compileArguments() - XCTAssertMatch(swiftInteropLib, [.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++1z", .end]) + XCTAssertMatch(swiftInteropLib, [.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++1z", .anySequence]) let swiftLib = try result.target(for: "swiftLib").swiftTarget().compileArguments() XCTAssertNoMatch(swiftLib, [.anySequence, "-Xcc", "-std=c++1z", .anySequence]) } @@ -1516,6 +1526,11 @@ final class BuildPlanTests: XCTestCase { #if !os(Windows) // FIXME(5473) - modules flags on Windows dropped args += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] #endif + + if hostTriple.isLinux() { + args += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) @@ -2480,6 +2495,11 @@ final class BuildPlanTests: XCTestCase { #if !os(Windows) // FIXME(5473) - modules flags on Windows dropped expectedExeBasicArgs += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] #endif + + if triple.isLinux() { + expectedExeBasicArgs += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try exe.basicArguments(isCXX: false), expectedExeBasicArgs) XCTAssertEqual(try exe.objects, [buildPath.appending(components: "exe.build", "main.c.o")]) XCTAssertEqual(exe.moduleMap, nil) @@ -2498,6 +2518,11 @@ final class BuildPlanTests: XCTestCase { if shouldHaveModules { expectedLibBasicArgs += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] } + + if triple.isLinux() { + expectedLibBasicArgs += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try lib.basicArguments(isCXX: true), expectedLibBasicArgs) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.cpp.o")]) @@ -3382,21 +3407,71 @@ final class BuildPlanTests: XCTestCase { let result = try createResult(for: .x86_64Linux) let dep = try result.target(for: "t1").swiftTarget().compileArguments() - XCTAssertMatch(dep, [.anySequence, "-DDEP", .end]) + XCTAssertMatch(dep, [.anySequence, "-DDEP", "-Xcc", "-fno-omit-frame-pointer", .end]) let cbar = try result.target(for: "cbar").clangTarget().basicArguments(isCXX: false) - XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", .end]) + XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", "-fno-omit-frame-pointer", .end]) let bar = try result.target(for: "bar").swiftTarget().compileArguments() - XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", .end]) + XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", "-Xcc", "-fno-omit-frame-pointer", .end]) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, [.anySequence, "-DFOO", .end]) + XCTAssertMatch(exe, [.anySequence, "-DFOO", "-Xcc", "-fno-omit-frame-pointer", .end]) let linkExe = try result.buildProduct(for: "exe").linkArguments() XCTAssertMatch(linkExe, [.anySequence, "-lsqlite3", "-llibz", "-framework", "best", "-Ilfoo", "-L", "lbar", .end]) } + // omit frame pointers explicitly set to true + do { + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters( + destinationTriple: .x86_64Linux, + omitFramePointers: true + ), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + let dep = try result.target(for: "t1").swiftTarget().compileArguments() + XCTAssertMatch(dep, [.anySequence, "-DDEP", .anySequence]) + + let cbar = try result.target(for: "cbar").clangTarget().basicArguments(isCXX: false) + XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", "-fomit-frame-pointer", .end]) + + let bar = try result.target(for: "bar").swiftTarget().compileArguments() + XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", "-Xcc", "-fomit-frame-pointer", .end]) + + let exe = try result.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(exe, [.anySequence, "-DFOO", "-Xcc", "-fomit-frame-pointer", .end]) + } + + // omit frame pointers explicitly set to false + do { + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters( + destinationTriple: .x86_64Linux, + omitFramePointers: false + ), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + let dep = try result.target(for: "t1").swiftTarget().compileArguments() + XCTAssertMatch(dep, [.anySequence, "-DDEP", .anySequence]) + + let cbar = try result.target(for: "cbar").clangTarget().basicArguments(isCXX: false) + XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", "-fno-omit-frame-pointer", .end]) + + let bar = try result.target(for: "bar").swiftTarget().compileArguments() + XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", "-Xcc", "-fno-omit-frame-pointer", .end]) + + let exe = try result.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(exe, [.anySequence, "-DFOO", "-Xcc", "-fno-omit-frame-pointer", .end]) + } + do { let result = try createResult(for: .macOS) diff --git a/Tests/BuildTests/MockBuildTestHelper.swift b/Tests/BuildTests/MockBuildTestHelper.swift index 34f0528804e..8bcd1b0e7dc 100644 --- a/Tests/BuildTests/MockBuildTestHelper.swift +++ b/Tests/BuildTests/MockBuildTestHelper.swift @@ -76,7 +76,8 @@ func mockBuildParameters( destinationTriple: Basics.Triple = hostTriple, indexStoreMode: BuildParameters.IndexStoreMode = .off, useExplicitModuleBuild: Bool = false, - linkerDeadStrip: Bool = true + linkerDeadStrip: Bool = true, + omitFramePointers: Bool? = nil ) -> BuildParameters { return try! BuildParameters( dataPath: buildPath, @@ -91,6 +92,7 @@ func mockBuildParameters( canRenameEntrypointFunctionName: canRenameEntrypointFunctionName, indexStoreMode: indexStoreMode, useExplicitModuleBuild: useExplicitModuleBuild, + omitFramePointers: omitFramePointers, linkerDeadStrip: linkerDeadStrip ) }