2013年6月3日

harmony:spread (5)

とりあえずオペコード版のまとめ。

オペコードの選択は Parser.cpp で行われている。具体的には、ノードの情報として格納されている。引数はその後で argumentList 関数でパースされて、ここで spread operator の存在をチェックできる。

[js/src/frontend/Parser.cpp]

template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
{
...
        lhs = handler.newList(PNK_NEW, null(), JSOP_NEW);
...
        if (tokenStream.matchToken(TOK_LP) && !argumentList(lhs))
            return null();
...
}

なので、spread operator を見付けたら、ノードの格納されたオペコードを spread 版に置き換えればパーサ部分は完了。

…なんだけど、パーサは文法だけ見て何も生成しない SyntaxParseHandler とツリーを生成する FullParseHandler の両方に対応できるように設計されていて、オペコードを指定してノードを生成する事はできるんだけど、ノードからオペコードを取得する事はできない。なぜなら SyntaxParseHandler のノードは指定された情報を捨てちゃうから。

という事で、argumentList 関数には何も情報を持ってないノードが渡ってきてる可能性があるので、適切にオペコードを置き換える事はできない。フラグだけ返して、生成してる側で置き換える。

[js/src/frontend/Parser.cpp]

 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
 {
 ...
+        bool isSpread = false;
-        if (tokenStream.matchToken(TOK_LP) && !argumentList(lhs))
+        if (tokenStream.matchToken(TOK_LP) && !argumentList(lhs, &isSpread))
             return null();
+        if (isSpread)
+            handler.setOp(lhs, JSOP_SPREADNEW);
 ...
 }

[js/src/frontend/Parser.cpp]

 template <typename ParseHandler>
 bool
-Parser<ParseHandler>::argumentList(Node listNode)
+Parser<ParseHandler>::argumentList(Node listNode, bool *isSpread)
 {
 ...
     do {
+        bool spread = false;
+        if (tokenStream.matchToken(TOK_TRIPLEDOT, TSF_OPERAND)) {
+            if (tokenStream.matchToken(TOK_RP, TSF_OPERAND)) {
+                report(ParseError, false, null(), JSMSG_SYNTAX_ERROR);
+                return null();
+            }
+            spread = true;
+            *isSpread = true;
+        }
+
          Node argNode = assignExpr();
         if (!argNode)
             return false;
+        if (spread) {
+            argNode = handler.newUnary(PNK_SPREAD, argNode);
+            if (!argNode)
+                return null();
+        }
 ...
 #if JS_HAS_GENERATOR_EXPRS
-        if (tokenStream.matchToken(TOK_FOR)) {
+        if (!spread && tokenStream.matchToken(TOK_FOR)) {
 ...
         } else
 #endif
 ...
 }

これで、 BytecodeEmitter.cpp に来る時点で既にオペコードは決まってる状態になって、追加のフラグやら何やらは不要になる。

[js/src/frontend/BytecodeEmitter.cpp]

 static bool
 EmitCallOrNew(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
 ...
+    bool spread = false;
+    if (pn->isOp(JSOP_SPREADCALL) || pn->isOp(JSOP_SPREADNEW) ||
+        pn->isOp(JSOP_SPREADEVAL) || pn->isOp(JSOP_SPREADFUNCALL) ||
+        pn->isOp(JSOP_SPREADFUNAPPLY)) {
+        spread = true;
+    }
     switch (pn2->getKind()) {
       case PNK_NAME:
         if (bce->emitterMode == BytecodeEmitter::SelfHosting &&
-            pn2->name() == cx->names().callFunction)
+            pn2->name() == cx->names().callFunction &&
+            !spread)
         {
 ...
         }
     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;
-        for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
-            if (!EmitTree(cx, bce, pn3))
+                    return false;
-            if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
+                    return false;
+        if (!spread) {
+            for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
+                if (!EmitTree(cx, bce, pn3))
+                    return false;
+                if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
+                    return false;
+            }
+        } else {
+            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) {
+                if (!pn3->isKind(PNK_SPREAD)) {
+                    if (!EmitTree(cx, bce, pn3))
+                        return false;
+                    if (Emit1(cx, bce, JSOP_INITELEM_INC) < 0)
+                        return false;
+                } else {
+                    if (!EmitTree(cx, bce, pn3->pn_kid))
+                        return false;
+                    if (Emit1(cx, bce, JSOP_SPREAD) < 0)
+                        return false;
+                }
+            }
+            if (Emit1(cx, bce, JSOP_POP) < 0)
+                return false;
+            if (Emit1(cx, bce, JSOP_ENDINIT) < 0)
+                return false;
+            argc = 1;
         }
         bce->emittingForInit = oldEmittingForInit;
     }
 
     if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
         return false;
     CheckTypeSet(cx, bce, pn->getOp());
-    if (pn->isOp(JSOP_EVAL)) {
+    if (pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL)) {
 ...
     }
 ...
 }

生成されるバイトコードはこんな感じ。
引数は全部配列に放り込む。spread operator は配列の方で対応する。で、call の代わりに spreadcall になってる。引数は配列 1 個なので argc = 1。

