Bug 907744 - Implement iterator result boxing in ES6 generators. r=jorendorff
authorAndy Wingo <wingo@igalia.com>
Wed, 04 Sep 2013 13:20:48 -0400
changeset 145475 02b05f0a72278ab527d5b12ee7a76ad2218ae1b0
parent 145474 1f401bf37ef400bcd98fefa243a5fc726dcfab8e
child 145476 86114140b7973b8032684daf778efb7e3f32125f
push id25213
push userkwierso@gmail.com
push dateWed, 04 Sep 2013 23:18:26 +0000
treeherdermozilla-central@dffedf20a02d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs907744
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 907744 - Implement iterator result boxing in ES6 generators. r=jorendorff This patchset causes the bytecode emitter to yield boxed return values in ES6 generators, of the form { value: foo, done: bool }. If the generator function does not end in a return, the compiler inserts the equivalent of a "return { value: undefined, done:true }" statement at the end. When an ES6 generator finishes, it does so with {done:true} instead of throwing a StopIteration. This patch also ports lib/asserts.js to "default version" JS, and ports ecma_6/Generators to use it.
js/src/frontend/BytecodeEmitter.cpp
js/src/jit-test/lib/asserts.js
js/src/js.msg
js/src/jsiter.cpp
js/src/jsiter.h
js/src/tests/ecma_6/Generators/iteration.js
js/src/tests/ecma_6/Generators/objects.js
js/src/tests/ecma_6/Generators/runtime.js
js/src/tests/ecma_6/Generators/shell.js
js/src/tests/ecma_6/shell.js
js/src/vm/CommonPropertyNames.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1786,16 +1786,72 @@ BytecodeEmitter::reportStrictModeError(P
     va_start(args, errorNumber);
     bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict,
                                                                errorNumber, args);
     va_end(args);
     return result;
 }
 
 static bool
+IteratorResultShape(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned *shape)
+{
+    RootedObject obj(cx);
+    gc::AllocKind kind = GuessObjectGCKind(2);
+    obj = NewBuiltinClassInstance(cx, &JSObject::class_, kind);
+    if (!obj)
+        return false;
+
+    Rooted<jsid> value_id(cx, AtomToId(cx->names().value));
+    Rooted<jsid> done_id(cx, AtomToId(cx->names().done));
+    RootedValue undefined(cx, UndefinedValue());
+    if (!DefineNativeProperty(cx, obj, value_id, undefined, NULL, NULL, JSPROP_ENUMERATE, 0, 0))
+        return false;
+    if (!DefineNativeProperty(cx, obj, done_id, undefined, NULL, NULL, JSPROP_ENUMERATE, 0, 0))
+        return false;
+
+    ObjectBox *objbox = bce->parser->newObjectBox(obj);
+    if (!objbox)
+        return false;
+
+    *shape = bce->objectList.add(objbox);
+
+    return true;
+}
+
+static bool
+EmitPrepareIteratorResult(ExclusiveContext *cx, BytecodeEmitter *bce)
+{
+    unsigned shape;
+    if (!IteratorResultShape(cx, bce, &shape))
+        return false;
+    return EmitIndex32(cx, JSOP_NEWOBJECT, shape, bce);
+}
+
+static bool
+EmitFinishIteratorResult(ExclusiveContext *cx, BytecodeEmitter *bce, bool done)
+{
+    jsatomid value_id;
+    if (!bce->makeAtomIndex(cx->names().value, &value_id))
+        return UINT_MAX;
+    jsatomid done_id;
+    if (!bce->makeAtomIndex(cx->names().done, &done_id))
+        return UINT_MAX;
+
+    if (!EmitIndex32(cx, JSOP_INITPROP, value_id, bce))
+        return false;
+    if (Emit1(cx, bce, done ? JSOP_TRUE : JSOP_FALSE) < 0)
+        return false;
+    if (!EmitIndex32(cx, JSOP_INITPROP, done_id, bce))
+        return false;
+    if (Emit1(cx, bce, JSOP_ENDINIT) < 0)
+        return false;
+    return true;
+}
+
+static bool
 EmitNameOp(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool callContext)
 {
     JSOp op;
 
     if (!BindNameToSlot(cx, bce, pn))
         return false;
     op = pn->getOp();
 
@@ -2568,16 +2624,31 @@ frontend::EmitFunctionScript(ExclusiveCo
         if (Emit1(cx, bce, JSOP_RUNONCE) < 0)
             return false;
         bce->switchToMain();
     }
 
     if (!EmitTree(cx, bce, body))
         return false;
 
+    // If we fall off the end of an ES6 generator, return a boxed iterator
+    // result object of the form { value: undefined, done: true }.
+    if (bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isStarGenerator()) {
+        if (!EmitPrepareIteratorResult(cx, bce))
+            return false;
+        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+            return false;
+        if (!EmitFinishIteratorResult(cx, bce, true))
+            return false;
+
+        // No need to check for finally blocks, etc as in EmitReturn.
+        if (Emit1(cx, bce, JSOP_RETURN) < 0)
+            return false;
+    }
+
     /*
      * Always end the script with a JSOP_STOP. Some other parts of the codebase
      * depend on this opcode, e.g. js_InternalInterpret.
      */
     if (Emit1(cx, bce, JSOP_STOP) < 0)
         return false;
 
     if (!JSScript::fullyInitFromEmitter(cx, bce->script, bce))
@@ -4767,26 +4838,36 @@ EmitContinue(ExclusiveContext *cx, Bytec
 }
 
 static bool
 EmitReturn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin))
         return false;
 
+    if (bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isStarGenerator()) {
+        if (!EmitPrepareIteratorResult(cx, bce))
+            return false;
+    }
+
     /* Push a return value */
     if (ParseNode *pn2 = pn->pn_kid) {
         if (!EmitTree(cx, bce, pn2))
             return false;
     } else {
         /* No explicit return value provided */
         if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
             return false;
     }
 
