Bug 1147371 - Implement calling IteratorClose and "return" on iterators in yield*. (r=jandem)
☠☠ backed out by 076bdd3f1f7a ☠ ☠
authorShu-yu Guo <shu@rfrn.org>
Thu, 12 Jan 2017 23:51:35 -0800
changeset 374305 d9eef2331ae6a7896da0c6caca605317499db73b
parent 374304 7872a2456195318b5fad224f4026a2de32720acc
child 374306 408a37107c7f2fa27ddc0ed40ff5b86694760178
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1147371
milestone53.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 1147371 - Implement calling IteratorClose and "return" on iterators in yield*. (r=jandem) The forced return path is implemented as finally block. Unlike IteratorClose, this path checks the result returned by the "return" method. If !result.done, the yield loop continues. This also changes checking for the "throw" method with a JSOP_CALLPROP instead of a JSOP_IN to be in line with current spec.
js/src/frontend/BytecodeEmitter.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/js.msg
js/src/tests/ecma_6/Generators/delegating-yield-2.js
js/src/tests/ecma_6/Generators/yield-star-iterator-close.js
js/src/tests/ecma_6/shell.js
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/Opcodes.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -4901,16 +4901,24 @@ BytecodeEmitter::emitIteratorClose(Maybe
 
         IfThenElseEmitter ifReturnDone(this);
         if (!emit1(JSOP_DUP))                             // ITER OLDRESULT FTYPE FVALUE RESULT RESULT
             return false;
         if (!emitAtomOp(cx->names().done, JSOP_GETPROP))  // ITER OLDRESULT FTYPE FVALUE RESULT DONE
             return false;
         if (!ifReturnDone.emitIfElse())                   // ITER OLDRESULT FTYPE FVALUE RESULT
             return false;
+        if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE
+            return false;
+        if (!emitPrepareIteratorResult())                 // ITER OLDRESULT FTYPE FVALUE VALUE RESULT
+            return false;
+        if (!emit1(JSOP_SWAP))                            // ITER OLDRESULT FTYPE FVALUE RESULT VALUE
+            return false;
+        if (!emitFinishIteratorResult(true))              // ITER OLDRESULT FTYPE FVALUE RESULT
+            return false;
         if (!emit1(JSOP_DUP))                             // ITER OLDRESULT FTYPE FVALUE RESULT RESULT
             return false;
         if (!emit1(JSOP_SETRVAL))                         // ITER OLDRESULT FTYPE FVALUE RESULT
             return false;
         if (!ifReturnDone.emitElse())                     // ITER OLDRESULT FTYPE FVALUE RESULT
             return false;
         int32_t savedDepth = this->stackDepth;
         if (!emit2(JSOP_UNPICK, 3))                       // ITER RESULT OLDRESULT FTYPE FVALUE
@@ -8062,71 +8070,87 @@ BytecodeEmitter::emitYieldStar(ParseNode
         return false;
 
     JumpTarget tryEnd;
     if (!emitJumpTarget(&tryEnd))                                // tryEnd:
         return false;
 
     // Catch location.
     stackDepth = uint32_t(depth);                                // ITER RESULT
-    if (!emit1(JSOP_POP))                                        // ITER
-        return false;
-    // THROW? = 'throw' in ITER
-    if (!emit1(JSOP_EXCEPTION))                                  // ITER EXCEPTION
-        return false;
-    if (!emit1(JSOP_SWAP))                                       // EXCEPTION ITER
-        return false;
-    if (!emit1(JSOP_DUP))                                        // EXCEPTION ITER ITER
-        return false;
-    if (!emitAtomOp(cx->names().throw_, JSOP_STRING))            // EXCEPTION ITER ITER "throw"
-        return false;
-    if (!emit1(JSOP_SWAP))                                       // EXCEPTION ITER "throw" ITER
-        return false;
-    if (!emit1(JSOP_IN))                                         // EXCEPTION ITER THROW?
-        return false;
-    // if (THROW?) goto delegate
-    JumpList checkThrow;
-    if (!emitJump(JSOP_IFNE, &checkThrow))                       // EXCEPTION ITER
-        return false;
-    if (!emit1(JSOP_POP))                                        // EXCEPTION
-        return false;
-    if (!emit1(JSOP_THROW))                                      // throw EXCEPTION
-        return false;
-
-    if (!emitJumpTargetAndPatch(checkThrow))                     // delegate:
-        return false;
-    // RESULT = ITER.throw(EXCEPTION)                            // EXCEPTION ITER
-    stackDepth = uint32_t(depth);
-    if (!emit1(JSOP_DUP))                                        // EXCEPTION ITER ITER
-        return false;
-    if (!emit1(JSOP_DUP))                                        // EXCEPTION ITER ITER ITER
-        return false;
-    if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP))          // EXCEPTION ITER ITER THROW
-        return false;
-    if (!emit1(JSOP_SWAP))                                       // EXCEPTION ITER THROW ITER
-        return false;
-    if (!emit2(JSOP_PICK, 3))                                    // ITER THROW ITER EXCEPTION
-        return false;
-    if (!emitCall(JSOP_CALL, 1, iter))                           // ITER RESULT
+    if (!emit1(JSOP_EXCEPTION))                                  // ITER RESULT EXCEPTION
+        return false;
+    if (!emitDupAt(2))                                           // ITER RESULT EXCEPTION ITER
+        return false;
+    if (!emit1(JSOP_DUP))                                        // ITER RESULT EXCEPTION ITER ITER
+        return false;
+    if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP))          // ITER RESULT EXCEPTION ITER THROW
+        return false;
+    if (!emit1(JSOP_DUP))                                        // ITER RESULT EXCEPTION ITER THROW THROW
+        return false;
+    if (!emit1(JSOP_UNDEFINED))                                  // ITER RESULT EXCEPTION ITER THROW THROW UNDEFINED
+        return false;
+    if (!emit1(JSOP_EQ))                                         // ITER RESULT EXCEPTION ITER THROW ?EQL
+        return false;
+
+    IfThenElseEmitter ifThrowMethodIsNotDefined(this);
+    if (!ifThrowMethodIsNotDefined.emitIf())                     // ITER RESULT EXCEPTION ITER THROW
+        return false;
+    if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw
+        return false;
+    if (!ifThrowMethodIsNotDefined.emitEnd())                    // ITER OLDRESULT EXCEPTION ITER THROW
+        return false;
+    // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4.
+    // RESULT = ITER.throw(EXCEPTION)                            // ITER OLDRESULT EXCEPTION ITER THROW
+    if (!emit1(JSOP_SWAP))                                       // ITER OLDRESULT EXCEPTION THROW ITER
+        return false;
+    if (!emit2(JSOP_PICK, 2))                                    // ITER OLDRESULT THROW ITER EXCEPTION
+        return false;
+    if (!emitCall(JSOP_CALL, 1, iter))                           // ITER OLDRESULT RESULT
         return false;
     checkTypeSet(JSOP_CALL);
