mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 23:38:38 +09:00
Issue #2155 - Move TryEmitter and ForOfLoopControl to TryEmitter.{cpp.h} and ForOfLoopControl.{cpp.h}
Based-on: m-c 1460489/5
This commit is contained in:
parent
a8ac1cac3d
commit
70e1654c3c
6 changed files with 673 additions and 531 deletions
|
|
@ -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,
|
||||
|
|
|
|||
167
js/src/frontend/ForOfLoopControl.cpp
Normal file
167
js/src/frontend/ForOfLoopControl.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
99
js/src/frontend/ForOfLoopControl.h
Normal file
99
js/src/frontend/ForOfLoopControl.h
Normal file
|
|
@ -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 */
|
||||
286
js/src/frontend/TryEmitter.cpp
Normal file
286
js/src/frontend/TryEmitter.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
117
js/src/frontend/TryEmitter.h
Normal file
117
js/src/frontend/TryEmitter.h
Normal file
|
|
@ -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 */
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue