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()
すら消滅してしまい、スタートアップルーチンから直接引数 128
で exit()
を呼びだし、プログラムを終了するコードになっています。
$ 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 オプションを付けずにコンパイルしています。そのためスタートアップ・ファイル内の関数はそのまま最適化されずに残っています。