+    if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow))       // ITER OLDRESULT RESULT
+        return false;
+    if (!emit1(JSOP_SWAP))                                       // ITER RESULT OLDRESULT
+        return false;
+    if (!emit1(JSOP_POP))                                        // ITER RESULT
+        return false;
     MOZ_ASSERT(this->stackDepth == depth);
     JumpList checkResult;
+    // Note that there is no GOSUB to the finally block here. If the iterator has a
+    // "throw" method, it does not perform IteratorClose per
+    // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii.
     if (!emitJump(JSOP_GOTO, &checkResult))                      // goto checkResult
         return false;
 
-    // Catch epilogue.
+    // The finally block, IteratorClose logic.
+
+    JumpTarget finallyStart{ 0 };
+    if (!emitJumpTarget(&finallyStart))
+        return false;
+    if (!emit1(JSOP_FINALLY))                                    // ITER RESULT FTYPE FVALUE
+        return false;
+    if (!emitDupAt(3))                                           // ITER RESULT FTYPE FVALUE ITER
+        return false;
+    if (!emitIteratorClose(Some(tryStart)))                      // ITER RESULT FTYPE FVALUE
+        return false;
+    if (!emit1(JSOP_RETSUB))                                     // ITER RESULT
+        return false;
+
+    // Catch and finally epilogue.
 
     // This is a peace offering to ReconstructPCStack.  See the note in EmitTry.
     if (!emit1(JSOP_NOP))
         return false;
