Open2
自作Cコンパイラ(C11)に可変長引数対応を実装する
stdarg.h
C11規格における(当たり前だが)仕様のみ指定、実装は未指定。
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
stdarg.h
AMD64における- 可変長引数を取る関数が呼ばれるときは、ベクターレジスタに格納される浮動小数点数引数の数を
%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ユニットの初期化を避けるために必要。
va_list
Type
The typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_save_area;
} va_list[1];
var_start
Macro
The 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)になる。
va_arg
Macro
The 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