Reland "[interpreter] Optimize strict equal boolean"

This is a reland of commit 62632c0805.
Reason for previous revert: Performance regressions crbug.com/1315724.
The reland only optimizes strict equal boolean literal like "a===true"
or "a===false", and we generate TestReferenceEqual rather than
TestStrictEqual for the comparasion. And also add typed optimization
for ReferenceEqual when all inputs are boolean with boolean constant.

Original change's description:
> [interpreter] Optimize strict equal boolean
>
> For strict equal boolean literal like "a===true"
> or "a===false", we could generate TestReferenceEqual
> rather than TestStrictEqual. And in `execution_result()->IsTest()`
> case, we could directly emit JumpIfTrue/JumpIfFalse.
>
> E.g.
> ```
> a === true
> ```
> Generated Bytecode From:
> ```
> LdaGlobal
> Star1
> LdaTrue
> TestEqualStrict
> ```
> To:
> ```
> LdaGlobal
> Star1
> LdaTrue
> TestReferenceEqual
> ```
>
> E.g.
> ```
> if (a === true)
> ```
> Generated Bytecode From:
> ```
> LdaGlobal
> Star1
> LdaTrue
> TestEqualStrict
> JumpIfFalse
> ```
> To
> ```
> LdaGlobal
> JumpIfTrue
> Jump
> ```
>
>
> Bug: v8:6403
> Change-Id: Ieaca147acd2d523ac0d2466e7861afb2d29a1310
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3568923
> Reviewed-by: Leszek Swirski <leszeks@chromium.org>
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Commit-Queue: 王澳 <wangao.james@bytedance.com>
> Cr-Commit-Position: refs/heads/main@{#79935}

Bug: v8:6403
Change-Id: I2ae3ab57dce85313af200fa522e3632af5c3a554
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3592039
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Jakob Linke <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80141}
This commit is contained in:
jameslahm 2022-04-24 16:46:49 +08:00 committed by V8 LUCI CQ
parent 2c4d1b4c0a
commit fce1047f00
11 changed files with 669 additions and 0 deletions

View File

