Issue #2142 - Use JSOP_INITPROP for field initializers

We don't want to call setters for field initialization.

Based-on: m-c 1535471, 1547035
This commit is contained in:
Martok 2023-04-09 04:42:04 +02:00 • committed by roytam1
commit 849ab4417c
11 changed files with 89 additions and 40 deletions

View file

@ -2770,7 +2770,7 @@ ASTSerializer::classField(ClassField* classField, MutableHandleValue dst)
->head()->as<LexicalScopeNode>() ->head()->as<LexicalScopeNode>()
.scopeBody()->as<ListNode>() .scopeBody()->as<ListNode>()
.head()->as<UnaryNode>() .head()->as<UnaryNode>()
.kid()->as<AssignmentNode>() .kid()->as<BinaryNode>()
.right(); .right();
// RawUndefinedExpr is the node we use for "there is no initializer". If one // RawUndefinedExpr is the node we use for "there is no initializer". If one
// writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr

View file

@ -1214,6 +1214,10 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
return true; return true;
// Binary cases with obvious side effects. // Binary cases with obvious side effects.
case PNK_INITPROP:
*answer = true;
return true;
case PNK_ASSIGN: case PNK_ASSIGN:
case PNK_ADDASSIGN: case PNK_ADDASSIGN:
case PNK_SUBASSIGN: case PNK_SUBASSIGN:
@ -3697,9 +3701,14 @@ EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset)
} }
bool bool
BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp,
ParseNode* lhs, ParseNode* rhs)
{ {
bool isCompound = compoundOp != JSOP_NOP; bool isCompound = compoundOp != JSOP_NOP;
bool isInit = kind == PNK_INITPROP;
MOZ_ASSERT_IF(isInit, lhs->isKind(PNK_DOT) ||
lhs->isKind(PNK_ELEM));
// Name assignments are handled separately because choosing ops and when // Name assignments are handled separately because choosing ops and when
// to emit BINDNAME is involved and should avoid duplication. // to emit BINDNAME is involved and should avoid duplication.
@ -3754,7 +3763,8 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
poe.emplace(this, poe.emplace(this,
isCompound isCompound
? PropOpEmitter::Kind::CompoundAssignment ? PropOpEmitter::Kind::CompoundAssignment
: PropOpEmitter::Kind::SimpleAssignment, : isInit ? PropOpEmitter::Kind::PropInit
: PropOpEmitter::Kind::SimpleAssignment,
isSuper isSuper
? PropOpEmitter::ObjKind::Super ? PropOpEmitter::ObjKind::Super
: PropOpEmitter::ObjKind::Other); : PropOpEmitter::ObjKind::Other);
@ -3781,7 +3791,8 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
eoe.emplace(this, eoe.emplace(this,
isCompound isCompound
? ElemOpEmitter::Kind::CompoundAssignment ? ElemOpEmitter::Kind::CompoundAssignment
: ElemOpEmitter::Kind::SimpleAssignment, : isInit ? ElemOpEmitter::Kind::PropInit
: ElemOpEmitter::Kind::SimpleAssignment,
isSuper isSuper
? ElemOpEmitter::ObjKind::Super ? ElemOpEmitter::ObjKind::Super
: ElemOpEmitter::ObjKind::Other); : ElemOpEmitter::ObjKind::Other);
@ -4714,7 +4725,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead)
// initialization is just assigning the iteration value to a target // initialization is just assigning the iteration value to a target
// expression. // expression.
if (!parser->handler.isDeclarationList(target)) if (!parser->handler.isDeclarationList(target))
return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL return emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, target, nullptr); // ... ITERVAL
// Otherwise, per-loop initialization is (possibly) declaration // Otherwise, per-loop initialization is (possibly) declaration
// initialization. If the declaration is a lexical declaration, it must be // initialization. If the declaration is a lexical declaration, it must be
@ -4728,8 +4739,19 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead)
MOZ_ASSERT(target->isForLoopDeclaration()); MOZ_ASSERT(target->isForLoopDeclaration());
target = parser->handler.singleBindingFromDeclaration(&target->as<ListNode>()); target = parser->handler.singleBindingFromDeclaration(&target->as<ListNode>());
NameNode* nameNode = nullptr;
if (target->isKind(PNK_NAME)) { if (target->isKind(PNK_NAME)) {
NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize); nameNode = &target->as<NameNode>();
} else if (target->isKind(PNK_ASSIGN) ||
target->isKind(PNK_INITPROP)) {
BinaryNode* assignNode = &target->as<BinaryNode>();
if (assignNode->left()->is<NameNode>()) {
nameNode = &assignNode->left()->as<NameNode>();
}
}
if (nameNode) {
NameOpEmitter noe(this, nameNode->name(), NameOpEmitter::Kind::Initialize);
if (!noe.prepareForRhs()) { if (!noe.prepareForRhs()) {
return false; return false;
} }
@ -4755,7 +4777,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead)
return true; return true;
} }
MOZ_ASSERT(!target->isKind(PNK_ASSIGN), MOZ_ASSERT(!target->isKind(PNK_ASSIGN) && !target->isKind(PNK_INITPROP),
"for-in/of loop destructuring declarations can't have initializers"); "for-in/of loop destructuring declarations can't have initializers");
MOZ_ASSERT(target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)); MOZ_ASSERT(target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT));
@ -5396,7 +5418,7 @@ BytecodeEmitter::emitComprehensionForOf(ForNode* forNode)
// Notice: Comprehension for-of doesn't perform IteratorClose, since it's // Notice: Comprehension for-of doesn't perform IteratorClose, since it's
// not in the spec. // not in the spec.
if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE if (!emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, loopVariableName, nullptr)) // ITER RESULT VALUE
return false; return false;
// Remove VALUE from the stack to release it. // Remove VALUE from the stack to release it.
@ -5533,7 +5555,7 @@ BytecodeEmitter::emitComprehensionForIn(ForNode* forNode)
// Emit code to assign the enumeration value to the left hand side, but // Emit code to assign the enumeration value to the left hand side, but
// also leave it on the stack. // also leave it on the stack.
if (!emitAssignment(forHead->kid2(), JSOP_NOP, nullptr)) if (!emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, forHead->kid2(), nullptr))
return false; return false;
/* The stack should be balanced around the assignment opcode sequence. */ /* The stack should be balanced around the assignment opcode sequence. */
@ -8384,9 +8406,9 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody)
for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next) { for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next) {
ParseNode* bindingElement = arg; ParseNode* bindingElement = arg;
ParseNode* initializer = nullptr; ParseNode* initializer = nullptr;
if (arg->isKind(PNK_ASSIGN)) { if (arg->isKind(PNK_ASSIGN) || arg->isKind(PNK_INITPROP)) {
bindingElement = arg->as<AssignmentNode>().left(); bindingElement = arg->as<BinaryNode>().left();
initializer = arg->as<AssignmentNode>().right(); initializer = arg->as<BinaryNode>().right();
} }
bool hasInitializer = !!initializer; bool hasInitializer = !!initializer;
bool isRest = hasRest && arg->pn_next == funBody; bool isRest = hasRest && arg->pn_next == funBody;
@ -8830,6 +8852,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
return false; return false;
break; break;
case PNK_INITPROP:
case PNK_ASSIGN: case PNK_ASSIGN:
case PNK_ADDASSIGN: case PNK_ADDASSIGN:
case PNK_SUBASSIGN: case PNK_SUBASSIGN:
@ -8843,8 +8866,9 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
case PNK_DIVASSIGN: case PNK_DIVASSIGN:
case PNK_MODASSIGN: case PNK_MODASSIGN:
case PNK_POWASSIGN: { case PNK_POWASSIGN: {
AssignmentNode* assignNode = &pn->as<AssignmentNode>(); BinaryNode* assignNode = &pn->as<BinaryNode>();
if (!emitAssignment(assignNode->left(), assignNode->getOp(), assignNode->right())) if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->getOp(),
assignNode->left(), assignNode->right()))
return false; return false;
break; break;
} }

