As I mentioned in my 16 January post, I envisioned a GNU-less Linux distribution, laid out its problems such as toolchain, GTK, “GPL”, etc. And now onto the elephant in the room: toolchain.
The GNU toolchain is the staple of almost every Linux distributions ever existed. GCC is de facto free compiler for many years before its competitors even exist. Many BSD distrbution also have GCC by default (and still does on certain architectures), Apple Mac OS used latest GCC until the latter’s switch to GPLv3, leading to Apple (and the rest of BSDs, IIRC) staying on GCC 4.2.1 (the last GCC under GPLv2) and Clang’s launch.
And that’s just the compiler. We have GNU Make, de facto Make program
in Linux distributions, binutils (as
, ld
, et al.), glibc (probably
the only reason we have -gnu
in a Linux triplet), bison (GNU yacc,
basically), and GNU m4. There’s also GNU debugger and GNU autotools but
we’re going into those later on. The current elephants in the room are
GCC, glibc, and binutils.
Since I’m writing this assuming that one already use musl-libc instead of GNU libc, additional steps (which probably would include cross-compiling) would be needed if you’re from GNU libc.
The replacement
So, if I’m going to replace the GNU toolchain, I need replacements. What are the viable competitors to the GNU toolchain? Obviously, Clang is the answer to GCC, and it has been tested with the Linux kernel, but what else?
- GNU binutils
GNU binutils are composed of tools, such as linked (ld
), assembler (as
), basically managing binaries. - GCC libstdc++
The C++ standard library. I don’t know why is this part of GCC and not its own project, at least include it with glibc so it can be GNU Library Collection. - GCC libgcc
Some things are are inside this library, basically unwinder, compiler runtime library.
There are many more parts of GCC I haven’t mentioned yet (such as
libatomic
and libgomp
), but that’s for later time. The current LLVM
stack I’m building don’t need this, yet.
So, how do I replace this? Good news is, as of 9.0+, the LLVM stack has all of these. So I only need one component to rule them all.
GNU binutils will be replaced by LLVM tools, which has all of
binutils
except ld
, which would be handled by LLVM lld instead. GCC
libstdc++ is going to be replaced by LLVM libc++, and GCC libgcc_s is
going to be replaced by two different components, LLVM libunwind and
LLVM compiler-rt.
Actually building it
NOTE: This is what I could do when I first time built these bootstraps, I might have a better way since then. these steps are from my kiss-llvm repo.
As of 9.0.1, you can’t just throw all LLVM-related tarballs and compile it through gcc - it won’t work. You’ll need to build a temporary LLVM stack before you can make a unified LLVM build. Since I’m using KISS Linux as my host, I’ll be using their packaging format.
First step is building LLVM by itself, packaged as @llvm-pass1
. To
make sure our temporary LLVM doesn’t link to GNU ncurses, libedit (not
part of GNU), et al., we can add -DLLVM_ENABLE_LIBEDIT=OFF
to reduce
dependencies on stray libraries further, such aslibxml2
, we can add
-DLLVM_ENABLE_LIBXML2=OFF
. This way, we can get an LLVM binary which
only links to the C library and C++ library. In current case, musl and
libstdc++.
After LLVM, we build clang. It’s necessary to build Clang before compiler-rt since my experience building compiler-rt with gcc has been, less than stellar. If I tried to compile something with compiler-rt build with gcc, it’ll segfault. So I’m doing it the other way around (build clang before compiler-rt). The options are just building and linking the dynamic library, and turning off the examples, documentations, tests, and disable static build.
After clang, we build compiler-rt, packaged as @compiler-rt
.
Since we’re building on a musl-libc, we need to modify the CMake config
a little bit. Make sure we build it with Clang and Clang++ (thanks to
Void Linux).
sed -i 's/set(COMPILER_RT_HAS_SANITIZER_COMMON TRUE)/set(COMPILER_RT_HAS_SANITIZER_COMMON FALSE)/' cmake/config-ix.cmake
The next part involves libunwind, packaged as @llvm-libunwind
. As
usual, we build it with clang and clang++, but add a flag
-DLIBUNWIND_USE_COMPILER_RT=ON
, so libunwind is built with
compiler_rt instead of libgcc.
After that, we’re building libc++abi, packaged as @libc++abi
, the
low-level support for libc++. We still fetch libc++ there since we
need the libc++ headers. Make sure it uses both LLVM libunwind and
compiler-rt by adding the flags -DLIBCXXABI_USE_LLVM_UNWINDER=ON
and -DLIBCXXABI_USE_COMPILER_RT=ON
.
After building libc++, I usually did a test with Pass 1 clang first.
I tested it by building a simple C++ hello world program, and ldd
’d
it. If it returns libc++
, libunwind
, and libc++abi
, then the
it’s successful. Time for LLVM pass 2.
We’ve come to the Pass 2 build of our temporary LLVM build, this time,
we force LLVM to use libc++ we built earlier. (Note: building @lld
and
llvm
(in Clang, as GCC doesn’t support these options) with
--rtlib=compiler-rt
and --stdlib=libc++
as CXXFLAGS might be
sufficient, I haven’t tried this.), and then Pass 2 Clang. We still
build it from GCC (since Clang won’t be able to link with the pass 2
LLVM library) with libgcc, but this time, we link it with libc++.
Last but not least, @lld
, the LLVM linker. Not much to tell it
apart of telling it to use clang.
Well, that’s it. We can build the full unified llvm
package. Compiled
with Clang, linked with ld.lld
. It’ll link with the proper compiler-rt
and libc++ libraries. You have a full LLVM toolchain, GNU-free.
Next time, I want to look into other GNU components, if it can be replaced at all. Maybe later.