summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartok <martok@martoks-place.de>2023-03-12 22:46:53 +0100
committerMartok <martok@martoks-place.de>2023-03-13 00:37:54 +0100
commitb26511f287f02c4cfc7b124689cef0b459466fc1 (patch)
tree7e32e998343dadc45f36e23f084cca6dc0d7493d
parentda908629c12a465e5a98e7e6494a7224867ab702 (diff)
downloaduxp-b26511f287f02c4cfc7b124689cef0b459466fc1.tar.gz
Issue #2155 - Add PropOpEmitter, ElemOpEmitter, NameOpEmitter, CallOrNewEmitter
Attn: this plain patch port still contains issues caused by differences in the ParseNode tree that will be fixed in the following commits. Based-on: m-c 1466000/{7-10}
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp1863
-rw-r--r--js/src/frontend/BytecodeEmitter.h71
-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/NameOpEmitter.cpp382
-rw-r--r--js/src/frontend/NameOpEmitter.h193
-rw-r--r--js/src/frontend/ParseNode.h12
-rw-r--r--js/src/frontend/PropOpEmitter.cpp275
-rw-r--r--js/src/frontend/PropOpEmitter.h269
-rw-r--r--js/src/frontend/ValueUsage.h28
-rw-r--r--js/src/moz.build4
13 files changed, 3234 insertions, 1086 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index 130c795b87..d2b47089a0 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -27,8 +27,12 @@
#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"
@@ -969,7 +973,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;
}
@@ -2346,11 +2353,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
@@ -2373,6 +2390,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));
@@ -2784,7 +2816,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.
@@ -2802,7 +2833,15 @@ BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
if (!makeAtomIndex(atom, &index))
return false;
- return emitIndexOp(op, index);
+ return emitAtomOp(index, op);
+}
+
+bool
+BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op)
+{
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+
+ return emitIndexOp(op, atomIndex);
}
bool
@@ -2888,15 +2927,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)
{
@@ -3609,57 +3639,9 @@ BytecodeEmitter::emitToIteratorResult(bool done)
bool
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");
+ NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
+ if (!noe.emitGet()) {
+ return false;
}
return true;
@@ -3671,156 +3653,6 @@ BytecodeEmitter::emitGetName(ParseNode* pn)
return emitGetName(pn->name());
}
-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;
-}
-
bool
BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
{
@@ -3897,245 +3729,56 @@ BytecodeEmitter::emitPropLHS(ParseNode* pn)
}
bool
-BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall)
-{
- if (!emitGetThisForSuperBase(superBase)) { // THIS
- return false;
- }
- if (isCall && !emit1(JSOP_DUP)) { // THIS? THIS
- return false;
- }
- if (!emit1(JSOP_SUPERBASE)) { // THIS? THIS 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->pn_atom, 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)) { // THIS? THIS SUPERBASE
- return false;
- }
-
- if (!emitAtomOp(pn->pn_atom, op)) { // THIS? PROP
- return false;
- }
-
- if (isCall && !emit1(JSOP_SWAP)) { // PROP THIS
- 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->pn_atom, isSuper ? JSOP_GETPROP_SUPER : JSOP_GETPROP)) {
- return false; // THIS? OBJ V
}
- if (!emit1(JSOP_POS)) { // ... N
- return false;
- }
- if (post) {
- if (!emit1(JSOP_DUP)) { // ... N N
- return false;
- }
- }
- if (!emit1(JSOP_ONE)) { // ... N? N 1
- return false;
- }
- if (!emit1(binop)) { // ... N? N+1
+ if (!poe.emitIncDec(prop->nameAtom())) { // RESULT
return false;
}
- if (post) {
- if (isSuper) { // THIS OBJ N N+1
- if (!emit2(JSOP_PICK, 3)) { // OBJ N N+1 THIS
- return false;
- }
- if (!emit1(JSOP_SWAP)) { // OBJ N THIS N+1
- return false;
- }
- if (!emit2(JSOP_PICK, 3)) { // N THIS N+1 OBJ
- return false;
- }
- if (!emit1(JSOP_SWAP)) { // N THIS OBJ N+1
- return false;
- }
- } else { // OBJ N N+1
- if (!emit2(JSOP_PICK, 2)) { // N N+1 OBJ
- return false;
- }
- if (!emit1(JSOP_SWAP)) { // N 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->pn_atom, setOp)) // N? N+1
- return false;
- if (post) {
- if (!emit1(JSOP_POP)) { // N
- 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)) // 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))
- 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)) // OBJ
- return false;
-
- if (opts == EmitElemOption::IncDec) {
- if (!emit1(JSOP_CHECKOBJCOERCIBLE)) // OBJ
- return false;
- } else if (opts == EmitElemOption::Call) {
- if (!emit1(JSOP_DUP)) // OBJ OBJ
- return false;
- }
-
- if (!emitTree(pn->pn_right)) // OBJ? OBJ KEY
- 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)) // OBJ KEY
- 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());
+ MOZ_ASSERT(incDec->pn_kid->isKind(PNK_NAME));
- // 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.
-
- if (!emitTree(pn->pn_right))
+ ParseNodeKind kind = incDec->getKind();
+ NameNode* name = &incDec->pn_kid->as<NameNode>();
+ NameOpEmitter noe(this, name->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;
}
@@ -4150,41 +3793,37 @@ 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;
+ if (isSuper) {
+ if (!eoe.prepareForObj()) { //
+ return false;
+ }
+ UnaryNode* base = &elem->expression().as<UnaryNode>();
+ if (!emitGetThisForSuperBase(base)) { // THIS
+ return false;
+ }
+ if (!eoe.prepareForKey()) { // THIS
+ return false;
+ }
+ if (!emitTree(&elem->key())) { // THIS KEY
+ return false;
+ }
- if (!emitElemOperands(pn, opts)) { // OBJ? OBJ KEY
+ return true;
+ }
+
+ if (!eoe.prepareForObj()) { //
return false;
}
- if (!emitElemOpBase(op)) { // OBJ? ELEM
+ if (!emitTree(&elem->expression())) { // OBJ
return false;
}
- return true;
-}
-
-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;
-
- if (!emitSuperElemOperands(pn, opts))
+ if (!eoe.prepareForKey()) { // OBJ? OBJ
return false;
- if (!emitElemOpBase(op))
+ }
+ if (!emitTree(&elem->key())) { // OBJ? OBJ KEY
return false;
-
- if (isCall) {
- if (!emit1(JSOP_SWAP)) { // VALUE THIS
- return false;
- }
}
return true;
@@ -4193,74 +3832,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; // THIS KEY OBJ
- } else {
- if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec))
- return false; // OBJ KEY
- }
-
- 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 ELEM
- 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;
}
@@ -4726,21 +4318,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.
@@ -4757,7 +4334,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
@@ -4963,28 +4565,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()))
- return false; // THIS SUPERBASE
+ 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) {
+ UnaryNode* base = &prop->expression().as<UnaryNode>();
+ if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE
+ return false;
+ }
+ // SUPERBASE is pushed onto THIS in poe.prepareForRhs below.
*emitted = 2;
} else {
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; // THIS KEY OBJ
+ 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; // OBJ KEY
*emitted = 2;
}
+ if (!eoe.prepareForRhs()) { // [Super]
+ // // THIS KEY SUPERBASE
+ // // [Other]
+ // // OBJ KEY
+ return false;
+ }
break;
}
@@ -5024,38 +4661,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: {
@@ -5064,45 +4676,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()) // THIS OBJ VAL
- setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER;
- else // OBJ VAL
- setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
- if (!emitAtomOp(target->pn_atom, setOp)) // VAL
+ // // [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()) { // THIS KEY OBJ VAL
- JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER;
- // emitDestructuringLHSRef already did emitSuperElemOperands
- // part of emitSuperElemOp. Perform remaining part here.
- if (!emitElemOpBase(setOp)) // VAL
- return false;
- } else { // OBJ KEY VAL
- JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
- if (!emitElemOpBase(setOp)) // VAL
- 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;
}
@@ -5118,7 +4773,7 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri
}
// Pop the assigned value.
- if (!emit1(JSOP_POP))
+ if (!emit1(JSOP_POP)) // !STACK EMPTY!
return false;
}
@@ -5446,7 +5101,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;
}
@@ -6100,25 +5755,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(ParseNodeKind::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
@@ -6146,77 +5809,97 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
// 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 = [lhs, compoundOp, rhs, isCompound](BytecodeEmitter* bce,
- const NameLocation& lhsLoc,
- bool emittedBindOp)
- {
- // For compound assignments, first get the LHS value, then emit
- // the RHS and the compoundOp.
- if (isCompound) {
- 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->pn_atom, 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;
+ }
+ if (rhs && rhs->isDirectRHSAnonFunction()) {
+ MOZ_ASSERT(!lhs->isInParens());
+ MOZ_ASSERT(!isCompound);
+ RootedAtom name(cx, lhs->name());
+ if (!setOrEmitSetFunName(rhs, name)) { // ENV? VAL? RHS
return false;
-
- if (!lhs->isInParens() && !isCompound && 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 (isCompound && !bce->emit1(compoundOp))
+ // 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()))
- return false; // THIS SUPERBASE
+ 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) {
+ UnaryNode* base = &prop->expression().as<UnaryNode>();
+ if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE
+ return false;
+ }
+ // SUPERBASE is pushed onto THIS later in poe->emitGet below.
offset += 2;
} else {
- if (!emitTree(lhs->expr())) // OBJ
+ 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 = isCompound ? EmitElemOption::CompoundAssign : EmitElemOption::Get;
- if (lhs->as<PropertyByValue>().isSuper()) {
- if (!emitSuperElemOperands(lhs, opt)) // THIS KEY OBJ
- 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)) // OBJ KEY
- return false;
offset += 2;
}
break;
@@ -6245,41 +5928,19 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
MOZ_ASSERT(rhs);
switch (lhs->getKind()) {
case PNK_DOT: {
- JSOp getOp;
- if (lhs->as<PropertyAccess>().isSuper()) {
- if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ
- return false;
- getOp = JSOP_GETPROP_SUPER;
- } else {
- if (!emit1(JSOP_DUP)) // OBJ OBJ
- return false;
- bool isLength = (lhs->pn_atom == cx->names().length);
- getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP;
- }
- if (!emitIndex32(getOp, atomIndex)) // THIS? OBJ VAL
+ 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)) // THIS KEY OBJ THIS
- return false;
- if (!emitDupAt(2)) // THIS KEY OBJ THIS KEY
- return false;
- if (!emitDupAt(2)) // THIS KEY OBJ THIS KEY OBJ
- return false;
- elemOp = JSOP_GETELEM_SUPER;
- } else {
- if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY
- return false;
- elemOp = JSOP_GETELEM;
- }
- if (!emitElemOpBase(elemOp)) // [Super]
- // // THIS KEY OBJ ELEM
- // // [Other]
- // // OBJ KEY ELEM
+ if (!eoe->emitGet()) { // KEY THIS OBJ ELEM
return false;
+ }
break;
}
case PNK_CALL:
@@ -6293,6 +5954,35 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
}
}
+ 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;
@@ -6307,22 +5997,23 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
/* 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)) // VAL
+ 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)) // VAL
+ if (!eoe->emitAssignment()) { // VAL
return false;
+ }
+
+ eoe.reset();
break;
}
case PNK_ARRAY:
@@ -7130,26 +6821,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),
@@ -7358,12 +7053,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))
@@ -8013,12 +7712,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.
@@ -8040,7 +7733,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;
@@ -8199,18 +7899,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;
@@ -9108,21 +8812,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.
+ UnaryNode* base = &propExpr->expression().as<UnaryNode>();
+ 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
@@ -9131,27 +8855,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))
+ UnaryNode* base = &elemExpr->expression().as<UnaryNode>();
+ if (!emitGetThisForSuperBase(base)) { // THIS
return false;
- if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER))
+ }
+ if (!eoe.prepareForKey()) { // THIS
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);
+ }
+ if (!emitTree(&elemExpr->key())) { // THIS KEY
+ return false;
+ }
+ } 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
@@ -9511,53 +9256,24 @@ 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: {
- JSAtom* name = calleeNode->name();
- NameLocation loc = lookupName(name);
- if (!emitGetNameAtLocation(name, loc)) { // CALLEE
- return false;
- }
- // Need to provide |this| value for call.
- if (isCall) {
- 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");
- }
- }
- break;
+ if (!cone.emitNameCallee(calleeNode->name())) { // CALLEE THIS
+ return false;
+ }
+ break;
}
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;
@@ -9565,7 +9281,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;
@@ -9573,7 +9291,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;
@@ -9581,51 +9301,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;
}
@@ -9649,10 +9358,21 @@ BytecodeEmitter::emitOptionalCall(
OptionalEmitter& oe,
ValueUsage valueUsage)
{
- bool isCall = true;
ParseNode* calleeNode = callNode->pn_head;
+ bool isCall = true;
+ bool isSpread = IsSpreadOp(callNode->getOp());
+ ListNode* argsList = &callNode->pn_right->as<ListNode>();
+ JSOp op = callNode->getOp();
+ uint32_t argc = argsList->pn_count;
+
+ CallOrNewEmitter cone(this, op,
+ isSpread && (argc == 1) &&
+ isRestParameter(argsList->pn_head->as<UnaryNode>().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;
}
@@ -9664,13 +9384,92 @@ BytecodeEmitter::emitOptionalCall(
}
}
- if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) {
+ if (!emitArguments(argsList, /* isCall = */ true, isSpread, cone)) {
+ // [stack] CALLEE THIS ARGS...
+ return false;
+ }
+
+ ParseNode* coordNode = getCoordNode(callNode, calleeNode, argsList);
+ if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) {
+ // [stack] RVAL
return false;
}
return true;
}
+ParseNode* BytecodeEmitter::getCoordNode(ParseNode* pn,
+ ParseNode* calleeNode,
+ ParseNode* argsList) {
+ 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
+ coordNode = argsList;
+
+ 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(ListNode* argsList, bool isCall, bool isSpread,
+ CallOrNewEmitter& cone)
+{
+ uint32_t argc = argsList->pn_count;
+ if (argc >= ARGC_LIMIT) {
+ reportError(argsList, 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 = argsList->pn_head; arg; arg = arg->pn_next) {
+ if (!emitTree(arg)) {
+ return false;
+ }
+ }
+ } else {
+ if (cone.wantSpreadOperand()) {
+ UnaryNode* spreadNode = &argsList->pn_head->as<UnaryNode>();
+ if (!emitTree(spreadNode->pn_kid)) { // CALLEE THIS ARG0
+ return false;
+ }
+ }
+ if (!cone.emitSpreadArgumentsTest()) { // CALLEE THIS
+ return false;
+ }
+ if (!emitArray(argsList->pn_head, argc, JSOP_NEWARRAY)) { // CALLEE THIS ARR
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool
BytecodeEmitter::emitCallOrNew(
ParseNode* callNode,
@@ -9693,10 +9492,12 @@ BytecodeEmitter::emitCallOrNew(
*/
ParseNode* calleeNode = callNode->pn_head;
bool isCall = callNode->isKind(PNK_CALL) || callNode->isKind(PNK_TAGGED_TEMPLATE);
+ bool isSpread = IsSpreadOp(callNode->getOp());
+ ListNode* argsList = &callNode->pn_right->as<ListNode>();
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.
@@ -9716,12 +9517,25 @@ BytecodeEmitter::emitCallOrNew(
// Fall through.
}
- if (!emitCalleeAndThis(callNode, calleeNode, isCall)) {
+ JSOp op = callNode->getOp();
+ uint32_t argc = argsList->pn_count;
+ CallOrNewEmitter cone(this, op,
+ isSpread && (argc == 1) &&
+ isRestParameter(argsList->pn_head->as<UnaryNode>().pn_kid)
+ ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest
+ : CallOrNewEmitter::ArgumentsKind::Other,
+ valueUsage);
+ if (!emitCalleeAndThis(callNode, calleeNode, cone)) { // CALLEE THIS
return false;
}
+ if (!emitArguments(argsList, isCall, isSpread, cone)) {
+ return false; // CALLEE THIS ARGS...
+ }
- if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) {
- return false;
+ ParseNode* coordNode = getCoordNode(callNode, calleeNode, argsList);
+
+ if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) {
+ return false; // RVAL
}
return true;
@@ -9731,122 +9545,82 @@ bool
BytecodeEmitter::emitCalleeAndThis(
ParseNode* callNode,
ParseNode* calleeNode,
- bool isCall)
+ CallOrNewEmitter& cone)
{
switch (calleeNode->getKind()) {
case PNK_NAME: {
- JSAtom* name = calleeNode->name();
- NameLocation loc = lookupName(name);
- if (!emitGetNameAtLocation(name, loc)) { // CALLEE
- return false;
- }
- // Need to provide |this| value for call.
- if (isCall) {
- 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");
- }
- }
- break;
+ 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)) { // CALLEE THIS?
+ PropertyAccess* prop = &calleeNode->as<PropertyAccess>();
+ bool isSuper = prop->isSuper();
+ PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
+ if (!poe.prepareForObj()) {
+ return false;
+ }
+ if (isSuper) {
+ UnaryNode* base = &prop->expression().as<UnaryNode>();
+ if (!emitGetThisForSuperBase(base)) { // THIS
return false;
}
} else {
- if (!emitPropOp(calleeNode, isCall ? JSOP_CALLPROP : JSOP_GETPROP)) { // CALLEE THIS?
+ 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)) { // CALLEE THIS?
- return false;
- }
- } else {
- if (isCall) {
- if (!emitElemOp(calleeNode, JSOP_CALLELEM)) { // THIS CALLEE
- return false;
- }
- if (!emit1(JSOP_SWAP)) { // CALLEE THIS
- return false;
- }
- } else {
- if (!emitElemOp(calleeNode, JSOP_GETELEM)) { // CALLEE
- 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 (!cone.prepareForOtherCallee()) {
+ return false;
+ }
if (!emitTree(calleeNode)) {
return false;
}
- isCall = false; /* trigger JSOP_UNDEFINED after */
break;
}
- if (!emitCallOrNewThis(callNode, isCall)) {
+ if (!cone.emitThis()) { // CALLEE THIS
return false;
}
@@ -10618,14 +10392,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;
@@ -10766,35 +10542,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;
}
}
@@ -10819,11 +10587,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;
@@ -10906,14 +10677,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
@@ -11298,25 +11076,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) {
+ UnaryNode* base = &prop->expression().as<UnaryNode>();
+ 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:
@@ -11506,30 +11311,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:
@@ -11589,7 +11404,7 @@ bool
BytecodeEmitter::emitCalleeAndThisForOptionalChain(
ParseNode* optionalChain,
ParseNode* callNode,
- bool isCall)
+ CallOrNewEmitter& cone)
{
ParseNode* calleeNode = optionalChain->pn_kid;
@@ -11597,7 +11412,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;
}
@@ -11646,12 +11461,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) {
@@ -11677,30 +11494,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->pn_atom, opForEmit)) {
- return false;
- }
- if (isCall && !emit1(JSOP_SWAP)) {
+ if (!poe.emitGet(prop->nameAtom())) {
+ // [stack] PROP
return false;
}
@@ -11710,20 +11505,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;
}
@@ -11740,32 +11538,19 @@ 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 (isCall) {
- if (!emitElemOpBase(JSOP_CALLELEM)) {
- return false;
- }
- if (!emit1(JSOP_SWAP)) {
- return false;
- }
- } else {
- if (!emitElemOpBase(JSOP_GETELEM)) {
- return false;
- }
+ if (!eoe.emitGet()) {
+ // [stack] ELEM
+ return false;
}
return true;
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
index 0da65a16d9..025d6035fe 100644
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -18,6 +18,7 @@
#include "frontend/Parser.h"
#include "frontend/SharedContext.h"
#include "frontend/SourceNotes.h"
+#include "frontend/ValueUsage.h"
#include "vm/Interpreter.h"
class OptionalEmitter;
@@ -119,11 +120,9 @@ static size_t MaxSrcNotesLength = INT32_MAX;
typedef Vector<jsbytecode, 256> BytecodeVector;
typedef Vector<jssrcnote, 64> SrcNotesVector;
-enum class ValueUsage {
- WantValue,
- IgnoreValue
-};
-
+class CallOrNewEmitter;
+class ElemOpEmitter;
+class PropOpEmitter;
class TDZCheckCache;
struct MOZ_STACK_CLASS BytecodeEmitter
@@ -447,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);
@@ -481,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);
@@ -494,6 +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(uint32_t atomIndex, JSOp op);
MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn);
MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count, JSOp op);
@@ -531,37 +536,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
}
MOZ_MUST_USE bool emitGetName(ParseNode* pn);
- 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 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,
@@ -590,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);
@@ -605,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);
@@ -715,7 +692,7 @@ 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);
@@ -738,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);
@@ -777,14 +752,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
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* argsList);
+ MOZ_MUST_USE bool emitArguments(ListNode* argsList, 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);
@@ -833,8 +812,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/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..6a397ade4e 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -1248,6 +1248,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 +1306,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/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 6ab55538b9..1754f1d2cd 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -140,11 +140,15 @@ 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',