[Interpreter] Adds delete operator to interpreter.

Adds support for delete operator, it's implementation and tests.

Adds tests for the following unary operators
  -BitwiseNot
  -Add
  -Sub

BUG=v8:4280
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#31620}
This commit is contained in:
mythria 2015-10-28 02:49:20 -07:00 committed by Commit bot
parent d1f773026c
commit 95e26ec423
11 changed files with 530 additions and 2 deletions

View File

@ -512,6 +512,18 @@ void BytecodeGraphBuilder::VisitTypeOf(
}
void BytecodeGraphBuilder::VisitDeletePropertyStrict(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitDeletePropertySloppy(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitTestEqual(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();

View File

@ -696,6 +696,13 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CallRuntime(
}
BytecodeArrayBuilder& BytecodeArrayBuilder::Delete(Register object,
LanguageMode language_mode) {
Output(BytecodeForDelete(language_mode), object.ToOperand());
return *this;
}
size_t BytecodeArrayBuilder::GetConstantPoolEntry(Handle<Object> object) {
// These constants shouldn't be added to the constant pool, the should use
// specialzed bytecodes instead.
@ -1015,6 +1022,22 @@ Bytecode BytecodeArrayBuilder::BytecodeForCreateArguments(
}
// static
Bytecode BytecodeArrayBuilder::BytecodeForDelete(LanguageMode language_mode) {
switch (language_mode) {
case SLOPPY:
return Bytecode::kDeletePropertySloppy;
case STRICT:
return Bytecode::kDeletePropertyStrict;
case STRONG:
UNIMPLEMENTED();
default:
UNREACHABLE();
}
return static_cast<Bytecode>(-1);
}
// static
bool BytecodeArrayBuilder::FitsInIdx8Operand(int value) {
return kMinUInt8 <= value && value <= kMaxUInt8;

View File

@ -160,6 +160,10 @@ class BytecodeArrayBuilder {
BytecodeArrayBuilder& LogicalNot();
BytecodeArrayBuilder& TypeOf();
// Deletes property from an object. This expects that accumulator contains
// the key to be deleted and the register contains a reference to the object.
BytecodeArrayBuilder& Delete(Register object, LanguageMode language_mode);
// Tests.
BytecodeArrayBuilder& CompareOperation(Token::Value op, Register reg,
Strength strength);
@ -206,6 +210,7 @@ class BytecodeArrayBuilder {
static Bytecode BytecodeForLoadGlobal(LanguageMode language_mode);
static Bytecode BytecodeForStoreGlobal(LanguageMode language_mode);
static Bytecode BytecodeForCreateArguments(CreateArgumentsType type);
static Bytecode BytecodeForDelete(LanguageMode language_mode);
static bool FitsInIdx8Operand(int value);
static bool FitsInIdx8Operand(size_t value);

View File

@ -1429,15 +1429,76 @@ void BytecodeGenerator::VisitUnaryOperation(UnaryOperation* expr) {
case Token::Value::VOID:
VisitVoid(expr);
break;
case Token::Value::BIT_NOT:
case Token::Value::DELETE:
UNIMPLEMENTED();
VisitDelete(expr);
break;
case Token::Value::BIT_NOT:
case Token::Value::ADD:
case Token::Value::SUB:
// These operators are converted to an equivalent binary operators in
// the parser. These operators are not expected to be visited here.
UNREACHABLE();
default:
UNREACHABLE();
}
}
void BytecodeGenerator::VisitDelete(UnaryOperation* expr) {
if (expr->expression()->IsProperty()) {
// Delete of an object property is allowed both in sloppy
// and strict modes.
Property* property = expr->expression()->AsProperty();
Register object = VisitForRegisterValue(property->obj());
VisitForAccumulatorValue(property->key());
builder()->Delete(object, language_mode());
} else if (expr->expression()->IsVariableProxy()) {
// Delete of an unqualified identifier is allowed in sloppy mode but is
// not allowed in strict mode. Deleting 'this' is allowed in both modes.
VariableProxy* proxy = expr->expression()->AsVariableProxy();
Variable* variable = proxy->var();
DCHECK(is_sloppy(language_mode()) || variable->HasThisName(isolate()));
switch (variable->location()) {
case VariableLocation::GLOBAL:
case VariableLocation::UNALLOCATED: {
// Global var, let, const or variables not explicitly declared.
Register global_object = execution_result()->NewRegister();
builder()
->LoadContextSlot(execution_context()->reg(),
Context::GLOBAL_OBJECT_INDEX)
.StoreAccumulatorInRegister(global_object)
.LoadLiteral(variable->name())
.Delete(global_object, language_mode());
break;
}
case VariableLocation::PARAMETER:
case VariableLocation::LOCAL:
case VariableLocation::CONTEXT: {
// Deleting local var/let/const, context variables, and arguments
// does not have any effect.
if (variable->HasThisName(isolate())) {
builder()->LoadTrue();
} else {
builder()->LoadFalse();
}
break;
}
case VariableLocation::LOOKUP: {
UNIMPLEMENTED();
break;
}
default:
UNREACHABLE();
}
} else {
// Delete of an unresolvable reference returns true.
VisitForEffect(expr->expression());
builder()->LoadTrue();
}
execution_result()->SetResultInAccumulator();
}
void BytecodeGenerator::VisitCountOperation(CountOperation* expr) {
DCHECK(expr->expression()->IsValidReferenceExpressionOrThis());

View File

@ -51,6 +51,7 @@ class BytecodeGenerator : public AstVisitor {
void VisitVoid(UnaryOperation* expr);
void VisitTypeOf(UnaryOperation* expr);
void VisitNot(UnaryOperation* expr);
void VisitDelete(UnaryOperation* expr);
// Helper visitors which perform common operations.
Register VisitArguments(ZoneList<Expression*>* arguments);

View File

@ -91,6 +91,8 @@ namespace interpreter {
V(Dec, OperandType::kNone) \
V(LogicalNot, OperandType::kNone) \
V(TypeOf, OperandType::kNone) \
V(DeletePropertyStrict, OperandType::kReg8) \
V(DeletePropertySloppy, OperandType::kReg8) \
\
/* Call operations */ \
V(Call, OperandType::kReg8, OperandType::kReg8, OperandType::kCount8) \

View File

@ -644,6 +644,37 @@ void Interpreter::DoTypeOf(compiler::InterpreterAssembler* assembler) {
}
void Interpreter::DoDelete(Runtime::FunctionId function_id,
compiler::InterpreterAssembler* assembler) {
Node* reg_index = __ BytecodeOperandReg8(0);
Node* object = __ LoadRegister(reg_index);
Node* key = __ GetAccumulator();
Node* result = __ CallRuntime(function_id, object, key);
__ SetAccumulator(result);
__ Dispatch();
}
// DeletePropertyStrict
//
// Delete the property specified in the accumulator from the object
// referenced by the register operand following strict mode semantics.
void Interpreter::DoDeletePropertyStrict(
compiler::InterpreterAssembler* assembler) {
DoDelete(Runtime::kDeleteProperty_Strict, assembler);
}
// DeletePropertySloppy
//
// Delete the property specified in the accumulator from the object
// referenced by the register operand following sloppy mode semantics.
void Interpreter::DoDeletePropertySloppy(
compiler::InterpreterAssembler* assembler) {
DoDelete(Runtime::kDeleteProperty_Sloppy, assembler);
}
// Call <callable> <receiver> <arg_count>
//
// Call a JSfunction or Callable in |callable| with the |receiver| and

View File

@ -85,6 +85,10 @@ class Interpreter {
void DoCreateLiteral(Runtime::FunctionId function_id,
compiler::InterpreterAssembler* assembler);
// Generates code to perform delete via function_id.
void DoDelete(Runtime::FunctionId function_id,
compiler::InterpreterAssembler* assembler);
bool IsInterpreterTableInitialized(Handle<FixedArray> handler_table);
Isolate* isolate_;

View File

@ -1842,6 +1842,153 @@ TEST(UnaryOperators) {
B(Return), //
},
0},
{"var x = 13;"
"return ~x;",
1 * kPointerSize,
1,
9,
{
B(LdaSmi8), U8(13), //
B(Star), R(0), //
B(LdaSmi8), U8(-1), //
B(BitwiseXor), R(0), //
B(Return), //
},
0},
{"var x = 13;"
"return +x;",
1 * kPointerSize,
1,
9,
{
B(LdaSmi8), U8(13), //
B(Star), R(0), //
B(LdaSmi8), U8(1), //
B(Mul), R(0), //
B(Return), //
},
0},
{"var x = 13;"
"return -x;",
1 * kPointerSize,
1,
9,
{
B(LdaSmi8), U8(13), //
B(Star), R(0), //
B(LdaSmi8), U8(-1), //
B(Mul), R(0), //
B(Return), //
},
0}};
for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array =
helper.MakeBytecodeForFunctionBody(snippets[i].code_snippet);
CheckBytecodeArrayEqual(snippets[i], bytecode_array);
}
}
TEST(Delete) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
int deep_elements_flags =
ObjectLiteral::kFastElements | ObjectLiteral::kDisableMementos;
int closure = Register::function_closure().index();
int first_context_slot = Context::MIN_CONTEXT_SLOTS;
ExpectedSnippet<InstanceType> snippets[] = {
{"var a = {x:13, y:14}; return delete a.x;",
1 * kPointerSize,
1,
12,
{
B(LdaConstant), U8(0), //
B(CreateObjectLiteral), U8(0), U8(deep_elements_flags), //
B(Star), R(0), //
B(LdaConstant), U8(1), //
B(DeletePropertySloppy), R(0), //
B(Return)
},
2,
{InstanceType::FIXED_ARRAY_TYPE,
InstanceType::ONE_BYTE_INTERNALIZED_STRING_TYPE}},
{"'use strict'; var a = {x:13, y:14}; return delete a.x;",
1 * kPointerSize,
1,
12,
{
B(LdaConstant), U8(0), //
B(CreateObjectLiteral), U8(0), U8(deep_elements_flags), //
B(Star), R(0), //
B(LdaConstant), U8(1), //
B(DeletePropertyStrict), R(0), //
B(Return)
},
2,
{InstanceType::FIXED_ARRAY_TYPE,
InstanceType::ONE_BYTE_INTERNALIZED_STRING_TYPE}},
{"var a = {1:13, 2:14}; return delete a[2];",
1 * kPointerSize,
1,
12,
{
B(LdaConstant), U8(0), //
B(CreateObjectLiteral), U8(0), U8(deep_elements_flags), //
B(Star), R(0), //
B(LdaSmi8), U8(2), //
B(DeletePropertySloppy), R(0), //
B(Return)
},
1,
{InstanceType::FIXED_ARRAY_TYPE}},
{"var a = 10; return delete a;",
1 * kPointerSize,
1,
6,
{
B(LdaSmi8), U8(10), //
B(Star), R(0), //
B(LdaFalse), //
B(Return)
},
0},
{"'use strict';"
"var a = {1:10};"
"(function f1() {return a;});"
"return delete a[1];",
2 * kPointerSize,
1,
29,
{
B(CallRuntime), U16(Runtime::kNewFunctionContext), //
R(closure), U8(1), //
B(PushContext), R(0), //
B(LdaConstant), U8(0), //
B(CreateObjectLiteral), U8(0), U8(deep_elements_flags), //
B(StaContextSlot), R(0), U8(first_context_slot), //
B(LdaConstant), U8(1), //
B(CreateClosure), U8(0), //
B(LdaContextSlot), R(0), U8(first_context_slot), //
B(Star), R(1), //
B(LdaSmi8), U8(1), //
B(DeletePropertyStrict), R(1), //
B(Return)
},
2,
{InstanceType::FIXED_ARRAY_TYPE,
InstanceType::SHARED_FUNCTION_INFO_TYPE}},
{"return delete 'test';",
0 * kPointerSize,
1,
2,
{
B(LdaTrue), //
B(Return)
},
0},
};
for (size_t i = 0; i < arraysize(snippets); i++) {
@ -1852,6 +1999,83 @@ TEST(UnaryOperators) {
}
TEST(GlobalDelete) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
Zone zone;
int context = Register::function_context().index();
int global_object_index = Context::GLOBAL_OBJECT_INDEX;
FeedbackVectorSpec feedback_spec(&zone);
FeedbackVectorSlot slot = feedback_spec.AddLoadICSlot();
Handle<i::TypeFeedbackVector> vector =
i::NewTypeFeedbackVector(helper.isolate(), &feedback_spec);
ExpectedSnippet<InstanceType> snippets[] = {
{"var a = {x:13, y:14};\n function f() { return delete a.x; };\n f();",
1 * kPointerSize,
1,
10,
{
B(LdaGlobalSloppy), U8(0), U8(vector->GetIndex(slot)), //
B(Star), R(0), //
B(LdaConstant), U8(1), //
B(DeletePropertySloppy), R(0), //
B(Return)
},
2,
{InstanceType::ONE_BYTE_INTERNALIZED_STRING_TYPE,
InstanceType::ONE_BYTE_INTERNALIZED_STRING_TYPE}},
{"a = {1:13, 2:14};\n"
"function f() {'use strict'; return delete a[1];};\n f();",
1 * kPointerSize,
1,
10,
{
B(LdaGlobalStrict), U8(0), U8(vector->GetIndex(slot)), //
B(Star), R(0), //
B(LdaSmi8), U8(1), //
B(DeletePropertyStrict), R(0), //
B(Return)
},
1,
{InstanceType::ONE_BYTE_INTERNALIZED_STRING_TYPE}},
{"var a = {x:13, y:14};\n function f() { return delete a; };\n f();",
1 * kPointerSize,
1,
10,
{
B(LdaContextSlot), R(context), U8(global_object_index), //
B(Star), R(0), //
B(LdaConstant), U8(0), //
B(DeletePropertySloppy), R(0), //
B(Return)
},
1,
{InstanceType::ONE_BYTE_INTERNALIZED_STRING_TYPE}},
{"b = 30;\n function f() { return delete b; };\n f();",
1 * kPointerSize,
1,
10,
{
B(LdaContextSlot), R(context), U8(global_object_index), //
B(Star), R(0), //
B(LdaConstant), U8(0), //
B(DeletePropertySloppy), R(0), //
B(Return)
},
1,
{InstanceType::ONE_BYTE_INTERNALIZED_STRING_TYPE}}};
for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array =
helper.MakeBytecode(snippets[i].code_snippet, "f");
CheckBytecodeArrayEqual(snippets[i], bytecode_array);
}
}
TEST(FunctionLiterals) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;

View File

@ -2335,3 +2335,166 @@ TEST(InterpreterConditional) {
CHECK(return_value->SameValue(*conditional[i].second));
}
}
TEST(InterpreterDelete) {
HandleAndZoneScope handles;
i::Isolate* isolate = handles.main_isolate();
i::Factory* factory = isolate->factory();
// Tests for delete for local variables that work both in strict
// and sloppy modes
std::pair<const char*, Handle<Object>> test_delete[] = {
std::make_pair(
"var a = { x:10, y:'abc', z:30.2}; delete a.x; return a.x;\n",
factory->undefined_value()),
std::make_pair(
"var b = { x:10, y:'abc', z:30.2}; delete b.x; return b.y;\n",
factory->NewStringFromStaticChars("abc")),
std::make_pair("var c = { x:10, y:'abc', z:30.2}; var d = c; delete d.x; "
"return c.x;\n",
factory->undefined_value()),
std::make_pair("var e = { x:10, y:'abc', z:30.2}; var g = e; delete g.x; "
"return e.y;\n",
factory->NewStringFromStaticChars("abc")),
std::make_pair("var a = { x:10, y:'abc', z:30.2};\n"
"var b = a;"
"delete b.x;"
"return b.x;\n",
factory->undefined_value()),
std::make_pair("var a = {1:10};\n"
"(function f1() {return a;});"
"return delete a[1];",
factory->ToBoolean(true)),
std::make_pair("return delete this;", factory->ToBoolean(true)),
std::make_pair("return delete 'test';", factory->ToBoolean(true))};
// Test delete in sloppy mode
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());
auto callable = tester.GetCallable<>();
Handle<i::Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*test_delete[i].second));
}
// Test delete in strict mode
for (size_t i = 0; i < arraysize(test_delete); i++) {
std::string strict_test =
"'use strict'; " + std::string(test_delete[i].first);
std::string source(InterpreterTester::SourceForBody(strict_test.c_str()));
InterpreterTester tester(handles.main_isolate(), source.c_str());
auto callable = tester.GetCallable<>();
Handle<i::Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*test_delete[i].second));
}
}
TEST(InterpreterDeleteSloppyUnqualifiedIdentifier) {
HandleAndZoneScope handles;
i::Isolate* isolate = handles.main_isolate();
i::Factory* factory = isolate->factory();
// 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"
" return undefined;\n"
"} else {\n"
" return 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"
// "try{return a.x;} catch(e) {return b;}\n"
"return b;",
factory->ToBoolean(true)),
std::make_pair("a = { x:10, y:'abc'};\n"
"var b = delete c;\n"
"return 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());
auto callable = tester.GetCallable<>();
Handle<i::Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*test_delete[i].second));
}
}
TEST(InterpreterGlobalDelete) {
HandleAndZoneScope handles;
i::Isolate* isolate = handles.main_isolate();
i::Factory* factory = isolate->factory();
std::pair<const char*, Handle<Object>> test_global_delete[] = {
std::make_pair("var a = { x:10, y:'abc', z:30.2 };\n"
"function f() {\n"
" delete a.x;\n"
" return a.x;\n"
"}\n"
"f();\n",
factory->undefined_value()),
std::make_pair("var b = {1:10, 2:'abc', 3:30.2 };\n"
"function f() {\n"
" delete b[2];\n"
" return b[1];\n"
" }\n"
"f();\n",
Handle<Object>(Smi::FromInt(10), isolate)),
std::make_pair("var c = { x:10, y:'abc', z:30.2 };\n"
"function f() {\n"
" var d = c;\n"
" delete d.y;\n"
" return d.x;\n"
"}\n"
"f();\n",
Handle<Object>(Smi::FromInt(10), isolate)),
std::make_pair("e = { x:10, y:'abc' };\n"
"function f() {\n"
" return delete e;\n"
"}\n"
"f();\n",
factory->ToBoolean(true)),
std::make_pair("var g = { x:10, y:'abc' };\n"
"function f() {\n"
" return delete g;\n"
"}\n"
"f();\n",
factory->ToBoolean(false)),
std::make_pair("function f() {\n"
" var obj = {h:10, f1() {return delete this;}};\n"
" return obj.f1();\n"
"}\n"
"f();",
factory->ToBoolean(true)),
std::make_pair("function f() {\n"
" var obj = {h:10,\n"
" f1() {\n"
" 'use strict';\n"
" return delete this.h;}};\n"
" return obj.f1();\n"
"}\n"
"f();",
factory->ToBoolean(true))};
for (size_t i = 0; i < arraysize(test_global_delete); i++) {
InterpreterTester tester(handles.main_isolate(),
test_global_delete[i].first);
auto callable = tester.GetCallable<>();
Handle<i::Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*test_global_delete[i].second));
}
}

View File

@ -105,6 +105,8 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
// Emit unary operator invocations.
builder.LogicalNot().TypeOf();
// Emit delete
builder.Delete(reg, LanguageMode::SLOPPY).Delete(reg, LanguageMode::STRICT);
// Emit new.
builder.New(reg, reg, 0);