Issue #2097 - Implement logical assignment operators

Based-on: 1629106/1, 1684020
This commit is contained in:
Martok 2023-04-26 15:16:33 +02:00 committed by roytam1
commit 10951a169c
9 changed files with 302 additions and 6 deletions

View file

@ -53,6 +53,8 @@ enum AssignmentOperator {
AOP_LSH, AOP_RSH, AOP_URSH,
/* binary */
AOP_BITOR, AOP_BITXOR, AOP_BITAND,
/* short-circuit */
AOP_COALESCE, AOP_OR, AOP_AND,
AOP_LIMIT
};
@ -122,6 +124,9 @@ static const char* const aopNames[] = {
"|=", /* AOP_BITOR */
"^=", /* AOP_BITXOR */
"&=" /* AOP_BITAND */
"\?\?=", /* AOP_COALESCE */
"||=", /* AOP_OR */
"&&=", /* AOP_AND */
};
static const char* const binopNames[] = {
@ -1974,6 +1979,12 @@ ASTSerializer::aop(JSOp op)
return AOP_BITXOR;
case JSOP_BITAND:
return AOP_BITAND;
case JSOP_COALESCE:
return AOP_COALESCE;
case JSOP_OR:
return AOP_OR;
case JSOP_AND:
return AOP_AND;
default:
return AOP_ERR;
}
@ -3103,6 +3114,9 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
case PNK_COALESCEASSIGN:
case PNK_ORASSIGN:
case PNK_ANDASSIGN:
case PNK_BITORASSIGN:
case PNK_BITXORASSIGN:
case PNK_BITANDASSIGN:

View file