+    if (bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isStarGenerator()) {
+        if (!EmitFinishIteratorResult(cx, bce, true))
+            return false;
+    }
+
     /*
      * EmitNonLocalJumpFixup may add fixup bytecode to close open try
      * blocks having finally clauses and to exit intermingled let blocks.
      * We can't simply transfer control flow to our caller in that case,
      * because we must gosub to those finally clauses from inner to outer,
      * with the correct stack pointer (i.e., after popping any with,
      * for/in, etc., slots nested inside the finally's try).
      *
@@ -5788,23 +5869,31 @@ frontend::EmitTree(ExclusiveContext *cx,
         break;
 
       case PNK_RETURN:
         ok = EmitReturn(cx, bce, pn);
         break;
 
       case PNK_YIELD:
         JS_ASSERT(bce->sc->isFunctionBox());
+        if (bce->sc->asFunctionBox()->isStarGenerator()) {
+            if (!EmitPrepareIteratorResult(cx, bce))
+                return false;
+        }
         if (pn->pn_kid) {
             if (!EmitTree(cx, bce, pn->pn_kid))
                 return false;
         } else {
             if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
                 return false;
         }
+        if (bce->sc->asFunctionBox()->isStarGenerator()) {
+            if (!EmitFinishIteratorResult(cx, bce, false))
+                return false;
+        }
         if (pn->pn_hidden && NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
             return false;
         if (Emit1(cx, bce, JSOP_YIELD) < 0)
             return false;
         break;
 
       case PNK_STATEMENTLIST:
         ok = EmitStatementList(cx, bce, pn, top);
--- a/js/src/jit-test/lib/asserts.js
+++ b/js/src/jit-test/lib/asserts.js
@@ -1,48 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
-if (typeof assertThrowsInstanceOf === 'undefined') {
-    var assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {
-        var fullmsg;
-        try {
-            f();
-        } catch (exc) {
-            if (exc instanceof ctor)
-                return;
-            fullmsg = "Assertion failed: expected exception " + ctor.name + ", got " + exc;
-        }
-        if (fullmsg === undefined)
-            fullmsg = "Assertion failed: expected exception " + ctor.name + ", no exception thrown";
-        if (msg !== undefined)
-            fullmsg += " - " + msg;
-        throw new Error(fullmsg);
-    };
-}
-
-if (typeof assertThrowsValue === 'undefined') {
-    var assertThrowsValue = function assertThrowsValue(f, val, msg) {
-        var fullmsg;
-        try {
-            f();
-        } catch (exc) {
-            if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
-                return;
-            fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
-        }
-        if (fullmsg === undefined)
-            fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
-        if (msg !== undefined)
-            fullmsg += " - " + msg;
-        throw new Error(fullmsg);
-    };
-}
+load(libdir + "../../tests/ecma_6/shell.js");
 
 if (typeof assertWarning === 'undefined') {
     var assertWarning = function assertWarning(f, errorClass, msg) {
         var hadWerror = options().split(",").indexOf("werror") !== -1;
 
         // Ensure the "werror" option is disabled.
         if (hadWerror)
             options("werror");
@@ -94,139 +60,8 @@ if (typeof assertNoWarning === 'undefine
                   "with warnings-as-errors enabled");
             throw exc;
         } finally {
             if (!hadWerror)
                 options("werror");
         }
     };
 }
-
-if (typeof assertDeepEq === 'undefined') {
-    let call = Function.prototype.call,
-        Map_ = Map,
-        Error_ = Error,
-        Map_has = call.bind(Map.prototype.has),
-        Map_get = call.bind(Map.prototype.get),
-        Map_set = call.bind(Map.prototype.set),
-        Object_toString = call.bind(Object.prototype.toString),
-        Function_toString = call.bind(Function.prototype.toString),
-        Object_getPrototypeOf = Object.getPrototypeOf,
-        Object_hasOwnProperty = call.bind(Object.prototype.hasOwnProperty),
-        Object_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
-        Object_isExtensible = Object.isExtensible,
-        Object_getOwnPropertyNames = Object.getOwnPropertyNames,
-        uneval_ = uneval;
-
-    // Return true iff ES6 Type(v) isn't Object.
-    // Note that `typeof document.all === "undefined"`.
-    let isPrimitive = v =>
-        v === null ||
-        v === undefined ||
-        typeof v === "boolean" ||
-        typeof v === "number" ||
-        typeof v === "string" ||
-        typeof v === "symbol";
-
-    let assertSameValue = (a, b, msg) => {
-        try {
-            assertEq(a, b);
-        } catch (exc) {
-            throw new Error(exc.message + (msg ? " " + msg : ""));
-        }
-    };
-
-    let assertSameClass = (a, b, msg) => {
-        var ac = Object_toString(a), bc = Object_toString(b);
-        assertSameValue(ac, bc, msg);
-        switch (ac) {
-        case "[object Function]":
-            assertSameValue(Function_toString(a), Function_toString(b), msg);
-        }
-    };
-
-    let at = (prevmsg, segment) => prevmsg ? prevmsg + segment : "at _" + segment;
-
-    // Assert that the arguments a and b are thoroughly structurally equivalent.
-    //
-    // For the sake of speed, we cut a corner:
-    //        var x = {}, y = {}, ax = [x];
-    //        assertDeepEq([ax, x], [ax, y]);  // passes (?!)
-    //
-    // Technically this should fail, since the two object graphs are different.
-    // (The graph of [ax, y] contains one more object than the graph of [ax, x].)
-    //
-    // To get technically correct behavior, pass {strictEquivalence: true}.
-    // This is slower because we have to walk the entire graph, and Object.prototype
-    // is big.
-    //
-    var assertDeepEq = function assertDeepEq(a, b, options) {
-        let strictEquivalence = options ? options.strictEquivalence : false;
-
-        let assertSameProto = (a, b, msg) => {
-            check(Object_getPrototypeOf(a), Object_getPrototypeOf(b), at(msg, ".__proto__"))
-        };
-
-        let failPropList = (na, nb, msg) => {
-            throw Error_("got own properties " + uneval_(na) + ", expected " + uneval_(nb) +
-                         (msg ? " " + msg : ""));
-        }
-
-        let assertSameProps = (a, b, msg) => {
-            var na = Object_getOwnPropertyNames(a),
-                nb = Object_getOwnPropertyNames(b);
-            if (na.length !== nb.length)
-                failPropList(na, nb, msg);
-            for (var i = 0; i < na.length; i++) {
-                var name = na[i];
-                if (name !== nb[i])
-                    failPropList(na, nb, msg);
-                var da = Object_getOwnPropertyDescriptor(a, name),
-                    db = Object_getOwnPropertyDescriptor(b, name);
-                var pmsg = at(msg, /^[_$A-Za-z0-9]+$/.test(name)
-                                   ? /0|[1-9][0-9]*/.test(name) ? "[" + name + "]" : "." + name
-                                   : "[" + uneval_(name) + "]");
-                assertSameValue(da.configurable, db.configurable, at(pmsg, ".[[Configurable]]"));
-                assertSameValue(da.enumerable, db.enumerable, at(pmsg, ".[[Enumerable]]"));
-                if (Object_hasOwnProperty(da, "value")) {
-                    if (!Object_hasOwnProperty(db, "value"))
-                        throw Error_("got data property, expected accessor property" + pmsg);
-                    check(da.value, db.value, pmsg);
-                } else {
-                    if (Object_hasOwnProperty(db, "value"))
-                        throw Error_("got accessor property, expected data property" + pmsg);
-                    check(da.get, db.get, at(pmsg, ".[[Get]]"));
-                    check(da.set, db.set, at(pmsg, ".[[Set]]"));
-                }
-            }
-        };
-
-        var ab = Map_();
-        var bpath = Map_();
-
-        let check = (a, b, path) => {
-            if (isPrimitive(a)) {
-                assertSameValue(a, b, path);
-            } else if (isPrimitive(b)) {
-                throw Error_("got " + Object_toString(a) + ", expected " + uneval_(b) + " " + path);
-            } else if (Map_has(ab, a)) {
-                assertSameValue(Map_get(ab, a), b, path);
-            } else if (Map_has(bpath, b)) {
-                var bPrevPath = Map_get(bpath, b) || "_";
-                throw Error_("got distinct objects " + at(path, "") + " and " + at(bPrevPath, "") +
-                             ", expected the same object both places");
-            } else {
-                Map_set(ab, a, b);
-                Map_set(bpath, b, path);
-                if (a !== b || strictEquivalence) {
-                    assertSameClass(a, b, path);
-                    assertSameProto(a, b, path);
-                    assertSameProps(a, b, path);
-                    assertSameValue(Object_isExtensible(a),
-                                    Object_isExtensible(b),
-                                    at(path, ".[[Extensible]]"));
-                }
-            }
-        }
-
-        check(a, b, "");
-    };
-}
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -404,11 +404,11 @@ MSG_DEF(JSMSG_DEPRECATED_SOURCE_MAP,  35
 MSG_DEF(JSMSG_BAD_DESTRUCT_ASSIGN,    351, 1, JSEXN_SYNTAXERR, "can't assign to {0} using destructuring assignment")
 MSG_DEF(JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS, 352, 0, JSEXN_ERR, "Invalid arguments")
 MSG_DEF(JSMSG_BINARYDATA_BINARYARRAY_BAD_INDEX, 353, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_BINARYDATA_STRUCTTYPE_BAD_ARGS, 354, 0, JSEXN_RANGEERR, "invalid field descriptor")
 MSG_DEF(JSMSG_BINARYDATA_NOT_BINARYSTRUCT,   355, 1, JSEXN_TYPEERR, "{0} is not a BinaryStruct")
 MSG_DEF(JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, 356, 1, JSEXN_ERR, "argument {0} must be an integer")
 MSG_DEF(JSMSG_BINARYDATA_STRUCTTYPE_EMPTY_DESCRIPTOR, 357, 0, JSEXN_ERR, "field descriptor cannot be empty")
 MSG_DEF(JSMSG_BINARYDATA_STRUCTTYPE_BAD_FIELD, 358, 1, JSEXN_ERR, "field {0} is not a valid BinaryData Type descriptor")
-MSG_DEF(JSMSG_ES6_UNIMPLEMENTED,      359, 0, JSEXN_ERR, "ES6 functionality not yet implemented")
+MSG_DEF(JSMSG_GENERATOR_FINISHED,     359, 0, JSEXN_TYPEERR, "generator has already finished")
 MSG_DEF(JSMSG_BINARYDATA_TOO_BIG, 360, 0, JSEXN_ERR, "Type is too large to allocate")
 MSG_DEF(JSMSG_BINARYDATA_NOT_TYPE_OBJECT, 361, 0, JSEXN_ERR, "Expected a type object")
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -995,34 +995,16 @@ js::ValueToIterator(JSContext *cx, unsig
                 return false;
         }
     }
 
     return GetIterator(cx, obj, flags, vp);
 }
 
 bool
