summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartok <martok@martoks-place.de>2023-03-24 01:16:57 +0100
committerMartok <martok@martoks-place.de>2023-03-26 20:20:16 +0200
commitc26df1a48a4f27d6df75ff4904f94bc403b97e0d (patch)
treefedcbfd60a97415d4458a5cd66d99a03f6abb0a8
parent0a2eb24435f3114aa70c253710dd6a4ebe863ff5 (diff)
downloaduxp-c26df1a48a4f27d6df75ff4904f94bc403b97e0d.tar.gz
Issue #2155 - Move TryEmitter and ForOfLoopControl to TryEmitter.{cpp.h} and ForOfLoopControl.{cpp.h}
Based-on: m-c 1460489/5
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp533
-rw-r--r--js/src/frontend/ForOfLoopControl.cpp167
-rw-r--r--js/src/frontend/ForOfLoopControl.h99
-rw-r--r--js/src/frontend/TryEmitter.cpp286
-rw-r--r--js/src/frontend/TryEmitter.h117
-rw-r--r--js/src/moz.build2
6 files changed, 673 insertions, 531 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index 85e123b7c4..8a67a50655 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -32,12 +32,14 @@
#include "frontend/CallOrNewEmitter.h"
#include "frontend/ElemOpEmitter.h"
#include "frontend/EmitterScope.h"
+#include "frontend/ForOfLoopControl.h"
#include "frontend/IfEmitter.h"
#include "frontend/NameOpEmitter.h"
#include "frontend/Parser.h"
#include "frontend/PropOpEmitter.h"
#include "frontend/TDZCheckCache.h"
#include "frontend/TokenStream.h"
+#include "frontend/TryEmitter.h"
#include "vm/Debugger.h"
#include "vm/GeneratorObject.h"
#include "vm/Stack.h"
@@ -85,334 +87,6 @@ GetCallArgsAndCount(ParseNode* callNode, ParseNode** argumentNode)
return callNode->pn_count - 1;
}
-class MOZ_STACK_CLASS TryEmitter
-{
- public:
- enum Kind {
- TryCatch,
- TryCatchFinally,
- TryFinally
- };
- enum ShouldUseRetVal {
- UseRetVal,
- DontUseRetVal
- };
- enum ShouldUseControl {
- UseControl,
- DontUseControl,
- };
-
- private:
- BytecodeEmitter* bce_;
- Kind kind_;
- ShouldUseRetVal retValKind_;
-
- // Track jumps-over-catches and gosubs-to-finally for later fixup.
- //
- // When a finally block is active, non-local jumps (including
- // jumps-over-catches) result in a GOSUB being written into the bytecode
- // stream and fixed-up later.
- //
- // If ShouldUseControl is DontUseControl, all that handling is skipped.
- // DontUseControl is used by yield* and the internal try-catch around
- // IteratorClose. These internal uses must:
- // * have only one catch block
- // * have no catch guard
- // * have JSOP_GOTO at the end of catch-block
- // * have no non-local-jump
- // * don't use finally block for normal completion of try-block and
- // catch-block
- //
- // Additionally, a finally block may be emitted when ShouldUseControl is
- // DontUseControl, even if the kind is not TryCatchFinally or TryFinally,
- // because GOSUBs are not emitted. This internal use shares the
- // requirements as above.
- Maybe<TryFinallyControl> controlInfo_;
-
- int depth_;
- unsigned noteIndex_;
- ptrdiff_t tryStart_;
- JumpList catchAndFinallyJump_;
- JumpTarget tryEnd_;
- JumpTarget finallyStart_;
-
- enum State {
- Start,
- Try,
- TryEnd,
- Catch,
- CatchEnd,
- Finally,
- FinallyEnd,
- End
- };
- State state_;
-
- bool hasCatch() const {
- return kind_ == TryCatch || kind_ == TryCatchFinally;
- }
- bool hasFinally() const {
- return kind_ == TryCatchFinally || kind_ == TryFinally;
- }
-
- public:
- TryEmitter(BytecodeEmitter* bce, Kind kind, ShouldUseRetVal retValKind = UseRetVal,
- ShouldUseControl controlKind = UseControl)
- : bce_(bce),
- kind_(kind),
- retValKind_(retValKind),
- depth_(0),
- noteIndex_(0),
- tryStart_(0),
- state_(Start)
- {
- if (controlKind == UseControl)
- controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
- finallyStart_.offset = 0;
- }
-
- bool emitJumpOverCatchAndFinally() {
- if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
- return false;
- return true;
- }
-
- bool emitTry() {
- MOZ_ASSERT(state_ == Start);
-
- // Since an exception can be thrown at any place inside the try block,
- // we need to restore the stack and the scope chain before we transfer
- // the control to the exception handler.
- //
- // For that we store in a try note associated with the catch or
- // finally block the stack depth upon the try entry. The interpreter
- // uses this depth to properly unwind the stack and the scope chain.
- depth_ = bce_->stackDepth;
-
- // Record the try location, then emit the try block.
- if (!bce_->newSrcNote(SRC_TRY, &noteIndex_))
- return false;
- if (!bce_->emit1(JSOP_TRY))
- return false;
- tryStart_ = bce_->offset();
-
- state_ = Try;
- return true;
- }
-
- private:
- bool emitTryEnd() {
- MOZ_ASSERT(state_ == Try);
- MOZ_ASSERT(depth_ == bce_->stackDepth);
-
- // GOSUB to finally, if present.
- if (hasFinally() && controlInfo_) {
- if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
- return false;
- }
-
- // Source note points to the jump at the end of the try block.
- if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH))
- return false;
-
- // Emit jump over catch and/or finally.
- if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
- return false;
-
- if (!bce_->emitJumpTarget(&tryEnd_))
- return false;
-
- return true;
- }
-
- public:
- bool emitCatch() {
- if (state_ == Try) {
- if (!emitTryEnd())
- return false;
- } else {
- MOZ_ASSERT(state_ == Catch);
- if (!emitCatchEnd(true))
- return false;
- }
-
- MOZ_ASSERT(bce_->stackDepth == depth_);
-
- if (retValKind_ == UseRetVal) {
- // Clear the frame's return value that might have been set by the
- // try block:
- //
- // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
- if (!bce_->emit1(JSOP_UNDEFINED))
- return false;
- if (!bce_->emit1(JSOP_SETRVAL))
- return false;
- }
-
- state_ = Catch;
- return true;
- }
-
- private:
- bool emitCatchEnd(bool hasNext) {
- MOZ_ASSERT(state_ == Catch);
-
- if (!controlInfo_)
- return true;
-
- // gosub <finally>, if required.
- if (hasFinally()) {
- if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
- return false;
- MOZ_ASSERT(bce_->stackDepth == depth_);
- }
-
- // Jump over the remaining catch blocks. This will get fixed
- // up to jump to after catch/finally.
- if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
- return false;
-
- // If this catch block had a guard clause, patch the guard jump to
- // come here.
- if (controlInfo_->guardJump.offset != -1) {
- if (!bce_->emitJumpTargetAndPatch(controlInfo_->guardJump))
- return false;
- controlInfo_->guardJump.offset = -1;
-
- // If this catch block is the last one, rethrow, delegating
- // execution of any finally block to the exception handler.
- if (!hasNext) {
- if (!bce_->emit1(JSOP_EXCEPTION))
- return false;
- if (!bce_->emit1(JSOP_THROW))
- return false;
- }
- }
-
- return true;
- }
-
- public:
- bool emitFinally(Maybe<uint32_t> finallyPos = Nothing()) {
- // If we are using controlInfo_ (i.e., emitting a syntactic try
- // blocks), we must have specified up front if there will be a finally
- // close. For internal try blocks, like those emitted for yield* and
- // IteratorClose inside for-of loops, we can emitFinally even without
- // specifying up front, since the internal try blocks emit no GOSUBs.
- if (!controlInfo_) {
- if (kind_ == TryCatch)
- kind_ = TryCatchFinally;
- } else {
- MOZ_ASSERT(hasFinally());
- }
-
- if (state_ == Try) {
- if (!emitTryEnd())
- return false;
- } else {
- MOZ_ASSERT(state_ == Catch);
- if (!emitCatchEnd(false))
- return false;
- }
-
- MOZ_ASSERT(bce_->stackDepth == depth_);
-
- if (!bce_->emitJumpTarget(&finallyStart_))
- return false;
-
- if (controlInfo_) {
- // Fix up the gosubs that might have been emitted before non-local
- // jumps to the finally code.
- bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_);
-
- // Indicate that we're emitting a subroutine body.
- controlInfo_->setEmittingSubroutine();
- }
- if (finallyPos) {
- if (!bce_->updateSourceCoordNotes(finallyPos.value()))
- return false;
- }
- if (!bce_->emit1(JSOP_FINALLY))
- return false;
-
- if (retValKind_ == UseRetVal) {
- if (!bce_->emit1(JSOP_GETRVAL))
- return false;
-
- // Clear the frame's return value to make break/continue return
- // correct value even if there's no other statement before them:
- //
- // eval("x: try { 1 } finally { break x; }"); // undefined, not 1
- if (!bce_->emit1(JSOP_UNDEFINED))
- return false;
- if (!bce_->emit1(JSOP_SETRVAL))
- return false;
- }
-
- state_ = Finally;
- return true;
- }
-
- private:
- bool emitFinallyEnd() {
- MOZ_ASSERT(state_ == Finally);
-
- if (retValKind_ == UseRetVal) {
- if (!bce_->emit1(JSOP_SETRVAL))
- return false;
- }
-
- if (!bce_->emit1(JSOP_RETSUB))
- return false;
-
- bce_->hasTryFinally = true;
- return true;
- }
-
- public:
- bool emitEnd() {
- if (state_ == Catch) {
- MOZ_ASSERT(!hasFinally());
- if (!emitCatchEnd(false))
- return false;
- } else {
- MOZ_ASSERT(state_ == Finally);
- MOZ_ASSERT(hasFinally());
- if (!emitFinallyEnd())
- return false;
- }
-
- MOZ_ASSERT(bce_->stackDepth == depth_);
-
- // ReconstructPCStack needs a NOP here to mark the end of the last
- // catch block.
- if (!bce_->emit1(JSOP_NOP))
- return false;
-
- // Fix up the end-of-try/catch jumps to come here.
- if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_))
- return false;
-
- // Add the try note last, to let post-order give us the right ordering
- // (first to last for a given nesting level, inner to outer by level).
- if (hasCatch()) {
- if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset))
- return false;
- }
-
- // If we've got a finally, mark try+catch region with additional
- // trynote to catch exceptions (re)thrown from a catch block or
- // for the try{}finally{} case.
- if (hasFinally()) {
- if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset))
- return false;
- }
-
- state_ = End;
- return true;
- }
-};
-
// Class for emitting bytecode for optional expressions.
class MOZ_RAII OptionalEmitter
{
@@ -488,209 +162,6 @@ class MOZ_RAII OptionalEmitter
MOZ_MUST_USE bool emitOptionalJumpTarget(JSOp op, Kind kind = Kind::Other);
};
-class ForOfLoopControl : public LoopControl
-{
- // The stack depth of the iterator.
- int32_t iterDepth_;
-
- // for-of loops, when throwing from non-iterator code (i.e. from the body
- // or from evaluating the LHS of the loop condition), need to call
- // IteratorClose. This is done by enclosing non-iterator code with
- // try-catch and call IteratorClose in `catch` block.
- // If IteratorClose itself throws, we must not re-call IteratorClose. Since
- // non-local jumps like break and return call IteratorClose, whenever a
- // non-local jump is emitted, we must tell catch block not to perform
- // IteratorClose.
- //
- // for (x of y) {
- // // Operations for iterator (IteratorNext etc) are outside of
- // // try-block.
- // try {
- // ...
- // if (...) {
- // // Before non-local jump, clear iterator on the stack to tell
- // // catch block not to perform IteratorClose.
- // tmpIterator = iterator;
- // iterator = undefined;
- // IteratorClose(tmpIterator, { break });
- // break;
- // }
- // ...
- // } catch (e) {
- // // Just throw again when iterator is cleared by non-local jump.
- // if (iterator === undefined)
- // throw e;
- // IteratorClose(iterator, { throw, e });
- // }
- // }
- Maybe<TryEmitter> tryCatch_;
-
- // Used to track if any yields were emitted between calls to to
- // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose.
- uint32_t numYieldsAtBeginCodeNeedingIterClose_;
-
- bool allowSelfHosted_;
-
- IteratorKind iterKind_;
-
- public:
- ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted,
- IteratorKind iterKind)
- : LoopControl(bce, StatementKind::ForOfLoop),
- iterDepth_(iterDepth),
- numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX),
- allowSelfHosted_(allowSelfHosted),
- iterKind_(iterKind)
- {
- }
-
- bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
- tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal,
- TryEmitter::DontUseControl);
-
- if (!tryCatch_->emitTry())
- return false;
-
- MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX);
- numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldAndAwaitOffsetList.numYields;
-
- return true;
- }
-
- bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
- if (!tryCatch_->emitCatch()) // ITER ...
- return false;
-
- if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION
- return false;
- unsigned slotFromTop = bce->stackDepth - iterDepth_;
- if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
- return false;
-
- // If ITER is undefined, it means the exception is thrown by
- // IteratorClose for non-local jump, and we should't perform
- // IteratorClose again here.
- if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF
- return false;
- if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE
- return false;
-
- InternalIfEmitter ifIteratorIsNotClosed(bce);
- if (!ifIteratorIsNotClosed.emitThen()) // ITER ... EXCEPTION
- return false;
-
- MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_));
- if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
- return false;
- if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Throw))
- return false; // ITER ... EXCEPTION
-
- if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION
- return false;
-
- if (!bce->emit1(JSOP_THROW)) // ITER ...
- return false;
-
- // If any yields were emitted, then this for-of loop is inside a star
- // generator and must handle the case of Generator.return. Like in
- // yield*, it is handled with a finally block.
- uint32_t numYieldsEmitted = bce->yieldAndAwaitOffsetList.numYields;
- if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) {
- if (!tryCatch_->emitFinally())
- return false;
-
- InternalIfEmitter ifGeneratorClosing(bce);
- if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING
- return false;
- if (!ifGeneratorClosing.emitThen()) // ITER ... FTYPE FVALUE
- return false;
- if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER
- return false;
- if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Normal))
- return false; // ITER ... FTYPE FVALUE
- if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE
- return false;
- }
-
- if (!tryCatch_->emitEnd())
- return false;
-
- tryCatch_.reset();
- numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX;
-
- return true;
- }
-
- bool emitIteratorCloseInInnermostScope(BytecodeEmitter* bce,
- CompletionKind completionKind = CompletionKind::Normal) {
- return emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), completionKind);
- }
-
- bool emitIteratorCloseInScope(BytecodeEmitter* bce,
- EmitterScope& currentScope,
- CompletionKind completionKind = CompletionKind::Normal) {
- ptrdiff_t start = bce->offset();
- if (!bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind,
- allowSelfHosted_))
- {
- return false;
- }
- ptrdiff_t end = bce->offset();
- return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end);
- }
-
- bool emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce,
- EmitterScope& currentScope,
- bool isTarget) {
- // Pop unnecessary values from the stack. Effectively this means
- // leaving try-catch block. However, the performing IteratorClose can
- // reach the depth for try-catch, and effectively re-enter the
- // try-catch block.
- if (!bce->emit1(JSOP_POP)) // ITER RESULT
- return false;
- if (!bce->emit1(JSOP_POP)) // ITER
- return false;
-
- // Clear ITER slot on the stack to tell catch block to avoid performing
- // IteratorClose again.
- if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF
- return false;
- if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER
- return false;
-
- if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) // UNDEF
- return false;
-
- if (isTarget) {
- // At the level of the target block, there's bytecode after the
- // loop that will pop the iterator and the value, so push
- // undefineds to balance the stack.
- if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF
- return false;
- if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF
- return false;
- } else {
- if (!bce->emit1(JSOP_POP)) //
- return false;
- }
-
- return true;
- }
-};
-
-namespace js {
-namespace frontend {
-
-template <>
-bool
-NestableControl::is<ForOfLoopControl>() const
-{
- return kind_ == StatementKind::ForOfLoop;
-}
-
-} // namespace frontend
-} // namespace js
-
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
Parser<FullParseHandler>* parser, SharedContext* sc,
HandleScript script, Handle<LazyScript*> lazyScript,
diff --git a/js/src/frontend/ForOfLoopControl.cpp b/js/src/frontend/ForOfLoopControl.cpp
new file mode 100644
index 0000000000..9556a03bf2
--- /dev/null
+++ b/js/src/frontend/ForOfLoopControl.cpp
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#include "frontend/ForOfLoopControl.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/EmitterScope.h"
+#include "frontend/IfEmitter.h"
+
+using namespace js;
+using namespace js::frontend;
+
+ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted,
+ IteratorKind iterKind)
+ : LoopControl(bce, StatementKind::ForOfLoop),
+ iterDepth_(iterDepth),
+ numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX),
+ allowSelfHosted_(allowSelfHosted),
+ iterKind_(iterKind)
+{
+}
+
+bool
+ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce)
+{
+ tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal,
+ TryEmitter::DontUseControl);
+
+ if (!tryCatch_->emitTry())
+ return false;
+
+ MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX);
+ numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldAndAwaitOffsetList.numYields;
+
+ return true;
+}
+
+bool
+ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce)
+{
+ if (!tryCatch_->emitCatch()) // ITER ...
+ return false;
+
+ if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION
+ return false;
+ unsigned slotFromTop = bce->stackDepth - iterDepth_;
+ if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
+ return false;
+
+ // If ITER is undefined, it means the exception is thrown by
+ // IteratorClose for non-local jump, and we should't perform
+ // IteratorClose again here.
+ if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF
+ return false;
+ if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE
+ return false;
+
+ InternalIfEmitter ifIteratorIsNotClosed(bce);
+ if (!ifIteratorIsNotClosed.emitThen()) // ITER ... EXCEPTION
+ return false;
+
+ MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_));
+ if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
+ return false;
+ if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Throw))
+ return false; // ITER ... EXCEPTION
+
+ if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION
+ return false;
+
+ if (!bce->emit1(JSOP_THROW)) // ITER ...
+ return false;
+
+ // If any yields were emitted, then this for-of loop is inside a star
+ // generator and must handle the case of Generator.return. Like in
+ // yield*, it is handled with a finally block.
+ uint32_t numYieldsEmitted = bce->yieldAndAwaitOffsetList.numYields;
+ if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) {
+ if (!tryCatch_->emitFinally())
+ return false;
+
+ InternalIfEmitter ifGeneratorClosing(bce);
+ if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING
+ return false;
+ if (!ifGeneratorClosing.emitThen()) // ITER ... FTYPE FVALUE
+ return false;
+ if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER
+ return false;
+ if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Normal))
+ return false; // ITER ... FTYPE FVALUE
+ if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE
+ return false;
+ }
+
+ if (!tryCatch_->emitEnd())
+ return false;
+
+ tryCatch_.reset();
+ numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX;
+
+ return true;
+}
+
+bool
+ForOfLoopControl::emitIteratorCloseInInnermostScope(BytecodeEmitter* bce,
+ CompletionKind completionKind /* = CompletionKind::Normal */)
+{
+ return emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), completionKind);
+}
+
+bool
+ForOfLoopControl::emitIteratorCloseInScope(BytecodeEmitter* bce,
+ EmitterScope& currentScope,
+ CompletionKind completionKind /* = CompletionKind::Normal */)
+{
+ ptrdiff_t start = bce->offset();
+ if (!bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind,
+ allowSelfHosted_))
+ {
+ return false;
+ }
+ ptrdiff_t end = bce->offset();
+ return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end);
+}
+
+bool
+ForOfLoopControl::emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce,
+ EmitterScope& currentScope,
+ bool isTarget)
+{
+ // Pop unnecessary values from the stack. Effectively this means
+ // leaving try-catch block. However, the performing IteratorClose can
+ // reach the depth for try-catch, and effectively re-enter the
+ // try-catch block.
+ if (!bce->emit1(JSOP_POP)) // ITER RESULT
+ return false;
+ if (!bce->emit1(JSOP_POP)) // ITER
+ return false;
+
+ // Clear ITER slot on the stack to tell catch block to avoid performing
+ // IteratorClose again.
+ if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF
+ return false;
+ if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER
+ return false;
+
+ if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) // UNDEF
+ return false;
+
+ if (isTarget) {
+ // At the level of the target block, there's bytecode after the
+ // loop that will pop the iterator and the value, so push
+ // undefineds to balance the stack.
+ if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF
+ return false;
+ if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF
+ return false;
+ } else {
+ if (!bce->emit1(JSOP_POP)) //
+ return false;
+ }
+
+ return true;
+}
diff --git a/js/src/frontend/ForOfLoopControl.h b/js/src/frontend/ForOfLoopControl.h
new file mode 100644
index 0000000000..4ac9d4b2f8
--- /dev/null
+++ b/js/src/frontend/ForOfLoopControl.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifndef frontend_ForOfLoopControl_h
+#define frontend_ForOfLoopControl_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "jsapi.h"
+
+#include "frontend/BytecodeControlStructures.h"
+#include "frontend/TryEmitter.h"
+#include "jsiter.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class EmitterScope;
+
+class ForOfLoopControl : public LoopControl
+{
+ // The stack depth of the iterator.
+ int32_t iterDepth_;
+
+ // for-of loops, when throwing from non-iterator code (i.e. from the body
+ // or from evaluating the LHS of the loop condition), need to call
+ // IteratorClose. This is done by enclosing non-iterator code with
+ // try-catch and call IteratorClose in `catch` block.
+ // If IteratorClose itself throws, we must not re-call IteratorClose. Since
+ // non-local jumps like break and return call IteratorClose, whenever a
+ // non-local jump is emitted, we must tell catch block not to perform
+ // IteratorClose.
+ //
+ // for (x of y) {
+ // // Operations for iterator (IteratorNext etc) are outside of
+ // // try-block.
+ // try {
+ // ...
+ // if (...) {
+ // // Before non-local jump, clear iterator on the stack to tell
+ // // catch block not to perform IteratorClose.
+ // tmpIterator = iterator;
+ // iterator = undefined;
+ // IteratorClose(tmpIterator, { break });
+ // break;
+ // }
+ // ...
+ // } catch (e) {
+ // // Just throw again when iterator is cleared by non-local jump.
+ // if (iterator === undefined)
+ // throw e;
+ // IteratorClose(iterator, { throw, e });
+ // }
+ // }
+ mozilla::Maybe<TryEmitter> tryCatch_;
+
+ // Used to track if any yields were emitted between calls to to
+ // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose.
+ uint32_t numYieldsAtBeginCodeNeedingIterClose_;
+
+ bool allowSelfHosted_;
+
+ IteratorKind iterKind_;
+
+ public:
+ ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted,
+ IteratorKind iterKind);
+
+ MOZ_MUST_USE bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce);
+ MOZ_MUST_USE bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce);
+
+ MOZ_MUST_USE bool emitIteratorCloseInInnermostScope(BytecodeEmitter* bce,
+ CompletionKind completionKind = CompletionKind::Normal);
+ MOZ_MUST_USE bool emitIteratorCloseInScope(BytecodeEmitter* bce,
+ EmitterScope& currentScope,
+ CompletionKind completionKind = CompletionKind::Normal);
+
+ MOZ_MUST_USE bool emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce,
+ EmitterScope& currentScope,
+ bool isTarget);
+};
+template <>
+inline bool
+NestableControl::is<ForOfLoopControl>() const
+{
+ return kind_ == StatementKind::ForOfLoop;
+}
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ForOfLoopControl_h */
diff --git a/js/src/frontend/TryEmitter.cpp b/js/src/frontend/TryEmitter.cpp
new file mode 100644
index 0000000000..2d6e774456
--- /dev/null
+++ b/js/src/frontend/TryEmitter.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#include "frontend/TryEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SourceNotes.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind,
+ ShouldUseRetVal retValKind,
+ ShouldUseControl controlKind)
+ : bce_(bce),
+ kind_(kind),
+ retValKind_(retValKind),
+ depth_(0),
+ noteIndex_(0),
+ tryStart_(0),
+ state_(Start)
+{
+ if (controlKind == UseControl)
+ controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
+ finallyStart_.offset = 0;
+}
+
+// Emits JSOP_GOTO to the end of try-catch-finally.
+// Used in `yield*`.
+bool
+TryEmitter::emitJumpOverCatchAndFinally()
+{
+ if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
+ return false;
+ return true;
+}
+
+bool
+TryEmitter::emitTry()
+{
+ MOZ_ASSERT(state_ == Start);
+
+ // Since an exception can be thrown at any place inside the try block,
+ // we need to restore the stack and the scope chain before we transfer
+ // the control to the exception handler.
+ //
+ // For that we store in a try note associated with the catch or
+ // finally block the stack depth upon the try entry. The interpreter
+ // uses this depth to properly unwind the stack and the scope chain.
+ depth_ = bce_->stackDepth;
+
+ // Record the try location, then emit the try block.
+ if (!bce_->newSrcNote(SRC_TRY, &noteIndex_))
+ return false;
+ if (!bce_->emit1(JSOP_TRY))
+ return false;
+ tryStart_ = bce_->offset();
+
+ state_ = Try;
+ return true;
+}
+
+bool
+TryEmitter::emitTryEnd()
+{
+ MOZ_ASSERT(state_ == Try);
+ MOZ_ASSERT(depth_ == bce_->stackDepth);
+
+ // GOSUB to finally, if present.
+ if (hasFinally() && controlInfo_) {
+ if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
+ return false;
+ }
+
+ // Source note points to the jump at the end of the try block.
+ if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH))
+ return false;
+
+ // Emit jump over catch and/or finally.
+ if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
+ return false;
+
+ if (!bce_->emitJumpTarget(&tryEnd_))
+ return false;
+
+ return true;
+}
+
+bool
+TryEmitter::emitCatch()
+{
+ if (state_ == Try) {
+ if (!emitTryEnd())
+ return false;
+ } else {
+ MOZ_ASSERT(state_ == Catch);
+ if (!emitCatchEnd(true))
+ return false;
+ }
+
+ MOZ_ASSERT(bce_->stackDepth == depth_);
+
+ if (retValKind_ == UseRetVal) {
+ // Clear the frame's return value that might have been set by the
+ // try block:
+ //
+ // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
+ if (!bce_->emit1(JSOP_UNDEFINED))
+ return false;
+ if (!bce_->emit1(JSOP_SETRVAL))
+ return false;
+ }
+
+ state_ = Catch;
+ return true;
+}
+
+
+bool
+TryEmitter::emitCatchEnd(bool hasNext)
+{
+ MOZ_ASSERT(state_ == Catch);
+
+ if (!controlInfo_)
+ return true;
+
+ // gosub <finally>, if required.
+ if (hasFinally()) {
+ if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
+ return false;
+ MOZ_ASSERT(bce_->stackDepth == depth_);
+ }
+
+ // Jump over the remaining catch blocks. This will get fixed
+ // up to jump to after catch/finally.
+ if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
+ return false;
+
+ // If this catch block had a guard clause, patch the guard jump to
+ // come here.
+ if (controlInfo_->guardJump.offset != -1) {
+ if (!bce_->emitJumpTargetAndPatch(controlInfo_->guardJump))
+ return false;
+ controlInfo_->guardJump.offset = -1;
+
+ // If this catch block is the last one, rethrow, delegating
+ // execution of any finally block to the exception handler.
+ if (!hasNext) {
+ if (!bce_->emit1(JSOP_EXCEPTION))
+ return false;
+ if (!bce_->emit1(JSOP_THROW))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+TryEmitter::emitFinally(const Maybe<uint32_t>& finallyPos /* = Nothing() */)
+{
+ // If we are using controlInfo_ (i.e., emitting a syntactic try
+ // blocks), we must have specified up front if there will be a finally
+ // close. For internal try blocks, like those emitted for yield* and
+ // IteratorClose inside for-of loops, we can emitFinally even without
+ // specifying up front, since the internal try blocks emit no GOSUBs.
+ if (!controlInfo_) {
+ if (kind_ == TryCatch)
+ kind_ = TryCatchFinally;
+ } else {
+ MOZ_ASSERT(hasFinally());
+ }
+
+ if (state_ == Try) {
+ if (!emitTryEnd())
+ return false;
+ } else {
+ MOZ_ASSERT(state_ == Catch);
+ if (!emitCatchEnd(false))
+ return false;
+ }
+
+ MOZ_ASSERT(bce_->stackDepth == depth_);
+
+ if (!bce_->emitJumpTarget(&finallyStart_))
+ return false;
+
+ if (controlInfo_) {
+ // Fix up the gosubs that might have been emitted before non-local
+ // jumps to the finally code.
+ bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_);
+
+ // Indicate that we're emitting a subroutine body.
+ controlInfo_->setEmittingSubroutine();
+ }
+ if (finallyPos) {
+ if (!bce_->updateSourceCoordNotes(finallyPos.value()))
+ return false;
+ }
+ if (!bce_->emit1(JSOP_FINALLY))
+ return false;
+
+ if (retValKind_ == UseRetVal) {
+ if (!bce_->emit1(JSOP_GETRVAL))
+ return false;
+
+ // Clear the frame's return value to make break/continue return
+ // correct value even if there's no other statement before them:
+ //
+ // eval("x: try { 1 } finally { break x; }"); // undefined, not 1
+ if (!bce_->emit1(JSOP_UNDEFINED))
+ return false;
+ if (!bce_->emit1(JSOP_SETRVAL))
+ return false;
+ }
+
+ state_ = Finally;
+ return true;
+}
+
+bool
+TryEmitter::emitFinallyEnd()
+{
+ MOZ_ASSERT(state_ == Finally);
+
+ if (retValKind_ == UseRetVal) {
+ if (!bce_->emit1(JSOP_SETRVAL))
+ return false;
+ }
+
+ if (!bce_->emit1(JSOP_RETSUB))
+ return false;
+
+ bce_->hasTryFinally = true;
+ return true;
+}
+
+bool
+TryEmitter::emitEnd()
+{
+ if (state_ == Catch) {
+ MOZ_ASSERT(!hasFinally());
+ if (!emitCatchEnd(false))
+ return false;
+ } else {
+ MOZ_ASSERT(state_ == Finally);
+ MOZ_ASSERT(hasFinally());
+ if (!emitFinallyEnd())
+ return false;
+ }
+
+ MOZ_ASSERT(bce_->stackDepth == depth_);
+
+ // ReconstructPCStack needs a NOP here to mark the end of the last
+ // catch block.
+ if (!bce_->emit1(JSOP_NOP))
+ return false;
+
+ // Fix up the end-of-try/catch jumps to come here.
+ if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_))
+ return false;
+
+ // Add the try note last, to let post-order give us the right ordering
+ // (first to last for a given nesting level, inner to outer by level).
+ if (hasCatch()) {
+ if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset))
+ return false;
+ }
+
+ // If we've got a finally, mark try+catch region with additional
+ // trynote to catch exceptions (re)thrown from a catch block or
+ // for the try{}finally{} case.
+ if (hasFinally()) {
+ if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset))
+ return false;
+ }
+
+ state_ = End;
+ return true;
+}
diff --git a/js/src/frontend/TryEmitter.h b/js/src/frontend/TryEmitter.h
new file mode 100644
index 0000000000..ff42cda0d1
--- /dev/null
+++ b/js/src/frontend/TryEmitter.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifndef frontend_TryEmitter_h
+#define frontend_TryEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "frontend/BytecodeControlStructures.h"
+#include "frontend/JumpList.h"
+#include "frontend/TDZCheckCache.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+class MOZ_STACK_CLASS TryEmitter
+{
+ public:
+ enum Kind {
+ TryCatch,
+ TryCatchFinally,
+ TryFinally
+ };
+ enum ShouldUseRetVal {
+ UseRetVal,
+ DontUseRetVal
+ };
+ enum ShouldUseControl {
+ UseControl,
+ DontUseControl,
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+ Kind kind_;
+ ShouldUseRetVal retValKind_;
+
+ // Track jumps-over-catches and gosubs-to-finally for later fixup.
+ //
+ // When a finally block is active, non-local jumps (including
+ // jumps-over-catches) result in a GOSUB being written into the bytecode
+ // stream and fixed-up later.
+ //
+ // If ShouldUseControl is DontUseControl, all that handling is skipped.
+ // DontUseControl is used by yield* and the internal try-catch around
+ // IteratorClose. These internal uses must:
+ // * have only one catch block
+ // * have no catch guard
+ // * have JSOP_GOTO at the end of catch-block
+ // * have no non-local-jump
+ // * don't use finally block for normal completion of try-block and
+ // catch-block
+ //
+ // Additionally, a finally block may be emitted when ShouldUseControl is
+ // DontUseControl, even if the kind is not TryCatchFinally or TryFinally,
+ // because GOSUBs are not emitted. This internal use shares the
+ // requirements as above.
+ Maybe<TryFinallyControl> controlInfo_;
+
+ int depth_;
+ unsigned noteIndex_;
+ ptrdiff_t tryStart_;
+ JumpList catchAndFinallyJump_;
+ JumpTarget tryEnd_;
+ JumpTarget finallyStart_;
+
+ enum State {
+ Start,
+ Try,
+ TryEnd,
+ Catch,
+ CatchEnd,
+ Finally,
+ FinallyEnd,
+ End
+ };
+ State state_;
+
+ bool hasCatch() const {
+ return kind_ == TryCatch || kind_ == TryCatchFinally;
+ }
+ bool hasFinally() const {
+ return kind_ == TryCatchFinally || kind_ == TryFinally;
+ }
+
+ public:
+ TryEmitter(BytecodeEmitter* bce, Kind kind,
+ ShouldUseRetVal retValKind = UseRetVal, ShouldUseControl controlKind = UseControl);
+
+ MOZ_MUST_USE bool emitJumpOverCatchAndFinally();
+
+ MOZ_MUST_USE bool emitTry();
+ MOZ_MUST_USE bool emitCatch();
+
+ MOZ_MUST_USE bool emitFinally(const mozilla::Maybe<uint32_t>& finallyPos = mozilla::Nothing());
+
+ MOZ_MUST_USE bool emitEnd();
+
+ private:
+ MOZ_MUST_USE bool emitTryEnd();
+ MOZ_MUST_USE bool emitCatchEnd(bool hasNext);
+ MOZ_MUST_USE bool emitFinallyEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_TryEmitter_h */
diff --git a/js/src/moz.build b/js/src/moz.build
index 709d94b9a1..eae53d04b7 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -145,6 +145,7 @@ UNIFIED_SOURCES += [
'frontend/ElemOpEmitter.cpp',
'frontend/EmitterScope.cpp',
'frontend/FoldConstants.cpp',
+ 'frontend/ForOfLoopControl.cpp',
'frontend/IfEmitter.cpp',
'frontend/JumpList.cpp',
'frontend/NameFunctions.cpp',
@@ -153,6 +154,7 @@ UNIFIED_SOURCES += [
'frontend/PropOpEmitter.cpp',
'frontend/TDZCheckCache.cpp',
'frontend/TokenStream.cpp',
+ 'frontend/TryEmitter.cpp',
'gc/Allocator.cpp',
'gc/Barrier.cpp',
'gc/GCTrace.cpp',