@ -517,6 +517,17 @@ BytecodeEmitter::emitPopN(unsigned n)
return emitUint16Operand(JSOP_POPN, n);
}
bool
BytecodeEmitter::emitUnpickN(unsigned n)
{
MOZ_ASSERT(n != 0);
if (n == 1)
return emit1(JSOP_SWAP);
return emit2(JSOP_UNPICK, n);
}
bool
BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind)
{
@ -1221,6 +1232,9 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
case PNK_COALESCEASSIGN:
case PNK_ORASSIGN:
case PNK_ANDASSIGN:
case PNK_BITORASSIGN:
case PNK_BITXORASSIGN:
case PNK_BITANDASSIGN:
@ -3977,6 +3991,225 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp,
return true;
}
bool
BytecodeEmitter::emitShortCircuitAssignment(ParseNodeKind kind, JSOp op,
ParseNode* lhs, ParseNode* rhs)
{
TDZCheckCache tdzCache(this);
// |name| is used within NameOpEmitter, so its lifetime must surpass |noe|.
RootedAtom name(cx);
// Select the appropriate emitter based on the left-hand side.
Maybe<NameOpEmitter> noe;
Maybe<PropOpEmitter> poe;
Maybe<ElemOpEmitter> eoe;
int32_t startDepth = stackDepth;
// Number of values pushed onto the stack in addition to the lhs value.
int32_t numPushed;
// Evaluate the left-hand side expression and compute any stack values needed
// for the assignment.
switch (lhs->getKind()) {
case PNK_NAME: {
name = lhs->as<NameNode>().name();
noe.emplace(this, name, NameOpEmitter::Kind::CompoundAssignment);
if (!noe->prepareForRhs()) {
// [stack] ENV? LHS
return false;
}
numPushed = noe->emittedBindOp();
break;
}
case PNK_DOT: {
PropertyAccess* prop = &lhs->as<PropertyAccess>();
bool isSuper = prop->isSuper();
poe.emplace(this, PropOpEmitter::Kind::CompoundAssignment,
isSuper ? PropOpEmitter::ObjKind::Super
: PropOpEmitter::ObjKind::Other);
if (!poe->prepareForObj())
return false;
if (isSuper) {
UnaryNode* base = &prop->expression().as<UnaryNode>();
if (!emitGetThisForSuperBase(base)) {
// [stack] THIS SUPERBASE
return false;
}
} else {
if (!emitTree(&prop->expression())) {
// [stack] OBJ
return false;
}
}
if (!poe->emitGet(prop->key().atom())) {
// [stack] # if Super
// [stack] THIS SUPERBASE LHS
// [stack] # otherwise
// [stack] OBJ LHS
return false;
}
if (!poe->prepareForRhs()) {
// [stack] # if Super
// [stack] THIS SUPERBASE LHS
// [stack] # otherwise
// [stack] OBJ LHS
return false;
}
numPushed = 1 + isSuper;
break;
}
case PNK_ELEM: {
PropertyByValue* elem = &lhs->as<PropertyByValue>();
bool isSuper = elem->isSuper();
eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment,
isSuper ? ElemOpEmitter::ObjKind::Super
: ElemOpEmitter::ObjKind::Other);
if (!emitElemObjAndKey(elem, isSuper, *eoe)) {
// [stack] # if Super
// [stack] THIS KEY
// [stack] # otherwise
// [stack] OBJ KEY
return false;
}
if (!eoe->emitGet()) {
// [stack] # if Super
// [stack] THIS KEY SUPERBASE LHS
// [stack] # otherwise
// [stack] OBJ KEY LHS
return false;
}
if (!eoe->prepareForRhs()) {
// [stack] # if Super
// [stack] THIS KEY SUPERBASE LHS
// [stack] # otherwise
// [stack] OBJ KEY LHS
return false;
}
numPushed = 2 + isSuper;
break;
}
default:
MOZ_CRASH();
}
MOZ_ASSERT(stackDepth == startDepth + numPushed + 1);
// Test for the short-circuit condition.
JumpList jump;
if (!emitJump(op, &jump)) {
// [stack] ... LHS
return false;
}
// The short-circuit condition wasn't fulfilled, pop the left-hand side value
// which was kept on the stack.
if (!emit1(JSOP_POP)) {
// [stack] ...
return false;
}
// TODO: Open spec issue about setting inferred function names.
// <https://github.com/tc39/proposal-logical-assignment/issues/23>
if (!emitTree(rhs)) {
// [stack] ... RHS
return false;
}
// Perform the actual assignment.
switch (lhs->getKind()) {
case PNK_NAME: {
if (!noe->emitAssignment()) {
// [stack] RHS
return false;
}
break;
}
case PNK_DOT: {
PropertyAccess* prop = &lhs->as<PropertyAccess>();
if (!poe->emitAssignment(prop->key().atom())) {
// [stack] RHS
return false;
}
break;
}
case PNK_ELEM: {
if (!eoe->emitAssignment()) {
// [stack] RHS
return false;
}
break;
}
default:
MOZ_CRASH();
}
MOZ_ASSERT(stackDepth == startDepth + 1);
// Join with the short-circuit jump and pop anything left on the stack.
if (numPushed > 0) {
JumpList jumpAroundPop;
if (!emitJump(JSOP_GOTO, &jumpAroundPop)) {
// [stack] RHS
return false;
}
if (!emitJumpTargetAndPatch(jump)) {
// [stack] ... LHS
return false;
}
// Reconstruct the stack depth after the jump.
stackDepth = startDepth + 1 + numPushed;
// Move the left-hand side value to the bottom and pop the rest.
if (!emitUnpickN(numPushed)) {
// [stack] LHS ...
return false;
}
if (!emitPopN(numPushed)) {
// [stack] LHS
return false;
}
if (!emitJumpTargetAndPatch(jumpAroundPop)) {
// [stack] LHS | RHS
return false;
}
} else {
if (!emitJumpTargetAndPatch(jump)) {
// [stack] LHS | RHS
return false;
}
}
MOZ_ASSERT(stackDepth == startDepth + 1);
return true;
}
bool
ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects,
MutableHandleValue vp, Value* compare, size_t ncompare,
@ -9074,6 +9307,17 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
break;
}
case PNK_COALESCEASSIGN:
case PNK_ORASSIGN:
case PNK_ANDASSIGN: {
BinaryNode* assignNode = &pn->as<BinaryNode>();
if (!emitShortCircuitAssignment(assignNode->getKind(), assignNode->getOp(),
assignNode->left(), assignNode->right())) {
return false;
}
break;
}
case PNK_CONDITIONAL:
if (!emitConditionalExpression(pn->as<ConditionalExpression>(), valueUsage))
return false;

View file

@ -457,6 +457,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
// Helper to emit JSOP_POP or JSOP_POPN.
MOZ_MUST_USE bool emitPopN(unsigned n);
// Helper to emit JSOP_SWAP or JSOP_UNPICK.
MOZ_MUST_USE bool emitUnpickN(unsigned n);
// Helper to emit JSOP_CHECKISOBJ.
MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind);
@ -701,6 +704,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitTemplateString(ListNode* templateString);
MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp,
ParseNode* lhs, ParseNode* rhs);
MOZ_MUST_USE bool emitShortCircuitAssignment(ParseNodeKind kind, JSOp op,
ParseNode* lhs, ParseNode* rhs);
MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode);
MOZ_MUST_USE bool emitStatement(UnaryNode* exprStmt);

View file

