Using clang on macOS to compile g++ / libstdc++ compatible binaries

clang++ by default compiles for system “native” C++ library. On macOS, that is LLVM’s libc++. But what happens if happen to have a lot of binaries (on in my case) libraries compiled with g++ using its own libdstdc++ library?

Why was I doing that in the first place? Work stuff is mostly Linux and therefore uses gcc, and the Apple-provided clang that ships with doesn’t support OpenMP, so I ended up building a lot of stuff with a homebrew-sourced gcc. But it turns out that gcc’s OpenMP support on macOS (in particular the implementation of the synchronization primitives in libgomp) is surprisingly slow (on Linux it’s much better). I ended up with a piece of OpenMP code whose performance was so bad, I need to cross check what the cause was, and so I installed a (non-Apple) clang with which I wanted to compile the code in question, but preferably without having to rebuild all my gcc-compiled (and therefore libstdc++-using) dependencies.

The following seems to work for a homebrew-installed gcc 9.3 and llvm (clang) 10:

CXXFLAGS="-stdlib=libstdc++ -stdlib++-isystem /usr/local/Cellar/gcc/9.3.0_1/include/c++/9.3.0/ -cxx-isystem /usr/local/Cellar/gcc/9.3.0_1/include/c++/9.3.0/x86_64-apple-darwin19"
LDFLAGS="-stdlib=libstdc++ -L /usr/local/Cellar/gcc/9.3.0_1/lib/gcc/9 -L /usr/local/opt/llvm/lib"

The last part of the link flags that add the library path back to the homebrew clang are only required so that that clang‘s OpenMP support can find its libomp support library.

This works unless you used something like libstdc++‘s implementation of std::call_once, because that needs internal symbols that are declared as __thread, which with how homebrew’s gcc is configurationed specifically or gcc on Darwin behaves generally used “emulated thread-local storage” (tls), which in turn requires mangling that __thread declared symbol differently 😔. clang even knows about that (see -femulated-tls), but when I tried to use that variant (which did indeed link without complaint) I got weird malloc errors in emutls functions, and it was easier for me to replace std::call_once with a hack than figuring out where those errors came from.

Leave a Reply

Your email address will not be published. Required fields are marked *