MSYS2上のclang, llvm, lld, gccのインストールサイズを減らす

# ディスク使用状況

MSYS2 上に、clang や llvm, lld, gcc などのツールチェインをインストールすると、意外とディスク容量を消費することに気づくでしょう。

特に、clang などは 1 つの実行ファイルが 100MB 近くもあり、それが複数あることが分かります。

```
$ ls -l /mingw{32,64}/bin/clang{,++,-cl,-cpp}.exe
-rwxr-xr-x 1 user group 98M 10月 11 05:35 /mingw32/bin/clang.exe*
-rwxr-xr-x 1 user group 98M 10月 11 05:35 /mingw32/bin/clang++.exe*
-rwxr-xr-x 1 user group 98M 10月 11 05:35 /mingw32/bin/clang-cl.exe*
-rwxr-xr-x 1 user group 98M 10月 11 05:35 /mingw32/bin/clang-cpp.exe*
-rwxr-xr-x 1 user group 92M 10月 11 01:47 /mingw64/bin/clang.exe*
-rwxr-xr-x 1 user group 92M 10月 11 01:47 /mingw64/bin/clang++.exe*
-rwxr-xr-x 1 user group 92M 10月 11 01:47 /mingw64/bin/clang-cl.exe*
-rwxr-xr-x 1 user group 92M 10月 11 01:47 /mingw64/bin/clang-cpp.exe*
```

lld もかなりファイルサイズが大きいです。

```
$ ls -l /mingw{32,64}/bin/{*lld*,wasm-ld}.exe
-rwxr-xr-x 1 user group 66M 10月 11 05:35 /mingw32/bin/ld.lld.exe*
-rwxr-xr-x 1 user group 66M 10月 11 05:35 /mingw32/bin/ld64.lld.exe*
-rwxr-xr-x 1 user group 66M 10月 11 05:35 /mingw32/bin/lld.exe*
-rwxr-xr-x 1 user group 66M 10月 11 05:35 /mingw32/bin/lld-link.exe*
-rwxr-xr-x 1 user group 66M 10月 11 05:35 /mingw32/bin/wasm-ld.exe*
-rwxr-xr-x 1 user group 63M 10月 11 01:47 /mingw64/bin/ld.lld.exe*
-rwxr-xr-x 1 user group 63M 10月 11 01:47 /mingw64/bin/ld64.lld.exe*
-rwxr-xr-x 1 user group 63M 10月 11 01:47 /mingw64/bin/lld.exe*
-rwxr-xr-x 1 user group 63M 10月 11 01:47 /mingw64/bin/lld-link.exe*
-rwxr-xr-x 1 user group 63M 10月 11 01:47 /mingw64/bin/wasm-ld.exe*
```

# ディスク使用量を減らすには？

よく見ると、同じアーキテクチャ用の実行ファイルは同じファイルサイズであることに気づきます。そこで、diff コマンド等を使ってファイルを比較してみると、`clang.exe`, `clang++.exe`, `clang-cl.exe`, `clang-cpp.exe` の 4 つのファイルは完全に一致することが分かります。

Unix 系のコマンドでは、実行ファイル名を変更すると、それに応じて動作が変わるものがよくありますが、実は clang もそうなのです。こんな時 Unix では、本体の実行ファイルは 1 つだけにし、シンボリックリンクで別名のファイルを作ることで容量を抑えることができます。
一方 Windows では、シンボリックリンクの作成には権限が必要な場合があったりして、あまり使い勝手がよくありません。仕方なく、同一ファイルの別名コピーを作ることになり、容量を無駄に消費してしまいます。

シンボリックリンクを使わずに、容量を抑える方法は何かないでしょうか？
実はあります。ハードリンクを使えばよいのです。ハードリンクは、Windows の WinSxS フォルダのサイズ削減にも使われていることが知られています。これを使って同一内容のファイル同士をリンクしてサイズを削減してみましょう。

# スクリプト