View file

@ -696,7 +696,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj);
MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); MOZ_MUST_USE bool emitTemplateString(ListNode* templateString);
MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs); MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp,
ParseNode* lhs, ParseNode* rhs);
MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode); MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode);
MOZ_MUST_USE bool emitStatement(UnaryNode* exprStmt); MOZ_MUST_USE bool emitStatement(UnaryNode* exprStmt);

View file

@ -132,11 +132,11 @@ ElemOpEmitter::emitGet()
bool bool
ElemOpEmitter::prepareForRhs() ElemOpEmitter::prepareForRhs()
{ {
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); MOZ_ASSERT(isSimpleAssignment() || isPropInit()|| isCompoundAssignment());
MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key); MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key);
MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
if (isSimpleAssignment()) { if (isSimpleAssignment() || isPropInit()) {
// For CompoundAssignment, SUPERBASE is already emitted by emitGet. // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
if (isSuper()) { if (isSuper()) {
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE
@ -155,7 +155,7 @@ bool
ElemOpEmitter::skipObjAndKeyAndRhs() ElemOpEmitter::skipObjAndKeyAndRhs()
{ {
MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(state_ == State::Start);
MOZ_ASSERT(isSimpleAssignment()); MOZ_ASSERT(isSimpleAssignment() || isPropInit());
#ifdef DEBUG #ifdef DEBUG
state_ = State::Rhs; state_ = State::Rhs;
@ -203,12 +203,15 @@ ElemOpEmitter::emitDelete()
bool bool
ElemOpEmitter::emitAssignment() ElemOpEmitter::emitAssignment()
{ {
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
MOZ_ASSERT(state_ == State::Rhs); MOZ_ASSERT(state_ == State::Rhs);
JSOp setOp = isSuper() MOZ_ASSERT_IF(isPropInit(), !isSuper());
? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
: bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; JSOp setOp = isPropInit() ? JSOP_INITELEM
: isSuper()
? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
: bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (!bce_->emitElemOpBase(setOp)) { // ELEM if (!bce_->emitElemOpBase(setOp)) { // ELEM
return false; return false;
} }

View file

@ -129,6 +129,7 @@ class MOZ_STACK_CLASS ElemOpEmitter
PostDecrement, PostDecrement,
PreDecrement, PreDecrement,
SimpleAssignment, SimpleAssignment,
PropInit,
CompoundAssignment CompoundAssignment
}; };
enum class ObjKind { enum class ObjKind {
@ -176,6 +177,7 @@ class MOZ_STACK_CLASS ElemOpEmitter
// | +--------+ | // | +--------+ |
// | +-------------------+ // | +-------------------+
// | [SimpleAssignment] | // | [SimpleAssignment] |
// | [PropInit] |
// | prepareForRhs v +-----+ // | prepareForRhs v +-----+
// +--------------------->+-------------->+->| Rhs |-+ // +--------------------->+-------------->+->| Rhs |-+
// | ^ +-----+ | // | ^ +-----+ |
@ -225,6 +227,10 @@ class MOZ_STACK_CLASS ElemOpEmitter
return kind_ == Kind::SimpleAssignment; return kind_ == Kind::SimpleAssignment;
} }
MOZ_MUST_USE bool isPropInit() const {
return kind_ == Kind::PropInit;
}
MOZ_MUST_USE bool isDelete() const { MOZ_MUST_USE bool isDelete() const {
return kind_ == Kind::Delete; return kind_ == Kind::Delete;
} }

View file

@ -351,6 +351,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_DIV: case PNK_DIV:
case PNK_MOD: case PNK_MOD:
case PNK_POW: case PNK_POW:
case PNK_INITPROP:
case PNK_ASSIGN: case PNK_ASSIGN:
case PNK_ADDASSIGN: case PNK_ADDASSIGN:
case PNK_SUBASSIGN: case PNK_SUBASSIGN:
@ -1877,6 +1878,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
case PNK_SWITCH: case PNK_SWITCH:
case PNK_COLON: case PNK_COLON:
case PNK_INITPROP:
case PNK_ASSIGN: case PNK_ASSIGN:
case PNK_ADDASSIGN: case PNK_ADDASSIGN:
case PNK_SUBASSIGN: case PNK_SUBASSIGN:

View file

@ -257,6 +257,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
// Binary nodes with two non-null children. // Binary nodes with two non-null children.
// All assignment and compound assignment nodes qualify. // All assignment and compound assignment nodes qualify.
case PNK_INITPROP:
case PNK_ASSIGN: case PNK_ASSIGN:
case PNK_ADDASSIGN: case PNK_ADDASSIGN:
case PNK_SUBASSIGN: case PNK_SUBASSIGN:

View file

@ -125,6 +125,7 @@ class ObjectBox;
F(SUPERBASE) \ F(SUPERBASE) \
F(SUPERCALL) \ F(SUPERCALL) \
F(SETTHIS) \ F(SETTHIS) \
F(INITPROP) \
F(IMPORT_META) \ F(IMPORT_META) \
F(CALL_IMPORT) \ F(CALL_IMPORT) \
\ \
@ -170,7 +171,7 @@ class ObjectBox;
F(POW) \ F(POW) \
\ \
/* Assignment operators (= += -= etc.). */ \ /* Assignment operators (= += -= etc.). */ \
/* ParseNode::isAssignment assumes all these are consecutive. */ \ /* AssignmentNode::test assumes all these are consecutive. */ \
F(ASSIGN) \ F(ASSIGN) \
F(ADDASSIGN) \ F(ADDASSIGN) \
F(SUBASSIGN) \ F(SUBASSIGN) \
@ -385,7 +386,10 @@ IsTypeofKind(ParseNodeKind kind)
* PNK_COMMA (ListNode) * PNK_COMMA (ListNode)
* head: list of N comma-separated exprs * head: list of N comma-separated exprs
* count: N >= 2 * count: N >= 2
* PNK_ASSIGN (BinaryNode) * PNK_INITPROP (BinaryNode)
* left: target of assignment, base-class setter will not be invoked
* right: value to assign
* PNK_ASSIGN (AssignmentNode)
* left: target of assignment * left: target of assignment
* right: value to assign * right: value to assign
* PNK_ADDASSIGN, PNK_SUBASSIGN, PNK_BITORASSIGN, PNK_BITXORASSIGN, * PNK_ADDASSIGN, PNK_SUBASSIGN, PNK_BITORASSIGN, PNK_BITXORASSIGN,

View file

@ -8048,18 +8048,18 @@ Parser<ParseHandler>::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH
return null(); return null();
} }
// Synthesize an assignment expression for the property. // Synthesize a property init.
AssignmentNodeType initializerAssignment = handler.newAssignment(PNK_ASSIGN, AssignmentNodeType initializerPropInit = handler.newAssignment(PNK_INITPROP,
propAssignFieldAccess, initializerExpr, propAssignFieldAccess, initializerExpr,
JSOP_NOP); JSOP_NOP);
if (!initializerAssignment) if (!initializerPropInit)
return null(); return null();
bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings();
if (!declareFunctionThis(canSkipLazyClosedOverBindings)) if (!declareFunctionThis(canSkipLazyClosedOverBindings))
return null(); return null();
UnaryNodeType exprStatement = handler.newExprStatement(initializerAssignment, wholeInitializerPos.end); UnaryNodeType exprStatement = handler.newExprStatement(initializerPropInit, wholeInitializerPos.end);
if (!exprStatement) if (!exprStatement)
return null(); return null();

View file

@ -110,11 +110,11 @@ PropOpEmitter::emitGet(JSAtom* prop)
bool bool
PropOpEmitter::prepareForRhs() PropOpEmitter::prepareForRhs()
{ {
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj); MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Obj);
MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
if (isSimpleAssignment()) { if (isSimpleAssignment() || isPropInit()) {
// For CompoundAssignment, SUPERBASE is already emitted by emitGet. // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
if (isSuper()) { if (isSuper()) {
if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE
@ -133,7 +133,7 @@ bool
PropOpEmitter::skipObjAndRhs() PropOpEmitter::skipObjAndRhs()
{ {
MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(state_ == State::Start);
MOZ_ASSERT(isSimpleAssignment()); MOZ_ASSERT(isSimpleAssignment() || isPropInit());
#ifdef DEBUG #ifdef DEBUG
state_ = State::Rhs; state_ = State::Rhs;
@ -182,18 +182,20 @@ PropOpEmitter::emitDelete(JSAtom* prop)
bool bool
PropOpEmitter::emitAssignment(JSAtom* prop) PropOpEmitter::emitAssignment(JSAtom* prop)
{ {
MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
MOZ_ASSERT(state_ == State::Rhs); MOZ_ASSERT(state_ == State::Rhs);
if (isSimpleAssignment()) { if (isSimpleAssignment() || isPropInit()) {
if (!prepareAtomIndex(prop)) { if (!prepareAtomIndex(prop)) {
return false; return false;
} }
} }
JSOp setOp = isSuper() MOZ_ASSERT_IF(isPropInit(), !isSuper());
? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER JSOp setOp = isPropInit() ? JSOP_INITPROP
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; : isSuper()
? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
: bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // VAL if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // VAL
return false; return false;
} }

View file

@ -115,6 +115,7 @@ class MOZ_STACK_CLASS PropOpEmitter
PostDecrement, PostDecrement,
PreDecrement, PreDecrement,
SimpleAssignment, SimpleAssignment,
PropInit,
CompoundAssignment CompoundAssignment
}; };
enum class ObjKind { enum class ObjKind {
@ -167,6 +168,7 @@ class MOZ_STACK_CLASS PropOpEmitter
// | +--------+ | // | +--------+ |
// | | // | |
// | [SimpleAssignment] | // | [SimpleAssignment] |
// | [PropInit] |
// | prepareForRhs | +-----+ // | prepareForRhs | +-----+
// +--------------------->+-------------->+->| Rhs |-+ // +--------------------->+-------------->+->| Rhs |-+
// | ^ +-----+ | // | ^ +-----+ |
@ -217,6 +219,10 @@ class MOZ_STACK_CLASS PropOpEmitter
return kind_ == Kind::SimpleAssignment; return kind_ == Kind::SimpleAssignment;
} }
MOZ_MUST_USE bool isPropInit() const {
return kind_ == Kind::PropInit;
}
MOZ_MUST_USE bool isDelete() const { MOZ_MUST_USE bool isDelete() const {
return kind_ == Kind::Delete; return kind_ == Kind::Delete;
} }