[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}
This commit is contained in:
jameslahm 2022-04-06 09:43:43 +08:00 committed by V8 LUCI CQ
parent 4c29cf1b78
commit 62632c0805
10 changed files with 608 additions and 10 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

@ -3977,19 +3977,24 @@ void BytecodeGraphBuilder::BuildJumpIfNotEqual(Node* comperand) {
}
void BytecodeGraphBuilder::BuildJumpIfFalse() {
NewBranch(environment()->LookupAccumulator(), BranchHint::kNone);
Node* accumulator = environment()->LookupAccumulator();
Node* condition = NewNode(simplified()->ReferenceEqual(), accumulator,
jsgraph()->FalseConstant());
NewBranch(condition, BranchHint::kNone);
{
SubEnvironment sub_environment(this);
NewIfFalse();
NewIfTrue();
environment()->BindAccumulator(jsgraph()->FalseConstant());
MergeIntoSuccessorEnvironment(bytecode_iterator().GetJumpTargetOffset());
}
NewIfTrue();
environment()->BindAccumulator(jsgraph()->TrueConstant());
NewIfFalse();
}
void BytecodeGraphBuilder::BuildJumpIfTrue() {
NewBranch(environment()->LookupAccumulator(), BranchHint::kNone);
Node* accumulator = environment()->LookupAccumulator();
Node* condition = NewNode(simplified()->ReferenceEqual(), accumulator,
jsgraph()->TrueConstant());
NewBranch(condition, BranchHint::kNone);
{
SubEnvironment sub_environment(this);
NewIfTrue();
@ -3997,7 +4002,6 @@ void BytecodeGraphBuilder::BuildJumpIfTrue() {
MergeIntoSuccessorEnvironment(bytecode_iterator().GetJumpTargetOffset());
}
NewIfFalse();
environment()->BindAccumulator(jsgraph()->FalseConstant());
}
void BytecodeGraphBuilder::BuildJumpIfToBooleanTrue() {

View File

@ -6157,6 +6157,29 @@ void BytecodeGenerator::BuildLiteralCompareNil(
}
}
void BytecodeGenerator::BuildLiteralStrictCompareBoolean(Literal* literal) {
DCHECK(literal->IsBooleanLiteral());
if (execution_result()->IsTest()) {
TestResultScope* test_result = execution_result()->AsTest();
if (literal->AsBooleanLiteral()) {
builder()->JumpIfTrue(ToBooleanMode::kAlreadyBoolean,
test_result->NewThenLabel());
} else {
builder()->JumpIfFalse(ToBooleanMode::kAlreadyBoolean,
test_result->NewThenLabel());
}
if (test_result->fallthrough() != TestFallthrough::kElse) {
builder()->Jump(test_result->NewElseLabel());
}
test_result->SetResultConsumedByTest();
} else {
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;
@ -6172,6 +6195,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

@ -1915,7 +1915,6 @@ IGNITION_HANDLER(JumpConstant, InterpreterAssembler) {
// will misbehave if passed arbitrary input values.
IGNITION_HANDLER(JumpIfTrue, InterpreterAssembler) {
TNode<Object> accumulator = GetAccumulator();
CSA_DCHECK(this, IsBoolean(CAST(accumulator)));
JumpIfTaggedEqual(accumulator, TrueConstant(), 0);
}
@ -1926,7 +1925,6 @@ IGNITION_HANDLER(JumpIfTrue, InterpreterAssembler) {
// and will misbehave if passed arbitrary input values.
IGNITION_HANDLER(JumpIfTrueConstant, InterpreterAssembler) {
TNode<Object> accumulator = GetAccumulator();
CSA_DCHECK(this, IsBoolean(CAST(accumulator)));
JumpIfTaggedEqualConstant(accumulator, TrueConstant(), 0);
}
@ -1937,7 +1935,6 @@ IGNITION_HANDLER(JumpIfTrueConstant, InterpreterAssembler) {
// will misbehave if passed arbitrary input values.
IGNITION_HANDLER(JumpIfFalse, InterpreterAssembler) {
TNode<Object> accumulator = GetAccumulator();
CSA_DCHECK(this, IsBoolean(CAST(accumulator)));
JumpIfTaggedEqual(accumulator, FalseConstant(), 0);
}
@ -1948,7 +1945,6 @@ IGNITION_HANDLER(JumpIfFalse, InterpreterAssembler) {
// and will misbehave if passed arbitrary input values.
IGNITION_HANDLER(JumpIfFalseConstant, InterpreterAssembler) {
TNode<Object> accumulator = GetAccumulator();
CSA_DCHECK(this, IsBoolean(CAST(accumulator)));
JumpIfTaggedEqualConstant(accumulator, FalseConstant(), 0);
}

View File

@ -0,0 +1,368 @@
#
# 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: 14
bytecodes: [
/* 42 S> */ B(LdaSmi), I8(1),
B(Star0),
/* 45 S> */ B(JumpIfTrue), U8(4),
B(Jump), 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: 13
bytecodes: [
/* 42 S> */ B(LdaTrue),
B(Star0),
/* 48 S> */ B(JumpIfFalse), U8(4),
B(Jump), 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: 12
bytecodes: [
/* 42 S> */ B(LdaSmi), I8(1),
B(Star0),
/* 45 S> */ 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: 1
parameter count: 1
bytecode array length: 12
bytecodes: [
/* 42 S> */ B(LdaFalse),
B(Star0),
/* 49 S> */ B(LdaNull),
B(JumpIfFalse), 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: 9
bytecodes: [
/* 42 S> */ B(LdaZero),
B(Star0),
/* 45 S> */ 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: 18
bytecodes: [
/* 42 S> */ B(LdaTrue),
B(Star0),
/* 56 S> */ B(LdaZero),
B(Star1),
/* 68 S> */ B(Ldar), R(0),
B(JumpIfTrue), U8(10),
/* 82 S> */ B(Ldar), R(1),
B(Inc), U8(0),
B(Star1),
/* 59 E> */ B(JumpLoop), U8(9), I8(0),
B(LdaUndefined),
/* 89 S> */ B(Return),
]
constant pool: [
]
handlers: [
]
---
snippet: "
(0 === true) ? 1 : 2;
"
frame size: 0
parameter count: 1
bytecode array length: 13
bytecodes: [
/* 34 S> */ B(LdaZero),
B(JumpIfTrue), U8(4),
B(Jump), 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: 0
parameter count: 1
bytecode array length: 11
bytecodes: [
/* 34 S> */ B(LdaZero),
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: 0
parameter count: 1
bytecode array length: 13
bytecodes: [
/* 34 S> */ B(LdaZero),
B(JumpIfFalse), U8(4),
B(Jump), 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: 0
parameter count: 1
bytecode array length: 16
bytecodes: [
/* 34 S> */ B(LdaZero),
B(JumpIfTrue), U8(7),
B(LdaZero),
B(JumpIfFalse), U8(4),
B(Jump), 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: 0
parameter count: 1
bytecode array length: 13
bytecodes: [
/* 34 S> */ B(LdaZero),
B(JumpIfTrue), U8(7),
B(LdaZero),
B(JumpIfFalse), U8(4),
B(Jump), 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: 0
parameter count: 1
bytecode array length: 9
bytecodes: [
/* 34 S> */ B(LdaConstant), U8(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: [
]
---
snippet: "
if (!('false' !== false)) return 1;
"
frame size: 0
parameter count: 1
bytecode array length: 11
bytecodes: [
/* 34 S> */ B(LdaConstant), U8(0),
B(JumpIfFalse), U8(4),
B(Jump), 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"},