summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartok <martok@martoks-place.de>2023-04-26 15:16:33 +0200
committerMartok <martok@martoks-place.de>2023-05-01 17:16:22 +0200
commit3d38507c23f3dc7b0cb8b69d54d41752fb8be08c (patch)
tree3ebce8f6772b48b37a93a9b903f2d6cc31b0f824
parent12626eeba378d8f528b73d230ec1aec5752e6f22 (diff)
downloaduxp-3d38507c23f3dc7b0cb8b69d54d41752fb8be08c.tar.gz
Issue #2097 - Implement logical assignment operators
Based-on: 1629106/1, 1684020
-rw-r--r--js/src/builtin/ReflectParse.cpp14
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp244
-rw-r--r--js/src/frontend/BytecodeEmitter.h5
-rw-r--r--js/src/frontend/FoldConstants.cpp6
-rw-r--r--js/src/frontend/ParseNode.cpp3
-rw-r--r--js/src/frontend/ParseNode.h9
-rw-r--r--js/src/frontend/Parser.cpp14
-rw-r--r--js/src/frontend/TokenKind.h3
-rw-r--r--js/src/frontend/TokenStream.cpp10
9 files changed, 302 insertions, 6 deletions
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp
index b40c5fb408..0205887ff6 100644
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -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:
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index eb25903911..1e6390f5f1 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -518,6 +518,17 @@ BytecodeEmitter::emitPopN(unsigned 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)
{
return emit2(JSOP_CHECKISOBJ, uint8_t(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:
@@ -3978,6 +3992,225 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp,
}
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,
NewObjectKind newKind)
@@ -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;
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
index 7521e236dc..732a1f24f8 100644
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -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);
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
index 813900a23f..9fb963f63e 100644
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -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:
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
index a529c93d14..7ad470865f 100644
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -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:
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
index 471f42f905..46977ee253 100644
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -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
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index a088949c8b..a33b3d0d90 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -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();
diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h
index f6f947f608..232e373dcf 100644
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -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, "'&='") \
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
index d1e586afc2..a201e42a52 100644
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -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;