-    if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset))
-        return false;
-
-    // After the try/catch block: send the received value to the iterator.
+    size_t tryStartOffset = tryStart.offset + JSOP_TRY_LENGTH;
+    if (!tryNoteList.append(JSTRY_CATCH, depth, tryStartOffset, tryEnd.offset))
+        return false;
+    if (!tryNoteList.append(JSTRY_FINALLY, depth, tryStartOffset, finallyStart.offset))
+        return false;
+
+    // After the try-catch-finally block: send the received value to the iterator.
     if (!emitJumpTargetAndPatch(send))                           // send:
         return false;
 
     // Send location.
     // result = iter.next(received)                              // ITER RECEIVED
     if (!emit1(JSOP_SWAP))                                       // RECEIVED ITER
         return false;
     if (!emit1(JSOP_DUP))                                        // RECEIVED ITER ITER
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -3973,17 +3973,17 @@ BaselineCompiler::emit_JSOP_MOREITER()
     if (!emitOpIC(compiler.getStub(&stubSpace_)))
         return false;
 
     frame.push(R0);
     return true;
 }
 
 bool
-BaselineCompiler::emit_JSOP_ISNOITER()
+BaselineCompiler::emitIsMagicValue()
 {
     frame.syncStack(0);
 
     Label isMagic, done;
     masm.branchTestMagic(Assembler::Equal, frame.addressOfStackValue(frame.peek(-1)),
                          &isMagic);
     masm.moveValue(BooleanValue(false), R0);
     masm.jump(&done);
@@ -3992,27 +3992,39 @@ BaselineCompiler::emit_JSOP_ISNOITER()
     masm.moveValue(BooleanValue(true), R0);
 
     masm.bind(&done);
     frame.push(R0, JSVAL_TYPE_BOOLEAN);
     return true;
 }
 
 bool
+BaselineCompiler::emit_JSOP_ISNOITER()
+{
+    return emitIsMagicValue();
+}
+
+bool
 BaselineCompiler::emit_JSOP_ENDITER()
 {
     if (!emit_JSOP_JUMPTARGET())
         return false;
     frame.popRegsAndSync(1);
 
     ICIteratorClose_Fallback::Compiler compiler(cx);
     return emitOpIC(compiler.getStub(&stubSpace_));
 }
 
 bool
