Changeset 247819 in webkit


Ignore:
Timestamp:
Jul 25, 2019 12:50:46 AM (5 years ago)
Author:
Ross Kirsling
Message:

[ESNext] Implement nullish coalescing
https://bugs.webkit.org/show_bug.cgi?id=200072

Reviewed by Darin Adler.

JSTests:

  • stress/nullish-coalescing.js: Added.

Source/JavaScriptCore:

Implement the nullish coalescing proposal, which has now reached Stage 3 at TC39.

This introduces a ?? operator which:

acts like
but checks for nullishness instead of truthiness
has a precedence lower than
(or any other binary operator)
must be disambiguated with parentheses when combined with
or &&
  • bytecompiler/NodesCodegen.cpp:

(JSC::CoalesceNode::emitBytecode): Added.
Bytecode must use OpIsUndefinedOrNull and not OpNeqNull because of document.all.

  • parser/ASTBuilder.h:

(JSC::ASTBuilder::makeBinaryNode):

  • parser/Lexer.cpp:

(JSC::Lexer<T>::lexWithoutClearingLineTerminator):

  • parser/NodeConstructors.h:

(JSC::CoalesceNode::CoalesceNode): Added.

  • parser/Nodes.h:

Introduce new token and AST node.

  • parser/Parser.cpp:

(JSC::Parser<LexerType>::parseBinaryExpression):
Implement early error.

  • parser/ParserTokens.h:

Since this patch needs to shift the value of every binary operator token anyway,
let's only bother to increment their LSBs when we actually have a precedence conflict.

  • parser/ResultType.h:

(JSC::ResultType::definitelyIsNull const): Added.
(JSC::ResultType::mightBeUndefinedOrNull const): Added.
(JSC::ResultType::forCoalesce): Added.
We can do better than forLogicalOp here; let's be as accurate as possible.

  • runtime/Options.h:

Add runtime feature flag.

Tools:

  • Scripts/run-jsc-stress-tests:
