From b151d8db22be308738192497a68c2c7c0d8d4070 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 22 Apr 2020 11:57:57 -0500 Subject: [PATCH] Implement logical assignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://tc39.es/proposal-logical-assignment/ Bug: v8:10372 Change-Id: I538d54af6b4b24d450d1398c74f76dd57fdb0147 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2158119 Reviewed-by: Shu-yu Guo Reviewed-by: Marja Hölttä Reviewed-by: Mythri Alle Commit-Queue: Marja Hölttä Cr-Commit-Position: refs/heads/master@{#67330} --- src/flags/flag-definitions.h | 3 +- src/init/bootstrapper.cc | 1 + src/interpreter/bytecode-generator.cc | 20 ++++++++-- src/parsing/parse-info.cc | 1 + src/parsing/parse-info.h | 3 +- src/parsing/parser-base.h | 5 +++ src/parsing/scanner-inl.h | 12 +++--- src/parsing/token.h | 10 ++--- test/cctest/test-parsing.cc | 3 -- test/mjsunit/harmony/logical-assignment.js | 42 +++++++++++++++++++++ test/test262/test262.status | 43 ++-------------------- test/test262/testcfg.py | 1 + 12 files changed, 86 insertions(+), 58 deletions(-) create mode 100644 test/mjsunit/harmony/logical-assignment.js diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h index a36900c68e..2c7106ed62 100644 --- a/src/flags/flag-definitions.h +++ b/src/flags/flag-definitions.h @@ -220,7 +220,8 @@ DEFINE_IMPLICATION(harmony_weak_refs_with_cleanup_some, harmony_weak_refs) V(harmony_weak_refs_with_cleanup_some, \ "harmony weak references with FinalizationRegistry.prototype.cleanupSome") \ V(harmony_regexp_match_indices, "harmony regexp match indices") \ - V(harmony_top_level_await, "harmony top level await") + V(harmony_top_level_await, "harmony top level await") \ + V(harmony_logical_assignment, "harmony logical assignment") #ifdef V8_INTL_SUPPORT #define HARMONY_INPROGRESS(V) \ diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc index 77b7023e94..6202a412e8 100644 --- a/src/init/bootstrapper.cc +++ b/src/init/bootstrapper.cc @@ -4246,6 +4246,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_dynamic_import) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_import_meta) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_sequence) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_top_level_await) +EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_logical_assignment) #ifdef V8_INTL_SUPPORT EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_add_calendar_numbering_system) diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc index ab111f656d..92662932fa 100644 --- a/src/interpreter/bytecode-generator.cc +++ b/src/interpreter/bytecode-generator.cc @@ -4142,9 +4142,23 @@ void BytecodeGenerator::VisitCompoundAssignment(CompoundAssignment* expr) { } } - BinaryOperation* binop = expr->AsCompoundAssignment()->binary_operation(); + BinaryOperation* binop = expr->binary_operation(); FeedbackSlot slot = feedback_spec()->AddBinaryOpICSlot(); - if (expr->value()->IsSmiLiteral()) { + BytecodeLabel short_circuit; + if (binop->op() == Token::NULLISH) { + BytecodeLabel nullish; + builder() + ->JumpIfUndefinedOrNull(&nullish) + .Jump(&short_circuit) + .Bind(&nullish); + VisitForAccumulatorValue(expr->value()); + } else if (binop->op() == Token::OR) { + builder()->JumpIfTrue(ToBooleanMode::kConvertToBoolean, &short_circuit); + VisitForAccumulatorValue(expr->value()); + } else if (binop->op() == Token::AND) { + builder()->JumpIfFalse(ToBooleanMode::kConvertToBoolean, &short_circuit); + VisitForAccumulatorValue(expr->value()); + } else if (expr->value()->IsSmiLiteral()) { builder()->BinaryOperationSmiLiteral( binop->op(), expr->value()->AsLiteral()->AsSmiLiteral(), feedback_index(slot)); @@ -4154,9 +4168,9 @@ void BytecodeGenerator::VisitCompoundAssignment(CompoundAssignment* expr) { VisitForAccumulatorValue(expr->value()); builder()->BinaryOperation(binop->op(), old_value, feedback_index(slot)); } - builder()->SetExpressionPosition(expr); BuildAssignment(lhs_data, expr->op(), expr->lookup_hoisting_mode()); + builder()->Bind(&short_circuit); } // Suspends the generator to resume at the next suspend_id, with output stored diff --git a/src/parsing/parse-info.cc b/src/parsing/parse-info.cc index 5d78167a7c..cd1ab7e003 100644 --- a/src/parsing/parse-info.cc +++ b/src/parsing/parse-info.cc @@ -38,6 +38,7 @@ UnoptimizedCompileFlags::UnoptimizedCompileFlags(Isolate* isolate, set_collect_source_positions(!FLAG_enable_lazy_source_positions || isolate->NeedsDetailedOptimizedCodeLineInfo()); set_allow_harmony_top_level_await(FLAG_harmony_top_level_await); + set_allow_harmony_logical_assignment(FLAG_harmony_logical_assignment); } // static diff --git a/src/parsing/parse-info.h b/src/parsing/parse-info.h index 2ae93a6e27..1e130a9ba6 100644 --- a/src/parsing/parse-info.h +++ b/src/parsing/parse-info.h @@ -66,7 +66,8 @@ class Zone; V(is_oneshot_iife, bool, 1, _) \ V(collect_source_positions, bool, 1, _) \ V(allow_harmony_top_level_await, bool, 1, _) \ - V(is_repl_mode, bool, 1, _) + V(is_repl_mode, bool, 1, _) \ + V(allow_harmony_logical_assignment, bool, 1, _) class V8_EXPORT_PRIVATE UnoptimizedCompileFlags { public: diff --git a/src/parsing/parser-base.h b/src/parsing/parser-base.h index fbc969f678..9a707dad92 100644 --- a/src/parsing/parser-base.h +++ b/src/parsing/parser-base.h @@ -2750,6 +2750,11 @@ ParserBase::ParseAssignmentExpressionCoverGrammar() { Token::Value op = peek(); if (!Token::IsArrowOrAssignmentOp(op)) return expression; + if ((op == Token::ASSIGN_NULLISH || op == Token::ASSIGN_OR || + op == Token::ASSIGN_AND) && + !flags().allow_harmony_logical_assignment()) { + return expression; + } // Arrow functions. if (V8_UNLIKELY(op == Token::ARROW)) { diff --git a/src/parsing/scanner-inl.h b/src/parsing/scanner-inl.h index 0593b17987..bd4d0284d8 100644 --- a/src/parsing/scanner-inl.h +++ b/src/parsing/scanner-inl.h @@ -364,14 +364,14 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() { return Select(token); case Token::CONDITIONAL: - // ? ?. ?? + // ? ?. ?? ??= Advance(); if (c0_ == '.') { Advance(); if (!IsDecimalDigit(c0_)) return Token::QUESTION_PERIOD; PushBack('.'); } else if (c0_ == '?') { - return Select(Token::NULLISH); + return Select('=', Token::ASSIGN_NULLISH, Token::NULLISH); } return Token::CONDITIONAL; @@ -471,16 +471,16 @@ V8_INLINE Token::Value Scanner::ScanSingleToken() { return Token::DIV; case Token::BIT_AND: - // & && &= + // & && &= &&= Advance(); - if (c0_ == '&') return Select(Token::AND); + if (c0_ == '&') return Select('=', Token::ASSIGN_AND, Token::AND); if (c0_ == '=') return Select(Token::ASSIGN_BIT_AND); return Token::BIT_AND; case Token::BIT_OR: - // | || |= + // | || |= ||= Advance(); - if (c0_ == '|') return Select(Token::OR); + if (c0_ == '|') return Select('=', Token::ASSIGN_OR, Token::OR); if (c0_ == '=') return Select(Token::ASSIGN_BIT_OR); return Token::BIT_OR; diff --git a/src/parsing/token.h b/src/parsing/token.h index 4749945ebd..ef92238de2 100644 --- a/src/parsing/token.h +++ b/src/parsing/token.h @@ -31,6 +31,9 @@ namespace internal { /* Binary operators */ /* ADD and SUB are at the end since they are UnaryOp */ #define BINARY_OP_TOKEN_LIST(T, E) \ + E(T, NULLISH, "??", 3) \ + E(T, OR, "||", 4) \ + E(T, AND, "&&", 5) \ E(T, BIT_OR, "|", 6) \ E(T, BIT_XOR, "^", 7) \ E(T, BIT_AND, "&", 8) \ @@ -97,9 +100,6 @@ namespace internal { /* IsBinaryOp() relies on this block of enum values */ \ /* being contiguous and sorted in the same order! */ \ T(COMMA, ",", 1) \ - T(NULLISH, "??", 3) \ - T(OR, "||", 4) \ - T(AND, "&&", 5) \ \ /* Unary operators, starting at ADD in BINARY_OP_TOKEN_LIST */ \ /* IsUnaryOp() relies on this block of enum values */ \ @@ -297,8 +297,8 @@ class V8_EXPORT_PRIVATE Token { } static Value BinaryOpForAssignment(Value op) { - DCHECK(base::IsInRange(op, ASSIGN_BIT_OR, ASSIGN_SUB)); - Value result = static_cast(op - ASSIGN_BIT_OR + BIT_OR); + DCHECK(base::IsInRange(op, ASSIGN_NULLISH, ASSIGN_SUB)); + Value result = static_cast(op - ASSIGN_NULLISH + NULLISH); DCHECK(IsBinaryOp(result)); return result; } diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index f082156993..bb6d020488 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -261,9 +261,6 @@ TEST(ArrowOrAssignmentOp) { bool TokenIsBinaryOp(Token::Value token) { switch (token) { case Token::COMMA: - case Token::NULLISH: - case Token::OR: - case Token::AND: #define T(name, string, precedence) case Token::name: BINARY_OP_TOKEN_LIST(T, EXPAND_BINOP_TOKEN) #undef T diff --git a/test/mjsunit/harmony/logical-assignment.js b/test/mjsunit/harmony/logical-assignment.js new file mode 100644 index 0000000000..92153547c2 --- /dev/null +++ b/test/mjsunit/harmony/logical-assignment.js @@ -0,0 +1,42 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-logical-assignment + +{ + let x = null; + let y = 0; + x ??= y ||= 5; + + assertEquals(x, 5); + assertEquals(y, 5); +} + + +{ + let x = null; + let y = 4; + x ??= y ||= 5; + + assertEquals(x, 4); + assertEquals(y, 4); +} + +{ + let x = 1; + let y = 0; + x &&= y ||= 5; + + assertEquals(x, 5); + assertEquals(y, 5); +} + +{ + let x = 0; + let y = 0; + x &&= y ||= 5; + + assertEquals(x, 0); + assertEquals(y, 0); +} diff --git a/test/test262/test262.status b/test/test262/test262.status index 9c3ae02f2e..50420e1610 100644 --- a/test/test262/test262.status +++ b/test/test262/test262.status @@ -659,44 +659,6 @@ 'built-ins/AsyncFromSyncIteratorPrototype/return/absent-value-not-passed': [FAIL], 'built-ins/AsyncFromSyncIteratorPrototype/throw/absent-value-not-passed': [FAIL], - # https://bugs.chromium.org/p/v8/issues/detail?id=10394 - 'language/expressions/logical-assignment/lgcl-and-assignment-operator': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-bigint': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-lhs-before-rhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-no-set': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-no-set-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-non-extensible': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-non-writeable': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-non-writeable-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-unresolved-lhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-unresolved-rhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-assignment-operator-unresolved-rhs-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-and-whitespace': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-bigint': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-lhs-before-rhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-no-set': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-no-set-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-non-extensible': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-non-writeable': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-non-writeable-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-unresolved-lhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-unresolved-rhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-unresolved-rhs-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-nullish-whitespace': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-bigint': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-lhs-before-rhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-no-set': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-no-set-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-non-extensible': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-non-writeable': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-non-writeable-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-unresolved-lhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-unresolved-rhs': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-assignment-operator-unresolved-rhs-put': [FAIL], - 'language/expressions/logical-assignment/lgcl-or-whitespace': [FAIL], - # https://bugs.chromium.org/p/v8/issues/detail?id=10397 'language/statements/for-await-of/iterator-close-throw-get-method-abrupt': [FAIL], 'language/statements/for-of/iterator-close-throw-get-method-abrupt': [FAIL], @@ -716,7 +678,10 @@ # https://github.com/tc39/ecma262/pull/889 'annexB/language/function-code/block-decl-func-skip-arguments': [FAIL], - # https://bugs.chromium.org/p/v8/issues/detail?id=6538 + # Non-simple assignment targets are runtime errors instead of syntax errors for web compat. + 'language/expressions/logical-assignment/lgcl-or-assignment-operator-non-simple-lhs': [FAIL], + 'language/expressions/logical-assignment/lgcl-and-assignment-operator-non-simple-lhs': [FAIL], + 'language/expressions/logical-assignment/lgcl-nullish-assignment-operator-non-simple-lhs': [FAIL], ############################ INVALID TESTS ############################# diff --git a/test/test262/testcfg.py b/test/test262/testcfg.py index 2b9d0f4f05..2e32ff981b 100644 --- a/test/test262/testcfg.py +++ b/test/test262/testcfg.py @@ -63,6 +63,7 @@ FEATURE_FLAGS = { 'class-methods-private': '--harmony-private-methods', 'class-static-methods-private': '--harmony-private-methods', 'AggregateError': '--harmony-promise-any', + 'logical-assignment-operators': '--harmony-logical-assignment', } SKIPPED_FEATURES = set([])