-IsStarGeneratorObject(HandleValue v)
-{
-    return v.isObject() && v.toObject().is<StarGeneratorObject>();
-}
-
-bool
-IsLegacyGeneratorObject(HandleValue v)
-{
-    return v.isObject() && v.toObject().is<LegacyGeneratorObject>();
-}
-
-bool
-IsGeneratorObject(HandleValue v)
-{
-    return IsLegacyGeneratorObject(v) || IsStarGeneratorObject(v);
-}
-
-bool
 js::CloseIterator(JSContext *cx, HandleObject obj)
 {
     cx->iterValue.setMagic(JS_NO_ITER_VALUE);
 
     if (obj->is<PropertyIteratorObject>()) {
         /* Remove enumerators from the active list, which is a stack. */
         NativeIterator *ni = obj->as<PropertyIteratorObject>().getNativeIterator();
 
@@ -1334,36 +1316,25 @@ Class StopIterationObject::class_ = {
     NULL,                    /* checkAccess */
     NULL,                    /* call        */
     stopiter_hasInstance,
     NULL                     /* construct   */
 };
 
 /*** Generators **********************************************************************************/
 
-static JSGenerator*
-GetGenerator(JSObject *obj)
-{
-    if (obj->is<LegacyGeneratorObject>())
-        return obj->as<LegacyGeneratorObject>().getGenerator();
-    JS_ASSERT(obj->is<StarGeneratorObject>());
-    return obj->as<StarGeneratorObject>().getGenerator();
-}
-
+template<typename T>
 static void
-generator_finalize(FreeOp *fop, JSObject *obj)
+FinalizeGenerator(FreeOp *fop, JSObject *obj)
 {
-    JSGenerator *gen = GetGenerator(obj);
-    if (!gen)
-        return;
-
-    /*
-     * gen is open when a script has not called its close method while
-     * explicitly manipulating it.
-     */
+    JS_ASSERT(obj->is<T>());
+    JSGenerator *gen = obj->as<T>().getGenerator();
+    JS_ASSERT(gen);
+    // gen is open when a script has not called its close method while
+    // explicitly manipulating it.
     JS_ASSERT(gen->state == JSGEN_NEWBORN ||
               gen->state == JSGEN_CLOSED ||
               gen->state == JSGEN_OPEN);
     // If gen->state is JSGEN_CLOSED, gen->fp may be NULL.
     if (gen->fp)
         JS_POISON(gen->fp, JS_FREE_PATTERN, sizeof(StackFrame));
     JS_POISON(gen, JS_FREE_PATTERN, sizeof(JSGenerator));
     fop->free_(gen);
@@ -1399,18 +1370,18 @@ GeneratorWriteBarrierPost(JSContext *cx,
 #endif
 }
 
 /*
  * Only mark generator frames/slots when the generator is not active on the
  * stack or closed. Barriers when copying onto the stack or closing preserve
  * gc invariants.
  */
-bool
-js::GeneratorHasMarkableFrame(JSGenerator *gen)
+static bool
+GeneratorHasMarkableFrame(JSGenerator *gen)
 {
     return gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN;
 }
 
 /*
  * When a generator is closed, the GC things reachable from the contained frame
  * and slots become unreachable and thus require a write barrier.
  */
@@ -1427,16 +1398,27 @@ SetGeneratorClosed(JSContext *cx, JSGene
                     gen->fp->generatorArgsSnapshotEnd());
     MakeRangeGCSafe(gen->fp->generatorSlotsSnapshotBegin(),
                     gen->regs.sp);
     PodZero(&gen->regs, 1);
     gen->fp = NULL;
 #endif
 }
 
+template<typename T>
+static void
+TraceGenerator(JSTracer *trc, JSObject *obj)
+{
+    JS_ASSERT(obj->is<T>());
+    JSGenerator *gen = obj->as<T>().getGenerator();
+    JS_ASSERT(gen);
+    if (GeneratorHasMarkableFrame(gen))
+        MarkGeneratorFrame(trc, gen);
+}
+
 GeneratorState::GeneratorState(JSContext *cx, JSGenerator *gen, JSGeneratorState futureState)
   : RunState(cx, Generator, gen->fp->script()),
     cx_(cx),
     gen_(gen),
     futureState_(futureState),
     entered_(false)
 { }
 
@@ -1467,43 +1449,32 @@ GeneratorState::pushInterpreterFrame(JSC
 
     gen_->fp->clearSuspended();
 
     cx->enterGenerator(gen_);   /* OOM check above. */
     entered_ = true;
     return gen_->fp;
 }
 
-static void
-generator_trace(JSTracer *trc, JSObject *obj)
-{
-    JSGenerator *gen = GetGenerator(obj);
-    if (!gen)
-        return;
-
-    if (GeneratorHasMarkableFrame(gen))
-        MarkGeneratorFrame(trc, gen);
-}
-
 Class LegacyGeneratorObject::class_ = {
     "Generator",
     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS,
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
-    generator_finalize,
+    FinalizeGenerator<LegacyGeneratorObject>,
     NULL,                    /* checkAccess */
     NULL,                    /* call        */
     NULL,                    /* hasInstance */
     NULL,                    /* construct   */
-    generator_trace,
+    TraceGenerator<LegacyGeneratorObject>,
     {
         NULL,                /* outerObject    */
         NULL,                /* innerObject    */
         iterator_iteratorObject,
     }
 };
 
 Class StarGeneratorObject::class_ = {
@@ -1511,22 +1482,22 @@ Class StarGeneratorObject::class_ = {
     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS,
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
-    generator_finalize,
+    FinalizeGenerator<StarGeneratorObject>,
     NULL,                    /* checkAccess */
     NULL,                    /* call        */
     NULL,                    /* hasInstance */
     NULL,                    /* construct   */
-    generator_trace,
+    TraceGenerator<StarGeneratorObject>,
     {
         NULL,                /* outerObject    */
         NULL,                /* innerObject    */
         iterator_iteratorObject,
     }
 };
 
 /*
@@ -1621,18 +1592,21 @@ typedef enum JSGeneratorOp {
 } JSGeneratorOp;
 
 /*
  * Start newborn or restart yielding generator and perform the requested
  * operation inside its frame.
  */
 static bool
 SendToGenerator(JSContext *cx, JSGeneratorOp op, HandleObject obj,
-                JSGenerator *gen, HandleValue arg)
+                JSGenerator *gen, HandleValue arg, GeneratorKind generatorKind,
+                MutableHandleValue rval)
 {
+    JS_ASSERT(generatorKind == LegacyGenerator || generatorKind == StarGenerator);
+
     if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NESTING_GENERATOR);
         return false;
     }
 
     JSGeneratorState futureState;
     JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
     switch (op) {
@@ -1653,16 +1627,17 @@ SendToGenerator(JSContext *cx, JSGenerat
 
       case JSGENOP_THROW:
         cx->setPendingException(arg);
         futureState = JSGEN_RUNNING;
         break;
 
       default:
         JS_ASSERT(op == JSGENOP_CLOSE);
+        JS_ASSERT(generatorKind == LegacyGenerator);
         cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING));
         futureState = JSGEN_CLOSING;
         break;
     }
 
     bool ok;
     {
         GeneratorState state(cx, gen, futureState);
@@ -1676,155 +1651,180 @@ SendToGenerator(JSContext *cx, JSGenerat
          * Yield is ordinarily infallible, but ok can be false here if a
          * Debugger.Frame.onPop hook fails.
          */
         JS_ASSERT(gen->state == JSGEN_RUNNING);
         JS_ASSERT(op != JSGENOP_CLOSE);
         gen->fp->clearYielding();
         gen->state = JSGEN_OPEN;
         GeneratorWriteBarrierPost(cx, gen);
+        rval.set(gen->fp->returnValue());
         return ok;
     }
 
-    gen->fp->clearReturnValue();
-    SetGeneratorClosed(cx, gen);
     if (ok) {
-        /* Returned, explicitly or by falling off the end. */
-        if (op == JSGENOP_CLOSE)
-            return true;
-        return js_ThrowStopIteration(cx);
+        if (generatorKind == StarGenerator) {
+            // Star generators return a {value:FOO, done:true} object.
+            rval.set(gen->fp->returnValue());
+        } else {
+            JS_ASSERT(generatorKind == LegacyGenerator);
+
+            // Otherwise we discard the return value and throw a StopIteration
+            // if needed.
+            rval.setUndefined();
+            if (op != JSGENOP_CLOSE)
+                ok = js_ThrowStopIteration(cx);
+        }
     }
 
-    /*
-     * An error, silent termination by operation callback or an exception.
-     * Propagate the condition to the caller.
-     */
-    return false;
-}
-
-static bool
-CloseLegacyGenerator(JSContext *cx, HandleObject obj)
-{
-    JS_ASSERT(obj->is<LegacyGeneratorObject>());
-
-    JSGenerator *gen = GetGenerator(obj);
-    if (gen->state == JSGEN_CLOSED)
-        return true;
-
-    return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, JS::UndefinedHandleValue);
+    SetGeneratorClosed(cx, gen);
+    return ok;
 }
 
 JS_ALWAYS_INLINE bool
