Bug 1147371 - Implement IteratorClose for array destructuring. (r=arai)
☠☠ backed out by 9a5fc270c79c ☠ ☠
authorShu-yu Guo <shu@rfrn.org>
Thu, 12 Jan 2017 23:51:35 -0800
changeset 374304 7872a2456195318b5fad224f4026a2de32720acc
parent 374303 314efb239b642cbeb4ea9c8fd22806fe0f2a2ea1
child 374305 d9eef2331ae6a7896da0c6caca605317499db73b
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)
reviewersarai
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 IteratorClose for array destructuring. (r=arai) Since result.done is always needed now, always emit the code that pushes it on the stack. For throwing, like for-of, IteratorClose is only called when non-iterator code throws. Unlike for-of, both the iterator object and the done boolean value are on the stack for the trynote. IteratorClose is only called when !done.
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/jit/JitFrames.cpp
js/src/jsscript.h
js/src/shell/js.cpp
js/src/tests/ecma_6/Destructuring/array-iterator-close.js
js/src/vm/Interpreter.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -4950,26 +4950,26 @@ BytecodeEmitter::emitIteratorClose(Maybe
     if (!ifReturnMethodIsDefined.emitEnd())
         return false;
 
     return emit1(JSOP_POP);                               // ...
 }
 
 template <typename InnerEmitter>
 bool