手動で一々ファイルの中身が同一かを確認してからハードリンクを作るのは大変なので、スクリプトを作ってみました。

<https://gist.github.com/k-takata/cf7ad1244475ce6af04705d045e68262>

適当なディレクトリにスクリプトを保存して、 `./ln-mingw.sh` を実行してください。

```
$ ./ln-mingw.sh
Directory: /mingw32/i686-w64-mingw32/bin
Linking ld.exe to ld.bfd.exe
Linking ../../bin/ar.exe to ar.exe
Linking ../../bin/as.exe to as.exe
Linking ../../bin/dlltool.exe to dlltool.exe
Linking ../../bin/ld.bfd.exe to ld.bfd.exe
Linking ../../bin/ld.exe to ld.exe
Linking ../../bin/ld.gold.exe to ld.gold.exe
Linking ../../bin/nm.exe to nm.exe
Linking ../../bin/objcopy.exe to objcopy.exe
Linking ../../bin/objdump.exe to objdump.exe
Linking ../../bin/ranlib.exe to ranlib.exe
Linking ../../bin/readelf.exe to readelf.exe
Linking ../../bin/strip.exe to strip.exe
Directory: /mingw32/bin
Linking clang++.exe to clang.exe
Linking clang-cl.exe to clang.exe
Linking clang-cpp.exe to clang.exe
Linking ld.lld.exe to lld.exe
...
```

このように表示され、同一の候補のファイルを比較し、実際にファイル内容が一致すれば、ハードリンクに置き換えます。
置き換える候補は、clang, llvm, lld, gcc の 1MB 以上の実行ファイルを対象にしています。（それ以外のファイルは元々のサイズが小さく、効果が低いので対象外としています。）

# 実行結果

スクリプトを実行後、もう一度 clang の実行ファイルを見てみましょう。

```
$ ls -l /mingw{32,64}/bin/clang{,++,-cl,-cpp}.exe
-rwxr-xr-x 4 user group 98M 10月 11 05:35 /mingw32/bin/clang.exe*
-rwxr-xr-x 4 user group 98M 10月 11 05:35 /mingw32/bin/clang++.exe*
-rwxr-xr-x 4 user group 98M 10月 11 05:35 /mingw32/bin/clang-cl.exe*
-rwxr-xr-x 4 user group 98M 10月 11 05:35 /mingw32/bin/clang-cpp.exe*
-rwxr-xr-x 4 user group 92M 10月 11 01:47 /mingw64/bin/clang.exe*
-rwxr-xr-x 4 user group 92M 10月 11 01:47 /mingw64/bin/clang++.exe*
-rwxr-xr-x 4 user group 92M 10月 11 01:47 /mingw64/bin/clang-cl.exe*
-rwxr-xr-x 4 user group 92M 10月 11 01:47 /mingw64/bin/clang-cpp.exe*
           ^
```

`ls -l` の実行結果の 2 列目はハードリンクの数を示していますが、これが先ほどの 1 から 4 に増えていることが分かります。つまりこれは、これらのファイルのディスク使用量が 1/4 になったことを示しています。

私の環境では、このスクリプトの実行で、1.4GB ほどのディスク使用量の削減に成功しました。（gcc のみをインストールしている環境ではこれほどの効果は出ないでしょう。）

# 注意

clang, llvm, lld, gcc を更新や再インストールした場合にはリンクが切れて普通のファイルに戻ってしまうので、本スクリプトを再度実行する必要があります。

# 今後の展望

MSYS2 の git は実際にハードリンクを使ってインストールサイズを削減しています。clang などでもパッケージの作成時にハードリンクを使うようにすれば、ユーザー側で対処をする必要はなくなるかもしれません。

**追記**
実際に、clang/llvm/lld パッケージに対する PR を出してみました。
<https://github.com/msys2/MINGW-packages/pull/5970>

これがうまくいけば binutils, gcc でも同じことをするかもしれません。