-generator_next_impl(JSContext *cx, CallArgs args)
+star_generator_next(JSContext *cx, CallArgs args)
 {
-    JS_ASSERT(IsGeneratorObject(args.thisv()));
+    RootedObject thisObj(cx, &args.thisv().toObject());
+    JSGenerator *gen = thisObj->as<StarGeneratorObject>().getGenerator();
+
+    if (gen->state == JSGEN_CLOSED) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_GENERATOR_FINISHED);
+        return false;
+    }
 
+    if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) {
+        RootedValue val(cx, args[0]);
+        js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND,
+                            JSDVG_SEARCH_STACK, val, NullPtr());
+        return false;
+    }
+
+    return SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0), StarGenerator,
+                           args.rval());
+}
+
+JS_ALWAYS_INLINE bool
+star_generator_throw(JSContext *cx, CallArgs args)
+{
     RootedObject thisObj(cx, &args.thisv().toObject());
 
-    JSGenerator *gen = GetGenerator(thisObj);
+    JSGenerator *gen = thisObj->as<StarGeneratorObject>().getGenerator();
+    if (gen->state == JSGEN_CLOSED) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_GENERATOR_FINISHED);
+        return false;
+    }
+
+    return SendToGenerator(cx, JSGENOP_THROW, thisObj, gen, args.get(0), StarGenerator,
+                           args.rval());
+}
+
+JS_ALWAYS_INLINE bool
+legacy_generator_next(JSContext *cx, CallArgs args)
+{
+    RootedObject thisObj(cx, &args.thisv().toObject());
+
+    JSGenerator *gen = thisObj->as<LegacyGeneratorObject>().getGenerator();
     if (gen->state == JSGEN_CLOSED)
         return js_ThrowStopIteration(cx);
 
     if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) {
         RootedValue val(cx, args[0]);
         js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND,
                             JSDVG_SEARCH_STACK, val, NullPtr());
         return false;
     }
 
-    if (!SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0)))
-        return false;
-
-    args.rval().set(gen->fp->returnValue());
-    return true;
-}
-
-bool
-generator_next(JSContext *cx, unsigned argc, Value *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsGeneratorObject, generator_next_impl>(cx, args);
+    return SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0), LegacyGenerator,
+                           args.rval());
 }
 
 JS_ALWAYS_INLINE bool
-generator_throw_impl(JSContext *cx, CallArgs args)
+legacy_generator_throw(JSContext *cx, CallArgs args)
 {
-    JS_ASSERT(IsGeneratorObject(args.thisv()));
-
     RootedObject thisObj(cx, &args.thisv().toObject());
 
-    JSGenerator *gen = GetGenerator(thisObj);
+    JSGenerator *gen = thisObj->as<LegacyGeneratorObject>().getGenerator();
     if (gen->state == JSGEN_CLOSED) {
         cx->setPendingException(args.length() >= 1 ? args[0] : UndefinedValue());
         return false;
     }
 
-    if (!SendToGenerator(cx, JSGENOP_THROW, thisObj, gen, args.get(0)))
-        return false;
-
-    args.rval().set(gen->fp->returnValue());
-    return true;
+    return SendToGenerator(cx, JSGENOP_THROW, thisObj, gen, args.get(0), LegacyGenerator,
+                           args.rval());
 }
 
-bool
-generator_throw(JSContext *cx, unsigned argc, Value *vp)
+static bool
+CloseLegacyGenerator(JSContext *cx, HandleObject obj, MutableHandleValue rval)
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsGeneratorObject, generator_throw_impl>(cx, args);
-}
+    JS_ASSERT(obj->is<LegacyGeneratorObject>());
 
-JS_ALWAYS_INLINE bool
-generator_close_impl(JSContext *cx, CallArgs args)
-{
-    JS_ASSERT(IsLegacyGeneratorObject(args.thisv()));
+    JSGenerator *gen = obj->as<LegacyGeneratorObject>().getGenerator();
 
-    RootedObject thisObj(cx, &args.thisv().toObject());
-
-    JSGenerator *gen = GetGenerator(thisObj);
     if (gen->state == JSGEN_CLOSED) {
-        args.rval().setUndefined();
+        rval.setUndefined();
         return true;
     }
 
     if (gen->state == JSGEN_NEWBORN) {
         SetGeneratorClosed(cx, gen);
-        args.rval().setUndefined();
+        rval.setUndefined();
         return true;
     }
 
-    if (!SendToGenerator(cx, JSGENOP_CLOSE, thisObj, gen, JS::UndefinedHandleValue))
-        return false;
+    return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, JS::UndefinedHandleValue, LegacyGenerator,
+                           rval);
+}
 
-    args.rval().setUndefined();
-    return true;
+static bool
+CloseLegacyGenerator(JSContext *cx, HandleObject obj)
+{
+    RootedValue rval(cx);
+    return CloseLegacyGenerator(cx, obj, &rval);
 }
 
-bool
-generator_close(JSContext *cx, unsigned argc, Value *vp)
+JS_ALWAYS_INLINE bool
+legacy_generator_close(JSContext *cx, CallArgs args)
+{
+    RootedObject thisObj(cx, &args.thisv().toObject());
+
+    return CloseLegacyGenerator(cx, thisObj, args.rval());
+}
+
+template<typename T>
+JS_ALWAYS_INLINE bool
+IsObjectOfType(HandleValue v)
+{
+    return v.isObject() && v.toObject().is<T>();
+}
+
+template<typename T, NativeImpl Impl>
+static bool
+NativeMethod(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsLegacyGeneratorObject, generator_close_impl>(cx, args);
+    return CallNonGenericMethod<IsObjectOfType<T>, Impl>(cx, args);
 }
 
 #define JSPROP_ROPERM   (JSPROP_READONLY | JSPROP_PERMANENT)
+#define JS_METHOD(name, T, impl, len, perms) JS_FN(name, (NativeMethod<T,impl>), len, perms)
 
-static const JSFunctionSpec legacy_generator_methods[] = {
-    JS_FN("iterator",  iterator_iterator,  0, 0),
-    JS_FN("next",      generator_next,     1,JSPROP_ROPERM),
-    // Send is exactly the same as next.
-    JS_FN("send",      generator_next,     1,JSPROP_ROPERM),
-    JS_FN("throw",     generator_throw,    1,JSPROP_ROPERM),
-    JS_FN("close",     generator_close,    0,JSPROP_ROPERM),
+static const JSFunctionSpec star_generator_methods[] = {
+    JS_FN("iterator", iterator_iterator, 0, 0),
+    JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, JSPROP_ROPERM),
+    JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, JSPROP_ROPERM),
     JS_FS_END
 };
 