@ -103,6 +103,10 @@ bool Expression::IsNullLiteral() const {
return IsLiteral() && AsLiteral()->type() == Literal::kNull;
}
bool Expression::IsBooleanLiteral() const {
return IsLiteral() && AsLiteral()->type() == Literal::kBoolean;
}
bool Expression::IsTheHoleLiteral() const {
return IsLiteral() && AsLiteral()->type() == Literal::kTheHole;
}
@ -892,6 +896,24 @@ static bool IsVoidOfLiteral(Expression* expr) {
maybe_unary->expression()->IsLiteral();
}
static bool MatchLiteralStrictCompareBoolean(Expression* left, Token::Value op,
Expression* right,
Expression** expr,
Literal** literal) {
if (left->IsBooleanLiteral() && op == Token::EQ_STRICT) {
*expr = right;
*literal = left->AsLiteral();
return true;
}
return false;
}
bool CompareOperation::IsLiteralStrictCompareBoolean(Expression** expr,
Literal** literal) {
return MatchLiteralStrictCompareBoolean(left_, op(), right_, expr, literal) ||
MatchLiteralStrictCompareBoolean(right_, op(), left_, expr, literal);
}
// Check for the pattern: void <literal> equals <expression> or
// undefined equals <expression>
static bool MatchLiteralCompareUndefined(Expression* left, Token::Value op,

View File

@ -236,6 +236,8 @@ class Expression : public AstNode {
// True iff the expression is the null literal.
bool IsNullLiteral() const;
bool IsBooleanLiteral() const;
// True iff the expression is the hole literal.
bool IsTheHoleLiteral() const;
@ -955,6 +957,11 @@ class Literal final : public Expression {
return Smi::FromInt(smi_);
}
bool AsBooleanLiteral() const {
DCHECK_EQ(kBoolean, type());
return boolean_;
}
// Returns true if literal represents a Number.
bool IsNumber() const { return type() == kHeapNumber || type() == kSmi; }
double AsNumber() const {
@ -1963,6 +1970,7 @@ class CompareOperation final : public Expression {
// Match special cases.
bool IsLiteralCompareTypeof(Expression** expr, Literal** literal);
bool IsLiteralStrictCompareBoolean(Expression** expr, Literal** literal);
bool IsLiteralCompareUndefined(Expression** expr);
bool IsLiteralCompareNull(Expression** expr);

View File

@ -410,6 +410,20 @@ Reduction TypedOptimization::ReduceReferenceEqual(Node* node) {
return Replace(jsgraph()->FalseConstant());
}
}
if (rhs_type.Is(Type::Boolean()) && rhs_type.IsHeapConstant() &&
lhs_type.Is(Type::Boolean())) {
base::Optional<bool> maybe_result =
rhs_type.AsHeapConstant()->Ref().TryGetBooleanValue();
if (maybe_result.has_value()) {
if (maybe_result.value()) {
return Replace(node->InputAt(0));
} else {
node->TrimInputCount(1);
NodeProperties::ChangeOp(node, simplified()->BooleanNot());
return Changed(node);
}
}
}
return NoChange();
}

View File

@ -6162,6 +6162,14 @@ void BytecodeGenerator::BuildLiteralCompareNil(
}
}
void BytecodeGenerator::BuildLiteralStrictCompareBoolean(Literal* literal) {
DCHECK(literal->IsBooleanLiteral());
Register result = register_allocator()->NewRegister();
builder()->StoreAccumulatorInRegister(result);
builder()->LoadBoolean(literal->AsBooleanLiteral());
builder()->CompareReference(result);
}
void BytecodeGenerator::VisitCompareOperation(CompareOperation* expr) {
Expression* sub_expr;
Literal* literal;
@ -6177,6 +6185,11 @@ void BytecodeGenerator::VisitCompareOperation(CompareOperation* expr) {
} else {
builder()->CompareTypeOf(literal_flag);
}
} else if (expr->IsLiteralStrictCompareBoolean(&sub_expr, &literal)) {
DCHECK(expr->op() == Token::EQ_STRICT);
VisitForAccumulatorValue(sub_expr);
builder()->SetExpressionPosition(expr);
BuildLiteralStrictCompareBoolean(literal);
} else if (expr->IsLiteralCompareUndefined(&sub_expr)) {
VisitForAccumulatorValue(sub_expr);
builder()->SetExpressionPosition(expr);

View File

@ -263,6 +263,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
LookupHoistingMode lookup_hoisting_mode = LookupHoistingMode::kNormal);
void BuildLiteralCompareNil(Token::Value compare_op,
BytecodeArrayBuilder::NilValue nil);
void BuildLiteralStrictCompareBoolean(Literal* literal);
void BuildReturn(int source_position);
void BuildAsyncReturn(int source_position);
void BuildAsyncGeneratorReturn();

View File

@ -0,0 +1,400 @@
#
# Autogenerated by generate-bytecode-expectations.
#
---
wrap: yes
---
snippet: "
var a = 1;
return a === true;
"
frame size: 1
parameter count: 1
bytecode array length: 7
bytecodes: [
/* 42 S> */ B(LdaSmi), I8(1),
B(Star0),
/* 45 S> */ B(LdaTrue),
B(TestReferenceEqual), R(0),
/* 63 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = true;
return true === a;
"
frame size: 1
parameter count: 1
bytecode array length: 6
bytecodes: [
/* 42 S> */ B(LdaTrue),
B(Star0),
/* 48 S> */ B(LdaTrue),
B(TestReferenceEqual), R(0),
/* 66 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = false;
return true !== a;
"
frame size: 1
parameter count: 1
bytecode array length: 7
bytecodes: [
/* 42 S> */ B(LdaFalse),
B(Star0),
/* 49 S> */ B(LdaTrue),
B(TestReferenceEqual), R(0),
/* 61 E> */ B(LogicalNot),
/* 67 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = 1;
return true === a ? 1 : 2;
"
frame size: 1
parameter count: 1
bytecode array length: 15
bytecodes: [
/* 42 S> */ B(LdaSmi), I8(1),
B(Star0),
/* 45 S> */ B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfFalse), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
/* 71 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = true;
return false === a ? 1 : 2;
"
frame size: 1
parameter count: 1
bytecode array length: 14
bytecodes: [
/* 42 S> */ B(LdaTrue),
B(Star0),
/* 48 S> */ B(LdaFalse),
B(TestReferenceEqual), R(0),
B(JumpIfFalse), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
/* 75 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = 1;
return true !== a ? 1 : 2;
"
frame size: 1
parameter count: 1
bytecode array length: 15
bytecodes: [
/* 42 S> */ B(LdaSmi), I8(1),
B(Star0),
/* 45 S> */ B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfTrue), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
/* 71 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = false;
return false !== null ? 1 : 2;
"
frame size: 2
parameter count: 1
bytecode array length: 16
bytecodes: [
/* 42 S> */ B(LdaFalse),
B(Star0),
/* 49 S> */ B(LdaNull),
B(Star1),
B(LdaFalse),
B(TestReferenceEqual), R(1),
B(JumpIfTrue), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
/* 79 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = 0;
if (a !== true) {
return 1;
}
"
frame size: 1
parameter count: 1
bytecode array length: 12
bytecodes: [
/* 42 S> */ B(LdaZero),
B(Star0),
/* 45 S> */ B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfTrue), U8(5),
/* 65 S> */ B(LdaSmi), I8(1),
/* 74 S> */ B(Return),
B(LdaUndefined),
/* 77 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
var a = true;
var b = 0;
while (a !== true) {
b++;
}
"
frame size: 2
parameter count: 1
bytecode array length: 20
bytecodes: [
/* 42 S> */ B(LdaTrue),
B(Star0),
/* 56 S> */ B(LdaZero),
B(Star1),
/* 68 S> */ B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfTrue), U8(11),
/* 82 S> */ B(Ldar), R(1),
B(Inc), U8(0),
B(Star1),
/* 59 E> */ B(JumpLoop), U8(10), I8(0), U8(1),
B(LdaUndefined),
/* 89 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
(0 === true) ? 1 : 2;
"
frame size: 1
parameter count: 1
bytecode array length: 15
bytecodes: [
/* 34 S> */ B(LdaZero),
B(Star0),
B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfFalse), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
B(LdaUndefined),
/* 56 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
(0 !== true) ? 1 : 2;
"
frame size: 1
parameter count: 1
bytecode array length: 15
bytecodes: [
/* 34 S> */ B(LdaZero),
B(Star0),
B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfTrue), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
B(LdaUndefined),
/* 56 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
(false === 0) ? 1 : 2;
"
frame size: 1
parameter count: 1
bytecode array length: 15
bytecodes: [
/* 34 S> */ B(LdaZero),
B(Star0),
B(LdaFalse),
B(TestReferenceEqual), R(0),
B(JumpIfFalse), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
B(LdaUndefined),
/* 57 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
(0 === true || 0 === false) ? 1 : 2;
"
frame size: 1
parameter count: 1
bytecode array length: 22
bytecodes: [
/* 34 S> */ B(LdaZero),
B(Star0),
B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfTrue), U8(9),
B(LdaZero),
B(Star0),
B(LdaFalse),
B(TestReferenceEqual), R(0),
B(JumpIfFalse), U8(6),
B(LdaSmi), I8(1),
B(Jump), U8(4),
B(LdaSmi), I8(2),
B(LdaUndefined),
/* 71 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
if (0 === true || 0 === false) return 1;
"
frame size: 1
parameter count: 1
bytecode array length: 19
bytecodes: [
/* 34 S> */ B(LdaZero),
B(Star0),
B(LdaTrue),
B(TestReferenceEqual), R(0),
B(JumpIfTrue), U8(9),
B(LdaZero),
B(Star0),
B(LdaFalse),
B(TestReferenceEqual), R(0),
B(JumpIfFalse), U8(5),
/* 65 S> */ B(LdaSmi), I8(1),
/* 74 S> */ B(Return),
B(LdaUndefined),
/* 75 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
if (!('false' === false)) return 1;
"
frame size: 1
parameter count: 1
bytecode array length: 13
bytecodes: [
/* 34 S> */ B(LdaConstant), U8(0),
B(Star0),
B(LdaFalse),
B(TestReferenceEqual), R(0),
B(JumpIfTrue), U8(5),
/* 60 S> */ B(LdaSmi), I8(1),
/* 69 S> */ B(Return),
B(LdaUndefined),
/* 70 S> */ B(Return),
]
constant pool: [
ONE_BYTE_INTERNALIZED_STRING_TYPE ["false"],
]
handlers: [
]
---
snippet: "
if (!('false' !== false)) return 1;
"
frame size: 1
parameter count: 1
bytecode array length: 13
bytecodes: [
/* 34 S> */ B(LdaConstant), U8(0),
B(Star0),
B(LdaFalse),
B(TestReferenceEqual), R(0),
B(JumpIfFalse), U8(5),
/* 60 S> */ B(LdaSmi), I8(1),
/* 69 S> */ B(Return),
B(LdaUndefined),
/* 70 S> */ B(Return),
]
constant pool: [
ONE_BYTE_INTERNALIZED_STRING_TYPE ["false"],
]
handlers: [
]

