var args のスタック配置 on x86-64

ちょっと調べてみました。複数種類のレジスタ使ってレジスタ渡ししてるから大変だと思うんですよね。

0x7fffeca14960: (nil) 0 0.000000 (RDIの値ではないけど、数値引数を消費してない時はRDIが入るっぽい)
0x7fffeca14968: 0x2 2 0.000000 (RSI)
0x7fffeca14970: 0x3 3 0.000000 (RDX)
0x7fffeca14978: 0x4 4 0.000000 (RCX)
0x7fffeca14980: 0x5 5 0.000000 (R8)
0x7fffeca14988: 0x6 6 0.000000 (R9)
0x7fffeca14990: 0x3fb999999999999a -1717986918 0.100000 (XMM1)
0x7fffeca14998: (nil) 0 0.000000
0x7fffeca149a0: 0x3ff3333333333333 858993459 1.200000 (XMM2)
0x7fffeca149a8: (nil) 0 0.000000
0x7fffeca149b0: 0x4002666666666666 1717986918 2.300000 (XMM3)
0x7fffeca149b8: (nil) 0 0.000000
0x7fffeca149c0: 0x400b333333333333 858993459 3.400000 (XMM4)
0x7fffeca149c8: (nil) 0 0.000000
0x7fffeca149d0: 0x4012000000000000 0 4.500000 (XMM5)
0x7fffeca149d8: (nil) 0 0.000000
0x7fffeca149e0: 0x4016666666666666 1717986918 5.600000 (XMM6)
0x7fffeca149e8: (nil) 0 0.000000
0x7fffeca149f0: 0x401acccccccccccd -858993459 6.700000 (XMM7)
0x7fffeca149f8: (nil) 0 0.000000
0x7fffeca14a00: 0x401f333333333333 858993459 7.800000 (XMM8)
0x7fffeca14a08: (nil) 0 0.000000
0x7fffeca14a10: 0x7fffeca14a50 -324974000 0.000000 stack
0x7fffeca14a10: 0x7fffeca14a50 -324974000 0.000000 stack
0x7fffeca14a18: 0x400792 4196242 0.000000
0x7fffeca14a20: 0x2b4b00000007 7 0.000000 (7th integer argument)
0x7fffeca14a28: 0x8 8 0.000000 (8th integer argument)
0x7fffeca14a30: 0x4021cccccccccccd -858993459 8.900000 (9th floating argument)
0x7fffeca14a38: 0x4022000000000000 0 9.000000 (10th floating argument)
0x7fffeca14a40: 0x3fb999999999999a -1717986918 0.100000

コードはこんなの。

#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>

void showstack() {
    void* v;
    void** start = &v;
    void** p = start;
    void** end = (void**)(((intptr_t)p + 4095) & ~4095);
    for (; p <= end; p++) {
        printf("%p: %p %d %f", p, *p, *(int*)p, *(double*)p);
        void** pp = (void**)*p;
        if (p < pp && pp <= end) {
            printf(" stack");
        }
        puts("");
    }
}

void f(int i, ...) {
    showstack();
}

int main() {
    f(0.0, 0.0, 1, 2, 3, 4, 5, 6, 7, 8,
      0.1, 1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8, 8.9, 9.0);
}

値を適当に眺めた感じ、付近に残った引数の数とかは書いて無さげ。てことはコンパイラが特別な処理せにゃならぬ。

gcc-4.3.0/gcc/config/i386/i386.c を読む。やっぱ結構ややこしいことする必要あるよなぁ。最初の方で

static void
ix86_va_start (tree valist, rtx nextarg)
{
  HOST_WIDE_INT words, n_gpr, n_fpr;
  tree f_gpr, f_fpr, f_ovf, f_sav;
  tree gpr, fpr, ovf, sav, t;
  tree type;

  /* Only 64bit target needs something special.  */
  if (!TARGET_64BIT || TARGET_64BIT_MS_ABI)
    {
      std_expand_builtin_va_start (valist, nextarg);
      return;
    }

とかしてるので、 i386 ならまぁ余裕、と。そもそも TCC みたく C レベルで処理できるしな。 va_arg も大変そうなことになってるなあ。

MS_ABI がどうこう言ってるけど、 VC だと可変長引数の引数はスタック渡しになるってことかな。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h