-BytecodeEmitter::wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter)
+BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter)
 {
     MOZ_ASSERT(this->stackDepth >= iterDepth);
 
     ptrdiff_t start = offset();
     if (!emitter(this))
         return false;
     ptrdiff_t end = offset();
     if (start != end)
-        return tryNoteList.append(JSTRY_ITERCLOSE, iterDepth, start, end);
+        return tryNoteList.append(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start, end);
     return true;
 }
 
 bool
 BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern)
 {
     if (!emit1(JSOP_DUP))                                 // VALUE VALUE
         return false;
@@ -5060,118 +5060,161 @@ bool
 BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav)
 {
     MOZ_ASSERT(pattern->isKind(PNK_ARRAY));
     MOZ_ASSERT(pattern->isArity(PN_LIST));
     MOZ_ASSERT(this->stackDepth != 0);
 
     // Here's pseudo code for |let [a, b, , c=y, ...d] = x;|
     //
+    // Lines that are annotated "covered by trynote" mean that upon throwing
+    // an exception, IteratorClose is called on iter only if done is false.
+    //
     //   let x, y;
     //   let a, b, c, d;
     //   let iter, lref, result, done, value; // stack values
     //
     //   iter = x[Symbol.iterator]();
     //
     //   // ==== emitted by loop for a ====
-    //   lref = GetReference(a);
+    //   lref = GetReference(a);              // covered by trynote
     //
     //   result = iter.next();
     //   done = result.done;
     //
     //   if (done)
     //     value = undefined;
     //   else
     //     value = result.value;
     //
-    //   SetOrInitialize(lref, value);
+    //   SetOrInitialize(lref, value);        // covered by trynote
     //
     //   // ==== emitted by loop for b ====
-    //   lref = GetReference(b);
+    //   lref = GetReference(b);              // covered by trynote
     //
     //   if (done) {
     //     value = undefined;
     //   } else {
     //     result = iter.next();
     //     done = result.done;
     //     if (done)
     //       value = undefined;
     //     else
     //       value = result.value;
     //   }
     //
-    //   SetOrInitialize(lref, value);
+    //   SetOrInitialize(lref, value);        // covered by trynote
     //
     //   // ==== emitted by loop for elision ====
     //   if (done) {
     //     value = undefined;
     //   } else {
     //     result = iter.next();
     //     done = result.done;
     //     if (done)
     //       value = undefined;
     //     else
     //       value = result.value;
     //   }
     //
     //   // ==== emitted by loop for c ====
-    //   lref = GetReference(c);
+    //   lref = GetReference(c);              // covered by trynote
     //
     //   if (done) {
     //     value = undefined;
     //   } else {
     //     result = iter.next();
     //     done = result.done;
     //     if (done)
     //       value = undefined;
     //     else
     //       value = result.value;
     //   }
     //
     //   if (value === undefined)
-    //     value = y;
+    //     value = y;                         // covered by trynote
     //
-    //   SetOrInitialize(lref, value);
+    //   SetOrInitialize(lref, value);        // covered by trynote
     //
     //   // ==== emitted by loop for d ====
-    //   lref = GetReference(d);
+    //   lref = GetReference(d);              // covered by trynote
     //
     //   if (done)
     //     value = [];
     //   else
     //     value = [...iter];
     //
-    //   SetOrInitialize(lref, value);
+    //   SetOrInitialize(lref, value);        // covered by trynote
+    //
+    //   // === emitted after loop ===
+    //   if (!done)
+    //      IteratorClose(iter);
 
     // Use an iterator to destructure the RHS, instead of index lookup. We
     // must leave the *original* value on the stack.
     if (!emit1(JSOP_DUP))                                         // ... OBJ OBJ
         return false;
     if (!emitIterator())                                          // ... OBJ ITER
         return false;
 
+    // For an empty pattern [], call IteratorClose unconditionally. Nothing
+    // else needs to be done.
+    if (!pattern->pn_head)
+        return emitIteratorClose();                               // ... OBJ
+
+    // Push an initial FALSE value for DONE.
+    if (!emit1(JSOP_FALSE))                                       // ... OBJ ITER FALSE
+        return false;
+
+    // JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value
+    // to be the second to top and the top of the stack, respectively.
+    // IteratorClose is called upon exception only if done is false.
+    int32_t tryNoteDepth = stackDepth;
+
     for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
-        bool isHead = member == pattern->pn_head;
-        bool hasNext = !!member->pn_next;
+        bool isFirst = member == pattern->pn_head;
+        DebugOnly<bool> hasNext = !!member->pn_next;
+
+        size_t emitted = 0;
+
+        // Spec requires LHS reference to be evaluated first.
+        ParseNode* lhsPattern = member;
+        if (lhsPattern->isKind(PNK_ASSIGN))
+            lhsPattern = lhsPattern->pn_left;
+
+        bool isElision = lhsPattern->isKind(PNK_ELISION);
+        if (!isElision) {
+            auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) {
+                return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // ... OBJ ITER DONE *LREF
+            };
+            if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef))
+                return false;
+        }
+
+        // Pick the DONE value to the top of the stack.
+        if (emitted) {
+            if (!emit2(JSOP_PICK, emitted))                       // ... OBJ ITER *LREF DONE
+                return false;
+        }
+
+        if (isFirst) {
+            // If this element is the first, DONE is always FALSE, so pop it.
+            //
+            // Non-first elements should emit if-else depending on the
+            // member pattern, below.
+            if (!emit1(JSOP_POP))                                 // ... OBJ ITER *LREF
+                return false;
+        }
 
         if (member->isKind(PNK_SPREAD)) {
-            size_t emitted = 0;
-            if (!emitDestructuringLHSRef(member, &emitted))       // ... OBJ ITER ?DONE *LREF
-                return false;
-
             IfThenElseEmitter ifThenElse(this);
-            if (!isHead) {
+            if (!isFirst) {
                 // If spread is not the first element of the pattern,
                 // iterator can already be completed.
-                //                                                   ... OBJ ITER DONE *LREF
-                if (emitted) {
-                    if (!emit2(JSOP_PICK, emitted))               // ... OBJ ITER *LREF DONE
-                        return false;
-                }
-
+                                                                  // ... OBJ ITER *LREF DONE
                 if (!ifThenElse.emitIfElse())                     // ... OBJ ITER *LREF
                     return false;
 
                 if (!emitUint32Operand(JSOP_NEWARRAY, 0))         // ... OBJ ITER *LREF ARRAY
                     return false;
                 if (!ifThenElse.emitElse())                       // ... OBJ ITER *LREF
                     return false;
             }
@@ -5184,156 +5227,147 @@ BytecodeEmitter::emitDestructuringOpsArr
                 return false;
             if (!emitNumberOp(0))                                 // ... OBJ ITER *LREF ITER ARRAY INDEX
                 return false;
             if (!emitSpread())                                    // ... OBJ ITER *LREF ARRAY INDEX
                 return false;
             if (!emit1(JSOP_POP))                                 // ... OBJ ITER *LREF ARRAY
                 return false;
 
-            if (!isHead) {
+            if (!isFirst) {
                 if (!ifThenElse.emitEnd())
                     return false;
                 MOZ_ASSERT(ifThenElse.pushed() == 1);
             }
 
-            if (!emitSetOrInitializeDestructuring(member, flav))  // ... OBJ ITER
+            // At this point the iterator is done. Unpick a TRUE value for DONE above ITER.
+            if (!emit1(JSOP_TRUE))                                // ... OBJ ITER *LREF ARRAY TRUE
+                return false;
+            if (!emit2(JSOP_UNPICK, emitted + 1))                 // ... OBJ ITER TRUE *LREF ARRAY
+                return false;
+
+            auto emitAssignment = [member, flav](BytecodeEmitter* bce) {
+                return bce->emitSetOrInitializeDestructuring(member, flav); // ... OBJ ITER TRUE
+            };
+            if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment))
                 return false;
 
             MOZ_ASSERT(!hasNext);
             break;
         }
 
         ParseNode* pndefault = nullptr;