+BaselineCompiler::emit_JSOP_ISGENCLOSING()
+{
+    return emitIsMagicValue();
+}
+
+bool
 BaselineCompiler::emit_JSOP_GETRVAL()
 {
     frame.syncStack(0);
 
     emitLoadReturnValue(R0);
 
     frame.push(R0);
     return true;
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -198,16 +198,17 @@ namespace jit {
     _(JSOP_TOASYNC)            \
     _(JSOP_TOID)               \
     _(JSOP_TOSTRING)           \
     _(JSOP_TABLESWITCH)        \
     _(JSOP_ITER)               \
     _(JSOP_MOREITER)           \
     _(JSOP_ISNOITER)           \
     _(JSOP_ENDITER)            \
+    _(JSOP_ISGENCLOSING)       \
     _(JSOP_GENERATOR)          \
     _(JSOP_INITIALYIELD)       \
     _(JSOP_YIELD)              \
     _(JSOP_DEBUGAFTERYIELD)    \
     _(JSOP_FINALYIELDRVAL)     \
     _(JSOP_RESUME)             \
     _(JSOP_CALLEE)             \
     _(JSOP_GETRVAL)            \
@@ -337,16 +338,18 @@ class BaselineCompiler : public Baseline
     MOZ_MUST_USE bool emitInitPropGetterSetter();
     MOZ_MUST_USE bool emitInitElemGetterSetter();
 
     MOZ_MUST_USE bool emitFormalArgAccess(uint32_t arg, bool get);
 
     MOZ_MUST_USE bool emitThrowConstAssignment();
     MOZ_MUST_USE bool emitUninitializedLexicalCheck(const ValueOperand& val);
 
+    MOZ_MUST_USE bool emitIsMagicValue();
+
     MOZ_MUST_USE bool addPCMappingEntry(bool addIndexEntry);
 
     MOZ_MUST_USE bool addYieldOffset();
 
     void getEnvironmentCoordinateObject(Register reg);
     Address getEnvironmentCoordinateAddressFromObject(Register objReg, Register reg);
     Address getEnvironmentCoordinateAddress(Register reg);
 };
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -583,8 +583,9 @@ MSG_DEF(JSMSG_BAD_MODULE_STATE,         
 MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF,       0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
 MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
 MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE,    0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE,     0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
 MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
 
 // Iterator
 MSG_DEF(JSMSG_RETURN_NOT_CALLABLE,     0, JSEXN_TYPEERR, "property 'return' of iterator is not callable")
+MSG_DEF(JSMSG_ITERATOR_NO_THROW,       0, JSEXN_TYPEERR, "iterator does not have a 'throw' method")
--- a/js/src/tests/ecma_6/Generators/delegating-yield-2.js
+++ b/js/src/tests/ecma_6/Generators/delegating-yield-2.js
@@ -20,52 +20,54 @@ assertIteratorNext(outer, 1);
 assertIteratorResult(outer.throw(42), 42, false);
 assertThrowsValue(function () { outer.throw(42) }, 42);
 assertThrowsValue(function () { outer.throw(42) }, 42);
 
 // What would be an uncaught delegated throw, but with a monkeypatched iterator.
 inner = g1();
 outer = delegate(inner);
 assertIteratorNext(outer, 1);
-inner.throw = function(e) { return e*2; };
-assertEq(84, outer.throw(42));
+inner.throw = function(e) { return { value: e*2 }; };
+assertEq(84, outer.throw(42).value);
 assertIteratorDone(outer, undefined);
 
 // Monkeypatching inner.next.
 inner = g1();
 outer = delegate(inner);
 inner.next = function() { return { value: 13, done: true } };
 assertIteratorDone(outer, 13);
 
 // What would be a caught delegated throw, but with a monkeypunched prototype.
 inner = g2();
 outer = delegate(inner);
 assertIteratorNext(outer, 1);
 delete GeneratorObjectPrototype.throw;
 var outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
-assertThrowsValue(outer_throw_42, 42);
+// yield* protocol violation: no 'throw' method
+assertThrowsInstanceOf(outer_throw_42, TypeError);
+// Now done, so just throws.
 assertThrowsValue(outer_throw_42, 42);
 
 // Monkeypunch a different throw handler.
 inner = g2();
 outer = delegate(inner);
 outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
 assertIteratorNext(outer, 1);
-GeneratorObjectPrototype.throw = function(e) { return e*2; }
-assertEq(84, outer_throw_42());
-assertEq(84, outer_throw_42());
+GeneratorObjectPrototype.throw = function(e) { return { value: e*2 }; }
+assertEq(84, outer_throw_42().value);
+assertEq(84, outer_throw_42().value);
 // This continues indefinitely.
-assertEq(84, outer_throw_42());
+assertEq(84, outer_throw_42().value);
 assertIteratorDone(outer, undefined);
 
 // The same, but restoring the original pre-monkey throw.
 inner = g2();
 outer = delegate(inner);
 outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
 assertIteratorNext(outer, 1);
-assertEq(84, outer_throw_42());
-assertEq(84, outer_throw_42());
+assertEq(84, outer_throw_42().value);
+assertEq(84, outer_throw_42().value);
 GeneratorObjectPrototype.throw = GeneratorObjectPrototype_throw;
 assertIteratorResult(outer_throw_42(), 42, false);
 assertIteratorDone(outer, undefined);
 
 if (typeof reportCompare == "function")
     reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Generators/yield-star-iterator-close.js
@@ -0,0 +1,123 @@
+// Tests that the "return" method on iterators is called in yield*
+// expressions.
+
+function test() {
+    var returnCalled = 0;
+    var returnCalledExpected = 0;
+    var nextCalled = 0;
+    var nextCalledExpected = 0;
+    var iterable = {};
+    iterable[Symbol.iterator] = makeIterator({
+        next: function() {
+            nextCalled++;
+            return { done: false };
+        },
+        ret: function() {
+            returnCalled++;
+            return { done: true, value: "iter.return" };
+        }
+    });
+
+    function* y() {
+        yield* iterable;
+    }
+
+    // G.p.throw on an iterator without "throw" calls IteratorClose.
+    var g1 = y();
+    g1.next();
+    assertThrowsValue(function() {
+        g1.throw("foo");
+    }, "foo");
+    assertEq(returnCalled, ++returnCalledExpected);
+    assertEq(nextCalled, ++nextCalledExpected);
+    g1.next();
+    assertEq(nextCalled, nextCalledExpected);
+
+    // G.p.return calls "return", and if the result.done is true, return the
+    // result.
+    var g2 = y();
+    g2.next();
+    var v2 = g2.return("test return");
+    assertEq(v2.done, true);
+    assertEq(v2.value, "iter.return");
+    assertEq(returnCalled, ++returnCalledExpected);
+    assertEq(nextCalled, ++nextCalledExpected);
+    g2.next();
+    assertEq(nextCalled, nextCalledExpected);
+
+    // G.p.return calls "return", and if the result.done is false, continue
+    // yielding.
+    iterable[Symbol.iterator] = makeIterator({
+        next: function() {
+            nextCalled++;
+            return { done: false };
+        },
+        ret: function() {
+            returnCalled++;
+            return { done: false, value: "iter.return" };
+        }
+    });
+    var g3 = y();
+    g3.next();
+    var v3 = g3.return("test return");
+    assertEq(v3.done, false);
+    assertEq(v3.value, "iter.return");
+    assertEq(returnCalled, ++returnCalledExpected);
+    assertEq(nextCalled, ++nextCalledExpected);
+    g3.next();
+    assertEq(nextCalled, ++nextCalledExpected);
+
+    // G.p.return throwing does not re-call iter.return.
+    iterable[Symbol.iterator] = makeIterator({
+        ret: function() {
+            returnCalled++;
+            throw "in iter.return";
+        }
+    });
+    var g4 = y();
+    g4.next();
+    assertThrowsValue(function() {
+        g4.return("in test");
+    }, "in iter.return");
+    assertEq(returnCalled, ++returnCalledExpected);
+
+    // G.p.return expects iter.return to return an Object.
+    iterable[Symbol.iterator] = makeIterator({
+        ret: function() {
+            returnCalled++;
+            return 42;
+        }
+    });
+    var g5 = y();
+    g5.next();
+    assertThrowsInstanceOf(function() {
+        g5.return("foo");
+    }, TypeError);
+    assertEq(returnCalled, ++returnCalledExpected);
+
+    // IteratorClose expects iter.return to return an Object.
+    var g6 = y();
+    g6.next();
+    assertThrowsInstanceOf(function() {
+        g6.throw("foo");
+    }, TypeError);
+    assertEq(returnCalled, ++returnCalledExpected);
+
+    // G.p.return passes its argument to "return".
+    iterable[Symbol.iterator] = makeIterator({
+        ret: function(x) {
+            assertEq(x, "in test");
+            returnCalled++;
+            return { done: true };
+        }
+    });
+    var g7 = y();
+    g7.next();
+    g7.return("in test");
+    assertEq(returnCalled, ++returnCalledExpected);
+}
+
+test();
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
--- a/js/src/tests/ecma_6/shell.js
+++ b/js/src/tests/ecma_6/shell.js
@@ -17,24 +17,27 @@
                     yield [items[0]].concat(e);
             }
         }
     };
 
     /** Make an iterator with a return method. */
     global.makeIterator = function makeIterator(overrides) {
         var iterator = {
-            next: function() {
+            throw: function(e) {
+                throw e;
+            },
+            next: function(x) {
                 if (overrides && overrides.next)
-                    return overrides.next();
+                    return overrides.next(x);
                 return { done: false };
             },
-            return: function() {
+            return: function(x) {
                 if (overrides && overrides.ret)
-                    return overrides.ret();
+                    return overrides.ret(x);
                 return { done: true };
             }
         };
 
         return function() { return iterator; };
     };
 })(this);
 
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1881,17 +1881,16 @@ CASE(EnableInterruptsPseudoOpcode)
     /* Commence executing the actual opcode. */
     SANITY_CHECKS();
     DISPATCH_TO(op);
 }
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_NOP_DESTRUCTURING)
-CASE(JSOP_UNUSED187)
 CASE(JSOP_UNUSED192)
 CASE(JSOP_UNUSED209)
 CASE(JSOP_UNUSED210)
 CASE(JSOP_UNUSED211)
 CASE(JSOP_UNUSED219)
 CASE(JSOP_UNUSED220)
 CASE(JSOP_UNUSED221)
 CASE(JSOP_UNUSED222)
