Bug 762363 - ES6 spread-call syntax: f(...args). r=jorendorff.
authorTooru Fujisawa <arai_a@mac.com>
Fri, 13 Sep 2013 18:32:21 +0900
changeset 147430 d780eba18377806bf5e93511dede8e7c69aed83f
parent 147429 e21c1e60414f7a2caa39c101a62f624e8b6972fc
child 147431 5b52918bf6f65e793dbe0f87c829d3d7d4874375
push id33873
push userjorendorff@mozilla.com
push dateTue, 17 Sep 2013 14:03:01 +0000
treeherdermozilla-inbound@d780eba18377 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs762363
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 762363 - ES6 spread-call syntax: f(...args). r=jorendorff.
js/src/builtin/Eval.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/jit-test/tests/basic/expression-autopsy.js
js/src/jit-test/tests/basic/spread-array-invalid-syntax.js
js/src/jit-test/tests/basic/spread-array.js
js/src/jit-test/tests/basic/spread-call-eval.js
js/src/jit-test/tests/basic/spread-call-evaluation-order.js
js/src/jit-test/tests/basic/spread-call-funapply.js
js/src/jit-test/tests/basic/spread-call-funcall.js
js/src/jit-test/tests/basic/spread-call-invalid-syntax.js
js/src/jit-test/tests/basic/spread-call-length.js
js/src/jit-test/tests/basic/spread-call-maxarg.js
js/src/jit-test/tests/basic/spread-call-new.js
js/src/jit-test/tests/basic/spread-call-not-iterable.js
js/src/jit-test/tests/basic/spread-call-recursion.js
js/src/jit-test/tests/basic/spread-call-setcall.js
js/src/jit-test/tests/basic/spread-call-this-strict.js
js/src/jit-test/tests/basic/spread-call-this.js
js/src/jit-test/tests/basic/spread-call.js
js/src/js.msg
js/src/jsanalyze.cpp
js/src/jsinfer.cpp
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/Interpreter.cpp
js/src/vm/Xdr.h
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -411,17 +411,18 @@ js::IndirectEval(JSContext *cx, unsigned
 bool
 js::DirectEval(JSContext *cx, const CallArgs &args)
 {
     // Direct eval can assume it was called from an interpreted or baseline frame.
     ScriptFrameIter iter(cx);
     AbstractFramePtr caller = iter.abstractFramePtr();
 
     JS_ASSERT(IsBuiltinEvalForScope(caller.scopeChain(), args.calleev()));
-    JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL);
+    JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
+              JSOp(*iter.pc()) == JSOP_SPREADEVAL);
     JS_ASSERT_IF(caller.isFunctionFrame(),
                  caller.compartment() == caller.callee()->compartment());
 
     RootedObject scopeChain(cx, caller.scopeChain());
     return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc());
 }
 
 bool
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -5044,16 +5044,19 @@ EmitDelete(ExclusiveContext *cx, Bytecod
             return false;
       }
     }
 
     return true;
 }
 
 static bool
+EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint32_t count);
+
+static bool
 EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     bool callop = pn->isKind(PNK_CALL);
 
     /*
      * Emit callable invocation or operator new (constructor call) code.
      * First, emit code for the left operand to evaluate the callable or
      * constructable object expression.
@@ -5074,28 +5077,30 @@ EmitCallOrNew(ExclusiveContext *cx, Byte
         bce->parser->tokenStream.reportError(callop
                                              ? JSMSG_TOO_MANY_FUN_ARGS
                                              : JSMSG_TOO_MANY_CON_ARGS);
         return false;
     }
 
     bool emitArgs = true;
     ParseNode *pn2 = pn->pn_head;
+    bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE;
     switch (pn2->getKind()) {
       case PNK_NAME:
         if (bce->emitterMode == BytecodeEmitter::SelfHosting &&
-            pn2->name() == cx->names().callFunction)
+            pn2->name() == cx->names().callFunction &&
+            !spread)
         {
             /*
              * Special-casing of callFunction to emit bytecode that directly
              * invokes the callee with the correct |this| object and arguments.
-             * callFunction(fun, thisArg, ...args) thus becomes:
+             * callFunction(fun, thisArg, arg0, arg1) thus becomes:
              * - emit lookup for fun
              * - emit lookup for thisArg
-             * - emit lookups for ...args
+             * - emit lookups for arg0, arg1
              *
              * argc is set to the amount of actually emitted args and the
              * emitting of args below is disabled by setting emitArgs to false.
              */
             if (pn->pn_count < 3) {
                 bce->reportError(pn, JSMSG_MORE_ARGS_NEEDED, "callFunction", "1", "s");
                 return false;
             }
@@ -5171,29 +5176,39 @@ EmitCallOrNew(ExclusiveContext *cx, Byte
     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)
+        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 {
+            if (!EmitArray(cx, bce, pn2->pn_next, argc))
                 return false;
         }
         bce->emittingForInit = oldEmittingForInit;
     }
 
-    if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
-        return false;
+    if (!spread) {
+        if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
+            return false;
+    } else {
+        if (Emit1(cx, bce, pn->getOp()) < 0)
+            return false;
+    }
     CheckTypeSet(cx, bce, pn->getOp());
-    if (pn->isOp(JSOP_EVAL)) {
+    if (pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL)) {
         uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
         EMIT_UINT16_IMM_OP(JSOP_LINENO, lineNum);
     }
     if (pn->pn_xflags & PNX_SETCALL) {
         if (Emit1(cx, bce, JSOP_SETCALL) < 0)
             return false;
     }
     return true;
@@ -5554,67 +5569,66 @@ EmitObject(ExclusiveContext *cx, Bytecod
                       "newinit and newobject must have equal length to edit in-place");
         EMIT_UINT32_IN_PLACE(offset, JSOP_NEWOBJECT, uint32_t(index));
     }
 
     return true;
 }
 
 static bool
-EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
+EmitArrayComp(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
+{
+    if (!EmitNewInit(cx, bce, JSProto_Array, pn))
+        return false;
+
+    /*
+     * Pass the new array's stack index to the PNK_ARRAYPUSH case via
+     * bce->arrayCompDepth, then simply traverse the PNK_FOR node and
+     * its kids under pn2 to generate this comprehension.
+     */
+    JS_ASSERT(bce->stackDepth > 0);
+    unsigned saveDepth = bce->arrayCompDepth;
+    bce->arrayCompDepth = (uint32_t) (bce->stackDepth - 1);
+    if (!EmitTree(cx, bce, pn->pn_head))
+        return false;
+    bce->arrayCompDepth = saveDepth;
+
+    /* Emit the usual op needed for decompilation. */
+    return Emit1(cx, bce, JSOP_ENDINIT) >= 0;
+}
+
+static bool
+EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint32_t count)
 {
     /*
      * Emit code for [a, b, c] that is equivalent to constructing a new
      * array and in source order evaluating each element value and adding
      * it to the array, without invoking latent setters.  We use the
      * JSOP_NEWINIT and JSOP_INITELEM_ARRAY bytecodes to ignore setters and
      * to avoid dup'ing and popping the array as each element is added, as
      * JSOP_SETELEM/JSOP_SETPROP would do.
      */
 
-    if (pn->isKind(PNK_ARRAYCOMP)) {
-        if (!EmitNewInit(cx, bce, JSProto_Array, pn))
-            return false;
-
-        /*
-         * Pass the new array's stack index to the PNK_ARRAYPUSH case via
-         * bce->arrayCompDepth, then simply traverse the PNK_FOR node and
-         * its kids under pn2 to generate this comprehension.
-         */
-        JS_ASSERT(bce->stackDepth > 0);
-        unsigned saveDepth = bce->arrayCompDepth;
-        bce->arrayCompDepth = (uint32_t) (bce->stackDepth - 1);
-        if (!EmitTree(cx, bce, pn->pn_head))
-            return false;
-        bce->arrayCompDepth = saveDepth;
-
-        /* Emit the usual op needed for decompilation. */
-        return Emit1(cx, bce, JSOP_ENDINIT) >= 0;
-    }
-
-    if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
-        return EmitSingletonInitialiser(cx, bce, pn);
-
     int32_t nspread = 0;
-    for (ParseNode *elt = pn->pn_head; elt; elt = elt->pn_next) {
+    for (ParseNode *elt = pn; elt; elt = elt->pn_next) {
         if (elt->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);
 
     // For arrays with spread, this is a very pessimistic allocation, the
     // minimum possible final size.
-    SET_UINT24(pc, pn->pn_count - nspread);
-
-    ParseNode *pn2 = pn->pn_head;
+    SET_UINT24(pc, count - nspread);
+
+    ParseNode *pn2 = pn;
     jsatomid atomIndex;
     if (nspread && !EmitNumberOp(cx, 0, bce))
         return false;
     for (atomIndex = 0; pn2; atomIndex++, pn2 = pn2->pn_next) {
         if (pn2->isKind(PNK_ELISION)) {
             if (Emit1(cx, bce, JSOP_HOLE) < 0)
                 return false;
         } else {
@@ -5630,17 +5644,17 @@ EmitArray(ExclusiveContext *cx, Bytecode
                 return false;
         } else {
             off = EmitN(cx, bce, JSOP_INITELEM_ARRAY, 3);
             if (off < 0)
                 return false;
             SET_UINT24(bce->code(off), atomIndex);
         }
     }
-    JS_ASSERT(atomIndex == pn->pn_count);
+    JS_ASSERT(atomIndex == count);
     if (nspread) {
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
     }
 
     /* Emit an op to finish the array and aid in decompilation. */
     return Emit1(cx, bce, JSOP_ENDINIT) >= 0;
 }
@@ -6059,18 +6073,24 @@ frontend::EmitTree(ExclusiveContext *cx,
         if (slot < 0)
             return false;
         if (!EmitUnaliasedVarOp(cx, pn->getOp(), slot, bce))
             return false;
         break;
       }
 
       case PNK_ARRAY:
-      case PNK_ARRAYCOMP:
-        ok = EmitArray(cx, bce, pn);
+        if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
+            ok = EmitSingletonInitialiser(cx, bce, pn);
+        else
+            ok = EmitArray(cx, bce, pn->pn_head, pn->pn_count);
+        break;
+
+       case PNK_ARRAYCOMP:
+        ok = EmitArrayComp(cx, bce, pn);
         break;
 
       case PNK_OBJECT:
         ok = EmitObject(cx, bce, pn);
         break;
 
       case PNK_NAME:
         if (!EmitNameOp(cx, bce, pn, false))
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2993,17 +2993,18 @@ Parser<ParseHandler>::bindVarOrConst(Bin
 }
 
 template <>
 bool
 Parser<FullParseHandler>::makeSetCall(ParseNode *pn, unsigned msg)
 {
     JS_ASSERT(pn->isKind(PNK_CALL));
     JS_ASSERT(pn->isArity(PN_LIST));
-    JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_EVAL) ||
+    JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_SPREADCALL) ||
+              pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL) ||
               pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY));
 
     if (!report(ParseStrictError, pc->sc->strict, pn, msg))
         return false;
     handler.markAsSetCall(pn);
     return true;
 }
 
@@ -5428,18 +5429,18 @@ template <>
 bool
 Parser<FullParseHandler>::checkAndMarkAsIncOperand(ParseNode *kid, TokenKind tt, bool preorder)
 {
     // Check.
     if (!kid->isKind(PNK_NAME) &&
         !kid->isKind(PNK_DOT) &&
         !kid->isKind(PNK_ELEM) &&
         !(kid->isKind(PNK_CALL) &&
-          (kid->isOp(JSOP_CALL) ||
-           kid->isOp(JSOP_EVAL) ||
+          (kid->isOp(JSOP_CALL) || kid->isOp(JSOP_SPREADCALL) ||
+           kid->isOp(JSOP_EVAL) || kid->isOp(JSOP_SPREADEVAL) ||
            kid->isOp(JSOP_FUNCALL) ||
            kid->isOp(JSOP_FUNAPPLY))))
     {
         report(ParseError, false, null(), JSMSG_BAD_OPERAND, incop_name_str[tt == TOK_DEC]);
         return false;
     }
 
     if (!checkStrictAssignment(kid, IncDecAssignment))
@@ -6189,36 +6190,49 @@ Parser<ParseHandler>::assignExprWithoutY
                          msg, js_yield_str);
         return null();
     }
     return res;
 }
 
 template <typename ParseHandler>
 bool
