diff options
author | Martok <martok@martoks-place.de> | 2023-03-12 22:46:53 +0100 |
---|---|---|
committer | Martok <martok@martoks-place.de> | 2023-03-13 00:37:54 +0100 |
commit | b26511f287f02c4cfc7b124689cef0b459466fc1 (patch) | |
tree | 7e32e998343dadc45f36e23f084cca6dc0d7493d | |
parent | da908629c12a465e5a98e7e6494a7224867ab702 (diff) | |
download | uxp-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.cpp | 1863 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 71 | ||||
-rw-r--r-- | js/src/frontend/CallOrNewEmitter.cpp | 319 | ||||
-rw-r--r-- | js/src/frontend/CallOrNewEmitter.h | 338 | ||||
-rw-r--r-- | js/src/frontend/ElemOpEmitter.cpp | 288 | ||||
-rw-r--r-- | js/src/frontend/ElemOpEmitter.h | 278 | ||||
-rw-r--r-- | js/src/frontend/NameOpEmitter.cpp | 382 | ||||
-rw-r--r-- | js/src/frontend/NameOpEmitter.h | 193 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.h | 12 | ||||
-rw-r--r-- | js/src/frontend/PropOpEmitter.cpp | 275 | ||||
-rw-r--r-- | js/src/frontend/PropOpEmitter.h | 269 | ||||
-rw-r--r-- | js/src/frontend/ValueUsage.h | 28 | ||||
-rw-r--r-- | js/src/moz.build | 4 |
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, ¶mLoc](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', |