summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2023-03-22 16:20:08 +0000
committerMoonchild <moonchild@palemoon.org>2023-03-22 16:20:08 +0000
commitda245374140f6550e2008ced2ffa30cfa6423236 (patch)
tree2a4dc159362b2336111e98401c2030cf62134554 /js
parent16baa045334bfca53bec1b6a25291ebeec9b7cc6 (diff)
parentc3a35f75a2ff23d1006ec8645e9f99b53d7b4a95 (diff)
downloaduxp-da245374140f6550e2008ced2ffa30cfa6423236.tar.gz
Merge pull request 'Port ByteCodeEmitter helper classes' (#2159) from martok/UXP-contrib:mr/bce-emitters into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2159
Diffstat (limited to 'js')
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp2311
-rw-r--r--js/src/frontend/BytecodeEmitter.h150
-rw-r--r--js/src/frontend/CallOrNewEmitter.cpp319
-rw-r--r--js/src/frontend/CallOrNewEmitter.h338
-rw-r--r--js/src/frontend/ElemOpEmitter.cpp288
-rw-r--r--js/src/frontend/ElemOpEmitter.h278
-rw-r--r--js/src/frontend/IfEmitter.cpp231
-rw-r--r--js/src/frontend/IfEmitter.h220
-rw-r--r--js/src/frontend/JumpList.cpp33
-rw-r--r--js/src/frontend/JumpList.h76
-rw-r--r--js/src/frontend/NameOpEmitter.cpp382
-rw-r--r--js/src/frontend/NameOpEmitter.h193
-rw-r--r--js/src/frontend/ParseNode.h20
-rw-r--r--js/src/frontend/PropOpEmitter.cpp275
-rw-r--r--js/src/frontend/PropOpEmitter.h269
-rw-r--r--js/src/frontend/TDZCheckCache.cpp76
-rw-r--r--js/src/frontend/TDZCheckCache.h55
-rw-r--r--js/src/frontend/ValueUsage.h28
-rw-r--r--js/src/moz.build7
19 files changed, 4043 insertions, 1506 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index e21ab72407..4b80a99669 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -27,7 +27,13 @@
#include "jstypes.h"
#include "jsutil.h"
+#include "frontend/CallOrNewEmitter.h"
+#include "frontend/ElemOpEmitter.h"
+#include "frontend/IfEmitter.h"
+#include "frontend/NameOpEmitter.h"
#include "frontend/Parser.h"
+#include "frontend/PropOpEmitter.h"
+#include "frontend/TDZCheckCache.h"
#include "frontend/TokenStream.h"
#include "vm/Debugger.h"
#include "vm/GeneratorObject.h"
@@ -67,27 +73,19 @@ 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>
+uint32_t
+GetCallArgsAndCount(ParseNode* callNode, ParseNode** argumentNode)
{
- PooledMapPtr<CheckTDZMap> cache_;
-
- MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
- return cache_ || cache_.acquire(bce->cx);
+ // XXX This helper function exists to make ports less error-prone.
+ // The current parse tree splits the information between callNode and callee.
+ // A later refactor has a ListNode instead, with slightly different storage.
+ // (See also the "what is stored where" table in ParseNode.h)
+ ParseNode* calleeNode = callNode->pn_head;
+ if (argumentNode && calleeNode) {
+ *argumentNode = calleeNode->pn_next;
}
-
- 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);
-};
+ return callNode->pn_count - 1;
+}
class BytecodeEmitter::NestableControl : public Nestable<BytecodeEmitter::NestableControl>
{
@@ -212,7 +210,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_;
@@ -989,7 +987,10 @@ BytecodeEmitter::EmitterScope::enterComprehensionFor(BytecodeEmitter* bce,
RootedAtom name(bce->cx);
for (BindingIter bi(*bindings, frameSlotStart(), /* isNamedLambda = */ false); bi; bi++) {
name = bi.name();
- if (!bce->emitInitializeName(name, nop))
+ NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs())
+ return false;
+ if (!noe.emitAssignment())
return false;
}
@@ -1461,53 +1462,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 +1790,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 +1799,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 +1954,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 +1978,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 +2302,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)
{
@@ -2584,11 +2367,21 @@ BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump)
}
bool
+BytecodeEmitter::emitCall(JSOp op, uint16_t argc, const Maybe<uint32_t>& sourceCoordOffset)
+{
+ if (sourceCoordOffset.isSome()) {
+ if (!updateSourceCoordNotes(*sourceCoordOffset))
+ return false;
+ }
+ return emit3(op, ARGC_HI(argc), ARGC_LO(argc));
+}
+
+bool
BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn)
{
if (pn && !updateSourceCoordNotes(pn->pn_pos.begin))
return false;
- return emit3(op, ARGC_HI(argc), ARGC_LO(argc));
+ return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing());
}
bool
@@ -2611,6 +2404,21 @@ BytecodeEmitter::emitDupAt(unsigned slotFromTop)
}
bool
+BytecodeEmitter::emitPopN(unsigned n)
+{
+ MOZ_ASSERT(n != 0);
+
+ if (n == 1)
+ return emit1(JSOP_POP);
+
+ // 2 JSOP_POPs (2 bytes) are shorter than JSOP_POPN (3 bytes).
+ if (n == 2)
+ return emit1(JSOP_POP) && emit1(JSOP_POP);
+
+ return emitUint16Operand(JSOP_POPN, n);
+}
+
+bool
BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind)
{
return emit2(JSOP_CHECKISOBJ, uint8_t(kind));
@@ -3022,7 +2830,6 @@ bool
BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
{
MOZ_ASSERT(atom);
- MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
// .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
// JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
@@ -3040,14 +2847,15 @@ BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
if (!makeAtomIndex(atom, &index))
return false;
- return emitIndexOp(op, index);
+ return emitAtomOp(index, op);
}
bool
-BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op)
+BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op)
{
- MOZ_ASSERT(pn->pn_atom != nullptr);
- return emitAtomOp(pn->pn_atom, op);
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+
+ return emitIndexOp(op, atomIndex);
}
bool
@@ -3133,15 +2941,6 @@ BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec)
return true;
}
-static JSOp
-GetIncDecInfo(ParseNodeKind kind, bool* post)
-{
- MOZ_ASSERT(kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT ||
- kind == PNK_POSTDECREMENT || kind == PNK_PREDECREMENT);
- *post = kind == PNK_POSTINCREMENT || kind == PNK_POSTDECREMENT;
- return (kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT) ? JSOP_ADD : JSOP_SUB;
-}
-
JSOp
BytecodeEmitter::strictifySetNameOp(JSOp op)
{
@@ -3852,248 +3651,20 @@ BytecodeEmitter::emitToIteratorResult(bool done)
}
bool
-BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext)
+BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc)
{
- switch (loc.kind()) {
- case NameLocation::Kind::Dynamic:
- if (!emitAtomOp(name, JSOP_GETNAME))
- return false;
- break;
-
- case NameLocation::Kind::Global:
- if (!emitAtomOp(name, JSOP_GETGNAME))
- return false;
- break;
-
- case NameLocation::Kind::Intrinsic:
- if (!emitAtomOp(name, JSOP_GETINTRINSIC))
- return false;
- break;
-
- case NameLocation::Kind::NamedLambdaCallee:
- if (!emit1(JSOP_CALLEE))
- return false;
- break;
-
- case NameLocation::Kind::Import:
- if (!emitAtomOp(name, JSOP_GETIMPORT))
- return false;
- break;
-
- case NameLocation::Kind::ArgumentSlot:
- if (!emitArgOp(JSOP_GETARG, loc.argumentSlot()))
- return false;
- break;
-
- case NameLocation::Kind::FrameSlot:
- if (loc.isLexical()) {
- if (!emitTDZCheckIfNeeded(name, loc))
- return false;
- }
- if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot()))
- return false;
- break;
-
- case NameLocation::Kind::EnvironmentCoordinate:
- if (loc.isLexical()) {
- if (!emitTDZCheckIfNeeded(name, loc))
- return false;
- }
- if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate()))
- return false;
- break;
-
- case NameLocation::Kind::DynamicAnnexBVar:
- MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
- }
-
- // Need to provide |this| value for call.
- if (callContext) {
- switch (loc.kind()) {
- case NameLocation::Kind::Dynamic: {
- JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
- if (!emitAtomOp(name, thisOp))
- return false;
- break;
- }
-
- case NameLocation::Kind::Global:
- if (!emitAtomOp(name, JSOP_GIMPLICITTHIS))
- return false;
- break;
-
- case NameLocation::Kind::Intrinsic:
- case NameLocation::Kind::NamedLambdaCallee:
- case NameLocation::Kind::Import:
- case NameLocation::Kind::ArgumentSlot:
- case NameLocation::Kind::FrameSlot:
- case NameLocation::Kind::EnvironmentCoordinate:
- if (!emit1(JSOP_UNDEFINED))
- return false;
- break;
-
- case NameLocation::Kind::DynamicAnnexBVar:
- MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
- }
+ NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
+ if (!noe.emitGet()) {
+ return false;
}
return true;
}
bool
-BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext)
+BytecodeEmitter::emitGetName(ParseNode* pn)
{
- return emitGetName(pn->name(), callContext);
-}
-
-template <typename RHSEmitter>
-bool
-BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
- RHSEmitter emitRhs, bool initialize)
-{
- bool emittedBindOp = false;
-
- switch (loc.kind()) {
- case NameLocation::Kind::Dynamic:
- case NameLocation::Kind::Import:
- case NameLocation::Kind::DynamicAnnexBVar: {
- uint32_t atomIndex;
- if (!makeAtomIndex(name, &atomIndex))
- return false;
- if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) {
- // Annex B vars always go on the nearest variable environment,
- // even if lexical environments in between contain same-named
- // bindings.
- if (!emit1(JSOP_BINDVAR))
- return false;
- } else {
- if (!emitIndexOp(JSOP_BINDNAME, atomIndex))
- return false;
- }
- emittedBindOp = true;
- if (!emitRhs(this, loc, emittedBindOp))
- return false;
- if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex))
- return false;
- break;
- }
-
- case NameLocation::Kind::Global: {
- JSOp op;
- uint32_t atomIndex;
- if (!makeAtomIndex(name, &atomIndex))
- return false;
- if (loc.isLexical() && initialize) {
- // INITGLEXICAL always gets the global lexical scope. It doesn't
- // need a BINDGNAME.
- MOZ_ASSERT(innermostScope()->is<GlobalScope>());
- op = JSOP_INITGLEXICAL;
- } else {
- if (!emitIndexOp(JSOP_BINDGNAME, atomIndex))
- return false;
- emittedBindOp = true;
- op = strictifySetNameOp(JSOP_SETGNAME);
- }
- if (!emitRhs(this, loc, emittedBindOp))
- return false;
- if (!emitIndexOp(op, atomIndex))
- return false;
- break;
- }
-
- case NameLocation::Kind::Intrinsic:
- if (!emitRhs(this, loc, emittedBindOp))
- return false;
- if (!emitAtomOp(name, JSOP_SETINTRINSIC))
- return false;
- break;
-
- case NameLocation::Kind::NamedLambdaCallee:
- if (!emitRhs(this, loc, emittedBindOp))
- return false;
- // Assigning to the named lambda is a no-op in sloppy mode but
- // throws in strict mode.
- if (sc->strict() && !emit1(JSOP_THROWSETCALLEE))
- return false;
- break;
-
- case NameLocation::Kind::ArgumentSlot: {
- // If we assign to a positional formal parameter and the arguments
- // object is unmapped (strict mode or function with
- // default/rest/destructing args), parameters do not alias
- // arguments[i], and to make the arguments object reflect initial
- // parameter values prior to any mutation we create it eagerly
- // whenever parameters are (or might, in the case of calls to eval)
- // assigned.
- FunctionBox* funbox = sc->asFunctionBox();
- if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj())
- funbox->setDefinitelyNeedsArgsObj();
-
- if (!emitRhs(this, loc, emittedBindOp))
- return false;
- if (!emitArgOp(JSOP_SETARG, loc.argumentSlot()))
- return false;
- break;
- }
-
- case NameLocation::Kind::FrameSlot: {
- JSOp op = JSOP_SETLOCAL;
- if (!emitRhs(this, loc, emittedBindOp))
- return false;
- if (loc.isLexical()) {
- if (initialize) {
- op = JSOP_INITLEXICAL;
- } else {
- if (loc.isConst())
- op = JSOP_THROWSETCONST;
-
- if (!emitTDZCheckIfNeeded(name, loc))
- return false;
- }
- }
- if (!emitLocalOp(op, loc.frameSlot()))
- return false;
- if (op == JSOP_INITLEXICAL) {
- if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
- return false;
- }
- break;
- }
-
- case NameLocation::Kind::EnvironmentCoordinate: {
- JSOp op = JSOP_SETALIASEDVAR;
- if (!emitRhs(this, loc, emittedBindOp))
- return false;
- if (loc.isLexical()) {
- if (initialize) {
- op = JSOP_INITALIASEDLEXICAL;
- } else {
- if (loc.isConst())
- op = JSOP_THROWSETALIASEDCONST;
-
- if (!emitTDZCheckIfNeeded(name, loc))
- return false;
- }
- }
- if (loc.bindingKind() == BindingKind::NamedLambdaCallee) {
- // Assigning to the named lambda is a no-op in sloppy mode and throws
- // in strict mode.
- op = JSOP_THROWSETALIASEDCONST;
- if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate()))
- return false;
- } else {
- if (!emitEnvCoordOp(op, loc.environmentCoordinate()))
- return false;
- }
- if (op == JSOP_INITALIASEDLEXICAL) {
- if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
- return false;
- }
- break;
- }
- }
-
- return true;
+ return emitGetName(pn->name());
}
bool
@@ -4156,7 +3727,7 @@ BytecodeEmitter::emitPropLHS(ParseNode* pn)
do {
/* Walk back up the list, emitting annotated name ops. */
- if (!emitAtomOp(pndot, JSOP_GETPROP))
+ if (!emitAtomOp(pndot->pn_atom, JSOP_GETPROP))
return false;
/* Reverse the pn_expr link again. */
@@ -4172,217 +3743,56 @@ BytecodeEmitter::emitPropLHS(ParseNode* pn)
}
bool
-BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall)
-{
- if (!emitGetThisForSuperBase(superBase))
- return false;
- if (isCall && !emit1(JSOP_DUP))
- return false;
- if (!emit1(JSOP_SUPERBASE))
- return false;
- return true;
-}
-
-bool
-BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op)
-{
- MOZ_ASSERT(pn->isArity(PN_NAME));
-
- if (!emitPropLHS(pn))
- return false;
-
- if (op == JSOP_CALLPROP && !emit1(JSOP_DUP))
- return false;
-
- if (!emitAtomOp(pn, op))
- return false;
-
- if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP))
- return false;
-
- return true;
-}
-
-bool
-BytecodeEmitter::emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall)
-{
- ParseNode* base = &pn->as<PropertyAccess>().expression();
- if (!emitSuperPropLHS(base, isCall))
- return false;
-
- if (!emitAtomOp(pn, op))
- return false;
-
- if (isCall && !emit1(JSOP_SWAP))
- return false;
-
- return true;
-}
-
-bool
BytecodeEmitter::emitPropIncDec(ParseNode* pn)
{
MOZ_ASSERT(pn->pn_kid->isKind(PNK_DOT));
+ PropertyAccess* prop = &pn->pn_kid->as<PropertyAccess>();
+ bool isSuper = prop->isSuper();
- bool post;
- bool isSuper = pn->pn_kid->as<PropertyAccess>().isSuper();
- JSOp binop = GetIncDecInfo(pn->getKind(), &post);
-
+ ParseNodeKind kind = pn->getKind();
+ PropOpEmitter poe(this,
+ kind == PNK_POSTINCREMENT ? PropOpEmitter::Kind::PostIncrement
+ : kind == PNK_PREINCREMENT ? PropOpEmitter::Kind::PreIncrement
+ : kind == PNK_POSTDECREMENT ? PropOpEmitter::Kind::PostDecrement
+ : PropOpEmitter::Kind::PreDecrement,
+ isSuper
+ ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+ if (!poe.prepareForObj()) {
+ return false;
+ }
if (isSuper) {
ParseNode* base = &pn->pn_kid->as<PropertyAccess>().expression();
- if (!emitSuperPropLHS(base)) // THIS OBJ
- return false;
- if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ
+ if (!emitGetThisForSuperBase(base)) { // THIS
return false;
+ }
} else {
if (!emitPropLHS(pn->pn_kid)) // OBJ
return false;
- if (!emit1(JSOP_DUP)) // OBJ OBJ
- return false;
}
- if (!emitAtomOp(pn->pn_kid, isSuper? JSOP_GETPROP_SUPER : JSOP_GETPROP)) // OBJ V
- return false;
- if (!emit1(JSOP_POS)) // OBJ N
- return false;
- if (post && !emit1(JSOP_DUP)) // OBJ N? N
- return false;
- if (!emit1(JSOP_ONE)) // OBJ N? N 1
- return false;
- if (!emit1(binop)) // OBJ N? N+1
- return false;
-
- if (post) {
- if (!emit2(JSOP_PICK, 2 + isSuper)) // N? N+1 OBJ
- return false;
- if (!emit1(JSOP_SWAP)) // N? OBJ N+1
- return false;
- if (isSuper) {
- if (!emit2(JSOP_PICK, 3)) // N THIS N+1 OBJ
- return false;
- if (!emit1(JSOP_SWAP)) // N THIS OBJ N+1
- return false;
- }
- }
-
- JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
- : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
- if (!emitAtomOp(pn->pn_kid, setOp)) // N? N+1
- return false;
- if (post && !emit1(JSOP_POP)) // RESULT
- return false;
-
- return true;
-}
-
-bool
-BytecodeEmitter::emitNameIncDec(ParseNode* pn)
-{
- MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
-
- bool post;
- JSOp binop = GetIncDecInfo(pn->getKind(), &post);
-
- auto emitRhs = [pn, post, binop](BytecodeEmitter* bce, const NameLocation& loc,
- bool emittedBindOp)
- {
- JSAtom* name = pn->pn_kid->name();
- if (!bce->emitGetNameAtLocation(name, loc, false)) // SCOPE? V
- return false;
- if (!bce->emit1(JSOP_POS)) // SCOPE? N
- return false;
- if (post && !bce->emit1(JSOP_DUP)) // SCOPE? N? N
- return false;
- if (!bce->emit1(JSOP_ONE)) // SCOPE? N? N 1
- return false;
- if (!bce->emit1(binop)) // SCOPE? N? N+1
- return false;
-
- if (post && emittedBindOp) {
- if (!bce->emit2(JSOP_PICK, 2)) // N? N+1 SCOPE?
- return false;
- if (!bce->emit1(JSOP_SWAP)) // N? SCOPE? N+1
- return false;
- }
-
- return true;
- };
-
- if (!emitSetName(pn->pn_kid, emitRhs))
+ if (!poe.emitIncDec(prop->nameAtom())) { // RESULT
return false;
-
- if (post && !emit1(JSOP_POP))
- return false;
-
- return true;
-}
-
-bool
-BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts)
-{
- MOZ_ASSERT(pn->isArity(PN_BINARY));
-
- if (!emitTree(pn->pn_left))
- return false;
-
- if (opts == EmitElemOption::IncDec) {
- if (!emit1(JSOP_CHECKOBJCOERCIBLE))
- return false;
- } else if (opts == EmitElemOption::Call) {
- if (!emit1(JSOP_DUP))
- return false;
}
- if (!emitTree(pn->pn_right))
- return false;
-
- if (opts == EmitElemOption::Set) {
- if (!emit2(JSOP_PICK, 2))
- return false;
- } else if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
- if (!emit1(JSOP_TOID))
- return false;
- }
return true;
}
bool
-BytecodeEmitter::emitSuperElemOperands(ParseNode* pn, EmitElemOption opts)
+BytecodeEmitter::emitNameIncDec(ParseNode* incDec)
{
- MOZ_ASSERT(pn->isKind(PNK_ELEM) && pn->as<PropertyByValue>().isSuper());
-
- // The ordering here is somewhat screwy. We need to evaluate the propval
- // first, by spec. Do a little dance to not emit more than one JSOP_THIS.
- // Since JSOP_THIS might throw in derived class constructors, we cannot
- // just push it earlier as the receiver. We have to swap it down instead.
+ MOZ_ASSERT(incDec->pn_kid->isKind(PNK_NAME));
- if (!emitTree(pn->pn_right))
+ ParseNodeKind kind = incDec->getKind();
+ NameNode* name = &incDec->pn_kid->as<NameNode>();
+ NameOpEmitter noe(this, name->pn_atom,
+ kind == PNK_POSTINCREMENT ? NameOpEmitter::Kind::PostIncrement
+ : kind == PNK_PREINCREMENT ? NameOpEmitter::Kind::PreIncrement
+ : kind == PNK_POSTDECREMENT ? NameOpEmitter::Kind::PostDecrement
+ : NameOpEmitter::Kind::PreDecrement);
+ if (!noe.emitIncDec()) {
return false;
-
- // We need to convert the key to an object id first, so that we do not do
- // it inside both the GETELEM and the SETELEM.
- if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
- if (!emit1(JSOP_TOID))
- return false;
- }
-
- if (!emitGetThisForSuperBase(pn->pn_left))
- return false;
-
- if (opts == EmitElemOption::Call) {
- if (!emit1(JSOP_SWAP))
- return false;
-
- // We need another |this| on top, also
- if (!emitDupAt(1))
- return false;
}
- if (!emit1(JSOP_SUPERBASE))
- return false;
-
- if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 3))
- return false;
-
return true;
}
@@ -4397,33 +3807,38 @@ BytecodeEmitter::emitElemOpBase(JSOp op)
}
bool
-BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op)
+BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe)
{
- EmitElemOption opts = EmitElemOption::Get;
- if (op == JSOP_CALLELEM)
- opts = EmitElemOption::Call;
- else if (op == JSOP_SETELEM || op == JSOP_STRICTSETELEM)
- opts = EmitElemOption::Set;
-
- return emitElemOperands(pn, opts) && emitElemOpBase(op);
-}
+ if (isSuper) {
+ if (!eoe.prepareForObj()) { //
+ return false;
+ }
+ ParseNode* base = &elem->expression();
+ if (!emitGetThisForSuperBase(base)) { // THIS
+ return false;
+ }
+ if (!eoe.prepareForKey()) { // THIS
+ return false;
+ }
+ if (!emitTree(&elem->key())) { // THIS KEY
+ return false;
+ }
-bool
-BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall)
-{
- EmitElemOption opts = EmitElemOption::Get;
- if (isCall)
- opts = EmitElemOption::Call;
- else if (op == JSOP_SETELEM_SUPER || op == JSOP_STRICTSETELEM_SUPER)
- opts = EmitElemOption::Set;
+ return true;
+ }
- if (!emitSuperElemOperands(pn, opts))
+ if (!eoe.prepareForObj()) { //
return false;
- if (!emitElemOpBase(op))
+ }
+ if (!emitTree(&elem->expression())) { // OBJ
return false;
-
- if (isCall && !emit1(JSOP_SWAP))
+ }
+ if (!eoe.prepareForKey()) { // OBJ? OBJ
return false;
+ }
+ if (!emitTree(&elem->key())) { // OBJ? OBJ KEY
+ return false;
+ }
return true;
}
@@ -4431,74 +3846,27 @@ BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall)
bool
BytecodeEmitter::emitElemIncDec(ParseNode* pn)
{
- MOZ_ASSERT(pn->pn_kid->isKind(PNK_ELEM));
-
- bool isSuper = pn->pn_kid->as<PropertyByValue>().isSuper();
-
- // We need to convert the key to an object id first, so that we do not do
- // it inside both the GETELEM and the SETELEM. This is done by
- // emit(Super)ElemOperands.
- if (isSuper) {
- if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec))
- return false;
- } else {
- if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec))
- return false;
- }
-
- bool post;
- JSOp binop = GetIncDecInfo(pn->getKind(), &post);
-
- JSOp getOp;
- if (isSuper) {
- // There's no such thing as JSOP_DUP3, so we have to be creative.
- // Note that pushing things again is no fewer JSOps.
- if (!emitDupAt(2)) // KEY THIS OBJ KEY
- return false;
- if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS
- return false;
- if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS OBJ
- return false;
- getOp = JSOP_GETELEM_SUPER;
- } else {
- // OBJ KEY
- if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY
- return false;
- getOp = JSOP_GETELEM;
- }
- if (!emitElemOpBase(getOp)) // OBJ KEY V
- return false;
- if (!emit1(JSOP_POS)) // OBJ KEY N
- return false;
- if (post && !emit1(JSOP_DUP)) // OBJ KEY N? N
- return false;
- if (!emit1(JSOP_ONE)) // OBJ KEY N? N 1
- return false;
- if (!emit1(binop)) // OBJ KEY N? N+1
- return false;
-
- if (post) {
- if (isSuper) {
- // We have one more value to rotate around, because of |this|
- // on the stack
- if (!emit2(JSOP_PICK, 4))
- return false;
- }
- if (!emit2(JSOP_PICK, 3 + isSuper)) // KEY N N+1 OBJ
- return false;
- if (!emit2(JSOP_PICK, 3 + isSuper)) // N N+1 OBJ KEY
- return false;
- if (!emit2(JSOP_PICK, 2 + isSuper)) // N OBJ KEY N+1
- return false;
+ PropertyByValue* elemExpr = &pn->pn_kid->as<PropertyByValue>();
+ bool isSuper = elemExpr->isSuper();
+ ParseNodeKind kind = pn->getKind();
+ ElemOpEmitter eoe(this,
+ kind == PNK_POSTINCREMENT ? ElemOpEmitter::Kind::PostIncrement
+ : kind == PNK_PREINCREMENT ? ElemOpEmitter::Kind::PreIncrement
+ : kind == PNK_POSTDECREMENT ? ElemOpEmitter::Kind::PostDecrement
+ : ElemOpEmitter::Kind::PreDecrement,
+ isSuper
+ ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { // [Super]
+ // // THIS KEY
+ // // [Other]
+ // // OBJ KEY
+ return false;
+ }
+ if (!eoe.emitIncDec()) { // RESULT
+ return false;
}
- JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
- : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
- if (!emitElemOpBase(setOp)) // N? N+1
- return false;
- if (post && !emit1(JSOP_POP)) // RESULT
- return false;
-
return true;
}
@@ -4964,21 +4332,6 @@ BytecodeEmitter::emitSetThis(ParseNode* pn)
MOZ_ASSERT(pn->pn_left->isKind(PNK_NAME));
RootedAtom name(cx, pn->pn_left->name());
- auto emitRhs = [&name, pn](BytecodeEmitter* bce, const NameLocation&, bool) {
- // Emit the new |this| value.
- if (!bce->emitTree(pn->pn_right))
- return false;
- // Get the original |this| and throw if we already initialized
- // it. Do *not* use the NameLocation argument, as that's the special
- // lexical location below to deal with super() semantics.
- if (!bce->emitGetName(name))
- return false;
- if (!bce->emit1(JSOP_CHECKTHISREINIT))
- return false;
- if (!bce->emit1(JSOP_POP))
- return false;
- return true;
- };
// The 'this' binding is not lexical, but due to super() semantics this
// initialization needs to be treated as a lexical one.
@@ -4995,7 +4348,32 @@ BytecodeEmitter::emitSetThis(ParseNode* pn)
lexicalLoc = loc;
}
- return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true);
+ NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) { //
+ return false;
+ }
+
+ // Emit the new |this| value.
+ if (!emitTree(pn->pn_right)) // NEWTHIS
+ return false;
+
+ // Get the original |this| and throw if we already initialized
+ // it. Do *not* use the NameLocation argument, as that's the special
+ // lexical location below to deal with super() semantics.
+ if (!emitGetName(name)) { // NEWTHIS THIS
+ return false;
+ }
+ if (!emit1(JSOP_CHECKTHISREINIT)) { // NEWTHIS THIS
+ return false;
+ }
+ if (!emit1(JSOP_POP)) { // NEWTHIS
+ return false;
+ }
+ if (!noe.emitAssignment()) { // NEWTHIS
+ return false;
+ }
+
+ return true;
}
bool
@@ -5201,28 +4579,63 @@ BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted)
switch (target->getKind()) {
case PNK_DOT: {
- if (target->as<PropertyAccess>().isSuper()) {
- if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression()))
+ PropertyAccess* prop = &target->as<PropertyAccess>();
+ bool isSuper = prop->isSuper();
+ PropOpEmitter poe(this,
+ PropOpEmitter::Kind::SimpleAssignment,
+ isSuper
+ ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+ if (!poe.prepareForObj()) {
+ return false;
+ }
+ if (isSuper) {
+ ParseNode* base = &prop->expression();
+ if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE
return false;
+ }
+ // SUPERBASE is pushed onto THIS in poe.prepareForRhs below.
*emitted = 2;
} else {
- if (!emitTree(target->pn_expr))
+ if (!emitTree(target->pn_expr)) // OBJ
return false;
*emitted = 1;
}
+ if (!poe.prepareForRhs()) { // [Super]
+ // // THIS SUPERBASE
+ // // [Other]
+ // // OBJ
+ return false;
+ }
break;
}
case PNK_ELEM: {
- if (target->as<PropertyByValue>().isSuper()) {
- if (!emitSuperElemOperands(target, EmitElemOption::Ref))
- return false;
+ PropertyByValue* elem = &target->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ ElemOpEmitter eoe(this,
+ ElemOpEmitter::Kind::SimpleAssignment,
+ isSuper
+ ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super]
+ // // THIS KEY
+ // // [Other]
+ // // OBJ KEY
+ return false;
+ }
+ if (isSuper) {
+ // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below.
*emitted = 3;
} else {
- if (!emitElemOperands(target, EmitElemOption::Ref))
- return false;
*emitted = 2;
}
+ if (!eoe.prepareForRhs()) { // [Super]
+ // // THIS KEY SUPERBASE
+ // // [Other]
+ // // OBJ KEY
+ return false;
+ }
break;
}
@@ -5262,38 +4675,13 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri
} else {
switch (target->getKind()) {
case PNK_NAME: {
- auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
- bool emittedBindOp)
- {
- if (emittedBindOp) {
- // This is like ordinary assignment, but with one
- // difference.
- //
- // In `a = b`, we first determine a binding for `a` (using
- // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`,
- // then a JSOP_SETNAME instruction.
- //
- // In `[a] = [b]`, per spec, `b` is evaluated first, then
- // we determine a binding for `a`. Then we need to do
- // assignment-- but the operands are on the stack in the
- // wrong order for JSOP_SETPROP, so we have to add a
- // JSOP_SWAP.
- //
- // In the cases where we are emitting a name op, emit a
- // swap because of this.
- return bce->emit1(JSOP_SWAP);
- }
-
- // In cases of emitting a frame slot or environment slot,
- // nothing needs be done.
- return true;
- };
-
RootedAtom name(cx, target->name());
+ NameLocation loc;
+ NameOpEmitter::Kind kind;
switch (flav) {
case DestructuringDeclaration:
- if (!emitInitializeName(name, emitSwapScopeAndRhs))
- return false;
+ loc = lookupName(name);
+ kind = NameOpEmitter::Kind::Initialize;
break;
case DestructuringFormalParameterInVarScope: {
@@ -5302,45 +4690,88 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri
// the function scope. The innermost scope is the var scope,
// and its enclosing scope is the function scope.
EmitterScope* funScope = innermostEmitterScope()->enclosingInFrame();
- NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope);
- if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true))
- return false;
+ loc = *locationOfNameBoundInScope(name, funScope);
+ kind = NameOpEmitter::Kind::Initialize;
break;
}
case DestructuringAssignment:
- if (!emitSetName(name, emitSwapScopeAndRhs))
- return false;
+ loc = lookupName(name);
+ kind = NameOpEmitter::Kind::SimpleAssignment;
break;
}
+ NameOpEmitter noe(this, name, loc, kind);
+ if (!noe.prepareForRhs()) { // V ENV?
+ return false;
+ }
+ if (noe.emittedBindOp()) {
+ // This is like ordinary assignment, but with one difference.
+ //
+ // In `a = b`, we first determine a binding for `a` (using
+ // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then
+ // a JSOP_SETNAME instruction.
+ //
+ // In `[a] = [b]`, per spec, `b` is evaluated first, then we
+ // determine a binding for `a`. Then we need to do assignment--
+ // but the operands are on the stack in the wrong order for
+ // JSOP_SETPROP, so we have to add a JSOP_SWAP.
+ //
+ // In the cases where we are emitting a name op, emit a swap
+ // because of this.
+ if (!emit1(JSOP_SWAP)) { // ENV V
+ return false;
+ }
+ } else {
+ // In cases of emitting a frame slot or environment slot,
+ // nothing needs be done.
+ }
+ if (!noe.emitAssignment()) { // V
+ return false;
+ }
break;
}
case PNK_DOT: {
// The reference is already pushed by emitDestructuringLHSRef.
- JSOp setOp;
- if (target->as<PropertyAccess>().isSuper())
- setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER;
- else
- setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
- if (!emitAtomOp(target, setOp))
+ // // [Super]
+ // // THIS SUPERBASE VAL
+ // // [Other]
+ // // OBJ VAL
+ PropertyAccess* prop = &target->as<PropertyAccess>();
+ bool isSuper = prop->isSuper();
+ PropOpEmitter poe(this,
+ PropOpEmitter::Kind::SimpleAssignment,
+ isSuper
+ ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+ if (!poe.skipObjAndRhs()) {
return false;
+ }
+ if (!poe.emitAssignment(prop->nameAtom())) {
+ return false; // VAL
+ }
break;
}
case PNK_ELEM: {
// The reference is already pushed by emitDestructuringLHSRef.
- if (target->as<PropertyByValue>().isSuper()) {
- JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER;
- // emitDestructuringLHSRef already did emitSuperElemOperands
- // part of emitSuperElemOp. Perform remaining part here.
- if (!emitElemOpBase(setOp))
- return false;
- } else {
- JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
- if (!emitElemOpBase(setOp))
- return false;
+ // // [Super]
+ // // THIS KEY SUPERBASE VAL
+ // // [Other]
+ // // OBJ KEY VAL
+ PropertyByValue* elem = &target->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ ElemOpEmitter eoe(this,
+ ElemOpEmitter::Kind::SimpleAssignment,
+ isSuper
+ ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!eoe.skipObjAndKeyAndRhs()) {
+ return false;
+ }
+ if (!eoe.emitAssignment()) { // VAL
+ return false;
}
break;
}
@@ -5356,7 +4787,7 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri
}
// Pop the assigned value.
- if (!emit1(JSOP_POP))
+ if (!emit1(JSOP_POP)) // !STACK EMPTY!
return false;
}
@@ -5467,14 +4898,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) {
@@ -5684,7 +5115,7 @@ BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern)
initializer->isDirectRHSAnonFunction())
{
RootedAtom name(cx, pattern->name());
- if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None))
+ if (!setOrEmitSetFunName(initializer, name))
return false;
}
@@ -5847,12 +5278,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 +5333,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 +5373,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 +5425,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;
@@ -6338,25 +5769,33 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl,
if (!initializer && declList->isKind(PNK_VAR))
return true;
- auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) {
- if (!initializer) {
- // Lexical declarations are initialized to undefined without an
- // initializer.
- MOZ_ASSERT(declList->isKind(PNK_LET),
- "var declarations without initializers handled above, "
- "and const declarations must have initializers");
- return bce->emit1(JSOP_UNDEFINED);
+ NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) { // ENV?
+ return false;
+ }
+ if (!initializer) {
+ // Lexical declarations are initialized to undefined without an
+ // initializer.
+ MOZ_ASSERT(declList->isKind(PNK_LET),
+ "var declarations without initializers handled above, "
+ "and const declarations must have initializers");
+ if (!emit1(JSOP_UNDEFINED)) { // ENV? UNDEF
+ return false;
}
-
+ } else {
MOZ_ASSERT(initializer);
- return bce->emitInitializer(initializer, decl);
- };
-
- if (!emitInitializeName(decl, emitRhs))
+ if (!emitInitializer(initializer, decl)) { // ENV? V
+ return false;
+ }
+ }
+ if (!noe.emitAssignment()) { // V
+ return false;
+ }
+ if (!emit1(JSOP_POP)) { //
return false;
+ }
- // Pop the RHS.
- return emit1(JSOP_POP);
+ return true;
}
static bool
@@ -6377,81 +5816,104 @@ EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset)
}
bool
-BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs)
+BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
{
+ bool isCompound = compoundOp != JSOP_NOP;
+
// Name assignments are handled separately because choosing ops and when
// to emit BINDNAME is involved and should avoid duplication.
if (lhs->isKind(PNK_NAME)) {
- auto emitRhs = [op, lhs, rhs](BytecodeEmitter* bce, const NameLocation& lhsLoc,
- bool emittedBindOp)
- {
- // For compound assignments, first get the LHS value, then emit
- // the RHS and the op.
- if (op != JSOP_NOP) {
- if (lhsLoc.kind() == NameLocation::Kind::Dynamic) {
- // For dynamic accesses we can do better than a GETNAME
- // since the assignment already emitted a BINDNAME on the
- // top of the stack. As an optimization, use that to get
- // the name.
- if (!bce->emit1(JSOP_DUP))
- return false;
- if (!bce->emitAtomOp(lhs, JSOP_GETXPROP))
- return false;
- } else {
- if (!bce->emitGetNameAtLocation(lhs->name(), lhsLoc))
- return false;
- }
- }
+ NameOpEmitter noe(this,
+ lhs->name(),
+ isCompound
+ ? NameOpEmitter::Kind::CompoundAssignment
+ : NameOpEmitter::Kind::SimpleAssignment);
+ if (!noe.prepareForRhs()) { // ENV? VAL?
+ return false;
+ }
- // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
- // the top of the stack and we need to pick the right RHS value.
- if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1))
+ // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
+ // the top of the stack and we need to pick the right RHS value.
+ uint8_t offset = noe.emittedBindOp() ? 2 : 1;
+ if (!EmitAssignmentRhs(this, rhs, offset)) { // ENV? VAL? RHS
+ return false;
+ }
+ // Assign inferred function name, unless the lhs is parenthesized
+ if (rhs && rhs->isDirectRHSAnonFunction() && !lhs->isInParens()) {
+ MOZ_ASSERT(!isCompound);
+ RootedAtom name(cx, lhs->name());
+ if (!setOrEmitSetFunName(rhs, name)) { // ENV? VAL? RHS
return false;
-
- if (!lhs->isInParens() && op == JSOP_NOP && rhs && rhs->isDirectRHSAnonFunction()) {
- RootedAtom name(bce->cx, lhs->name());
- if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None))
- return false;
}
+ }
- // Emit the compound assignment op if there is one.
- if (op != JSOP_NOP && !bce->emit1(op))
+ // Emit the compound assignment op if there is one.
+ if (isCompound) {
+ if (!emit1(compoundOp)) { // ENV? VAL
return false;
+ }
+ }
+ if (!noe.emitAssignment()) { // VAL
+ return false;
+ }
- return true;
- };
-
- return emitSetName(lhs, emitRhs);
+ return true;
}
+ Maybe<PropOpEmitter> poe;
+ Maybe<ElemOpEmitter> eoe;
+
// Deal with non-name assignments.
- uint32_t atomIndex = (uint32_t) -1;
uint8_t offset = 1;
switch (lhs->getKind()) {
- case PNK_DOT:
- if (lhs->as<PropertyAccess>().isSuper()) {
- if (!emitSuperPropLHS(&lhs->as<PropertyAccess>().expression()))
+ case PNK_DOT: {
+ PropertyAccess* prop = &lhs->as<PropertyAccess>();
+ bool isSuper = prop->isSuper();
+ poe.emplace(this,
+ isCompound
+ ? PropOpEmitter::Kind::CompoundAssignment
+ : PropOpEmitter::Kind::SimpleAssignment,
+ isSuper
+ ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+ if (!poe->prepareForObj()) {
+ return false;
+ }
+ if (isSuper) {
+ ParseNode* base = &prop->expression();
+ if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE
return false;
+ }
+ // SUPERBASE is pushed onto THIS later in poe->emitGet below.
offset += 2;
} else {
- if (!emitTree(lhs->expr()))
+ if (!emitTree(&prop->expression())) // OBJ
return false;
offset += 1;
}
- if (!makeAtomIndex(lhs->pn_atom, &atomIndex))
- return false;
break;
+ }
case PNK_ELEM: {
- MOZ_ASSERT(lhs->isArity(PN_BINARY));
- EmitElemOption opt = op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign;
- if (lhs->as<PropertyByValue>().isSuper()) {
- if (!emitSuperElemOperands(lhs, opt))
- return false;
+ PropertyByValue* elem = &lhs->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ eoe.emplace(this,
+ isCompound
+ ? ElemOpEmitter::Kind::CompoundAssignment
+ : ElemOpEmitter::Kind::SimpleAssignment,
+ isSuper
+ ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elem, isSuper, *eoe)) { // [Super]
+ // // THIS KEY
+ // // [Other]
+ // // OBJ KEY
+ return false;
+ }
+ if (isSuper) {
+ // SUPERBASE is pushed onto KEY in eoe->emitGet below.
offset += 3;
} else {
- if (!emitElemOperands(lhs, opt))
- return false;
offset += 2;
}
break;
@@ -6476,42 +5938,23 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs)
MOZ_ASSERT(0);
}
- if (op != JSOP_NOP) {
+ if (isCompound) {
MOZ_ASSERT(rhs);
switch (lhs->getKind()) {
case PNK_DOT: {
- JSOp getOp;
- if (lhs->as<PropertyAccess>().isSuper()) {
- if (!emit1(JSOP_DUP2))
- return false;
- getOp = JSOP_GETPROP_SUPER;
- } else {
- if (!emit1(JSOP_DUP))
- return false;
- bool isLength = (lhs->pn_atom == cx->names().length);
- getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP;
- }
- if (!emitIndex32(getOp, atomIndex))
+ PropertyAccess* prop = &lhs->as<PropertyAccess>();
+ if (!poe->emitGet(prop->nameAtom())) { // [Super]
+ // // THIS SUPERBASE PROP
+ // // [Other]
+ // // OBJ PROP
return false;
+ }
break;
}
case PNK_ELEM: {
- JSOp elemOp;
- if (lhs->as<PropertyByValue>().isSuper()) {
- if (!emitDupAt(2))
- return false;
- if (!emitDupAt(2))
- return false;
- if (!emitDupAt(2))
- return false;
- elemOp = JSOP_GETELEM_SUPER;
- } else {
- if (!emit1(JSOP_DUP2))
- return false;
- elemOp = JSOP_GETELEM;
- }
- if (!emitElemOpBase(elemOp))
+ if (!eoe->emitGet()) { // KEY THIS OBJ ELEM
return false;
+ }
break;
}
case PNK_CALL:
@@ -6525,36 +5968,66 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs)
}
}
- if (!EmitAssignmentRhs(this, rhs, offset))
+ switch (lhs->getKind()) {
+ case PNK_DOT:
+ if (!poe->prepareForRhs()) { // [Simple,Super]
+ // // THIS SUPERBASE
+ // // [Simple,Other]
+ // // OBJ
+ // // [Compound,Super]
+ // // THIS SUPERBASE PROP
+ // // [Compound,Other]
+ // // OBJ PROP
+ return false;
+ }
+ break;
+ case PNK_ELEM:
+ if (!eoe->prepareForRhs()) { // [Simple,Super]
+ // // THIS KEY SUPERBASE
+ // // [Simple,Other]
+ // // OBJ KEY
+ // // [Compound,Super]
+ // // THIS KEY SUPERBASE ELEM
+ // // [Compound,Other]
+ // // OBJ KEY ELEM
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!EmitAssignmentRhs(this, rhs, offset)) // ... VAL? RHS
return false;
/* If += etc., emit the binary operator with a source note. */
- if (op != JSOP_NOP) {
+ if (isCompound) {
if (!newSrcNote(SRC_ASSIGNOP))
return false;
- if (!emit1(op))
+ if (!emit1(compoundOp)) // ... VAL
return false;
}
/* Finally, emit the specialized assignment bytecode. */
switch (lhs->getKind()) {
case PNK_DOT: {
- JSOp setOp = lhs->as<PropertyAccess>().isSuper() ?
- (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) :
- (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP);
- if (!emitIndexOp(setOp, atomIndex))
+ PropertyAccess* prop = &lhs->as<PropertyAccess>();
+ if (!poe->emitAssignment(prop->nameAtom())) { // VAL
return false;
+ }
+
+ poe.reset();
break;
}
case PNK_CALL:
// We threw above, so nothing to do here.
break;
case PNK_ELEM: {
- JSOp setOp = lhs->as<PropertyByValue>().isSuper() ?
- sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER :
- sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
- if (!emit1(setOp))
+ if (!eoe->emitAssignment()) { // VAL
return false;
+ }
+
+ eoe.reset();
break;
}
case PNK_ARRAY:
@@ -6931,7 +6404,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 +6413,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 +6425,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 +6675,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
@@ -7358,26 +6835,30 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(ParseNode* forHead)
target = parser->handler.singleBindingFromDeclaration(target);
if (target->isKind(PNK_NAME)) {
- auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
- bool emittedBindOp)
- {
- if (emittedBindOp) {
- // Per-iteration initialization in for-in/of loops computes the
- // iteration value *before* initializing. Thus the
- // initializing value may be buried under a bind-specific value
- // on the stack. Swap it to the top of the stack.
- MOZ_ASSERT(bce->stackDepth >= 2);
- return bce->emit1(JSOP_SWAP);
+ NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+ if (noe.emittedBindOp()) {
+ // Per-iteration initialization in for-in/of loops computes the
+ // iteration value *before* initializing. Thus the initializing
+ // value may be buried under a bind-specific value on the stack.
+ // Swap it to the top of the stack.
+ MOZ_ASSERT(stackDepth >= 2);
+ if (!emit1(JSOP_SWAP)) {
+ return false;
}
-
- // In cases of emitting a frame slot or environment slot,
- // nothing needs be done.
- MOZ_ASSERT(bce->stackDepth >= 1);
- return true;
- };
+ } else {
+ // In cases of emitting a frame slot or environment slot,
+ // nothing needs be done.
+ MOZ_ASSERT(stackDepth >= 1);
+ }
+ if (!noe.emitAssignment()) {
+ return false;
+ }
// The caller handles removing the iteration value from the stack.
- return emitInitializeName(target, emitSwapScopeAndRhs);
+ return true;
}
MOZ_ASSERT(!target->isKind(PNK_ASSIGN),
@@ -7586,12 +7067,16 @@ BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitte
if (!updateSourceCoordNotes(decl->pn_pos.begin))
return false;
- auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) {
- return bce->emitInitializer(initializer, decl);
- };
-
- if (!emitInitializeName(decl, emitRhs))
+ NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+ if (!emitInitializer(initializer, decl)) {
return false;
+ }
+ if (!noe.emitAssignment()) {
+ return false;
+ }
// Pop the initializer.
if (!emit1(JSOP_POP))
@@ -8241,12 +7726,6 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
// definitions are seen for the second time, we need to emit the
// assignment that assigns the function to the outer 'var' binding.
if (funbox->isAnnexB) {
- auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) {
- // The RHS is the value of the lexically bound name in the
- // innermost scope.
- return bce->emitGetName(name);
- };
-
// Get the location of the 'var' binding in the body scope. The
// name must be found, else there is a bug in the Annex B handling
// in Parser.
@@ -8268,7 +7747,14 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
sc->asFunctionBox()->hasParameterExprs));
}
- if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false))
+ NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+ if (!emitGetName(name)) {
+ return false;
+ }
+ if (!noe.emitAssignment())
return false;
if (!emit1(JSOP_POP))
return false;
@@ -8427,18 +7913,22 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
// For functions nested within functions and blocks, make a lambda and
// initialize the binding name of the function in the current scope.
- bool isAsync = funbox->isAsync();
- bool isStarGenerator = funbox->isStarGenerator();
- auto emitLambda = [index, isAsync, isStarGenerator](BytecodeEmitter* bce,
- const NameLocation&, bool) {
- if (isAsync) {
- return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
- /* isArrow = */ false, isStarGenerator);
+ NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+ if (funbox->isAsync()) {
+ if (!emitAsyncWrapper(index, /* needsHomeObject = */ false,
+ /* isArrow = */ false, funbox->isStarGenerator()))
+ {
+ return false;
}
- return bce->emitIndexOp(JSOP_LAMBDA, index);
- };
-
- if (!emitInitializeName(name, emitLambda))
+ } else {
+ if (!emitIndexOp(JSOP_LAMBDA, index)) {
+ return false;
+ }
+ }
+ if (!noe.emitAssignment())
return false;
if (!emit1(JSOP_POP))
return false;
@@ -8694,10 +8184,26 @@ BytecodeEmitter::emitGetFunctionThis(ParseNode* pn)
MOZ_ASSERT(pn->isKind(PNK_NAME));
MOZ_ASSERT(pn->name() == cx->names().dotThis);
- if (!emitTree(pn))
- return false;
- if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS))
+ return emitGetFunctionThis(Some(pn->pn_pos.begin));
+}
+
+bool
+BytecodeEmitter::emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset)
+{
+ if (offset) {
+ if (!updateLineNumberNotes(*offset)) {
+ return false;
+ }
+ }
+
+ if (!emitGetName(cx->names().dotThis)) { // THIS
return false;
+ }
+ if (sc->needsThisTDZChecks()) {
+ if (!emit1(JSOP_CHECKTHIS)) { // THIS
+ return false;
+ }
+ }
return true;
}
@@ -8706,7 +8212,7 @@ bool
BytecodeEmitter::emitGetThisForSuperBase(ParseNode* pn)
{
MOZ_ASSERT(pn->isKind(PNK_SUPERBASE));
- return emitGetFunctionThis(pn->pn_kid);
+ return emitGetFunctionThis(pn->pn_kid); // THIS
}
bool
@@ -8715,13 +8221,13 @@ BytecodeEmitter::emitThisLiteral(ParseNode* pn)
MOZ_ASSERT(pn->isKind(PNK_THIS));
if (ParseNode* thisName = pn->pn_kid)
- return emitGetFunctionThis(thisName);
+ return emitGetFunctionThis(thisName); // THIS
if (sc->thisBinding() == ThisBinding::Module)
- return emit1(JSOP_UNDEFINED);
+ return emit1(JSOP_UNDEFINED); // UNDEF
MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global);
- return emit1(JSOP_GLOBALTHIS);
+ return emit1(JSOP_GLOBALTHIS); // THIS
}
bool
@@ -8983,8 +8489,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 +8545,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 +8564,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 +8576,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 +8601,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;
@@ -9311,7 +8817,7 @@ BytecodeEmitter::emitDeleteName(ParseNode* node)
ParseNode* nameExpr = node->pn_kid;
MOZ_ASSERT(nameExpr->isKind(PNK_NAME));
- return emitAtomOp(nameExpr, JSOP_DELNAME);
+ return emitAtomOp(nameExpr->pn_atom, JSOP_DELNAME);
}
bool
@@ -9320,21 +8826,41 @@ BytecodeEmitter::emitDeleteProperty(ParseNode* node)
MOZ_ASSERT(node->isKind(PNK_DELETEPROP));
MOZ_ASSERT(node->isArity(PN_UNARY));
- ParseNode* propExpr = node->pn_kid;
+ PropertyAccess* propExpr = &node->pn_kid->as<PropertyAccess>();
MOZ_ASSERT(propExpr->isKind(PNK_DOT));
+ PropOpEmitter poe(this,
+ PropOpEmitter::Kind::Delete,
+ propExpr->as<PropertyAccess>().isSuper()
+ ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+
if (propExpr->as<PropertyAccess>().isSuper()) {
- // Still have to calculate the base, even though we are are going
- // to throw unconditionally, as calculating the base could also
- // throw.
- if (!emit1(JSOP_SUPERBASE))
+ // The expression |delete super.foo;| has to evaluate |super.foo|,
+ // which could throw if |this| hasn't yet been set by a |super(...)|
+ // call or the super-base is not an object, before throwing a
+ // ReferenceError for attempting to delete a super-reference.
+ ParseNode* base = &propExpr->expression();
+ if (!emitGetThisForSuperBase(base)) { // THIS
+ return false;
+ }
+ } else {
+ if (!poe.prepareForObj()) {
+ return false;
+ }
+ if (!emitPropLHS(propExpr)) { // OBJ
return false;
+ }
+ }
- return emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER);
+ if (!poe.emitDelete(propExpr->nameAtom())) { // [Super]
+ // // THIS
+ // // [Other]
+ // // SUCCEEDED
+ return false;
}
- JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
- return emitPropOp(propExpr, delOp);
+ return true;
}
bool
@@ -9343,27 +8869,48 @@ BytecodeEmitter::emitDeleteElement(ParseNode* node)
MOZ_ASSERT(node->isKind(PNK_DELETEELEM));
MOZ_ASSERT(node->isArity(PN_UNARY));
- ParseNode* elemExpr = node->pn_kid;
+ PropertyByValue* elemExpr = &node->pn_kid->as<PropertyByValue>();
MOZ_ASSERT(elemExpr->isKind(PNK_ELEM));
- if (elemExpr->as<PropertyByValue>().isSuper()) {
- // Still have to calculate everything, even though we're gonna throw
- // since it may have side effects
- if (!emitTree(elemExpr->pn_right))
+ bool isSuper = elemExpr->isSuper();
+ ElemOpEmitter eoe(this,
+ ElemOpEmitter::Kind::Delete,
+ isSuper
+ ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (isSuper) {
+ // The expression |delete super[foo];| has to evaluate |super[foo]|,
+ // which could throw if |this| hasn't yet been set by a |super(...)|
+ // call, or trigger side-effects when evaluating ToPropertyKey(foo),
+ // or also throw when the super-base is not an object, before throwing
+ // a ReferenceError for attempting to delete a super-reference.
+ if (!eoe.prepareForObj()) { //
return false;
+ }
- if (!emit1(JSOP_SUPERBASE))
+ ParseNode* base = &elemExpr->expression();
+ if (!emitGetThisForSuperBase(base)) { // THIS
+ return false;
+ }
+ if (!eoe.prepareForKey()) { // THIS
return false;
- if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER))
+ }
+ if (!emitTree(&elemExpr->key())) { // THIS KEY
return false;
-
- // Another wrinkle: Balance the stack from the emitter's point of view.
- // Execution will not reach here, as the last bytecode threw.
- return emit1(JSOP_POP);
+ }
+ } else {
+ if (!emitElemObjAndKey(elemExpr, false, eoe)) { // OBJ KEY
+ return false;
+ }
+ }
+ if (!eoe.emitDelete()) { // [Super]
+ // // THIS
+ // // [Other]
+ // // SUCCEEDED
+ return false;
}
- JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
- return emitElemOp(elemExpr, delOp);
+ return true;
}
bool
@@ -9461,7 +9008,7 @@ BytecodeEmitter::emitDeletePropertyInOptChain(
}
JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
- if (!emitAtomOp(propExpr, delOp)) {
+ if (!emitAtomOp(propExpr->pn_atom, delOp)) {
return false;
}
@@ -9641,28 +9188,23 @@ BytecodeEmitter::emitSelfHostedAllowContentIter(ParseNode* pn)
}
bool
-BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result)
+BytecodeEmitter::isRestParameter(ParseNode* pn)
{
- if (!sc->isFunctionBox()) {
- *result = false;
- return true;
- }
+ if (!sc->isFunctionBox())
+ return false;
FunctionBox* funbox = sc->asFunctionBox();
RootedFunction fun(cx, funbox->function());
- if (!funbox->hasRest()) {
- *result = false;
- return true;
- }
+ if (!funbox->hasRest())
+ return false;
if (!pn->isKind(PNK_NAME)) {
if (emitterMode == BytecodeEmitter::SelfHosting && pn->isKind(PNK_CALL)) {
ParseNode* pn2 = pn->pn_head;
if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentIter)
- return isRestParameter(pn2->pn_next, result);
+ return isRestParameter(pn2->pn_next);
}
- *result = false;
- return true;
+ return false;
}
JSAtom* name = pn->name();
@@ -9674,12 +9216,11 @@ BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result)
// used: `function f(...[]) {}`.
JSAtom* paramName =
bindings->trailingNames[bindings->nonPositionalFormalStart - 1].name();
- *result = paramName && name == paramName;
- return true;
+ return paramName && name == paramName;
}
}
- return true;
+ return false;
}
bool
@@ -9696,11 +9237,7 @@ BytecodeEmitter::emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitte
// skip spread operation and pass it directly to spread call operation.
// See the comment in OptimizeSpreadCall in Interpreter.cpp for the
// optimizable conditons.
- bool result = false;
- if (!isRestParameter(arg0, &result))
- return false;
-
- if (!result) {
+ if (!isRestParameter(arg0)) {
*emitted = false;
return true;
}
@@ -9733,14 +9270,14 @@ bool
BytecodeEmitter::emitOptionalCalleeAndThis(
ParseNode* callNode,
ParseNode* calleeNode,
- bool isCall,
+ CallOrNewEmitter& cone,
OptionalEmitter& oe)
{
JS_CHECK_RECURSION(cx, return false);
switch (calleeNode->getKind()) {
case PNK_NAME: {
- if (!emitGetName(calleeNode, isCall)) {
+ if (!cone.emitNameCallee(calleeNode->name())) { // CALLEE THIS
return false;
}
break;
@@ -9748,7 +9285,9 @@ BytecodeEmitter::emitOptionalCalleeAndThis(
case PNK_OPTDOT: {
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
OptionalPropertyAccess* prop = &calleeNode->as<OptionalPropertyAccess>();
- if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) {
+ bool isSuper = false;
+ PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
+ if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) {
return false;
}
break;
@@ -9756,7 +9295,9 @@ BytecodeEmitter::emitOptionalCalleeAndThis(
case PNK_DOT: {
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
PropertyAccess* prop = &calleeNode->as<PropertyAccess>();
- if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) {
+ bool isSuper = prop->isSuper();
+ PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
+ if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) {
return false;
}
break;
@@ -9764,7 +9305,9 @@ BytecodeEmitter::emitOptionalCalleeAndThis(
case PNK_OPTELEM: {
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
OptionalPropertyByValue* elem = &calleeNode->as<OptionalPropertyByValue>();
- if (!emitOptionalElemExpression(elem, oe, calleeNode, isCall)) {
+ bool isSuper = false;
+ ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) {
return false;
}
break;
@@ -9772,51 +9315,40 @@ BytecodeEmitter::emitOptionalCalleeAndThis(
case PNK_ELEM: {
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
PropertyByValue* elem = &calleeNode->as<PropertyByValue>();
- if (!emitOptionalElemExpression(elem, oe, calleeNode, isCall)) {
+ bool isSuper = elem->isSuper();
+ ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) {
return false;
}
break;
}
case PNK_FUNCTION: {
- /*
- * Top level lambdas which are immediately invoked should be
- * treated as only running once. Every time they execute we will
- * create new types and scripts for their contents, to increase
- * the quality of type information within them and enable more
- * backend optimizations. Note that this does not depend on the
- * lambda being invoked at most once (it may be named or be
- * accessed via foo.caller indirection), as multiple executions
- * will just cause the inner scripts to be repeatedly cloned.
- */
- MOZ_ASSERT(!emittingRunOnceLambda);
- if (checkRunOnceContext()) {
- emittingRunOnceLambda = true;
- if (!emitOptionalTree(calleeNode, oe)) {
- return false;
- }
- emittingRunOnceLambda = false;
- } else {
- if (!emitOptionalTree(calleeNode, oe)) {
- return false;
- }
+ if (!cone.prepareForFunctionCallee()) {
+ return false;
+ }
+ if (!emitOptionalTree(calleeNode, oe)) {
+ // [stack] CALLEE
+ return false;
}
- isCall = false;
break;
}
case PNK_OPTCHAIN: {
- return emitCalleeAndThisForOptionalChain(calleeNode, callNode, isCall);
+ return emitCalleeAndThisForOptionalChain(calleeNode, callNode, cone);
}
default: {
MOZ_RELEASE_ASSERT(calleeNode->getKind() != PNK_SUPERBASE);
+ if (!cone.prepareForOtherCallee()) {
+ return false;
+ }
if (!emitOptionalTree(calleeNode, oe)) {
+ // [stack] CALLEE
return false;
}
- isCall = false; /* trigger JSOP_UNDEFINED after */
break;
}
}
- if (!emitCallOrNewThis(callNode, isCall)) {
+ if (!cone.emitThis()) {
return false;
}
@@ -9840,10 +9372,21 @@ BytecodeEmitter::emitOptionalCall(
OptionalEmitter& oe,
ValueUsage valueUsage)
{
- bool isCall = true;
ParseNode* calleeNode = callNode->pn_head;
+ bool isCall = true;
+ bool isSpread = IsSpreadOp(callNode->getOp());
+ ParseNode* firstArg = nullptr;
+ uint32_t argc = GetCallArgsAndCount(callNode, &firstArg);
+ JSOp op = callNode->getOp();
+
+ CallOrNewEmitter cone(this, op,
+ isSpread && (argc == 1) &&
+ isRestParameter(firstArg->pn_kid)
+ ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest
+ : CallOrNewEmitter::ArgumentsKind::Other,
+ valueUsage);
- if (!emitOptionalCalleeAndThis(callNode, calleeNode, isCall, oe)) {
+ if (!emitOptionalCalleeAndThis(callNode, calleeNode, cone, oe)) {
// [stack] CALLEE THIS
return false;
}
@@ -9855,9 +9398,91 @@ BytecodeEmitter::emitOptionalCall(
}
}
- if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) {
+ if (!emitArguments(firstArg, argc, /* isCall = */ true, isSpread, cone)) {
+ // [stack] CALLEE THIS ARGS...
+ return false;
+ }
+
+ ParseNode* coordNode = getCoordNode(callNode, calleeNode, firstArg);
+ if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) {
+ // [stack] RVAL
+ return false;
+ }
+
+ return true;
+}
+
+ParseNode* BytecodeEmitter::getCoordNode(ParseNode* pn,
+ ParseNode* calleeNode,
+ ParseNode* firstArg) {
+ ParseNode* coordNode = pn;
+ if (pn->isOp(JSOP_CALL) || pn->isOp(JSOP_SPREADCALL) || pn->isOp(JSOP_FUNCALL) ||
+ pn->isOp(JSOP_FUNAPPLY)) {
+ // Default to using the location of the `(` itself.
+ // obj[expr]() // expression
+ // ^ // column coord
+ if (firstArg) {
+ // XXX In our version, firstArg points to the first argument and may be null if there are none
+ coordNode = firstArg;
+ }
+
+ switch (calleeNode->getKind()) {
+ case PNK_DOT:
+ // Use the position of a property access identifier.
+ //
+ // obj().aprop() // expression
+ // ^ // column coord
+ //
+ // Note: Because of the constant folding logic in FoldElement,
+ // this case also applies for constant string properties.
+ //
+ // obj()['aprop']() // expression
+ // ^ // column coord
+ coordNode = calleeNode->pn_right;
+ break;
+ case PNK_NAME:
+ // Use the start of callee names.
+ coordNode = calleeNode;
+ break;
+ default:
+ break;
+ }
+ }
+ return coordNode;
+}
+
+bool
+BytecodeEmitter::emitArguments(ParseNode* firstArgNode, uint32_t argc, bool isCall, bool isSpread,
+ CallOrNewEmitter& cone)
+{
+ if (argc >= ARGC_LIMIT) {
+ parser->tokenStream.reportError(isCall
+ ? JSMSG_TOO_MANY_FUN_ARGS
+ : JSMSG_TOO_MANY_CON_ARGS);
return false;
}
+ if (!isSpread) {
+ if (!cone.prepareForNonSpreadArguments()) { // CALLEE THIS
+ return false;
+ }
+ for (ParseNode* arg = firstArgNode; arg; arg = arg->pn_next) {
+ if (!emitTree(arg)) {
+ return false;
+ }
+ }
+ } else {
+ if (cone.wantSpreadOperand()) {
+ if (!emitTree(firstArgNode->pn_kid)) { // CALLEE THIS ARG0
+ return false;
+ }
+ }
+ if (!cone.emitSpreadArgumentsTest()) { // CALLEE THIS
+ return false;
+ }
+ if (!emitArray(firstArgNode, argc, JSOP_SPREADCALLARRAY)) { // CALLEE THIS ARR
+ return false;
+ }
+ }
return true;
}
@@ -9884,10 +9509,13 @@ BytecodeEmitter::emitCallOrNew(
*/
ParseNode* calleeNode = callNode->pn_head;
bool isCall = callNode->isKind(PNK_CALL) || callNode->isKind(PNK_TAGGED_TEMPLATE);
+ bool isSpread = IsSpreadOp(callNode->getOp());
+ ParseNode* firstArg = nullptr;
+ uint32_t argc = GetCallArgsAndCount(callNode, &firstArg);
if (calleeNode->isKind(PNK_NAME) &&
emitterMode == BytecodeEmitter::SelfHosting &&
- !IsSpreadOp(callNode->getOp())) {
+ !isSpread) {
// Calls to "forceInterpreter", "callFunction",
// "callContentFunction", or "resumeGenerator" in self-hosted
// code generate inline bytecode.
@@ -9907,12 +9535,24 @@ BytecodeEmitter::emitCallOrNew(
// Fall through.
}
- if (!emitCalleeAndThis(callNode, calleeNode, isCall)) {
+ JSOp op = callNode->getOp();
+ CallOrNewEmitter cone(this, op,
+ isSpread && (argc == 1) &&
+ isRestParameter(firstArg->pn_kid)
+ ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest
+ : CallOrNewEmitter::ArgumentsKind::Other,
+ valueUsage);
+ if (!emitCalleeAndThis(callNode, calleeNode, cone)) { // CALLEE THIS
return false;
}
+ if (!emitArguments(firstArg, argc, isCall, isSpread, cone)) {
+ return false; // CALLEE THIS ARGS...
+ }
- if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) {
- return false;
+ ParseNode* coordNode = getCoordNode(callNode, calleeNode, firstArg);
+
+ if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) {
+ return false; // RVAL
}
return true;
@@ -9922,209 +9562,85 @@ bool
BytecodeEmitter::emitCalleeAndThis(
ParseNode* callNode,
ParseNode* calleeNode,
- bool isCall)
+ CallOrNewEmitter& cone)
{
switch (calleeNode->getKind()) {
- case PNK_NAME:
- if (!emitGetName(calleeNode, isCall)) {
+ case PNK_NAME: {
+ if (!cone.emitNameCallee(calleeNode->name())) { // CALLEE THIS
return false;
}
break;
- case PNK_DOT:
+ }
+ case PNK_DOT: {
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
- if (calleeNode->as<PropertyAccess>().isSuper()) {
- if (!emitSuperPropOp(calleeNode, JSOP_GETPROP_SUPER, isCall)) {
+ PropertyAccess* prop = &calleeNode->as<PropertyAccess>();
+ bool isSuper = prop->isSuper();
+ PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
+ if (!poe.prepareForObj()) {
+ return false;
+ }
+ if (isSuper) {
+ ParseNode* base = &prop->expression();
+ if (!emitGetThisForSuperBase(base)) { // THIS
return false;
}
} else {
- if (!emitPropOp(calleeNode, isCall ? JSOP_CALLPROP : JSOP_GETPROP)) {
+ if (!emitPropLHS(prop)) { // OBJ
return false;
}
}
+ if (!poe.emitGet(prop->nameAtom())) { // CALLEE THIS?
+ return false;
+ }
break;
- case PNK_ELEM:
+ }
+ case PNK_ELEM: {
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
- if (calleeNode->as<PropertyByValue>().isSuper()) {
- if (!emitSuperElemOp(calleeNode, JSOP_GETELEM_SUPER, isCall)) {
- return false;
- }
- } else {
- if (!emitElemOp(calleeNode, isCall ? JSOP_CALLELEM : JSOP_GETELEM)) {
- return false;
- }
- if (isCall) {
- if (!emit1(JSOP_SWAP)) {
- return false;
- }
- }
+ PropertyByValue* elem = &calleeNode->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+ if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super]
+ // // THIS? THIS KEY
+ // // [needsThis,Other]
+ // // OBJ? OBJ KEY
+ return false;
+ }
+ if (!eoe.emitGet()) { // CALLEE? THIS
+ return false;
}
break;
+ }
case PNK_FUNCTION:
- /*
- * Top level lambdas which are immediately invoked should be
- * treated as only running once. Every time they execute we will
- * create new types and scripts for their contents, to increase
- * the quality of type information within them and enable more
- * backend optimizations. Note that this does not depend on the
- * lambda being invoked at most once (it may be named or be
- * accessed via foo.caller indirection), as multiple executions
- * will just cause the inner scripts to be repeatedly cloned.
- */
- MOZ_ASSERT(!emittingRunOnceLambda);
- if (checkRunOnceContext()) {
- emittingRunOnceLambda = true;
- if (!emitTree(calleeNode)) {
- return false;
- }
- emittingRunOnceLambda = false;
- } else {
- if (!emitTree(calleeNode)) {
- return false;
- }
+ if (!cone.prepareForFunctionCallee()) {
+ return false;
+ }
+ if (!emitTree(calleeNode)) { // CALLEE
+ return false;
}
- isCall = false;
break;
case PNK_SUPERBASE:
MOZ_ASSERT(callNode->isKind(PNK_SUPERCALL));
MOZ_ASSERT(parser->handler.isSuperBase(calleeNode));
- if (!emit1(JSOP_SUPERFUN)) {
+ if (!cone.emitSuperCallee()) { // CALLEE THIS
return false;
}
break;
case PNK_OPTCHAIN:
- return emitCalleeAndThisForOptionalChain(calleeNode, callNode, isCall);
+ return emitCalleeAndThisForOptionalChain(calleeNode, callNode, cone);
default:
- if (!emitTree(calleeNode)) {
+ if (!cone.prepareForOtherCallee()) {
return false;
}
- isCall = false; /* trigger JSOP_UNDEFINED after */
- break;
- }
-
- if (!emitCallOrNewThis(callNode, isCall)) {
- return false;
- }
-
- return true;
-}
-
-// NOTE: Equivalent to CallOrNewEmitter::emitThis
-bool
-BytecodeEmitter::emitCallOrNewThis(
- ParseNode* callNode,
- bool isCall)
-{
- // Emit room for |this|.
- if (!isCall) {
- JSOp opForEmit;
- if (IsNewOp(callNode->getOp())) {
- opForEmit = JSOP_IS_CONSTRUCTING;
- } else {
- opForEmit = JSOP_UNDEFINED;
- }
- if (!emit1(opForEmit)) {
+ if (!emitTree(calleeNode)) {
return false;
}
+ break;
}
- return true;
-}
-
-// NOTE: The following function is equivalent to CallOrNewEmitter::emitEnd
-// and BytecodeEmitter::emitArguments in a future refactor
-bool
-BytecodeEmitter::emitCallOrNewArgumentsAndEnd(
- ParseNode* callNode,
- ParseNode* calleeNode,
- bool isCall,
- ValueUsage valueUsage)
-{
- uint32_t argumentCount = callNode->pn_count - 1;
- if (argumentCount >= ARGC_LIMIT) {
- parser->tokenStream.reportError(isCall
- ? JSMSG_TOO_MANY_FUN_ARGS
- : JSMSG_TOO_MANY_CON_ARGS);
+ if (!cone.emitThis()) { // CALLEE THIS
return false;
}
- JSOp op = callNode->getOp();
- bool isSpread = IsSpreadOp(op);
-
- /*
- * Emit code for each argument in order, then emit the JSOP_*CALL or
- * JSOP_NEW bytecode with a two-byte immediate telling how many args
- * were pushed on the operand stack.
- */
- ParseNode* argumentNode = calleeNode->pn_next;
- if (!isSpread) {
- while (argumentNode) {
- if (!emitTree(argumentNode)) {
- return false;
- }
- argumentNode = argumentNode->pn_next;
- }
- } else {
- JumpList jmp;
- bool optCodeEmitted = false;
- if (argumentCount == 1) {
- if (!emitOptimizeSpread(argumentNode->pn_kid, &jmp,
- &optCodeEmitted)) {
- return false;
- }
- }
-
- if (!emitArray(argumentNode, argumentCount, JSOP_SPREADCALLARRAY)) {
- return false;
- }
-
- if (optCodeEmitted) {
- if (!emitJumpTargetAndPatch(jmp)) {
- return false;
- }
- }
- }
-
- if (IsNewOp(op)) {
- if (callNode->isKind(PNK_SUPERCALL)) {
- if (!emit1(JSOP_NEWTARGET)) {
- return false;
- }
- } else {
- // Repush the callee as new.target
- uint32_t finalArgumentCount = argumentCount + 1;
- if (isSpread) {
- finalArgumentCount = 2;
- }
- if (!emitDupAt(finalArgumentCount)) {
- return false;
- }
- }
- }
-
- if (!isSpread) {
- JSOp callOp = op;
- if (callOp == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
- callOp = JSOP_CALL_IGNORES_RV;
- }
- if (!emitCall(callOp, argumentCount, callNode)) {
- return false;
- }
- checkTypeSet(callOp);
- } else {
- if (!emit1(op)) {
- return false;
- }
- checkTypeSet(op);
- }
-
- if (IsEvalOp(op)) {
- uint32_t lineNum =
- parser->tokenStream.srcCoords.lineNum(callNode->pn_pos.begin);
- if (!emitUint32Operand(JSOP_LINENO, lineNum)) {
- return false;
- }
- }
-
return true;
}
@@ -10288,7 +9804,7 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional,
if (!emitTree(&conditional.condition()))
return false;
- IfThenElseEmitter ifThenElse(this);
+ IfEmitter ifThenElse(this);
if (!ifThenElse.emitCond())
return false;
@@ -10773,14 +10289,16 @@ BytecodeEmitter::emitFunctionFormalParametersAndBody(ParseNode *pn)
MOZ_ASSERT(name != cx->names().dotThis &&
name != cx->names().dotGenerator);
- NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope);
- auto emitRhs = [&name, &paramLoc](BytecodeEmitter* bce,
- const NameLocation&, bool)
- {
- return bce->emitGetNameAtLocation(name, paramLoc);
- };
+ NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
- if (!emitInitializeName(name, emitRhs))
+ NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope);
+ if (!emitGetNameAtLocation(name, paramLoc)) {
+ return false;
+ }
+ if (!noe.emitAssignment())
return false;
if (!emit1(JSOP_POP))
return false;
@@ -10921,35 +10439,27 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn)
if (!emit1(JSOP_POP))
return false;
- } else {
+ } else if (hasParameterExprs || isRest) {
RootedAtom paramName(cx, bindingElement->name());
NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope);
+ NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
if (hasParameterExprs) {
- auto emitRhs = [argSlot, initializer, isRest](BytecodeEmitter* bce,
- const NameLocation&, bool)
- {
- // If we had an initializer or a rest parameter, the value is
- // already on the stack.
- if (!initializer && !isRest)
- return bce->emitArgOp(JSOP_GETARG, argSlot);
- return true;
- };
-
- if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs, true))
- return false;
- if (!emit1(JSOP_POP))
- return false;
- } else if (isRest) {
- // The rest value is already on top of the stack.
- auto nop = [](BytecodeEmitter*, const NameLocation&, bool) {
- return true;
- };
-
- if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true))
- return false;
- if (!emit1(JSOP_POP))
- return false;
+ // If we had an initializer or a rest parameter, the value is
+ // already on the stack.
+ if (!initializer && !isRest) {
+ if (!emitArgOp(JSOP_GETARG, argSlot))
+ return false;
+ }
+ }
+ if (!noe.emitAssignment()) {
+ return false;
+ }
+ if (!emit1(JSOP_POP)) {
+ return false;
}
}
@@ -10974,11 +10484,14 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames()
// call environment.
MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
- auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&, bool) {
- return bce->emit1(op);
- };
-
- if (!bce->emitInitializeName(name, emitInitial))
+ NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+ if (!bce->emit1(op)) {
+ return false;
+ }
+ if (!noe.emitAssignment())
return false;
if (!bce->emit1(JSOP_POP))
return false;
@@ -11061,14 +10574,21 @@ BytecodeEmitter::emitFunctionBody(ParseNode* funBody)
bool
BytecodeEmitter::emitLexicalInitialization(ParseNode* pn)
{
+ NameOpEmitter noe(this, pn->name(), NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+
// The caller has pushed the RHS to the top of the stack. Assert that the
// name is lexical and no BIND[G]NAME ops were emitted.
- auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc, bool emittedBindOp) {
- MOZ_ASSERT(loc.isLexical());
- MOZ_ASSERT(!emittedBindOp);
- return true;
- };
- return emitInitializeName(pn, assertLexical);
+ MOZ_ASSERT(noe.loc().isLexical());
+ MOZ_ASSERT(!noe.emittedBindOp());
+
+ if (!noe.emitAssignment()) {
+ return false;
+ }
+
+ return true;
}
// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
@@ -11453,25 +10973,52 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
}
break;
- case PNK_DOT:
- if (pn->as<PropertyAccess>().isSuper()) {
- if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER))
+ case PNK_DOT: {
+ PropertyAccess* prop = &pn->as<PropertyAccess>();
+ bool isSuper = prop->isSuper();
+ PropOpEmitter poe(this,
+ PropOpEmitter::Kind::Get,
+ isSuper
+ ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+ if (!poe.prepareForObj()) {
+ return false;
+ }
+ if (isSuper) {
+ ParseNode* base = &prop->expression();
+ if (!emitGetThisForSuperBase(base)) { // THIS
return false;
+ }
} else {
- if (!emitPropOp(pn, JSOP_GETPROP))
+ if (!emitPropLHS(prop)) { // OBJ
return false;
+ }
+ }
+ if (!poe.emitGet(prop->nameAtom())) { // PROP
+ return false;
}
break;
+ }
- case PNK_ELEM:
- if (pn->as<PropertyByValue>().isSuper()) {
- if (!emitSuperElemOp(pn, JSOP_GETELEM_SUPER))
- return false;
- } else {
- if (!emitElemOp(pn, JSOP_GETELEM))
- return false;
+ case PNK_ELEM: {
+ PropertyByValue* elem = &pn->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ ElemOpEmitter eoe(this,
+ ElemOpEmitter::Kind::Get,
+ isSuper
+ ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [Super]
+ // // THIS KEY
+ // // [Other]
+ // // OBJ KEY
+ return false;
+ }
+ if (!eoe.emitGet()) { // ELEM
+ return false;
}
break;
+ }
case PNK_NEW:
case PNK_TAGGED_TEMPLATE:
@@ -11567,7 +11114,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
case PNK_TEMPLATE_STRING:
case PNK_STRING:
- if (!emitAtomOp(pn, JSOP_STRING))
+ if (!emitAtomOp(pn->pn_atom, JSOP_STRING))
return false;
break;
@@ -11661,30 +11208,40 @@ BytecodeEmitter::emitOptionalTree(
switch (kind) {
case PNK_OPTDOT: {
OptionalPropertyAccess* prop = &pn->as<OptionalPropertyAccess>();
- if (!emitOptionalDotExpression(prop, oe, pn, false)) {
+ bool isSuper = false;
+ PropOpEmitter poe(this, PropOpEmitter::Kind::Get,
+ PropOpEmitter::ObjKind::Other);
+ if (!emitOptionalDotExpression(prop, poe, isSuper, oe))
return false;
- }
break;
}
case PNK_DOT: {
PropertyAccess* prop = &pn->as<PropertyAccess>();
- if (!emitOptionalDotExpression(prop, oe, pn, false)) {
+ bool isSuper = prop->isSuper();
+ PropOpEmitter poe(this, PropOpEmitter::Kind::Get,
+ isSuper ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+ if (!emitOptionalDotExpression(prop, poe, isSuper, oe))
return false;
- }
break;
}
case PNK_OPTELEM: {
OptionalPropertyByValue* elem = &pn->as<OptionalPropertyByValue>();
- if (!emitOptionalElemExpression(elem, oe, pn, false)) {
+ bool isSuper = false;
+ ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get,
+ ElemOpEmitter::ObjKind::Other);
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe))
return false;
- }
break;
}
case PNK_ELEM: {
PropertyByValue* elem = &pn->as<PropertyByValue>();
- if (!emitOptionalElemExpression(elem, oe, pn, false)) {
+ bool isSuper = elem->isSuper();
+ ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get,
+ isSuper ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe))
return false;
- }
break;
}
case PNK_CALL:
@@ -11744,7 +11301,7 @@ bool
BytecodeEmitter::emitCalleeAndThisForOptionalChain(
ParseNode* optionalChain,
ParseNode* callNode,
- bool isCall)
+ CallOrNewEmitter& cone)
{
ParseNode* calleeNode = optionalChain->pn_kid;
@@ -11752,7 +11309,7 @@ BytecodeEmitter::emitCalleeAndThisForOptionalChain(
// in isolation.
OptionalEmitter oe(this, stackDepth);
- if (!emitOptionalCalleeAndThis(callNode, calleeNode, isCall, oe)) {
+ if (!emitOptionalCalleeAndThis(callNode, calleeNode, cone, oe)) {
// [stack] CALLEE THIS
return false;
}
@@ -11801,12 +11358,14 @@ BytecodeEmitter::emitOptionalChain(
bool
BytecodeEmitter::emitOptionalDotExpression(
PropertyAccessBase* prop,
- OptionalEmitter& oe,
- ParseNode* calleeNode,
- bool isCall)
+ PropOpEmitter& poe,
+ bool isSuper,
+ OptionalEmitter& oe)
{
- bool isSuper = prop->is<PropertyAccess>() &&
- prop->as<PropertyAccess>().isSuper();
+ if (!poe.prepareForObj()) {
+ // [stack]
+ return false;
+ }
ParseNode* base = &prop->expression();
if (isSuper) {
@@ -11832,30 +11391,8 @@ BytecodeEmitter::emitOptionalDotExpression(
}
}
- // XXX emitSuperPropLHS, through emitSuperPropOp, contains a call to
- // emitGetThisForSuperBase, which we've already called early in this
- // function. However, modifying emitSuperPropLHS to prevent it from
- // calling emitGetThisForSuperBase will involve too many changes to
- // its callers. So, we duplicate their functionality here.
- //
- // emitPropOp, on the other hand, calls emitTree again, which is
- // unnecessary for our case.
- //
- // This should be equivalent to PropOpEmitter::emitGet
- if (isCall && !emit1(JSOP_DUP)) {
- return false;
- }
- JSOp opForEmit = isCall ? JSOP_CALLPROP : JSOP_GETPROP;
- if (isSuper) {
- if (!emit1(JSOP_SUPERBASE)) {
- return false;
- }
- opForEmit = JSOP_GETPROP_SUPER;
- }
- if (!emitAtomOp(calleeNode, opForEmit)) {
- return false;
- }
- if (isCall && !emit1(JSOP_SWAP)) {
+ if (!poe.emitGet(prop->nameAtom())) {
+ // [stack] PROP
return false;
}
@@ -11865,20 +11402,23 @@ BytecodeEmitter::emitOptionalDotExpression(
bool
BytecodeEmitter::emitOptionalElemExpression(
PropertyByValueBase* elem,
- OptionalEmitter& oe,
- ParseNode* calleeNode,
- bool isCall)
+ ElemOpEmitter& eoe,
+ bool isSuper,
+ OptionalEmitter& oe)
{
- bool isSuper = elem->is<PropertyByValue>() &&
- elem->as<PropertyByValue>().isSuper();
+ if (!eoe.prepareForObj()) {
+ // [stack]
+ return false;
+ }
+ ParseNode* base = &elem->expression();
if (isSuper) {
- if (!emitGetThisForSuperBase(calleeNode)) {
+ if (!emitGetThisForSuperBase(base)) {
// [stack] OBJ
return false;
}
} else {
- if (!emitOptionalTree(calleeNode->pn_left, oe)) {
+ if (!emitOptionalTree(base, oe)) {
// [stack] OBJ
return false;
}
@@ -11895,29 +11435,20 @@ BytecodeEmitter::emitOptionalElemExpression(
}
}
- // Note: the following conditional is more-or-less equivalent
- // to ElemOpEmitter::prepareForKey in a future refactor
- if (isCall) {
- if (!emit1(JSOP_DUP)) {
- return false;
- }
+ if (!eoe.prepareForKey()) {
+ // [stack] OBJ? OBJ
+ return false;
}
- if (!emitTree(calleeNode->pn_right)) {
+ if (!emitTree(&elem->key())) {
// [stack] OBJ? OBJ KEY
return false;
}
- // Note: the two (2) conditionals below are more-or-less
- // equivalent to ElemOpEmitter::emitGet in a future refactor
- if (!emitElemOpBase(isCall ? JSOP_CALLELEM : JSOP_GETELEM)) {
+ if (!eoe.emitGet()) {
+ // [stack] ELEM
return false;
}
- if (isCall) {
- if (!emit1(JSOP_SWAP)) {
- return false;
- }
- }
return true;
}
@@ -12159,7 +11690,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 +11701,7 @@ OptionalEmitter::emitJumpShortCircuit() {
return false;
}
- if (!ifEmitter.emitIf() /* emitThen() */) {
+ if (!ifEmitter.emitThen()) {
return false;
}
@@ -12204,7 +11735,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 +11746,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..47400658dc 100644
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -14,9 +14,11 @@
#include "jsscript.h"
#include "ds/InlineTable.h"
+#include "frontend/JumpList.h"
#include "frontend/Parser.h"
#include "frontend/SharedContext.h"
#include "frontend/SourceNotes.h"
+#include "frontend/ValueUsage.h"
#include "vm/Interpreter.h"
class OptionalEmitter;
@@ -118,69 +120,13 @@ 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 CallOrNewEmitter;
+class ElemOpEmitter;
+class PropOpEmitter;
+class TDZCheckCache;
struct MOZ_STACK_CLASS BytecodeEmitter
{
- class TDZCheckCache;
class NestableControl;
class EmitterScope;
@@ -500,6 +446,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
// JS stack, as measured from the top.
MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop);
+ // Helper to emit JSOP_POP or JSOP_POPN.
+ MOZ_MUST_USE bool emitPopN(unsigned n);
+
// Helper to emit JSOP_CHECKISOBJ.
MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind);
@@ -520,6 +469,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitThisLiteral(ParseNode* pn);
MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn);
+ MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset);
MOZ_MUST_USE bool emitGetThisForSuperBase(ParseNode* pn);
MOZ_MUST_USE bool emitSetThis(ParseNode* pn);
MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn();
@@ -533,6 +483,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
void patchJumpsToTarget(JumpList jump, JumpTarget target);
MOZ_MUST_USE bool emitJumpTargetAndPatch(JumpList jump);
+ MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc,
+ const mozilla::Maybe<uint32_t>& sourceCoordOffset);
MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr);
MOZ_MUST_USE bool emitCallIncDec(ParseNode* incDec);
@@ -546,7 +498,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index);
MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op);
- MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op);
+ MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op);
MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn);
MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count, JSOp op);
@@ -578,44 +530,15 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
- MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc,
- bool callContext = false);
- MOZ_MUST_USE bool emitGetName(JSAtom* name, bool callContext = false) {
- return emitGetNameAtLocation(name, lookupName(name), callContext);
- }
- MOZ_MUST_USE bool emitGetName(ParseNode* pn, bool callContext = false);
-
- template <typename RHSEmitter>
- MOZ_MUST_USE bool emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
- RHSEmitter emitRhs, bool initialize);
- template <typename RHSEmitter>
- MOZ_MUST_USE bool emitSetOrInitializeName(HandleAtom name, RHSEmitter emitRhs,
- bool initialize)
- {
- return emitSetOrInitializeNameAtLocation(name, lookupName(name), emitRhs, initialize);
- }
- template <typename RHSEmitter>
- MOZ_MUST_USE bool emitSetName(ParseNode* pn, RHSEmitter emitRhs) {
- RootedAtom name(cx, pn->name());
- return emitSetName(name, emitRhs);
- }
- template <typename RHSEmitter>
- MOZ_MUST_USE bool emitSetName(HandleAtom name, RHSEmitter emitRhs) {
- return emitSetOrInitializeName(name, emitRhs, false);
- }
- template <typename RHSEmitter>
- MOZ_MUST_USE bool emitInitializeName(ParseNode* pn, RHSEmitter emitRhs) {
- RootedAtom name(cx, pn->name());
- return emitInitializeName(name, emitRhs);
- }
- template <typename RHSEmitter>
- MOZ_MUST_USE bool emitInitializeName(HandleAtom name, RHSEmitter emitRhs) {
- return emitSetOrInitializeName(name, emitRhs, true);
+ MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc);
+ MOZ_MUST_USE bool emitGetName(JSAtom* name) {
+ return emitGetNameAtLocation(name, lookupName(name));
}
+ MOZ_MUST_USE bool emitGetName(ParseNode* pn);
MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc);
- MOZ_MUST_USE bool emitNameIncDec(ParseNode* pn);
+ MOZ_MUST_USE bool emitNameIncDec(ParseNode* incDec);
MOZ_MUST_USE bool emitDeclarationList(ParseNode* decls);
MOZ_MUST_USE bool emitSingleDeclaration(ParseNode* decls, ParseNode* decl,
@@ -644,7 +567,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitAwaitInInnermostScope(ParseNode* pn);
MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope);
MOZ_MUST_USE bool emitPropLHS(ParseNode* pn);
- MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op);
MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn);
MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
@@ -659,6 +581,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref };
MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts);
+ MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe);
MOZ_MUST_USE bool emitElemOpBase(JSOp op);
MOZ_MUST_USE bool emitElemOp(ParseNode* pn, JSOp op);
MOZ_MUST_USE bool emitElemIncDec(ParseNode* pn);
@@ -769,14 +692,14 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name,
- FunctionPrefixKind prefixKind);
+ FunctionPrefixKind prefixKind = FunctionPrefixKind::None);
MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern);
MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern);
MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn);
MOZ_MUST_USE bool emitTemplateString(ParseNode* pn);
- MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs);
+ MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs);
MOZ_MUST_USE bool emitReturn(ParseNode* pn);
MOZ_MUST_USE bool emitStatement(ParseNode* pn);
@@ -792,19 +715,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
ValueUsage valueUsage);
MOZ_MUST_USE bool emitCalleeAndThisForOptionalChain(ParseNode* optionalChain,
ParseNode* callNode,
- bool isCall);
+ CallOrNewEmitter& cone);
MOZ_MUST_USE bool emitDeleteOptionalChain(ParseNode* deleteNode);
// Optional methods which emit a shortCircuit jump. They need to be called by
// a method which emits an Optional Jump Target, see below.
MOZ_MUST_USE bool emitOptionalDotExpression(PropertyAccessBase* prop,
- OptionalEmitter& oe,
- ParseNode* calleeNode,
- bool isCall);
+ PropOpEmitter& poe, bool isSuper,
+ OptionalEmitter& oe);
MOZ_MUST_USE bool emitOptionalElemExpression(PropertyByValueBase* elem,
- OptionalEmitter& oe,
- ParseNode* calleeNode,
- bool isCall);
+ ElemOpEmitter& eoe, bool isSuper,
+ OptionalEmitter& oe);
MOZ_MUST_USE bool emitOptionalCall(ParseNode* callNode,
OptionalEmitter& oe,
ValueUsage valueUsage);
@@ -828,24 +749,23 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional,
ValueUsage valueUsage = ValueUsage::WantValue);
- MOZ_MUST_USE bool isRestParameter(ParseNode* pn, bool* result);
+ MOZ_MUST_USE bool isRestParameter(ParseNode* pn);
MOZ_MUST_USE bool emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted);
+ MOZ_MUST_USE ParseNode* getCoordNode(ParseNode* callNode, ParseNode* calleeNode,
+ ParseNode* firstArgNode);
+ MOZ_MUST_USE bool emitArguments(ParseNode* firstArgNode, uint32_t argc,
+ bool isCall, bool isSpread,
+ CallOrNewEmitter& cone);
MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn,
ValueUsage valueUsage = ValueUsage::WantValue);
MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callNode,
ParseNode* calleeNode,
- bool isCall);
+ CallOrNewEmitter& cone);
MOZ_MUST_USE bool emitOptionalCalleeAndThis(ParseNode* callNode,
ParseNode* calleeNode,
- bool isCall,
+ CallOrNewEmitter& cone,
OptionalEmitter& oe);
- MOZ_MUST_USE bool emitCallOrNewThis(ParseNode* callNode,
- bool isCall);
- MOZ_MUST_USE bool emitCallOrNewArgumentsAndEnd(ParseNode* callNode,
- ParseNode* calleeNode,
- bool isCall,
- ValueUsage valueUsage);
MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn);
MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn);
@@ -887,8 +807,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false);
MOZ_MUST_USE bool emitClass(ParseNode* pn);
- MOZ_MUST_USE bool emitSuperPropLHS(ParseNode* superBase, bool isCall = false);
- MOZ_MUST_USE bool emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall = false);
MOZ_MUST_USE bool emitSuperElemOperands(ParseNode* pn,
EmitElemOption opts = EmitElemOption::Get);
MOZ_MUST_USE bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false);
diff --git a/js/src/frontend/CallOrNewEmitter.cpp b/js/src/frontend/CallOrNewEmitter.cpp
new file mode 100644
index 0000000000..bd1e32ab02
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -0,0 +1,319 @@
+/* -*- 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/CallOrNewEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/NameOpEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+#include "vm/String.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+AutoEmittingRunOnceLambda::AutoEmittingRunOnceLambda(BytecodeEmitter* bce)
+ : bce_(bce)
+{
+ MOZ_ASSERT(!bce_->emittingRunOnceLambda);
+ bce_->emittingRunOnceLambda = true;
+}
+
+AutoEmittingRunOnceLambda::~AutoEmittingRunOnceLambda()
+{
+ bce_->emittingRunOnceLambda = false;
+}
+
+CallOrNewEmitter::CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
+ ArgumentsKind argumentsKind,
+ ValueUsage valueUsage)
+ : bce_(bce),
+ op_(op),
+ argumentsKind_(argumentsKind)
+{
+ if (op_ == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
+ op_ = JSOP_CALL_IGNORES_RV;
+ }
+
+ MOZ_ASSERT(isCall() || isNew() || isSuperCall());
+}
+
+bool
+CallOrNewEmitter::emitNameCallee(JSAtom* name)
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ NameOpEmitter noe(bce_, name,
+ isCall()
+ ? NameOpEmitter::Kind::Call
+ : NameOpEmitter::Kind::Get);
+ if (!noe.emitGet()) { // CALLEE THIS
+ return false;
+ }
+
+ state_ = State::NameCallee;
+ return true;
+}
+
+MOZ_MUST_USE PropOpEmitter&
+CallOrNewEmitter::prepareForPropCallee(bool isSuperProp)
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ poe_.emplace(bce_,
+ isCall()
+ ? PropOpEmitter::Kind::Call
+ : PropOpEmitter::Kind::Get,
+ isSuperProp
+ ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+
+ state_ = State::PropCallee;
+ return *poe_;
+}
+
+MOZ_MUST_USE ElemOpEmitter&
+CallOrNewEmitter::prepareForElemCallee(bool isSuperElem)
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ eoe_.emplace(bce_,
+ isCall()
+ ? ElemOpEmitter::Kind::Call
+ : ElemOpEmitter::Kind::Get,
+ isSuperElem
+ ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+
+ state_ = State::ElemCallee;
+ return *eoe_;
+}
+
+bool
+CallOrNewEmitter::prepareForFunctionCallee()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ // Top level lambdas which are immediately invoked should be treated as
+ // only running once. Every time they execute we will create new types and
+ // scripts for their contents, to increase the quality of type information
+ // within them and enable more backend optimizations. Note that this does
+ // not depend on the lambda being invoked at most once (it may be named or
+ // be accessed via foo.caller indirection), as multiple executions will
+ // just cause the inner scripts to be repeatedly cloned.
+ MOZ_ASSERT(!bce_->emittingRunOnceLambda);
+ if (bce_->checkRunOnceContext()) {
+ autoEmittingRunOnceLambda_.emplace(bce_);
+ }
+
+ state_ = State::FunctionCallee;
+ return true;
+}
+
+bool
+CallOrNewEmitter::emitSuperCallee()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ if (!bce_->emit1(JSOP_SUPERFUN)) { // CALLEE
+ return false;
+ }
+ if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS
+ return false;
+ }
+
+ state_ = State::SuperCallee;
+ return true;
+}
+
+bool
+CallOrNewEmitter::prepareForOtherCallee()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ state_ = State::OtherCallee;
+ return true;
+}
+
+bool
+CallOrNewEmitter::emitThis()
+{
+ MOZ_ASSERT(state_ == State::NameCallee ||
+ state_ == State::PropCallee ||
+ state_ == State::ElemCallee ||
+ state_ == State::FunctionCallee ||
+ state_ == State::SuperCallee ||
+ state_ == State::OtherCallee);
+
+ bool needsThis = false;
+ switch (state_) {
+ case State::NameCallee:
+ if (!isCall()) {
+ needsThis = true;
+ }
+ break;
+ case State::PropCallee:
+ poe_.reset();
+ if (!isCall()) {
+ needsThis = true;
+ }
+ break;
+ case State::ElemCallee:
+ eoe_.reset();
+ if (!isCall()) {
+ needsThis = true;
+ }
+ break;
+ case State::FunctionCallee:
+ autoEmittingRunOnceLambda_.reset();
+ needsThis = true;
+ break;
+ case State::SuperCallee:
+ break;
+ case State::OtherCallee:
+ needsThis = true;
+ break;
+ default:;
+ }
+ if (needsThis) {
+ if (isNew() || isSuperCall()) {
+ if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOP_UNDEFINED)) { // CALLEE THIS
+ return false;
+ }
+ }
+ }
+
+ state_ = State::This;
+ return true;
+}
+
+// Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
+// across multiple chained calls.
+void
+CallOrNewEmitter::reset()
+{
+ MOZ_ASSERT(state_ == State::End);
+ state_ = State::Start;
+}
+
+bool
+CallOrNewEmitter::prepareForNonSpreadArguments()
+{
+ MOZ_ASSERT(state_ == State::This);
+ MOZ_ASSERT(!isSpread());
+
+ state_ = State::Arguments;
+ return true;
+}
+
+// See the usage in the comment at the top of the class.
+bool
+CallOrNewEmitter::wantSpreadOperand()
+{
+ MOZ_ASSERT(state_ == State::This);
+ MOZ_ASSERT(isSpread());
+
+ state_ = State::WantSpreadOperand;
+ return isSingleSpreadRest();
+}
+
+bool
+CallOrNewEmitter::emitSpreadArgumentsTest()
+{
+ // Caller should check wantSpreadOperand before this.
+ MOZ_ASSERT(state_ == State::WantSpreadOperand);
+ MOZ_ASSERT(isSpread());
+
+ if (isSingleSpreadRest()) {
+ // Emit a preparation code to optimize the spread call with a rest
+ // parameter:
+ //
+ // function f(...args) {
+ // g(...args);
+ // }
+ //
+ // If the spread operand is a rest parameter and it's optimizable
+ // array, skip spread operation and pass it directly to spread call
+ // operation. See the comment in OptimizeSpreadCall in
+ // Interpreter.cpp for the optimizable conditons.
+
+ ifNotOptimizable_.emplace(bce_);
+ // // CALLEE THIS ARG0
+ if (!bce_->emit1(JSOP_OPTIMIZE_SPREADCALL)) { // CALLEE THIS ARG0 OPTIMIZED
+ return false;
+ }
+ if (!bce_->emit1(JSOP_NOT)) { // CALLEE THIS ARG0 !OPTIMIZED
+ return false;
+ }
+ if (!ifNotOptimizable_->emitThen()) { // CALLEE THIS ARG0
+ return false;
+ }
+ if (!bce_->emit1(JSOP_POP)) { // CALLEE THIS
+ return false;
+ }
+ }
+
+ state_ = State::Arguments;
+ return true;
+}
+
+bool
+CallOrNewEmitter::emitEnd(uint32_t argc, const Maybe<uint32_t>& beginPos)
+{
+ MOZ_ASSERT(state_ == State::Arguments);
+
+ if (isSingleSpreadRest()) {
+ if (!ifNotOptimizable_->emitEnd()) { // CALLEE THIS ARR
+ return false;
+ }
+
+ ifNotOptimizable_.reset();
+ }
+ if (isNew() || isSuperCall()) {
+ if (isSuperCall()) {
+ if (!bce_->emit1(JSOP_NEWTARGET)) { // CALLEE THIS ARG.. NEW.TARGET
+ return false;
+ }
+ } else {
+ // Repush the callee as new.target
+ uint32_t effectiveArgc = isSpread() ? 1 : argc;
+ if (!bce_->emitDupAt(effectiveArgc + 1)) {
+ return false; // CALLEE THIS ARR CALLEE
+ }
+ }
+ }
+ if (!isSpread()) {
+ if (!bce_->emitCall(op_, argc, beginPos)) { // RVAL
+ return false;
+ }
+ } else {
+ if (beginPos) {
+ if (!bce_->updateSourceCoordNotes(*beginPos)) {
+ return false;
+ }
+ }
+ if (!bce_->emit1(op_)) { // RVAL
+ return false;
+ }
+ }
+ bce_->checkTypeSet(op_);
+
+ if (isEval() && beginPos) {
+ uint32_t lineNum = bce_->parser->tokenStream.srcCoords.lineNum(*beginPos);
+ if (!bce_->emitUint32Operand(JSOP_LINENO, lineNum)) {
+ return false;
+ }
+ }
+
+ state_ = State::End;
+ return true;
+}
diff --git a/js/src/frontend/CallOrNewEmitter.h b/js/src/frontend/CallOrNewEmitter.h
new file mode 100644
index 0000000000..089fe6bca7
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.h
@@ -0,0 +1,338 @@
+/* -*- 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_CallOrNewEmitter_h
+#define frontend_CallOrNewEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "frontend/ElemOpEmitter.h"
+#include "frontend/IfEmitter.h"
+#include "frontend/PropOpEmitter.h"
+#include "frontend/ValueUsage.h"
+#include "js/TypeDecls.h"
+#include "vm/Opcodes.h"
+#include "jsopcode.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+class MOZ_RAII AutoEmittingRunOnceLambda
+{
+ BytecodeEmitter* bce_;
+
+ public:
+ explicit AutoEmittingRunOnceLambda(BytecodeEmitter* bce);
+ ~AutoEmittingRunOnceLambda();
+};
+
+// Class for emitting bytecode for call or new expression.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `print(arg);`
+// CallOrNewEmitter cone(this, JSOP_CALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitNameCallee();
+// emit(print);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+// `callee.prop(arg1, arg2);`
+// CallOrNewEmitter cone(this, JSOP_CALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// PropOpEmitter& poe = cone.prepareForPropCallee(false);
+// ... emit `callee.prop` with `poe` here...
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg1);
+// emit(arg2);
+// cone.emitEnd(2, Some(offset_of_callee));
+//
+// `callee[key](arg);`
+// CallOrNewEmitter cone(this, JSOP_CALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// ElemOpEmitter& eoe = cone.prepareForElemCallee(false);
+// ... emit `callee[key]` with `eoe` here...
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+// `(function() { ... })(arg);`
+// CallOrNewEmitter cone(this, JSOP_CALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.prepareForFunctionCallee();
+// emit(function);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+// `super(arg);`
+// CallOrNewEmitter cone(this, JSOP_CALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitSuperCallee();
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+// `(some_other_expression)(arg);`
+// CallOrNewEmitter cone(this, JSOP_CALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.prepareForOtherCallee();
+// emit(some_other_expression);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+// `print(...arg);`
+// CallOrNewEmitter cone(this, JSOP_SPREADCALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitNameCallee();
+// emit(print);
+// cone.emitThis();
+// if (cone.wantSpreadOperand())
+// emit(arg)
+// cone.emitSpreadArgumentsTest();
+// emit([...arg]);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+// `print(...rest);`
+// where `rest` is rest parameter
+// CallOrNewEmitter cone(this, JSOP_SPREADCALL,
+// CallOrNewEmitter::ArgumentsKind::SingleSpreadRest,
+// ValueUsage::WantValue);
+// cone.emitNameCallee();
+// emit(print);
+// cone.emitThis();
+// if (cone.wantSpreadOperand())
+// emit(arg)
+// cone.emitSpreadArgumentsTest();
+// emit([...arg]);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+// `new f(arg);`
+// CallOrNewEmitter cone(this, JSOP_NEW,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitNameCallee();
+// emit(f);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, Some(offset_of_callee));
+//
+class MOZ_STACK_CLASS CallOrNewEmitter
+{
+ public:
+ enum class ArgumentsKind {
+ Other,
+
+ // Specify this for the following case:
+ //
+ // function f(...rest) {
+ // g(...rest);
+ // }
+ //
+ // This enables optimization to avoid allocating an intermediate array
+ // for spread operation.
+ //
+ // wantSpreadOperand() returns true when this is specified.
+ SingleSpreadRest
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ // The opcode for the call or new.
+ JSOp op_;
+
+ // Whether the call is a spread call with single rest parameter or not.
+ // See the comment in emitSpreadArgumentsTest for more details.
+ ArgumentsKind argumentsKind_;
+
+ // The branch for spread call optimization.
+ mozilla::Maybe<InternalIfEmitter> ifNotOptimizable_;
+
+ mozilla::Maybe<AutoEmittingRunOnceLambda> autoEmittingRunOnceLambda_;
+
+ mozilla::Maybe<PropOpEmitter> poe_;
+ mozilla::Maybe<ElemOpEmitter> eoe_;
+
+ // The state of this emitter.
+ //
+ // +-------+ emitNameCallee +------------+
+ // | Start |-+------------------------->| NameCallee |------+
+ // +-------+ | +------------+ |
+ // | |
+ // | prepareForPropCallee +------------+ v
+ // +------------------------->| PropCallee |----->+
+ // | +------------+ |
+ // | |
+ // | prepareForElemCallee +------------+ v
+ // +------------------------->| ElemCallee |----->+
+ // | +------------+ |
+ // | |
+ // | prepareForFunctionCallee +----------------+ v
+ // +------------------------->| FunctionCallee |->+
+ // | +----------------+ |
+ // | |
+ // | emitSuperCallee +-------------+ v
+ // +------------------------->| SuperCallee |---->+
+ // | +-------------+ |
+ // | |
+ // | prepareForOtherCallee +-------------+ v
+ // +------------------------->| OtherCallee |---->+
+ // +-------------+ |
+ // |
+ // +--------------------------------------------------------+
+ // |
+ // | emitThis +------+
+ // +--------->| This |-+
+ // +------+ |
+ // |
+ // +-------------------+
+ // |
+ // | [!isSpread]
+ // | prepareForNonSpreadArguments +-----------+ emitEnd +-----+
+ // +------------------------------->+->| Arguments |-------->| End |
+ // | ^ +-----------+ +-----+
+ // | |
+ // | +----------------------------------+
+ // | |
+ // | [isSpread] |
+ // | wantSpreadOperand +-------------------+ emitSpreadArgumentsTest |
+ // +-------------------->| WantSpreadOperand |-------------------------+
+ // +-------------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitNameCallee.
+ NameCallee,
+
+ // After calling prepareForPropCallee.
+ PropCallee,
+
+ // After calling prepareForElemCallee.
+ ElemCallee,
+
+ // After calling prepareForFunctionCallee.
+ FunctionCallee,
+
+ // After calling emitSuperCallee.
+ SuperCallee,
+
+ // After calling prepareForOtherCallee.
+ OtherCallee,
+
+ // After calling emitThis.
+ This,
+
+ // After calling wantSpreadOperand.
+ WantSpreadOperand,
+
+ // After calling prepareForNonSpreadArguments.
+ Arguments,
+
+ // After calling emitEnd.
+ End
+ };
+ State state_ = State::Start;
+
+ public:
+ CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
+ ArgumentsKind argumentsKind,
+ ValueUsage valueUsage);
+
+ private:
+ MOZ_MUST_USE bool isCall() const {
+ return op_ == JSOP_CALL || op_ == JSOP_CALL_IGNORES_RV ||
+ op_ == JSOP_SPREADCALL ||
+ isEval() || isFunApply() || isFunCall();
+ }
+
+ MOZ_MUST_USE bool isNew() const {
+ return op_ == JSOP_NEW || op_ == JSOP_SPREADNEW;
+ }
+
+ MOZ_MUST_USE bool isSuperCall() const {
+ return op_ == JSOP_SUPERCALL || op_ == JSOP_SPREADSUPERCALL;
+ }
+
+ MOZ_MUST_USE bool isEval() const {
+ return op_ == JSOP_EVAL || op_ == JSOP_STRICTEVAL ||
+ op_ == JSOP_SPREADEVAL || op_ == JSOP_STRICTSPREADEVAL;
+ }
+
+ MOZ_MUST_USE bool isFunApply() const {
+ return op_ == JSOP_FUNAPPLY;
+ }
+
+ MOZ_MUST_USE bool isFunCall() const {
+ return op_ == JSOP_FUNCALL;
+ }
+
+ MOZ_MUST_USE bool isSpread() const {
+ return JOF_OPTYPE(op_) == JOF_BYTE;
+ }
+
+ MOZ_MUST_USE bool isSingleSpreadRest() const {
+ return argumentsKind_ == ArgumentsKind::SingleSpreadRest;
+ }
+
+ public:
+ MOZ_MUST_USE bool emitNameCallee(JSAtom* name);
+ MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp);
+ MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem);
+ MOZ_MUST_USE bool prepareForFunctionCallee();
+ MOZ_MUST_USE bool emitSuperCallee();
+ MOZ_MUST_USE bool prepareForOtherCallee();
+
+ MOZ_MUST_USE bool emitThis();
+
+ // Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
+ // across multiple chained calls.
+ void reset();
+
+ MOZ_MUST_USE bool prepareForNonSpreadArguments();
+
+ // See the usage in the comment at the top of the class.
+ MOZ_MUST_USE bool wantSpreadOperand();
+ MOZ_MUST_USE bool emitSpreadArgumentsTest();
+
+ // Parameters are the offset in the source code for each character below:
+ //
+ // callee(arg);
+ // ^
+ // |
+ // beginPos
+ //
+ // Can be Nothing() if not available.
+ MOZ_MUST_USE bool emitEnd(uint32_t argc, const mozilla::Maybe<uint32_t>& beginPos);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_CallOrNewEmitter_h */
diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp
new file mode 100644
index 0000000000..916cca08c1
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -0,0 +1,288 @@
+/* -*- 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/ElemOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
+ : bce_(bce),
+ kind_(kind),
+ objKind_(objKind)
+{}
+
+bool
+ElemOpEmitter::prepareForObj()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+#ifdef DEBUG
+ state_ = State::Obj;
+#endif
+ return true;
+}
+
+bool
+ElemOpEmitter::prepareForKey()
+{
+ MOZ_ASSERT(state_ == State::Obj);
+
+ if (!isSuper() && isIncDec()) {
+ if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) { // OBJ
+ return false;
+ }
+ }
+ if (isCall()) {
+ if (!bce_->emit1(JSOP_DUP)) { // [Super]
+ // // THIS THIS
+ // // [Other]
+ // // OBJ OBJ
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Key;
+#endif
+ return true;
+}
+
+bool
+ElemOpEmitter::emitGet()
+{
+ MOZ_ASSERT(state_ == State::Key);
+
+ if (isIncDec() || isCompoundAssignment()) {
+ if (!bce_->emit1(JSOP_TOID)) { // [Super]
+ // // THIS KEY
+ // // [Other]
+ // // OBJ KEY
+ return false;
+ }
+ }
+ if (isSuper()) {
+ if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS? THIS KEY SUPERBASE
+ return false;
+ }
+ }
+ if (isIncDec() || isCompoundAssignment()) {
+ if (isSuper()) {
+ // There's no such thing as JSOP_DUP3, so we have to be creative.
+ // Note that pushing things again is no fewer JSOps.
+ if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS
+ return false;
+ }
+ if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS KEY
+ return false;
+ }
+ if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS KEY SUPERBASE
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOP_DUP2)) { // OBJ KEY OBJ KEY
+ return false;
+ }
+ }
+ }
+
+ JSOp op;
+ if (isSuper()) {
+ op = JSOP_GETELEM_SUPER;
+ } else if (isCall()) {
+ op = JSOP_CALLELEM;
+ } else {
+ op = JSOP_GETELEM;
+ }
+ if (!bce_->emitElemOpBase(op)) { // [Get]
+ // // ELEM
+ // // [Call]
+ // // THIS ELEM
+ // // [Inc/Dec/Assignment,
+ // // Super]
+ // // THIS KEY SUPERBASE ELEM
+ // // [Inc/Dec/Assignment,
+ // // Other]
+ // // OBJ KEY ELEM
+ return false;
+ }
+ if (isCall()) {
+ if (!bce_->emit1(JSOP_SWAP)) { // ELEM THIS
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool
+ElemOpEmitter::prepareForRhs()
+{
+ MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+ MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key);
+ MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+ if (isSimpleAssignment()) {
+ // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
+ if (isSuper()) {
+ if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool
+ElemOpEmitter::skipObjAndKeyAndRhs()
+{
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(isSimpleAssignment());
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool
+ElemOpEmitter::emitDelete()
+{
+ MOZ_ASSERT(state_ == State::Key);
+ MOZ_ASSERT(isDelete());
+
+ if (isSuper()) {
+ if (!bce_->emit1(JSOP_TOID)) { // THIS KEY
+ return false;
+ }
+ if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE
+ return false;
+ }
+
+ // Unconditionally throw when attempting to delete a super-reference.
+ if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
+ return false; // THIS KEY SUPERBASE
+ }
+
+ // Another wrinkle: Balance the stack from the emitter's point of view.
+ // Execution will not reach here, as the last bytecode threw.
+ if (!bce_->emitPopN(2)) { // THIS
+ return false;
+ }
+ } else {
+ JSOp op = bce_->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
+ if (!bce_->emitElemOpBase(op)){ // SUCCEEDED
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Delete;
+#endif
+ return true;
+}
+
+bool
+ElemOpEmitter::emitAssignment()
+{
+ MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+ MOZ_ASSERT(state_ == State::Rhs);
+
+ JSOp setOp = isSuper()
+ ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
+ : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
+ if (!bce_->emitElemOpBase(setOp)) { // ELEM
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool
+ElemOpEmitter::emitIncDec()
+{
+ MOZ_ASSERT(state_ == State::Key);
+ MOZ_ASSERT(isIncDec());
+
+ if (!emitGet()) { // ... ELEM
+ return false;
+ }
+
+ MOZ_ASSERT(state_ == State::Get);
+
+ JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+ if (!bce_->emit1(JSOP_POS)) { // ... N
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (!bce_->emit1(JSOP_DUP)) { // ... N? N
+ return false;
+ }
+ }
+ if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1
+ return false;
+ }
+ if (!bce_->emit1(binOp)) { // ... N? N+1
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (isSuper()) { // THIS KEY OBJ N N+1
+ if (!bce_->emit2(JSOP_PICK, 4)) { // KEY SUPERBASE N N+1 THIS
+ return false;
+ }
+ if (!bce_->emit2(JSOP_PICK, 4)) { // SUPERBASE N N+1 THIS KEY
+ return false;
+ }
+ if (!bce_->emit2(JSOP_PICK, 4)) { // N N+1 THIS KEY SUPERBASE
+ return false;
+ }
+ if (!bce_->emit2(JSOP_PICK, 3)) { // N THIS KEY SUPERBASE N+1
+ return false;
+ }
+ } else { // OBJ KEY N N+1
+ if (!bce_->emit2(JSOP_PICK, 3)) { // KEY N N+1 OBJ
+ return false;
+ }
+ if (!bce_->emit2(JSOP_PICK, 3)) { // N N+1 OBJ KEY
+ return false;
+ }
+ if (!bce_->emit2(JSOP_PICK, 2)) { // N OBJ KEY N+1
+ return false;
+ }
+ }
+ }
+
+ JSOp setOp = isSuper()
+ ? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
+ : (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
+ if (!bce_->emitElemOpBase(setOp)) { // N? N+1
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (!bce_->emit1(JSOP_POP)) { // N
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::IncDec;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/ElemOpEmitter.h b/js/src/frontend/ElemOpEmitter.h
new file mode 100644
index 0000000000..48b3eaa43c
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.h
@@ -0,0 +1,278 @@
+/* -*- 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_ElemOpEmitter_h
+#define frontend_ElemOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for element operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `obj[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Get,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+//
+// `super[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Get,
+// ElemOpEmitter::ObjKind::Super);
+// eoe.prepareForObj();
+// emit(this_for_super);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+//
+// `obj[key]();`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Call,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+// emit_call_here();
+//
+// `new obj[key]();`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Call,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+// emit_call_here();
+//
+// `delete obj[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Delete,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitDelete();
+//
+// `delete super[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Delete,
+// ElemOpEmitter::ObjKind::Super);
+// eoe.prepareForObj();
+// emit(this_for_super);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitDelete();
+//
+// `obj[key]++;`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::PostIncrement,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitIncDec();
+//
+// `obj[key] = value;`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::SimpleAssignment,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.prepareForRhs();
+// emit(value);
+// eoe.emitAssignment();
+//
+// `obj[key] += value;`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::CompoundAssignment,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+// eoe.prepareForRhs();
+// emit(value);
+// emit_add_op_here();
+// eoe.emitAssignment();
+//
+class MOZ_STACK_CLASS ElemOpEmitter
+{
+ public:
+ enum class Kind {
+ Get,
+ Call,
+ Set,
+ Delete,
+ PostIncrement,
+ PreIncrement,
+ PostDecrement,
+ PreDecrement,
+ SimpleAssignment,
+ CompoundAssignment
+ };
+ enum class ObjKind {
+ Super,
+ Other
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ Kind kind_;
+ ObjKind objKind_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // skipObjAndKeyAndRhs
+ // +------------------------------------------------+
+ // | |
+ // +-------+ | prepareForObj +-----+ prepareForKey +-----+ |
+ // | Start |-+-------------->| Obj |-------------->| Key |-+ |
+ // +-------+ +-----+ +-----+ | |
+ // | |
+ // +-------------------------------------------------------+ |
+ // | |
+ // | |
+ // | |
+ // | [Get] |
+ // | [Call] |
+ // | emitGet +-----+ |
+ // +---------->| Get | |
+ // | +-----+ |
+ // | |
+ // | [Delete] |
+ // | emitDelete +--------+ |
+ // +------------->| Delete | |
+ // | +--------+ |
+ // | |
+ // | [PostIncrement] |
+ // | [PreIncrement] |
+ // | [PostDecrement] |
+ // | [PreDecrement] |
+ // | emitIncDec +--------+ |
+ // +------------->| IncDec | |
+ // | +--------+ |
+ // | +-------------------+
+ // | [SimpleAssignment] |
+ // | prepareForRhs v +-----+
+ // +--------------------->+-------------->+->| Rhs |-+
+ // | ^ +-----+ |
+ // | | |
+ // | | +-------------+
+ // | [CompoundAssignment] | |
+ // | emitGet +-----+ | | emitAssignment +------------+
+ // +---------->| Get |----+ +--------------->| Assignment |
+ // +-----+ +------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling prepareForObj.
+ Obj,
+
+ // After calling emitKey.
+ Key,
+
+ // After calling emitGet.
+ Get,
+
+ // After calling emitDelete.
+ Delete,
+
+ // After calling emitIncDec.
+ IncDec,
+
+ // After calling prepareForRhs or skipObjAndKeyAndRhs.
+ Rhs,
+
+ // After calling emitAssignment.
+ Assignment,
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
+
+ private:
+ MOZ_MUST_USE bool isCall() const {
+ return kind_ == Kind::Call;
+ }
+
+ MOZ_MUST_USE bool isSimpleAssignment() const {
+ return kind_ == Kind::SimpleAssignment;
+ }
+
+ MOZ_MUST_USE bool isDelete() const {
+ return kind_ == Kind::Delete;
+ }
+
+ MOZ_MUST_USE bool isCompoundAssignment() const {
+ return kind_ == Kind::CompoundAssignment;
+ }
+
+ MOZ_MUST_USE bool isIncDec() const {
+ return isPostIncDec() || isPreIncDec();
+ }
+
+ MOZ_MUST_USE bool isPostIncDec() const {
+ return kind_ == Kind::PostIncrement ||
+ kind_ == Kind::PostDecrement;
+ }
+
+ MOZ_MUST_USE bool isPreIncDec() const {
+ return kind_ == Kind::PreIncrement ||
+ kind_ == Kind::PreDecrement;
+ }
+
+ MOZ_MUST_USE bool isInc() const {
+ return kind_ == Kind::PostIncrement ||
+ kind_ == Kind::PreIncrement;
+ }
+
+ MOZ_MUST_USE bool isSuper() const {
+ return objKind_ == ObjKind::Super;
+ }
+
+ public:
+ MOZ_MUST_USE bool prepareForObj();
+ MOZ_MUST_USE bool prepareForKey();
+
+ MOZ_MUST_USE bool emitGet();
+
+ MOZ_MUST_USE bool prepareForRhs();
+ MOZ_MUST_USE bool skipObjAndKeyAndRhs();
+
+ MOZ_MUST_USE bool emitDelete();
+
+ MOZ_MUST_USE bool emitAssignment();
+
+ MOZ_MUST_USE bool emitIncDec();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ElemOpEmitter_h */
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, &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)
+{}
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 <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 */
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 <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 */
diff --git a/js/src/frontend/NameOpEmitter.cpp b/js/src/frontend/NameOpEmitter.cpp
new file mode 100644
index 0000000000..01489239b8
--- /dev/null
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -0,0 +1,382 @@
+/* -*- 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/NameOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "frontend/TDZCheckCache.h"
+#include "vm/Stack.h" // for MaybeCheckTDZ
+#include "vm/Opcodes.h"
+#include "vm/Scope.h"
+#include "vm/String.h"
+
+using namespace js;
+using namespace js::frontend;
+
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind)
+ : bce_(bce),
+ kind_(kind),
+ name_(bce_->cx, name),
+ loc_(bce_->lookupName(name_))
+{}
+
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc,
+ Kind kind)
+ : bce_(bce),
+ kind_(kind),
+ name_(bce_->cx, name),
+ loc_(loc)
+{}
+
+bool
+NameOpEmitter::emitGet()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic:
+ if (!bce_->emitAtomOp(name_, JSOP_GETNAME)) { // VAL
+ return false;
+ }
+ break;
+ case NameLocation::Kind::Global:
+ if (!bce_->emitAtomOp(name_, JSOP_GETGNAME)) {// VAL
+ return false;
+ }
+ break;
+ case NameLocation::Kind::Intrinsic:
+ if (!bce_->emitAtomOp(name_, JSOP_GETINTRINSIC)) {
+ return false; // VAL
+ }
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ if (!bce_->emit1(JSOP_CALLEE)) { // VAL
+ return false;
+ }
+ break;
+ case NameLocation::Kind::Import:
+ if (!bce_->emitAtomOp(name_, JSOP_GETIMPORT)) {
+ return false; // VAL
+ }
+ break;
+ case NameLocation::Kind::ArgumentSlot:
+ if (!bce_->emitArgOp(JSOP_GETARG, loc_.argumentSlot())) {
+ return false; // VAL
+ }
+ break;
+ case NameLocation::Kind::FrameSlot:
+ if (loc_.isLexical()) {
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+ return false;
+ }
+ }
+ if (!bce_->emitLocalOp(JSOP_GETLOCAL, loc_.frameSlot())) {
+ return false; // VAL
+ }
+ break;
+ case NameLocation::Kind::EnvironmentCoordinate:
+ if (loc_.isLexical()) {
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+ return false;
+ }
+ }
+ if (!bce_->emitEnvCoordOp(JSOP_GETALIASEDVAR, loc_.environmentCoordinate())) {
+ return false; // VAL
+ }
+ break;
+ case NameLocation::Kind::DynamicAnnexBVar:
+ MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+ }
+
+ if (isCall()) {
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic: {
+ JSOp thisOp = bce_->needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
+ if (!bce_->emitAtomOp(name_, thisOp)) { // CALLEE THIS
+ return false;
+ }
+ break;
+ }
+ case NameLocation::Kind::Global:
+ if (!bce_->emitAtomOp(name_, JSOP_GIMPLICITTHIS)) {
+ return false; // CALLEE THIS
+ }
+ break;
+ case NameLocation::Kind::Intrinsic:
+ case NameLocation::Kind::NamedLambdaCallee:
+ case NameLocation::Kind::Import:
+ case NameLocation::Kind::ArgumentSlot:
+ case NameLocation::Kind::FrameSlot:
+ case NameLocation::Kind::EnvironmentCoordinate:
+ if (!bce_->emit1(JSOP_UNDEFINED)) { // CALLEE UNDEF
+ return false;
+ }
+ break;
+ case NameLocation::Kind::DynamicAnnexBVar:
+ MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool
+NameOpEmitter::prepareForRhs()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic:
+ case NameLocation::Kind::Import:
+ case NameLocation::Kind::DynamicAnnexBVar:
+ if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
+ return false;
+ }
+ if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) {
+ // Annex B vars always go on the nearest variable environment,
+ // even if lexical environments in between contain same-named
+ // bindings.
+ if (!bce_->emit1(JSOP_BINDVAR)) { // ENV
+ return false;
+ }
+ } else {
+ if (!bce_->emitIndexOp(JSOP_BINDNAME, atomIndex_)) {
+ return false; // ENV
+ }
+ }
+ emittedBindOp_ = true;
+ break;
+ case NameLocation::Kind::Global:
+ if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
+ return false;
+ }
+ if (loc_.isLexical() && isInitialize()) {
+ // INITGLEXICAL always gets the global lexical scope. It doesn't
+ // need a BINDGNAME.
+ MOZ_ASSERT(bce_->innermostScope()->is<GlobalScope>());
+ } else {
+ if (!bce_->emitIndexOp(JSOP_BINDGNAME, atomIndex_)) {
+ return false; // ENV
+ }
+ emittedBindOp_ = true;
+ }
+ break;
+ case NameLocation::Kind::Intrinsic:
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ break;
+ case NameLocation::Kind::ArgumentSlot: {
+ // If we assign to a positional formal parameter and the arguments
+ // object is unmapped (strict mode or function with
+ // default/rest/destructing args), parameters do not alias
+ // arguments[i], and to make the arguments object reflect initial
+ // parameter values prior to any mutation we create it eagerly
+ // whenever parameters are (or might, in the case of calls to eval)
+ // assigned.
+ FunctionBox* funbox = bce_->sc->asFunctionBox();
+ if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) {
+ funbox->setDefinitelyNeedsArgsObj();
+ }
+ break;
+ }
+ case NameLocation::Kind::FrameSlot:
+ break;
+ case NameLocation::Kind::EnvironmentCoordinate:
+ break;
+ }
+
+ // For compound assignments, first get the LHS value, then emit
+ // the RHS and the op.
+ if (isCompoundAssignment() || isIncDec()) {
+ if (loc_.kind() == NameLocation::Kind::Dynamic) {
+ // For dynamic accesses we need to emit GETBOUNDNAME instead of
+ // GETNAME for correctness: looking up @@unscopables on the
+ // environment chain (due to 'with' environments) must only happen
+ // once.
+ //
+ // GETBOUNDNAME uses the environment already pushed on the stack
+ // from the earlier BINDNAME.
+ if (!bce_->emit1(JSOP_DUP)) { // ENV ENV
+ return false;
+ }
+ if (!bce_->emitAtomOp(name_, JSOP_GETXPROP)) {
+ return false; // ENV V
+ }
+ } else {
+ if (!emitGet()) { // ENV? V
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool
+NameOpEmitter::emitAssignment()
+{
+ MOZ_ASSERT(state_ == State::Rhs);
+
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic:
+ case NameLocation::Kind::Import:
+ case NameLocation::Kind::DynamicAnnexBVar:
+ if (!bce_->emitIndexOp(bce_->strictifySetNameOp(JSOP_SETNAME), atomIndex_)) {
+ return false;
+ }
+ break;
+ case NameLocation::Kind::Global: {
+ JSOp op;
+ if (emittedBindOp_) {
+ op = bce_->strictifySetNameOp(JSOP_SETGNAME);
+ } else {
+ op = JSOP_INITGLEXICAL;
+ }
+ if (!bce_->emitIndexOp(op, atomIndex_)) {
+ return false;
+ }
+ break;
+ }
+ case NameLocation::Kind::Intrinsic:
+ if (!bce_->emitAtomOp(name_, JSOP_SETINTRINSIC)) {
+ return false;
+ }
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ // Assigning to the named lambda is a no-op in sloppy mode but
+ // throws in strict mode.
+ if (bce_->sc->strict()) {
+ if (!bce_->emit1(JSOP_THROWSETCALLEE)) {
+ return false;
+ }
+ }
+ break;
+ case NameLocation::Kind::ArgumentSlot:
+ if (!bce_->emitArgOp(JSOP_SETARG, loc_.argumentSlot())) {
+ return false;
+ }
+ break;
+ case NameLocation::Kind::FrameSlot: {
+ JSOp op = JSOP_SETLOCAL;
+ if (loc_.isLexical()) {
+ if (isInitialize()) {
+ op = JSOP_INITLEXICAL;
+ } else {
+ if (loc_.isConst()) {
+ op = JSOP_THROWSETCONST;
+ }
+
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+ return false;
+ }
+ }
+ }
+ if (!bce_->emitLocalOp(op, loc_.frameSlot())) {
+ return false;
+ }
+ if (op == JSOP_INITLEXICAL) {
+ if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) {
+ return false;
+ }
+ }
+ break;
+ }
+ case NameLocation::Kind::EnvironmentCoordinate: {
+ JSOp op = JSOP_SETALIASEDVAR;
+ if (loc_.isLexical()) {
+ if (isInitialize()) {
+ op = JSOP_INITALIASEDLEXICAL;
+ } else {
+ if (loc_.isConst()) {
+ op = JSOP_THROWSETALIASEDCONST;
+ }
+
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+ return false;
+ }
+ }
+ }
+ if (loc_.bindingKind() == BindingKind::NamedLambdaCallee) {
+ // Assigning to the named lambda is a no-op in sloppy mode and throws
+ // in strict mode.
+ op = JSOP_THROWSETALIASEDCONST;
+ if (bce_->sc->strict()) {
+ if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) {
+ return false;
+ }
+ }
+ } else {
+ if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) {
+ return false;
+ }
+ }
+ if (op == JSOP_INITALIASEDLEXICAL) {
+ if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) {
+ return false;
+ }
+ }
+ break;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool
+NameOpEmitter::emitIncDec()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+ JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+ if (!prepareForRhs()) { // ENV? V
+ return false;
+ }
+ if (!bce_->emit1(JSOP_POS)) { // ENV? N
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (!bce_->emit1(JSOP_DUP)) { // ENV? N? N
+ return false;
+ }
+ }
+ if (!bce_->emit1(JSOP_ONE)) { // ENV? N? N 1
+ return false;
+ }
+ if (!bce_->emit1(binOp)) { // ENV? N? N+1
+ return false;
+ }
+ if (isPostIncDec() && emittedBindOp()) {
+ if (!bce_->emit2(JSOP_PICK, 2)) { // N? N+1 ENV?
+ return false;
+ }
+ if (!bce_->emit1(JSOP_SWAP)) { // N? ENV? N+1
+ return false;
+ }
+ }
+ if (!emitAssignment()) { // N? N+1
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (!bce_->emit1(JSOP_POP)) { // N
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::IncDec;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/NameOpEmitter.h b/js/src/frontend/NameOpEmitter.h
new file mode 100644
index 0000000000..5317e7865b
--- /dev/null
+++ b/js/src/frontend/NameOpEmitter.h
@@ -0,0 +1,193 @@
+/* -*- 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_NameOpEmitter_h
+#define frontend_NameOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include <stdint.h>
+
+#include "frontend/NameAnalysisTypes.h"
+#include "js/TypeDecls.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for name operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `name;`
+// NameOpEmitter noe(this, atom_of_name
+// ElemOpEmitter::Kind::Get);
+// noe.emitGet();
+//
+// `name();`
+// this is handled in CallOrNewEmitter
+//
+// `name++;`
+// NameOpEmitter noe(this, atom_of_name
+// ElemOpEmitter::Kind::PostIncrement);
+// noe.emitIncDec();
+//
+// `name = 10;`
+// NameOpEmitter noe(this, atom_of_name
+// ElemOpEmitter::Kind::SimpleAssignment);
+// noe.prepareForRhs();
+// emit(10);
+// noe.emitAssignment();
+//
+// `name += 10;`
+// NameOpEmitter noe(this, atom_of_name
+// ElemOpEmitter::Kind::CompoundAssignment);
+// noe.prepareForRhs();
+// emit(10);
+// emit_add_op_here();
+// noe.emitAssignment();
+//
+// `name = 10;` part of `let name = 10;`
+// NameOpEmitter noe(this, atom_of_name
+// ElemOpEmitter::Kind::Initialize);
+// noe.prepareForRhs();
+// emit(10);
+// noe.emitAssignment();
+//
+class MOZ_STACK_CLASS NameOpEmitter
+{
+ public:
+ enum class Kind {
+ Get,
+ Call,
+ PostIncrement,
+ PreIncrement,
+ PostDecrement,
+ PreDecrement,
+ SimpleAssignment,
+ CompoundAssignment,
+ Initialize
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ Kind kind_;
+
+ bool emittedBindOp_ = false;
+
+ RootedAtom name_;
+
+ uint32_t atomIndex_;
+
+ NameLocation loc_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // [Get]
+ // [Call]
+ // +-------+ emitGet +-----+
+ // | Start |-+-+--------->| Get |
+ // +-------+ | +-----+
+ // |
+ // | [PostIncrement]
+ // | [PreIncrement]
+ // | [PostDecrement]
+ // | [PreDecrement]
+ // | emitIncDec +--------+
+ // +------------->| IncDec |
+ // | +--------+
+ // |
+ // | [SimpleAssignment]
+ // | prepareForRhs +-----+
+ // +--------------------->+-------------->| Rhs |-+
+ // | ^ +-----+ |
+ // | | |
+ // | | +------------------+
+ // | [CompoundAssignment] | |
+ // | emitGet +-----+ | | emitAssignment +------------+
+ // +---------->| Get |----+ + -------------->| Assignment |
+ // +-----+ +------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitGet.
+ Get,
+
+ // After calling emitIncDec.
+ IncDec,
+
+ // After calling prepareForRhs.
+ Rhs,
+
+ // After calling emitAssignment.
+ Assignment,
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind);
+ NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc, Kind kind);
+
+ private:
+ MOZ_MUST_USE bool isCall() const {
+ return kind_ == Kind::Call;
+ }
+
+ MOZ_MUST_USE bool isSimpleAssignment() const {
+ return kind_ == Kind::SimpleAssignment;
+ }
+
+ MOZ_MUST_USE bool isCompoundAssignment() const {
+ return kind_ == Kind::CompoundAssignment;
+ }
+
+ MOZ_MUST_USE bool isIncDec() const {
+ return isPostIncDec() || isPreIncDec();
+ }
+
+ MOZ_MUST_USE bool isPostIncDec() const {
+ return kind_ == Kind::PostIncrement ||
+ kind_ == Kind::PostDecrement;
+ }
+
+ MOZ_MUST_USE bool isPreIncDec() const {
+ return kind_ == Kind::PreIncrement ||
+ kind_ == Kind::PreDecrement;
+ }
+
+ MOZ_MUST_USE bool isInc() const {
+ return kind_ == Kind::PostIncrement ||
+ kind_ == Kind::PreIncrement;
+ }
+
+ MOZ_MUST_USE bool isInitialize() const {
+ return kind_ == Kind::Initialize;
+ }
+
+ public:
+ MOZ_MUST_USE bool emittedBindOp() const {
+ return emittedBindOp_;
+ }
+
+ MOZ_MUST_USE const NameLocation& loc() const {
+ return loc_;
+ }
+
+ MOZ_MUST_USE bool emitGet();
+ MOZ_MUST_USE bool prepareForRhs();
+ MOZ_MUST_USE bool emitAssignment();
+ MOZ_MUST_USE bool emitIncDec();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_NameOpEmitter_h */
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index 377eb999d9..bdba7a1ff9 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -886,6 +886,10 @@ struct UnaryNode : public ParseNode
pn_kid = kid;
}
+ static bool test(const ParseNode& node) {
+ return node.isArity(PN_UNARY);
+ }
+
#ifdef DEBUG
void dump(int indent);
#endif
@@ -996,6 +1000,10 @@ struct NameNode : public ParseNode
pn_expr = nullptr;
}
+ static bool test(const ParseNode& node) {
+ return node.isArity(PN_NAME);
+ }
+
#ifdef DEBUG
void dump(int indent);
#endif
@@ -1248,6 +1256,10 @@ class PropertyAccessBase : public ParseNode
PropertyName& name() const {
return *pn_u.name.atom->asPropertyName();
}
+
+ JSAtom* nameAtom() const {
+ return pn_u.name.atom;
+ }
};
class PropertyAccess : public PropertyAccessBase
@@ -1302,6 +1314,14 @@ class PropertyByValueBase : public ParseNode
pn_u.binary.right = propExpr;
}
+ ParseNode& expression() const {
+ return *pn_u.binary.left;
+ }
+
+ ParseNode& key() const {
+ return *pn_u.binary.right;
+ }
+
static bool test(const ParseNode& node) {
bool match = node.isKind(PNK_ELEM) ||
node.isKind(PNK_OPTELEM);
diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp
new file mode 100644
index 0000000000..fd00024e7c
--- /dev/null
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -0,0 +1,275 @@
+/* -*- 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/PropOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+#include "vm/String.h"
+
+using namespace js;
+using namespace js::frontend;
+
+PropOpEmitter::PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
+ : bce_(bce),
+ kind_(kind),
+ objKind_(objKind)
+{}
+
+bool
+PropOpEmitter::prepareAtomIndex(JSAtom* prop)
+{
+ if (!bce_->makeAtomIndex(prop, &propAtomIndex_)) {
+ return false;
+ }
+ isLength_ = prop == bce_->cx->names().length;
+
+ return true;
+}
+
+bool
+PropOpEmitter::prepareForObj()
+{
+ MOZ_ASSERT(state_ == State::Start);
+
+#ifdef DEBUG
+ state_ = State::Obj;
+#endif
+ return true;
+}
+
+bool
+PropOpEmitter::emitGet(JSAtom* prop)
+{
+ MOZ_ASSERT(state_ == State::Obj);
+
+ if (!prepareAtomIndex(prop)) {
+ return false;
+ }
+ if (isCall()) {
+ if (!bce_->emit1(JSOP_DUP)) { // [Super]
+ // // THIS THIS
+ // // [Other]
+ // // OBJ OBJ
+ return false;
+ }
+ }
+ if (isSuper()) {
+ if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS? THIS SUPERBASE
+ return false;
+ }
+ }
+ if (isIncDec() || isCompoundAssignment()) {
+ if (isSuper()) {
+ if (!bce_->emit1(JSOP_DUP2)) { // THIS SUPERBASE THIS SUPERBASE
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOP_DUP)) { // OBJ OBJ
+ return false;
+ }
+ }
+ }
+
+ JSOp op;
+ if (isSuper()) {
+ op = JSOP_GETPROP_SUPER;
+ } else if (isCall()) {
+ op = JSOP_CALLPROP;
+ } else {
+ op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP;
+ }
+ if (!bce_->emitAtomOp(propAtomIndex_, op)) { // [Get]
+ // // PROP
+ // // [Call]
+ // // THIS PROP
+ // // [Inc/Dec/Compound,
+ // // Super]
+ // // THIS SUPERBASE PROP
+ // // [Inc/Dec/Compound,
+ // // Other]
+ // // OBJ PROP
+ return false;
+ }
+ if (isCall()) {
+ if (!bce_->emit1(JSOP_SWAP)) { // PROP THIS
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool
+PropOpEmitter::prepareForRhs()
+{
+ MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+ MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj);
+ MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+ if (isSimpleAssignment()) {
+ // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
+ if (isSuper()) {
+ if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool
+PropOpEmitter::skipObjAndRhs()
+{
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(isSimpleAssignment());
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool
+PropOpEmitter::emitDelete(JSAtom* prop)
+{
+ MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj);
+ MOZ_ASSERT_IF(isSuper(), state_ == State::Start);
+ MOZ_ASSERT(isDelete());
+
+ if (!prepareAtomIndex(prop)) {
+ return false;
+ }
+ if (isSuper()) {
+ if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE
+ return false;
+ }
+
+ // Unconditionally throw when attempting to delete a super-reference.
+ if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
+ return false; // THIS SUPERBASE
+ }
+
+ // Another wrinkle: Balance the stack from the emitter's point of view.
+ // Execution will not reach here, as the last bytecode threw.
+ if (!bce_->emit1(JSOP_POP)) { // THIS
+ return false;
+ }
+ } else {
+ JSOp op = bce_->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
+ if (!bce_->emitAtomOp(propAtomIndex_, op)) { // SUCCEEDED
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Delete;
+#endif
+ return true;
+}
+
+bool
+PropOpEmitter::emitAssignment(JSAtom* prop)
+{
+ MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+ MOZ_ASSERT(state_ == State::Rhs);
+
+ if (isSimpleAssignment()) {
+ if (!prepareAtomIndex(prop)) {
+ return false;
+ }
+ }
+
+ JSOp setOp = isSuper()
+ ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
+ : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
+ if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // VAL
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool
+PropOpEmitter::emitIncDec(JSAtom* prop)
+{
+ MOZ_ASSERT(state_ == State::Obj);
+ MOZ_ASSERT(isIncDec());
+
+ if (!emitGet(prop)) {
+ return false;
+ }
+
+ MOZ_ASSERT(state_ == State::Get);
+
+ JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+
+ if (!bce_->emit1(JSOP_POS)) { // ... N
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (!bce_->emit1(JSOP_DUP)) { // ... N N
+ return false;
+ }
+ }
+ if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1
+ return false;
+ }
+ if (!bce_->emit1(binOp)) { // ... N? N+1
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (isSuper()) { // THIS OBJ N N+1
+ if (!bce_->emit2(JSOP_PICK, 3)) { // OBJ N N+1 THIS
+ return false;
+ }
+ if (!bce_->emit1(JSOP_SWAP)) { // OBJ N THIS N+1
+ return false;
+ }
+ if (!bce_->emit2(JSOP_PICK, 3)) { // N THIS N+1 OBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOP_SWAP)) { // N THIS OBJ N+1
+ return false;
+ }
+ } else { // OBJ N N+1
+ if (!bce_->emit2(JSOP_PICK, 2)) { // N N+1 OBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOP_SWAP)) { // N OBJ N+1
+ return false;
+ }
+ }
+ }
+
+ JSOp setOp = isSuper()
+ ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
+ : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
+ if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // N? N+1
+ return false;
+ }
+ if (isPostIncDec()) {
+ if (!bce_->emit1(JSOP_POP)) { // N
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::IncDec;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/PropOpEmitter.h b/js/src/frontend/PropOpEmitter.h
new file mode 100644
index 0000000000..2c5a26ec45
--- /dev/null
+++ b/js/src/frontend/PropOpEmitter.h
@@ -0,0 +1,269 @@
+/* -*- 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_PropOpEmitter_h
+#define frontend_PropOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include <stdint.h>
+
+#include "js/TypeDecls.h"
+
+class JSAtom;
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for property operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `obj.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Get,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+//
+// `super.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Get,
+// PropOpEmitter::ObjKind::Super);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+//
+// `obj.prop();`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Call,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+// emit_call_here();
+//
+// `new obj.prop();`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Call,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+// emit_call_here();
+//
+// `delete obj.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Delete,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitDelete(atom_of_prop);
+//
+// `delete super.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Delete,
+// PropOpEmitter::ObjKind::Other);
+// poe.emitDelete(atom_of_prop);
+//
+// `obj.prop++;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::PostIncrement,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitIncDec(atom_of_prop);
+//
+// `obj.prop = value;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::SimpleAssignment,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.prepareForRhs();
+// emit(value);
+// poe.emitAssignment(atom_of_prop);
+//
+// `obj.prop += value;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::CompoundAssignment,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+// poe.prepareForRhs();
+// emit(value);
+// emit_add_op_here();
+// poe.emitAssignment(nullptr); // nullptr for CompoundAssignment
+//
+class MOZ_STACK_CLASS PropOpEmitter
+{
+ public:
+ enum class Kind {
+ Get,
+ Call,
+ Set,
+ Delete,
+ PostIncrement,
+ PreIncrement,
+ PostDecrement,
+ PreDecrement,
+ SimpleAssignment,
+ CompoundAssignment
+ };
+ enum class ObjKind {
+ Super,
+ Other
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ Kind kind_;
+ ObjKind objKind_;
+
+ // The index for the property name's atom.
+ uint32_t propAtomIndex_ = 0;
+
+ // Whether the property name is `length` or not.
+ bool isLength_ = false;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // skipObjAndRhs
+ // +----------------------------+
+ // | |
+ // +-------+ | prepareForObj +-----+ |
+ // | Start |-+-------------->| Obj |-+ |
+ // +-------+ +-----+ | |
+ // | |
+ // +---------------------------------+ |
+ // | |
+ // | |
+ // | [Get] |
+ // | [Call] |
+ // | emitGet +-----+ |
+ // +---------->| Get | |
+ // | +-----+ |
+ // | |
+ // | [Delete] |
+ // | emitDelete +--------+ |
+ // +------------->| Delete | |
+ // | +--------+ |
+ // | |
+ // | [PostIncrement] |
+ // | [PreIncrement] |
+ // | [PostDecrement] |
+ // | [PreDecrement] |
+ // | emitIncDec +--------+ |
+ // +------------->| IncDec | |
+ // | +--------+ |
+ // | |
+ // | [SimpleAssignment] |
+ // | prepareForRhs | +-----+
+ // +--------------------->+-------------->+->| Rhs |-+
+ // | ^ +-----+ |
+ // | | |
+ // | | +---------+
+ // | [CompoundAssignment] | |
+ // | emitGet +-----+ | | emitAssignment +------------+
+ // +---------->| Get |----+ + -------------->| Assignment |
+ // +-----+ +------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling prepareForObj.
+ Obj,
+
+ // After calling emitGet.
+ Get,
+
+ // After calling emitDelete.
+ Delete,
+
+ // After calling emitIncDec.
+ IncDec,
+
+ // After calling prepareForRhs or skipObjAndRhs.
+ Rhs,
+
+ // After calling emitAssignment.
+ Assignment,
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
+
+ private:
+ MOZ_MUST_USE bool isCall() const {
+ return kind_ == Kind::Call;
+ }
+
+ MOZ_MUST_USE bool isSuper() const {
+ return objKind_ == ObjKind::Super;
+ }
+
+ MOZ_MUST_USE bool isSimpleAssignment() const {
+ return kind_ == Kind::SimpleAssignment;
+ }
+
+ MOZ_MUST_USE bool isDelete() const {
+ return kind_ == Kind::Delete;
+ }
+
+ MOZ_MUST_USE bool isCompoundAssignment() const {
+ return kind_ == Kind::CompoundAssignment;
+ }
+
+ MOZ_MUST_USE bool isIncDec() const {
+ return isPostIncDec() || isPreIncDec();
+ }
+
+ MOZ_MUST_USE bool isPostIncDec() const {
+ return kind_ == Kind::PostIncrement ||
+ kind_ == Kind::PostDecrement;
+ }
+
+ MOZ_MUST_USE bool isPreIncDec() const {
+ return kind_ == Kind::PreIncrement ||
+ kind_ == Kind::PreDecrement;
+ }
+
+ MOZ_MUST_USE bool isInc() const {
+ return kind_ == Kind::PostIncrement ||
+ kind_ == Kind::PreIncrement;
+ }
+
+ MOZ_MUST_USE bool
+ prepareAtomIndex(JSAtom* prop);
+
+ public:
+ MOZ_MUST_USE bool prepareForObj();
+
+ MOZ_MUST_USE bool emitGet(JSAtom* prop);
+
+ MOZ_MUST_USE bool prepareForRhs();
+ MOZ_MUST_USE bool skipObjAndRhs();
+
+ MOZ_MUST_USE bool emitDelete(JSAtom* prop);
+
+ // `prop` can be nullptr for CompoundAssignment.
+ MOZ_MUST_USE bool emitAssignment(JSAtom* prop);
+
+ MOZ_MUST_USE bool emitIncDec(JSAtom* prop);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_PropOpEmitter_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<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;
+}
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<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 */
diff --git a/js/src/frontend/ValueUsage.h b/js/src/frontend/ValueUsage.h
new file mode 100644
index 0000000000..9bb6458e21
--- /dev/null
+++ b/js/src/frontend/ValueUsage.h
@@ -0,0 +1,28 @@
+/* -*- 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_ValueUsage_h
+#define frontend_ValueUsage_h
+
+namespace js {
+namespace frontend {
+
+// Used to control whether JSOP_CALL_IGNORES_RV is emitted for function calls.
+enum class ValueUsage {
+ // Assume the value of the current expression may be used. This is always
+ // correct but prohibits JSOP_CALL_IGNORES_RV.
+ WantValue,
+
+ // Pass this when emitting an expression if the expression's value is
+ // definitely unused by later instructions. You must make sure the next
+ // instruction is JSOP_POP, a jump to a JSOP_POP, or something similar.
+ IgnoreValue
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ValueUsage_h */
diff --git a/js/src/moz.build b/js/src/moz.build
index d54547aecf..1754f1d2cd 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -140,9 +140,16 @@ UNIFIED_SOURCES += [
'ds/MemoryProtectionExceptionHandler.cpp',
'frontend/BytecodeCompiler.cpp',
'frontend/BytecodeEmitter.cpp',
+ 'frontend/CallOrNewEmitter.cpp',
+ 'frontend/ElemOpEmitter.cpp',
'frontend/FoldConstants.cpp',
+ 'frontend/IfEmitter.cpp',
+ 'frontend/JumpList.cpp',
'frontend/NameFunctions.cpp',
+ 'frontend/NameOpEmitter.cpp',
'frontend/ParseNode.cpp',
+ 'frontend/PropOpEmitter.cpp',
+ 'frontend/TDZCheckCache.cpp',
'frontend/TokenStream.cpp',
'gc/Allocator.cpp',
'gc/Barrier.cpp',