前回の続き。サンプルプログラムをコンパイルして、どのようなアセンブリ言語が出力されるのか観察する。
vararg.cpp
#include <stdarg.h> int sum_i(int amount, ...) { int i = 0; int val = 0; int sum = 0; va_list vl; va_start(vl, amount); for (i = 0; i < amount; i++) { val = va_arg(vl, int); sum += val; } va_end(vl); return sum; } int test_vararg() { int a = sum_i(10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); return a; }
上記のコードをコンパイルして、どのようなコードが生成されているのか見てみる。
./bin/clang -O0 -c vararg.cpp -emit-llvm -o vararg.bc # ABI=LP64でコンパイルした場合 ./bin/llc -stats -debug -target-abi=lp64 -march=myriscvx32 -mcpu=simple32 -enable-MYRISCVX-tail-calls=false -relocation-model=static -filetype=asm vararg.bc -o - # ABI=STACK32でコンパイルした場合 ./bin/llc -stats -debug -target-abi=stack32 -march=myriscvx32 -mcpu=simple32 -enable-MYRISCVX-tail-calls=false -relocation-model=static -filetype=asm vararg.bc -o -
- Caller側
- ABI=LP64の場合
addi x10, zero, 7 # レジスタ経由で入りきらない引数はスタックを経由する。第8引数 sw x10, 0(x2) addi x10, x2, 8 addi x11, zero, 9 # レジスタ経由で入りきらない引数はスタックを経由する。第10引数 sw x11, 0(x10) addi x10, x2, 4 addi x11, zero, 8 # レジスタ経由で入りきらない引数はスタックを経由する。第9引数 sw x11, 0(x10) addi x10, zero, 10 # 引数の数 : amount addi x11, zero, 0 # 第1引数 addi x12, zero, 1 # 第2引数 ... addi x13, zero, 2 addi x14, zero, 3 addi x15, zero, 4 addi x16, zero, 5 addi x17, zero, 6 # レジスタ経由はこれが限界 jal _Z5sum_iiz
レジスタ渡しできる限りはレジスタ経由で渡す。A0-A7までのレジスタを使って引数を渡し、それでも足りないので残りはスタック経由で渡している。
- ABI=STACK32の場合
addi x10, zero, 10 sw x10, 0(x2) addi x10, x2, 40 addi x11, zero, 9 sw x11, 0(x10) addi x10, x2, 36 ... addi x11, zero, 1 sw x11, 0(x10) addi x10, x2, 4 addi x11, zero, 0 sw x11, 0(x10) jal _Z5sum_iiz
見てわかる通り、すべてスタック経由での引数渡しとなっている。
- Callee側
- ABI=LP64の場合
_Z5sum_iiz: .cfi_startproc ... # %bb.0: # %entry addi x2, x2, -64 .cfi_def_cfa_offset 64 sw x17, 60(x2) # 可変引数8をスタックに格納 sw x16, 56(x2) # 可変引数7をスタックに格納 sw x15, 52(x2) # .... sw x14, 48(x2) sw x13, 44(x2) sw x12, 40(x2) sw x11, 36(x2) # 可変引数1をスタックに格納 sw x10, 32(x2) # 引数amountをスタックに格納 addi x10, zero, 0
まず、レジスタ経由で渡した引数をすべてスタックに格納する。もったいないだが、こうすることで以降の処理コードをより簡潔にする。
- ABI=STACK32の場合
_Z5sum_iiz: ... # %bb.0: # %entry addi x2, x2, -32 .cfi_def_cfa_offset 32 sw x10, 28(x2) lw x10, 32(x2) addi x10, zero, 0 ...
引数のスタック退避はない。
続いて、実際の引数の処理に入る。先ほど説明したように、引数のサイズが40バイトを超えない場合はスタックで処理され、それを超える場合はグローバル領域経由で処理される。
# %bb.2: # %for.body # in Loop: Header=BB0_1 Depth=1 addi x10, x2, 0 lw x12, 0(x2) addi x11, zero, 40 sltu x11, x11, x12 bne x11, zero, $BB0_4
続いて、実際の引数の処理に入る。40バイト以内では、vaarg.in_reg
のラベルがついているコードが実行される。
# %bb.3: # %vaarg.in_reg # in Loop: Header=BB0_1 Depth=1 addi x11, x10, 12 lw x11, 0(x11) add x11, x11, x12 addi x12, x12, 8 sw x12, 0(x10) j $BB0_5
最終的に加算が行われるのは以下だ。%vararg.end
ラベル以下が、実際の処理になる。
$BB0_5: # %vaarg.end # in Loop: Header=BB0_1 Depth=1 lw x10, 0(x11) sw x10, 20(x2) lw x10, 20(x2) lw x11, 16(x2) add x10, x11, x10 sw x10, 16(x2)