Commit 4926be6e authored by oth's avatar oth Committed by Commit bot

[Interpreter] Implement ForIn in bytecode graph builder.

A pre-requisite for this change was changing the interpreter to use
Runtime::ForInStep to bring the interpreter implementation closer
to the turbofan implementation. Also required to flatten out the
cache parameters into the interpreter frame for de-opt.

BUG=v8:4280
LOG=N

Review URL: https://codereview.chromium.org/1531693002

Cr-Commit-Position: refs/heads/master@{#32986}
parent 8bfb7189
......@@ -1394,15 +1394,15 @@ void BytecodeGraphBuilder::VisitToName(
}
void BytecodeGraphBuilder::VisitToNumber(
void BytecodeGraphBuilder::VisitToObject(
const interpreter::BytecodeArrayIterator& iterator) {
BuildCastOperator(javascript()->ToNumber(), iterator);
BuildCastOperator(javascript()->ToObject(), iterator);
}
void BytecodeGraphBuilder::VisitToObject(
void BytecodeGraphBuilder::VisitToNumber(
const interpreter::BytecodeArrayIterator& iterator) {
BuildCastOperator(javascript()->ToObject(), iterator);
BuildCastOperator(javascript()->ToNumber(), iterator);
}
......@@ -1513,19 +1513,55 @@ void BytecodeGraphBuilder::VisitReturn(
void BytecodeGraphBuilder::VisitForInPrepare(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
Node* prepare = nullptr;
{
FrameStateBeforeAndAfter states(this, iterator);
Node* receiver = environment()->LookupAccumulator();
prepare = NewNode(javascript()->ForInPrepare(), receiver);
environment()->RecordAfterState(prepare, &states);
}
// Project cache_type, cache_array, cache_length into register
// operands 1, 2, 3.
for (int i = 0; i < 3; i++) {
environment()->BindRegister(iterator.GetRegisterOperand(i),
NewNode(common()->Projection(i), prepare));
}
}
void BytecodeGraphBuilder::VisitForInDone(
const interpreter::BytecodeArrayIterator& iterator) {
FrameStateBeforeAndAfter states(this, iterator);
Node* index = environment()->LookupRegister(iterator.GetRegisterOperand(0));
Node* cache_length =
environment()->LookupRegister(iterator.GetRegisterOperand(1));
Node* exit_cond = NewNode(javascript()->ForInDone(), index, cache_length);
environment()->BindAccumulator(exit_cond, &states);
}
void BytecodeGraphBuilder::VisitForInNext(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
FrameStateBeforeAndAfter states(this, iterator);
Node* receiver =
environment()->LookupRegister(iterator.GetRegisterOperand(0));
Node* cache_type =
environment()->LookupRegister(iterator.GetRegisterOperand(1));
Node* cache_array =
environment()->LookupRegister(iterator.GetRegisterOperand(2));
Node* index = environment()->LookupRegister(iterator.GetRegisterOperand(3));
Node* value = NewNode(javascript()->ForInNext(), receiver, cache_array,
cache_type, index);
environment()->BindAccumulator(value, &states);
}
void BytecodeGraphBuilder::VisitForInDone(
void BytecodeGraphBuilder::VisitForInStep(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
FrameStateBeforeAndAfter states(this, iterator);
Node* index = environment()->LookupRegister(iterator.GetRegisterOperand(0));
index = NewNode(javascript()->ForInStep(), index);
environment()->BindAccumulator(index, &states);
}
......
......@@ -71,6 +71,7 @@ BytecodeArrayBuilder::BytecodeArrayBuilder(Isolate* isolate, Zone* zone)
last_block_end_(0),
last_bytecode_start_(~0),
exit_seen_in_block_(false),
unbound_jumps_(0),
constants_map_(isolate->heap(), zone),
constants_(zone),
parameter_count_(-1),
......@@ -80,6 +81,9 @@ BytecodeArrayBuilder::BytecodeArrayBuilder(Isolate* isolate, Zone* zone)
free_temporaries_(zone) {}
BytecodeArrayBuilder::~BytecodeArrayBuilder() { DCHECK_EQ(0, unbound_jumps_); }
void BytecodeArrayBuilder::set_locals_count(int number_of_locals) {
local_register_count_ = number_of_locals;
DCHECK_LE(context_register_count_, 0);
......@@ -782,6 +786,7 @@ void BytecodeArrayBuilder::PatchJump(
UNIMPLEMENTED();
}
}
unbound_jumps_--;
}
......@@ -826,6 +831,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::OutputJump(Bytecode jump_bytecode,
// that will be patched when the label is bound.
label->set_referrer(bytecodes()->size());
delta = 0;
unbound_jumps_++;
}
if (FitsInImm8Operand(delta)) {
......@@ -884,21 +890,33 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::Return() {
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInPrepare(Register receiver) {
Output(Bytecode::kForInPrepare, receiver.ToOperand());
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInPrepare(
Register cache_type, Register cache_array, Register cache_length) {
Output(Bytecode::kForInPrepare, cache_type.ToOperand(),
cache_array.ToOperand(), cache_length.ToOperand());
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInDone(Register index,
Register cache_length) {
Output(Bytecode::kForInDone, index.ToOperand(), cache_length.ToOperand());
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInNext(Register for_in_state,
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInNext(Register receiver,
Register cache_type,
Register cache_array,
Register index) {
Output(Bytecode::kForInNext, for_in_state.ToOperand(), index.ToOperand());
Output(Bytecode::kForInNext, receiver.ToOperand(), cache_type.ToOperand(),
cache_array.ToOperand(), index.ToOperand());
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInDone(Register for_in_state) {
Output(Bytecode::kForInDone, for_in_state.ToOperand());
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInStep(Register index) {
Output(Bytecode::kForInStep, index.ToOperand());
return *this;
}
......
......@@ -27,9 +27,11 @@ class Register;
// when rest parameters implementation has settled down.
enum class CreateArgumentsType { kMappedArguments, kUnmappedArguments };
class BytecodeArrayBuilder {
class BytecodeArrayBuilder final {
public:
BytecodeArrayBuilder(Isolate* isolate, Zone* zone);
~BytecodeArrayBuilder();
Handle<BytecodeArray> ToBytecodeArray();
// Set the number of parameters expected by function.
......@@ -211,9 +213,12 @@ class BytecodeArrayBuilder {
BytecodeArrayBuilder& Return();
// Complex flow control.
BytecodeArrayBuilder& ForInPrepare(Register receiver);
BytecodeArrayBuilder& ForInNext(Register for_in_state, Register index);
BytecodeArrayBuilder& ForInDone(Register for_in_state);
BytecodeArrayBuilder& ForInPrepare(Register cache_type, Register cache_array,
Register cache_length);
BytecodeArrayBuilder& ForInDone(Register index, Register cache_length);
BytecodeArrayBuilder& ForInNext(Register receiver, Register cache_type,
Register cache_array, Register index);
BytecodeArrayBuilder& ForInStep(Register index);
// Accessors
Zone* zone() const { return zone_; }
......@@ -288,6 +293,7 @@ class BytecodeArrayBuilder {
size_t last_block_end_;
size_t last_bytecode_start_;
bool exit_seen_in_block_;
int unbound_jumps_;
IdentityMap<size_t> constants_map_;
ZoneVector<Handle<Object>> constants_;
......
......@@ -855,7 +855,6 @@ void BytecodeGenerator::VisitForInAssignment(Expression* expr,
void BytecodeGenerator::VisitForInStatement(ForInStatement* stmt) {
EffectResultScope statement_result_scope(this);
if (stmt->subject()->IsNullLiteral() ||
stmt->subject()->IsUndefinedLiteral(isolate())) {
// ForIn generates lots of code, skip if it wouldn't produce any effects.
......@@ -864,41 +863,43 @@ void BytecodeGenerator::VisitForInStatement(ForInStatement* stmt) {
LoopBuilder loop_builder(builder());
ControlScopeForIteration control_scope(this, stmt, &loop_builder);
BytecodeLabel subject_null_label, subject_undefined_label, not_object_label;
// Prepare the state for executing ForIn.
VisitForAccumulatorValue(stmt->subject());
loop_builder.BreakIfUndefined();
loop_builder.BreakIfNull();
builder()->JumpIfUndefined(&subject_undefined_label);
builder()->JumpIfNull(&subject_null_label);
Register receiver = execution_result()->NewRegister();
builder()->CastAccumulatorToJSObject();
builder()->JumpIfNull(&not_object_label);
builder()->StoreAccumulatorInRegister(receiver);
builder()->CallRuntime(Runtime::kGetPropertyNamesFast, receiver, 1);
builder()->ForInPrepare(receiver);
loop_builder.BreakIfUndefined();
Register for_in_state = execution_result()->NewRegister();
builder()->StoreAccumulatorInRegister(for_in_state);
Register cache_type = execution_result()->NewRegister();
Register cache_array = execution_result()->NewRegister();
Register cache_length = execution_result()->NewRegister();
builder()->ForInPrepare(cache_type, cache_array, cache_length);
// Check loop termination (accumulator holds index).
Register index = receiver; // Re-using register as receiver no longer used.
// Set up loop counter
Register index = execution_result()->NewRegister();
builder()->LoadLiteral(Smi::FromInt(0));
builder()->StoreAccumulatorInRegister(index);
// The loop
loop_builder.LoopHeader();
loop_builder.Condition();
builder()->StoreAccumulatorInRegister(index).ForInDone(for_in_state);
builder()->ForInDone(index, cache_length);
loop_builder.BreakIfTrue();
builder()->ForInNext(for_in_state, index);
builder()->ForInNext(receiver, cache_type, cache_array, index);
loop_builder.ContinueIfUndefined();
VisitForInAssignment(stmt->each(), stmt->EachFeedbackSlot());
Visit(stmt->body());
// TODO(oth): replace CountOperation here with ForInStep.
loop_builder.Next();
builder()->LoadAccumulatorWithRegister(index).CountOperation(
Token::Value::ADD, language_mode_strength());
builder()->ForInStep(index);
builder()->StoreAccumulatorInRegister(index);
loop_builder.JumpToHeader();
loop_builder.EndLoop();
builder()->Bind(&not_object_label);
builder()->Bind(&subject_null_label);
builder()->Bind(&subject_undefined_label);
}
......
......@@ -199,9 +199,11 @@ namespace interpreter {
V(JumpIfUndefinedConstant, OperandType::kIdx8) \
\
/* Complex flow control For..in */ \
V(ForInPrepare, OperandType::kReg8) \
V(ForInNext, OperandType::kReg8, OperandType::kReg8) \
V(ForInDone, OperandType::kReg8) \
V(ForInPrepare, OperandType::kReg8, OperandType::kReg8, OperandType::kReg8) \
V(ForInDone, OperandType::kReg8, OperandType::kReg8) \
V(ForInNext, OperandType::kReg8, OperandType::kReg8, OperandType::kReg8, \
OperandType::kReg8) \
V(ForInStep, OperandType::kReg8) \
\
/* Non-local flow control */ \
V(Throw, OperandType::kNone) \
......
......@@ -90,6 +90,16 @@ void BlockBuilder::EndBlock() {
LoopBuilder::~LoopBuilder() { DCHECK(continue_sites_.empty()); }
void LoopBuilder::LoopHeader() {
// Jumps from before the loop header into the loop violate ordering
// requirements of bytecode basic blocks. The only entry into a loop
// must be the loop header. Surely breaks is okay? Not if nested
// and misplaced between the headers.
DCHECK(break_sites_.empty() && continue_sites_.empty());
builder()->Bind(&loop_header_);
}
void LoopBuilder::EndLoop() {
// Loop must have closed form, i.e. all loop elements are within the loop,
// the loop header precedes the body and next elements in the loop.
......
......@@ -60,7 +60,6 @@ class BreakableControlFlowBuilder : public ControlFlowBuilder {
void BindLabels(const BytecodeLabel& target, ZoneVector<BytecodeLabel>* site);
private:
// Unbound labels that identify jumps for break statements in the code.
ZoneVector<BytecodeLabel> break_sites_;
};
......@@ -88,7 +87,7 @@ class LoopBuilder final : public BreakableControlFlowBuilder {
continue_sites_(builder->zone()) {}
~LoopBuilder();
void LoopHeader() { builder()->Bind(&loop_header_); }
void LoopHeader();
void Condition() { builder()->Bind(&condition_); }
void Next() { builder()->Bind(&next_); }
void JumpToHeader() { builder()->Jump(&loop_header_); }
......
......@@ -1516,33 +1516,36 @@ void Interpreter::DoReturn(compiler::InterpreterAssembler* assembler) {
}
// ForInPrepare <receiver>
// ForInPrepare <cache_type> <cache_array> <cache_length>
//
// Returns state for for..in loop execution based on the |receiver| and
// the property names in the accumulator.
// Returns state for for..in loop execution based on the object in the
// accumulator. The registers |cache_type|, |cache_array|, and
// |cache_length| represent output parameters.
void Interpreter::DoForInPrepare(compiler::InterpreterAssembler* assembler) {
Node* receiver_reg = __ BytecodeOperandReg(0);
Node* receiver = __ LoadRegister(receiver_reg);
Node* property_names = __ GetAccumulator();
Node* result = __ CallRuntime(Runtime::kInterpreterForInPrepare, receiver,
property_names);
Node* object = __ GetAccumulator();
Node* result = __ CallRuntime(Runtime::kInterpreterForInPrepare, object);
for (int i = 0; i < 3; i++) {
// 0 == cache_type, 1 == cache_array, 2 == cache_length
Node* cache_info = __ LoadFixedArrayElement(result, i);
Node* cache_info_reg = __ BytecodeOperandReg(i);
__ StoreRegister(cache_info, cache_info_reg);
}
__ SetAccumulator(result);
__ Dispatch();
}
// ForInNext <for_in_state> <index>
// ForInNext <receiver> <cache_type> <cache_array> <index>
//
// Returns the next key in a for..in loop. The state associated with the
// iteration is contained in |for_in_state| and |index| is the current
// zero-based iteration count.
// Returns the next enumerable property in the the accumulator.
void Interpreter::DoForInNext(compiler::InterpreterAssembler* assembler) {
Node* for_in_state_reg = __ BytecodeOperandReg(0);
Node* for_in_state = __ LoadRegister(for_in_state_reg);
Node* receiver = __ LoadFixedArrayElement(for_in_state, 0);
Node* cache_array = __ LoadFixedArrayElement(for_in_state, 1);
Node* cache_type = __ LoadFixedArrayElement(for_in_state, 2);
Node* index_reg = __ BytecodeOperandReg(1);
Node* receiver_reg = __ BytecodeOperandReg(0);
Node* receiver = __ LoadRegister(receiver_reg);
Node* cache_type_reg = __ BytecodeOperandReg(1);
Node* cache_type = __ LoadRegister(cache_type_reg);
Node* cache_array_reg = __ BytecodeOperandReg(2);
Node* cache_array = __ LoadRegister(cache_array_reg);
Node* index_reg = __ BytecodeOperandReg(3);
Node* index = __ LoadRegister(index_reg);
Node* result = __ CallRuntime(Runtime::kForInNext, receiver, cache_array,
cache_type, index);
......@@ -1551,22 +1554,34 @@ void Interpreter::DoForInNext(compiler::InterpreterAssembler* assembler) {
}
// ForInDone <for_in_state>
// ForInDone <index> <cache_length>
//
// Returns the next key in a for..in loop. The accumulator contains the current
// zero-based iteration count and |for_in_state| is the state returned by an
// earlier invocation of ForInPrepare.
// Returns true if the end of the enumerable properties has been reached.
void Interpreter::DoForInDone(compiler::InterpreterAssembler* assembler) {
Node* index = __ GetAccumulator();
Node* for_in_state_reg = __ BytecodeOperandReg(0);
Node* for_in_state = __ LoadRegister(for_in_state_reg);
Node* cache_length = __ LoadFixedArrayElement(for_in_state, 3);
// TODO(oth): Implement directly rather than making a runtime call.
Node* index_reg = __ BytecodeOperandReg(0);
Node* index = __ LoadRegister(index_reg);
Node* cache_length_reg = __ BytecodeOperandReg(1);
Node* cache_length = __ LoadRegister(cache_length_reg);
Node* result = __ CallRuntime(Runtime::kForInDone, index, cache_length);
__ SetAccumulator(result);
__ Dispatch();
}
// ForInStep <index>
//
// Increments the loop counter in register |index| and stores the result
// in the accumulator.
void Interpreter::DoForInStep(compiler::InterpreterAssembler* assembler) {
// TODO(oth): Implement directly rather than making a runtime call.
Node* index_reg = __ BytecodeOperandReg(0);
Node* index = __ LoadRegister(index_reg);
Node* result = __ CallRuntime(Runtime::kForInStep, index);
__ SetAccumulator(result);
__ Dispatch();
}
} // namespace interpreter
} // namespace internal
} // namespace v8
......@@ -150,18 +150,24 @@ RUNTIME_FUNCTION(Runtime_InterpreterNewClosure) {
RUNTIME_FUNCTION(Runtime_InterpreterForInPrepare) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
CONVERT_ARG_HANDLE_CHECKED(HeapObject, property_names, 1);
Handle<Object> cache_type = property_names;
Handle<Map> cache_type_map = handle(property_names->map(), isolate);
Handle<Map> receiver_map = handle(receiver->map(), isolate);
Object* property_names = Runtime_GetPropertyNamesFast(
1, Handle<Object>::cast(receiver).location(), isolate);
if (isolate->has_pending_exception()) {
return property_names;
}
Handle<Object> cache_type(property_names, isolate);
Handle<FixedArray> cache_array;
int cache_length;
if (cache_type_map.is_identical_to(isolate->factory()->meta_map())) {
Handle<Map> receiver_map = handle(receiver->map(), isolate);
if (cache_type->IsMap()) {
Handle<Map> cache_type_map =
handle(Handle<Map>::cast(cache_type)->map(), isolate);
DCHECK(cache_type_map.is_identical_to(isolate->factory()->meta_map()));
int enum_length = cache_type_map->EnumLength();
DescriptorArray* descriptors = receiver_map->instance_descriptors();
if (enum_length > 0 && descriptors->HasEnumCache()) {
......@@ -185,14 +191,12 @@ RUNTIME_FUNCTION(Runtime_InterpreterForInPrepare) {
}
}
Handle<FixedArray> result = isolate->factory()->NewFixedArray(4);
result->set(0, *receiver);
Handle<FixedArray> result = isolate->factory()->NewFixedArray(3);
result->set(0, *cache_type);
result->set(1, *cache_array);
result->set(2, *cache_type);
result->set(3, Smi::FromInt(cache_length));
result->set(2, Smi::FromInt(cache_length));
return *result;
}
} // namespace internal
} // namespace v8
......@@ -1880,6 +1880,79 @@ TEST(BytecodeGraphBuilderFor) {
}
}
TEST(BytecodeGraphBuilderForIn) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
Factory* factory = isolate->factory();
ExpectedSnippet<0> snippets[] = {
{"var sum = 0;\n"
"var empty = null;\n"
"for (var x in empty) { sum++; }\n"
"return sum;",
{factory->NewNumberFromInt(0)}},
{"var sum = 100;\n"
"var empty = 1;\n"
"for (var x in empty) { sum++; }\n"
"return sum;",
{factory->NewNumberFromInt(100)}},
{"for (var x in [ 10, 20, 30 ]) {}\n"
"return 2;",
{factory->NewNumberFromInt(2)}},
{"var last = 0;\n"
"for (var x in [ 10, 20, 30 ]) {\n"
" last = x;\n"
"}\n"
"return +last;",
{factory->NewNumberFromInt(2)}},
{"var first = -1;\n"
"for (var x in [ 10, 20, 30 ]) {\n"
" first = +x;\n"
" if (first > 0) break;\n"
"}\n"
"return first;",
{factory->NewNumberFromInt(1)}},
{"var first = -1;\n"
"for (var x in [ 10, 20, 30 ]) {\n"
" if (first >= 0) continue;\n"
" first = x;\n"
"}\n"
"return +first;",
{factory->NewNumberFromInt(0)}},
{"var sum = 0;\n"
"for (var x in [ 10, 20, 30 ]) {\n"
" for (var y in [ 11, 22, 33, 44, 55, 66, 77 ]) {\n"
" sum += 1;\n"
" }\n"
"}\n"
"return sum;",
{factory->NewNumberFromInt(21)}},
{"var sum = 0;\n"
"for (var x in [ 10, 20, 30 ]) {\n"
" for (var y in [ 11, 22, 33, 44, 55, 66, 77 ]) {\n"
" if (sum == 7) break;\n"
" if (sum == 6) continue;\n"
" sum += 1;\n"
" }\n"
"}\n"
"return sum;",
{factory->NewNumberFromInt(6)}},
};
for (size_t i = 0; i < arraysize(snippets); i++) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s() { %s }\n%s();", kFunctionName,
snippets[i].code_snippet, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*snippets[i].return_value()));
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -216,7 +216,10 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.Throw()
.Bind(&after_throw);
builder.ForInPrepare(reg).ForInDone(reg).ForInNext(reg, reg);
builder.ForInPrepare(reg, reg, reg)
.ForInDone(reg, reg)
.ForInNext(reg, reg, reg, reg)
.ForInStep(reg);
// Wide constant pool loads
for (int i = 0; i < 256; i++) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment