Hey there,
TL;DR - I was exploring adding the undefined behavior sanitizer support to SPM. To accomplish this we need to be able to specify clang++
vs ld
as the linker which doesn't seem supported in Darwin environments. Below I try to walk through how I got to this request and different things I've tried along the way.
I was exploring adding support for the Undefined Behavior sanitizer to Swift Package Manager to complement the existing address and thread sanitizer support. It's been fun exploring llbuild, spm and the swift driver but I think I'm at the point where I would like guidance from the community.
Here is how we got here:
When reviewing the LLVM UBSan documentation, and came across the requirement that clang++
must be used for linking vs ld
.
From the docs:
Use clang++ to compile and link your program with -fsanitize=undefined flag. Make sure to use clang++ (not ld) as a linker, so that your executable is linked with proper UBSan runtime libraries. You can use clang instead of clang++ if you’re compiling/linking C code.
I began exploring specifying clang++
when generating the llbuild
manifest but that quickly seemed like it wasn't the right path forward since the majority of the other flags were specific to swiftc
.
After that I started looking into the flags available I could pass to swiftc
(via -Xswiftc
) and came across -use-ld
.
-use-ld=<value> Specifies the linker to be used
This seemed like the right path forward so I started trying to change the linker by passing a statement like swift test -Xlinker -fsantize=undefined -use-ld=clang++
from SPM. Even after passing the proper flags, ld
was still used for linking which lead me to start looking into the driver source code to see how -use-ld
was handled.
Linkers are specified as part of the ToolChain interface in the Swift Driver.
The Unix Toolchain utilizes this flag and specifies clang++
as the default linker
if (!Linker.empty()) {
#if defined(__HAIKU__)
// For now, passing -fuse-ld on Haiku doesn't work as swiftc doesn't
// recognise it. Passing -use-ld= as the argument works fine.
Arguments.push_back(context.Args.MakeArgString("-use-ld=" + Linker));
#else
Arguments.push_back(context.Args.MakeArgString("-fuse-ld=" + Linker));
#endif
}
Since I'm encountering this issue on macOS, I referenced the Darwin toolchain to see if it was handling the flag as well. The Darwin toolchain seems to ignore this flag and only use ld
, there is support for specifying a toolchain path but it will also only look for ld
there.
// Configure the toolchain.
// By default, use the system `ld` to link.
const char *LD = "ld";
if (const Arg *A = context.Args.getLastArg(options::OPT_tools_directory)) {
StringRef toolchainPath(A->getValue());
// If there is a 'ld' in the toolchain folder, use that instead.
if (auto toolchainLD =
llvm::sys::findProgramByName("ld", {toolchainPath})) {
LD = context.Args.MakeArgString(toolchainLD.get());
}
}
I started to add support for -use-ld
in the Darwin toolchain but stopped when I came across this comment.
// FIXME: If we used Clang as a linker instead of going straight to ld,
// we wouldn't have to replicate a bunch of Clang's logic here.
// Always link the regular compiler_rt if it's present.
//
// Note: Normally we'd just add this unconditionally, but it's valid to build
// Swift and use it as a linker without building compiler_rt.
I'm unsure about this history of this comment or why clang
wasn't used originally so it would be good to get more context behind this statement. I'd be happy to work on this change but would possibly need guidance around any linker flags that could not be used directly with clang
/clang++
.
Last but not least, I wanted to know if this change would be welcome by the community.
Thanks for reading !