From e8fee205662ac1c2b86a773928bcde1c0d0628d0 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 12 Mar 2023 22:19:39 +0100 Subject: [PATCH] Issue #2155 - Split TDZCheckCache, IfEmitter/InternalIfEmitter, JumpList from BytecodeEmitter --- js/src/frontend/BytecodeEmitter.cpp | 320 ++++------------------------ js/src/frontend/BytecodeEmitter.h | 59 +---- js/src/frontend/IfEmitter.cpp | 231 ++++++++++++++++++++ js/src/frontend/IfEmitter.h | 220 +++++++++++++++++++ js/src/frontend/JumpList.cpp | 33 +++ js/src/frontend/JumpList.h | 76 +++++++ js/src/frontend/TDZCheckCache.cpp | 76 +++++++ js/src/frontend/TDZCheckCache.h | 55 +++++ js/src/moz.build | 3 + 9 files changed, 740 insertions(+), 333 deletions(-) create mode 100644 js/src/frontend/IfEmitter.cpp create mode 100644 js/src/frontend/IfEmitter.h create mode 100644 js/src/frontend/JumpList.cpp create mode 100644 js/src/frontend/JumpList.h create mode 100644 js/src/frontend/TDZCheckCache.cpp create mode 100644 js/src/frontend/TDZCheckCache.h diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index e21ab72407..13ba7c45cd 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -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 -{ - PooledMapPtr cache_; - - MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) { - return cache_ || cache_.acquire(bce->cx); - } - - public: - explicit TDZCheckCache(BytecodeEmitter* bce) - : Nestable(&bce->innermostTDZCheckCache), - cache_(bce->cx->frontendCollectionPool()) - { } - - Maybe needsTDZCheck(BytecodeEmitter* bce, JSAtom* name); - MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check); -}; - class BytecodeEmitter::NestableControl : public Nestable { 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 -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; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index bf1154e6e1..19842ca646 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -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 BytecodeVector; typedef Vector 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; diff --git a/js/src/frontend/IfEmitter.cpp b/js/src/frontend/IfEmitter.cpp new file mode 100644 index 0000000000..b8586932f2 --- /dev/null +++ b/js/src/frontend/IfEmitter.cpp @@ -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) +{} diff --git a/js/src/frontend/IfEmitter.h b/js/src/frontend/IfEmitter.h new file mode 100644 index 0000000000..a8d436d6b5 --- /dev/null +++ b/js/src/frontend/IfEmitter.h @@ -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 + +#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 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 */ diff --git a/js/src/frontend/JumpList.cpp b/js/src/frontend/JumpList.cpp new file mode 100644 index 0000000000..20155af537 --- /dev/null +++ b/js/src/frontend/JumpList.cpp @@ -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); + } +} diff --git a/js/src/frontend/JumpList.h b/js/src/frontend/JumpList.h new file mode 100644 index 0000000000..7be913ae35 --- /dev/null +++ b/js/src/frontend/JumpList.h @@ -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 + +#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 */ diff --git a/js/src/frontend/TDZCheckCache.cpp b/js/src/frontend/TDZCheckCache.cpp new file mode 100644 index 0000000000..2f9245e7d1 --- /dev/null +++ b/js/src/frontend/TDZCheckCache.cpp @@ -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(&bce->innermostTDZCheckCache), + cache_(bce->cx->frontendCollectionPool()) +{} + +bool +TDZCheckCache::ensureCache(BytecodeEmitter* bce) +{ + return cache_ || cache_.acquire(bce->cx); +} + +Maybe +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; +} diff --git a/js/src/frontend/TDZCheckCache.h b/js/src/frontend/TDZCheckCache.h new file mode 100644 index 0000000000..0789cbeeaf --- /dev/null +++ b/js/src/frontend/TDZCheckCache.h @@ -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 +{ + PooledMapPtr cache_; + + MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce); + + public: + explicit TDZCheckCache(BytecodeEmitter* bce); + + mozilla::Maybe needsTDZCheck(BytecodeEmitter* bce, JSAtom* name); + MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_TDZCheckCache_h */ diff --git a/js/src/moz.build b/js/src/moz.build index d54547aecf..6ab55538b9 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -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',