@@ -2176,16 +2175,23 @@ CASE(JSOP_ENDITER)
     ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-1].toObject());
     bool ok = CloseIterator(cx, obj);
     REGS.sp--;
     if (!ok)
         goto error;
 }
 END_CASE(JSOP_ENDITER)
 
+CASE(JSOP_ISGENCLOSING)
+{
+    bool b = REGS.sp[-1].isMagic(JS_GENERATOR_CLOSING);
+    PUSH_BOOLEAN(b);
+}
+END_CASE(JSOP_ISGENCLOSING)
+
 CASE(JSOP_DUP)
 {
     MOZ_ASSERT(REGS.stackDepth() >= 1);
     const Value& rref = REGS.sp[-1];
     PUSH_COPY(rref);
 }
 END_CASE(JSOP_DUP)
 
@@ -5071,16 +5077,20 @@ js::ThrowCheckIsObject(JSContext* cx, Ch
       case CheckIsObjectKind::IteratorNext:
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next");
         break;
       case CheckIsObjectKind::IteratorReturn:
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "return");
         break;
+      case CheckIsObjectKind::IteratorThrow:
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                  JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "throw");
+        break;
       case CheckIsObjectKind::GetIterator:
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_GET_ITER_RETURNED_PRIMITIVE);
         break;
       default:
         MOZ_CRASH("Unknown kind");
     }
     return false;
 }
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -558,16 +558,17 @@ ReportRuntimeLexicalError(JSContext* cx,
 // script. Due to the extensibility of the global lexical scope, we also check
 // for redeclarations during runtime in JSOP_DEF{VAR,LET,CONST}.
 void
 ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, const char* redeclKind);
 
 enum class CheckIsObjectKind : uint8_t {
     IteratorNext,
     IteratorReturn,
+    IteratorThrow,
     GetIterator
 };
 
 bool
 ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind);
 
 bool
 ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame);
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1911,18 +1911,26 @@ 1234567890123456789012345678901234567890
      * Pushes 'this' value for current stack frame onto the stack. Emitted when
      * 'this' refers to the global 'this'.
      *   Category: Variables and Scopes
      *   Type: This
      *   Operands:
      *   Stack: => this
      */ \
     macro(JSOP_GLOBALTHIS,    186,"globalthis", NULL,     1,  0,  1,  JOF_BYTE) \
-    macro(JSOP_UNUSED187,     187,"unused187",  NULL,     1,  0,  0,  JOF_BYTE) \
-    \
+    /*
+     * Pushes a boolean indicating whether the top of the stack is
+     * MagicValue(JS_GENERATOR_CLOSING).
+     *
+     *   Category: Statements
+     *   Type: For-In Statement
+     *   Operands:
+     *   Stack: val => val, res
+     */ \
+    macro(JSOP_ISGENCLOSING,  187, "isgenclosing",   NULL,         1,  1,  2,  JOF_BYTE) \
     /*
      * Pushes unsigned 24-bit int immediate integer operand onto the stack.
      *   Category: Literals
      *   Type: Constants
      *   Operands: uint24_t val
      *   Stack: => val
      */ \
     macro(JSOP_UINT24,        188,"uint24",     NULL,     4,  0,  1, JOF_UINT24) \