FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

LLVMのバックエンドを作るための第一歩 (39. 可変長引数で生成されたアセンブリを見てみる)

f:id:msyksphinz:20190425001356p:plain

前回の続き。サンプルプログラムをコンパイルして、どのようなアセンブリ言語が出力されるのか観察する。

  • 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)