[JSC] Ensure x?.y ?? z is fast
authorross.kirsling@sony.com <ross.kirsling@sony.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Aug 2019 21:09:35 +0000 (21:09 +0000)
committerross.kirsling@sony.com <ross.kirsling@sony.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Aug 2019 21:09:35 +0000 (21:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=200875

Reviewed by Yusuke Suzuki.

JSTests:

* stress/nullish-coalescing.js:

Source/JavaScriptCore:

We anticipate `x?.y ?? z` to quickly become a common idiom in JS. With a little bytecode rearrangement,
we can avoid the "load undefined and check it" dance in the middle and just turn this into two jumps.

Before:
        (get x)
  ----- jundefined_or_null
  |     (get y)
  | --- jmp
  > |   (load undefined)
    > - jnundefined_or_null
      | (get z)
      > end

After:
        (get x)
    --- jundefined_or_null
    |   (get y)
    | - jnundefined_or_null
    > | (get z)
      > end

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::popOptionalChainTarget): Added specialization.
* bytecompiler/BytecodeGenerator.h:
* bytecompiler/NodesCodegen.cpp:
(JSC::CoalesceNode::emitBytecode):
(JSC::OptionalChainNode::emitBytecode):
* parser/ASTBuilder.h:
(JSC::ASTBuilder::makeDeleteNode):
(JSC::ASTBuilder::makeCoalesceNode): Added.
(JSC::ASTBuilder::makeBinaryNode):
* parser/NodeConstructors.h:
(JSC::CoalesceNode::CoalesceNode):
* parser/Nodes.h:
(JSC::ExpressionNode::isDeleteNode const): Added. (Replaces OptionalChainNode::m_isDelete.)

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@249117 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/nullish-coalescing.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/parser/ASTBuilder.h
Source/JavaScriptCore/parser/NodeConstructors.h
Source/JavaScriptCore/parser/Nodes.h

index b76ba95..4d2a8a2 100644 (file)
@@ -1,3 +1,12 @@
+2019-08-26  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [JSC] Ensure x?.y ?? z is fast
+        https://bugs.webkit.org/show_bug.cgi?id=200875
+
+        Reviewed by Yusuke Suzuki.
+
+        * stress/nullish-coalescing.js:
+
 2019-08-23  Tadeu Zagallo  <tzagallo@apple.com>
 
         Remove MaximalFlushInsertionPhase
index 8cbad44..eb38e59 100644 (file)
@@ -89,3 +89,11 @@ shouldNotThrow('0 ?? (1 && 2)');
 shouldNotThrow('0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3 << 4 >> 5 >>> 6 + 7 - 8 * 9 / 0 % 1 ** 2');
 shouldThrowSyntaxError('0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3 << 4 >> 5 >>> 6 + 7 - 8 * 9 / 0 % 1 ** 2 ?? 3');
 shouldThrowSyntaxError('3 ?? 2 ** 1 % 0 / 9 * 8 - 7 + 6 >>> 5 >> 4 << 3 >= 2 <= 1 > 0 < 9 !== 8 === 7 != 6 == 5 & 4 ^ 3 | 2 && 1 || 0');
+
+shouldBe(null?.x ?? 3, 3);
+shouldBe(({})?.x ?? 3, 3);
+shouldBe(({ x: 0 })?.x ?? 3, 0);
+shouldBe(null?.() ?? 3, 3);
+shouldBe((() => 0)?.() ?? 3, 0);
+shouldBe(({ x: 0 })?.[null?.a ?? 'x'] ?? 3, 0);
+shouldBe((() => 0)?.(null?.a ?? 'x') ?? 3, 0);
index d9d1e4a..2aab86b 100644 (file)
@@ -1,3 +1,46 @@
+2019-08-26  Ross Kirsling  <ross.kirsling@sony.com>
+
+        [JSC] Ensure x?.y ?? z is fast
+        https://bugs.webkit.org/show_bug.cgi?id=200875
+
+        Reviewed by Yusuke Suzuki.
+
+        We anticipate `x?.y ?? z` to quickly become a common idiom in JS. With a little bytecode rearrangement,
+        we can avoid the "load undefined and check it" dance in the middle and just turn this into two jumps.
+
+        Before:
+                (get x)
+          ----- jundefined_or_null
+          |     (get y)
+          | --- jmp
+          > |   (load undefined)
+            > - jnundefined_or_null
+              | (get z)
+              > end
+
+        After:
+                (get x)
+            --- jundefined_or_null
+            |   (get y)
+            | - jnundefined_or_null
+            > | (get z)
+              > end
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::popOptionalChainTarget): Added specialization.
+        * bytecompiler/BytecodeGenerator.h:
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::CoalesceNode::emitBytecode):
+        (JSC::OptionalChainNode::emitBytecode):
+        * parser/ASTBuilder.h:
+        (JSC::ASTBuilder::makeDeleteNode):
+        (JSC::ASTBuilder::makeCoalesceNode): Added.
+        (JSC::ASTBuilder::makeBinaryNode):
+        * parser/NodeConstructors.h:
+        (JSC::CoalesceNode::CoalesceNode):
+        * parser/Nodes.h:
+        (JSC::ExpressionNode::isDeleteNode const): Added. (Replaces OptionalChainNode::m_isDelete.)
+
 2019-08-26  Carlos Alberto Lopez Perez  <clopez@igalia.com>
 
         Missing media controls when WebKit is built with Python3