View File

@ -1161,6 +1161,62 @@ TEST(CompareTypeOf) {
LoadGolden("CompareTypeOf.golden")));
}
TEST(CompareBoolean) {
InitializedIgnitionHandleScope scope;
BytecodeExpectationsPrinter printer(CcTest::isolate());
std::string snippets[] = {
"var a = 1;\n"
"return a === true;\n",
"var a = true;\n"
"return true === a;\n",
"var a = false;\n"
"return true !== a;\n",
"var a = 1;\n"
"return true === a ? 1 : 2;\n",
"var a = true;\n"
"return false === a ? 1 : 2;\n",
"var a = 1;\n"
"return true !== a ? 1 : 2;\n",
"var a = false;\n"
"return false !== null ? 1 : 2;\n",
"var a = 0;\n"
"if (a !== true) {\n"
" return 1;\n"
"}\n",
"var a = true;\n"
"var b = 0;\n"
"while (a !== true) {\n"
" b++;\n"
"}\n",
"(0 === true) ? 1 : 2;\n",
"(0 !== true) ? 1 : 2;\n",
"(false === 0) ? 1 : 2;\n",
"(0 === true || 0 === false) ? 1 : 2;\n",
"if (0 === true || 0 === false) return 1;\n",
"if (!('false' === false)) return 1;\n",
"if (!('false' !== false)) return 1;\n",
};
CHECK(CompareTexts(BuildActual(printer, snippets),
LoadGolden("CompareBoolean.golden")));
}
TEST(CompareNil) {
InitializedIgnitionHandleScope scope;
BytecodeExpectationsPrinter printer(CcTest::isolate());

View File

@ -16,6 +16,7 @@ addBenchmark('Number-StrictEquals-False', NumberStrictEqualsFalse);
addBenchmark('String-StrictEquals-True', StringStrictEqualsTrue);
addBenchmark('String-StrictEquals-False', StringStrictEqualsFalse);
addBenchmark('SmiString-StrictEquals', MixedStrictEquals);
addBenchmark('Boolean-StrictEquals', BooleanStrictEquals);
addBenchmark('Smi-Equals-True', SmiEqualsTrue);
addBenchmark('Smi-Equals-False', SmiEqualsFalse);
addBenchmark('Number-Equals-True', NumberEqualsTrue);
@ -46,6 +47,113 @@ function strictEquals(a, b) {
}
}
function strictEqualsBoolean(a) {
var ret;
for (var i = 0; i < 1000; ++i) {
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === true) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
if (a === false) ret = true;
}
return ret;
}
function equals(a, b) {
for (var i = 0; i < 1000; ++i) {
a == b; a == b; a == b; a == b; a == b; a == b; a == b; a == b; a == b; a == b;
@ -104,6 +212,12 @@ function StringStrictEqualsTrue() {
strictEquals("abc", "abc");
}
function BooleanStrictEquals() {
strictEqualsBoolean("a");
strictEqualsBoolean(true);
strictEqualsBoolean(false);
}
function MixedStrictEquals() {
strictEquals(10, "10");
}

View File

@ -318,6 +318,7 @@
{"name": "String-StrictEquals-True"},
{"name": "String-StrictEquals-False"},
{"name": "SmiString-StrictEquals"},
{"name": "Boolean-StrictEquals"},
{"name": "Smi-Equals-True"},
{"name": "Smi-Equals-False"},
{"name": "Number-Equals-True"},

View File

@ -0,0 +1,20 @@
// Copyright 2022 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: --allow-natives-syntax
// Ensure we could reduce reference equal with boolean input.
// When the inputs are all boolean with true constant, we could
// reduce to the input. And when the inputs are all boolean with
// false constant, we could reduce to the input with BooleanNot.
function foo(x, y) {
const v = (x === y);
%TurbofanStaticAssert(((v === true) === v));
%TurbofanStaticAssert((!(v === false) === v));
};
%PrepareFunctionForOptimization(foo);
foo(1, 2);
foo(2, 3);
%OptimizeFunctionOnNextCall(foo);
foo(3, 4);

View File

@ -103,6 +103,26 @@ TEST_F(TypedOptimizationTest, ToBooleanWithAny) {
ASSERT_FALSE(r.Changed());
}
// -----------------------------------------------------------------------------
// ReferenceEqual
TEST_F(TypedOptimizationTest, ReferenceEqualWithBooleanTrueConstant) {
Node* left = Parameter(Type::Boolean(), 0);
Node* right = TrueConstant();
Reduction r =
Reduce(graph()->NewNode(simplified()->ReferenceEqual(), left, right));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), left);
}
TEST_F(TypedOptimizationTest, ReferenceEqualWithBooleanFalseConstant) {
Node* left = Parameter(Type::Boolean(), 0);
Node* right = FalseConstant();
Reduction r =
Reduce(graph()->NewNode(simplified()->ReferenceEqual(), left, right));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsBooleanNot(left));
}
} // namespace typed_optimization_unittest
} // namespace compiler
} // namespace internal