[Interpreter] Optimize equality check with null/undefined with a check on the map.

Equality with null/undefined is equivalent to a check on the undetectable bit
on the map of the object. This would be more efficient than performing the entire
comparison operation.

This cl introduces:
1. A new bytecode called TestUndetectable that checks if the object is null/undefined.
2. Updates peeophole optimizer to emit TestUndetectable when a LdaNull/Undefined
precedes equality check.
4. TestUndetectable is transformed to ObjectIsUndetectable operator when building
turbofan graph.

BUG=v8:4280

Review-Url: https://codereview.chromium.org/2547043002
Cr-Commit-Position: refs/heads/master@{#41514}
This commit is contained in:
mythria 2016-12-06 03:32:09 -08:00 committed by Commit bot
parent d50108adc8
commit 9119d16904
15 changed files with 298 additions and 40 deletions

View File

@ -10,6 +10,7 @@
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/linkage.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/interpreter/bytecodes.h"
#include "src/objects-inl.h"
@ -1643,6 +1644,13 @@ void BytecodeGraphBuilder::VisitTestInstanceOf() {
BuildCompareOp(javascript()->InstanceOf());
}
void BytecodeGraphBuilder::VisitTestUndetectable() {
Node* object =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0));
Node* node = NewNode(jsgraph()->simplified()->ObjectIsUndetectable(), object);
environment()->BindAccumulator(node);
}
void BytecodeGraphBuilder::BuildCastOperator(const Operator* js_op) {
PrepareEagerCheckpoint();
Node* value = NewNode(js_op, environment()->LookupAccumulator());

View File

@ -588,6 +588,7 @@ BytecodeGenerator::BytecodeGenerator(CompilationInfo* info)
const AstRawString* prototype_string = ast_value_factory->prototype_string();
ast_value_factory->Internalize(info->isolate());
prototype_string_ = prototype_string->string();
undefined_string_ = ast_value_factory->undefined_string();
}
Handle<BytecodeArray> BytecodeGenerator::FinalizeBytecode(Isolate* isolate) {
@ -1831,8 +1832,15 @@ void BytecodeGenerator::BuildVariableLoad(Variable* variable,
break;
}
case VariableLocation::UNALLOCATED: {
builder()->LoadGlobal(variable->name(), feedback_index(slot),
typeof_mode);
// The global identifier "undefined" is immutable. Everything
// else could be reassigned. For performance, we do a pointer comparison
// rather than checking if the raw_name is really "undefined".
if (variable->raw_name() == undefined_string()) {
builder()->LoadUndefined();
} else {
builder()->LoadGlobal(variable->name(), feedback_index(slot),
typeof_mode);
}
break;
}
case VariableLocation::CONTEXT: {

View File

@ -195,6 +195,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
Handle<Name> home_object_symbol() const { return home_object_symbol_; }
Handle<Name> prototype_string() const { return prototype_string_; }
Handle<FixedArray> empty_fixed_array() const { return empty_fixed_array_; }
const AstRawString* undefined_string() const { return undefined_string_; }
Zone* zone_;
BytecodeArrayBuilder* builder_;
@ -218,6 +219,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
Handle<Name> home_object_symbol_;
Handle<Name> prototype_string_;
Handle<FixedArray> empty_fixed_array_;
const AstRawString* undefined_string_;
};
} // namespace interpreter

View File

