swift-frontend compiles main.swift to LLVM bitcode and writes main-1.bc
clang runs two steps:
a. clang -cc1 optimizes the LLVM bitcode and writes main-1-e1c2b7.o
b. ld links main-1-e1c2b7.o as lto_full executable
The same works with -lto=llvm-thin
How can I do this with Xcode or xcodebuild?
The Xcode UI doesn't seem to expose an option for -lto. Passing it as OTHER_SWIFT_FLAGS in my hello world Swift command-line app causes a strange error:
> xcodebuild -scheme Test -configuration Release OTHER_SWIFT_FLAGS="-lto=llvm-full"
...
ld: file cannot be open()ed, errno=2 path=/Users/.../Objects-normal/arm64/main.o
The linker doesn't find the object file main.o because it doesn't exist. We only have the LLVM bitcode file:
> ls /Users/.../Objects-normal/arm64/main.*
/Users/.../Objects-normal/arm64/main.bc
Swift Forums is dedicated to swift.org open source project and closed-source software like Xcode is off-topic here. If you're interested exclusively in Xcode, you should file an issue at https://feedbackassistant.apple.com.
To possibly keep this on-topic, is it reproducible for you when using --build-system swiftbuild with latest snapshots, which hopefully should be closer to xcodebuild in its behavior? Maybe @jakepetroules or @owenv can provide some other steps to reproduce this with swift.org distributions outside of Xcode and xcodebuild.
This is very similar to the behavior in xcodebuild: swiftc produces a LLVM bitcode file that clang cannot find. In particular (you can find all of it in the log above), I get:
swift-build writing the linker input paths to /Users/.../Test/.build/arm64-apple-macosx/release/Test.product/Objects.LinkFileList
swift-frontend compiling the LLVM bitcode file in my CWD (this is clearly a bug on its own): swift-frontend -frontend -emit-bc /Users/.../Test/Sources/main.swift -o main.bc
clang looking for the LLVM bitcode in the Objects.LinkFileList path /Users/.../Test/.build/arm64-apple-macosx/release/Test.build/main.swift.o, which doesn't exist
When I change the clang invocation and point it at the actual bitcode file /Users/.../Test/main.bc instead, it works perfectly:
Edit: Also added .unsafeFlags(["-Xclang-linker", "-flto=full"], .when(configuration: .release)) to linkerSettings in Package.swift but that didn't affect the behavior of swift-build
Thanks for the detailed investigation! This seems to me like something that SwiftPM should be able to handle regardless of the underlying build system. Would you mind filing an issue with your findings on the SwiftPM repository?
In general, you're not going to find much success passing unsafe flags, especially if those flags change things like compiler outputs, because the build planning done by Xcode and SwiftPM (and most build systems, typically) won't take those into account beyond passing them opaquely to the compiler/linker. So if a flag changes the compiler's outputs from .o to .bc, SwiftPM won't be aware of that and it'll still pass expect the former.
It looks like SwiftPM added a --experimental-lto-mode={thin,full} flag a while back that addresses the different expectations in file extensions; that might be worth a try. (But I don't know if there's a way to get Xcode to pass that through.)
As the person who added SwiftPM's --experimental-lto-mode={thin,full} flag, I'd like to note that you will find bugs with this implementation currently (like -lto_object_path not being set). I assume the new/wip swiftbuild backend for SwiftPM will be a lot more robust.
Perfect, thanks! I only tested LLVM_LTO before and had no luck. The SWIFT_LTO flag is exactly what I was looking for. In Xcode 16.3, this works with my initial hello world example in xcodebuild:
I didn't get anywhere with swift build quickly. It seems like SWIFT_LTO isn't supported here and --experimental-lto-mode isn't yet available in this release of the toolchain.
FWIW that experimental lto option has been in swift build for a while:
โ swiftly use
Swift 6.0.3 (default)
โ swift build --version
Swift Package Manager - Swift 6.0.3
โ swift build --help-hidden | rg lto
--experimental-lto-mode <experimental-lto-mode>