-        ParseNode* subpattern = member;
-        if (subpattern->isKind(PNK_ASSIGN)) {
-            pndefault = subpattern->pn_right;
-            subpattern = subpattern->pn_left;
-        }
-
-        bool isElision = subpattern->isKind(PNK_ELISION);
-
-        MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD));
-
-        size_t emitted = 0;
-        if (!isElision) {
-            if (!emitDestructuringLHSRef(subpattern, &emitted))   // ... OBJ ITER ?DONE *LREF
-                return false;
-        }
+        if (member->isKind(PNK_ASSIGN))
+            pndefault = member->pn_right;
+
+        MOZ_ASSERT(!member->isKind(PNK_SPREAD));
 
         IfThenElseEmitter ifAlreadyDone(this);
-        if (!isHead) {
-            // If this element is not the first element of the pattern,
-            // iterator can already be completed.
-            //                                                       ... OBJ ITER DONE *LREF
-            if (emitted) {
-                if (hasNext) {
-                    if (!emitDupAt(emitted))                      // ... OBJ ITER DONE *LREF DONE
-                        return false;
-                } else {
-                    if (!emit2(JSOP_PICK, emitted))               // ... OBJ ITER *LREF DONE
-                        return false;
-                }
-            } else {
-                if (hasNext) {
-                    // The position of LREF in the following stack comment
-                    // isn't accurate for the operation, but it's equivalent
-                    // since LREF is nothing
-                    if (!emit1(JSOP_DUP))                         // ... OBJ ITER DONE *LREF DONE
-                        return false;
-                }
-            }
-            if (!ifAlreadyDone.emitIfElse())                      // ... OBJ ITER ?DONE *LREF
-                return false;
-
-            if (!emit1(JSOP_UNDEFINED))                           // ... OBJ ITER ?DONE *LREF UNDEF
-                return false;
-            if (!emit1(JSOP_NOP_DESTRUCTURING))                   // ... OBJ ITER ?DONE *LREF UNDEF
-                return false;
-
-            if (!ifAlreadyDone.emitElse())                        // ... OBJ ITER ?DONE *LREF
-                return false;
-
-            if (hasNext) {
-                if (emitted) {
-                    if (!emit2(JSOP_PICK, emitted))               // ... OBJ ITER *LREF DONE
-                        return false;
-                }
-                if (!emit1(JSOP_POP))                             // ... OBJ ITER *LREF
-                    return false;
-            }
+        if (!isFirst) {
+                                                                  // ... OBJ ITER *LREF DONE
+            if (!ifAlreadyDone.emitIfElse())                      // ... OBJ ITER *LREF
+                return false;
+
+            if (!emit1(JSOP_UNDEFINED))                           // ... OBJ ITER *LREF UNDEF
+                return false;
+            if (!emit1(JSOP_NOP_DESTRUCTURING))                   // ... OBJ ITER *LREF UNDEF
+                return false;
+
+            // The iterator is done. Unpick a TRUE value for DONE above ITER.
+            if (!emit1(JSOP_TRUE))                                // ... OBJ ITER *LREF UNDEF TRUE
+                return false;
+            if (!emit2(JSOP_UNPICK, emitted + 1))                 // ... OBJ ITER TRUE *LREF UNDEF
+                return false;
+
+            if (!ifAlreadyDone.emitElse())                        // ... OBJ ITER *LREF
+                return false;
         }
 
         if (emitted) {
             if (!emitDupAt(emitted))                              // ... OBJ ITER *LREF ITER
                 return false;
         } else {
             if (!emit1(JSOP_DUP))                                 // ... OBJ ITER *LREF ITER
                 return false;
         }
         if (!emitIteratorNext(pattern))                           // ... OBJ ITER *LREF RESULT
             return false;
         if (!emit1(JSOP_DUP))                                     // ... OBJ ITER *LREF RESULT RESULT
             return false;
         if (!emitAtomOp(cx->names().done, JSOP_GETPROP))          // ... OBJ ITER *LREF RESULT DONE
             return false;
 
-        if (hasNext) {
-            if (!emit1(JSOP_DUP))                                 // ... OBJ ITER *LREF RESULT DONE DONE
-                return false;
-            if (!emit2(JSOP_UNPICK, emitted + 2))                 // ... OBJ ITER DONE *LREF RESULT DONE
-                return false;
-        }
+        if (!emit1(JSOP_DUP))                                     // ... OBJ ITER *LREF RESULT DONE DONE
+            return false;
+        if (!emit2(JSOP_UNPICK, emitted + 2))                     // ... OBJ ITER DONE *LREF RESULT DONE
+            return false;
 
         IfThenElseEmitter ifDone(this);
-        if (!ifDone.emitIfElse())                                 // ... OBJ ITER ?DONE *LREF RESULT
-            return false;
-
-        if (!emit1(JSOP_POP))                                     // ... OBJ ITER ?DONE *LREF
-            return false;
-        if (!emit1(JSOP_UNDEFINED))                               // ... OBJ ITER ?DONE *LREF UNDEF
-            return false;
-        if (!emit1(JSOP_NOP_DESTRUCTURING))                       // ... OBJ ITER ?DONE *LREF UNDEF
-            return false;
-
-        if (!ifDone.emitElse())                                   // ... OBJ ITER ?DONE *LREF RESULT
-            return false;
-
-        if (!emitAtomOp(cx->names().value, JSOP_GETPROP))         // ... OBJ ITER ?DONE *LREF VALUE
+        if (!ifDone.emitIfElse())                                 // ... OBJ ITER DONE *LREF RESULT
+            return false;
+
+        if (!emit1(JSOP_POP))                                     // ... OBJ ITER DONE *LREF
+            return false;
+        if (!emit1(JSOP_UNDEFINED))                               // ... OBJ ITER DONE *LREF UNDEF
+            return false;
+        if (!emit1(JSOP_NOP_DESTRUCTURING))                       // ... OBJ ITER DONE *LREF UNDEF
+            return false;
+
+        if (!ifDone.emitElse())                                   // ... OBJ ITER DONE *LREF RESULT
+            return false;
+
+        if (!emitAtomOp(cx->names().value, JSOP_GETPROP))         // ... OBJ ITER DONE *LREF VALUE
             return false;
 
         if (!ifDone.emitEnd())
             return false;
         MOZ_ASSERT(ifDone.pushed() == 0);
 
-        if (!isHead) {
+        if (!isFirst) {
             if (!ifAlreadyDone.emitEnd())
                 return false;
-            MOZ_ASSERT(ifAlreadyDone.pushed() == 1);
+            MOZ_ASSERT(ifAlreadyDone.pushed() == 2);
         }
 
         if (pndefault) {
-            if (!emitDefault(pndefault, subpattern))              // ... OBJ ITER ?DONE *LREF VALUE
+            auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) {
+                return bce->emitDefault(pndefault, lhsPattern);    // ... OBJ ITER DONE *LREF VALUE
+            };
+
+            if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault))
                 return false;
         }
 
         if (!isElision) {
-            if (!emitSetOrInitializeDestructuring(subpattern,
-                                                  flav))          // ... OBJ ITER ?DONE
-            {
-                return false;
-            }
+            auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
+                return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ ITER DONE
+            };
+
+            if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment))
+                return false;
         } else {
-            if (!emit1(JSOP_POP))                                 // ... OBJ ITER ?DONE
-                return false;
-        }
-    }
-
+            if (!emit1(JSOP_POP))                                 // ... OBJ ITER DONE
+                return false;
+        }
+    }
+
+    // The last DONE value is on top of the stack. If not DONE, call
+    // IteratorClose.
+                                                                  // ... OBJ ITER DONE
+    IfThenElseEmitter ifDone(this);
+    if (!ifDone.emitIfElse())                                     // ... OBJ ITER
+        return false;
     if (!emit1(JSOP_POP))                                         // ... OBJ
         return false;