@ -355,6 +355,9 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
case PNK_COALESCEASSIGN:
case PNK_ORASSIGN:
case PNK_ANDASSIGN:
case PNK_BITORASSIGN:
case PNK_BITXORASSIGN:
case PNK_BITANDASSIGN:
@ -1884,6 +1887,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
case PNK_COALESCEASSIGN:
case PNK_ORASSIGN:
case PNK_ANDASSIGN:
case PNK_BITORASSIGN:
case PNK_BITANDASSIGN:
case PNK_BITXORASSIGN:

View file

@ -262,6 +262,9 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
case PNK_COALESCEASSIGN:
case PNK_ORASSIGN:
case PNK_ANDASSIGN:
case PNK_BITORASSIGN:
case PNK_BITXORASSIGN:
case PNK_BITANDASSIGN:

View file

@ -176,6 +176,9 @@ class ObjectBox;
F(ASSIGN) \
F(ADDASSIGN) \
F(SUBASSIGN) \
F(COALESCEASSIGN) \
F(ORASSIGN) \
F(ANDASSIGN) \
F(BITORASSIGN) \
F(BITXORASSIGN) \
F(BITANDASSIGN) \
@ -395,8 +398,10 @@ IsTypeofKind(ParseNodeKind kind)
* PNK_ASSIGN (AssignmentNode)
* left: target of assignment
* right: value to assign
* PNK_ADDASSIGN, PNK_SUBASSIGN, PNK_BITORASSIGN, PNK_BITXORASSIGN,
* PNK_BITANDASSIGN, PNK_LSHASSIGN, PNK_RSHASSIGN, PNK_URSHASSIGN,
* PNK_ADDASSIGN, PNK_SUBASSIGN,
* PNK_COALESCEASSIGN, PNK_ORASSIGN, PNK_ANDASSIGN,
* PNK_BITORASSIGN, PNK_BITXORASSIGN, PNK_BITANDASSIGN,
* PNK_LSHASSIGN, PNK_RSHASSIGN, PNK_URSHASSIGN,
* PNK_MULASSIGN, PNK_DIVASSIGN, PNK_MODASSIGN, PNK_POWASSIGN (AssignmentNode)
* left: target of assignment
* right: value to assign

View file

@ -9150,6 +9150,9 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl
case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break;
case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break;
case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break;
case TOK_COALESCEASSIGN: kind = PNK_COALESCEASSIGN; op = JSOP_COALESCE; break;
case TOK_ORASSIGN: kind = PNK_ORASSIGN; op = JSOP_OR; break;
case TOK_ANDASSIGN: kind = PNK_ANDASSIGN; op = JSOP_AND; break;
case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break;
case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; op = JSOP_BITXOR; break;
case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; op = JSOP_BITAND; break;
@ -9279,6 +9282,17 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl
} else if (handler.isPropertyAccess(lhs)) {
// Permitted: no additional testing/fixup needed.
} else if (handler.isFunctionCall(lhs)) {
// We don't have to worry about backward compatibility issues with the new
// compound assignment operators, so we always throw here. Also that way we
// don't have to worry if |f() &&= expr| should always throw an error or
// only if |f()| returns true.
if (kind == PNK_COALESCEASSIGN ||
kind == PNK_ORASSIGN ||
kind == PNK_ADDASSIGN) {
errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS);
return null();
}
if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS))
return null();

View file

@ -219,6 +219,9 @@
range(ASSIGNMENT_START, ASSIGN) \
macro(ADDASSIGN, "'+='") \
macro(SUBASSIGN, "'-='") \
macro(COALESCEASSIGN, "'\?\?='") /* avoid trigraphs warning */ \
macro(ORASSIGN, "'||='") \
macro(ANDASSIGN, "'&&='") \
macro(BITORASSIGN, "'|='") \
macro(BITXORASSIGN, "'^='") \
macro(BITANDASSIGN, "'&='") \

View file

@ -1876,7 +1876,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
case '|':
if (matchChar('|'))
tp->type = TOK_OR;
tp->type = matchChar('=') ? TOK_ORASSIGN : TOK_OR;
else
tp->type = matchChar('=') ? TOK_BITORASSIGN : TOK_BITOR;
goto out;
@ -1887,7 +1887,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
case '&':
if (matchChar('&'))
tp->type = TOK_AND;
tp->type = matchChar('=') ? TOK_ANDASSIGN : TOK_AND;
else
tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND;
goto out;
@ -1906,8 +1906,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
ungetCharIgnoreEOL(c);
tp->type = TOK_OPTCHAIN;
}
} else {
tp->type = matchChar('?') ? TOK_COALESCE : TOK_HOOK;
} else if (matchChar('?')) {
tp->type = matchChar('=') ? TOK_COALESCEASSIGN : TOK_COALESCE;
} else {
tp->type = TOK_HOOK;
}
goto out;