-static const JSFunctionSpec star_generator_methods[] = {
-    JS_FN("iterator",  iterator_iterator,  0, 0),
-    JS_FN("next",      generator_next,     1,JSPROP_ROPERM),
-    JS_FN("throw",     generator_throw,    1,JSPROP_ROPERM),
+static const JSFunctionSpec legacy_generator_methods[] = {
+    JS_FN("iterator", iterator_iterator, 0, 0),
+    // "send" is an alias for "next".
+    JS_METHOD("next", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM),
+    JS_METHOD("send", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM),
+    JS_METHOD("throw", LegacyGeneratorObject, legacy_generator_throw, 1, JSPROP_ROPERM),
+    JS_METHOD("close", LegacyGeneratorObject, legacy_generator_close, 0, JSPROP_ROPERM),
     JS_FS_END
 };
 
 static JSObject*
 NewObjectWithObjectPrototype(JSContext *cx, Handle<GlobalObject *> global)
 {
     JSObject *proto = global->getOrCreateObjectPrototype(cx);
     if (!proto)
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -346,19 +346,12 @@ struct JSGenerator
     JSGenerator         *prevGenerator;
     js::StackFrame      *fp;
     js::HeapValue       stackSnapshot[1];
 };
 
 extern JSObject *
 js_NewGenerator(JSContext *cx, const js::FrameRegs &regs);
 
-namespace js {
-
-bool
-GeneratorHasMarkableFrame(JSGenerator *gen);
-
-} /* namespace js */
-
 extern JSObject *
 js_InitIteratorClasses(JSContext *cx, js::HandleObject obj);
 
 #endif /* jsiter_h */
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Generators/iteration.js
@@ -0,0 +1,615 @@
+// This file was written by Andy Wingo <wingo@igalia.com> and originally
+// contributed to V8 as generators-objects.js, available here:
+//
+// http://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/harmony/generators-objects.js
+
+// Test aspects of the generator runtime.
+
+
+var GeneratorFunction = (function*(){yield 1;}).constructor;
+
+
+function TestGeneratorResultPrototype() {
+    function* g() { yield 1; }
+    var iter = g();
+    var result = iter.next();
+    assertIteratorResult(1, false, result);
+    result = iter.next();
+    assertIteratorResult(undefined, true, result);
+    assertThrowsInstanceOf(function() { iter.next() }, TypeError);
+}
+TestGeneratorResultPrototype();
+
+function TestGenerator(g, expected_values_for_next,
+                       send_val, expected_values_for_send) {
+    function testNext(thunk) {
+        var iter = thunk();
+        for (var i = 0; i < expected_values_for_next.length; i++) {
+            assertIteratorResult(expected_values_for_next[i],
+                                 i == expected_values_for_next.length - 1,
+                                 iter.next());
+        }
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    function testSend(thunk) {
+        var iter = thunk();
+        for (var i = 0; i < expected_values_for_send.length; i++) {
+            assertIteratorResult(expected_values_for_send[i],
+                                 i == expected_values_for_send.length - 1,
+                                 i ? iter.next(send_val) : iter.next());
+        }
+        assertThrowsInstanceOf(function() { iter.next(send_val); }, TypeError);
+    }
+    function testThrow(thunk) {
+        for (var i = 0; i < expected_values_for_next.length; i++) {
+            var iter = thunk();
+            for (var j = 0; j < i; j++) {
+                assertIteratorResult(expected_values_for_next[j],
+                                     j == expected_values_for_next.length - 1,
+                                     iter.next());
+            }
+            var Sentinel = function () {}
+            assertThrowsInstanceOf(function () { iter.throw(new Sentinel); }, Sentinel);
+            assertThrowsInstanceOf(function () { iter.next(); }, TypeError);
+        }
+    }
+
+    testNext(g);
+    testSend(g);
+    testThrow(g);
+
+    // FIXME: Implement yield*.  Bug 907738.
+    //
+    // testNext(function*() { return yield* g(); });
+    // testSend(function*() { return yield* g(); });
+    // testThrow(function*() { return yield* g(); });
+
+    if (g instanceof GeneratorFunction) {
+        testNext(function() { return new g(); });
+        testSend(function() { return new g(); });
+        testThrow(function() { return new g(); });
+    }
+}
+
+TestGenerator(function* g1() { },
+              [undefined],
+              "foo",
+              [undefined]);
+
+TestGenerator(function* g2() { yield 1; },
+              [1, undefined],
+              "foo",
+              [1, undefined]);
+
+TestGenerator(function* g3() { yield 1; yield 2; },
+              [1, 2, undefined],
+              "foo",
+              [1, 2, undefined]);
+
+TestGenerator(function* g4() { yield 1; yield 2; return 3; },
+              [1, 2, 3],
+              "foo",
+              [1, 2, 3]);
+
+TestGenerator(function* g5() { return 1; },
+              [1],
+              "foo",
+              [1]);
+
+TestGenerator(function* g6() { var x = yield 1; return x; },
+              [1, undefined],
+              "foo",
+              [1, "foo"]);
+
+TestGenerator(function* g7() { var x = yield 1; yield 2; return x; },
+              [1, 2, undefined],
+              "foo",
+              [1, 2, "foo"]);
+
+TestGenerator(function* g8() { for (var x = 0; x < 4; x++) { yield x; } },
+              [0, 1, 2, 3, undefined],
+              "foo",
+              [0, 1, 2, 3, undefined]);
+
+// Generator with arguments.
+TestGenerator(
+    function g9() {
+        return (function*(a, b, c, d) {
+            yield a; yield b; yield c; yield d;
+        })("fee", "fi", "fo", "fum");
+    },
+    ["fee", "fi", "fo", "fum", undefined],
+    "foo",
+    ["fee", "fi", "fo", "fum", undefined]);
+
+// Too few arguments.
+TestGenerator(
+    function g10() {
+        return (function*(a, b, c, d) {
+            yield a; yield b; yield c; yield d;
+        })("fee", "fi");
+    },
+    ["fee", "fi", undefined, undefined, undefined],
+    "foo",
+    ["fee", "fi", undefined, undefined, undefined]);
+
+// Too many arguments.
+TestGenerator(
+    function g11() {
+        return (function*(a, b, c, d) {
+            yield a; yield b; yield c; yield d;
+        })("fee", "fi", "fo", "fum", "I smell the blood of an Englishman");
+    },
+    ["fee", "fi", "fo", "fum", undefined],
+    "foo",
+    ["fee", "fi", "fo", "fum", undefined]);
+
+// The arguments object.
+TestGenerator(
+    function g12() {
+        return (function*(a, b, c, d) {
+            for (var i = 0; i < arguments.length; i++) {
+                yield arguments[i];
+            }
+        })("fee", "fi", "fo", "fum", "I smell the blood of an Englishman");
+    },
+    ["fee", "fi", "fo", "fum", "I smell the blood of an Englishman",
+     undefined],
+    "foo",
+    ["fee", "fi", "fo", "fum", "I smell the blood of an Englishman",
+     undefined]);
+
+// Access to captured free variables.
+TestGenerator(
+    function g13() {
+        return (function(a, b, c, d) {
+            return (function*() {
+                yield a; yield b; yield c; yield d;
+            })();
+        })("fee", "fi", "fo", "fum");
+    },
+    ["fee", "fi", "fo", "fum", undefined],
+    "foo",
+    ["fee", "fi", "fo", "fum", undefined]);
+
+// Abusing the arguments object.
+TestGenerator(
+    function g14() {
+        return (function*(a, b, c, d) {
+            arguments[0] = "Be he live";
+            arguments[1] = "or be he dead";
+            arguments[2] = "I'll grind his bones";
+            arguments[3] = "to make my bread";
+            yield a; yield b; yield c; yield d;
+        })("fee", "fi", "fo", "fum");
+    },
+    ["Be he live", "or be he dead", "I'll grind his bones", "to make my bread",
+     undefined],
+    "foo",
+    ["Be he live", "or be he dead", "I'll grind his bones", "to make my bread",
+     undefined]);
+
+// Abusing the arguments object: strict mode.
+TestGenerator(
+    function g15() {
+        return (function*(a, b, c, d) {
+            "use strict";
+            arguments[0] = "Be he live";
+            arguments[1] = "or be he dead";
+            arguments[2] = "I'll grind his bones";
+            arguments[3] = "to make my bread";
+            yield a; yield b; yield c; yield d;
+        })("fee", "fi", "fo", "fum");
+    },
+    ["fee", "fi", "fo", "fum", undefined],
+    "foo",
+    ["fee", "fi", "fo", "fum", undefined]);
+
+// GC.
+if (typeof gc == 'function') {
+    TestGenerator(function* g16() { yield "baz"; gc(); yield "qux"; },
+                  ["baz", "qux", undefined],
+                  "foo",
+                  ["baz", "qux", undefined]);
+}
+
+// Receivers.
+TestGenerator(
+    function g17() {
+        function* g() { yield this.x; yield this.y; }
+        var o = { start: g, x: 1, y: 2 };
+        return o.start();
+    },
+    [1, 2, undefined],
+    "foo",
+    [1, 2, undefined]);
+
+// FIXME: Capture the generator object as "this" in new g().  Bug 907742.
+// TestGenerator(
+//     function g18() {
+//         function* g() { yield this.x; yield this.y; }
+//         var iter = new g;
+//         iter.x = 1;
+//         iter.y = 2;
+//         return iter;
+//     },
+//     [1, 2, undefined],
+//     "foo",
+//     [1, 2, undefined]);
+
+TestGenerator(
+    function* g19() {
+        var x = 1;
+        yield x;
+        with({x:2}) { yield x; }
+        yield x;
+    },
+    [1, 2, 1, undefined],
+    "foo",
+    [1, 2, 1, undefined]);
+
+TestGenerator(
+    function* g20() { yield (1 + (yield 2) + 3); },
+    [2, NaN, undefined],
+    "foo",
+    [2, "1foo3", undefined]);
+
+TestGenerator(
+    function* g21() { return (1 + (yield 2) + 3); },
+    [2, NaN],
+    "foo",
+    [2, "1foo3"]);
+
+TestGenerator(
+    function* g22() { yield (1 + (yield 2) + 3); yield (4 + (yield 5) + 6); },
+    [2, NaN, 5, NaN, undefined],
+    "foo",
+    [2, "1foo3", 5, "4foo6", undefined]);
+
+TestGenerator(
+    function* g23() {
+        return (yield (1 + (yield 2) + 3)) + (yield (4 + (yield 5) + 6));
+    },
+    [2, NaN, 5, NaN, NaN],
+    "foo",
+    [2, "1foo3", 5, "4foo6", "foofoo"]);
+
+// Rewind a try context with and without operands on the stack.
+TestGenerator(
+    function* g24() {
+        try {
+            return (yield (1 + (yield 2) + 3)) + (yield (4 + (yield 5) + 6));
+        } catch (e) {
+            throw e;
+        }
+    },
+    [2, NaN, 5, NaN, NaN],
+    "foo",
+    [2, "1foo3", 5, "4foo6", "foofoo"]);
+
+// Yielding in a catch context, with and without operands on the stack.
+TestGenerator(
+    function* g25() {
+        try {
+            throw (yield (1 + (yield 2) + 3))
+        } catch (e) {
+            if (typeof e == 'object') throw e;
+            return e + (yield (4 + (yield 5) + 6));
+        }
+    },
+    [2, NaN, 5, NaN, NaN],
+    "foo",
+    [2, "1foo3", 5, "4foo6", "foofoo"]);
+
+// Generator function instances.
+TestGenerator(GeneratorFunction(),
+              [undefined],
+              "foo",
+              [undefined]);
+
+TestGenerator(new GeneratorFunction(),
+              [undefined],
+              "foo",
+              [undefined]);
+
+TestGenerator(GeneratorFunction('yield 1;'),
+              [1, undefined],
+              "foo",
+              [1, undefined]);
+
+TestGenerator(
+    function() { return GeneratorFunction('x', 'y', 'yield x + y;')(1, 2) },
+    [3, undefined],
+    "foo",
+    [3, undefined]);
+
+// Access to this with formal arguments.
+TestGenerator(
+    function () {
+        return ({ x: 42, g: function* (a) { yield this.x } }).g(0);
+    },
+    [42, undefined],
+    "foo",
+    [42, undefined]);
+
+/* FIXME: Implement yield*.  Bug 907738.
+
+// Test that yield* re-yields received results without re-boxing.
+function TestDelegatingYield() {
+    function results(results) {
+        var i = 0;
+        function next() {
+            return results[i++];
+        }
+        return { next: next }
+    }
+    function* yield_results(expected) {
+        return yield* results(expected);
+    }
+    function collect_results(iter) {
+        var ret = [];
+        var result;
+        do {
+            result = iter.next();
+            ret.push(result);
+        } while (!result.done);
+        return ret;
+    }
+    // We have to put a full result for the end, because the return will re-box.
+    var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
+
+    // Sanity check.
+    assertDeepEq(expected, collect_results(results(expected)));
+    assertDeepEq(expected, collect_results(yield_results(expected)));
+}
+TestDelegatingYield();
+*/
+
+function TestTryCatch(instantiate) {
+    function* g() { yield 1; try { yield 2; } catch (e) { yield e; } yield 3; }
+    function Sentinel() {}
+
+    function Test1(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        assertIteratorResult(3, false, iter.next());
+        assertIteratorResult(undefined, true, iter.next());
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test1(instantiate(g));
+
+    function Test2(iter) {
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test2(instantiate(g));
+
+    function Test3(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test3(instantiate(g));
+
+    function Test4(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        var exn = new Sentinel;
+        assertIteratorResult(exn, false, iter.throw(exn));
+        assertIteratorResult(3, false, iter.next());
+        assertIteratorResult(undefined, true, iter.next());
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test4(instantiate(g));
+
+    function Test5(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        var exn = new Sentinel;
+        assertIteratorResult(exn, false, iter.throw(exn));
+        assertIteratorResult(3, false, iter.next());
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+
+    }
+    Test5(instantiate(g));
+
+    function Test6(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        var exn = new Sentinel;
+        assertIteratorResult(exn, false, iter.throw(exn));
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test6(instantiate(g));
+}
+TestTryCatch(function (g) { return g(); });
+// FIXME: Implement yield*.  Bug 907738.
+// TestTryCatch(function* (g) { return yield* g(); });
+
+function TestTryFinally(instantiate) {
+    function* g() { yield 1; try { yield 2; } finally { yield 3; } yield 4; }
+    function Sentinel() {}
+    function Sentinel2() {}
+
+    function Test1(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        assertIteratorResult(3, false, iter.next());
+        assertIteratorResult(4, false, iter.next());
+        assertIteratorResult(undefined, true, iter.next());
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test1(instantiate(g));
+
+    function Test2(iter) {
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test2(instantiate(g));
+
+    function Test3(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test3(instantiate(g));
+
+    function Test4(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        assertIteratorResult(3, false, iter.throw(new Sentinel));
+        assertThrowsInstanceOf(function() { iter.next(); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+
+    }
+    Test4(instantiate(g));
+
+    function Test5(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        assertIteratorResult(3, false, iter.throw(new Sentinel));
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel2); }, Sentinel2);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test5(instantiate(g));
+
+    function Test6(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        assertIteratorResult(3, false, iter.next());
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test6(instantiate(g));
+
+    function Test7(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        assertIteratorResult(3, false, iter.next());
+        assertIteratorResult(4, false, iter.next());
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test7(instantiate(g));
+}
+TestTryFinally(function (g) { return g(); });
+// FIXME: Implement yield*.  Bug 907738.
+// TestTryFinally(function* (g) { return yield* g(); });
+
+function TestNestedTry(instantiate) {
+    function* g() {
+        try {
+            yield 1;
+            try { yield 2; } catch (e) { yield e; }
+            yield 3;
+        } finally {
+            yield 4;
+        }
+        yield 5;
+    }
+    function Sentinel() {}
+    function Sentinel2() {}
+
+    function Test1(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        assertIteratorResult(3, false, iter.next());
+        assertIteratorResult(4, false, iter.next());
+        assertIteratorResult(5, false, iter.next());
+        assertIteratorResult(undefined, true, iter.next());
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test1(instantiate(g));
+
+    function Test2(iter) {
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test2(instantiate(g));
+
+    function Test3(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(4, false, iter.throw(new Sentinel));
+        assertThrowsInstanceOf(function() { iter.next(); }, Sentinel);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test3(instantiate(g));
+
+    function Test4(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(4, false, iter.throw(new Sentinel));
+        assertThrowsInstanceOf(function() { iter.throw(new Sentinel2); }, Sentinel2);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test4(instantiate(g));
+
+    function Test5(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        var exn = new Sentinel;
+        assertIteratorResult(exn, false, iter.throw(exn));
+        assertIteratorResult(3, false, iter.next());
+        assertIteratorResult(4, false, iter.next());
+        assertIteratorResult(5, false, iter.next());
+        assertIteratorResult(undefined, true, iter.next());
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+
+    }
+    Test5(instantiate(g));
+
+    function Test6(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        var exn = new Sentinel;
+        assertIteratorResult(exn, false, iter.throw(exn));
+        assertIteratorResult(4, false, iter.throw(new Sentinel2));
+        assertThrowsInstanceOf(function() { iter.next(); }, Sentinel2);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+    }
+    Test6(instantiate(g));
+
+    function Test7(iter) {
+        assertIteratorResult(1, false, iter.next());
+        assertIteratorResult(2, false, iter.next());
+        var exn = new Sentinel;
+        assertIteratorResult(exn, false, iter.throw(exn));
+        assertIteratorResult(3, false, iter.next());
+        assertIteratorResult(4, false, iter.throw(new Sentinel2));
+        assertThrowsInstanceOf(function() { iter.next(); }, Sentinel2);
+        assertThrowsInstanceOf(function() { iter.next(); }, TypeError);
+
+    }
+    Test7(instantiate(g));
+
+    // That's probably enough.
+}
+TestNestedTry(function (g) { return g(); });
+// FIXME: Implement yield*.  Bug 907738.
+// TestNestedTry(function* (g) { return yield* g(); });
+
+function TestRecursion() {
+    function TestNextRecursion() {
+        function* g() { yield iter.next(); }
+        var iter = g();
+        return iter.next();
+    }
+    function TestSendRecursion() {
+        function* g() { yield iter.next(42); }
+        var iter = g();
+        return iter.next();
+    }
+    function TestThrowRecursion() {
+        function* g() { yield iter.throw(1); }
+        var iter = g();
+        return iter.next();
+    }
+    assertThrowsInstanceOf(TestNextRecursion, TypeError);
+    assertThrowsInstanceOf(TestSendRecursion, TypeError);
+    assertThrowsInstanceOf(TestThrowRecursion, TypeError);
+}
+TestRecursion();
+
+if (typeof reportCompare == "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Generators/objects.js
@@ -0,0 +1,52 @@
+// This file was written by Andy Wingo <wingo@igalia.com> and originally
+// contributed to V8 as generators-objects.js, available here:
+//
+// http://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/harmony/generators-objects.js
+
+// Test aspects of the generator runtime.
+
+// Test the properties and prototype of a generator object.
+function TestGeneratorObject() {
+  function* g() { yield 1; }
+
+  var iter = g();
+  assertEq(Object.getPrototypeOf(iter), g.prototype);
+  assertTrue(iter instanceof g);
+  assertEq(String(iter), "[object Generator]");
+  assertDeepEq(Object.getOwnPropertyNames(iter), []);
+  assertNotEq(g(), iter);
+
+  // g() is the same as new g().
+  iter = new g();
+  assertEq(Object.getPrototypeOf(iter), g.prototype);
+  assertTrue(iter instanceof g);
+  assertEq(String(iter), "[object Generator]");
+  assertDeepEq(Object.getOwnPropertyNames(iter), []);
+  assertNotEq(new g(), iter);
+}
+TestGeneratorObject();
+
+
+// Test the methods of generator objects.
+function TestGeneratorObjectMethods() {
+  function* g() { yield 1; }
+  var iter = g();
+
+  function TestNonGenerator(non_generator) {
+    assertThrowsInstanceOf(function() { iter.next.call(non_generator); }, TypeError);
+    assertThrowsInstanceOf(function() { iter.next.call(non_generator, 1); }, TypeError);
+    assertThrowsInstanceOf(function() { iter.throw.call(non_generator, 1); }, TypeError);
+    assertThrowsInstanceOf(function() { iter.close.call(non_generator); }, TypeError);
+  }
+
+  TestNonGenerator(1);
+  TestNonGenerator({});
+  TestNonGenerator(function(){});
+  TestNonGenerator(g);
+  TestNonGenerator(g.prototype);
+}
+TestGeneratorObjectMethods();
+
+
+if (typeof reportCompare == "function")
+    reportCompare(true, true);
--- a/js/src/tests/ecma_6/Generators/runtime.js
+++ b/js/src/tests/ecma_6/Generators/runtime.js
@@ -3,38 +3,17 @@
 //
 // http://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/harmony/generators-runtime.js
 
 // Test aspects of the generator runtime.
 
 // See http://people.mozilla.org/~jorendorff/es6-draft.html#sec-15.19.3.
 
 function assertSyntaxError(str) {
-    var msg;
-    var evil = eval;
-    try {
-        // Non-direct eval.
-        evil(str);
-    } catch (exc) {
-        if (exc instanceof SyntaxError)
-            return;
-        msg = "Assertion failed: expected SyntaxError, got " + exc;
-    }
-    if (msg === undefined)
-        msg = "Assertion failed: expected SyntaxError, but no exception thrown";
-    throw new Error(msg + " - " + str);
-}
-
-function assertFalse(a) { assertEq(a, false) }
-function assertTrue(a) { assertEq(a, true) }
-function assertNotEq(found, not_expected) { assertFalse(found === expected) }
-function assertArrayEq(found, expected) {
-    assertEq(found.length, expected.length);
-    for (var i = 0; i < expected.length; i++)
-        assertEq(found[i], expected[i]);
+    assertThrowsInstanceOf(Function(str), SyntaxError);
 }
 
 
 function f() { }
 function* g() { yield 1; }
 var GeneratorFunctionPrototype = Object.getPrototypeOf(g);
 var GeneratorFunction = GeneratorFunctionPrototype.constructor;
 var GeneratorObjectPrototype = GeneratorFunctionPrototype.prototype;
@@ -44,17 +23,17 @@ var GeneratorObjectPrototype = Generator
 // other function.
 function TestGeneratorFunctionInstance() {
     var f_own_property_names = Object.getOwnPropertyNames(f);
     var g_own_property_names = Object.getOwnPropertyNames(g);
 
     f_own_property_names.sort();
     g_own_property_names.sort();
 
-    assertArrayEq(f_own_property_names, g_own_property_names);
+    assertDeepEq(f_own_property_names, g_own_property_names);
     var i;
     for (i = 0; i < f_own_property_names.length; i++) {
         var prop = f_own_property_names[i];
         var f_desc = Object.getOwnPropertyDescriptor(f, prop);
         var g_desc = Object.getOwnPropertyDescriptor(g, prop);
         assertEq(f_desc.configurable, g_desc.configurable, prop);
         assertEq(f_desc.writable, g_desc.writable, prop);
         assertEq(f_desc.enumerable, g_desc.enumerable, prop);
@@ -87,17 +66,17 @@ function TestGeneratorObjectPrototype() 
 
     var expected_property_names = ["iterator", "next", "throw", "constructor"];
     var found_property_names =
         Object.getOwnPropertyNames(GeneratorObjectPrototype);
 
     expected_property_names.sort();
     found_property_names.sort();
 
-    assertArrayEq(found_property_names, expected_property_names);
+    assertDeepEq(found_property_names, expected_property_names);
 }
 TestGeneratorObjectPrototype();
 
 
 // This tests the object that would be called "GeneratorFunction", if it were
 // like "Function".
 function TestGeneratorFunction() {
     assertEq(GeneratorFunctionPrototype, GeneratorFunction.prototype);
@@ -136,15 +115,15 @@ function TestPerGeneratorPrototype() {
     assertNotEq((function*(){}).prototype, (function*(){}).prototype);
     assertNotEq((function*(){}).prototype, g.prototype);
     assertEq(typeof GeneratorFunctionPrototype, "object");
     assertEq(g.prototype.__proto__.constructor, GeneratorFunctionPrototype, "object");
     assertEq(Object.getPrototypeOf(g.prototype), GeneratorObjectPrototype);
     assertFalse(g.prototype instanceof Function);
     assertEq(typeof (g.prototype), "object");
 
-    assertArrayEq(Object.getOwnPropertyNames(g.prototype), []);
+    assertDeepEq(Object.getOwnPropertyNames(g.prototype), []);
 }
 TestPerGeneratorPrototype();
 
 
 if (typeof reportCompare == "function")
     reportCompare(true, true);
--- a/js/src/tests/ecma_6/Generators/shell.js
+++ b/js/src/tests/ecma_6/Generators/shell.js
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+function assertFalse(a) { assertEq(a, false) }
+function assertTrue(a) { assertEq(a, true) }
+function assertNotEq(found, not_expected) { assertFalse(found === expected) }
+function assertIteratorResult(value, done, result) {
+    assertDeepEq(result, { value: value, done: done });
+}
--- a/js/src/tests/ecma_6/shell.js
+++ b/js/src/tests/ecma_6/shell.js
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+if (typeof assertThrowsInstanceOf === 'undefined') {
+    var assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {
+        var fullmsg;
+        try {
+            f();
+        } catch (exc) {
+            if (exc instanceof ctor)
+                return;
+            fullmsg = "Assertion failed: expected exception " + ctor.name + ", got " + exc;
+        }
+        if (fullmsg === undefined)
+            fullmsg = "Assertion failed: expected exception " + ctor.name + ", no exception thrown";
+        if (msg !== undefined)
+            fullmsg += " - " + msg;
+        throw new Error(fullmsg);
+    };
+}
+
+if (typeof assertThrowsValue === 'undefined') {
+    var assertThrowsValue = function assertThrowsValue(f, val, msg) {
+        var fullmsg;
+        try {
+            f();
+        } catch (exc) {
+            if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
+                return;
+            fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
+        }
+        if (fullmsg === undefined)
+            fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
+        if (msg !== undefined)
+            fullmsg += " - " + msg;
+        throw new Error(fullmsg);
+    };
+}
+
+if (typeof assertDeepEq === 'undefined') {
+    var assertDeepEq = (function(){
+        var call = Function.prototype.call,
+            Map_ = Map,
+            Error_ = Error,
+            Map_has = call.bind(Map.prototype.has),
+            Map_get = call.bind(Map.prototype.get),
+            Map_set = call.bind(Map.prototype.set),
+            Object_toString = call.bind(Object.prototype.toString),
+            Function_toString = call.bind(Function.prototype.toString),
+            Object_getPrototypeOf = Object.getPrototypeOf,
+            Object_hasOwnProperty = call.bind(Object.prototype.hasOwnProperty),
+            Object_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
+            Object_isExtensible = Object.isExtensible,
+            Object_getOwnPropertyNames = Object.getOwnPropertyNames,
+            uneval_ = uneval;
+
+        // Return true iff ES6 Type(v) isn't Object.
+        // Note that `typeof document.all === "undefined"`.
+        function isPrimitive(v) {
+            return (v === null ||
+                    v === undefined ||
+                    typeof v === "boolean" ||
+                    typeof v === "number" ||
+                    typeof v === "string" ||
+                    typeof v === "symbol");
+        }
+
+        function assertSameValue(a, b, msg) {
+            try {
+                assertEq(a, b);
+            } catch (exc) {
+                throw new Error(exc.message + (msg ? " " + msg : ""));
+            }
+        }
+
+        function assertSameClass(a, b, msg) {
+            var ac = Object_toString(a), bc = Object_toString(b);
+            assertSameValue(ac, bc, msg);
+            switch (ac) {
+            case "[object Function]":
+                assertSameValue(Function_toString(a), Function_toString(b), msg);
+            }
+        }
+
+        function at(prevmsg, segment) {
+            return prevmsg ? prevmsg + segment : "at _" + segment;
+        }
+
+        // Assert that the arguments a and b are thoroughly structurally equivalent.
+        //
+        // For the sake of speed, we cut a corner:
+        //        var x = {}, y = {}, ax = [x];
+        //        assertDeepEq([ax, x], [ax, y]);  // passes (?!)
+        //
+        // Technically this should fail, since the two object graphs are different.
+        // (The graph of [ax, y] contains one more object than the graph of [ax, x].)
+        //
+        // To get technically correct behavior, pass {strictEquivalence: true}.
+        // This is slower because we have to walk the entire graph, and Object.prototype
+        // is big.
+        //
+        return function assertDeepEq(a, b, options) {
+            var strictEquivalence = options ? options.strictEquivalence : false;
+
+            function assertSameProto(a, b, msg) {
+                check(Object_getPrototypeOf(a), Object_getPrototypeOf(b), at(msg, ".__proto__"));
+            }
+
+            function failPropList(na, nb, msg) {
+                throw Error_("got own properties " + uneval_(na) + ", expected " + uneval_(nb) +
+                             (msg ? " " + msg : ""));
+            }
+
+            function assertSameProps(a, b, msg) {
+                var na = Object_getOwnPropertyNames(a),
+                    nb = Object_getOwnPropertyNames(b);
+                if (na.length !== nb.length)
+                    failPropList(na, nb, msg);
+                for (var i = 0; i < na.length; i++) {
+                    var name = na[i];
+                    if (name !== nb[i])
+                        failPropList(na, nb, msg);
+                    var da = Object_getOwnPropertyDescriptor(a, name),
+                        db = Object_getOwnPropertyDescriptor(b, name);
+                    var pmsg = at(msg, /^[_$A-Za-z0-9]+$/.test(name)
+                                       ? /0|[1-9][0-9]*/.test(name) ? "[" + name + "]" : "." + name
+                                       : "[" + uneval_(name) + "]");
+                    assertSameValue(da.configurable, db.configurable, at(pmsg, ".[[Configurable]]"));
+                    assertSameValue(da.enumerable, db.enumerable, at(pmsg, ".[[Enumerable]]"));
+                    if (Object_hasOwnProperty(da, "value")) {
+                        if (!Object_hasOwnProperty(db, "value"))
+                            throw Error_("got data property, expected accessor property" + pmsg);
+                        check(da.value, db.value, pmsg);
+                    } else {
+                        if (Object_hasOwnProperty(db, "value"))
+                            throw Error_("got accessor property, expected data property" + pmsg);
+                        check(da.get, db.get, at(pmsg, ".[[Get]]"));
+                        check(da.set, db.set, at(pmsg, ".[[Set]]"));
+                    }
+                }
+            };
+
+            var ab = Map_();
+            var bpath = Map_();
+
+            function check(a, b, path) {
+                if (isPrimitive(a)) {
+                    assertSameValue(a, b, path);
+                } else if (isPrimitive(b)) {
+                    throw Error_("got " + Object_toString(a) + ", expected " + uneval_(b) + " " + path);
+                } else if (Map_has(ab, a)) {
+                    assertSameValue(Map_get(ab, a), b, path);
+                } else if (Map_has(bpath, b)) {
+                    var bPrevPath = Map_get(bpath, b) || "_";
+                    throw Error_("got distinct objects " + at(path, "") + " and " + at(bPrevPath, "") +
+                                 ", expected the same object both places");
+                } else {
+                    Map_set(ab, a, b);
+                    Map_set(bpath, b, path);
+                    if (a !== b || strictEquivalence) {
+                        assertSameClass(a, b, path);
+                        assertSameProto(a, b, path);
+                        assertSameProps(a, b, path);
+                        assertSameValue(Object_isExtensible(a),
+                                        Object_isExtensible(b),
+                                        at(path, ".[[Extensible]]"));
+                    }
+                }
+            }
+
+            check(a, b, "");
+        };
+    })();
+}
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -41,16 +41,17 @@
     macro(DateTimeFormatFormatGet, DateTimeFormatFormatGet, "Intl_DateTimeFormat_format_get") \
     macro(decodeURI, decodeURI, "decodeURI") \
     macro(decodeURIComponent, decodeURIComponent, "decodeURIComponent") \
     macro(defineProperty, defineProperty, "defineProperty") \
     macro(defineGetter, defineGetter, "__defineGetter__") \
     macro(defineSetter, defineSetter, "__defineSetter__") \
     macro(delete, delete_, "delete") \
     macro(deleteProperty, deleteProperty, "deleteProperty") \
+    macro(done, done, "done") \
     macro(each, each, "each") \
     macro(elementType, elementType, "elementType") \
     macro(empty, empty, "") \
     macro(encodeURI, encodeURI, "encodeURI") \
     macro(encodeURIComponent, encodeURIComponent, "encodeURIComponent") \
     macro(enumerable, enumerable, "enumerable") \
     macro(enumerate, enumerate, "enumerate") \
     macro(escape, escape, "escape") \