-Parser<ParseHandler>::argumentList(Node listNode)
+Parser<ParseHandler>::argumentList(Node listNode, bool *isSpread)
 {
     if (tokenStream.matchToken(TOK_RP, TokenStream::Operand))
         return true;
 
     uint32_t startYieldOffset = pc->lastYieldOffset;
     bool arg0 = true;
 
     do {
+        bool spread = false;
+        uint32_t begin = 0;
+        if (tokenStream.matchToken(TOK_TRIPLEDOT, TokenStream::Operand)) {
+            spread = true;
+            begin = pos().begin;
+            *isSpread = true;
+        }
+
         Node argNode = assignExpr();
         if (!argNode)
             return false;
+        if (spread) {
+            argNode = handler.newUnary(PNK_SPREAD, JSOP_NOP, begin, argNode);
+            if (!argNode)
+                return null();
+        }
 
         if (handler.isOperationWithoutParens(argNode, PNK_YIELD) &&
             tokenStream.peekToken() == TOK_COMMA) {
             report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str);
             return false;
         }
 #if JS_HAS_GENERATOR_EXPRS
-        if (tokenStream.matchToken(TOK_FOR)) {
+        if (!spread && tokenStream.matchToken(TOK_FOR)) {
             if (pc->lastYieldOffset != startYieldOffset) {
                 reportWithOffset(ParseError, false, pc->lastYieldOffset,
                                  JSMSG_BAD_GENEXP_BODY, js_yield_str);
                 return false;
             }
             argNode = generatorExpr(argNode);
             if (!argNode)
                 return false;
@@ -6258,18 +6272,23 @@ Parser<ParseHandler>::memberExpr(TokenKi
 
         tt = tokenStream.getToken(TokenStream::Operand);
         Node ctorExpr = memberExpr(tt, false);
         if (!ctorExpr)
             return null();
 
         handler.addList(lhs, ctorExpr);
 
-        if (tokenStream.matchToken(TOK_LP) && !argumentList(lhs))
-            return null();
+        if (tokenStream.matchToken(TOK_LP)) {
+            bool isSpread = false;
+            if (!argumentList(lhs, &isSpread))
+                return null();
+            if (isSpread)
+                handler.setOp(lhs, JSOP_SPREADNEW);
+        }
     } else {
         lhs = primaryExpr(tt);
         if (!lhs)
             return null();
     }
 
     while ((tt = tokenStream.getToken()) > TOK_EOF) {
         Node nextMember;
@@ -6292,49 +6311,54 @@ Parser<ParseHandler>::memberExpr(TokenKi
                 return null();
 
             MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
 
             nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end);
             if (!nextMember)
                 return null();
         } else if (allowCallSyntax && tt == TOK_LP) {
+            JSOp op = JSOP_CALL;
             nextMember = handler.newList(PNK_CALL, null(), JSOP_CALL);
             if (!nextMember)
                 return null();
 
             if (JSAtom *atom = handler.isName(lhs)) {
                 if (atom == context->names().eval) {
                     /* Select JSOP_EVAL and flag pc as heavyweight. */
-                    handler.setOp(nextMember, JSOP_EVAL);
+                    op = JSOP_EVAL;
                     pc->sc->setBindingsAccessedDynamically();
 
                     /*
                      * In non-strict mode code, direct calls to eval can add
                      * variables to the call object.
                      */
                     if (pc->sc->isFunctionBox() && !pc->sc->strict)
                         pc->sc->asFunctionBox()->setHasExtensibleScope();
                 }
             } else if (JSAtom *atom = handler.isGetProp(lhs)) {
                 /* Select JSOP_FUNAPPLY given foo.apply(...). */
                 if (atom == context->names().apply) {
-                    handler.setOp(nextMember, JSOP_FUNAPPLY);
+                    op = JSOP_FUNAPPLY;
                     if (pc->sc->isFunctionBox())
                         pc->sc->asFunctionBox()->usesApply = true;
                 } else if (atom == context->names().call) {
-                    handler.setOp(nextMember, JSOP_FUNCALL);
+                    op = JSOP_FUNCALL;
                 }
             }
 
             handler.setBeginPosition(nextMember, lhs);
             handler.addList(nextMember, lhs);
 
-            if (!argumentList(nextMember))
+            bool isSpread = false;
+            if (!argumentList(nextMember, &isSpread))
                 return null();
+            if (isSpread)
+                op = (op == JSOP_EVAL ? JSOP_SPREADEVAL : JSOP_SPREADCALL);
+            handler.setOp(nextMember, op);
         } else {
             tokenStream.ungetToken();
             return lhs;
         }
 
         lhs = nextMember;
     }
     if (tt == TOK_ERROR)
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -539,17 +539,17 @@ class Parser : private AutoGCRooter, pub
     Node unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin);
 
     Node condition();
     Node comprehensionTail(Node kid, unsigned blockid, bool isGenexp,
                            ParseContext<ParseHandler> *outerpc,
                            ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP);
     bool arrayInitializerComprehensionTail(Node pn);
     Node generatorExpr(Node kid);
-    bool argumentList(Node listNode);
+    bool argumentList(Node listNode, bool *isSpread);
     Node letBlock(LetContext letContext);
     Node destructuringExpr(BindData<ParseHandler> *data, TokenKind tt);
 
     Node identifierName();
 
     bool matchLabel(MutableHandle<PropertyName*> label);
 
     bool allowsForEachIn() {
--- a/js/src/jit-test/tests/basic/expression-autopsy.js
+++ b/js/src/jit-test/tests/basic/expression-autopsy.js
@@ -71,16 +71,19 @@ check("o[3]");
 check("o[256]");
 check("o[65536]");
 check("o[268435455]");
 check("o['1.1']");
 check("o[4 + 'h']", "o['4h']");
 check("this.x");
 check("ieval(undef)", "ieval(...)");
 check("ieval.call()", "ieval.call(...)");
+check("ieval(...[])", "ieval(...)");
+check("ieval(...[undef])", "ieval(...)");
+check("ieval(...[undef, undef])", "ieval(...)");
 
 for (let tok of ["|", "^", "&", "==", "!==", "===", "!==", "<", "<=", ">", ">=",
                  ">>", "<<", ">>>", "+", "-", "*", "/", "%"]) {
     check("o[(undef " + tok + " 4)]");
 }
 
 check("o[!(o)]");
 check("o[~(o)]");
--- a/js/src/jit-test/tests/basic/spread-array-invalid-syntax.js
+++ b/js/src/jit-test/tests/basic/spread-array-invalid-syntax.js
@@ -1,14 +1,19 @@
 load(libdir + "asserts.js");
 
 var offenders = [
     "(1 ... n)",
     "[1 ... n]",
     "(...x)",
     "[...x for (x of y)]",
+    "[...x, x for (x of y)]",
     "[...]",
     "(...)",
-    "[...,]"
+    "[...,]",
+    "[... ...[]]",
+    "(... ...[])",
+    "[x, ...]",
+    "(x, ...)"
 ];
 for (var sample of offenders) {
     assertThrowsInstanceOf(function () { eval(sample); }, SyntaxError);
 }
--- a/js/src/jit-test/tests/basic/spread-array.js
+++ b/js/src/jit-test/tests/basic/spread-array.js
@@ -1,17 +1,54 @@
+load(libdir + "asserts.js");
 load(libdir + "eqArrayHelper.js");
 
 assertEqArray([...[1, 2, 3]], [1, 2, 3]);
 assertEqArray([1, ...[2, 3, 4], 5], [1, 2, 3, 4, 5]);
 assertEqArray([1, ...[], 2], [1, 2]);
 assertEqArray([1, ...[2, 3], 4, ...[5, 6]], [1, 2, 3, 4, 5, 6]);
 assertEqArray([1, ...[], 2], [1, 2]);
 assertEqArray([1,, ...[2]], [1,, 2]);
 assertEqArray([1,, ...[2],, 3,, 4,], [1,, 2,, 3,, 4,]);
 assertEqArray([...[1, 2, 3],,,,], [1, 2, 3,,,,]);
 assertEqArray([,,...[1, 2, 3],,,,], [,,1,2,3,,,,]);
 
+assertEqArray([...[undefined]], [undefined]);
+
+// other iterable objects
+assertEqArray([...Int32Array([1, 2, 3])], [1, 2, 3]);
+assertEqArray([..."abc"], ["a", "b", "c"]);
+assertEqArray([...[1, 2, 3].iterator()], [1, 2, 3]);
+assertEqArray([...Set([1, 2, 3])], [1, 2, 3]);
+assertEqArray([...Map([["a", "A"], ["b", "B"], ["c", "C"]])].map(([k, v]) => k + v), ["aA", "bB", "cC"]);
+let itr = {
+  iterator: function() {
+    return {
+      i: 1,
+      next: function() {
+        if (this.i < 4)
+          return this.i++;
+        else
+          throw StopIteration;
+      }
+    };
+  }
+};
+assertEqArray([...itr], [1, 2, 3]);
+let gen = {
+  iterator: function() {
+    for (let i = 1; i < 4; i ++)
+      yield i;
+  }
+};
+assertEqArray([...gen], [1, 2, 3]);
+
+let a, b = [1, 2, 3];
+assertEqArray([...a=b], [1, 2, 3]);
+
 // According to the draft spec, null and undefined are to be treated as empty
 // arrays. However, they are not iterable. If the spec is not changed to be in
 // terms of iterables, these tests should be fixed.
 //assertEqArray([1, ...null, 2], [1, 2]);
 //assertEqArray([1, ...undefined, 2], [1, 2]);
+assertThrowsInstanceOf(() => [...null], TypeError);
+assertThrowsInstanceOf(() => [...undefined], TypeError);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-eval.js
@@ -0,0 +1,59 @@
+load(libdir + "asserts.js");
+
+assertEq(eval(...[]), undefined);
+assertEq(eval(...["1 + 2"]), 3);
+
+let a = 10, b = 1;
+assertEq(eval(...["a + b"]), 11);
+
+(function() {
+  let a = 20;
+  assertEq(eval(...["a + b"]), 21);
+})();
+
+with ({ a: 30 }) {
+  assertEq(eval(...["a + b"]), 31);
+}
+
+let line0 = Error().lineNumber;
+try {             // line0 + 1
+  eval(...["("]); // line0 + 2
+} catch (e) {
+  assertEq(e.lineNumber, line0 + 2);
+}
+
+// other iterable objects
+assertEq(eval(...["a + b"].iterator()), 11);
+assertEq(eval(...Set(["a + b"])), 11);
+let itr = {
+  iterator: function() {
+    return {
+      i: 0,
+      next: function() {
+        this.i++;
+        if (this.i == 1)
+          return "a + b";
+        else
+          throw StopIteration;
+      }
+    };
+  }
+};
+assertEq(eval(...itr), 11);
+let gen = {
+  iterator: function() {
+    yield "a + b";
+  }
+};
+assertEq(eval(...gen), 11);
+
+let c = ["C"], d = "D";
+assertEq(eval(...c=["c[0] + d"]), "c[0] + dD");
+
+// According to the draft spec, null and undefined are to be treated as empty
+// arrays. However, they are not iterable. If the spec is not changed to be in
+// terms of iterables, these tests should be fixed.
+//assertEq(eval("a + b", ...null), 11);
+//assertEq(eval("a + b", ...undefined), 11);
+assertThrowsInstanceOf(() => eval("a + b", ...null), TypeError);
+assertThrowsInstanceOf(() => eval("a + b", ...undefined), TypeError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-evaluation-order.js
@@ -0,0 +1,13 @@
+load(libdir + "eqArrayHelper.js");
+
+var check = [];
+function t(token) {
+    check.push(token);
+    return token;
+}
+let f = (...x) => x;
+f(3, ...[t(1)], ...[t(2), t(3)], 34, 42, ...[t(4)]);
+assertEqArray(check, [1, 2, 3, 4]);
+
+var arr = [1, 2, 3];
+assertEqArray(f(...arr, arr.pop()), [1, 2, 3, 3]);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-funapply.js
@@ -0,0 +1,93 @@
+load(libdir + "asserts.js");
+load(libdir + "eqArrayHelper.js");
+
+function checkCommon(f) {
+  assertEqArray(f.apply(null, ...[[1, 2, 3]]), [1, 2, 3]);
+  assertEqArray(f.apply(...[null], [1, 2, 3]), [1, 2, 3]);
+  assertEqArray(f.apply(...[null], ...[[1, 2, 3]]), [1, 2, 3]);
+  assertEqArray(f.apply(...[null, [1, 2, 3]]), [1, 2, 3]);
+
+  // other iterable objects
+  assertEqArray(f.apply(...Set([null, [1, 2, 3]])), [1, 2, 3]);
+  assertEqArray(f.apply(...[null, [1, 2, 3]].iterator()), [1, 2, 3]);
+  let itr = {
+    iterator: function() {
+      return {
+        i: 0,
+        next: function() {
+          this.i++;
+          if (this.i == 1)
+            return null;
+          else if (this.i == 2)
+            return [1, 2, 3];
+          else
+            throw StopIteration;
+        }
+      };
+    }
+  };
+  assertEqArray(f.apply(...itr), [1, 2, 3]);
+  let gen = {
+    iterator: function() {
+      yield null;
+      yield [1, 2, 3];
+    }
+  };
+  assertEqArray(f.apply(...gen), [1, 2, 3]);
+
+  let a;
+  assertEqArray(f.apply(null, ...a=[[1, 2, 3]]), [1, 2, 3]);
+
+  // According to the draft spec, null and undefined are to be treated as empty
+  // arrays. However, they are not iterable. If the spec is not changed to be in
+  // terms of iterables, these tests should be fixed.
+  //assertEqArray(f.apply(null, ...null, [1, 2, 3]), [1, 2, 3]);
+  //assertEqArray(f.apply(null, ...undefined, [1, 2, 3]), [1, 2, 3]);
+  assertThrowsInstanceOf(() => f.apply(null, ...null, [1, 2, 3]), TypeError);
+  assertThrowsInstanceOf(() => f.apply(null, ...undefined, [1, 2, 3]), TypeError);
+}
+
+function checkNormal(f) {
+  checkCommon(f);
+
+  assertEqArray(f.apply(null, ...[[]]), [undefined, undefined, undefined]);
+  assertEqArray(f.apply(null, ...[[1]]), [1, undefined, undefined]);
+  assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2, undefined]);
+  assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3]);
+
+  assertEqArray(f.apply(null, ...[[undefined]]), [undefined, undefined, undefined]);
+}
+
+checkNormal(function(a, b, c) [a, b, c]);
+checkNormal((a, b, c) => [a, b, c]);
+
+function checkDefault(f) {
+  checkCommon(f);
+
+  assertEqArray(f.apply(null, ...[[]]), [-1, -2, -3]);
+  assertEqArray(f.apply(null, ...[[1]]), [1, -2, -3]);
+  assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2, -3]);
+  assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3]);
+
+  assertEqArray(f.apply(null, ...[[undefined]]), [-1, -2, -3]);
+}
+
+checkDefault(function(a = -1, b = -2, c = -3) [a, b, c]);
+checkDefault((a = -1, b = -2, c = -3) => [a, b, c]);
+
+function checkRest(f) {
+  checkCommon(f);
+
+  assertEqArray(f.apply(null, ...[[]]), []);
+  assertEqArray(f.apply(null, ...[[1]]), [1]);
+  assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2]);
+  assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3, 4]);
+
+  assertEqArray(f.apply(null, ...[[undefined]]), [undefined]);
+
+  // other iterable objects
+  assertEqArray(f.apply(null, ...Map([[["a", "A"], ["b", "B"]]])).map(([k, v]) => k + v), ["aA", "bB"]);
+}
+
+checkRest(function(...x) x);
+checkRest((...x) => x);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-funcall.js
@@ -0,0 +1,11 @@
+load(libdir + "eqArrayHelper.js");
+
+function check(f) {
+  assertEqArray(f.call(...[null], 1, 2, 3), [1, 2, 3]);
+  assertEqArray(f.call(...[null], 1, ...[2, 3], 4, ...[5, 6]), [1, 2, 3, 4, 5, 6]);
+  assertEqArray(f.call(...[null, 1], ...[2, 3], 4, ...[5, 6]), [1, 2, 3, 4, 5, 6]);
+  assertEqArray(f.call(...[null, 1, ...[2, 3], 4, ...[5, 6]]), [1, 2, 3, 4, 5, 6]);
+}
+
+check(function(...x) x);
+check((...x) => x);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-invalid-syntax.js
@@ -0,0 +1,16 @@
+load(libdir + "asserts.js");
+
+var offenders = [
+    "f(1 ... n)",
+    "f(...x for (x in y))",
+    "f(...)",
+    "f(...,)",
+    "f(... ...[])",
+    "f(...x,)",
+    "f(x, ...)",
+    "f(...x, x for (x in y))",
+    "f(x for (x in y), ...x)"
+];
+for (var sample of offenders) {
+    assertThrowsInstanceOf(function() { eval(sample); }, SyntaxError);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-length.js
@@ -0,0 +1,55 @@
+let makeCall    = farg => Function("f", "arg", "return f(" + farg + ");");
+let makeFunCall = farg => Function("f", "arg", "return f.call(null, " + farg + ");");
+let makeNew     = farg => Function("f", "arg", "return new f(" + farg + ").length;");
+
+function checkLength(f, makeFn) {
+  assertEq(makeFn("...[1, 2, 3]")(f), 3);
+  assertEq(makeFn("1, ...[2], 3")(f), 3);
+  assertEq(makeFn("1, ...[2], ...[3]")(f), 3);
+  assertEq(makeFn("1, ...[2, 3]")(f), 3);
+  assertEq(makeFn("1, ...[], 2, 3")(f), 3);
+
+  assertEq(makeFn("...[1]")(f), 1);
+  assertEq(makeFn("...[1, 2]")(f), 2);
+  assertEq(makeFn("...[1, 2, 3, 4]")(f), 4);
+  assertEq(makeFn("1, ...[2, 3, 4], 5")(f), 5);
+
+  assertEq(makeFn("...[undefined]")(f), 1);
+
+  // other iterable objects
+  assertEq(makeFn("...arg")(f, Int32Array([1, 2, 3])), 3);
+  assertEq(makeFn("...arg")(f, "abc"), 3);
+  assertEq(makeFn("...arg")(f, [1, 2, 3].iterator()), 3);
+  assertEq(makeFn("...arg")(f, Set([1, 2, 3])), 3);
+  assertEq(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])), 3);
+  let itr = {
+    iterator: function() {
+      return {
+        i: 1,
+        next: function() {
+          if (this.i < 4)
+            return this.i++;
+          else
+            throw StopIteration;
+        }
+      };
+    }
+  };
+  assertEq(makeFn("...arg")(f, itr), 3);
+  let gen = {
+    iterator: function() {
+      for (let i = 1; i < 4; i ++)
+        yield i;
+    }
+  };
+  assertEq(makeFn("...arg")(f, gen), 3);
+}
+
+checkLength(function(x) arguments.length, makeCall);
+checkLength(function(x) arguments.length, makeFunCall);
+checkLength((x) => arguments.length, makeCall);
+checkLength((x) => arguments.length, makeFunCall);
+function lengthClass(x) {
+  this.length = arguments.length;
+}
+checkLength(lengthClass, makeNew);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-maxarg.js
@@ -0,0 +1,23 @@
+let a = [];
+a.length = getMaxArgs() + 1;
+
+let f = function() {
+};
+
+try {
+  f(...a);
+} catch (e) {
+  assertEq(e.message, "too many function arguments");
+}
+
+try {
+  new f(...a);
+} catch (e) {
+  assertEq(e.message, "too many constructor arguments");
+}
+
+try {
+  eval(...a);
+} catch (e) {
+  assertEq(e.message, "too many function arguments");
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-new.js
@@ -0,0 +1,9 @@
+load(libdir + "eqArrayHelper.js");
+
+function g(a, b, c) {
+  this.value = [a, b, c];
+  assertEq(Object.getPrototypeOf(this), g.prototype);
+  assertEq(arguments.callee, g);
+}
+
+assertEqArray(new g(...[1, 2, 3]).value, [1, 2, 3]);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-not-iterable.js
@@ -0,0 +1,16 @@
+load(libdir + "asserts.js");
+
+assertThrowsInstanceOf(() => Math.sin(...true), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...false), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...new Date()), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...Function("")), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...function () {}), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...(x => x)), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...1), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...{}), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...{ iterator: 10 }), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...{ iterator: function() undefined }), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...{ iterator: function() this }), TypeError);
+assertThrowsValue(() => Math.sin(...{ iterator: function() this, next: function() { throw 10; } }), 10);
+assertThrowsInstanceOf(() => Math.sin(.../a/), TypeError);
+assertThrowsInstanceOf(() => Math.sin(...new Error()), TypeError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-recursion.js
@@ -0,0 +1,18 @@
+let a = [];
+a.length = 30;
+
+function check(f) {
+  try {
+    f();
+  } catch (e) {
+    assertEq(e.message, "too much recursion");
+  }
+}
+
+let f = function() f(...a) + 1;
+let g = () => g(...a) + 1;
+let h = function() new h(...a) + 1;
+
+check(f);
+check(g);
+check(h);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-setcall.js
@@ -0,0 +1,26 @@
+load(libdir + "asserts.js");
+
+function g() {
+}
+
+let a = {
+  g: function() {
+  }
+};
+
+function check(expr) {
+  assertThrowsInstanceOf(Function(expr), ReferenceError);
+}
+
+check("g(...[]) = 1");
+check("a.g(...[]) = 1");
+check("eval(...['1']) = 1");
+check("[g(...[])] = 1");
+check("[a.g(...[])] = 1");
+check("[eval(...['1'])] = 1");
+check("({y: g(...[])}) = 1");
+check("({y: a.g(...[])}) = 1");
+check("({y: eval(...['1'])}) = 1");
+check("g(...[]) ++");
+check("a.g(...[]) ++");
+check("eval(...['1']) ++");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-this-strict.js
@@ -0,0 +1,105 @@
+"use strict";
+
+let global = this;
+let p = {};
+let q = {};
+
+let g1 = function() {
+  assertEq(this, undefined);
+};
+g1(...[]);
+
+let g2 = x => {
+  assertEq(this, global);
+};
+g2(...[]);
+
+let g3 = function() {
+  assertEq(this, p);
+};
+g3.apply(p, ...[]);
+g3.call(p, ...[]);
+
+g2.apply(p, ...[]);
+g2.call(p, ...[]);
+
+let o = {
+  f1: function() {
+    assertEq(this, o);
+
+    let g1 = function() {
+      assertEq(this, undefined);
+    };
+    g1(...[]);
+
+    let g2 = x => {
+      assertEq(this, o);
+    };
+    g2(...[]);
+
+    let g3 = function() {
+      assertEq(this, q);
+    };
+    g3.apply(q, ...[]);
+    g3.call(q, ...[]);
+
+    let g4 = x => {
+      assertEq(this, o);
+    };
+    g4.apply(q, ...[]);
+    g4.call(q, ...[]);
+  },
+  f2: x => {
+    assertEq(this, global);
+    let g1 = function() {
+      assertEq(this, undefined);
+    };
+    g1(...[]);
+
+    let g2 = x => {
+      assertEq(this, global);
+    };
+    g2(...[]);
+
+    let g3 = function() {
+      assertEq(this, q);
+    };
+    g3.apply(q, ...[]);
+    g3.call(q, ...[]);
+
+    let g4 = x => {
+      assertEq(this, global);
+    };
+    g4.apply(q, ...[]);
+    g4.call(q, ...[]);
+  },
+  f3: function() {
+    assertEq(this, p);
+
+    let g1 = function() {
+      assertEq(this, undefined);
+    };
+    g1(...[]);
+
+    let g2 = x => {
+      assertEq(this, p);
+    };
+    g2(...[]);
+
+    let g3 = function() {
+      assertEq(this, q);
+    };
+    g3.apply(q, ...[]);
+    g3.call(q, ...[]);
+
+    let g4 = x => {
+      assertEq(this, p);
+    };
+    g4.apply(q, ...[]);
+    g4.call(q, ...[]);
+  }
+};
+o.f1(...[]);
+o.f2(...[]);
+o.f3.apply(p, ...[]);
+o.f2.apply(p, ...[]);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call-this.js
@@ -0,0 +1,123 @@
+let global = this;
+let p = {};
+let q = {};
+
+let g1 = function() {
+  assertEq(this, global);
+  assertEq(arguments.callee, g1);
+};
+g1(...[]);
+
+let g2 = x => {
+  assertEq(this, global);
+  // arguments.callee is unbound function object, and following assertion fails.
+  // see Bug 889158
+  //assertEq(arguments.callee, g2);
+};
+g2(...[]);
+
+let g3 = function() {
+  assertEq(this, p);
+  assertEq(arguments.callee, g3);
+};
+g3.apply(p, ...[]);
+g3.call(p, ...[]);
+
+g2.apply(p, ...[]);
+g2.call(p, ...[]);
+
+let o = {
+  f1: function() {
+    assertEq(this, o);
+    assertEq(arguments.callee, o.f1);
+
+    let g1 = function() {
+      assertEq(this, global);
+      assertEq(arguments.callee, g1);
+    };
+    g1(...[]);
+
+    let g2 = x => {
+      assertEq(this, o);
+      //assertEq(arguments.callee, g2);
+    };
+    g2(...[]);
+
+    let g3 = function() {
+      assertEq(this, q);
+      assertEq(arguments.callee, g3);
+    };
+    g3.apply(q, ...[]);
+    g3.call(q, ...[]);
+
+    let g4 = x => {
+      assertEq(this, o);
+      //assertEq(arguments.callee, g4);
+    };
+    g4.apply(q, ...[]);
+    g4.call(q, ...[]);
+  },
+  f2: x => {
+    assertEq(this, global);
+    //assertEq(arguments.callee, o.f2);
+    let g1 = function() {
+      assertEq(this, global);
+      assertEq(arguments.callee, g1);
+    };
+    g1(...[]);
+
+    let g2 = x => {
+      assertEq(this, global);
+      //assertEq(arguments.callee, g2);
+    };
+    g2(...[]);
+
+    let g3 = function() {
+      assertEq(this, q);
+      assertEq(arguments.callee, g3);
+    };
+    g3.apply(q, ...[]);
+    g3.call(q, ...[]);
+
+    let g4 = x => {
+      assertEq(this, global);
+      //assertEq(arguments.callee, g4);
+    };
+    g4.apply(q, ...[]);
+    g4.call(q, ...[]);
+  },
+  f3: function() {
+    assertEq(this, p);
+    assertEq(arguments.callee, o.f3);
+
+    let g1 = function() {
+      assertEq(this, global);
+      assertEq(arguments.callee, g1);
+    };
+    g1(...[]);
+
+    let g2 = x => {
+      assertEq(this, p);
+      //assertEq(arguments.callee, g2);
+    };
+    g2(...[]);
+
+    let g3 = function() {
+      assertEq(this, q);
+      assertEq(arguments.callee, g3);
+    };
+    g3.apply(q, ...[]);
+    g3.call(q, ...[]);
+
+    let g4 = x => {
+      assertEq(this, p);
+      //assertEq(arguments.callee, g4);
+    };
+    g4.apply(q, ...[]);
+    g4.call(q, ...[]);
+  }
+};
+o.f1(...[]);
+o.f2(...[]);
+o.f3.apply(p, ...[]);
+o.f2.apply(p, ...[]);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-call.js
@@ -0,0 +1,115 @@
+load(libdir + "asserts.js");
+load(libdir + "eqArrayHelper.js");
+
+let makeCall    = farg => Function("f", "arg", "return f(" + farg + ");");
+let makeFunCall = farg => Function("f", "arg", "return f.call(null, " + farg + ");");
+let makeNew     = farg => Function("f", "arg", "return new f(" + farg + ").value;");
+
+function checkCommon(f, makeFn) {
+  assertEqArray(makeFn("...[1, 2, 3]")(f), [1, 2, 3]);
+  assertEqArray(makeFn("1, ...[2], 3")(f), [1, 2, 3]);
+  assertEqArray(makeFn("1, ...[2], ...[3]")(f), [1, 2, 3]);
+  assertEqArray(makeFn("1, ...[2, 3]")(f), [1, 2, 3]);
+  assertEqArray(makeFn("1, ...[], 2, 3")(f), [1, 2, 3]);
+
+  // other iterable objects
+  assertEqArray(makeFn("...arg")(f, Int32Array([1, 2, 3])), [1, 2, 3]);
+  assertEqArray(makeFn("...arg")(f, "abc"), ["a", "b", "c"]);
+  assertEqArray(makeFn("...arg")(f, [1, 2, 3].iterator()), [1, 2, 3]);
+  assertEqArray(makeFn("...arg")(f, Set([1, 2, 3])), [1, 2, 3]);
+  assertEqArray(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])).map(([k, v]) => k + v), ["aA", "bB", "cC"]);
+  let itr = {
+    iterator: function() {
+      return {
+        i: 1,
+        next: function() {
+          if (this.i < 4)
+            return this.i++;
+          else
+            throw StopIteration;
+        }
+      };
+    }
+  };
+  assertEqArray(makeFn("...arg")(f, itr), [1, 2, 3]);
+  let gen = {
+    iterator: function() {
+      for (let i = 1; i < 4; i ++)
+        yield i;
+    }
+  };
+  assertEqArray(makeFn("...arg")(f, gen), [1, 2, 3]);
+
+  assertEqArray(makeFn("...arg=[1, 2, 3]")(f), [1, 2, 3]);
+
+  // According to the draft spec, null and undefined are to be treated as empty
+  // arrays. However, they are not iterable. If the spec is not changed to be in
+  // terms of iterables, these tests should be fixed.
+  //assertEqArray(makeFn(1, ...null, 2, 3)(f), [1, 2, 3]);
+  //assertEqArray(makeFn(1, ...undefined, 2, 3)(f), [1, 2, 3]);
+  assertThrowsInstanceOf(makeFn("1, ...null, 2, 3"), TypeError);
+  assertThrowsInstanceOf(makeFn("1, ...undefined, 2, 3"), TypeError);
+}
+
+function checkNormal(f, makeFn) {
+  checkCommon(f, makeFn);
+
+  assertEqArray(makeFn("...[]")(f), [undefined, undefined, undefined]);
+  assertEqArray(makeFn("...[1]")(f), [1, undefined, undefined]);
+  assertEqArray(makeFn("...[1, 2]")(f), [1, 2, undefined]);
+  assertEqArray(makeFn("...[1, 2, 3, 4]")(f), [1, 2, 3]);
+
+  assertEqArray(makeFn("...[undefined]")(f), [undefined, undefined, undefined]);
+}
+
+checkNormal(function(a, b, c) [a, b, c], makeCall);
+checkNormal(function(a, b, c) [a, b, c], makeFunCall);
+checkNormal((a, b, c) => [a, b, c], makeCall);
+checkNormal((a, b, c) => [a, b, c], makeFunCall);
+function normalClass(a, b, c) {
+  this.value = [a, b, c];
+  assertEq(Object.getPrototypeOf(this), normalClass.prototype);
+}
+checkNormal(normalClass, makeNew);
+
+function checkDefault(f, makeFn) {
+  checkCommon(f, makeFn);
+
+  assertEqArray(makeFn("...[]")(f), [-1, -2, -3]);
+  assertEqArray(makeFn("...[1]")(f), [1, -2, -3]);
+  assertEqArray(makeFn("...[1, 2]")(f), [1, 2, -3]);
+  assertEqArray(makeFn("...[1, 2, 3, 4]")(f), [1, 2, 3]);
+
+  assertEqArray(makeFn("...[undefined]")(f), [-1, -2, -3]);
+}
+
+checkDefault(function(a = -1, b = -2, c = -3) [a, b, c], makeCall);
+checkDefault(function(a = -1, b = -2, c = -3) [a, b, c], makeFunCall);
+checkDefault((a = -1, b = -2, c = -3) => [a, b, c], makeCall);
+checkDefault((a = -1, b = -2, c = -3) => [a, b, c], makeFunCall);
+function defaultClass(a = -1, b = -2, c = -3) {
+  this.value = [a, b, c];
+  assertEq(Object.getPrototypeOf(this), defaultClass.prototype);
+}
+checkDefault(defaultClass, makeNew);
+
+function checkRest(f, makeFn) {
+  checkCommon(f, makeFn);
+
+  assertEqArray(makeFn("...[]")(f), []);
+  assertEqArray(makeFn("1, ...[2, 3, 4], 5")(f), [1, 2, 3, 4, 5]);
+  assertEqArray(makeFn("1, ...[], 2")(f), [1, 2]);
+  assertEqArray(makeFn("1, ...[2, 3], 4, ...[5, 6]")(f), [1, 2, 3, 4, 5, 6]);
+
+  assertEqArray(makeFn("...[undefined]")(f), [undefined]);
+}
+
+checkRest(function(...x) x, makeCall);
+checkRest(function(...x) x, makeFunCall);
+checkRest((...x) => x, makeCall);
+checkRest((...x) => x, makeFunCall);
+function restClass(...x) {
+  this.value = x;
+  assertEq(Object.getPrototypeOf(this), restClass.prototype);
+}
+checkRest(restClass, makeNew);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -407,8 +407,10 @@ MSG_DEF(JSMSG_TYPEDOBJECT_BINARYARRAY_BA
 MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS, 354, 0, JSEXN_RANGEERR, "invalid field descriptor")
 MSG_DEF(JSMSG_TYPEDOBJECT_NOT_BINARYSTRUCT,   355, 1, JSEXN_TYPEERR, "{0} is not a BinaryStruct")
 MSG_DEF(JSMSG_TYPEDOBJECT_SUBARRAY_INTEGER_ARG, 356, 1, JSEXN_ERR, "argument {0} must be an integer")
 MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_EMPTY_DESCRIPTOR, 357, 0, JSEXN_ERR, "field descriptor cannot be empty")
 MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_FIELD, 358, 1, JSEXN_ERR, "field {0} is not a valid BinaryData Type descriptor")
 MSG_DEF(JSMSG_GENERATOR_FINISHED,     359, 0, JSEXN_TYPEERR, "generator has already finished")
 MSG_DEF(JSMSG_TYPEDOBJECT_TOO_BIG, 360, 0, JSEXN_ERR, "Type is too large to allocate")
 MSG_DEF(JSMSG_TYPEDOBJECT_NOT_TYPE_OBJECT, 361, 0, JSEXN_ERR, "Expected a type object")
