ThinLTO の例

注釈

以下はユーザーコードに限定されますが、exeGCC でも同様です。また、exeClang では必要な --fat-lto-objects オプションは exeGCC では不要です。

従来のプログラムの最適化では、コンパイル単位をまたぐ最適化はできませんでした。

以下の簡単な例で確認してみます。

// f.h
int test(int x);
// f.c
#include "f.h"

int test(int x) {
  return x + 51;
}
// main.c
#include "f.h"

int main() {
  return test(77); // 77 + 51 = 128 (0x80)
}

以下は exeClang(RV64) s008 の GC ターゲットで、LTO 無しでリンク・実行した例(MSYS2 上で実行)です。

$ source /c/GCC4/RV64/s008/setup-rv64gc.sh
$ clang $CLANG_CFLAGS -Wall -Wextra -g -O2 -ffunction-sections -fdata-sections -c f.c
$ clang $CLANG_CFLAGS -Wall -Wextra -g -O2 -ffunction-sections -fdata-sections -c main.c
$ clang $PTHREAD_STUBS $QEMU_LDFLAGS $CLANG_LDFLAGS -g -Wl,--gc-sections f.o main.o
$ /c/KMC/QEMU_SOLID/RISCV/qemu/qemu-system-riscv64.exe -M virt -cpu rv64 -m 4G -nographic -semihosting -bios none -kernel a.out
$ echo $?
128
$ riscv64-kmc-elf-size.exe a.out
   text    data     bss     dec     hex filename
  10992    6616       0   17608    44c8 a.out

コンパイル単位をまたぐインライン展開はできないので、関数呼び出しになっています。

$ riscv64-kmc-elf-objdump.exe -d a.out
...
80002ac6: ef 00 20 02       jal     0x80002ae8 <main>
80002aca: ef f0 5f cb       jal     0x8000277e <exit>
...
0000000080002ae2 <test>:
80002ae2: 1b 05 35 03       addiw   a0, a0, 0x33
80002ae6: 82 80             ret

0000000080002ae8 <main>:
80002ae8: 13 05 d0 04       li      a0, 0x4d
80002aec: dd bf             j       0x80002ae2 <test>
...

LTO を行うと、コンパイル単位をまたぐインライン展開が行われ、その結果として test() はもちろん、 main() すら消滅してしまい、スタートアップルーチンから直接引数 128exit() を呼びだし、プログラムを終了するコードになっています。

$ clang $CLANG_CFLAGS -Wall -Wextra -g -O2 -ffunction-sections -fdata-sections -flto=thin -ffat-lto-objects -c f.c
$ clang $CLANG_CFLAGS -Wall -Wextra -g -O2 -ffunction-sections -fdata-sections -flto=thin -ffat-lto-objects -c main.c
$ clang $PTHREAD_STUBS $QEMU_LDFLAGS $CLANG_LDFLAGS -g -Wl,--fat-lto-objects -Wl,--gc-sections f.o main.o

(実行結果は同じなので省略)

$ riscv64-kmc-elf-objdump.exe -d a.out
...
80001214: 13 05 00 08       li      a0, 0x80
80001218: ef f0 ff cc       jal     0x80000ee6 <exit>
...

この簡単な例でも .text セクションのサイズが半分以下になっていて、LTO の効果の大きさがわかります。

$ riscv64-kmc-elf-size.exe a.out
   text    data     bss     dec     hex filename
   4816    5856       0   10672    29b0 a.out

注釈

C/C++ の仕様の範囲外( main() 以前)となるスタートアップ・ファイルが LTO の対象になってしまうと様々な問題が発生してしまうので、LTO オプションを付けずにコンパイルしています。そのためスタートアップ・ファイル内の関数はそのまま最適化されずに残っています。