@ -138,6 +138,17 @@ void TransformLdaZeroBinaryOpToBinaryOpWithZero(Bytecode new_bytecode,
}
}
void TransformEqualityWithNullOrUndefinedToTestUndetectable(
BytecodeNode* const last, BytecodeNode* const current) {
DCHECK((last->bytecode() == Bytecode::kLdaNull) ||
(last->bytecode() == Bytecode::kLdaUndefined));
DCHECK_EQ(current->bytecode(), Bytecode::kTestEqual);
current->set_bytecode(Bytecode::kTestUndetectable, current->operand(0));
if (last->source_info().is_valid()) {
current->set_source_info(last->source_info());
}
}
} // namespace
void BytecodePeepholeOptimizer::DefaultAction(
@ -251,6 +262,16 @@ void BytecodePeepholeOptimizer::
}
}
void BytecodePeepholeOptimizer::
TransformEqualityWithNullOrUndefinedToTestUndetectableAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());
DCHECK(!Bytecodes::IsJump(node->bytecode()));
// Fused last and current into current.
TransformEqualityWithNullOrUndefinedToTestUndetectable(last(), node);
SetLast(node);
}
void BytecodePeepholeOptimizer::DefaultJumpAction(
BytecodeNode* const node, const PeepholeActionAndData* action_data) {
DCHECK(LastIsValid());

View File

@ -11,16 +11,17 @@ namespace v8 {
namespace internal {
namespace interpreter {
#define PEEPHOLE_NON_JUMP_ACTION_LIST(V) \
V(DefaultAction) \
V(UpdateLastAction) \
V(UpdateLastIfSourceInfoPresentAction) \
V(ElideCurrentAction) \
V(ElideCurrentIfOperand0MatchesAction) \
V(ElideLastAction) \
V(ChangeBytecodeAction) \
V(TransformLdaSmiBinaryOpToBinaryOpWithSmiAction) \
V(TransformLdaZeroBinaryOpToBinaryOpWithZeroAction)
#define PEEPHOLE_NON_JUMP_ACTION_LIST(V) \
V(DefaultAction) \
V(UpdateLastAction) \
V(UpdateLastIfSourceInfoPresentAction) \
V(ElideCurrentAction) \
V(ElideCurrentIfOperand0MatchesAction) \
V(ElideLastAction) \
V(ChangeBytecodeAction) \
V(TransformLdaSmiBinaryOpToBinaryOpWithSmiAction) \
V(TransformLdaZeroBinaryOpToBinaryOpWithZeroAction) \
V(TransformEqualityWithNullOrUndefinedToTestUndetectableAction)
#define PEEPHOLE_JUMP_ACTION_LIST(V) \
V(DefaultJumpAction) \

View File

@ -183,6 +183,9 @@ namespace interpreter {
V(TestInstanceOf, AccumulatorUse::kReadWrite, OperandType::kReg) \
V(TestIn, AccumulatorUse::kReadWrite, OperandType::kReg) \
\
/* TestEqual with Null or Undefined */ \
V(TestUndetectable, AccumulatorUse::kWrite, OperandType::kReg) \
\
/* Cast operators */ \
V(ToName, AccumulatorUse::kRead, OperandType::kRegOut) \
V(ToNumber, AccumulatorUse::kRead, OperandType::kRegOut) \
@ -483,6 +486,7 @@ class V8_EXPORT_PRIVATE Bytecodes final {
case Bytecode::kTestGreaterThanOrEqual:
case Bytecode::kTestInstanceOf:
case Bytecode::kTestIn:
case Bytecode::kTestUndetectable:
case Bytecode::kForInContinue:
return true;
default:

View File

@ -1918,6 +1918,38 @@ void Interpreter::DoTestInstanceOf(InterpreterAssembler* assembler) {
DoCompareOp(Token::INSTANCEOF, assembler);
}
// TestUndetectable <src>
//
// Test if the value in the <src> register equals to Null/Undefined. This is
// done by checking undetectable bit on the map of the object.
void Interpreter::DoTestUndetectable(InterpreterAssembler* assembler) {
Node* reg_index = __ BytecodeOperandReg(0);
Node* object = __ LoadRegister(reg_index);
Label not_equal(assembler), end(assembler);
// If the object is an Smi then return false.
__ GotoIf(__ TaggedIsSmi(object), &not_equal);
// If it is a HeapObject, load the map and check for undetectable bit.
Node* map = __ LoadMap(object);
Node* map_bitfield = __ LoadMapBitField(map);
Node* map_undetectable =
__ Word32And(map_bitfield, __ Int32Constant(1 << Map::kIsUndetectable));
__ GotoIf(__ Word32Equal(map_undetectable, __ Int32Constant(0)), &not_equal);
__ SetAccumulator(__ BooleanConstant(true));
__ Goto(&end);
__ Bind(&not_equal);
{
__ SetAccumulator(__ BooleanConstant(false));
__ Goto(&end);
}
__ Bind(&end);
__ Dispatch();
}
// Jump <imm>
//
// Jump by number of bytes represented by the immediate operand |imm|.

View File

@ -192,6 +192,19 @@ PeepholeActionAndData PeepholeActionTableWriter::LookupActionAndData(
}
}
// Fuse LdaNull/LdaUndefined followed by a equality comparison with test
// undetectable. Testing undetectable is a simple check on the map which is
// more efficient than the full comparison operation.
// Note: StrictEquals cannot use this, they need to compare it with the
// Null/undefined map.
if (last == Bytecode::kLdaNull || last == Bytecode::kLdaUndefined) {
if (current == Bytecode::kTestEqual) {
return {PeepholeAction::
kTransformEqualityWithNullOrUndefinedToTestUndetectableAction,
Bytecode::kIllegal};
}
}
// If there is no last bytecode to optimize against, store the incoming
// bytecode or for jumps emit incoming bytecode immediately.
if (last == Bytecode::kIllegal) {

View File

@ -11,7 +11,7 @@ snippet: "
"
frame size: 15
parameter count: 1
bytecode array length: 268
bytecode array length: 266
bytecodes: [
/* 30 E> */ B(StackCheck),
B(LdaZero),
@ -71,11 +71,10 @@ bytecodes: [
B(Star), R(11),
B(LdaZero),
B(TestEqualStrict), R(4), U8(15),
B(JumpIfTrue), U8(113),
B(JumpIfTrue), U8(111),
B(LdaNamedProperty), R(2), U8(7), U8(16),
B(Star), R(6),
B(LdaNull),
B(TestEqual), R(6), U8(18),
B(TestUndetectable), R(6),
B(JumpIfFalse), U8(4),
B(Jump), U8(99),
B(LdaSmi), U8(1),
@ -144,7 +143,7 @@ constant pool: [
handlers: [
[7, 120, 126],
[10, 84, 86],
[195, 205, 207],
[193, 203, 205],
]
---
@ -154,7 +153,7 @@ snippet: "
"
frame size: 16
parameter count: 1
bytecode array length: 279
bytecode array length: 277
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 42 S> */ B(LdaConstant), U8(0),
@ -215,11 +214,10 @@ bytecodes: [
B(Star), R(12),
B(LdaZero),
B(TestEqualStrict), R(5), U8(15),
B(JumpIfTrue), U8(113),
B(JumpIfTrue), U8(111),
B(LdaNamedProperty), R(3), U8(7), U8(16),
B(Star), R(7),
B(LdaNull),
B(TestEqual), R(7), U8(18),
B(TestUndetectable), R(7),
B(JumpIfFalse), U8(4),
B(Jump), U8(99),
B(LdaSmi), U8(1),
@ -293,7 +291,7 @@ constant pool: [
handlers: [
[11, 120, 126],
[14, 84, 86],
[196, 206, 208],
[194, 204, 206],
]
---
@ -305,7 +303,7 @@ snippet: "
"
frame size: 15
parameter count: 1
bytecode array length: 286
bytecode array length: 284
bytecodes: [
/* 30 E> */ B(StackCheck),
B(LdaZero),
@ -373,11 +371,10 @@ bytecodes: [
B(Star), R(11),
B(LdaZero),
B(TestEqualStrict), R(4), U8(17),
B(JumpIfTrue), U8(113),
B(JumpIfTrue), U8(111),
B(LdaNamedProperty), R(2), U8(7), U8(18),
B(Star), R(6),
B(LdaNull),
B(TestEqual), R(6), U8(20),
B(TestUndetectable), R(6),
B(JumpIfFalse), U8(4),
B(Jump), U8(99),
B(LdaSmi), U8(1),
@ -446,7 +443,7 @@ constant pool: [
handlers: [
[7, 138, 144],
[10, 102, 104],
[213, 223, 225],
[211, 221, 223],
]
---
@ -456,7 +453,7 @@ snippet: "
"
frame size: 14
parameter count: 1
bytecode array length: 293
bytecode array length: 291
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 42 S> */ B(CreateObjectLiteral), U8(0), U8(0), U8(1), R(8),
@ -521,11 +518,10 @@ bytecodes: [
B(Star), R(10),
B(LdaZero),
B(TestEqualStrict), R(3), U8(19),
B(JumpIfTrue), U8(113),
B(JumpIfTrue), U8(111),
B(LdaNamedProperty), R(1), U8(9), U8(20),
B(Star), R(5),
B(LdaNull),
B(TestEqual), R(5), U8(22),
B(TestUndetectable), R(5),
B(JumpIfFalse), U8(4),
B(Jump), U8(99),
B(LdaSmi), U8(1),
@ -601,6 +597,6 @@ constant pool: [
handlers: [
[15, 134, 140],
[18, 98, 100],
[210, 220, 222],
[208, 218, 220],
]

View File

@ -0,0 +1,123 @@
#
# Autogenerated by generate-bytecode-expectations.
#
---
wrap: yes
---
snippet: "
var obj_a = {val:1};
var b = 10;
if (obj_a == null) { b = 20;}
return b;
"
frame size: 3
parameter count: 1
bytecode array length: 24
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 46 S> */ B(CreateObjectLiteral), U8(0), U8(0), U8(1), R(2),
B(Mov), R(2), R(0),
/* 63 S> */ B(LdaSmi), U8(10),
B(Star), R(1),
/* 67 S> */ B(TestUndetectable), R(0),
B(JumpIfFalse), U8(6),
/* 88 S> */ B(LdaSmi), U8(20),
B(Star), R(1),
/* 97 S> */ B(Ldar), R(1),
/* 107 S> */ B(Return),
]
constant pool: [
FIXED_ARRAY_TYPE,
]
handlers: [
]
---
snippet: "
var obj_a = {val:1};
var b = 10;
if (obj_a == undefined) { b = 20;}
return b;
"
frame size: 3
parameter count: 1
bytecode array length: 24
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 46 S> */ B(CreateObjectLiteral), U8(0), U8(0), U8(1), R(2),
B(Mov), R(2), R(0),
/* 63 S> */ B(LdaSmi), U8(10),
B(Star), R(1),
/* 67 S> */ B(TestUndetectable), R(0),
B(JumpIfFalse), U8(6),
/* 93 S> */ B(LdaSmi), U8(20),
B(Star), R(1),
/* 102 S> */ B(Ldar), R(1),
/* 112 S> */ B(Return),
]
constant pool: [
FIXED_ARRAY_TYPE,
]
handlers: [
]
---
snippet: "
var obj_a = {val:1};
var b = 10;
if (obj_a != null) { b = 20;}
return b;
"
frame size: 3
parameter count: 1
bytecode array length: 24
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 46 S> */ B(CreateObjectLiteral), U8(0), U8(0), U8(1), R(2),
B(Mov), R(2), R(0),
/* 63 S> */ B(LdaSmi), U8(10),
B(Star), R(1),
/* 67 S> */ B(TestUndetectable), R(0),
B(JumpIfTrue), U8(6),
/* 88 S> */ B(LdaSmi), U8(20),
B(Star), R(1),
/* 97 S> */ B(Ldar), R(1),
/* 107 S> */ B(Return),
]
constant pool: [
FIXED_ARRAY_TYPE,
]
handlers: [
]
---
snippet: "
var obj_a = {val:1};
var b = 10;
if (obj_a != undefined) { b = 20;}
return b;
"
frame size: 3
parameter count: 1
bytecode array length: 24
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 46 S> */ B(CreateObjectLiteral), U8(0), U8(0), U8(1), R(2),
B(Mov), R(2), R(0),
/* 63 S> */ B(LdaSmi), U8(10),
B(Star), R(1),
/* 67 S> */ B(TestUndetectable), R(0),
B(JumpIfTrue), U8(6),
/* 93 S> */ B(LdaSmi), U8(20),
B(Star), R(1),
/* 102 S> */ B(Ldar), R(1),
/* 112 S> */ B(Return),
]
constant pool: [
FIXED_ARRAY_TYPE,
]
handlers: [
]

View File

@ -277,7 +277,7 @@ snippet: "
"
frame size: 17
parameter count: 1
bytecode array length: 771
bytecode array length: 769
bytecodes: [
B(Ldar), R(new_target),
B(JumpIfUndefined), U8(28),
@ -475,8 +475,7 @@ bytecodes: [
B(StaContextSlot), R(1), U8(11), U8(0),
B(LdaContextSlot), R(1), U8(11), U8(0),
B(Star), R(10),
B(LdaNull),
B(TestEqual), R(10), U8(18),
B(TestUndetectable), R(10),
B(JumpIfFalse), U8(4),
B(JumpConstant), U8(16),
B(LdaContextSlot), R(1), U8(9), U8(0),
@ -620,13 +619,13 @@ constant pool: [
ONE_BYTE_INTERNALIZED_STRING_TYPE [""],
FIXED_ARRAY_TYPE,
Smi [133],
Smi [161],
Smi [581],
Smi [159],
Smi [579],
]
handlers: [
[46, 690, 696],
[46, 688, 694],
[143, 438, 444],
[146, 394, 396],
[542, 558, 560],
[540, 556, 558],
]

View File

@ -1742,6 +1742,31 @@ TEST(RemoveRedundantLdar) {
LoadGolden("RemoveRedundantLdar.golden")));
}
TEST(GenerateTestUndetectable) {
InitializedIgnitionHandleScope scope;
BytecodeExpectationsPrinter printer(CcTest::isolate());
const char* snippets[] = {
"var obj_a = {val:1};\n"
"var b = 10;\n"
"if (obj_a == null) { b = 20;}\n"
"return b;\n",
"var obj_a = {val:1};\n"
"var b = 10;\n"
"if (obj_a == undefined) { b = 20;}\n"
"return b;\n",
"var obj_a = {val:1};\n"
"var b = 10;\n"
"if (obj_a != null) { b = 20;}\n"
"return b;\n",
"var obj_a = {val:1};\n"
"var b = 10;\n"
"if (obj_a != undefined) { b = 20;}\n"
"return b;\n"};
CHECK(CompareTexts(BuildActual(printer, snippets),
LoadGolden("GenerateTestUndetectable.golden")));
}
TEST(AssignmentsInBinaryExpression) {
InitializedIgnitionHandleScope scope;
BytecodeExpectationsPrinter printer(CcTest::isolate());

View File

@ -1075,7 +1075,7 @@ TEST(CodeSerializerLargeCodeObject) {
Vector<const uint8_t> source =
ConstructSource(STATIC_CHAR_VECTOR("var j=1; if (j == 0) {"),
STATIC_CHAR_VECTOR("for (let i of Object.prototype);"),
STATIC_CHAR_VECTOR("} j=7; j"), 1000);
STATIC_CHAR_VECTOR("} j=7; j"), 1050);
Handle<String> source_str =
isolate->factory()->NewStringFromOneByte(source).ToHandleChecked();

View File

@ -195,6 +195,12 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.CompareOperation(Token::Value::INSTANCEOF, reg, 8)
.CompareOperation(Token::Value::IN, reg, 9);
// Emit peephole optimizations of equality with Null or Undefined.
builder.LoadUndefined()
.CompareOperation(Token::Value::EQ, reg, 1)
.LoadNull()
.CompareOperation(Token::Value::EQ, reg, 1);
// Emit conversion operator invocations.
builder.ConvertAccumulatorToNumber(reg)
.ConvertAccumulatorToObject(reg)
@ -398,6 +404,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
scorecard[Bytecodes::ToByte(Bytecode::kBitwiseOrSmi)] = 1;
scorecard[Bytecodes::ToByte(Bytecode::kShiftLeftSmi)] = 1;
scorecard[Bytecodes::ToByte(Bytecode::kShiftRightSmi)] = 1;
scorecard[Bytecodes::ToByte(Bytecode::kTestUndetectable)] = 1;
}
// Check return occurs at the end and only once in the BytecodeArray.

View File

@ -403,6 +403,25 @@ TEST_F(BytecodePeepholeOptimizerTest, MergeLdaZeroWithBinaryOp) {
}
}
TEST_F(BytecodePeepholeOptimizerTest, MergeLdaNullOrUndefinedWithCompareOp) {
Bytecode first_bytecodes[] = {Bytecode::kLdaUndefined, Bytecode::kLdaNull};
for (auto first_bytecode : first_bytecodes) {
uint32_t reg_operand = Register(0).ToOperand();
uint32_t idx_operand = 1;
BytecodeNode first(first_bytecode);
BytecodeNode second(Bytecode::kTestEqual, reg_operand, idx_operand);
optimizer()->Write(&first);
optimizer()->Write(&second);
Flush();
CHECK_EQ(write_count(), 1);
CHECK_EQ(last_written().bytecode(), Bytecode::kTestUndetectable);
CHECK_EQ(last_written().operand_count(), 1);
CHECK_EQ(last_written().operand(0), reg_operand);
Reset();
}
}
} // namespace interpreter
} // namespace internal
} // namespace v8