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>()
.scopeBody()->as<ListNode>()
.head()->as<UnaryNode>()
.kid()->as<AssignmentNode>()
.kid()->as<BinaryNode>()
.right();
// RawUndefinedExpr is the node we use for "there is no initializer". If one
// writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr

View file

@ -1214,6 +1214,10 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
return true;
// Binary cases with obvious side effects.
case PNK_INITPROP:
*answer = true;
return true;
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
@ -3697,9 +3701,14 @@ EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset)
}
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 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
// to emit BINDNAME is involved and should avoid duplication.
@ -3754,7 +3763,8 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
poe.emplace(this,
isCompound
? PropOpEmitter::Kind::CompoundAssignment
: PropOpEmitter::Kind::SimpleAssignment,
: isInit ? PropOpEmitter::Kind::PropInit
: PropOpEmitter::Kind::SimpleAssignment,
isSuper
? PropOpEmitter::ObjKind::Super
: PropOpEmitter::ObjKind::Other);
@ -3781,7 +3791,8 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
eoe.emplace(this,
isCompound
? ElemOpEmitter::Kind::CompoundAssignment
: ElemOpEmitter::Kind::SimpleAssignment,
: isInit ? ElemOpEmitter::Kind::PropInit
: ElemOpEmitter::Kind::SimpleAssignment,
isSuper
? ElemOpEmitter::ObjKind::Super
: ElemOpEmitter::ObjKind::Other);
@ -4714,7 +4725,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead)
// initialization is just assigning the iteration value to a target
// expression.
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
// initialization. If the declaration is a lexical declaration, it must be
@ -4728,8 +4739,19 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead)
MOZ_ASSERT(target->isForLoopDeclaration());
target = parser->handler.singleBindingFromDeclaration(&target->as<ListNode>());
NameNode* nameNode = nullptr;
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()) {
return false;
}
@ -4755,7 +4777,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead)
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");
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
// 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;
// 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
// also leave it on the stack.
if (!emitAssignment(forHead->kid2(), JSOP_NOP, nullptr))
if (!emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, forHead->kid2(), nullptr))
return false;
/* 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) {
ParseNode* bindingElement = arg;
ParseNode* initializer = nullptr;
if (arg->isKind(PNK_ASSIGN)) {
bindingElement = arg->as<AssignmentNode>().left();
initializer = arg->as<AssignmentNode>().right();
if (arg->isKind(PNK_ASSIGN) || arg->isKind(PNK_INITPROP)) {
bindingElement = arg->as<BinaryNode>().left();
initializer = arg->as<BinaryNode>().right();
}
bool hasInitializer = !!initializer;
bool isRest = hasRest && arg->pn_next == funBody;
@ -8830,6 +8852,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
return false;
break;
case PNK_INITPROP:
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
@ -8843,8 +8866,9 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
case PNK_DIVASSIGN:
case PNK_MODASSIGN:
case PNK_POWASSIGN: {
AssignmentNode* assignNode = &pn->as<AssignmentNode>();
if (!emitAssignment(assignNode->left(), assignNode->getOp(), assignNode->right()))
BinaryNode* assignNode = &pn->as<BinaryNode>();
if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->getOp(),
assignNode->left(), assignNode->right()))
return false;
break;
}

View file

@ -696,7 +696,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj);
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 emitStatement(UnaryNode* exprStmt);

View file

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

View file

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

View file

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

View file

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

View file

@ -125,6 +125,7 @@ class ObjectBox;
F(SUPERBASE) \
F(SUPERCALL) \
F(SETTHIS) \
F(INITPROP) \
F(IMPORT_META) \
F(CALL_IMPORT) \
\
@ -170,7 +171,7 @@ class ObjectBox;
F(POW) \
\
/* Assignment operators (= += -= etc.). */ \
/* ParseNode::isAssignment assumes all these are consecutive. */ \
/* AssignmentNode::test assumes all these are consecutive. */ \
F(ASSIGN) \
F(ADDASSIGN) \
F(SUBASSIGN) \
@ -385,7 +386,10 @@ IsTypeofKind(ParseNodeKind kind)
* PNK_COMMA (ListNode)
* head: list of N comma-separated exprs
* 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
* right: value to assign
* PNK_ADDASSIGN, PNK_SUBASSIGN, PNK_BITORASSIGN, PNK_BITXORASSIGN,

View file

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

View file

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

View file

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