From 356f75e7a4345ca79be72dac31d1aa2002fc5d3c Mon Sep 17 00:00:00 2001 From: roytam1 Date: Mon, 1 May 2023 13:30:57 +0800 Subject: [PATCH] import from UXP: Issue #2142 - Add PropertyEmitter, ObjectEmitter, ClassEmitter, LexicalScopeEmitter, DefaultEmitter (1b89be6d) --- js/src/frontend/BytecodeEmitter.cpp | 626 ++++++++++--------- js/src/frontend/BytecodeEmitter.h | 20 +- js/src/frontend/DefaultEmitter.cpp | 73 +++ js/src/frontend/DefaultEmitter.h | 65 ++ js/src/frontend/EmitterScope.cpp | 4 +- js/src/frontend/EmitterScope.h | 4 +- js/src/frontend/LexicalScopeEmitter.cpp | 60 ++ js/src/frontend/LexicalScopeEmitter.h | 99 +++ js/src/frontend/ObjectEmitter.cpp | 762 ++++++++++++++++++++++++ js/src/frontend/ObjectEmitter.h | 727 ++++++++++++++++++++++ js/src/moz.build | 3 + 11 files changed, 2139 insertions(+), 304 deletions(-) create mode 100644 js/src/frontend/DefaultEmitter.cpp create mode 100644 js/src/frontend/DefaultEmitter.h create mode 100644 js/src/frontend/LexicalScopeEmitter.cpp create mode 100644 js/src/frontend/LexicalScopeEmitter.h create mode 100644 js/src/frontend/ObjectEmitter.cpp create mode 100644 js/src/frontend/ObjectEmitter.h diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index a63b2e18d..a1024fd44 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -31,11 +31,14 @@ #include "ds/Nestable.h" #include "frontend/BytecodeControlStructures.h" #include "frontend/CallOrNewEmitter.h" +#include "frontend/DefaultEmitter.h" // DefaultEmitter #include "frontend/ElemOpEmitter.h" #include "frontend/EmitterScope.h" #include "frontend/ForOfLoopControl.h" #include "frontend/IfEmitter.h" +#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter #include "frontend/NameOpEmitter.h" +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter #include "frontend/Parser.h" #include "frontend/PropOpEmitter.h" #include "frontend/SwitchEmitter.h" @@ -3010,62 +3013,70 @@ BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, In bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { - if (!emit1(JSOP_DUP)) // VALUE VALUE + // [stack] VALUE + + DefaultEmitter de(this); + if (!de.prepareForDefault()) { + // [stack] return false; - if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED + } + if (!emitInitializer(defaultExpr, pattern)) { + // [stack] DEFAULTVALUE return false; - if (!emit1(JSOP_STRICTEQ)) // VALUE EQL? - return false; - // Emit source note to enable ion compilation. - if (!newSrcNote(SRC_IF)) - return false; - JumpList jump; - if (!emitJump(JSOP_IFEQ, &jump)) // VALUE - return false; - if (!emit1(JSOP_POP)) // . - return false; - if (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE - return false; - if (!emitJumpTargetAndPatch(jump)) + } + if (!de.emitEnd()) { + // [stack] VALUE/DEFAULTVALUE return false; + } return true; } bool -BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind) +BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name) { if (maybeFun->is()) { // Function doesn't have 'name' property at this point. // Set function's name at compile time. - RootedFunction fun(cx, maybeFun->as().funbox()->function()); + return setFunName(maybeFun->as().funbox()->function(), name); + } - // Single node can be emitted multiple times if it appears in - // array destructuring default. If function already has a name, - // just return. - if (fun->hasCompileTimeName()) { + MOZ_ASSERT(maybeFun->isKind(PNK_CLASS)); + + return emitSetClassConstructorName(name); +} + +bool +BytecodeEmitter::setFunName(JSFunction* fun, JSAtom* name) +{ + // Single node can be emitted multiple times if it appears in + // array destructuring default. If function already has a name, + // just return. + if (fun->hasCompileTimeName()) { #ifdef DEBUG - RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); - if (!funName) - return false; - MOZ_ASSERT(funName == fun->compileTimeName()); -#endif - return true; - } - - RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + RootedAtom funName(cx, name); if (!funName) return false; - fun->setCompileTimeName(name); + MOZ_ASSERT(funName == fun->compileTimeName()); +#endif return true; } + RootedAtom funName(cx, name); + if (!funName) + return false; + fun->setCompileTimeName(funName); + return true; +} + +bool +BytecodeEmitter::emitSetClassConstructorName(JSAtom* name) +{ uint32_t nameIndex; if (!makeAtomIndex(name, &nameIndex)) return false; if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME return false; - uint8_t kind = uint8_t(prefixKind); + uint8_t kind = uint8_t(FunctionPrefixKind::None); if (!emit2(JSOP_SETFUNNAME, kind)) // FUN return false; return true; @@ -3088,13 +3099,6 @@ BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) return true; } -bool -BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern) -{ - TDZCheckCache tdzCache(this); - return emitInitializer(initializer, pattern); -} - bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, DestructuringFlavor flav) { @@ -4230,7 +4234,7 @@ BytecodeEmitter::emitCatch(TernaryNode* catchNode) break; case PNK_NAME: - if (!emitLexicalInitialization(pn2)) + if (!emitLexicalInitialization(&pn2->as())) return false; if (!emit1(JSOP_POP)) return false; @@ -4454,11 +4458,21 @@ BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLi MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) { - TDZCheckCache tdzCache(this); + LexicalScopeEmitter lse(this); ParseNode* body = lexicalScope->scopeBody(); - if (lexicalScope->isEmptyScope()) - return emitLexicalScopeBody(body); + if (lexicalScope->isEmptyScope()) { + if (!lse.emitEmptyScope()) + return false; + + if (!emitLexicalScopeBody(body)) + return false; + + if (!lse.emitEnd()) + return false; + + return true; + } // Update line number notes before emitting TDZ poison in // EmitterScope::enterLexical to avoid spurious pausing on seemingly @@ -4484,7 +4498,6 @@ BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) return false; } - EmitterScope emitterScope(this); ScopeKind kind; if (body->isKind(PNK_CATCH)) { TernaryNode* catchNode = &body->as(); @@ -4494,21 +4507,21 @@ BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) } else kind = ScopeKind::Lexical; - if (!emitterScope.enterLexical(this, kind, lexicalScope->scopeBindings())) + if (!lse.emitScope(kind, lexicalScope->scopeBindings())) return false; if (body->isKind(PNK_FOR)) { // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are // lexical declarations in the head. Signal this by passing a // non-nullptr lexical scope. - if (!emitFor(&body->as(), &emitterScope)) + if (!emitFor(&body->as(), &lse.emitterScope())) return false; } else { if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) return false; } - return emitterScope.leave(this); + return lse.emitEnd(); } bool @@ -4834,7 +4847,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) } bool -BytecodeEmitter::emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitForOf(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); @@ -5007,7 +5020,7 @@ BytecodeEmitter::emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterSco } bool -BytecodeEmitter::emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitForIn(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); MOZ_ASSERT(forNode->isOp(JSOP_ITER)); @@ -5156,7 +5169,7 @@ BytecodeEmitter::emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterSco /* C-style `for (init; cond; update) ...` loop. */ bool -BytecodeEmitter::emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { LoopControl loopInfo(this, StatementKind::ForLoop); @@ -5342,7 +5355,7 @@ BytecodeEmitter::emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitte } bool -BytecodeEmitter::emitFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); @@ -7792,8 +7805,10 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional, } bool -BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type) +BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type) { + // [stack] CTOR? OBJ + for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { // Skip over class fields and emit them at the end. This is needed @@ -7801,163 +7816,235 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, // into a local variable continue; } - if (!updateSourceCoordNotes(propdef->pn_pos.begin)) - return false; // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(PNK_MUTATEPROTO)) { + // [stack] OBJ MOZ_ASSERT(type == ObjectLiteral); - if (!emitTree(propdef->as().kid())) + if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { + // [stack] OBJ return false; - objp.set(nullptr); - if (!emit1(JSOP_MUTATEPROTO)) + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ + return false; + } continue; } if (propdef->isKind(PNK_SPREAD)) { MOZ_ASSERT(type == ObjectLiteral); - - if (!emit1(JSOP_DUP)) + // [stack] OBJ + if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ return false; - - if (!emitTree(propdef->as().kid())) + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL return false; - - if (!emitCopyDataProperties(CopyOption::Unfiltered)) + } + if (!pe.emitSpread()) { + // [stack] OBJ return false; - - objp.set(nullptr); + } continue; } - bool extraPop = false; - if (type == ClassBody && propdef->as().isStatic()) { - extraPop = true; - if (!emit1(JSOP_DUP2)) - return false; - if (!emit1(JSOP_POP)) - return false; - } - /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ - ParseNode* key = propdef->as().left(); - bool isIndex = false; - if (key->isKind(PNK_NUMBER)) { - if (!emitNumberOp(key->as().value())) - return false; - isIndex = true; - } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { - // EmitClass took care of constructor already. - if (type == ClassBody && key->as().atom() == cx->names().constructor && - !propdef->as().isStatic()) - { - continue; - } - } else { - MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME)); - if (!emitComputedPropertyName(&key->as())) - return false; - isIndex = true; - } - - /* Emit code for the property initializer. */ - ParseNode* propVal = propdef->as().right(); - if (!emitTree(propVal)) - return false; + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + ParseNode* propVal = prop->right(); + bool isPropertyAnonFunctionOrClass = propVal->isDirectRHSAnonFunction(); JSOp op = propdef->getOp(); - MOZ_ASSERT(op == JSOP_INITPROP || - op == JSOP_INITPROP_GETTER || + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); - FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get - : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set - : FunctionPrefixKind::None; + auto emitValue = [this, &propVal, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? - if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) - objp.set(nullptr); - - if (propVal->is() && - propVal->as().funbox()->needsHomeObject()) { - FunctionBox* funbox = propVal->as().funbox(); - MOZ_ASSERT(funbox->function()->allowSuperProperty()); - bool isAsync = funbox->isAsync(); - if (isAsync) { - if (!emit1(JSOP_SWAP)) - return false; - } - if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) + if (!emitTree(propVal)) { + // [stack] CTOR? OBJ CTOR? KEY? VAL return false; - if (isAsync) { - if (!emit1(JSOP_POP)) - return false; } - } - // Class methods are not enumerable. - if (type == ClassBody) { - switch (op) { - case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; - default: MOZ_CRASH("Invalid op"); - } - } + if (propVal->is() && + propVal->as().funbox()->needsHomeObject()) { + FunctionBox* funbox = propVal->as().funbox(); + MOZ_ASSERT(funbox->function()->allowSuperProperty()); - if (isIndex) { - objp.set(nullptr); - switch (op) { - case JSOP_INITPROP: op = JSOP_INITELEM; break; - case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; - case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; - case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; - default: MOZ_CRASH("Invalid op"); - } - if (propVal->isDirectRHSAnonFunction()) { - if (!emitDupAt(1)) - return false; - if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) - return false; - } - if (!emit1(op)) - return false; - } else { - MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)); - - uint32_t index; - if (!makeAtomIndex(key->as().atom(), &index)) - return false; - - if (objp) { - MOZ_ASSERT(type == ObjectLiteral); - MOZ_ASSERT(!IsHiddenInitOp(op)); - MOZ_ASSERT(!objp->inDictionaryMode()); - Rooted id(cx, AtomToId(key->as().atom())); - if (!NativeDefineProperty(cx, objp, id, UndefinedHandleValue, nullptr, nullptr, - JSPROP_ENUMERATE)) - { + if (!pe.emitInitHomeObject(funbox->asyncKind())) { + // [stack] CTOR? OBJ CTOR? KEY? FUN return false; } - if (objp->inDictionaryMode()) - objp.set(nullptr); } + return true; + }; - if (propVal->isDirectRHSAnonFunction()) { - RootedAtom keyName(cx, key->as().atom()); - if (!setOrEmitSetFunName(propVal, keyName, prefixKind)) - return false; - } - if (!emitIndex32(op, index)) + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + + if (key->isKind(PNK_NUMBER)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; + } + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitIndexProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); + } + continue; } - if (extraPop) { - if (!emit1(JSOP_POP)) + if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { + // EmitClass took care of constructor already. + if (type == ClassBody && key->as().atom() == cx->names().constructor && + !propdef->as().isStatic()) { + continue; + } + + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + RootedFunction anonFunction(cx); + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP); + + if (propVal->is()) { + // When the value is function, we set the function's name + // at the compile-time, instead of emitting SETFUNNAME. + FunctionBox* funbox = propVal->as().funbox(); + anonFunction = funbox->function(); + } else { + // Only object literal can have a property where key is + // name and value is an anonymous class. + // + // ({ foo: class {} }); + MOZ_ASSERT(type == ObjectLiteral); + MOZ_ASSERT(propVal->isKind(PNK_CLASS)); + } + } + + RootedAtom keyAtom(cx, key->as().atom()); + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitProp(keyAtom, isPropertyAnonFunctionOrClass, + anonFunction)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitGetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitSetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + default: MOZ_CRASH("Invalid op"); + } + + continue; + } + + MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME)); + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitTree(key->as().kid())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForComputedPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitComputedProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); } } @@ -8148,38 +8235,21 @@ BytecodeEmitter::emitObject(ListNode* objNode) if (!objNode->hasNonConstInitializer() && objNode->head() && checkSingletonContext()) return emitSingletonInitialiser(objNode); - /* - * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing - * a new object and defining (in source order) each property on the object - * (or mutating the object's [[Prototype]], in the case of __proto__). - */ - ptrdiff_t offset = this->offset(); - if (!emitNewInit(JSProto_Object)) + // [stack] + + ObjectEmitter oe(this); + if (!oe.emitObject(objNode->count())) { + // [stack] OBJ + return false; + } + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + if (!oe.emitEnd()) { + // [stack] OBJ return false; - - // Try to construct the shape of the object as we go, so we can emit a - // JSOP_NEWOBJECT with the final shape instead. - // In the case of computed property names and indices, we cannot fix the - // shape at bytecode compile time. When the shape cannot be determined, - // |obj| is nulled out. - - // No need to do any guessing for the object kind, since we know the upper - // bound of how many properties we plan to have. - gc::AllocKind kind = gc::GetGCObjectKind(objNode->count()); - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); - if (!obj) - return false; - - if (!emitPropertyList(objNode, &obj, ObjectLiteral)) - return false; - - if (obj) { - // The object survived and has a predictable shape: update the original - // bytecode. - if (!replaceNewInitWithNewObject(obj, offset)) - return false; } - return true; } @@ -8546,6 +8616,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) // If we have an initializer, emit the initializer and assign it // to the argument slot. TDZ is taken care of afterwards. MOZ_ASSERT(hasParameterExprs); + IfEmitter ifUndefined(this); if (!emitArgOp(JSOP_GETARG, argSlot)) return false; if (!emit1(JSOP_DUP)) @@ -8554,17 +8625,13 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) return false; if (!emit1(JSOP_STRICTEQ)) return false; - // Emit source note to enable Ion compilation. - if (!newSrcNote(SRC_IF)) - return false; - JumpList jump; - if (!emitJump(JSOP_IFEQ, &jump)) + if (!ifUndefined.emitThen()) return false; if (!emit1(JSOP_POP)) return false; - if (!emitInitializerInBranch(initializer, bindingElement)) + if (!emitInitializer(initializer, bindingElement)) return false; - if (!emitJumpTargetAndPatch(jump)) + if (!ifUndefined.emitEnd()) return false; } else if (isRest) { if (!emit1(JSOP_REST)) @@ -8730,9 +8797,15 @@ BytecodeEmitter::emitFunctionBody(ParseNode* funBody) } bool -BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) +BytecodeEmitter::emitLexicalInitialization(NameNode* pn) { - NameOpEmitter noe(this, pn->name(), NameOpEmitter::Kind::Initialize); + return emitLexicalInitialization(pn->name()); +} + +bool +BytecodeEmitter::emitLexicalInitialization(JSAtom* name) +{ + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } @@ -8748,7 +8821,6 @@ BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) return true; } - class AutoResetFieldInitializers { BytecodeEmitter* bce; @@ -8791,106 +8863,78 @@ BytecodeEmitter::emitClass(ClassNode* classNode) // set this->fieldInitializers_ AutoResetFieldInitializers _innermostClassAutoReset(this, setupFieldInitializers(classMembers)); - bool savedStrictness = sc->setLocalStrictMode(true); + // [stack] + + ClassEmitter ce(this); + RootedAtom innerName(cx); + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; - Maybe tdzCache; - Maybe emitterScope; if (names) { - tdzCache.emplace(this); - emitterScope.emplace(this); - if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode->scopeBindings())) + innerName = names->innerBinding()->as().atom(); + MOZ_ASSERT(innerName); + + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->as().atom()); + MOZ_ASSERT(names->outerBinding()->as().atom() == innerName); + kind = ClassEmitter::Kind::Declaration; + } + + if (!ce.emitScopeForNamedClass(classNode->scopeBindings())) { + // [stack] return false; + } } // This is kind of silly. In order to the get the home object defined on // the constructor, we have to make it second, but we want the prototype // on top for EmitPropertyList, because we expect static properties to be // rarer. The result is a few more swaps than we would like. Such is life. - if (heritageExpression) { - if (!emitTree(heritageExpression)) + bool isDerived = !!heritageExpression; + if (isDerived) { + if (!emitTree(heritageExpression)) { + // [stack] HERITAGE return false; - if (!emit1(JSOP_CLASSHERITAGE)) - return false; - if (!emit1(JSOP_OBJWITHPROTO)) - return false; - - // JSOP_CLASSHERITAGE leaves both protos on the stack. After - // creating the prototype, swap it to the bottom to make the - // constructor. - if (!emit1(JSOP_SWAP)) + } + if (!ce.emitDerivedClass(innerName)) { + // [stack] HERITAGE HOMEOBJ return false; + } } else { - if (!emitNewInit(JSProto_Object)) + if (!ce.emitClass(innerName)) { + // [stack] HOMEOBJ return false; + } } if (constructor) { - if (!emitFunction(constructor, !!heritageExpression)) + bool needsHomeObject = constructor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (!emitFunction(constructor, isDerived)) { + // [stack] HOMEOBJ CTOR + return false; + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ return false; - if (constructor->funbox()->needsHomeObject()) { - if (!emit2(JSOP_INITHOMEOBJECT, 0)) - return false; } } else { - // In the case of default class constructors, emit the start and end - // offsets in the source buffer as source notes so that when we - // actually make the constructor during execution, we can give it the - // correct toString output. - ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin); - ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end); - if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) + if (!ce.emitInitDefaultConstructor(Some(classNode->pn_pos.begin), + Some(classNode->pn_pos.end))) { + // [stack] CTOR HOMEOBJ return false; - - JSAtom *name = names ? names->innerBinding()->as().atom() : cx->names().empty; - if (heritageExpression) { - if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) - return false; - } else { - if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) - return false; } } - - if (!emit1(JSOP_SWAP)) + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ + return false; + } + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR return false; - - if (!emit1(JSOP_DUP2)) - return false; - if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) - return false; - if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) - return false; - - RootedPlainObject obj(cx); - if (!emitPropertyList(classMembers, &obj, ClassBody)) - return false; - - if (!emit1(JSOP_POP)) - return false; - - if (names) { - ParseNode* innerName = names->innerBinding(); - if (!emitLexicalInitialization(innerName)) - return false; - - // Pop the inner scope. - if (!emitterScope->leave(this)) - return false; - emitterScope.reset(); - - ParseNode* outerName = names->outerBinding(); - if (outerName) { - if (!emitLexicalInitialization(outerName)) - return false; - // Only class statements make outer bindings, and they do not leave - // themselves on the stack. - if (!emit1(JSOP_POP)) - return false; - } } - - MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); - return true; } @@ -9240,7 +9284,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: if (!emitTree(ed->left())) return false; if (ed->right()) { - if (!emitLexicalInitialization(ed->right())) + if (!emitLexicalInitialization(&ed->right()->as())) return false; if (!emit1(JSOP_POP)) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index b229b8043..9c1f165a5 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -125,6 +125,7 @@ class CallOrNewEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; +class PropertyEmitter; class PropOpEmitter; class TDZCheckCache; @@ -522,7 +523,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList); - MOZ_MUST_USE bool emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, + MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type); FieldInitializers setupFieldInitializers(ListNode* classMembers); @@ -696,11 +697,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter // is called at compile time. MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); - MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind = FunctionPrefixKind::None); + MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name); + MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name); + MOZ_MUST_USE bool emitSetClassConstructorName(JSAtom* name); MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); - MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); @@ -784,10 +785,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitDo(BinaryNode* doNode); MOZ_MUST_USE bool emitWhile(BinaryNode* whileNode); - MOZ_MUST_USE bool emitFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope = nullptr); - MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope); - MOZ_MUST_USE bool emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterScope); - MOZ_MUST_USE bool emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); + MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForIn(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForOf(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead); @@ -798,7 +799,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn); - MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(NameNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name); // Emit bytecode for the spread operator. // diff --git a/js/src/frontend/DefaultEmitter.cpp b/js/src/frontend/DefaultEmitter.cpp new file mode 100644 index 000000000..35a836c2b --- /dev/null +++ b/js/src/frontend/DefaultEmitter.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/DefaultEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "vm/Opcodes.h" // JSOP_* + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; + +DefaultEmitter::DefaultEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DefaultEmitter::prepareForDefault() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] VALUE + + ifUndefined_.emplace(bce_); + + if (!bce_->emit1(JSOP_DUP)) { + // [stack] VALUE VALUE + return false; + } + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] VALUE VALUE UNDEFINED + return false; + } + if (!bce_->emit1(JSOP_STRICTEQ)) { + // [stack] VALUE EQ? + return false; + } + + if (!ifUndefined_->emitThen()) { + // [stack] VALUE + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool DefaultEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULTVALUE + + if (!ifUndefined_->emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + ifUndefined_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/DefaultEmitter.h b/js/src/frontend/DefaultEmitter.h new file mode 100644 index 000000000..a9e3aa4aa --- /dev/null +++ b/js/src/frontend/DefaultEmitter.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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_DefaultEmitter_h +#define frontend_DefaultEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/IfEmitter.h" // IfEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting default parameter or default value. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `x = 10` in `function (x = 10) {}` +// // the value of arguments[0] is on the stack +// DefaultEmitter de(this); +// de.prepareForDefault(); +// emit(10); +// de.emitEnd(); +// +class MOZ_STACK_CLASS DefaultEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe ifUndefined_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForDefault +---------+ emitEnd +-----+ + // | Start |------------------>| Default |-------->| End | + // +-------+ +---------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForDefault. + Default, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit DefaultEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp index d6ebfe265..76b06ba4c 100644 --- a/js/src/frontend/EmitterScope.cpp +++ b/js/src/frontend/EmitterScope.cpp @@ -370,7 +370,7 @@ EmitterScope::appendScopeNote(BytecodeEmitter* bce) } bool -EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) +EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) const { // Lexical bindings throw ReferenceErrors if they are used before // initialization. See ES6 8.1.1.1.6. @@ -993,7 +993,7 @@ EmitterScope::enterWith(BytecodeEmitter* bce) } bool -EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) +EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) const { return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); } diff --git a/js/src/frontend/EmitterScope.h b/js/src/frontend/EmitterScope.h index cfee86a21..a2f54df0f 100644 --- a/js/src/frontend/EmitterScope.h +++ b/js/src/frontend/EmitterScope.h @@ -86,7 +86,7 @@ class EmitterScope : public Nestable MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce); MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, - uint32_t slotEnd); + uint32_t slotEnd) const; public: explicit EmitterScope(BytecodeEmitter* bce); @@ -105,7 +105,7 @@ class EmitterScope : public Nestable MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc); MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc); MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce); - MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce); + MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce) const; MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false); diff --git a/js/src/frontend/LexicalScopeEmitter.cpp b/js/src/frontend/LexicalScopeEmitter.cpp new file mode 100644 index 000000000..4ee6fc386 --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/LexicalScopeEmitter.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter + +using namespace js; +using namespace js::frontend; + +LexicalScopeEmitter::LexicalScopeEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool LexicalScopeEmitter::emitScope(ScopeKind kind, JS::Handle bindings) + +{ + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bindings); + + tdzCache_.emplace(bce_); + emitterScope_.emplace(bce_); + if (!emitterScope_->enterLexical(bce_, kind, bindings)) + return false; + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEmptyScope() +{ + MOZ_ASSERT(state_ == State::Start); + + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEnd() +{ + MOZ_ASSERT(state_ == State::Scope); + + if (emitterScope_) { + if (!emitterScope_->leave(bce_)) + return false; + emitterScope_.reset(); + } + tdzCache_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/LexicalScopeEmitter.h b/js/src/frontend/LexicalScopeEmitter.h new file mode 100644 index 000000000..9e1b9f479 --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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_LexicalScopeEmitter_h +#define frontend_LexicalScopeEmitter_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Handle +#include "vm/Scope.h" // ScopeKind, LexicalScope + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for lexical scope. +// +// In addition to emitting code for entering and leaving a scope, this RAII +// guard affects the code emitted for `break` and other non-structured +// control flow. See NonLocalExitControl::prepareForNonLocalJump(). +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{ ... }` -- lexical scope with no bindings +// LexicalScopeEmitter lse(this); +// lse.emitEmptyScope(); +// emit(scopeBody); +// lse.emitEnd(); +// +// `{ let a; body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Lexical, scopeBinding); +// emit(let_and_body); +// lse.emitEnd(); +// +// `catch (e) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::SimpleCatch, scopeBinding); +// emit(body); +// lse.emitEnd(); +// +// `catch ([a, b]) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Catch, scopeBinding); +// emit(body); +// lse.emitEnd(); +class MOZ_STACK_CLASS LexicalScopeEmitter +{ + BytecodeEmitter* bce_; + + mozilla::Maybe tdzCache_; + mozilla::Maybe emitterScope_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitScope +-------+ emitEnd +-----+ + // | Start |----------->| Scope |--------->| End | + // +-------+ +-------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitScope/emitEmptyScope. + Scope, + + // After calling emitEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + explicit LexicalScopeEmitter(BytecodeEmitter* bce); + + // Returns the scope object for non-empty scope. + const EmitterScope& emitterScope() const { + return *emitterScope_; + } + + MOZ_MUST_USE bool emitScope(ScopeKind kind, + JS::Handle bindings); + MOZ_MUST_USE bool emitEmptyScope(); + + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LexicalScopeEmitter_h */ diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp new file mode 100644 index 000000000..548196238 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.cpp @@ -0,0 +1,762 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/ObjectEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "jsatominlines.h" // AtomToId +#include "jsgcinlines.h" // GetGCObjectKind +#include "jsobjinlines.h" // NewBuiltinClassInstance + + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "frontend/SourceNotes.h" // SRC_* +#include "gc/Heap.h" // AllocKind +#include "js/Id.h" // jsid +#include "js/Value.h" // UndefinedHandleValue +#include "vm/NativeObject.h" // NativeDefineDataProperty +#include "vm/ObjectGroup.h" // TenuredObject +#include "vm/Runtime.h" // JSAtomState (cx->names()) + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) + : bce_(bce), obj_(bce->cx) {} + +bool PropertyEmitter::prepareForProtoValue(const Maybe& keyPos) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ CTOR? + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ProtoValue; +#endif + return true; +} + +bool PropertyEmitter::emitMutateProto() +{ + MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue); + + // [stack] OBJ PROTO + + if (!bce_->emit1(JSOP_MUTATEPROTO)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::prepareForSpreadOperand(const Maybe& spreadPos) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] OBJ + + if (spreadPos) { + if (!bce_->updateSourceCoordNotes(*spreadPos)) + return false; + } + if (!bce_->emit1(JSOP_DUP)) { + // [stack] OBJ OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::SpreadOperand; +#endif + return true; +} + +bool PropertyEmitter::emitSpread() +{ + MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand); + + // [stack] OBJ OBJ VAL + + if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(const Maybe& keyPos, + bool isStatic, bool isIndexOrComputed) +{ + isStatic_ = isStatic; + isIndexOrComputed_ = isIndexOrComputed; + + // [stack] CTOR? OBJ + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) + return false; + } + + if (isStatic_) { + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + } + + return true; +} + +bool PropertyEmitter::prepareForPropValue(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ false)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::PropValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropKey(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::IndexKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropValue() +{ + MOZ_ASSERT(propertyState_ == PropertyState::IndexKey); + + // [stack] CTOR? OBJ CTOR? KEY + +#ifdef DEBUG + propertyState_ = PropertyState::IndexValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropKey(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropValue() +{ + MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey); + + // [stack] CTOR? OBJ CTOR? KEY + + if (!bce_->emit1(JSOP_TOID)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedValue; +#endif + return true; +} + +bool PropertyEmitter::emitInitHomeObject(FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::ComputedValue); + + // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN + + bool isAsync = kind == FunctionAsyncKind::AsyncFunction; + if (isAsync) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED + return false; + } + } + + if (!bce_->emit2(JSOP_INITHOMEOBJECT, isIndexOrComputed_ + isAsync)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN + return false; + } + if (isAsync) { + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED + return false; + } + } + +#ifdef DEBUG + if (propertyState_ == PropertyState::PropValue) { + propertyState_ = PropertyState::InitHomeObj; + } else if (propertyState_ == PropertyState::IndexValue) { + propertyState_ = PropertyState::InitHomeObjForIndex; + } else { + propertyState_ = PropertyState::InitHomeObjForComputed; + } +#endif + return true; +} + +bool PropertyEmitter::emitInitProp(JS::Handle key, + bool isPropertyAnonFunctionOrClass /* = false */, + JS::Handle anonFunction /* = nullptr */) +{ + return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key, + isPropertyAnonFunctionOrClass, anonFunction); +} + +bool PropertyEmitter::emitInitGetter(JS::Handle key) +{ + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitSetter(JS::Handle key) +{ + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitIndexProp(bool isPropertyAnonFunctionOrClass /* = false */) +{ + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitIndexGetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, false); +} + +bool PropertyEmitter::emitInitIndexSetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, false); +} + +bool PropertyEmitter::emitInitComputedProp(bool isPropertyAnonFunctionOrClass /* = false */) +{ + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitComputedGetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, true); +} + +bool PropertyEmitter::emitInitComputedSetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, true); +} + +bool PropertyEmitter::emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction) +{ + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::InitHomeObj); + + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP || + op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER || + op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER); + + // [stack] CTOR? OBJ CTOR? VAL + + uint32_t index; + if (!bce_->makeAtomIndex(key, &index)) + return false; + + if (obj_) { + MOZ_ASSERT(!IsHiddenInitOp(op)); + MOZ_ASSERT(!obj_->inDictionaryMode()); + JS::RootedId id(bce_->cx, AtomToId(key)); + if (!NativeDefineProperty(bce_->cx, obj_, id, UndefinedHandleValue, nullptr, nullptr, + JSPROP_ENUMERATE)) + return false; + if (obj_->inDictionaryMode()) + obj_ = nullptr; + } + + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP); + + if (anonFunction) { + if (!bce_->setFunName(anonFunction, key)) + return false; + } else { + // NOTE: This is setting the constructor's name of the class which is + // the property value. Not of the enclosing class. + if (!bce_->emitSetClassConstructorName(key)) { + // [stack] CTOR? OBJ CTOR? FUN + return false; + } + } + } + + if (!bce_->emitIndex32(op, index)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) + return false; + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitInitIndexOrComputed(JSOp op, FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass) +{ + MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::InitHomeObjForIndex || + propertyState_ == PropertyState::ComputedValue || + propertyState_ == PropertyState::InitHomeObjForComputed); + + MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM || + op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER || + op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER); + + // [stack] CTOR? OBJ CTOR? KEY VAL + + if (isPropertyAnonFunctionOrClass) { + if (!bce_->emitDupAt(1)) { + // [stack] CTOR? OBJ CTOR? KEY FUN FUN + return false; + } + if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { + // [stack] CTOR? OBJ CTOR? KEY FUN + return false; + } + } + + if (!bce_->emit1(op)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) + return false; + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitPopClassConstructor() +{ + if (isStatic_) { + // [stack] CTOR HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ + return false; + } + } + + return true; +} + +ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {} + +bool ObjectEmitter::emitObject(size_t propertyCount) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(objectState_ == ObjectState::Start); + + // [stack] + + // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + // a new object and defining (in source order) each property on the object + // (or mutating the object's [[Prototype]], in the case of __proto__). + top_ = bce_->offset(); + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] OBJ + return false; + } + + // Try to construct the shape of the object as we go, so we can emit a + // JSOP_NEWOBJECT with the final shape instead. + // In the case of computed property names and indices, we cannot fix the + // shape at bytecode compile time. When the shape cannot be determined, + // |obj| is nulled out. + + // No need to do any guessing for the object kind, since we know the upper + // bound of how many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(propertyCount); + obj_ = NewBuiltinClassInstance(bce_->cx, kind, TenuredObject); + if (!obj_) + return false; + +#ifdef DEBUG + objectState_ = ObjectState::Object; +#endif + return true; +} + +bool ObjectEmitter::emitEnd() +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(objectState_ == ObjectState::Object); + + // [stack] OBJ + + if (obj_) { + // The object survived and has a predictable shape: update the original + // bytecode. + if (!bce_->replaceNewInitWithNewObject(obj_, top_)) { + // [stack] OBJ + return false; + } + } + +#ifdef DEBUG + objectState_ = ObjectState::End; +#endif + return true; +} + +AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) +{ + savedStrictness_ = sc_->setLocalStrictMode(true); +} + +AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() +{ + if (sc_) { + restore(); + } +} + +void AutoSaveLocalStrictMode::restore() +{ + MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_)); + sc_ = nullptr; +} + +ClassEmitter::ClassEmitter(BytecodeEmitter* bce) + : PropertyEmitter(bce), strictMode_(bce->sc), name_(bce->cx) +{ + isClass_ = true; +} + +bool ClassEmitter::emitScopeForNamedClass(JS::Handle scopeBindings) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start); + + tdzCacheForInnerName_.emplace(bce_); + innerNameScope_.emplace(bce_); + if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) + return false; + +#ifdef DEBUG + classState_ = ClassState::Scope; +#endif + return true; +} + +bool ClassEmitter::emitClass(JS::Handle name) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] + + setName(name); + isDerived_ = false; + + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitDerivedClass(JS::Handle name) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] HERITAGE + + setName(name); + isDerived_ = true; + + if (!bce_->emit1(JSOP_CLASSHERITAGE)) { + // [stack] funcProto objProto + return false; + } + if (!bce_->emit1(JSOP_OBJWITHPROTO)) { + // [stack] funcProto HOMEOBJ + return false; + } + + // JSOP_CLASSHERITAGE leaves both protos on the stack. After + // creating the prototype, swap it to the bottom to make the + // constructor. + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] HOMEOBJ funcProto + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +void ClassEmitter::setName(JS::Handle name) +{ + name_ = name; + if (!name_) + name_ = bce_->cx->names().empty; +} + +bool ClassEmitter::emitInitConstructor(bool needsHomeObject) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + // [stack] HOMEOBJ CTOR + + if (needsHomeObject) { + if (!bce_->emit2(JSOP_INITHOMEOBJECT, 0)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, + const Maybe& classEnd) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + if (classStart && classEnd) { + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart), + ptrdiff_t(*classEnd))) { + return false; + } + } + + if (isDerived_) { + // [stack] HERITAGE PROTO + if (!bce_->emitAtomOp(name_, JSOP_DERIVEDCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } else { + // [stack] HOMEOBJ + if (!bce_->emitAtomOp(name_, JSOP_CLASSCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::initProtoAndCtor() +{ + // [stack] HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) { + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +bool ClassEmitter::emitEnd(Kind kind) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InitConstructor); + + // [stack] CTOR HOMEOBJ + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR + return false; + } + + if (name_ != bce_->cx->names().empty) { + MOZ_ASSERT(tdzCacheForInnerName_.isSome()); + MOZ_ASSERT(innerNameScope_.isSome()); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + + // Pop the inner scope. + if (!innerNameScope_->leave(bce_)) + return false; + innerNameScope_.reset(); + + if (kind == Kind::Declaration) { + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + + tdzCacheForInnerName_.reset(); + } else { + // [stack] CTOR + + MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); + } + + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + + strictMode_.restore(); + +#ifdef DEBUG + classState_ = ClassState::End; +#endif + return true; +} diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h new file mode 100644 index 000000000..247e22176 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.h @@ -0,0 +1,727 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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_ObjectEmitter_h +#define frontend_ObjectEmitter_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // Maybe + +#include // size_t, ptrdiff_t +#include // uint32_t + +#include "jsopcode.h" // JSOp +#include "jsfun.h" // JSFunction +#include "jsscript.h" // FunctionAsyncKind + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "vm/String.h" // JSAtom +#include "vm/NativeObject.h" // PlainObject +#include "vm/Scope.h" // LexicalScope + +namespace js { + +namespace frontend { + +struct BytecodeEmitter; +class SharedContext; + +// Class for emitting bytecode for object and class properties. +// See ObjectEmitter and ClassEmitter for usage. +class MOZ_STACK_CLASS PropertyEmitter +{ + public: + enum class Kind { + // Prototype property. + Prototype, + + // Class static property. + Static + }; + + protected: + BytecodeEmitter* bce_; + + // True if the object is class. + // Set by ClassEmitter. + bool isClass_ = false; + + // True if the property is class static method. + bool isStatic_ = false; + + // True if the property has computed or index key. + bool isIndexOrComputed_ = false; + + // An object which keeps the shape of this object literal. + // This fields is reset to nullptr whenever the object literal turns out to + // have at least one numeric, computed, spread or __proto__ property, or + // the object becomes dictionary mode. + // This field is used only in ObjectEmitter. + JS::Rooted obj_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +---------+ + // | + // | +------------------------------------------------------------+ + // | | | + // | | [normal property/method/accessor] | + // | v prepareForPropValue +-----------+ +------+ | + // +->+----------------------->| PropValue |-+ +->| Init |-+ + // | +-----------+ | | +------+ + // | | | + // | +----------------------------------+ +-----------+ + // | | | + // | +-+---------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +-------------+ v | + // | +--------------------->| InitHomeObj |->+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------- + | + // | | | + // | | emitInitProp | + // | | emitInitGetter | + // | | emitInitSetter | + // | +------------------------------------------------------>+ + // | ^ + // | [index property/method/accessor] | + // | prepareForIndexPropKey +----------+ | + // +-------------------------->| IndexKey |-+ | + // | +----------+ | | + // | | | + // | +-------------------------------------+ | + // | | | + // | | prepareForIndexPropValue +------------+ | + // | +------------------------->| IndexValue |-+ | + // | +------------+ | | + // | | | + // | +---------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +---------------------+ v | + // | +--------------------->| InitHomeObjForIndex |---->+ | + // | +---------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitIndexProp | + // | | emitInitIndexGetter | + // | | emitInitIndexSetter | + // | +---------------------------------------------------->+ + // | | + // | [computed property/method/accessor] | + // | prepareForComputedPropKey +-------------+ | + // +----------------------------->| ComputedKey |-+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------------+ | + // | | | + // | | prepareForComputedPropValue +---------------+ | + // | +---------------------------->| ComputedValue |-+ | + // | +---------------+ | | + // | | | + // | +---------------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +------------------------+ v | + // | +--------------------->| InitHomeObjForComputed |->+ | + // | +------------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitComputedProp | + // | | emitInitComputedGetter | + // | | emitInitComputedSetter | + // | +---------------------------------------------------->+ + // | ^ + // | | + // | [__proto__] | + // | prepareForProtoValue +------------+ emitMutateProto | + // +------------------------>| ProtoValue |-------------------->+ + // | +------------+ ^ + // | | + // | [...prop] | + // | prepareForSpreadOperand +---------------+ emitSpread | + // +-------------------------->| SpreadOperand |----------------+ + // +---------------+ + enum class PropertyState { + // The initial state. + Start, + + // After calling prepareForPropValue. + PropValue, + + // After calling emitInitHomeObject, from PropValue. + InitHomeObj, + + // After calling prepareForIndexPropKey. + IndexKey, + + // prepareForIndexPropValue. + IndexValue, + + // After calling emitInitHomeObject, from IndexValue. + InitHomeObjForIndex, + + // After calling prepareForComputedPropKey. + ComputedKey, + + // prepareForComputedPropValue. + ComputedValue, + + // After calling emitInitHomeObject, from ComputedValue. + InitHomeObjForComputed, + + // After calling prepareForProtoValue. + ProtoValue, + + // After calling prepareForSpreadOperand. + SpreadOperand, + + // After calling one of emitInitProp, emitInitGetter, emitInitSetter, + // emitInitIndexOrComputedProp, emitInitIndexOrComputedGetter, + // emitInitIndexOrComputedSetter, emitMutateProto, or emitSpread. + Init, + }; + PropertyState propertyState_ = PropertyState::Start; +#endif + + public: + explicit PropertyEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // { __proto__: protoValue } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForProtoValue( + const mozilla::Maybe& keyPos); + MOZ_MUST_USE bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + MOZ_MUST_USE bool prepareForSpreadOperand( + const mozilla::Maybe& spreadPos); + MOZ_MUST_USE bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe& keyPos, + Kind kind = Kind::Prototype); + + // { 1: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForIndexPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForComputedPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForComputedPropValue(); + + MOZ_MUST_USE bool emitInitHomeObject( + FunctionAsyncKind kind = FunctionAsyncKind::SyncFunction); + + // @param key + // Property key + // @param isPropertyAnonFunctionOrClass + // True if the property value is an anonymous function or + // an anonymous class + // @param anonFunction + // The anonymous function object for property value + MOZ_MUST_USE bool emitInitProp( + JS::Handle key, bool isPropertyAnonFunctionOrClass = false, + JS::Handle anonFunction = nullptr); + MOZ_MUST_USE bool emitInitGetter(JS::Handle key); + MOZ_MUST_USE bool emitInitSetter(JS::Handle key); + + MOZ_MUST_USE bool emitInitIndexProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitIndexGetter(); + MOZ_MUST_USE bool emitInitIndexSetter(); + + MOZ_MUST_USE bool emitInitComputedProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitComputedGetter(); + MOZ_MUST_USE bool emitInitComputedSetter(); + + private: + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp( + const mozilla::Maybe& keyPos, bool isStatic, bool isComputed); + + // @param op + // Opcode for initializing property + // @param prefixKind + // None, Get, or Set + // @param key + // Atom of the property if the property key is not computed + // @param isPropertyAnonFunctionOrClass + // True if the property is either an anonymous function or an + // anonymous class + // @param anonFunction + // Anonymous function object for the property + MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction); + MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op, + FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass); + + MOZ_MUST_USE bool emitPopClassConstructor(); +}; + +// Class for emitting bytecode for object literal. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{}` +// ObjectEmitter oe(this); +// oe.emitObject(0); +// oe.emitEnd(); +// +// `{ prop: 10 }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(10); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ prop: function() {} }`, when property value is anonymous function +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function); +// oe.emitInitProp(atom_of_prop, true, function_object); +// +// oe.emitEnd(); +// +// `{ get prop() { ... }, set prop(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(2); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_getter); +// oe.emitInitGetter(atom_of_prop); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_setter); +// oe.emitInitSetter(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ 1: 10, get 2() { ... }, set 3(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForIndexPropKey(Some(offset_of_prop)); +// emit(1); +// oe.prepareForIndexPropValue(); +// emit(10); +// oe.emitInitIndexedProp(atom_of_prop); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(2); +// oe.prepareForIndexPropValue(); +// emit(function_for_getter); +// oe.emitInitIndexGetter(); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(3); +// oe.prepareForIndexPropValue(); +// emit(function_for_setter); +// oe.emitInitIndexSetter(); +// +// oe.emitEnd(); +// +// `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop1); +// oe.prepareForComputedPropValue(); +// emit(10); +// oe.emitInitComputedProp(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop2); +// oe.prepareForComputedPropValue(); +// emit(function_for_getter); +// oe.emitInitComputedGetter(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop3); +// oe.prepareForComputedPropValue(); +// emit(function_for_setter); +// oe.emitInitComputedSetter(); +// +// oe.emitEnd(); +// +// `{ __proto__: obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForProtoValue(Some(offset_of___proto__)); +// emit(obj); +// oe.emitMutateProto(); +// oe.emitEnd(); +// +// `{ ...obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForSpreadOperand(Some(offset_of_triple_dots)); +// emit(obj); +// oe.emitSpread(); +// oe.emitEnd(); +// +class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter +{ + private: + // The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later + // when the object is known to have a fixed shape. + ptrdiff_t top_ = 0; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitObject +--------+ + // | Start |----------->| Object |-+ + // +-------+ +--------+ | + // | + // +-----------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ObjectState { + // The initial state. + Start, + + // After calling emitObject. + Object, + + // After calling emitEnd. + End, + }; + ObjectState objectState_ = ObjectState::Start; +#endif + + public: + explicit ObjectEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitObject(size_t propertyCount); + MOZ_MUST_USE bool emitEnd(); +}; + +// Save and restore the strictness. +// Used by class declaration/expression to temporarily enable strict mode. +class MOZ_RAII AutoSaveLocalStrictMode +{ + SharedContext* sc_; + bool savedStrictness_; + + public: + explicit AutoSaveLocalStrictMode(SharedContext* sc); + ~AutoSaveLocalStrictMode(); + + // Force restore the strictness now. + void restore(); +}; + +// Class for emitting bytecode for JS class. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `class {}` +// ClassEmitter ce(this); +// ce.emitClass(); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitClass(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... super.f(); ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// // pass true if constructor contains super.prop access +// ce.emitInitConstructor(/* needsHomeObject = */ true); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `async m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(FunctionAsyncKind::Async); +// ce.emitInitProp(atom_of_m); +// +// `get p() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_p)); +// emit(function_for_p); +// ce.emitInitHomeObject(); +// ce.emitInitGetter(atom_of_m); +// +// `static m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `static get [p]() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForComputedPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(p); +// ce.prepareForComputedPropValue(); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitComputedGetter(); +// +class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter +{ + public: + enum class Kind { + // Class expression. + Expression, + + // Class declaration. + Declaration, + }; + + private: + // Pseudocode for class declarations: + // + // class extends BaseExpression { + // constructor() { ... } + // ... + // } + // + // + // if defined { + // let heritage = BaseExpression; + // + // if (heritage !== null) { + // funProto = heritage; + // objProto = heritage.prototype; + // } else { + // funProto = %FunctionPrototype%; + // objProto = null; + // } + // } else { + // objProto = %ObjectPrototype%; + // } + // + // let homeObject = ObjectCreate(objProto); + // + // if defined { + // if defined { + // cons = DefineMethod(, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(, proto=homeObject); + // } + // } else { + // if defined { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe tdzCacheForInnerName_; + mozilla::Maybe innerNameScope_; + AutoSaveLocalStrictMode strictMode_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+------------------------------------>+-+ + // +-------+ | ^ | + // | [named class] | | + // | emitScopeForNamedClass +-------+ | | + // +-------------------------->| Scope |-+ | + // +-------+ | + // | + // +-----------------------------------------------+ + // | + // | emitClass +-------+ + // +-+----------------->+->| Class |-+ + // | ^ +-------+ | + // | emitDerivedClass | | + // +------------------+ | + // | + // +-------------------------------+ + // | + // | + // | emitInitConstructor +-----------------+ + // +-+--------------------------->+->| InitConstructor |-+ + // | ^ +-----------------+ | + // | emitInitDefaultConstructor | | + // +----------------------------+ | + // | + // +---------------------------------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ClassState { + // The initial state. + Start, + + // After calling emitScopeForNamedClass. + Scope, + + // After calling emitClass or emitDerivedClass. + Class, + + // After calling emitInitConstructor or emitInitDefaultConstructor. + InitConstructor, + + // After calling emitEnd. + End, + }; + ClassState classState_ = ClassState::Start; +#endif + + JS::Rooted name_; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitScopeForNamedClass( + JS::Handle scopeBindings); + + // @param name + // Name of the class (nullptr if this is anonymous class) + MOZ_MUST_USE bool emitClass(JS::Handle name); + MOZ_MUST_USE bool emitDerivedClass(JS::Handle name); + + // @param needsHomeObject + // True if the constructor contains `super.foo` + MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject); + + // Parameters are the offset in the source code for each character below: + // + // class X { foo() {} } + // ^ ^ + // | | + // | classEnd + // | + // classStart + // + MOZ_MUST_USE bool emitInitDefaultConstructor( + const mozilla::Maybe& classStart, + const mozilla::Maybe& classEnd); + + MOZ_MUST_USE bool emitEnd(Kind kind); + + private: + void setName(JS::Handle name); + MOZ_MUST_USE bool initProtoAndCtor(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ObjectEmitter_h */ diff --git a/js/src/moz.build b/js/src/moz.build index f1004fda1..72f1e78b9 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -186,14 +186,17 @@ main_deunified_sources = [ 'frontend/BytecodeControlStructures.cpp', 'frontend/BytecodeEmitter.cpp', 'frontend/CallOrNewEmitter.cpp', + 'frontend/DefaultEmitter.cpp', 'frontend/ElemOpEmitter.cpp', 'frontend/EmitterScope.cpp', 'frontend/FoldConstants.cpp', 'frontend/ForOfLoopControl.cpp', 'frontend/IfEmitter.cpp', 'frontend/JumpList.cpp', + 'frontend/LexicalScopeEmitter.cpp', 'frontend/NameFunctions.cpp', 'frontend/NameOpEmitter.cpp', + 'frontend/ObjectEmitter.cpp', 'frontend/ParseNode.cpp', 'frontend/PropOpEmitter.cpp', 'frontend/SwitchEmitter.cpp',