index 950aaa2..1a3e742 100644 (file)
@@ -4969,14 +4969,18 @@ void BytecodeGenerator::pushOptionalChainTarget()
     m_optionalChainTargetStack.append(newLabel());
 }
 
-void BytecodeGenerator::popOptionalChainTarget(RegisterID* dst, bool isDelete)
+void BytecodeGenerator::popOptionalChainTarget()
 {
     ASSERT(m_optionalChainTargetStack.size());
+    emitLabel(m_optionalChainTargetStack.takeLast().get());
+}
 
+void BytecodeGenerator::popOptionalChainTarget(RegisterID* dst, bool isDelete)
+{
     Ref<Label> endLabel = newLabel();
     emitJump(endLabel.get());
 
-    emitLabel(m_optionalChainTargetStack.takeLast().get());
+    popOptionalChainTarget();
     emitLoad(dst, isDelete ? jsBoolean(true) : jsUndefined());
 
     emitLabel(endLabel.get());
index 36a6694..3b1e85d 100644 (file)
@@ -960,7 +960,8 @@ namespace JSC {
         void popFinallyControlFlowScope();
 
         void pushOptionalChainTarget();
-        void popOptionalChainTarget(RegisterID* dst, bool isDelete = false);
+        void popOptionalChainTarget();
+        void popOptionalChainTarget(RegisterID* dst, bool isDelete);
         void emitOptionalCheck(RegisterID* src);
 
         void pushIndexedForInScope(RegisterID* local, RegisterID* index);
index 862f0e5..b66c82b 100644 (file)
@@ -2408,13 +2408,18 @@ void LogicalOpNode::emitBytecodeInConditionContext(BytecodeGenerator& generator,
 RegisterID* CoalesceNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
 {
     RefPtr<RegisterID> temp = generator.tempDestination(dst);
-    Ref<Label> target = generator.newLabel();
+    Ref<Label> endLabel = generator.newLabel();
 
+    if (m_hasAbsorbedOptionalChain)
+        generator.pushOptionalChainTarget();
     generator.emitNode(temp.get(), m_expr1);
-    generator.emitJumpIfFalse(generator.emitIsUndefinedOrNull(generator.newTemporary(), temp.get()), target.get());
+    generator.emitJumpIfFalse(generator.emitIsUndefinedOrNull(generator.newTemporary(), temp.get()), endLabel.get());
+
+    if (m_hasAbsorbedOptionalChain)
+        generator.popOptionalChainTarget();
     generator.emitNodeInTailPosition(temp.get(), m_expr2);
-    generator.emitLabel(target.get());
 
+    generator.emitLabel(endLabel.get());
     return generator.move(dst, temp.get());
 }
 
@@ -2428,7 +2433,7 @@ RegisterID* OptionalChainNode::emitBytecode(BytecodeGenerator& generator, Regist
         generator.pushOptionalChainTarget();
     generator.emitNodeInTailPosition(finalDest.get(), m_expr);
     if (m_isOutermost)
-        generator.popOptionalChainTarget(finalDest.get(), m_isDelete);
+        generator.popOptionalChainTarget(finalDest.get(), m_expr->isDeleteNode());
 
     return finalDest.get();
 }
index 3b751ed..f70cfeb 100644 (file)
@@ -152,6 +152,7 @@ public:
     ExpressionNode* makeBitXOrNode(const JSTokenLocation&, ExpressionNode* left, ExpressionNode* right, bool rightHasAssignments);
     ExpressionNode* makeBitAndNode(const JSTokenLocation&, ExpressionNode* left, ExpressionNode* right, bool rightHasAssignments);
     ExpressionNode* makeBitOrNode(const JSTokenLocation&, ExpressionNode* left, ExpressionNode* right, bool rightHasAssignments);
+    ExpressionNode* makeCoalesceNode(const JSTokenLocation&, ExpressionNode* left, ExpressionNode* right);
     ExpressionNode* makeLeftShiftNode(const JSTokenLocation&, ExpressionNode* left, ExpressionNode* right, bool rightHasAssignments);
     ExpressionNode* makeRightShiftNode(const JSTokenLocation&, ExpressionNode* left, ExpressionNode* right, bool rightHasAssignments);
     ExpressionNode* makeURightShiftNode(const JSTokenLocation&, ExpressionNode* left, ExpressionNode* right, bool rightHasAssignments);
@@ -1159,7 +1160,6 @@ ExpressionNode* ASTBuilder::makeDeleteNode(const JSTokenLocation& location, Expr
         if (optionalChain->expr()->isLocation()) {
             ASSERT(!optionalChain->expr()->isResolveNode());
             optionalChain->setExpr(makeDeleteNode(location, optionalChain->expr(), start, divot, end));
-            optionalChain->setIsDelete();
             return optionalChain;
         }
     }
@@ -1361,6 +1361,20 @@ ExpressionNode* ASTBuilder::makeBitXOrNode(const JSTokenLocation& location, Expr
     return new (m_parserArena) BitXOrNode(location, expr1, expr2, rightHasAssignments);
 }
 
+ExpressionNode* ASTBuilder::makeCoalesceNode(const JSTokenLocation& location, ExpressionNode* expr1, ExpressionNode* expr2)
+{
+    // Optimization for `x?.y ?? z`.
+    if (expr1->isOptionalChain()) {
+        OptionalChainNode* optionalChain = static_cast<OptionalChainNode*>(expr1);
+        if (!optionalChain->expr()->isDeleteNode()) {
+            constexpr bool hasAbsorbedOptionalChain = true;
+            return new (m_parserArena) CoalesceNode(location, optionalChain->expr(), expr2, hasAbsorbedOptionalChain);
+        }
+    }
+    constexpr bool hasAbsorbedOptionalChain = false;
+    return new (m_parserArena) CoalesceNode(location, expr1, expr2, hasAbsorbedOptionalChain);
+}
+
 ExpressionNode* ASTBuilder::makeFunctionCallNode(const JSTokenLocation& location, ExpressionNode* func, bool previousBaseWasSuper, ArgumentsNode* args, const JSTextPosition& divotStart, const JSTextPosition& divot, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth, bool isOptionalCall)
 {
     ASSERT(divot.offset >= divot.lineStartOffset);
@@ -1424,7 +1438,7 @@ ExpressionNode* ASTBuilder::makeBinaryNode(const JSTokenLocation& location, int
 {
     switch (token) {
     case COALESCE:
-        return new (m_parserArena) CoalesceNode(location, lhs.first, rhs.first);
+        return makeCoalesceNode(location, lhs.first, rhs.first);
 
     case OR:
         return new (m_parserArena) LogicalOpNode(location, lhs.first, rhs.first, OpLogicalOr);
index d65b08b..7589d7c 100644 (file)
@@ -669,10 +669,11 @@ namespace JSC {
     {
     }
 
-    inline CoalesceNode::CoalesceNode(const JSTokenLocation& location, ExpressionNode* expr1, ExpressionNode* expr2)
+    inline CoalesceNode::CoalesceNode(const JSTokenLocation& location, ExpressionNode* expr1, ExpressionNode* expr2, bool hasAbsorbedOptionalChain)
         : ExpressionNode(location, ResultType::forCoalesce(expr1->resultDescriptor(), expr2->resultDescriptor()))
         , m_expr1(expr1)
         , m_expr2(expr2)
+        , m_hasAbsorbedOptionalChain(hasAbsorbedOptionalChain)
     {
     }
 
index fa70acb..0e77e62 100644 (file)
@@ -205,6 +205,7 @@ namespace JSC {
         virtual bool isBytecodeIntrinsicNode() const { return false; }
         virtual bool isBinaryOpNode() const { return false; }
         virtual bool isFunctionCall() const { return false; }
+        virtual bool isDeleteNode() const { return false; }
         virtual bool isOptionalChain() const { return false; }
 
         virtual void emitBytecodeInConditionContext(BytecodeGenerator&, Label&, Label&, FallThroughMode);
@@ -994,6 +995,8 @@ namespace JSC {
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
 
+        bool isDeleteNode() const final { return true; }
+
         const Identifier& m_ident;
     };
 
@@ -1004,6 +1007,8 @@ namespace JSC {
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
 
+        bool isDeleteNode() const final { return true; }
+
         ExpressionNode* m_base;
         ExpressionNode* m_subscript;
     };
@@ -1015,6 +1020,8 @@ namespace JSC {
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
 
+        bool isDeleteNode() const final { return true; }
+
         ExpressionNode* m_base;
         const Identifier& m_ident;
     };
@@ -1026,6 +1033,8 @@ namespace JSC {
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
 
+        bool isDeleteNode() const final { return true; }
+
         ExpressionNode* m_expr;
     };
 
@@ -1312,13 +1321,14 @@ namespace JSC {
 
     class CoalesceNode final : public ExpressionNode {
     public:
-        CoalesceNode(const JSTokenLocation&, ExpressionNode* expr1, ExpressionNode* expr2);
+        CoalesceNode(const JSTokenLocation&, ExpressionNode* expr1, ExpressionNode* expr2, bool);
 
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = nullptr) final;
 
         ExpressionNode* m_expr1;
         ExpressionNode* m_expr2;
+        bool m_hasAbsorbedOptionalChain;
     };
 
     class OptionalChainNode final : public ExpressionNode {
@@ -1328,8 +1338,6 @@ namespace JSC {
         void setExpr(ExpressionNode* expr) { m_expr = expr; }
         ExpressionNode* expr() const { return m_expr; }
 
-        void setIsDelete() { m_isDelete = true; }
-
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = nullptr) final;
 
@@ -1337,7 +1345,6 @@ namespace JSC {
 
         ExpressionNode* m_expr;
         bool m_isOutermost;
-        bool m_isDelete { false };
     };
 
     // The ternary operator, "m_logical ? m_expr1 : m_expr2"