---
# System prepended metadata

title: LTOでのインライン展開
tags: [elkurin-daily-note, C++-General]

---

# LTOでのインライン展開

[base::debug::Alias](/Nr6YGOpET_qTEBYPAkTUtw)はリンク時の最適化で変数が消滅することを避けるためのクラスだった。  
変数が使われていないように見えても、Stack Traceで見たかったりログ・デバッグ目的で残っててほしいことは多い。そんなときに`Alias(var)`とすると`var`が消えないようになるというクラスだった。

ところで、LTOでどういうときに関数が消えるのか実際にみてみようと思う。  

## LTOを使うには
今回使用するのはGNU(g++)コンパイラ。  
普通にコンパイルするとLTOはenableされていない。  
そこで[`-flto`](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html)というオプションを追加すると最適化が走るようになる。

```
gcc -c -O2 -flto foo.c
gcc -c -O2 -flto bar.c
gcc -o myprog -flto -O2 foo.o bar.o
```
`-c`はオブジェクトファイルを生成するためのコマンド。これがないとシンボル解決までしようとして失敗する。  
上記のように各オブジェクトファイルをそれぞれ作っていくが、この際同じオプションを使うことが推奨されている。  

[Optimization Options](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html)の中には他にもLTO関連のフラグがあるが、今回は`-flto`に絞って確認する。

## やってみた
今回使用するサンプルコードは以下。
```cpp
// main.cc
void Alias(const void* var);

int main(void) {
  int a = 1;
  Alias(&a);
  return 0;
}


// alias.cc
void Alias(const void* var) {}
```
観てわかるとおり、Aliasの中身はないので、最適化でAliasの呼び出しそのものが消えるのが予想。

### LTOなし
まずはLTOなしバージョンを確認する。  
以下のコマンドでコンパイルしてみる。
```
g++ -c -O2 main.cc
g++ -c -O2 alias.cc
g++ -O2 main.o alias.o
```

これでできた`a.out`に対して解析をかける。  
オブジェクトファイルや実行ファイルの中身は`objdump -d`で確認することができる。  
`objdump -d a.out`の結果を見てみると実際にAliasがいた。

以下mainとAliasの中だけ抜粋。
```
0000000000001040 <main>:
    1040:	48 83 ec 18          	sub    $0x18,%rsp
    1044:	48 8d 7c 24 0c       	lea    0xc(%rsp),%rdi
    1049:	c7 44 24 0c 01 00 00 	movl   $0x1,0xc(%rsp)
    1050:	00 
    1051:	e8 fa 00 00 00       	call   1150 <_Z5AliasPKv>
    1056:	31 c0                	xor    %eax,%eax
    1058:	48 83 c4 18          	add    $0x18,%rsp
    105c:	c3                   	ret
    105d:	0f 1f 00             	nopl   (%rax)
    
...

0000000000001150 <_Z5AliasPKv>:
    1150:	c3                   	ret


```
`_Z5AliasPKv`がAlias関数のシンボル名で、mainの`call`命令で呼び出されている。

ちなみに各サイズは
```
-rw-r--r-- 1 elkurin primarygroup    31 Oct 12 21:38 alias.cc
-rw-r--r-- 1 elkurin primarygroup  1080 Oct 12 21:40 alias.o
-rwxr-xr-x 1 elkurin primarygroup 15896 Oct 12 21:40 a.out
-rw-r--r-- 1 elkurin primarygroup    87 Oct 12 21:39 main.cc
-rw-r--r-- 1 elkurin primarygroup  1312 Oct 12 21:39 main.o
```

### -fltoつき
以下のコマンドでコンパイルしてみる。
```
g++ -c -O2 -flto main.cc
g++ -c -O2 -flto alias.cc
g++ -O2 -flto main.o alias.o
```

先程と同様に`objdump -d a.out`の結果を見てみると全然変わった。
```
0000000000001040 <main>:
    1040:	31 c0                	xor    %eax,%eax
    1042:	c3                   	ret
    1043:	66 2e 0f 1f 84 00 00 	cs nopw 0x0(%rax,%rax,1)
    104a:	00 00 00 
    104d:	0f 1f 00             	nopl   (%rax)
```
mainの中身は小さく、またAliasシンボルは消滅した。

ちなみに各サイズは
```
-rw-r--r-- 1 elkurin primarygroup    31 Oct 12 21:38 alias.cc
-rw-r--r-- 1 elkurin primarygroup  3536 Oct 12 21:47 alias.o
-rwxr-xr-x 1 elkurin primarygroup 15800 Oct 12 21:47 a.out
-rw-r--r-- 1 elkurin primarygroup    87 Oct 12 21:39 main.cc
-rw-r--r-- 1 elkurin primarygroup  3784 Oct 12 21:47 main.o
```

## オブジェクトファイルのサイズについて
前のセクションの結果から、LTOなしのセクションと比べるとLTOありではオブジェクトファイルがめっちゃデカくなっていることがわかる。  

なんでデカくなっているのか調べるためにオブジェクトファイルのセクションを見てみる。  
セクションは`objdump -x`で列挙してくれる。

まずはLTOなし。
```
$ objdump -x main.o

...

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000000  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000040  2**0
                  ALLOC
  3 .text.startup 0000001d  0000000000000000  0000000000000000  00000040  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  4 .comment      0000001f  0000000000000000  0000000000000000  0000005d  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  0000007c  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000030  0000000000000000  0000000000000000  00000080  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 main.cc
0000000000000000 l    d  .text.startup	0000000000000000 .text.startup
0000000000000000 g     F .text.startup	000000000000001d main
0000000000000000         *UND*	0000000000000000 _Z5AliasPKv

...
```
ついでにSYMBOL TABLEを確認するとたしかに`_Z5AliasPKv`がいた。

