Ubuntu で clang + libc++ を使う

## これはなに

Ubuntu 上の clang で C++ を使うときに (GNU Toolchain の) libstdc++ ではなく (LLVM の) libc++ を使う、という話です。
「それって当たり前だよね」と思うかもしれませんが、実はそうでもないのです。

ついでに、その他の runtime library の切り替えについても調べたことを書いておきます。

このあたりに関する公式ドキュメントはこちら。
https://clang.llvm.org/docs/Toolchain.html#runtime-libraries

## 環境

Ubuntu 18.04 に [LLVM の標準の手順](https://apt.llvm.org/) でインストールしたものです。

```
bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
```

```
$ clang++-10 --version
clang version 10.0.1-++20200507062652+bab8d1790a3-1~exp1~20200507163249.158
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
```

## 指定なし

簡単なサンプルプログラムをコンパイルします。(例外が発生しうるコードにしてあります)

```c++
#include <cstdio>
#include <string>
int main() { puts(std::string("hello").c_str()); }
```

コンパイル・実行します。

```
$ clang++-10 test.cc
$ ./a.out
hello
```

何も不思議はないですね。 `-v` をつけてみます。

```

$ clang++-10 -v test.cc
clang version 10.0.1-++20200507062652+bab8d1790a3-1~exp1~20200507163249.158
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/8
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7.5.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/8
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0
Candidate multilib: .;@m64
Selected multilib: .;@m64
 "/usr/lib/llvm-10/bin/clang" -cc1 -triple x86_64-pc-linux-gnu -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name test.cc -mrelocation-model static -mthread-model posix -mframe-pointer=all -fmath-errno -fno-rounding-math -masm-verbose -mconstructor-aliases -munwind-tables -target-cpu x86-64 -dwarf-column-info -fno-split-dwarf-inlining -debugger-tuning=gdb -v -resource-dir /usr/lib/llvm-10/lib/clang/10.0.1 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/x86_64-linux-gnu/c++/7.5.0 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/x86_64-linux-gnu/c++/7.5.0 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/backward -internal-isystem /usr/local/include -internal-isystem /usr/lib/llvm-10/lib/clang/10.0.1/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fdeprecated-macro -fdebug-compilation-dir /home/ohta/projects/hagoromo/hgrm.tools.ci/generic/config/docker -ferror-limit 19 -fmessage-length 0 -fgnuc-version=4.2.1 -fobjc-runtime=gcc -fcxx-exceptions -fexceptions -fdiagnostics-show-option -fcolor-diagnostics -faddrsig -o /tmp/test-593447.o -x c++ test.cc
clang -cc1 version 10.0.1 based upon LLVM 10.0.1 default target x86_64-pc-linux-gnu
ignoring nonexistent directory "/include"
ignoring duplicate directory "/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/x86_64-linux-gnu/c++/7.5.0"
#include "..." search starts here:
#include <...> search starts here:
 /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0
 /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/x86_64-linux-gnu/c++/7.5.0
 /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/backward
 /usr/local/include
 /usr/lib/llvm-10/lib/clang/10.0.1/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
 "/usr/bin/ld" -z relro --hash-style=gnu --build-id --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o a.out /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/crt1.o /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/crti.o /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/crtbegin.o -L/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0 -L/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../.. -L/usr/lib/llvm-10/bin/../lib -L/lib -L/usr/lib /tmp/test-593447.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/crtend.o /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/crtn.o
```

注目は最後のリンカを起動している行です。
 `-lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc` と、 libc++ ではなく libstdc++ がリンクされていることがわかります。

## -stdlib=libc++

`-stdlib=libc++` をつけると libc++ を使ってくれる、はずです。

```
$ clang++-10 -stdlib=libc++ test.cc
test.cc:1:10: fatal error: 'cstdio' file not found
#include <cstdio>
         ^~~~~~~~
1 error generated.
```

が、エラーになってしまいました。どうやら、標準のインストール方法では libc++ はインストールされないようです。

インストールします。

```
# apt install -y libc++-10-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  libc++1-10 libc++abi1-10
Suggested packages:
  clang
The following NEW packages will be installed:
  libc++-10-dev libc++1-10 libc++abi1-10
0 upgraded, 3 newly installed, 0 to remove and 9 not upgraded.
```

`libc++abi1-10` というパッケージも入ったようです。

コンパイルをやりなおしてみます。

```
$ clang++-10 -stdlib=libc++ test.cc
/usr/bin/ld: cannot find -lc++abi
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

エラーが変わりました。 libc++abi が見つけられていない。インストールされてたのに。

```
$ ls -l /usr/lib/x86_64-linux-gnu/libc++*
lrwxrwxrwx 1 root root 23 May  7 16:32 /usr/lib/x86_64-linux-gnu/libc++.a -> ../llvm-10/lib/libc++.a
lrwxrwxrwx 1 root root 24 May  7 16:32 /usr/lib/x86_64-linux-gnu/libc++.so -> ../llvm-10/lib/libc++.so
lrwxrwxrwx 1 root root 13 May  7 16:32 /usr/lib/x86_64-linux-gnu/libc++.so.1 -> libc++.so.1.0
lrwxrwxrwx 1 root root 28 May  7 16:32 /usr/lib/x86_64-linux-gnu/libc++.so.1.0 -> ../llvm-10/lib/libc++.so.1.0
lrwxrwxrwx 1 root root 16 May  7 16:32 /usr/lib/x86_64-linux-gnu/libc++abi.so.1 -> libc++abi.so.1.0
lrwxrwxrwx 1 root root 31 May  7 16:32 /usr/lib/x86_64-linux-gnu/libc++abi.so.1.0 -> ../llvm-10/lib/libc++abi.so.1.0
```

libc++abi.so が存在していないので、 symlink してみます。

```
$ cd usr/lib/x86_64-linux-gnu
$ sudo ln -s libc++abi.so.1 libc++abi.so
```

これで通るようになりました。(これでいいのかなぁ?)

```
$ clang++-10 -stdlib=libc++ test.cc
$ ./a.out
hello
```

`-v` をつけてリンク部分を見てみます。

```
-lc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc 
```

なるほど、確かに libstdc++ ではなく libc++ を使っているようです。

## -rtlib=compiler-rt

しかし、 compiler runtime library として LLVM の [compiler-rt](https://compiler-rt.llvm.org/) は使われていないようです。せっかくなのでこれも LLVM のものを使いたい。

```
$ clang++-10 -v -stdlib=libc++ -rtlib=compiler-rt test.cc
```

すると確かにリンクオプションが

```
-lc++ -lm /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/libclang_rt.builtins-x86_64.a -lc /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/libclang_rt.builtins-x86_64.a /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/clang_rt.crtend-x86_64.o 
```

のように変わり、 compiler-rt を使ってくれているようです。
ところが以下のようなリンクエラーとなります。

```
/tmp/test-2ede37.o: In function `main':
test.cc:(.text+0x59): undefined reference to `_Unwind_Resume'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libc++abi.so: undefined reference to `_Unwind_RaiseException'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libc++abi.so: undefined reference to `_Unwind_GetLanguageSpecificData'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libc++abi.so: undefined reference to `_Unwind_DeleteException'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libc++abi.so: undefined reference to `_Unwind_SetIP'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libc++abi.so: undefined reference to `_Unwind_GetRegionStart'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libc++abi.so: undefined reference to `_Unwind_SetGR'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libc++abi.so: undefined reference to `_Unwind_GetIP'
```

## -unwindlib=libgcc

どうやら、unwind library (https://clang.llvm.org/docs/Toolchain.html#unwind-library に説明がある ) がリンクできていないようです。確かにリンクオプションにもそれっぽい指定がない。

上記ドキュメントにも指定の方法の説明がなく、ここでハマったので LLVM のソースコードを見てみました。

https://github.com/llvm/llvm-project/blob/17e13da29de48e10fa4ae50831e781d9c44edde9/clang/lib/Driver/ToolChain.cpp#L791-L816
https://github.com/llvm/llvm-project/blob/2da89df4e8d240160d5f265ecac4b0c6a0705d65/clang/lib/Driver/ToolChains/CommonArgs.cpp#L1218-L1234

どうやら、  `-unwindlib` というオプションに `libgcc` とか `libunwind` を指定すればよいようです。
(`clang++ --help` を見てみたらあっさりとした説明がありました: `-unwindlib=<value> Unwind library to use`)

せっかくなので [LLVM な libunwind](https://clang.llvm.org/docs/Toolchain.html#libunwind-llvm) を使いたいところですが、どうも LLVM な apt repository では提供されていないように見えます。
仕方がないのでここでは  [libgcc_s (GNU)](https://clang.llvm.org/docs/Toolchain.html#id2) を使うことにしました。

```
$ clang++-10 -stdlib=libc++ -rtlib=compiler-rt -unwindlib=libgcc test.cc
$ ./a.out
hello
```

ようやく通りました。
リンクオプションはこんな感じ (一部抜粋)。 `-lgcc_s` が増えています。

```
-lc++ -lm /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/libclang_rt.builtins-x86_64.a -lgcc_s -lc /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/libclang_rt.builtins-x86_64.a -lgcc_s /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/clang_rt.crtend-x86_64.o 
```

全体も載せておこう。
```
 "/usr/bin/ld" -z relro --hash-style=gnu --build-id --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o a.out /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/crt1.o /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/crti.o /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/clang_rt.crtbegin-x86_64.o -L/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0 -L/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../.. -L/usr/lib/llvm-10/bin/../lib -L/lib -L/usr/lib /tmp/test-91de86.o -lc++ -lm /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/libclang_rt.builtins-x86_64.a -lgcc_s -lc /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/libclang_rt.builtins-x86_64.a -lgcc_s /usr/lib/llvm-10/lib/clang/10.0.1/lib/linux/clang_rt.crtend-x86_64.o /usr/bin/../lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/crtn.o
```

## まとめ

- (少なくとも LLVM 公式手順で Ubuntu にインストールした) clang++ はデフォルトでは libc++ ではなく libstdc++ を使う
- runtime library の切り替えは `-stdlib` / `-rtlib` / `-unwindlib` オプションで行う
- LLVM 公式手順で Ubuntu にインストールするだけだと libc++ を使うのは意外にてこずります

### Ubuntu で clang と libc++ を使う手順

-  [LLVM の標準の手順](https://apt.llvm.org/)  でインストール
- `apt install -y libc++-10-dev`
- `cd usr/lib/x86_64-linux-gnu && sudo ln -s libc++abi.so.1 libc++abi.so`

としたうえで、

- libc++ だけを使う: `-stdlib=libc++`
- compiler-rt も使う: `-stdlib=libc++ -rtlib=compiler-rt -unwindlib=libgcc`
- LLVM libunwind も使う: (これは今回試してない)

とします。