f(x, ...[y, z])

00000:  callgname "f"
00005:  undefined
00006:  notearg

00007:  newarray 1
00011:    zero

00012:    getgname "x"
00017:    initelem_inc

00018:    newarray 2
00022:      getgname "y"
00027:      initelem_array 0

00031:      getgname "z"
00036:      initelem_array 1
00040:    endinit
00041:    spread

00042:    pop
00043:  endinit

00044:  spreadcall 1

で、インタープリタではオペコードで分岐する。

[js/src/jsinterp.cpp]

 BEGIN_CASE(JSOP_EVAL)
+BEGIN_CASE(JSOP_SPREADEVAL)
 {
-    CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
+    uint32_t argc = GET_ARGC(regs.pc);
+    JS_ASSERT(regs.stackDepth() >= 2 + argc);
+    if (op == JSOP_SPREADEVAL) {
+        RootedObject &arr = rootObject0;
+        arr = &regs.sp[-1].toObject();
+        regs.sp -= 1;
+        JS_ASSERT(arr->isArray());
+        if (!GetLengthProperty(cx, arr, &argc))
+            goto error;
+        if (!GetElements(cx, arr, argc, regs.sp))
+            goto error;
+        regs.sp += argc;
+    }
+    CallArgs args = CallArgsFromSp(argc, regs.sp);
 ...
 }
 ...
 BEGIN_CASE(JSOP_FUNAPPLY)
+BEGIN_CASE(JSOP_SPREADFUNAPPLY)
     if (!GuardFunApplyArgumentsOptimization(cx))
         goto error;
     /* FALL THROUGH */
 
 BEGIN_CASE(JSOP_NEW)
+BEGIN_CASE(JSOP_SPREADNEW)
 BEGIN_CASE(JSOP_CALL)
+BEGIN_CASE(JSOP_SPREADCALL)
 BEGIN_CASE(JSOP_FUNCALL)
+BEGIN_CASE(JSOP_SPREADFUNCALL)
 {
     if (regs.fp()->hasPushedSPSFrame())
         cx->runtime->spsProfiler.updatePC(script, regs.pc);
-    JS_ASSERT(regs.stackDepth() >= 2 + GET_ARGC(regs.pc));
-    CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
-
-    bool construct = (*regs.pc == JSOP_NEW);
+
+    uint32_t argc = GET_ARGC(regs.pc);
+    JS_ASSERT(regs.stackDepth() >= 2 + argc);
+    if (op == JSOP_SPREADNEW || op == JSOP_SPREADCALL ||
+        op == JSOP_SPREADFUNCALL || op == JSOP_SPREADFUNAPPLY) {
+        RootedObject &arr = rootObject0;
+        arr = &regs.sp[-1].toObject();
+        regs.sp -= 1;
+        JS_ASSERT(arr->isArray());
+        if (!GetLengthProperty(cx, arr, &argc))
+            goto error;
+        if (!GetElements(cx, arr, argc, regs.sp))
+            goto error;
+        regs.sp += argc;
+    }
+    CallArgs args = CallArgsFromSp(argc, regs.sp);
+
+    bool construct = (*regs.pc == JSOP_NEW || *regs.pc == JSOP_SPREADNEW);
 ...
 }

そしてオペコードを登録しておく。

[js/src/jsopcode.tbl]

-OPDEF(JSOP_UNUSED41,  41, "unused41",   NULL,         1,  0,  0,  JOF_BYTE)
-OPDEF(JSOP_UNUSED42,  42, "unused42",   NULL,         1,  0,  0,  JOF_BYTE)
-OPDEF(JSOP_UNUSED43,  43, "unused43",   NULL,         1,  0,  0,  JOF_BYTE)
-OPDEF(JSOP_UNUSED44,  44, "unused44",   NULL,         1,  0,  0,  JOF_BYTE)
-OPDEF(JSOP_UNUSED45,  45, "unused45",   NULL,         1,  0,  0,  JOF_BYTE)
+/* spreadcall variant of JSOP_CALL */
+OPDEF(JSOP_SPREADCALL,      41, "spreadcall",      NULL,  3, -1,  1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
+/* spreadcall variant of JSOP_NEW */
+OPDEF(JSOP_SPREADNEW,       42, "spreadnew",       NULL,  3, -1,  1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
+/* spreadcall variant of JSOP_EVAL */
+OPDEF(JSOP_SPREADEVAL,      43, "spreadeval",      NULL,  3, -1,  1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
+/* spreadcall variant of JSOP_FUNCALL */
+OPDEF(JSOP_SPREADFUNCALL,   44, "spreadfuncall",   NULL,  3, -1,  1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
+/* spreadcall variant of JSOP_FUNAPPLY */
+OPDEF(JSOP_SPREADFUNAPPLY,  45, "spreadfunapply",  NULL,  3, -1,  1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)

あとは、spread 版のチェックを各所に追加する。
これは沢山あるので省略。

0 件のコメント:

コメントを投稿