2013年5月29日

harmony:spread (2)

結局 JSOP_SPREADARG を使った可変長のスタック生成、消費の方向は諦めて解決したのでメモメモ。

問題になってたのは、オペコードが生成、消費するスタックの数が定数である必要があるのに、スタックに乗せる引数の数を可変にしたい、という点。
で、スタックに乗せるオペコードと消費するオペコードが別だから可変長の生成と消費になるワケで、生成も消費も JSOP_CALL の中でやっちゃえば外から見れば結果は定数になっちゃう、という案。

で、そのためには JSOP_CALL が固定長の引数を拾って可変長に展開する、という事ができなくちゃいけない。
なので、引数をスタックに順番に乗せるんじゃなくて、配列に全部放り込んで、1 引数であるかのように JSOP_CALL まで持っていく。

こうする事で更に便利なのは、spread の部分が JSOP_SPREAD をそのまま使える。
引数生成の部分をそのまま spread な配列生成のループに置き換えてしまう。

で、あいかわらず引数は特別扱いの -1 を使う。

[js/src/frontend/BytecodeEmitter.cpp]

static bool
EmitCallOrNew(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
...
    if (emitArgs) {
        /*
         * Emit code for each argument in order, then emit the JSOP_*CALL or
         * JSOP_NEW bytecode with a two-byte immediate telling how many args
         * were pushed on the operand stack.
         */
        bool oldEmittingForInit = bce->emittingForInit;
        bce->emittingForInit = false;
        if (spread) {
            int32_t nspread = 0;
            for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
                if (pn3->isKind(PNK_SPREAD))
                    nspread++;
            }

            ptrdiff_t off = EmitN(cx, bce, JSOP_NEWARRAY, 3);
            if (off < 0)
                return false;
            CheckTypeSet(cx, bce, JSOP_NEWARRAY);
            jsbytecode *pc = bce->code(off);

            SET_UINT24(pc, argc - nspread);

            if (!EmitNumberOp(cx, 0, bce))
                return false;
        }
        for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
            ParseNode *expr = pn3->isKind(PNK_SPREAD) ? pn3->pn_kid : pn3;
            if (!EmitTree(cx, bce, expr))
                return false;
            if (spread) {
                if (pn3->isKind(PNK_SPREAD)) {
                    if (Emit1(cx, bce, JSOP_SPREAD) < 0)
                        return false;
                }
                else {
                    if (Emit1(cx, bce, JSOP_INITELEM_INC) < 0)
                        return false;
                }
            }
            else {
                if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
                    return false;
            }
        }
        bce->emittingForInit = oldEmittingForInit;
        if (spread) {
            if (Emit1(cx, bce, JSOP_POP) < 0)
                return false;
            if (Emit1(cx, bce, JSOP_ENDINIT) < 0)
                return false;
            argc = -1;
        }
    }
...
}

生成されるバイトコードとしては
THIS.FUNC (ARG1, ARG2, ...ARG3)
↓
{FUNC}               // 関数本体に相当するバイトコード列
{THIS}               // this に相当するバイトコード列
JSOP_NOTEARG
JSOP_NEWARRAY
{ARG1}               // 第 1 引数に相当するバイトコード列
JSOP_INITELEM_INC
{ARG2}               // 第 2 引数に相当するバイトコード列
JSOP_INITELEM_INC
{ARG3}               // 第 3 引数に相当するバイトコード列
JSOP_SPREAD
JSOP_POP
JSOP_ENDINIT
JSOP_CALL(argc=-1)

こんな感じ。
配列生成になったので JSOP_SPREADARG は御役御免。
JSOP_NOTEARG は全部 JSOP_CALL の方に任せる事にする (IonMonkey の方は手をつけてないからどうなるか知らないけど)。

で、今回は引数は全部配列に入ってるから、実質的にスタックに乗ってる引数の数は 1 個って事になる。

[js/src/jsopcode.cpp]

unsigned
js::StackUses(JSScript *script, jsbytecode *pc)
{
...
      default:
        /* stack: fun, this, [argc arguments] */
        JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
                  op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
        if (GET_ARGC(pc) == (uint16_t)-1) {
            return 2 + 1;
        }
        return 2 + GET_ARGC(pc);
...
}

で、JSOP_CALL の実態の方でスタックに展開する。
なんとも有り難い事に、要素をまとめて展開する関数 GetElements があるので使う。

[js/src/jsinterp.cpp]

BEGIN_CASE(JSOP_NEW)
BEGIN_CASE(JSOP_CALL)
BEGIN_CASE(JSOP_FUNCALL)
{
...
    CallArgs args;
    if (GET_ARGC(regs.pc) == (uint16_t)-1) {
        JS_ASSERT(regs.stackDepth() >= 3);
        RootedObject &arr = rootObject0;
        arr = &regs.sp[-1].toObject();

        uint32_t argc;
        if (!GetLengthProperty(cx, arr, &argc))
            goto error;
        regs.sp --;

        GetElements(cx, arr, argc, regs.sp);
        regs.sp += argc;

        args = CallArgsFromSp(argc, regs.sp);
    }
    else {
        JS_ASSERT(regs.stackDepth() >= 2 + GET_ARGC(regs.pc));
        args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
    }
...
}

という事で、一段落と言っていいところまで来たと思うのでパッチにして投げる予定。

0 件のコメント:

コメントを投稿