Issue #2155 - Split TDZCheckCache, IfEmitter/InternalIfEmitter, JumpList from BytecodeEmitter

This commit is contained in:
Martok 2023-03-12 22:19:39 +01:00 committed by roytam1
commit e8fee20566
9 changed files with 740 additions and 333 deletions

View file

@ -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, &noteIndex_))
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;
}

View file

@ -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;

View 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, &noteIndex_))
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
View 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 */

View 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);
}
}

View 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 */

View 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;
}

View 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 */

View file

@ -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',