diff options
author | Martok <martok@martoks-place.de> | 2023-03-24 01:16:57 +0100 |
---|---|---|
committer | Martok <martok@martoks-place.de> | 2023-03-26 20:20:16 +0200 |
commit | c26df1a48a4f27d6df75ff4904f94bc403b97e0d (patch) | |
tree | fedcbfd60a97415d4458a5cd66d99a03f6abb0a8 | |
parent | 0a2eb24435f3114aa70c253710dd6a4ebe863ff5 (diff) | |
download | uxp-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.cpp | 533 | ||||
-rw-r--r-- | js/src/frontend/ForOfLoopControl.cpp | 167 | ||||
-rw-r--r-- | js/src/frontend/ForOfLoopControl.h | 99 | ||||
-rw-r--r-- | js/src/frontend/TryEmitter.cpp | 286 | ||||
-rw-r--r-- | js/src/frontend/TryEmitter.h | 117 | ||||
-rw-r--r-- | js/src/moz.build | 2 |
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, ¬eIndex_)) - 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, ¬eIndex_)) + 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', |