mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-06 15:58:39 +09:00
Issue #2155 - Split TDZCheckCache, IfEmitter/InternalIfEmitter, JumpList from BytecodeEmitter
This commit is contained in:
parent
05d29a53db
commit
e8fee20566
9 changed files with 740 additions and 333 deletions
|
|
@ -27,7 +27,9 @@
|
|||
#include "jstypes.h"
|
||||
#include "jsutil.h"
|
||||
|
||||
#include "frontend/IfEmitter.h"
|
||||
#include "frontend/Parser.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "frontend/TokenStream.h"
|
||||
#include "vm/Debugger.h"
|
||||
#include "vm/GeneratorObject.h"
|
||||
|
|
@ -67,28 +69,6 @@ ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn)
|
|||
return pn->getKind() == PNK_WHILE || pn->getKind() == PNK_FOR;
|
||||
}
|
||||
|
||||
// A cache that tracks superfluous TDZ checks.
|
||||
//
|
||||
// Each basic block should have a TDZCheckCache in scope. Some NestableControl
|
||||
// subclasses contain a TDZCheckCache.
|
||||
class BytecodeEmitter::TDZCheckCache : public Nestable<BytecodeEmitter::TDZCheckCache>
|
||||
{
|
||||
PooledMapPtr<CheckTDZMap> cache_;
|
||||
|
||||
MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
|
||||
return cache_ || cache_.acquire(bce->cx);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit TDZCheckCache(BytecodeEmitter* bce)
|
||||
: Nestable<TDZCheckCache>(&bce->innermostTDZCheckCache),
|
||||
cache_(bce->cx->frontendCollectionPool())
|
||||
{ }
|
||||
|
||||
Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce, JSAtom* name);
|
||||
MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check);
|
||||
};
|
||||
|
||||
class BytecodeEmitter::NestableControl : public Nestable<BytecodeEmitter::NestableControl>
|
||||
{
|
||||
StatementKind kind_;
|
||||
|
|
@ -212,7 +192,7 @@ class LoopControl : public BreakableControl
|
|||
{
|
||||
// Loops' children are emitted in dominance order, so they can always
|
||||
// have a TDZCheckCache.
|
||||
BytecodeEmitter::TDZCheckCache tdzCache_;
|
||||
TDZCheckCache tdzCache_;
|
||||
|
||||
// Stack depth when this loop was pushed on the control stack.
|
||||
int32_t stackDepth_;
|
||||
|
|
@ -1461,53 +1441,6 @@ BytecodeEmitter::EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal)
|
|||
return true;
|
||||
}
|
||||
|
||||
Maybe<MaybeCheckTDZ>
|
||||
BytecodeEmitter::TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name)
|
||||
{
|
||||
if (!ensureCache(bce))
|
||||
return Nothing();
|
||||
|
||||
CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
|
||||
if (p)
|
||||
return Some(p->value().wrapped);
|
||||
|
||||
MaybeCheckTDZ rv = CheckTDZ;
|
||||
for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) {
|
||||
if (it->cache_) {
|
||||
if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) {
|
||||
rv = p2->value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache_->add(p, name, rv)) {
|
||||
ReportOutOfMemory(bce->cx);
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
return Some(rv);
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name,
|
||||
MaybeCheckTDZ check)
|
||||
{
|
||||
if (!ensureCache(bce))
|
||||
return false;
|
||||
|
||||
CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
|
||||
if (p) {
|
||||
MOZ_ASSERT(!check, "TDZ only needs to be checked once per binding per basic block.");
|
||||
p->value() = check;
|
||||
} else {
|
||||
if (!cache_->add(p, name, check))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class MOZ_STACK_CLASS TryEmitter
|
||||
{
|
||||
public:
|
||||
|
|
@ -1836,156 +1769,6 @@ class MOZ_STACK_CLASS TryEmitter
|
|||
}
|
||||
};
|
||||
|
||||
class MOZ_STACK_CLASS IfThenElseEmitter
|
||||
{
|
||||
BytecodeEmitter* bce_;
|
||||
JumpList jumpAroundThen_;
|
||||
JumpList jumpsAroundElse_;
|
||||
unsigned noteIndex_;
|
||||
int32_t thenDepth_;
|
||||
#ifdef DEBUG
|
||||
int32_t pushed_;
|
||||
bool calculatedPushed_;
|
||||
#endif
|
||||
enum State {
|
||||
Start,
|
||||
If,
|
||||
Cond,
|
||||
IfElse,
|
||||
Else,
|
||||
End
|
||||
};
|
||||
State state_;
|
||||
|
||||
public:
|
||||
explicit IfThenElseEmitter(BytecodeEmitter* bce)
|
||||
: bce_(bce),
|
||||
noteIndex_(-1),
|
||||
thenDepth_(0),
|
||||
#ifdef DEBUG
|
||||
pushed_(0),
|
||||
calculatedPushed_(false),
|
||||
#endif
|
||||
state_(Start)
|
||||
{}
|
||||
|
||||
~IfThenElseEmitter()
|
||||
{}
|
||||
|
||||
private:
|
||||
bool emitIf(State nextState) {
|
||||
MOZ_ASSERT(state_ == Start || state_ == Else);
|
||||
MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond);
|
||||
|
||||
// Clear jumpAroundThen_ offset that points previous JSOP_IFEQ.
|
||||
if (state_ == Else)
|
||||
jumpAroundThen_ = JumpList();
|
||||
|
||||
// Emit an annotated branch-if-false around the then part.
|
||||
SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND;
|
||||
if (!bce_->newSrcNote(type, ¬eIndex_))
|
||||
return false;
|
||||
if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_))
|
||||
return false;
|
||||
|
||||
// To restore stack depth in else part, save depth of the then part.
|
||||
#ifdef DEBUG
|
||||
// If DEBUG, this is also necessary to calculate |pushed_|.
|
||||
thenDepth_ = bce_->stackDepth;
|
||||
#else
|
||||
if (nextState == IfElse || nextState == Cond)
|
||||
thenDepth_ = bce_->stackDepth;
|
||||
#endif
|
||||
state_ = nextState;
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
bool emitIf() {
|
||||
return emitIf(If);
|
||||
}
|
||||
|
||||
bool emitCond() {
|
||||
return emitIf(Cond);
|
||||
}
|
||||
|
||||
bool emitIfElse() {
|
||||
return emitIf(IfElse);
|
||||
}
|
||||
|
||||
bool emitElse() {
|
||||
MOZ_ASSERT(state_ == IfElse || state_ == Cond);
|
||||
|
||||
calculateOrCheckPushed();
|
||||
|
||||
// Emit a jump from the end of our then part around the else part. The
|
||||
// patchJumpsToTarget call at the bottom of this function will fix up
|
||||
// the offset with jumpsAroundElse value.
|
||||
if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_))
|
||||
return false;
|
||||
|
||||
// Ensure the branch-if-false comes here, then emit the else.
|
||||
if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_))
|
||||
return false;
|
||||
|
||||
// Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to
|
||||
// jump, for IonMonkey's benefit. We can't just "back up" from the pc
|
||||
// of the else clause, because we don't know whether an extended
|
||||
// jump was required to leap from the end of the then clause over
|
||||
// the else clause.
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 0,
|
||||
jumpsAroundElse_.offset - jumpAroundThen_.offset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore stack depth of the then part.
|
||||
bce_->stackDepth = thenDepth_;
|
||||
state_ = Else;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool emitEnd() {
|
||||
MOZ_ASSERT(state_ == If || state_ == Else);
|
||||
|
||||
calculateOrCheckPushed();
|
||||
|
||||
if (state_ == If) {
|
||||
// No else part, fixup the branch-if-false to come here.
|
||||
if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Patch all the jumps around else parts.
|
||||
if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_))
|
||||
return false;
|
||||
|
||||
state_ = End;
|
||||
return true;
|
||||
}
|
||||
|
||||
void calculateOrCheckPushed() {
|
||||
#ifdef DEBUG
|
||||
if (!calculatedPushed_) {
|
||||
pushed_ = bce_->stackDepth - thenDepth_;
|
||||
calculatedPushed_ = true;
|
||||
} else {
|
||||
MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
int32_t pushed() const {
|
||||
return pushed_;
|
||||
}
|
||||
|
||||
int32_t popped() const {
|
||||
return -pushed_;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// Class for emitting bytecode for optional expressions.
|
||||
class MOZ_RAII OptionalEmitter
|
||||
{
|
||||
|
|
@ -1995,7 +1778,7 @@ class MOZ_RAII OptionalEmitter
|
|||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
BytecodeEmitter::TDZCheckCache tdzCache_;
|
||||
TDZCheckCache tdzCache_;
|
||||
|
||||
// Jump target for short circuiting code, which has null or undefined values.
|
||||
JumpList jumpShortCircuit_;
|
||||
|
|
@ -2150,8 +1933,8 @@ class ForOfLoopControl : public LoopControl
|
|||
if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifIteratorIsNotClosed(bce);
|
||||
if (!ifIteratorIsNotClosed.emitIf()) // ITER ... EXCEPTION
|
||||
InternalIfEmitter ifIteratorIsNotClosed(bce);
|
||||
if (!ifIteratorIsNotClosed.emitThen()) // ITER ... EXCEPTION
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_));
|
||||
|
|
@ -2174,10 +1957,10 @@ class ForOfLoopControl : public LoopControl
|
|||
if (!tryCatch_->emitFinally())
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifGeneratorClosing(bce);
|
||||
InternalIfEmitter ifGeneratorClosing(bce);
|
||||
if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING
|
||||
return false;
|
||||
if (!ifGeneratorClosing.emitIf()) // ITER ... FTYPE FVALUE
|
||||
if (!ifGeneratorClosing.emitThen()) // ITER ... FTYPE FVALUE
|
||||
return false;
|
||||
if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER
|
||||
return false;
|
||||
|
|
@ -2498,27 +2281,6 @@ BytecodeEmitter::emitJumpTarget(JumpTarget* target)
|
|||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset)
|
||||
{
|
||||
SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset);
|
||||
offset = jumpOffset;
|
||||
}
|
||||
|
||||
void
|
||||
JumpList::patchAll(jsbytecode* code, JumpTarget target)
|
||||
{
|
||||
ptrdiff_t delta;
|
||||
for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) {
|
||||
jsbytecode* pc = &code[jumpOffset];
|
||||
MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL);
|
||||
delta = GET_JUMP_OFFSET(pc);
|
||||
MOZ_ASSERT(delta < 0);
|
||||
ptrdiff_t span = target.offset - jumpOffset;
|
||||
SET_JUMP_OFFSET(pc, span);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump)
|
||||
{
|
||||
|
|
@ -5467,14 +5229,14 @@ BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope,
|
|||
// Step 4.
|
||||
//
|
||||
// Do nothing if "return" is null or undefined.
|
||||
IfThenElseEmitter ifReturnMethodIsDefined(this);
|
||||
InternalIfEmitter ifReturnMethodIsDefined(this);
|
||||
if (!emit1(JSOP_DUP)) // ... ITER RET RET
|
||||
return false;
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... ITER RET RET UNDEFINED
|
||||
return false;
|
||||
if (!emit1(JSOP_NE)) // ... ITER RET ?NEQL
|
||||
return false;
|
||||
if (!ifReturnMethodIsDefined.emitIfElse())
|
||||
if (!ifReturnMethodIsDefined.emitThenElse())
|
||||
return false;
|
||||
|
||||
if (completionKind == CompletionKind::Throw) {
|
||||
|
|
@ -5847,12 +5609,12 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
}
|
||||
|
||||
if (member->isKind(PNK_SPREAD)) {
|
||||
IfThenElseEmitter ifThenElse(this);
|
||||
InternalIfEmitter ifThenElse(this);
|
||||
if (!isFirst) {
|
||||
// If spread is not the first element of the pattern,
|
||||
// iterator can already be completed.
|
||||
// ... OBJ ITER *LREF DONE
|
||||
if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF
|
||||
if (!ifThenElse.emitThenElse()) // ... OBJ ITER *LREF
|
||||
return false;
|
||||
|
||||
if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ARRAY
|
||||
|
|
@ -5902,10 +5664,10 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
|
||||
MOZ_ASSERT(!member->isKind(PNK_SPREAD));
|
||||
|
||||
IfThenElseEmitter ifAlreadyDone(this);
|
||||
InternalIfEmitter ifAlreadyDone(this);
|
||||
if (!isFirst) {
|
||||
// ... OBJ ITER *LREF DONE
|
||||
if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF
|
||||
if (!ifAlreadyDone.emitThenElse()) // ... OBJ ITER *LREF
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF
|
||||
|
|
@ -5942,8 +5704,8 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifDone(this);
|
||||
if (!ifDone.emitIfElse()) // ... OBJ ITER DONE *LREF RESULT
|
||||
InternalIfEmitter ifDone(this);
|
||||
if (!ifDone.emitThenElse()) // ... OBJ ITER DONE *LREF RESULT
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF
|
||||
|
|
@ -5994,8 +5756,8 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav
|
|||
// The last DONE value is on top of the stack. If not DONE, call
|
||||
// IteratorClose.
|
||||
// ... OBJ ITER DONE
|
||||
IfThenElseEmitter ifDone(this);
|
||||
if (!ifDone.emitIfElse()) // ... OBJ ITER
|
||||
InternalIfEmitter ifDone(this);
|
||||
if (!ifDone.emitThenElse()) // ... OBJ ITER
|
||||
return false;
|
||||
if (!emit1(JSOP_POP)) // ... OBJ
|
||||
return false;
|
||||
|
|
@ -6931,7 +6693,7 @@ BytecodeEmitter::emitTry(ParseNode* pn)
|
|||
bool
|
||||
BytecodeEmitter::emitIf(ParseNode* pn)
|
||||
{
|
||||
IfThenElseEmitter ifThenElse(this);
|
||||
IfEmitter ifThenElse(this);
|
||||
|
||||
if_again:
|
||||
/* Emit code for the condition before pushing stmtInfo. */
|
||||
|
|
@ -6940,10 +6702,10 @@ BytecodeEmitter::emitIf(ParseNode* pn)
|
|||
|
||||
ParseNode* elseNode = pn->pn_kid3;
|
||||
if (elseNode) {
|
||||
if (!ifThenElse.emitIfElse())
|
||||
if (!ifThenElse.emitThenElse())
|
||||
return false;
|
||||
} else {
|
||||
if (!ifThenElse.emitIf())
|
||||
if (!ifThenElse.emitThen())
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -6952,14 +6714,18 @@ BytecodeEmitter::emitIf(ParseNode* pn)
|
|||
return false;
|
||||
|
||||
if (elseNode) {
|
||||
if (!ifThenElse.emitElse())
|
||||
return false;
|
||||
|
||||
if (elseNode->isKind(PNK_IF)) {
|
||||
pn = elseNode;
|
||||
|
||||
if (!ifThenElse.emitElseIf())
|
||||
return false;
|
||||
|
||||
goto if_again;
|
||||
}
|
||||
|
||||
if (!ifThenElse.emitElse())
|
||||
return false;
|
||||
|
||||
/* Emit code for the else part. */
|
||||
if (!emitTreeInBranch(elseNode))
|
||||
return false;
|
||||
|
|
@ -7198,14 +6964,14 @@ BytecodeEmitter::emitAsyncIterator()
|
|||
if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifAsyncIterIsUndefined(this);
|
||||
InternalIfEmitter ifAsyncIterIsUndefined(this);
|
||||
if (!emit1(JSOP_DUP)) // OBJ ITERFN ITERFN
|
||||
return false;
|
||||
if (!emit1(JSOP_UNDEFINED)) // OBJ ITERFN ITERFN UNDEF
|
||||
return false;
|
||||
if (!emit1(JSOP_EQ)) // OBJ ITERFN EQ
|
||||
return false;
|
||||
if (!ifAsyncIterIsUndefined.emitIfElse()) // OBJ ITERFN
|
||||
if (!ifAsyncIterIsUndefined.emitThenElse()) // OBJ ITERFN
|
||||
return false;
|
||||
|
||||
if (!emit1(JSOP_POP)) // OBJ
|
||||
|
|
@ -8983,8 +8749,8 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter)
|
|||
if (!emit1(JSOP_EQ)) // ITER RESULT EXCEPTION ITER THROW ?EQL
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifThrowMethodIsNotDefined(this);
|
||||
if (!ifThrowMethodIsNotDefined.emitIf()) // ITER RESULT EXCEPTION ITER THROW
|
||||
InternalIfEmitter ifThrowMethodIsNotDefined(this);
|
||||
if (!ifThrowMethodIsNotDefined.emitThen()) // ITER RESULT EXCEPTION ITER THROW
|
||||
return false;
|
||||
savedDepthTemp = stackDepth;
|
||||
if (!emit1(JSOP_POP)) // ITER RESULT EXCEPTION ITER
|
||||
|
|
@ -9039,10 +8805,10 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter)
|
|||
// Call iterator.return() for receiving a "forced return" completion from
|
||||
// the generator.
|
||||
|
||||
IfThenElseEmitter ifGeneratorClosing(this);
|
||||
InternalIfEmitter ifGeneratorClosing(this);
|
||||
if (!emit1(JSOP_ISGENCLOSING)) // ITER RESULT FTYPE FVALUE CLOSING
|
||||
return false;
|
||||
if (!ifGeneratorClosing.emitIf()) // ITER RESULT FTYPE FVALUE
|
||||
if (!ifGeneratorClosing.emitThen()) // ITER RESULT FTYPE FVALUE
|
||||
return false;
|
||||
|
||||
// Step ii.
|
||||
|
|
@ -9058,7 +8824,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter)
|
|||
// Step iii.
|
||||
//
|
||||
// Do nothing if "return" is undefined.
|
||||
IfThenElseEmitter ifReturnMethodIsDefined(this);
|
||||
InternalIfEmitter ifReturnMethodIsDefined(this);
|
||||
if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER RET RET
|
||||
return false;
|
||||
if (!emit1(JSOP_UNDEFINED)) // ITER RESULT FTYPE FVALUE ITER RET RET UNDEFINED
|
||||
|
|
@ -9070,7 +8836,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter)
|
|||
//
|
||||
// Call "return" with the argument passed to Generator.prototype.return,
|
||||
// which is currently in rval.value.
|
||||
if (!ifReturnMethodIsDefined.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE ITER RET
|
||||
if (!ifReturnMethodIsDefined.emitThenElse()) // ITER OLDRESULT FTYPE FVALUE ITER RET
|
||||
return false;
|
||||
if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RET ITER
|
||||
return false;
|
||||
|
|
@ -9095,12 +8861,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter)
|
|||
//
|
||||
// Check if the returned object from iterator.return() is done. If not,
|
||||
// continuing yielding.
|
||||
IfThenElseEmitter ifReturnDone(this);
|
||||
InternalIfEmitter ifReturnDone(this);
|
||||
if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT
|
||||
return false;
|
||||
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RESULT DONE
|
||||
return false;
|
||||
if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT
|
||||
if (!ifReturnDone.emitThenElse()) // ITER OLDRESULT FTYPE FVALUE RESULT
|
||||
return false;
|
||||
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE
|
||||
return false;
|
||||
|
|
@ -10288,7 +10054,7 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional,
|
|||
if (!emitTree(&conditional.condition()))
|
||||
return false;
|
||||
|
||||
IfThenElseEmitter ifThenElse(this);
|
||||
IfEmitter ifThenElse(this);
|
||||
if (!ifThenElse.emitCond())
|
||||
return false;
|
||||
|
||||
|
|
@ -12159,7 +11925,7 @@ OptionalEmitter::emitJumpShortCircuit() {
|
|||
state_ == State::ShortCircuitForCall);
|
||||
MOZ_ASSERT(initialDepth_ + 1 == bce_->stackDepth);
|
||||
|
||||
IfThenElseEmitter ifEmitter(bce_);
|
||||
InternalIfEmitter ifEmitter(bce_);
|
||||
if (!bce_->emitPushNotUndefinedOrNull()) {
|
||||
// [stack] OBJ NOT-UNDEFINED-OR-NULL
|
||||
return false;
|
||||
|
|
@ -12170,7 +11936,7 @@ OptionalEmitter::emitJumpShortCircuit() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!ifEmitter.emitIf() /* emitThen() */) {
|
||||
if (!ifEmitter.emitThen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -12204,7 +11970,7 @@ OptionalEmitter::emitJumpShortCircuitForCall() {
|
|||
return false;
|
||||
}
|
||||
|
||||
IfThenElseEmitter ifEmitter(bce_);
|
||||
InternalIfEmitter ifEmitter(bce_);
|
||||
if (!bce_->emitPushNotUndefinedOrNull()) {
|
||||
// [stack] THIS CALLEE NOT-UNDEFINED-OR-NULL
|
||||
return false;
|
||||
|
|
@ -12215,7 +11981,7 @@ OptionalEmitter::emitJumpShortCircuitForCall() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!ifEmitter.emitIf() /* emitThen() */) {
|
||||
if (!ifEmitter.emitThen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "jsscript.h"
|
||||
|
||||
#include "ds/InlineTable.h"
|
||||
#include "frontend/JumpList.h"
|
||||
#include "frontend/Parser.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
|
|
@ -118,69 +119,15 @@ static size_t MaxSrcNotesLength = INT32_MAX;
|
|||
typedef Vector<jsbytecode, 256> BytecodeVector;
|
||||
typedef Vector<jssrcnote, 64> SrcNotesVector;
|
||||
|
||||
// Linked list of jump instructions that need to be patched. The linked list is
|
||||
// stored in the bytes of the incomplete bytecode that will be patched, so no
|
||||
// extra memory is needed, and patching the instructions destroys the list.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// JumpList brList;
|
||||
// if (!emitJump(JSOP_IFEQ, &brList))
|
||||
// return false;
|
||||
// ...
|
||||
// JumpTarget label;
|
||||
// if (!emitJumpTarget(&label))
|
||||
// return false;
|
||||
// ...
|
||||
// if (!emitJump(JSOP_GOTO, &brList))
|
||||
// return false;
|
||||
// ...
|
||||
// patchJumpsToTarget(brList, label);
|
||||
//
|
||||
// +-> -1
|
||||
// |
|
||||
// |
|
||||
// ifeq .. <+ + +-+ ifeq ..
|
||||
// .. | | ..
|
||||
// label: | +-> label:
|
||||
// jumptarget | | jumptarget
|
||||
// .. | | ..
|
||||
// goto .. <+ + +-+ goto .. <+
|
||||
// | |
|
||||
// | |
|
||||
// + +
|
||||
// brList brList
|
||||
//
|
||||
// | ^
|
||||
// +------- patchJumpsToTarget -------+
|
||||
//
|
||||
|
||||
// Offset of a jump target instruction, used for patching jump instructions.
|
||||
struct JumpTarget {
|
||||
ptrdiff_t offset;
|
||||
};
|
||||
|
||||
struct JumpList {
|
||||
// -1 is used to mark the end of jump lists.
|
||||
JumpList() : offset(-1) {}
|
||||
ptrdiff_t offset;
|
||||
|
||||
// Add a jump instruction to the list.
|
||||
void push(jsbytecode* code, ptrdiff_t jumpOffset);
|
||||
|
||||
// Patch all jump instructions in this list to jump to `target`. This
|
||||
// clobbers the list.
|
||||
void patchAll(jsbytecode* code, JumpTarget target);
|
||||
};
|
||||
|
||||
enum class ValueUsage {
|
||||
WantValue,
|
||||
IgnoreValue
|
||||
};
|
||||
|
||||
class TDZCheckCache;
|
||||
|
||||
struct MOZ_STACK_CLASS BytecodeEmitter
|
||||
{
|
||||
class TDZCheckCache;
|
||||
class NestableControl;
|
||||
class EmitterScope;
|
||||
|
||||
|
|
|
|||
231
js/src/frontend/IfEmitter.cpp
Normal file
231
js/src/frontend/IfEmitter.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
/* -*- 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/IfEmitter.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "vm/Opcodes.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
IfEmitter::IfEmitter(BytecodeEmitter* bce, Kind kind)
|
||||
: bce_(bce),
|
||||
noteIndex_(-1),
|
||||
thenDepth_(0),
|
||||
kind_(kind)
|
||||
#ifdef DEBUG
|
||||
, pushed_(0),
|
||||
calculatedPushed_(false),
|
||||
state_(State::Start)
|
||||
#endif
|
||||
{}
|
||||
|
||||
IfEmitter::IfEmitter(BytecodeEmitter* bce)
|
||||
: IfEmitter(bce, Kind::MayContainLexicalAccessInBranch)
|
||||
{}
|
||||
|
||||
bool
|
||||
IfEmitter::emitIfInternal(SrcNoteType type)
|
||||
{
|
||||
MOZ_ASSERT_IF(state_ == State::ElseIf, tdzCache_.isSome());
|
||||
MOZ_ASSERT_IF(state_ != State::ElseIf, tdzCache_.isNothing());
|
||||
|
||||
// The end of TDZCheckCache for cond for else-if.
|
||||
if (kind_ == Kind::MayContainLexicalAccessInBranch)
|
||||
tdzCache_.reset();
|
||||
|
||||
// Emit an annotated branch-if-false around the then part.
|
||||
if (!bce_->newSrcNote(type, ¬eIndex_))
|
||||
return false;
|
||||
if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_))
|
||||
return false;
|
||||
|
||||
// To restore stack depth in else part, save depth of the then part.
|
||||
#ifdef DEBUG
|
||||
// If DEBUG, this is also necessary to calculate |pushed_|.
|
||||
thenDepth_ = bce_->stackDepth;
|
||||
#else
|
||||
if (type == SRC_COND || type == SRC_IF_ELSE)
|
||||
thenDepth_ = bce_->stackDepth;
|
||||
#endif
|
||||
|
||||
// Enclose then-branch with TDZCheckCache.
|
||||
if (kind_ == Kind::MayContainLexicalAccessInBranch)
|
||||
tdzCache_.emplace(bce_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
IfEmitter::calculateOrCheckPushed()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if (!calculatedPushed_) {
|
||||
pushed_ = bce_->stackDepth - thenDepth_;
|
||||
calculatedPushed_ = true;
|
||||
} else {
|
||||
MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
IfEmitter::emitThen()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start || state_ == State::ElseIf);
|
||||
if (!emitIfInternal(SRC_IF))
|
||||
return false;
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Then;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IfEmitter::emitCond()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
if (!emitIfInternal(SRC_COND))
|
||||
return false;
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Cond;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IfEmitter::emitThenElse()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start || state_ == State::ElseIf);
|
||||
if (!emitIfInternal(SRC_IF_ELSE))
|
||||
return false;
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::ThenElse;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IfEmitter::emitElseInternal()
|
||||
{
|
||||
calculateOrCheckPushed();
|
||||
|
||||
// The end of TDZCheckCache for then-clause.
|
||||
if (kind_ == Kind::MayContainLexicalAccessInBranch) {
|
||||
MOZ_ASSERT(tdzCache_.isSome());
|
||||
tdzCache_.reset();
|
||||
}
|
||||
|
||||
// Emit a jump from the end of our then part around the else part. The
|
||||
// patchJumpsToTarget call at the bottom of this function will fix up
|
||||
// the offset with jumpsAroundElse value.
|
||||
if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_))
|
||||
return false;
|
||||
|
||||
// Ensure the branch-if-false comes here, then emit the else.
|
||||
if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_))
|
||||
return false;
|
||||
|
||||
// Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to
|
||||
// jump, for IonMonkey's benefit. We can't just "back up" from the pc
|
||||
// of the else clause, because we don't know whether an extended
|
||||
// jump was required to leap from the end of the then clause over
|
||||
// the else clause.
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 0,
|
||||
jumpsAroundElse_.offset - jumpAroundThen_.offset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear jumpAroundThen_ offset, to tell emitEnd there was an else part.
|
||||
jumpAroundThen_ = JumpList();
|
||||
|
||||
// Restore stack depth of the then part.
|
||||
bce_->stackDepth = thenDepth_;
|
||||
#ifdef DEBUG
|
||||
state_ = State::Else;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IfEmitter::emitElse()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::ThenElse || state_ == State::Cond);
|
||||
|
||||
if (!emitElseInternal())
|
||||
return false;
|
||||
|
||||
// Enclose else-branch with TDZCheckCache.
|
||||
if (kind_ == Kind::MayContainLexicalAccessInBranch)
|
||||
tdzCache_.emplace(bce_);
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::Else;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IfEmitter::emitElseIf()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::ThenElse);
|
||||
|
||||
if (!emitElseInternal())
|
||||
return false;
|
||||
|
||||
// Enclose cond for else-if with TDZCheckCache.
|
||||
if (kind_ == Kind::MayContainLexicalAccessInBranch)
|
||||
tdzCache_.emplace(bce_);
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::ElseIf;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IfEmitter::emitEnd()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Then || state_ == State::Else);
|
||||
// If there was an else part for the last branch, jumpAroundThen_ is
|
||||
// already fixed up when emitting the else part.
|
||||
MOZ_ASSERT_IF(state_ == State::Then, jumpAroundThen_.offset != -1);
|
||||
MOZ_ASSERT_IF(state_ == State::Else, jumpAroundThen_.offset == -1);
|
||||
|
||||
// The end of TDZCheckCache for then or else-clause.
|
||||
if (kind_ == Kind::MayContainLexicalAccessInBranch) {
|
||||
MOZ_ASSERT(tdzCache_.isSome());
|
||||
tdzCache_.reset();
|
||||
}
|
||||
|
||||
calculateOrCheckPushed();
|
||||
|
||||
if (jumpAroundThen_.offset != -1) {
|
||||
// No else part for the last branch, fixup the branch-if-false to
|
||||
// come here.
|
||||
if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Patch all the jumps around else parts.
|
||||
if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_))
|
||||
return false;
|
||||
|
||||
#ifdef DEBUG
|
||||
state_ = State::End;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
InternalIfEmitter::InternalIfEmitter(BytecodeEmitter* bce)
|
||||
: IfEmitter(bce, Kind::NoLexicalAccessInBranch)
|
||||
{}
|
||||
220
js/src/frontend/IfEmitter.h
Normal file
220
js/src/frontend/IfEmitter.h
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
/* -*- 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_IfEmitter_h
|
||||
#define frontend_IfEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "frontend/JumpList.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for blocks like if-then-else.
|
||||
//
|
||||
// This class can be used to emit single if-then-else block, or cascading
|
||||
// else-if blocks.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `if (cond) then_block`
|
||||
// IfEmitter ifThen(this);
|
||||
// emit(cond);
|
||||
// ifThen.emitThen();
|
||||
// emit(then_block);
|
||||
// ifThen.emitEnd();
|
||||
//
|
||||
// `if (cond) then_block else else_block`
|
||||
// IfEmitter ifThenElse(this);
|
||||
// emit(cond);
|
||||
// ifThenElse.emitThenElse();
|
||||
// emit(then_block);
|
||||
// ifThenElse.emitElse();
|
||||
// emit(else_block);
|
||||
// ifThenElse.emitEnd();
|
||||
//
|
||||
// `if (c1) b1 else if (c2) b2 else if (c3) b3 else b4`
|
||||
// IfEmitter ifThenElse(this);
|
||||
// emit(c1);
|
||||
// ifThenElse.emitThenElse();
|
||||
// emit(b1);
|
||||
// ifThenElse.emitElseIf();
|
||||
// emit(c2);
|
||||
// ifThenElse.emitThenElse();
|
||||
// emit(b2);
|
||||
// ifThenElse.emitElseIf();
|
||||
// emit(c3);
|
||||
// ifThenElse.emitThenElse();
|
||||
// emit(b3);
|
||||
// ifThenElse.emitElse();
|
||||
// emit(b4);
|
||||
// ifThenElse.emitEnd();
|
||||
//
|
||||
// `cond ? then_expr : else_expr`
|
||||
// IfEmitter condElse(this);
|
||||
// emit(cond);
|
||||
// condElse.emitCond();
|
||||
// emit(then_block);
|
||||
// condElse.emitElse();
|
||||
// emit(else_block);
|
||||
// condElse.emitEnd();
|
||||
//
|
||||
class MOZ_STACK_CLASS IfEmitter
|
||||
{
|
||||
public:
|
||||
// Whether the then-clause, the else-clause, or else-if condition may
|
||||
// contain declaration or access to lexical variables, which means they
|
||||
// should have their own TDZCheckCache. Basically TDZCheckCache should be
|
||||
// created for each basic block, which then-clause, else-clause, and
|
||||
// else-if condition are, but for internally used branches which are
|
||||
// known not to touch lexical variables we can skip creating TDZCheckCache
|
||||
// for them.
|
||||
//
|
||||
// See the comment for TDZCheckCache class for more details.
|
||||
enum class Kind {
|
||||
// For syntactic branches (if, if-else, and conditional expression),
|
||||
// which basically may contain declaration or accesses to lexical
|
||||
// variables inside then-clause, else-clause, and else-if condition.
|
||||
MayContainLexicalAccessInBranch,
|
||||
|
||||
// For internally used branches which don't touch lexical variables
|
||||
// inside then-clause, else-clause, nor else-if condition.
|
||||
NoLexicalAccessInBranch
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// Jump around the then clause, to the beginning of the else clause.
|
||||
JumpList jumpAroundThen_;
|
||||
|
||||
// Jump around the else clause, to the end of the entire branch.
|
||||
JumpList jumpsAroundElse_;
|
||||
|
||||
// Annotation index for IonBuilder
|
||||
unsigned noteIndex_;
|
||||
|
||||
// The stack depth before emitting the then block.
|
||||
// Used for restoring stack depth before emitting the else block.
|
||||
// Also used for assertion to make sure then and else blocks pushed the
|
||||
// same number of values.
|
||||
int32_t thenDepth_;
|
||||
|
||||
Kind kind_;
|
||||
mozilla::Maybe<TDZCheckCache> tdzCache_;
|
||||
|
||||
#ifdef DEBUG
|
||||
// The number of values pushed in the then and else blocks.
|
||||
int32_t pushed_;
|
||||
bool calculatedPushed_;
|
||||
|
||||
// The state of this emitter.
|
||||
//
|
||||
// +-------+ emitCond +------+ emitElse +------+ emitEnd +-----+
|
||||
// | Start |-+--------->| Cond |--------->| Else |------>+------->| End |
|
||||
// +-------+ | +------+ +------+ ^ +-----+
|
||||
// | |
|
||||
// v emitThen +------+ |
|
||||
// +->+--------->| Then |------------------------>+
|
||||
// ^ | +------+ ^
|
||||
// | | |
|
||||
// | | +---+
|
||||
// | | |
|
||||
// | | emitThenElse +----------+ emitElse +------+ |
|
||||
// | +------------->| ThenElse |-+--------->| Else |-+
|
||||
// | +----------+ | +------+
|
||||
// | |
|
||||
// | | emitElseIf +--------+
|
||||
// | +----------->| ElseIf |-+
|
||||
// | +--------+ |
|
||||
// | |
|
||||
// +------------------------------------------------------+
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling emitThen.
|
||||
Then,
|
||||
|
||||
// After calling emitCond.
|
||||
Cond,
|
||||
|
||||
// After calling emitThenElse.
|
||||
ThenElse,
|
||||
|
||||
// After calling emitElse.
|
||||
Else,
|
||||
|
||||
// After calling emitElseIf.
|
||||
ElseIf,
|
||||
|
||||
// After calling emitEnd.
|
||||
End
|
||||
};
|
||||
State state_;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// For InternalIfEmitter.
|
||||
IfEmitter(BytecodeEmitter* bce, Kind kind);
|
||||
|
||||
public:
|
||||
explicit IfEmitter(BytecodeEmitter* bce);
|
||||
|
||||
MOZ_MUST_USE bool emitThen();
|
||||
MOZ_MUST_USE bool emitCond();
|
||||
MOZ_MUST_USE bool emitThenElse();
|
||||
|
||||
MOZ_MUST_USE bool emitElse();
|
||||
MOZ_MUST_USE bool emitElseIf();
|
||||
|
||||
MOZ_MUST_USE bool emitEnd();
|
||||
|
||||
#ifdef DEBUG
|
||||
// Returns the number of values pushed onto the value stack inside
|
||||
// `then_block` and `else_block`.
|
||||
// Can be used in assertion after emitting if-then-else.
|
||||
int32_t pushed() const {
|
||||
return pushed_;
|
||||
}
|
||||
|
||||
// Returns the number of values popped onto the value stack inside
|
||||
// `then_block` and `else_block`.
|
||||
// Can be used in assertion after emitting if-then-else.
|
||||
int32_t popped() const {
|
||||
return -pushed_;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool emitIfInternal(SrcNoteType type);
|
||||
void calculateOrCheckPushed();
|
||||
MOZ_MUST_USE bool emitElseInternal();
|
||||
};
|
||||
|
||||
// Class for emitting bytecode for blocks like if-then-else which doesn't touch
|
||||
// lexical variables.
|
||||
//
|
||||
// See the comments above NoLexicalAccessInBranch for more details when to use
|
||||
// this instead of IfEmitter.
|
||||
class MOZ_STACK_CLASS InternalIfEmitter : public IfEmitter
|
||||
{
|
||||
public:
|
||||
explicit InternalIfEmitter(BytecodeEmitter* bce);
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_IfEmitter_h */
|
||||
33
js/src/frontend/JumpList.cpp
Normal file
33
js/src/frontend/JumpList.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- 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/JumpList.h"
|
||||
|
||||
#include "jsopcode.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
void
|
||||
JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset)
|
||||
{
|
||||
SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset);
|
||||
offset = jumpOffset;
|
||||
}
|
||||
|
||||
void
|
||||
JumpList::patchAll(jsbytecode* code, JumpTarget target)
|
||||
{
|
||||
ptrdiff_t delta;
|
||||
for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) {
|
||||
jsbytecode* pc = &code[jumpOffset];
|
||||
MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL);
|
||||
delta = GET_JUMP_OFFSET(pc);
|
||||
MOZ_ASSERT(delta < 0);
|
||||
ptrdiff_t span = target.offset - jumpOffset;
|
||||
SET_JUMP_OFFSET(pc, span);
|
||||
}
|
||||
}
|
||||
76
js/src/frontend/JumpList.h
Normal file
76
js/src/frontend/JumpList.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* -*- 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_JumpList_h
|
||||
#define frontend_JumpList_h
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "js/TypeDecls.h"
|
||||
#include "jsbytecode.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
// Linked list of jump instructions that need to be patched. The linked list is
|
||||
// stored in the bytes of the incomplete bytecode that will be patched, so no
|
||||
// extra memory is needed, and patching the instructions destroys the list.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// JumpList brList;
|
||||
// if (!emitJump(JSOP_IFEQ, &brList))
|
||||
// return false;
|
||||
// ...
|
||||
// JumpTarget label;
|
||||
// if (!emitJumpTarget(&label))
|
||||
// return false;
|
||||
// ...
|
||||
// if (!emitJump(JSOP_GOTO, &brList))
|
||||
// return false;
|
||||
// ...
|
||||
// patchJumpsToTarget(brList, label);
|
||||
//
|
||||
// +-> -1
|
||||
// |
|
||||
// |
|
||||
// ifeq .. <+ + +-+ ifeq ..
|
||||
// .. | | ..
|
||||
// label: | +-> label:
|
||||
// jumptarget | | jumptarget
|
||||
// .. | | ..
|
||||
// goto .. <+ + +-+ goto .. <+
|
||||
// | |
|
||||
// | |
|
||||
// + +
|
||||
// brList brList
|
||||
//
|
||||
// | ^
|
||||
// +------- patchJumpsToTarget -------+
|
||||
//
|
||||
|
||||
// Offset of a jump target instruction, used for patching jump instructions.
|
||||
struct JumpTarget {
|
||||
ptrdiff_t offset;
|
||||
};
|
||||
|
||||
struct JumpList {
|
||||
JumpList() {}
|
||||
// -1 is used to mark the end of jump lists.
|
||||
ptrdiff_t offset = -1;
|
||||
|
||||
// Add a jump instruction to the list.
|
||||
void push(jsbytecode* code, ptrdiff_t jumpOffset);
|
||||
|
||||
// Patch all jump instructions in this list to jump to `target`. This
|
||||
// clobbers the list.
|
||||
void patchAll(jsbytecode* code, JumpTarget target);
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_JumpList_h */
|
||||
76
js/src/frontend/TDZCheckCache.cpp
Normal file
76
js/src/frontend/TDZCheckCache.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* -*- 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/TDZCheckCache.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Nothing;
|
||||
using mozilla::Some;
|
||||
|
||||
TDZCheckCache::TDZCheckCache(BytecodeEmitter* bce)
|
||||
: Nestable<TDZCheckCache>(&bce->innermostTDZCheckCache),
|
||||
cache_(bce->cx->frontendCollectionPool())
|
||||
{}
|
||||
|
||||
bool
|
||||
TDZCheckCache::ensureCache(BytecodeEmitter* bce)
|
||||
{
|
||||
return cache_ || cache_.acquire(bce->cx);
|
||||
}
|
||||
|
||||
Maybe<MaybeCheckTDZ>
|
||||
TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name)
|
||||
{
|
||||
if (!ensureCache(bce))
|
||||
return Nothing();
|
||||
|
||||
CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
|
||||
if (p)
|
||||
return Some(p->value().wrapped);
|
||||
|
||||
MaybeCheckTDZ rv = CheckTDZ;
|
||||
for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) {
|
||||
if (it->cache_) {
|
||||
if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) {
|
||||
rv = p2->value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache_->add(p, name, rv)) {
|
||||
ReportOutOfMemory(bce->cx);
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
return Some(rv);
|
||||
}
|
||||
|
||||
bool
|
||||
TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name,
|
||||
MaybeCheckTDZ check)
|
||||
{
|
||||
if (!ensureCache(bce))
|
||||
return false;
|
||||
|
||||
CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
|
||||
if (p) {
|
||||
MOZ_ASSERT(!check, "TDZ only needs to be checked once per binding per basic block.");
|
||||
p->value() = check;
|
||||
} else {
|
||||
if (!cache_->add(p, name, check)) {
|
||||
ReportOutOfMemory(bce->cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
55
js/src/frontend/TDZCheckCache.h
Normal file
55
js/src/frontend/TDZCheckCache.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- 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_TDZCheckCache_h
|
||||
#define frontend_TDZCheckCache_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include "frontend/SharedContext.h" // for Nestable
|
||||
#include "frontend/NameCollections.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "vm/Stack.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// A cache that tracks Temporal Dead Zone (TDZ) checks, so that any use of a
|
||||
// lexical variable that's dominated by an earlier use, or by evaluation of its
|
||||
// declaration (which will initialize it, perhaps to |undefined|), doesn't have
|
||||
// to redundantly check that the lexical variable has been initialized
|
||||
//
|
||||
// Each basic block should have a TDZCheckCache in scope. Some NestableControl
|
||||
// subclasses contain a TDZCheckCache.
|
||||
//
|
||||
// When a scope containing lexical variables is entered, all such variables are
|
||||
// marked as CheckTDZ. When a lexical variable is accessed, its entry is
|
||||
// checked. If it's CheckTDZ, a JSOP_CHECKLEXICAL is emitted and then the
|
||||
// entry is marked DontCheckTDZ. If it's DontCheckTDZ, no check is emitted
|
||||
// because a prior check would have already failed. Finally, because
|
||||
// evaluating a lexical variable declaration initializes it (after any
|
||||
// initializer is evaluated), evaluating a lexical declaration marks its entry
|
||||
// as DontCheckTDZ.
|
||||
class TDZCheckCache : public Nestable<TDZCheckCache>
|
||||
{
|
||||
PooledMapPtr<CheckTDZMap> cache_;
|
||||
|
||||
MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce);
|
||||
|
||||
public:
|
||||
explicit TDZCheckCache(BytecodeEmitter* bce);
|
||||
|
||||
mozilla::Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce, JSAtom* name);
|
||||
MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check);
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_TDZCheckCache_h */
|
||||
|
|
@ -141,8 +141,11 @@ UNIFIED_SOURCES += [
|
|||
'frontend/BytecodeCompiler.cpp',
|
||||
'frontend/BytecodeEmitter.cpp',
|
||||
'frontend/FoldConstants.cpp',
|
||||
'frontend/IfEmitter.cpp',
|
||||
'frontend/JumpList.cpp',
|
||||
'frontend/NameFunctions.cpp',
|
||||
'frontend/ParseNode.cpp',
|
||||
'frontend/TDZCheckCache.cpp',
|
||||
'frontend/TokenStream.cpp',
|
||||
'gc/Allocator.cpp',
|
||||
'gc/Barrier.cpp',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue