Commit dcf757a1 authored by oth's avatar oth Committed by Commit bot

[Interpreter] Add support for for..in.

For..in introduces 3 new bytecodes ForInPrepare, ForInNext, and
ForInDone to start a for..in loop, get the next element, and check if
the loop is done.

For..in builds upon new LoopBuilder constructs for conditionally
breaking and continuing during iteration: BreakIf{Null|Undefined}
and ContinueIf{Null|Undefined}. New conditional jump bytecodes
support this succinctly: JumpIfNull and JumpIfUndefined.

Add missing check to BytecodeLabel that could allow multiple
forward referencess to the same label which is not supported.

BUG=v8:4280
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#31651}
parent 76d730b9
......@@ -20,6 +20,7 @@
*.xcodeproj
#*#
*~
.#*
.cpplint-cache
.cproject
.d8_history
......
......@@ -602,6 +602,12 @@ void BytecodeGraphBuilder::VisitToNumber(
}
void BytecodeGraphBuilder::VisitToObject(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitJump(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
......@@ -662,6 +668,30 @@ void BytecodeGraphBuilder::VisitJumpIfToBooleanFalseConstant(
}
void BytecodeGraphBuilder::VisitJumpIfNull(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitJumpIfNullConstant(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitJumpIfUndefined(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitJumpIfUndefinedConstant(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitReturn(
const interpreter::BytecodeArrayIterator& iterator) {
Node* control =
......@@ -670,6 +700,24 @@ void BytecodeGraphBuilder::VisitReturn(
}
void BytecodeGraphBuilder::VisitForInPrepare(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitForInNext(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitForInDone(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
Node** BytecodeGraphBuilder::EnsureInputBufferSize(int size) {
if (size > input_buffer_size_) {
size = size + kInputBufferSizeIncrement + input_buffer_size_;
......
......@@ -292,6 +292,15 @@ Node* InterpreterAssembler::LoadConstantPoolEntry(Node* index) {
}
Node* InterpreterAssembler::LoadFixedArrayElement(Node* fixed_array,
int index) {
Node* entry_offset =
IntPtrAdd(IntPtrConstant(FixedArray::kHeaderSize - kHeapObjectTag),
WordShl(Int32Constant(index), kPointerSizeLog2));
return raw_assembler_->Load(kMachAnyTagged, fixed_array, entry_offset);
}
Node* InterpreterAssembler::LoadObjectField(Node* object, int offset) {
return raw_assembler_->Load(kMachAnyTagged, object,
IntPtrConstant(offset - kHeapObjectTag));
......
......@@ -92,6 +92,9 @@ class InterpreterAssembler {
// Load constant at |index| in the constant pool.
Node* LoadConstantPoolEntry(Node* index);
// Load an element from a fixed array on the heap.
Node* LoadFixedArrayElement(Node* fixed_array, int index);
// Load a field from an object on the heap.
Node* LoadObjectField(Node* object, int offset);
......
......@@ -75,6 +75,10 @@ class RawMachineAssembler {
// place them into the current basic block. They don't perform control flow,
// hence will not switch the current basic block.
Node* NullConstant() {
return HeapConstant(isolate()->factory()->null_value());
}
Node* UndefinedConstant() {
return HeapConstant(isolate()->factory()->undefined_value());
}
......
......@@ -263,6 +263,8 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadFalse() {
BytecodeArrayBuilder& BytecodeArrayBuilder::LoadAccumulatorWithRegister(
Register reg) {
// TODO(oth): Avoid loading the accumulator with the register if the
// previous bytecode stored the accumulator with the same register.
Output(Bytecode::kLdar, reg.ToOperand());
return *this;
}
......@@ -270,6 +272,8 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadAccumulatorWithRegister(
BytecodeArrayBuilder& BytecodeArrayBuilder::StoreAccumulatorInRegister(
Register reg) {
// TODO(oth): Avoid storing the accumulator in the register if the
// previous bytecode loaded the accumulator with the same register.
Output(Bytecode::kStar, reg.ToOperand());
return *this;
}
......@@ -481,6 +485,12 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CastAccumulatorToBoolean() {
}
BytecodeArrayBuilder& BytecodeArrayBuilder::CastAccumulatorToJSObject() {
Output(Bytecode::kToObject);
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::CastAccumulatorToName() {
Output(Bytecode::kToName);
return *this;
......@@ -531,6 +541,10 @@ Bytecode BytecodeArrayBuilder::GetJumpWithConstantOperand(
return Bytecode::kJumpIfToBooleanTrueConstant;
case Bytecode::kJumpIfToBooleanFalse:
return Bytecode::kJumpIfToBooleanFalseConstant;
case Bytecode::kJumpIfNull:
return Bytecode::kJumpIfNullConstant;
case Bytecode::kJumpIfUndefined:
return Bytecode::kJumpIfUndefinedConstant;
default:
UNREACHABLE();
return Bytecode::kJumpConstant;
......@@ -631,6 +645,17 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::JumpIfToBooleanFalse(
}
BytecodeArrayBuilder& BytecodeArrayBuilder::JumpIfNull(BytecodeLabel* label) {
return OutputJump(Bytecode::kJumpIfNull, label);
}
BytecodeArrayBuilder& BytecodeArrayBuilder::JumpIfUndefined(
BytecodeLabel* label) {
return OutputJump(Bytecode::kJumpIfUndefined, label);
}
BytecodeArrayBuilder& BytecodeArrayBuilder::Throw() {
Output(Bytecode::kThrow);
exit_seen_in_block_ = true;
......@@ -645,6 +670,25 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::Return() {
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInPrepare(Register receiver) {
Output(Bytecode::kForInPrepare, receiver.ToOperand());
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInNext(Register for_in_state,
Register index) {
Output(Bytecode::kForInNext, for_in_state.ToOperand(), index.ToOperand());
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ForInDone(Register for_in_state) {
Output(Bytecode::kForInDone, for_in_state.ToOperand());
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::EnterBlock() { return *this; }
......
......@@ -170,6 +170,7 @@ class BytecodeArrayBuilder {
// Casts.
BytecodeArrayBuilder& CastAccumulatorToBoolean();
BytecodeArrayBuilder& CastAccumulatorToJSObject();
BytecodeArrayBuilder& CastAccumulatorToName();
BytecodeArrayBuilder& CastAccumulatorToNumber();
......@@ -180,6 +181,8 @@ class BytecodeArrayBuilder {
BytecodeArrayBuilder& Jump(BytecodeLabel* label);
BytecodeArrayBuilder& JumpIfTrue(BytecodeLabel* label);
BytecodeArrayBuilder& JumpIfFalse(BytecodeLabel* label);
BytecodeArrayBuilder& JumpIfNull(BytecodeLabel* label);
BytecodeArrayBuilder& JumpIfUndefined(BytecodeLabel* label);
// TODO(mythria) The following two functions should be merged into
// JumpIfTrue/False. These bytecodes should be automatically chosen rather
// than explicitly using them.
......@@ -189,6 +192,11 @@ class BytecodeArrayBuilder {
BytecodeArrayBuilder& Throw();
BytecodeArrayBuilder& Return();
// Complex flow control.
BytecodeArrayBuilder& ForInPrepare(Register receiver);
BytecodeArrayBuilder& ForInNext(Register for_in_state, Register index);
BytecodeArrayBuilder& ForInDone(Register for_in_state);
BytecodeArrayBuilder& EnterBlock();
BytecodeArrayBuilder& LeaveBlock();
......@@ -287,7 +295,7 @@ class BytecodeLabel final {
bound_ = true;
}
INLINE(void set_referrer(size_t offset)) {
DCHECK(!bound_ && offset != kInvalidOffset);
DCHECK(!bound_ && offset != kInvalidOffset && offset_ == kInvalidOffset);
offset_ = offset;
}
INLINE(size_t offset() const) { return offset_; }
......
......@@ -613,8 +613,117 @@ void BytecodeGenerator::VisitForStatement(ForStatement* stmt) {
}
void BytecodeGenerator::VisitForInAssignment(Expression* expr,
FeedbackVectorSlot slot) {
DCHECK(expr->IsValidReferenceExpression());
// Evaluate assignment starting with the value to be stored in the
// accumulator.
Property* property = expr->AsProperty();
LhsKind assign_type = Property::GetAssignType(property);
switch (assign_type) {
case VARIABLE: {
Variable* variable = expr->AsVariableProxy()->var();
VisitVariableAssignment(variable, slot);
break;
}
case NAMED_PROPERTY: {
TemporaryRegisterScope temporary_register_scope(builder());
Register value = temporary_register_scope.NewRegister();
builder()->StoreAccumulatorInRegister(value);
Register object = VisitForRegisterValue(property->obj());
size_t name_index = builder()->GetConstantPoolEntry(
property->key()->AsLiteral()->AsPropertyName());
builder()->StoreNamedProperty(object, name_index, feedback_index(slot),
language_mode());
break;
}
case KEYED_PROPERTY: {
TemporaryRegisterScope temporary_register_scope(builder());
Register value = temporary_register_scope.NewRegister();
builder()->StoreAccumulatorInRegister(value);
Register object = VisitForRegisterValue(property->obj());
Register key = VisitForRegisterValue(property->key());
builder()->LoadAccumulatorWithRegister(value);
builder()->StoreKeyedProperty(object, key, feedback_index(slot),
language_mode());
break;
}
case NAMED_SUPER_PROPERTY:
case KEYED_SUPER_PROPERTY:
UNIMPLEMENTED();
}
}
void BytecodeGenerator::VisitForInStatement(ForInStatement* stmt) {
UNIMPLEMENTED();
// TODO(oth): For now we need a parent scope for paths that end up
// in VisitLiteral which can allocate in the parent scope. A future
// CL in preparation will add a StatementResultScope that will
// remove the need for this EffectResultScope.
EffectResultScope result_scope(this);
if (stmt->subject()->IsNullLiteral() ||
stmt->subject()->IsUndefinedLiteral(isolate())) {
// ForIn generates lots of code, skip if it wouldn't produce any effects.
return;
}
LoopBuilder loop_builder(builder());
ControlScopeForIteration control_scope(this, stmt, &loop_builder);
// Prepare the state for executing ForIn.
builder()->EnterBlock();
VisitForAccumulatorValue(stmt->subject());
loop_builder.BreakIfUndefined();
loop_builder.BreakIfNull();
Register receiver = execution_result()->NewRegister();
builder()->CastAccumulatorToJSObject();
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);
// The loop.
BytecodeLabel condition_label, break_label, continue_label;
Register index = receiver; // Re-using register as receiver no longer used.
builder()->LoadLiteral(Smi::FromInt(0));
// Check loop termination (accumulator holds index).
builder()
->Bind(&condition_label)
.StoreAccumulatorInRegister(index)
.ForInDone(for_in_state);
loop_builder.BreakIfTrue();
// Get the next item.
builder()->ForInNext(for_in_state, index);
// Start again if the item, currently in the accumulator, is undefined.
loop_builder.ContinueIfUndefined();
// Store the value in the each variable.
VisitForInAssignment(stmt->each(), stmt->EachFeedbackSlot());
// NB the user's loop variable will be assigned the value of each so
// even an empty body will have this assignment.
Visit(stmt->body());
// Increment the index and start loop again.
builder()
->Bind(&continue_label)
.LoadAccumulatorWithRegister(index)
.CountOperation(Token::Value::ADD, language_mode_strength())
.Jump(&condition_label);
// End of loop
builder()->Bind(&break_label).LeaveBlock();
loop_builder.SetBreakTarget(break_label);
loop_builder.SetContinueTarget(continue_label);
}
......
......@@ -78,6 +78,7 @@ class BytecodeGenerator : public AstVisitor {
void VisitObjectLiteralAccessor(Register home_object,
ObjectLiteralProperty* property,
Register value_out);
void VisitForInAssignment(Expression* expr, FeedbackVectorSlot slot);
// Visitors for obtaining expression result in the accumulator, in a
......
......@@ -163,7 +163,9 @@ bool Bytecodes::IsJump(Bytecode bytecode) {
return bytecode == Bytecode::kJump || bytecode == Bytecode::kJumpIfTrue ||
bytecode == Bytecode::kJumpIfFalse ||
bytecode == Bytecode::kJumpIfToBooleanTrue ||
bytecode == Bytecode::kJumpIfToBooleanFalse;
bytecode == Bytecode::kJumpIfToBooleanFalse ||
bytecode == Bytecode::kJumpIfNull ||
bytecode == Bytecode::kJumpIfUndefined;
}
......@@ -173,7 +175,9 @@ bool Bytecodes::IsJumpConstant(Bytecode bytecode) {
bytecode == Bytecode::kJumpIfTrueConstant ||
bytecode == Bytecode::kJumpIfFalseConstant ||
bytecode == Bytecode::kJumpIfToBooleanTrueConstant ||
bytecode == Bytecode::kJumpIfToBooleanFalseConstant;
bytecode == Bytecode::kJumpIfToBooleanFalseConstant ||
bytecode == Bytecode::kJumpIfNull ||
bytecode == Bytecode::kJumpIfUndefinedConstant;
}
......
......@@ -118,6 +118,7 @@ namespace interpreter {
V(ToBoolean, OperandType::kNone) \
V(ToName, OperandType::kNone) \
V(ToNumber, OperandType::kNone) \
V(ToObject, OperandType::kNone) \
\
/* Literals */ \
V(CreateRegExpLiteral, OperandType::kIdx8, OperandType::kReg8) \
......@@ -142,6 +143,17 @@ namespace interpreter {
V(JumpIfToBooleanTrueConstant, OperandType::kIdx8) \
V(JumpIfToBooleanFalse, OperandType::kImm8) \
V(JumpIfToBooleanFalseConstant, OperandType::kIdx8) \
V(JumpIfNull, OperandType::kImm8) \
V(JumpIfNullConstant, OperandType::kIdx8) \
V(JumpIfUndefined, OperandType::kImm8) \
V(JumpIfUndefinedConstant, OperandType::kIdx8) \
\
/* Complex flow control For..in */ \
V(ForInPrepare, OperandType::kReg8) \
V(ForInNext, OperandType::kReg8, OperandType::kReg8) \
V(ForInDone, OperandType::kReg8) \
\
/* Non-local flow control */ \
V(Throw, OperandType::kNone) \
V(Return, OperandType::kNone)
......
......@@ -31,6 +31,24 @@ void LoopBuilder::EmitJump(ZoneVector<BytecodeLabel>* sites) {
}
void LoopBuilder::EmitJumpIfTrue(ZoneVector<BytecodeLabel>* sites) {
sites->push_back(BytecodeLabel());
builder()->JumpIfTrue(&sites->back());
}
void LoopBuilder::EmitJumpIfUndefined(ZoneVector<BytecodeLabel>* sites) {
sites->push_back(BytecodeLabel());
builder()->JumpIfUndefined(&sites->back());
}
void LoopBuilder::EmitJumpIfNull(ZoneVector<BytecodeLabel>* sites) {
sites->push_back(BytecodeLabel());
builder()->JumpIfNull(&sites->back());
}
void LoopBuilder::BindLabels(const BytecodeLabel& target,
ZoneVector<BytecodeLabel>* sites) {
for (size_t i = 0; i < sites->size(); i++) {
......
......@@ -49,11 +49,20 @@ class LoopBuilder : public ControlFlowBuilder {
// patched when the corresponding SetContinueTarget/SetBreakTarget
// is called.
void Break() { EmitJump(&break_sites_); }
void BreakIfTrue() { EmitJumpIfTrue(&break_sites_); }
void BreakIfUndefined() { EmitJumpIfUndefined(&break_sites_); }
void BreakIfNull() { EmitJumpIfNull(&break_sites_); }
void Continue() { EmitJump(&continue_sites_); }
void ContinueIfTrue() { EmitJumpIfTrue(&continue_sites_); }
void ContinueIfUndefined() { EmitJumpIfUndefined(&continue_sites_); }
void ContinueIfNull() { EmitJumpIfNull(&continue_sites_); }
private:
void BindLabels(const BytecodeLabel& target, ZoneVector<BytecodeLabel>* site);
void EmitJump(ZoneVector<BytecodeLabel>* labels);
void EmitJumpIfTrue(ZoneVector<BytecodeLabel>* labels);
void EmitJumpIfUndefined(ZoneVector<BytecodeLabel>* labels);
void EmitJumpIfNull(ZoneVector<BytecodeLabel>* labels);
// Unbound labels that identify jumps for continue/break statements
// in the code.
......
......@@ -847,6 +847,17 @@ void Interpreter::DoToNumber(compiler::InterpreterAssembler* assembler) {
}
// ToObject
//
// Cast the object referenced by the accumulator to a JSObject.
void Interpreter::DoToObject(compiler::InterpreterAssembler* assembler) {
Node* accumulator = __ GetAccumulator();
Node* result = __ CallRuntime(Runtime::kToObject, accumulator);
__ SetAccumulator(result);
__ Dispatch();
}
// Jump <imm8>
//
// Jump by number of bytes represented by the immediate operand |imm8|.
......@@ -987,6 +998,62 @@ void Interpreter::DoJumpIfToBooleanFalseConstant(
}
// JumpIfNull <imm8>
//
// Jump by number of bytes represented by an immediate operand if the object
// referenced by the accumulator is the null constant.
void Interpreter::DoJumpIfNull(compiler::InterpreterAssembler* assembler) {
Node* accumulator = __ GetAccumulator();
Node* null_value = __ HeapConstant(isolate_->factory()->null_value());
Node* relative_jump = __ BytecodeOperandImm8(0);
__ JumpIfWordEqual(accumulator, null_value, relative_jump);
}
// JumpIfNullConstant <idx>
//
// Jump by number of bytes in the Smi in the |idx| entry in the constant pool
// if the object referenced by the accumulator is the null constant.
void Interpreter::DoJumpIfNullConstant(
compiler::InterpreterAssembler* assembler) {
Node* accumulator = __ GetAccumulator();
Node* null_value = __ HeapConstant(isolate_->factory()->null_value());
Node* index = __ BytecodeOperandIdx8(0);
Node* constant = __ LoadConstantPoolEntry(index);
Node* relative_jump = __ SmiUntag(constant);
__ JumpIfWordEqual(accumulator, null_value, relative_jump);
}
// JumpIfUndefined <imm8>
//
// Jump by number of bytes represented by an immediate operand if the object
// referenced by the accumulator is the undefined constant.
void Interpreter::DoJumpIfUndefined(compiler::InterpreterAssembler* assembler) {
Node* accumulator = __ GetAccumulator();
Node* undefined_value =
__ HeapConstant(isolate_->factory()->undefined_value());
Node* relative_jump = __ BytecodeOperandImm8(0);
__ JumpIfWordEqual(accumulator, undefined_value, relative_jump);
}
// JumpIfUndefinedConstant <idx>
//
// Jump by number of bytes in the Smi in the |idx| entry in the constant pool
// if the object referenced by the accumulator is the undefined constant.
void Interpreter::DoJumpIfUndefinedConstant(
compiler::InterpreterAssembler* assembler) {
Node* accumulator = __ GetAccumulator();
Node* undefined_value =
__ HeapConstant(isolate_->factory()->undefined_value());
Node* index = __ BytecodeOperandIdx8(0);
Node* constant = __ LoadConstantPoolEntry(index);
Node* relative_jump = __ SmiUntag(constant);
__ JumpIfWordEqual(accumulator, undefined_value, relative_jump);
}
// CreateRegExpLiteral <idx> <flags_reg>
//
// Creates a regular expression literal for literal index <idx> with flags held
......@@ -1105,6 +1172,57 @@ void Interpreter::DoReturn(compiler::InterpreterAssembler* assembler) {
}
// ForInPrepare <receiver>
//
// Returns state for for..in loop execution based on the |receiver| and
// the property names in the accumulator.
void Interpreter::DoForInPrepare(compiler::InterpreterAssembler* assembler) {
Node* receiver_reg = __ BytecodeOperandReg8(0);
Node* receiver = __ LoadRegister(receiver_reg);
Node* property_names = __ GetAccumulator();
Node* result = __ CallRuntime(Runtime::kInterpreterForInPrepare, receiver,
property_names);
__ SetAccumulator(result);
__ Dispatch();
}
// ForInNext <for_in_state> <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.
void Interpreter::DoForInNext(compiler::InterpreterAssembler* assembler) {
Node* for_in_state_reg = __ BytecodeOperandReg8(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 = __ BytecodeOperandReg8(1);
Node* index = __ LoadRegister(index_reg);
Node* result = __ CallRuntime(Runtime::kForInNext, receiver, cache_array,
cache_type, index);
__ SetAccumulator(result);
__ Dispatch();
}
// ForInDone <for_in_state>
//
// 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.
void Interpreter::DoForInDone(compiler::InterpreterAssembler* assembler) {
Node* index = __ GetAccumulator();
Node* for_in_state_reg = __ BytecodeOperandReg8(0);
Node* for_in_state = __ LoadRegister(for_in_state_reg);
Node* cache_length = __ LoadFixedArrayElement(for_in_state, 3);
Node* result = __ CallRuntime(Runtime::kForInDone, index, cache_length);
__ SetAccumulator(result);
__ Dispatch();
}
} // namespace interpreter
} // namespace internal
} // namespace v8
......@@ -147,5 +147,53 @@ RUNTIME_FUNCTION(Runtime_InterpreterNewClosure) {
shared, context, static_cast<PretenureFlag>(pretenured_flag));
}
RUNTIME_FUNCTION(Runtime_InterpreterForInPrepare) {
HandleScope scope(isolate);
DCHECK_EQ(2, 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);
Handle<FixedArray> cache_array;
int cache_length;
if (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()) {
cache_array = handle(descriptors->GetEnumCache(), isolate);
cache_length = cache_array->length();
} else {
cache_array = isolate->factory()->empty_fixed_array();
cache_length = 0;
}
} else {
cache_array = Handle<FixedArray>::cast(cache_type);
cache_length = cache_array->length();
STATIC_ASSERT(FIRST_JS_PROXY_TYPE == FIRST_SPEC_OBJECT_TYPE);
if (receiver_map->instance_type() <= LAST_JS_PROXY_TYPE) {
DCHECK_GE(receiver_map->instance_type(), LAST_JS_PROXY_TYPE);
// Zero indicates proxy
cache_type = Handle<Object>(Smi::FromInt(0), isolate);
} else {
// One entails slow check
cache_type = Handle<Object>(Smi::FromInt(1), isolate);
}
}
Handle<FixedArray> result = isolate->factory()->NewFixedArray(4);
result->set(0, *receiver);
result->set(1, *cache_array);
result->set(2, *cache_type);
result->set(3, Smi::FromInt(cache_length));
return *result;
}
} // namespace internal
} // namespace v8
......@@ -228,7 +228,8 @@ namespace internal {
F(InterpreterToBoolean, 1, 1) \
F(InterpreterLogicalNot, 1, 1) \
F(InterpreterTypeOf, 1, 1) \
F(InterpreterNewClosure, 2, 1)
F(InterpreterNewClosure, 2, 1) \
F(InterpreterForInPrepare, 1, 1)
#define FOR_EACH_INTRINSIC_FUNCTION(F) \
......
......@@ -1214,6 +1214,42 @@ TEST(InterpreterConditionalJumps) {
}
TEST(InterpreterConditionalJumps2) {
// TODO(oth): Add tests for all conditional jumps near and far.
HandleAndZoneScope handles;
BytecodeArrayBuilder builder(handles.main_isolate(), handles.main_zone());
builder.set_locals_count(2);
builder.set_context_count(0);
builder.set_parameter_count(0);
Register reg(0), scratch(1);
BytecodeLabel label[2];
BytecodeLabel done, done1;
builder.LoadLiteral(Smi::FromInt(0))
.StoreAccumulatorInRegister(reg)
.LoadFalse()
.JumpIfFalse(&label[0]);
IncrementRegister(builder, reg, 1024, scratch)
.Bind(&label[0])
.LoadTrue()
.JumpIfFalse(&done);
IncrementRegister(builder, reg, 1, scratch).LoadTrue().JumpIfTrue(&label[1]);
IncrementRegister(builder, reg, 2048, scratch).Bind(&label[1]);
IncrementRegister(builder, reg, 2, scratch).LoadFalse().JumpIfTrue(&done1);
IncrementRegister(builder, reg, 4, scratch)
.LoadAccumulatorWithRegister(reg)
.Bind(&done)
.Bind(&done1)
.Return();
Handle<BytecodeArray> bytecode_array = builder.ToBytecodeArray();
InterpreterTester tester(handles.main_isolate(), bytecode_array);
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK_EQ(Smi::cast(*return_value)->value(), 7);
}
static const Token::Value kComparisonTypes[] = {
Token::Value::EQ, Token::Value::NE, Token::Value::EQ_STRICT,
Token::Value::NE_STRICT, Token::Value::LTE, Token::Value::LTE,
......@@ -2401,27 +2437,26 @@ TEST(InterpreterDeleteSloppyUnqualifiedIdentifier) {
// These tests generate a syntax error for strict mode. We don't
// test for it here.
std::pair<const char*, Handle<Object>> test_delete[] = {
std::make_pair("var a = { x:10, y:'abc'};\n"
"var b = delete a;\n"
"if (delete a) {\n"
std::make_pair("var sloppy_a = { x:10, y:'abc'};\n"
"var sloppy_b = delete sloppy_a;\n"
"if (delete sloppy_a) {\n"
" return undefined;\n"
"} else {\n"
" return a.x;\n"
" return sloppy_a.x;\n"
"}\n",
Handle<Object>(Smi::FromInt(10), isolate)),
// TODO(mythria) When try-catch is implemented change the tests to check
// if delete actually deletes
std::make_pair("a = { x:10, y:'abc'};\n"
"var b = delete a;\n"
std::make_pair("sloppy_a = { x:10, y:'abc'};\n"
"var sloppy_b = delete sloppy_a;\n"
// "try{return a.x;} catch(e) {return b;}\n"
"return b;",
"return sloppy_b;",
factory->ToBoolean(true)),
std::make_pair("a = { x:10, y:'abc'};\n"
"var b = delete c;\n"
"return b;",
std::make_pair("sloppy_a = { x:10, y:'abc'};\n"
"var sloppy_b = delete sloppy_c;\n"
"return sloppy_b;",
factory->ToBoolean(true))};
for (size_t i = 0; i < arraysize(test_delete); i++) {
std::string source(InterpreterTester::SourceForBody(test_delete[i].first));
InterpreterTester tester(handles.main_isolate(), source.c_str());
......@@ -2498,3 +2533,194 @@ TEST(InterpreterGlobalDelete) {
CHECK(return_value->SameValue(*test_global_delete[i].second));
}
}
TEST(InterpreterForIn) {
HandleAndZoneScope handles;
// TODO(oth): Add a test here for delete mid-loop when delete is ready.
std::pair<const char*, int> for_in_samples[] = {
{"function f() {\n"
" var r = -1;\n"
" for (var a in null) { r = a; }\n"
" return r;\n"
"}",
-1},
{"function f() {\n"
" var r = -1;\n"
" for (var a in undefined) { r = a; }\n"
" return r;\n"
"}",
-1},
{"function f() {\n"
" var r = 0;\n"
" for (var a in [0,6,7,9]) { r = r + (1 << a); }\n"
" return r;\n"
"}",
0xf},
{"function f() {\n"
" var r = 0;\n"
" for (var a in [0,6,7,9]) { r = r + (1 << a); }\n"
" var r = 0;\n"
" for (var a in [0,6,7,9]) { r = r + (1 << a); }\n"
" return r;\n"
"}",
0xf},
{"function f() {\n"
" var r = 0;\n"
" for (var a in 'foobar') { r = r + (1 << a); }\n"
" return r;\n"
"}",
0x3f},
{"function f() {\n"
" var r = 0;\n"
" for (var a in {1:0, 10:1, 100:2, 1000:3}) {\n"
" r = r + Number(a);\n"
" }\n"
" return r;\n"
"}",
1111},
{"function f() {\n"
" var r = 0;\n"
" var data = {1:0, 10:1, 100:2, 1000:3};\n"
" for (var a in data) {\n"
" if (a == 1) delete data[1];\n"
" r = r + Number(a);\n"
" }\n"
" return r;\n"
"}",
1111},
{"function f() {\n"
" var r = 0;\n"
" var data = {1:0, 10:1, 100:2, 1000:3};\n"
" for (var a in data) {\n"
" if (a == 10) delete data[100];\n"
" r = r + Number(a);\n"
" }\n"
" return r;\n"
"}",
1011},
{"function f() {\n"
" var r = 0;\n"
" var data = {1:0, 10:1, 100:2, 1000:3};\n"
" for (var a in data) {\n"
" if (a == 10) data[10000] = 4;\n"
" r = r + Number(a);\n"
" }\n"
" return r;\n"
"}",
1111},
{"function f() {\n"
" var r = 0;\n"
" var input = 'foobar';\n"
" for (var a in input) {\n"
" if (input[a] == 'b') break;\n"
" r = r + (1 << a);\n"
" }\n"
" return r;\n"
"}",
0x7},
{"function f() {\n"
"var r = 0;\n"
"var input = 'foobar';\n"
"for (var a in input) {\n"
" if (input[a] == 'b') continue;\n"
" r = r + (1 << a);\n"
"}\n"
"return r;\n"
"}",
0x37},
{"function f() {\n"
" var r = 0;\n"
" var data = {1:0, 10:1, 100:2, 1000:3};\n"
" for (var a in data) {\n"
" if (a == 10) {\n"
" data[10000] = 4;\n"
" }\n"
" r = r + Number(a);\n"
" }\n"
" return r;\n"
"}",
1111},
{"function f() {\n"
" var r = [ 3 ];\n"
" var data = {1:0, 10:1, 100:2, 1000:3};\n"
" for (r[10] in data) {\n"
" }\n"
" return Number(r[10]);\n"
"}",
1000},
{"function f() {\n"
" var r = [ 3 ];\n"
" var data = {1:0, 10:1, 100:2, 1000:3};\n"
" for (r['100'] in data) {\n"
" }\n"
" return Number(r['100']);\n"
"}",
1000},
{"function f() {\n"
" var obj = {}\n"
" var descObj = new Boolean(false);\n"
" var accessed = 0;\n"
" descObj.enumerable = true;\n"
" Object.defineProperties(obj, { prop:descObj });\n"
" for (var p in obj) {\n"
" if (p === 'prop') { accessed = 1; }\n"
" }\n"
" return accessed;"
"}",
1},
{"function f() {\n"
" var appointment = {};\n"
" Object.defineProperty(appointment, 'startTime', {\n"
" value: 1001,\n"
" writable: false,\n"
" enumerable: false,\n"
" configurable: true\n"
" });\n"
" Object.defineProperty(appointment, 'name', {\n"
" value: 'NAME',\n"
" writable: false,\n"
" enumerable: false,\n"
" configurable: true\n"
" });\n"
" var meeting = Object.create(appointment);\n"
" Object.defineProperty(meeting, 'conferenceCall', {\n"
" value: 'In-person meeting',\n"
" writable: false,\n"
" enumerable: false,\n"
" configurable: true\n"
" });\n"
"\n"
" var teamMeeting = Object.create(meeting);\n"
"\n"
" var flags = 0;\n"
" for (var p in teamMeeting) {\n"
" if (p === 'startTime') {\n"
" flags |= 1;\n"
" }\n"
" if (p === 'name') {\n"
" flags |= 2;\n"
" }\n"
" if (p === 'conferenceCall') {\n"
" flags |= 4;\n"
" }\n"
" }\n"
"\n"
" var hasOwnProperty = !teamMeeting.hasOwnProperty('name') &&\n"
" !teamMeeting.hasOwnProperty('startTime') &&\n"
" !teamMeeting.hasOwnProperty('conferenceCall');\n"
" if (!hasOwnProperty) {\n"
" flags |= 8;\n"
" }\n"
" return flags;\n"
" }",
0}};
for (size_t i = 0; i < arraysize(for_in_samples); i++) {
InterpreterTester tester(handles.main_isolate(), for_in_samples[i].first);
auto callable = tester.GetCallable<>();
Handle<Object> return_val = callable().ToHandleChecked();
CHECK_EQ(Handle<Smi>::cast(return_val)->value(), for_in_samples[i].second);
}
}
......@@ -471,6 +471,36 @@ TARGET_TEST_F(InterpreterAssemblerTest, LoadConstantPoolEntry) {
}
TARGET_TEST_F(InterpreterAssemblerTest, LoadFixedArrayElement) {
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
InterpreterAssemblerForTest m(this, bytecode);
int index = 3;
Node* fixed_array = m.IntPtrConstant(0xdeadbeef);
Node* load_element = m.LoadFixedArrayElement(fixed_array, index);
EXPECT_THAT(
load_element,
m.IsLoad(kMachAnyTagged, fixed_array,
IsIntPtrAdd(
IsIntPtrConstant(FixedArray::kHeaderSize - kHeapObjectTag),
IsWordShl(IsInt32Constant(index),
IsInt32Constant(kPointerSizeLog2)))));
}
}
TARGET_TEST_F(InterpreterAssemblerTest, LoadObjectField) {
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
InterpreterAssemblerForTest m(this, bytecode);
Node* object = m.IntPtrConstant(0xdeadbeef);
int offset = 16;
Node* load_field = m.LoadObjectField(object, offset);
EXPECT_THAT(load_field,
m.IsLoad(kMachAnyTagged, object,
IsIntPtrConstant(offset - kHeapObjectTag)));
}
}
TARGET_TEST_F(InterpreterAssemblerTest, LoadContextSlot) {
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
InterpreterAssemblerForTest m(this, bytecode);
......@@ -505,19 +535,6 @@ TARGET_TEST_F(InterpreterAssemblerTest, StoreContextSlot) {
}
TARGET_TEST_F(InterpreterAssemblerTest, LoadObjectField) {
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
InterpreterAssemblerForTest m(this, bytecode);
Node* object = m.IntPtrConstant(0xdeadbeef);
int offset = 16;
Node* load_field = m.LoadObjectField(object, offset);
EXPECT_THAT(load_field,
m.IsLoad(kMachAnyTagged, object,
IsIntPtrConstant(offset - kHeapObjectTag)));
}
}
TARGET_TEST_F(InterpreterAssemblerTest, CallRuntime2) {
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
InterpreterAssemblerForTest m(this, bytecode);
......
......@@ -126,6 +126,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
// Emit cast operator invocations.
builder.CastAccumulatorToNumber()
.CastAccumulatorToBoolean()
.CastAccumulatorToJSObject()
.CastAccumulatorToName();
// Emit control flow. Return must be the last instruction.
......@@ -136,7 +137,10 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.JumpIfTrue(&start)
.JumpIfFalse(&start)
.JumpIfToBooleanTrue(&start)
.JumpIfToBooleanFalse(&start);
.JumpIfToBooleanFalse(&start)
.JumpIfNull(&start)
.JumpIfUndefined(&start);
// Insert dummy ops to force longer jumps
for (int i = 0; i < 128; i++) {
builder.LoadTrue();
......@@ -146,12 +150,16 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.JumpIfTrue(&start)
.JumpIfFalse(&start)
.JumpIfToBooleanTrue(&start)
.JumpIfToBooleanFalse(&start);
.JumpIfToBooleanFalse(&start)
.JumpIfNull(&start)
.JumpIfUndefined(&start);
builder.EnterBlock()
.Throw()
.LeaveBlock();
builder.ForInPrepare(reg).ForInDone(reg).ForInNext(reg, reg);
builder.Return();
// Generate BytecodeArray.
......
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