+MSG_DEF(JSMSG_TOO_MANY_CON_SPREADARGS, 362, 0, JSEXN_RANGEERR, "too many constructor arguments")
+MSG_DEF(JSMSG_TOO_MANY_FUN_SPREADARGS, 363, 0, JSEXN_RANGEERR, "too many function arguments")
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -260,16 +260,17 @@ ScriptAnalysis::analyzeBytecode(JSContex
           case JSOP_DEFCONST:
           case JSOP_SETCONST:
             usesScopeChain_ = true; // Requires access to VarObj via ScopeChain.
             canTrackVars = false;
             isIonInlineable = false;
             break;
 
           case JSOP_EVAL:
+          case JSOP_SPREADEVAL:
             canTrackVars = false;
             isIonInlineable = false;
             break;
 
           case JSOP_ENTERWITH:
             canTrackVars = false;
             isIonInlineable = false;
             break;
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1445,19 +1445,22 @@ types::UseNewType(JSContext *cx, JSScrip
      *
      * Sub1.prototype = new Super();
      * Sub2.prototype = new Super();
      *
      * Using distinct type objects for the particular prototypes of Sub1 and
      * Sub2 lets us continue to distinguish the two subclasses and any extra
      * properties added to those prototype objects.
      */
-    if (JSOp(*pc) != JSOP_NEW)
+    if (JSOp(*pc) == JSOP_NEW)
+        pc += JSOP_NEW_LENGTH;
+    else if (JSOp(*pc) == JSOP_SPREADNEW)
+        pc += JSOP_SPREADNEW_LENGTH;
+    else
         return false;
-    pc += JSOP_NEW_LENGTH;
     if (JSOp(*pc) == JSOP_SETPROP) {
         jsid id = GetAtomId(cx, script, pc, 0);
         if (id == id_prototype(cx))
             return true;
     }
 
     return false;
 }
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1303,16 +1303,19 @@ ExpressionDecompiler::decompilePC(jsbyte
       case JSOP_THIS:
         // |this| could convert to a very long object initialiser, so cite it by
         // its keyword name.
         return write(js_this_str);
       case JSOP_CALL:
       case JSOP_FUNCALL:
         return decompilePC(pcstack[-int32_t(GET_ARGC(pc) + 2)]) &&
                write("(...)");
+      case JSOP_SPREADCALL:
+        return decompilePC(pcstack[-int32_t(3)]) &&
+               write("(...)");
       case JSOP_NEWARRAY:
         return write("[]");
       case JSOP_REGEXP:
       case JSOP_OBJECT: {
         JSObject *obj = (op == JSOP_REGEXP)
                         ? script->getRegExp(GET_UINT32_INDEX(pc))
                         : script->getObject(GET_UINT32_INDEX(pc));
         RootedValue objv(cx, ObjectValue(*obj));
@@ -2004,21 +2007,23 @@ js::CallResultEscapes(jsbytecode *pc)
      * If we see any of these sequences, the result is unused:
      * - call / pop
      *
      * If we see any of these sequences, the result is only tested for nullness:
      * - call / ifeq
      * - call / not / ifeq
      */
 
-    if (*pc != JSOP_CALL)
+    if (*pc == JSOP_CALL)
+        pc += JSOP_CALL_LENGTH;
+    else if (*pc == JSOP_SPREADCALL)
+        pc += JSOP_SPREADCALL_LENGTH;
+    else
         return true;
 
-    pc += JSOP_CALL_LENGTH;
-
     if (*pc == JSOP_POP)
         return false;
 
     if (*pc == JSOP_NOT)
         pc += JSOP_NOT_LENGTH;
 
     return (*pc != JSOP_IFEQ);
 }
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -85,19 +85,23 @@ OPDEF(JSOP_BITNOT,    33, "bitnot",     
 OPDEF(JSOP_NEG,       34, "neg",        "- ",         1,  1,  1, JOF_BYTE|JOF_ARITH)
 OPDEF(JSOP_POS,       35, "pos",        "+ ",         1,  1,  1, JOF_BYTE|JOF_ARITH)
 OPDEF(JSOP_DELNAME,   36, "delname",    NULL,         5,  0,  1, JOF_ATOM|JOF_NAME)
 OPDEF(JSOP_DELPROP,   37, "delprop",    NULL,         5,  1,  1, JOF_ATOM|JOF_PROP)
 OPDEF(JSOP_DELELEM,   38, "delelem",    NULL,         1,  2,  1, JOF_BYTE |JOF_ELEM)
 OPDEF(JSOP_TYPEOF,    39, js_typeof_str,NULL,         1,  1,  1, JOF_BYTE|JOF_DETECTING)
 OPDEF(JSOP_VOID,      40, js_void_str,  NULL,         1,  1,  1, JOF_BYTE)
 
-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)
+/* spreadcall variant of JSOP_CALL */
+OPDEF(JSOP_SPREADCALL,41, "spreadcall", NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
+/* spreadcall variant of JSOP_NEW */
+OPDEF(JSOP_SPREADNEW, 42, "spreadnew",  NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
+/* spreadcall variant of JSOP_EVAL */
+OPDEF(JSOP_SPREADEVAL,43, "spreadeval", NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
+
 OPDEF(JSOP_UNUSED44,  44, "unused44",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED45,  45, "unused45",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED46,  46, "unused46",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED47,  47, "unused47",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED48,  48, "unused48",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED49,  49, "unused49",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED50,  50, "unused50",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED51,  51, "unused51",   NULL,         1,  0,  0,  JOF_BYTE)
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2157,20 +2157,22 @@ js_GetScriptLineExtent(JSScript *script)
 void
 js::CurrentScriptFileLineOrigin(JSContext *cx, const char **file, unsigned *linenop,
                                 JSPrincipals **origin, LineOption opt)
 {
     if (opt == CALLED_FROM_JSOP_EVAL) {
         JSScript *script = NULL;
         jsbytecode *pc = NULL;
         types::TypeScript::GetPcScript(cx, &script, &pc);
-        JS_ASSERT(JSOp(*pc) == JSOP_EVAL);
-        JS_ASSERT(*(pc + JSOP_EVAL_LENGTH) == JSOP_LINENO);
+        JS_ASSERT(JSOp(*pc) == JSOP_EVAL || JSOp(*pc) == JSOP_SPREADEVAL);
+        JS_ASSERT(*(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH
+                                                 : JSOP_SPREADEVAL_LENGTH)) == JSOP_LINENO);
         *file = script->filename();
-        *linenop = GET_UINT16(pc + JSOP_EVAL_LENGTH);
+        *linenop = GET_UINT16(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH
+                                                           : JSOP_SPREADEVAL_LENGTH));
         *origin = script->originPrincipals();
         return;
     }
 
     NonBuiltinScriptFrameIter iter(cx);
 
     if (iter.done()) {
         *file = NULL;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1466,18 +1466,18 @@ extern unsigned
 PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecode *pc,
                unsigned *columnp = NULL);
 
 /*
  * This function returns the file and line number of the script currently
  * executing on cx. If there is no current script executing on cx (e.g., a
  * native called directly through JSAPI (e.g., by setTimeout)), NULL and 0 are
  * returned as the file and line. Additionally, this function avoids the full
- * linear scan to compute line number when the caller guarnatees that the
- * script compilation occurs at a JSOP_EVAL.
+ * linear scan to compute line number when the caller guarantees that the
+ * script compilation occurs at a JSOP_EVAL/JSOP_SPREADEVAL.
  */
 
 enum LineOption {
     CALLED_FROM_JSOP_EVAL,
     NOT_CALLED_FROM_JSOP_EVAL
 };
 
 extern void
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -2400,16 +2400,71 @@ BEGIN_CASE(JSOP_EVAL)
         if (!Invoke(cx, args))
             goto error;
     }
     regs.sp = args.spAfterCall();
     TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
 }
 END_CASE(JSOP_EVAL)
 
+BEGIN_CASE(JSOP_SPREADNEW)
+BEGIN_CASE(JSOP_SPREADCALL)
+    if (regs.fp()->hasPushedSPSFrame())
+        cx->runtime()->spsProfiler.updatePC(script, regs.pc);
+    /* FALL THROUGH */
+
+BEGIN_CASE(JSOP_SPREADEVAL)
+{
+    JS_ASSERT(regs.stackDepth() >= 3);
+    RootedObject &aobj = rootObject0;
+    aobj = &regs.sp[-1].toObject();
+
+    uint32_t length = aobj->as<ArrayObject>().length();
+
+    if (length > ARGS_LENGTH_MAX) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             op == JSOP_SPREADNEW ? JSMSG_TOO_MANY_CON_SPREADARGS
+                                                  : JSMSG_TOO_MANY_FUN_SPREADARGS);
+        goto error;
+    }
+
+    InvokeArgs args(cx);
+
+    if (!args.init(length))
+        return false;
+
+    args.setCallee(regs.sp[-3]);
+    args.setThis(regs.sp[-2]);
+
+    if (!GetElements(cx, aobj, length, args.array()))
+        goto error;
+
+    if (op == JSOP_SPREADNEW) {
+        if (!InvokeConstructor(cx, args))
+            goto error;
+    } else if (op == JSOP_SPREADCALL) {
+        if (!Invoke(cx, args))
+            goto error;
+    } else {
+        JS_ASSERT(op == JSOP_SPREADEVAL);
+        if (IsBuiltinEvalForScope(regs.fp()->scopeChain(), args.calleev())) {
+            if (!DirectEval(cx, args))
+                goto error;
+        } else {
+            if (!Invoke(cx, args))
+                goto error;
+        }
+    }
+
+    regs.sp -= 2;
+    regs.sp[-1] = args.rval();
+    TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
+}
+END_CASE(JSOP_SPREADCALL)
+
 BEGIN_CASE(JSOP_FUNAPPLY)
 {
     CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
     if (!GuardFunApplyArgumentsOptimization(cx, regs.fp(), args.calleev(), args.array(),
                                             args.length()))
         goto error;
     /* FALL THROUGH */
 }
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -17,17 +17,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 151);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 152);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(NULL), cursor(NULL), limit(NULL) { }
 
     JSContext *cx() const {
         return context;