Open2
自作Cコンパイラ(C11)に可変長引数対応を実装する
C11規格におけるstdarg.h
(当たり前だが)仕様のみ指定、実装は未指定。
型va_list
可変長引数を格納する型
マクロva_arg
type va_arg(va_list ap, type);
va_list型の変数apから次の引数を型typeとして引き出す。
このマクロは次の引数を指すapの内部ポインタを1つ進める。
(※va_listの中で引数を指す内部実装がポインタとして実装されている必要はない)
マクロva_copy
void va_copy(va_list dest, va_list src);
srcをdestにコピーする。
マクロva_end
void va_end(va_list ap);
apの後処理をしてapを使えなくする。
関数から戻る前にva_endを呼ばなかった場合は未定義動作を起こす。
マクロva_start
void va_start(va_list ap, paramN);
apを初期化する。paramNは可変長引数(...)の直前の引数。
Reference
AMD64におけるstdarg.h
- 可変長引数を取る関数が呼ばれるときは、ベクターレジスタに格納される浮動小数点数引数の数を
%alに格納する。%alの値域は[0,7]なので、8つ以上は -
__m256もしくは__m512が可変長引数として渡されたときは必ずスタックに載せる。一方、名前付きの変数として渡された場合はレジスターに載せられる。
Register Save Area
- 可変長引数を受け取り
va_startマクロを呼ぶことが分かっている関数のプロローグは、register save areaに引数レジスタを保存する。それぞれのレジスタのregister save area内におけるオフセットは以下の表。
| Register | Offset |
|---|---|
| %rdi | 0 |
| %rsi | 8 |
| %rdx | 16 |
| %rcx | 24 |
| %r8 | 32 |
| %r9 | 40 |
| %xmm0 | 48 |
| %xmm1 | 64 |
| ... | |
| %xmm15 | 288 |
- 引数を格納するレジスタだけ保存する必要がある。
- レジスタに格納される引数を全く使わない場合は、register save areaは必要ない。これは一度も
var_argを呼ばないか、名前付き引数が引数レジスタを使い切っている場合。 - プロローグは
%alを参照して不必要なXMMレジスタを保存することを避けなければいけない。特に整数のみのプログラムでXMMユニットの初期化を避けるために必要。
The va_list Type
typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_save_area;
} va_list[1];
The var_start Macro
reg_save_area
register save areaの開始位置へのポインタ。
overflow_arg_area
スタックに載ってる引数を取ってくるのに使うポインタ。
スタックに載ってる最初の引数のアドレスで初期化される。
以降は、スタックに載っている次の引数の位置へと更新されていく。
gp_offset
次の汎用引数レジスタが保存されている箇所へのreg_save_areaからのバイト単位でのオフセットを格納する。
すべてのレジスタを使い切った後は48(= 6 * 8)になる。
fp_offset
次の浮動小数点数引数レジスタが保存されている箇所へのreg_save_areaからのバイト単位でのオフセットを格納する。
すべてのレジスタを使い切った後は304 (= 6 * 8 + 16 * 16)になる。
The va_arg Macro
va_arg(l, type)
-
typeはレジスタに渡されるかどうか判断する。違う場合は7へ。 -
num_gpに必要な汎用レジスタの数を格納する。num_fpに必要な浮動小数点数引数レジスタの数を格納する。 - 引数がレジスタにフィットするかどうか確認する。もし
l->gp_offset > 48 - num_gp * 8かl->fp_offset > 304 - num_fp * 16であれば7へ。 -
l->reg_save_areaからオフセットl->gp_offsetもしくはl->fp_offsetのところからtypeの値を取ってくる。引数が異なるレジスタクラスに渡される場合や、汎用レジスタのために8より大きいアライメントが必要な時、XMMのために16より大きいアライメントが必要な時は、一時的なところへコピーする必要がある。 l->gp_offset += num_gp*8; l->fp_offset += num_fp*16;-
typeの値を返す。 -
typeが8バイトを超える境界を必要とする場合は、l->overflow_arg_areaを16バイト境界へアラインする。 -
l->overflow_arg_areaからtypeの値を取ってくる。 l->overflow_arg_area += sizeof(type)-
l->overflow_arg_areaを8バイト境界へアラインする。 -
typeの値を返す。
movl l->gp_offset, %eax
cmpl $48, %eax
jae stack
leal $8(%rax), %edx
addq l->reg_save_area, %rax
movl %edx, l->gp_offset
jmp fetch
stack: movq l->overflow_arg_area, %rax
leaq 8(%rax), %rdx
movq %rdx, l->overflow_arg_area
fetch: movl (%rax), %eax
Reference