ひとり勉強会

ひとり楽しく勉強会

VM命令:可変長引数

前回可変長の扱いをサボったので今回ちゃんと読むことにしました。
まず、可変長引数の関数が呼び出されたときは、luaD_precall内でその「調整」が行われます。

    else {  /* vararg function */
      int nargs = cast_int(L->top - func) - 1;
      base = adjust_varargs(L, p, nargs);
      func = restorestack(L, funcr);  /* previous call may change the stack */
    }

adjust_varargsは互換性のためのコードが含まれててややこしいですが、そこを省くとこうです。

static StkId adjust_varargs (lua_State *L, Proto *p, int actual) {
  int nfixargs = p->numparams;
  ...
  fixed = L->top - actual;  /* first fixed argument */
  base = L->top;  /* final position of first argument */
  for (i=0; i<nfixargs; i++) {
    setobjs2s(L, L->top++, fixed+i);
    setnilvalue(fixed+i);
  }
  ...
}

nfixargsは、関数定義したときの固定引数の個数(function(a,b,...) なら 2)、actualが実際に渡された引数の個数です。これ、何をしているかというと、F=nfixars, A=actualとして、普通に関数呼び出し処理をするとスタックが

base                          top
 +-------+-------+-----+-------+-----+-------+---------------------+--
 | 引数1 | 引数2 | ... | 引数F | ... | 引数A |
 +-------+-------+-----+-------+-----+-------+---------------------+--

こうなっているのを、こう

                                            base                  top
 +-------+-------+-----+-------+-----+-------+---------------------+--
 |  nil  |  nil  | ... |  nil  | ... | 引数A | 引数1 | ... | 引数F |
 +-------+-------+-----+-------+-----+-------+---------------------+--

固定引数だけ後ろにもっていって、baseとtopもずらします。なので、固定引数へのアクセスは普通の関数の場合と同じにできて、可変長部分へのアクセスは、baseより前を見ればよいことになります。
"..." 式を実装する命令 VARARG の重要部分は以下の通りで、baseの後ろを指定されたレジスタにコピーしているのがわかります。

      case OP_VARARG: {
        int b = GETARG_B(i) - 1;
        ...
        for (j = 0; j < b; j++) {
          if (j < n) {
            setobjs2s(L, ra + j, ci->base - n + j);
          }
          else {
            setnilvalue(ra + j);
          }
        }
        continue;
      }