Location:
trunk
Files:
1 added
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r247811 r247819  
     12019-07-25  Ross Kirsling  <ross.kirsling@sony.com>
     2
     3        [ESNext] Implement nullish coalescing
     4        https://bugs.webkit.org/show_bug.cgi?id=200072
     5
     6        Reviewed by Darin Adler.
     7
     8        * stress/nullish-coalescing.js: Added.
     9
    1102019-07-24  Alexey Shvayka  <shvaikalesh@gmail.com>
    211
  • trunk/Source/JavaScriptCore/ChangeLog

    r247811 r247819  
     12019-07-25  Ross Kirsling  <ross.kirsling@sony.com>
     2
     3        [ESNext] Implement nullish coalescing
     4        https://bugs.webkit.org/show_bug.cgi?id=200072
     5
     6        Reviewed by Darin Adler.
     7
     8        Implement the nullish coalescing proposal, which has now reached Stage 3 at TC39.
     9
     10        This introduces a ?? operator which:
     11          - acts like || but checks for nullishness instead of truthiness
     12          - has a precedence lower than || (or any other binary operator)
     13          - must be disambiguated with parentheses when combined with || or &&
     14
     15        * bytecompiler/NodesCodegen.cpp:
     16        (JSC::CoalesceNode::emitBytecode): Added.
     17        Bytecode must use OpIsUndefinedOrNull and not OpNeqNull because of document.all.
     18
     19        * parser/ASTBuilder.h:
     20        (JSC::ASTBuilder::makeBinaryNode):
     21        * parser/Lexer.cpp:
     22        (JSC::Lexer<T>::lexWithoutClearingLineTerminator):
     23        * parser/NodeConstructors.h:
     24        (JSC::CoalesceNode::CoalesceNode): Added.
     25        * parser/Nodes.h:
     26        Introduce new token and AST node.
     27
     28        * parser/Parser.cpp:
     29        (JSC::Parser<LexerType>::parseBinaryExpression):
     30        Implement early error.
     31
     32        * parser/ParserTokens.h:
     33        Since this patch needs to shift the value of every binary operator token anyway,
     34        let's only bother to increment their LSBs when we actually have a precedence conflict.
     35
     36        * parser/ResultType.h:
     37        (JSC::ResultType::definitelyIsNull const): Added.
     38        (JSC::ResultType::mightBeUndefinedOrNull const): Added.
     39        (JSC::ResultType::forCoalesce): Added.
     40        We can do better than forLogicalOp here; let's be as accurate as possible.
     41
     42        * runtime/Options.h:
     43        Add runtime feature flag.
     44
    1452019-07-24  Alexey Shvayka  <shvaikalesh@gmail.com>
    246
  • trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

    r247088 r247819  
    23392339}
    23402340
     2341// ------------------------------ CoalesceNode ----------------------------
     2342
     2343RegisterID* CoalesceNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
     2344{
     2345    RefPtr<RegisterID> temp = generator.tempDestination(dst);
     2346    Ref<Label> target = generator.newLabel();
     2347
     2348    generator.emitNode(temp.get(), m_expr1);
     2349    generator.emitJumpIfFalse(generator.emitUnaryOp<OpIsUndefinedOrNull>(generator.newTemporary(), temp.get()), target.get());
     2350    generator.emitNodeInTailPosition(temp.get(), m_expr2);
     2351    generator.emitLabel(target.get());
     2352
     2353    return generator.move(dst, temp.get());
     2354}
     2355
    23412356// ------------------------------ ConditionalNode ------------------------------
    23422357
  • trunk/Source/JavaScriptCore/parser/ASTBuilder.h

    r245406 r247819  
    13941394{
    13951395    switch (token) {
     1396    case COALESCE:
     1397        return new (m_parserArena) CoalesceNode(location, lhs.first, rhs.first);
     1398
    13961399    case OR:
    13971400        return new (m_parserArena) LogicalOpNode(location, lhs.first, rhs.first, OpLogicalOr);
  • trunk/Source/JavaScriptCore/parser/Lexer.cpp

    r245697 r247819  
    21542154        break;
    21552155    case CharacterQuestion:
     2156        shift();
     2157        if (Options::useNullishCoalescing() && m_current == '?') {
     2158            shift();
     2159            token = COALESCE;
     2160            break;
     2161        }
    21562162        token = QUESTION;
    2157         shift();
    21582163        break;
    21592164    case CharacterTilde:
  • trunk/Source/JavaScriptCore/parser/NodeConstructors.h

    r238543 r247819  
    670670    }
    671671
     672    inline CoalesceNode::CoalesceNode(const JSTokenLocation& location, ExpressionNode* expr1, ExpressionNode* expr2)
     673        : ExpressionNode(location, ResultType::forCoalesce(expr1->resultDescriptor(), expr2->resultDescriptor()))
     674        , m_expr1(expr1)
     675        , m_expr2(expr2)
     676    {
     677    }
     678
    672679    inline ConditionalNode::ConditionalNode(const JSTokenLocation& location, ExpressionNode* logical, ExpressionNode* expr1, ExpressionNode* expr2)
    673680        : ExpressionNode(location)
  • trunk/Source/JavaScriptCore/parser/Nodes.h

    r245406 r247819  
    13061306    };
    13071307
     1308    class CoalesceNode final : public ExpressionNode {
     1309    public:
     1310        CoalesceNode(const JSTokenLocation&, ExpressionNode* expr1, ExpressionNode* expr2);
     1311
     1312    private:
     1313        RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
     1314
     1315        ExpressionNode* m_expr1;
     1316        ExpressionNode* m_expr2;
     1317    };
     1318
    13081319    // The ternary operator, "m_logical ? m_expr1 : m_expr2"
    13091320    class ConditionalNode final : public ExpressionNode {
  • trunk/Source/JavaScriptCore/parser/Parser.cpp

    r246549 r247819  
    38843884    typename TreeBuilder::BinaryExprContext binaryExprContext(context);
    38853885    JSTokenLocation location(tokenLocation());
     3886    bool hasLogicalOperator = false;
     3887    bool hasCoalesceOperator = false;
     3888
    38863889    while (true) {
    38873890        JSTextPosition exprStart = tokenStartPosition();
     
    38903893        TreeExpression current = parseUnaryExpression(context);
    38913894        failIfFalse(current, "Cannot parse expression");
    3892        
     3895
    38933896        context.appendBinaryExpressionInfo(operandStackDepth, current, exprStart, lastTokenEndPosition(), lastTokenEndPosition(), initialAssignments != m_parserState.assignmentCount);
     3897        int precedence = isBinaryOperator(m_token.m_type);
     3898        if (!precedence)
     3899            break;
    38943900
    38953901        // 12.6 https://tc39.github.io/ecma262/#sec-exp-operator
     
    39163922        failIfTrue(match(POW) && isUnaryOpExcludingUpdateOp(leadingTokenTypeForUnaryExpression), "Ambiguous unary expression in the left hand side of the exponentiation expression; parentheses must be used to disambiguate the expression");
    39173923
    3918         int precedence = isBinaryOperator(m_token.m_type);
    3919         if (!precedence)
    3920             break;
     3924        // Mixing ?? with || or && is currently specified as an early error.
     3925        // Since ?? is the lowest-precedence binary operator, it suffices to check whether these ever coexist in the operator stack.
     3926        if (match(AND) || match(OR))
     3927            hasLogicalOperator = true;
     3928        else if (match(COALESCE))
     3929            hasCoalesceOperator = true;
     3930        failIfTrue(hasLogicalOperator && hasCoalesceOperator, "Coalescing and logical operators used together in the same expression; parentheses must be used to disambiguate");
     3931
    39213932        m_parserState.nonTrivialExpressionCount++;
    39223933        m_parserState.nonLHSCount++;
  • trunk/Source/JavaScriptCore/parser/ParserTokens.h

    r241201 r247819  
    149149    VOIDTOKEN = 7 | UnaryOpTokenFlag | KeywordTokenFlag,
    150150    DELETETOKEN = 8 | UnaryOpTokenFlag | KeywordTokenFlag,
    151     OR = 0 | BINARY_OP_PRECEDENCE(1),
    152     AND = 1 | BINARY_OP_PRECEDENCE(2),
    153     BITOR = 2 | BINARY_OP_PRECEDENCE(3),
    154     BITXOR = 3 | BINARY_OP_PRECEDENCE(4),
    155     BITAND = 4 | BINARY_OP_PRECEDENCE(5),
    156     EQEQ = 5 | BINARY_OP_PRECEDENCE(6),
    157     NE = 6 | BINARY_OP_PRECEDENCE(6),
    158     STREQ = 7 | BINARY_OP_PRECEDENCE(6),
    159     STRNEQ = 8 | BINARY_OP_PRECEDENCE(6),
    160     LT = 9 | BINARY_OP_PRECEDENCE(7),
    161     GT = 10 | BINARY_OP_PRECEDENCE(7),
    162     LE = 11 | BINARY_OP_PRECEDENCE(7),
    163     GE = 12 | BINARY_OP_PRECEDENCE(7),
    164     INSTANCEOF = 13 | BINARY_OP_PRECEDENCE(7) | KeywordTokenFlag,
    165     INTOKEN = 14 | IN_OP_PRECEDENCE(7) | KeywordTokenFlag,
    166     LSHIFT = 15 | BINARY_OP_PRECEDENCE(8),
    167     RSHIFT = 16 | BINARY_OP_PRECEDENCE(8),
    168     URSHIFT = 17 | BINARY_OP_PRECEDENCE(8),
    169     PLUS = 18 | BINARY_OP_PRECEDENCE(9) | UnaryOpTokenFlag,
    170     MINUS = 19 | BINARY_OP_PRECEDENCE(9) | UnaryOpTokenFlag,
    171     TIMES = 20 | BINARY_OP_PRECEDENCE(10),
    172     DIVIDE = 21 | BINARY_OP_PRECEDENCE(10),
    173     MOD = 22 | BINARY_OP_PRECEDENCE(10),
    174     POW = 23 | BINARY_OP_PRECEDENCE(11) | RightAssociativeBinaryOpTokenFlag, // Make sure that POW has the highest operator precedence.
     151    COALESCE = 0 | BINARY_OP_PRECEDENCE(1),
     152    OR = 0 | BINARY_OP_PRECEDENCE(2),
     153    AND = 0 | BINARY_OP_PRECEDENCE(3),
     154    BITOR = 0 | BINARY_OP_PRECEDENCE(4),
     155    BITXOR = 0 | BINARY_OP_PRECEDENCE(5),
     156    BITAND = 0 | BINARY_OP_PRECEDENCE(6),
     157    EQEQ = 0 | BINARY_OP_PRECEDENCE(7),
     158    NE = 1 | BINARY_OP_PRECEDENCE(7),
     159    STREQ = 2 | BINARY_OP_PRECEDENCE(7),
     160    STRNEQ = 3 | BINARY_OP_PRECEDENCE(7),
     161    LT = 0 | BINARY_OP_PRECEDENCE(8),
     162    GT = 1 | BINARY_OP_PRECEDENCE(8),
     163    LE = 2 | BINARY_OP_PRECEDENCE(8),
     164    GE = 3 | BINARY_OP_PRECEDENCE(8),
     165    INSTANCEOF = 4 | BINARY_OP_PRECEDENCE(8) | KeywordTokenFlag,
     166    INTOKEN = 5 | IN_OP_PRECEDENCE(8) | KeywordTokenFlag,
     167    LSHIFT = 0 | BINARY_OP_PRECEDENCE(9),
     168    RSHIFT = 1 | BINARY_OP_PRECEDENCE(9),
     169    URSHIFT = 2 | BINARY_OP_PRECEDENCE(9),
     170    PLUS = 0 | BINARY_OP_PRECEDENCE(10) | UnaryOpTokenFlag,
     171    MINUS = 1 | BINARY_OP_PRECEDENCE(10) | UnaryOpTokenFlag,
     172    TIMES = 0 | BINARY_OP_PRECEDENCE(11),
     173    DIVIDE = 1 | BINARY_OP_PRECEDENCE(11),
     174    MOD = 2 | BINARY_OP_PRECEDENCE(11),
     175    POW = 0 | BINARY_OP_PRECEDENCE(12) | RightAssociativeBinaryOpTokenFlag, // Make sure that POW has the highest operator precedence.
    175176    ERRORTOK = 0 | ErrorTokenFlag,
    176177    UNTERMINATED_IDENTIFIER_ESCAPE_ERRORTOK = 0 | ErrorTokenFlag | UnterminatedErrorTokenFlag,
  • trunk/Source/JavaScriptCore/parser/ResultType.h

    r245906 r247819  
    7777        }
    7878
     79        constexpr bool definitelyIsNull() const
     80        {
     81            return (m_bits & TypeBits) == TypeMaybeNull;
     82        }
     83
     84        constexpr bool mightBeUndefinedOrNull() const
     85        {
     86            return m_bits & (TypeMaybeNull | TypeMaybeOther);
     87        }
     88
    7989        constexpr bool mightBeNumber() const
    8090        {
     
    173183        }
    174184
     185        static constexpr ResultType forCoalesce(ResultType op1, ResultType op2)
     186        {
     187            if (op1.definitelyIsNull())
     188                return op2;
     189            if (!op1.mightBeUndefinedOrNull())
     190                return op1;
     191            return unknownType();
     192        }
     193
    175194        static constexpr ResultType forBitOp()
    176195        {
  • trunk/Source/JavaScriptCore/runtime/Options.h

    r247713 r247819  
    495495    v(bool, useWeakRefs, false, Normal, "Expose the WeakRef constructor.") \
    496496    v(bool, useBigInt, false, Normal, "If true, we will enable BigInt support.") \
     497    v(bool, useNullishCoalescing, false, Normal, "Enable support for the ?? operator.") \
    497498    v(bool, useArrayAllocationProfiling, true, Normal, "If true, we will use our normal array allocation profiling. If false, the allocation profile will always claim to be undecided.") \
    498499    v(bool, forcePolyProto, false, Normal, "If true, create_this will always create an object with a poly proto structure.") \
  • trunk/Tools/ChangeLog

    r247818 r247819  
     12019-07-25  Ross Kirsling  <ross.kirsling@sony.com>
     2
     3        [ESNext] Implement nullish coalescing
     4        https://bugs.webkit.org/show_bug.cgi?id=200072
     5
     6        Reviewed by Darin Adler.
     7
     8        * Scripts/run-jsc-stress-tests:
     9
    1102019-07-24  Fujii Hironori  <Hironori.Fujii@sony.com>
    211
  • trunk/Tools/Scripts/run-jsc-stress-tests

    r247703 r247819  
    700700end
    701701
     702def runNullishCoalescingEnabled(*optionalTestSpecificOptions)
     703    run("nullish-coalescing-enabled", "--useNullishCoalescing=true" , *(FTL_OPTIONS + optionalTestSpecificOptions))
     704end
     705
    702706def runFTLNoCJIT(*optionalTestSpecificOptions)
    703707    run("misc-ftl-no-cjit", *(FTL_OPTIONS + NO_CJIT_OPTIONS + optionalTestSpecificOptions))
Note: See TracChangeset for help on using the changeset viewer.