+    if (!ifDone.emitElse())                                       // ... OBJ ITER
+        return false;
+    if (!emitIteratorClose())                                     // ... OBJ
+        return false;
+    if (!ifDone.emitEnd())
+        return false;
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitComputedPropertyName(ParseNode* computedPropName)
 {
     MOZ_ASSERT(computedPropName->isKind(PNK_COMPUTED_NAME));
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -679,17 +679,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     // Pops iterator from the top of the stack. Pushes the result of |.next()|
     // onto the stack.
     MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false);
     MOZ_MUST_USE bool emitIteratorClose(
         mozilla::Maybe<JumpTarget> yieldStarTryStart = mozilla::Nothing(),
         bool allowSelfHosted = false);
 
     template <typename InnerEmitter>
-    MOZ_MUST_USE bool wrapWithIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter);
+    MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth,
+                                                                InnerEmitter emitter);
 
     // Check if the value on top of the stack is "undefined". If so, replace
     // that value on the stack with the value defined by |defaultExpr|.
     // |pattern| is a lhs node of the default expression.  If it's an
     // identifier and |defaultExpr| is an anonymous function, |SetFunctionName|
     // is called at compile time.
     MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
 
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -325,38 +325,54 @@ NumArgAndLocalSlots(const InlineFrameIte
 {
     JSScript* script = frame.script();
     return CountArgSlots(script, frame.maybeCalleeTemplate()) + script->nfixed();
 }
 
 static void
 CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn)
 {
-    MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || tn->kind == JSTRY_ITERCLOSE);
-    MOZ_ASSERT(tn->stackDepth > 0);
+    MOZ_ASSERT(tn->kind == JSTRY_FOR_IN ||
+               tn->kind == JSTRY_ITERCLOSE ||
+               tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE);
+
+    bool isDestructuring = tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE;
+    MOZ_ASSERT_IF(!isDestructuring, tn->stackDepth > 0);
+    MOZ_ASSERT_IF(isDestructuring, tn->stackDepth > 1);
 
     SnapshotIterator si = frame.snapshotIterator();
 
-    // Skip stack slots until we reach the iterator object.
+    // Skip stack slots until we reach the iterator object on the stack. For
+    // the destructuring case, we also need to get the "done" value.
     uint32_t stackSlot = tn->stackDepth;
-    uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - 1;
+    uint32_t adjust = isDestructuring ? 2 : 1;
+    uint32_t skipSlots = NumArgAndLocalSlots(frame) + stackSlot - adjust;
 
     for (unsigned i = 0; i < skipSlots; i++)
         si.skip();
 
     Value v = si.read();
-    RootedObject obj(cx, &v.toObject());
+    RootedObject iterObject(cx, &v.toObject());
+
+    if (isDestructuring) {
+        Value v = si.read();
+        MOZ_ASSERT(v.isBoolean());
+        // Do not call IteratorClose if the destructuring iterator is already
+        // done.
+        if (v.isTrue())
+            return;
+    }
 
     if (cx->isExceptionPending()) {
         if (tn->kind == JSTRY_FOR_IN)
-            UnwindIteratorForException(cx, obj);
+            UnwindIteratorForException(cx, iterObject);
         else
-            IteratorCloseForException(cx, obj);
+            IteratorCloseForException(cx, iterObject);
     } else {
-        UnwindIteratorForUncatchableException(cx, obj);
+        UnwindIteratorForUncatchableException(cx, iterObject);
     }
 }
 
 class IonFrameStackDepthOp
 {
     uint32_t depth_;
 
   public:
@@ -421,23 +437,22 @@ HandleExceptionIon(JSContext* cx, const 
     if (!script->hasTrynotes())
         return;
 
     for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) {
         JSTryNote* tn = *tni;
 
         switch (tn->kind) {
           case JSTRY_FOR_IN:
-          case JSTRY_ITERCLOSE: {
+          case JSTRY_ITERCLOSE:
+          case JSTRY_DESTRUCTURING_ITERCLOSE:
             MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN,
                           JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER);
-            MOZ_ASSERT(tn->stackDepth > 0);
             CloseLiveIteratorIon(cx, frame, tn);
             break;
-          }
 
           case JSTRY_FOR_OF:
           case JSTRY_LOOP:
             break;
 
           case JSTRY_CATCH:
             if (cx->isExceptionPending()) {
                 // Ion can compile try-catch, but bailing out to catch
@@ -601,38 +616,62 @@ ProcessTryNotesBaseline(JSContext* cx, c
             rfe->target = script->baselineScript()->nativeCodeForPC(script, *pc);
             // Drop the exception instead of leaking cross compartment data.
             if (!cx->getPendingException(MutableHandleValue::fromMarkedLocation(&rfe->exception)))
                 rfe->exception = UndefinedValue();
             cx->clearPendingException();
             return true;
           }
 
-          case JSTRY_FOR_IN:
+          case JSTRY_FOR_IN: {
+            uint8_t* framePointer;
+            uint8_t* stackPointer;
+            BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
+            Value iterValue(*reinterpret_cast<Value*>(stackPointer));
+            RootedObject iterObject(cx, &iterValue.toObject());
+            if (!UnwindIteratorForException(cx, iterObject)) {
+                // See comment in the JSTRY_FOR_IN case in Interpreter.cpp's
+                // ProcessTryNotes.
+                SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
+                MOZ_ASSERT(**pc == JSOP_ENDITER);
+                return false;
+            }
+            break;
+          }
+
           case JSTRY_ITERCLOSE: {
             uint8_t* framePointer;
             uint8_t* stackPointer;
             BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
-            Value iterValue(*(reinterpret_cast<Value*>(stackPointer)));
+            Value iterValue(*reinterpret_cast<Value*>(stackPointer));
             RootedObject iterObject(cx, &iterValue.toObject());
-            bool ok;
-            if (tn->kind == JSTRY_FOR_IN)
-                ok = UnwindIteratorForException(cx, iterObject);
-            else
-                ok = IteratorCloseForException(cx, iterObject);
-            if (!ok) {
-                // See comment in the JSTRY_FOR_IN case in Interpreter.cpp's
-                // ProcessTryNotes.
+            if (!IteratorCloseForException(cx, iterObject)) {
                 SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
-                MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, **pc == JSOP_ENDITER);
                 return false;
             }
             break;
           }
 
+          case JSTRY_DESTRUCTURING_ITERCLOSE: {
+            uint8_t* framePointer;
+            uint8_t* stackPointer;
+            BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer);
+            Value doneValue(*(reinterpret_cast<Value*>(stackPointer)));
+            MOZ_ASSERT(doneValue.isBoolean());
+            if (doneValue.isFalse()) {
+                Value iterValue(*(reinterpret_cast<Value*>(stackPointer) + 1));
+                RootedObject iterObject(cx, &iterValue.toObject());
+                if (!IteratorCloseForException(cx, iterObject)) {
+                    SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
+                    return false;
+                }
+            }
+            break;
+          }
+
           case JSTRY_FOR_OF:
           case JSTRY_LOOP:
             break;
 
           default:
             MOZ_CRASH("Invalid try note");
         }
     }
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -80,17 +80,18 @@ CopyScript(JSContext* cx, HandleScript s
  * heuristics that need to know whether a given op is inside a loop.
  */
 enum JSTryNoteKind {
     JSTRY_CATCH,
     JSTRY_FINALLY,
     JSTRY_FOR_IN,
     JSTRY_FOR_OF,
     JSTRY_LOOP,
-    JSTRY_ITERCLOSE
+    JSTRY_ITERCLOSE,
+    JSTRY_DESTRUCTURING_ITERCLOSE
 };
 
 /*
  * Exception handling record.
  */
 struct JSTryNote {
     uint8_t         kind;       /* one of JSTryNoteKind */
     uint32_t        stackDepth; /* stack depth upon exception handler entry */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2584,40 +2584,55 @@ Notes(JSContext* cx, unsigned argc, Valu
 
     JSString* str = JS_NewStringCopyZ(cx, sprinter.string());
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
-JS_STATIC_ASSERT(JSTRY_CATCH == 0);
-JS_STATIC_ASSERT(JSTRY_FINALLY == 1);
-JS_STATIC_ASSERT(JSTRY_FOR_IN == 2);
-
-static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop",
-                                            "iterclose" };
+static const char*
+TryNoteName(JSTryNoteKind kind)
+{
+    switch (kind) {
+      case JSTRY_CATCH:
+        return "catch";
+      case JSTRY_FINALLY:
+        return "finally";
+      case JSTRY_FOR_IN:
+        return "for-in";
+      case JSTRY_FOR_OF:
+        return "for-of";
+      case JSTRY_LOOP:
+        return "loop";
+      case JSTRY_ITERCLOSE:
+        return "iterclose";
+      case JSTRY_DESTRUCTURING_ITERCLOSE:
+        return "dstr-iterclose";
+    }
+
+    MOZ_CRASH("Bad JSTryNoteKind");
+}
 
 static MOZ_MUST_USE bool
 TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
 {
     if (!script->hasTrynotes())
         return true;
 
-    if (sp->put("\nException table:\nkind        stack    start      end\n") < 0)
+    if (sp->put("\nException table:\nkind             stack    start      end\n") < 0)
         return false;
 
     JSTryNote* tn = script->trynotes()->vector;
     JSTryNote* tnlimit = tn + script->trynotes()->length;
     do {
-        MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames));
         uint32_t startOff = script->pcToOffset(script->main()) + tn->start;
-        if (!sp->jsprintf(" %-9s %6u %8u %8u\n",
-                          TryNoteNames[tn->kind], tn->stackDepth,
-                          startOff, startOff + tn->length))
+        if (!sp->jsprintf(" %-14s %6u %8u %8u\n",
+                          TryNoteName(static_cast<JSTryNoteKind>(tn->kind)),
+                          tn->stackDepth, startOff, startOff + tn->length))
         {
             return false;
         }
     } while (++tn != tnlimit);
     return true;
 }
 
 static MOZ_MUST_USE bool
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Destructuring/array-iterator-close.js
@@ -0,0 +1,77 @@
+// Tests that IteratorClose is called in array destructuring patterns.
+
+function test() {
+    var returnCalled = 0;
+    var returnCalledExpected = 0;
+    var iterable = {};
+
+    // empty [] calls IteratorClose regardless of "done" on the result.
+    iterable[Symbol.iterator] = makeIterator({
+        next: function() {
+            return { done: true };
+        },
+        ret: function() {
+            returnCalled++;
+            return {};
+        }
+    });
+    var [] = iterable;
+    assertEq(returnCalled, ++returnCalledExpected);
+
+    iterable[Symbol.iterator] = makeIterator({
+        ret: function() {
+            returnCalled++;
+            return {};
+        }
+    });
+    var [] = iterable;
+    assertEq(returnCalled, ++returnCalledExpected);
+
+    // Non-empty destructuring calls IteratorClose if iterator is not done by
+    // the end of destructuring.
+    var [a,b] = iterable;
+    assertEq(returnCalled, ++returnCalledExpected);
+    var [c,] = iterable;
+    assertEq(returnCalled, ++returnCalledExpected);
+
+    // throw in lhs ref calls IteratorClose
+    function throwlhs() {
+        throw "in lhs";
+    }
+    assertThrowsValue(function() {
+        0, [...{}[throwlhs()]] = iterable;
+    }, "in lhs");
+    assertEq(returnCalled, ++returnCalledExpected);
+
+    // throw in iter.next doesn't call IteratorClose
+    iterable[Symbol.iterator] = makeIterator({
+        next: function() {
+            throw "in next";
+        },
+        ret: function() {
+            returnCalled++;
+            return {};
+        }
+    });
+    assertThrowsValue(function() {
+        var [d] = iterable;
+    }, "in next");
+    assertEq(returnCalled, returnCalledExpected);
+
+    // "return" must return an Object.
+    iterable[Symbol.iterator] = makeIterator({
+        ret: function() {
+            returnCalled++;
+            return 42;
+        }
+    });
+    assertThrowsInstanceOf(function() {
+        var [] = iterable;
+    }, TypeError);
+    assertEq(returnCalled, ++returnCalledExpected);
+}
+
+test();
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1169,40 +1169,61 @@ ProcessTryNotes(JSContext* cx, Environme
                 break;
             SettleOnTryNote(cx, tn, ei, regs);
             return CatchContinuation;
 
           case JSTRY_FINALLY:
             SettleOnTryNote(cx, tn, ei, regs);
             return FinallyContinuation;
 
-          case JSTRY_FOR_IN:
-          case JSTRY_ITERCLOSE: {
+          case JSTRY_FOR_IN: {
             /* This is similar to JSOP_ENDITER in the interpreter loop. */
             DebugOnly<jsbytecode*> pc = regs.fp()->script()->main() + tn->start + tn->length;
-            MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, JSOp(*pc) == JSOP_ENDITER);
+            MOZ_ASSERT(JSOp(*pc) == JSOP_ENDITER);
             Value* sp = regs.spForStackDepth(tn->stackDepth);
             RootedObject obj(cx, &sp[-1].toObject());
-            bool ok;
-            if (tn->kind == JSTRY_FOR_IN)
-                ok = UnwindIteratorForException(cx, obj);
-            else
-                ok = IteratorCloseForException(cx, obj);
-            if (!ok) {
+            if (!UnwindIteratorForException(cx, obj)) {
                 // We should only settle on the note only if
                 // UnwindIteratorForException itself threw, as
                 // onExceptionUnwind should be called anew with the new
                 // location of the throw (the iterator). Indeed, we must
                 // settle to avoid infinitely handling the same exception.
                 SettleOnTryNote(cx, tn, ei, regs);
                 return ErrorReturnContinuation;
             }
             break;
           }
 
+          case JSTRY_ITERCLOSE: {
+            // The iterator object is at the top of the stack.
+            Value* sp = regs.spForStackDepth(tn->stackDepth);
+            RootedObject iterObject(cx, &sp[-1].toObject());
+            if (!IteratorCloseForException(cx, iterObject)) {
+                SettleOnTryNote(cx, tn, ei, regs);
+                return ErrorReturnContinuation;
+            }
+            break;
+          }
+
+          case JSTRY_DESTRUCTURING_ITERCLOSE: {
+            // Whether the destructuring iterator is done is at the top of the
+            // stack. The iterator object is second from the top.
+            MOZ_ASSERT(tn->stackDepth > 1);
+            Value* sp = regs.spForStackDepth(tn->stackDepth);
+            MOZ_ASSERT(sp[-1].isBoolean());
+            if (sp[-1].isFalse()) {
+                RootedObject iterObject(cx, &sp[-2].toObject());
+                if (!IteratorCloseForException(cx, iterObject)) {
+                    SettleOnTryNote(cx, tn, ei, regs);
+                    return ErrorReturnContinuation;
+                }
+            }
+            break;
+          }
+
           case JSTRY_FOR_OF:
           case JSTRY_LOOP:
             break;
 
           default:
             MOZ_CRASH("Invalid try note");
         }
     }