次にLTOあり。

```
$ objdump -x main.o

...

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000000  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000040  2**0
                  ALLOC
  3 .gnu.lto_.profile.b135ff34c05375c1 0000000f  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, READONLY, EXCLUDE
  4 .gnu.lto_.icf.b135ff34c05375c1 00000022  0000000000000000  0000000000000000  0000004f  2**0
                  CONTENTS, READONLY, EXCLUDE
  5 .gnu.lto_.ipa_sra.b135ff34c05375c1 0000001c  0000000000000000  0000000000000000  00000071  2**0
                  CONTENTS, READONLY, EXCLUDE
  6 .gnu.lto_.inline.b135ff34c05375c1 0000003e  0000000000000000  0000000000000000  0000008d  2**0
                  CONTENTS, READONLY, EXCLUDE
  7 .gnu.lto_.jmpfuncs.b135ff34c05375c1 00000037  0000000000000000  0000000000000000  000000cb  2**0
                  CONTENTS, READONLY, EXCLUDE
  8 .gnu.lto_.pureconst.b135ff34c05375c1 00000011  0000000000000000  0000000000000000  00000102  2**0
                  CONTENTS, READONLY, EXCLUDE
  9 .gnu.lto_.ipa_modref.b135ff34c05375c1 00000015  0000000000000000  0000000000000000  00000113  2**0
                  CONTENTS, READONLY, EXCLUDE
 10 .gnu.lto_.lto.b135ff34c05375c1 00000008  0000000000000000  0000000000000000  00000128  2**0
                  CONTENTS, READONLY, EXCLUDE
 11 .gnu.lto_main.0.b135ff34c05375c1 00000121  0000000000000000  0000000000000000  00000130  2**0
                  CONTENTS, READONLY, EXCLUDE
 12 .gnu.lto_.symbol_nodes.b135ff34c05375c1 0000004a  0000000000000000  0000000000000000  00000251  2**0
                  CONTENTS, READONLY, EXCLUDE
 13 .gnu.lto_.refs.b135ff34c05375c1 0000000e  0000000000000000  0000000000000000  0000029b  2**0
                  CONTENTS, READONLY, EXCLUDE
 14 .gnu.lto_.decls.b135ff34c05375c1 000002a2  0000000000000000  0000000000000000  000002a9  2**0
                  CONTENTS, READONLY, EXCLUDE
 15 .gnu.lto_.symtab.b135ff34c05375c1 0000002f  0000000000000000  0000000000000000  0000054b  2**0
                  CONTENTS, READONLY, EXCLUDE
 16 .gnu.lto_.ext_symtab.b135ff34c05375c1 00000005  0000000000000000  0000000000000000  0000057a  2**0
                  CONTENTS, READONLY, EXCLUDE
 17 .gnu.lto_.opts 0000008a  0000000000000000  0000000000000000  0000057f  2**0
                  CONTENTS, READONLY, EXCLUDE
 18 .comment      0000001f  0000000000000000  0000000000000000  00000609  2**0
                  CONTENTS, READONLY
 19 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00000628  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 main.cc
0000000000000001       O *COM*	0000000000000001 __gnu_lto_slim
```

LTOありの方ではなんか `.gnu.lto_hogehoge`というどう見てもLTOに使われそうなセクションがいっぱいある。  
GNUドキュメントの[LTO file sections](https://gcc.gnu.org/onlinedocs/gccint/LTO-object-file-layout.html)項目を見てみると、やはりこれらはLTOのための情報が書いてあるらしい。  
例えば.gnu.lto_.symtabはELFのシンボルテーブルをreplaceしているらしい。  

今回は各セクションの役割やどう使うかの詳細は追わないが、サイズの違いの理由はLTOに使用するための謎セクションがたくさんあることに起因するっぽい。  
なので、LTOをしたい場合は実行ファイルを生成するときだけでなくオブジェクトファイルを生成するときから`-flto`のオプションをつけておかないと期待通りに動かない。

なお、最終的に生成される実行ファイル`a.out`については、Aliasなどがいなくなった分か少しだけ小さくなっている。


## NOINLINE
最後にNOINLINE attributeをつけてみる。

[NOINLINE](https://source.chromium.org/chromium/chromium/src/+/main:base/compiler_specific.h;l=45;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771)はChromium上でのマクロで、中身は`noinline`アトリビュート。  
名前の通り、インライン展開しないでっていう指示をコンパイラに出している。

```cpp
// alias.cc
__attribute__((noinline)) void Alias(const void* var) {}
```

alias.ccをこのようにupdateしてもう一度`-flto`つきでコンパイルしてみる。

```
0000000000001040 <main>:
    1040:	31 c0                	xor    %eax,%eax
    1042:	c3                   	ret
    1043:	66 2e 0f 1f 84 00 00 	cs nopw 0x0(%rax,%rax,1)
    104a:	00 00 00 
    104d:	0f 1f 00             	nopl   (%rax)

...

0000000000001140 <_Z5AliasPKv.isra.0>:
    1140:	c3                   	ret
```

Aliasシンボルは生きてるけどmainの中にはおらん。  
ということはAliasに渡した引数も消えちゃう？

base::debug::Aliasにはこんなコメントが書いてある。
```cpp
// This file/function should be excluded from LTO/LTCG to ensure that the
// compiler can't see this function's implementation when compiling calls to it.
NOINLINE void Alias(const void* var) {}
```
今のままだとLTOで消えているように見える。残っていてほしいのはAliasではなく`var`として渡したものなので。

今回はChromiumとは違うコンパイラを使っているので、もしかしたらコンパイラ差かもしれない。あるいはどこかでLTOから外すようなコードがあるのかもしれない。  
TODO(everyone): investigate
