Commit 17363fa4 authored by oth's avatar oth Committed by Commit bot

[Interpreter] Add interpreter support for compare ops and ToBoolean.

The comparison operators and ToBoolean are implemented by calling into
the runtime. There are new runtime methods are prefixed with Interpreter
to make use case clear.

BUG=v8:4280
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#30983}
parent 4d03c3aa
...@@ -1156,6 +1156,7 @@ source_set("v8_base") { ...@@ -1156,6 +1156,7 @@ source_set("v8_base") {
"src/runtime/runtime-generator.cc", "src/runtime/runtime-generator.cc",
"src/runtime/runtime-i18n.cc", "src/runtime/runtime-i18n.cc",
"src/runtime/runtime-internal.cc", "src/runtime/runtime-internal.cc",
"src/runtime/runtime-interpreter.cc",
"src/runtime/runtime-json.cc", "src/runtime/runtime-json.cc",
"src/runtime/runtime-literals.cc", "src/runtime/runtime-literals.cc",
"src/runtime/runtime-liveedit.cc", "src/runtime/runtime-liveedit.cc",
......
...@@ -368,13 +368,13 @@ void BytecodeGraphBuilder::VisitTestGreaterThan( ...@@ -368,13 +368,13 @@ void BytecodeGraphBuilder::VisitTestGreaterThan(
} }
void BytecodeGraphBuilder::VisitTestLessThanEqual( void BytecodeGraphBuilder::VisitTestLessThanOrEqual(
const interpreter::BytecodeArrayIterator& iterator) { const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED(); UNIMPLEMENTED();
} }
void BytecodeGraphBuilder::VisitTestGreaterThanEqual( void BytecodeGraphBuilder::VisitTestGreaterThanOrEqual(
const interpreter::BytecodeArrayIterator& iterator) { const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED(); UNIMPLEMENTED();
} }
......
...@@ -289,9 +289,9 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CastAccumulatorToBoolean() { ...@@ -289,9 +289,9 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CastAccumulatorToBoolean() {
case Bytecode::kTestEqualStrict: case Bytecode::kTestEqualStrict:
case Bytecode::kTestNotEqualStrict: case Bytecode::kTestNotEqualStrict:
case Bytecode::kTestLessThan: case Bytecode::kTestLessThan:
case Bytecode::kTestLessThanEqual: case Bytecode::kTestLessThanOrEqual:
case Bytecode::kTestGreaterThan: case Bytecode::kTestGreaterThan:
case Bytecode::kTestGreaterThanEqual: case Bytecode::kTestGreaterThanOrEqual:
case Bytecode::kTestInstanceOf: case Bytecode::kTestInstanceOf:
case Bytecode::kTestIn: case Bytecode::kTestIn:
break; break;
...@@ -560,9 +560,9 @@ Bytecode BytecodeArrayBuilder::BytecodeForCompareOperation(Token::Value op) { ...@@ -560,9 +560,9 @@ Bytecode BytecodeArrayBuilder::BytecodeForCompareOperation(Token::Value op) {
case Token::Value::GT: case Token::Value::GT:
return Bytecode::kTestGreaterThan; return Bytecode::kTestGreaterThan;
case Token::Value::LTE: case Token::Value::LTE:
return Bytecode::kTestLessThanEqual; return Bytecode::kTestLessThanOrEqual;
case Token::Value::GTE: case Token::Value::GTE:
return Bytecode::kTestGreaterThanEqual; return Bytecode::kTestGreaterThanOrEqual;
case Token::Value::INSTANCEOF: case Token::Value::INSTANCEOF:
return Bytecode::kTestInstanceOf; return Bytecode::kTestInstanceOf;
case Token::Value::IN: case Token::Value::IN:
......
...@@ -68,8 +68,8 @@ namespace interpreter { ...@@ -68,8 +68,8 @@ namespace interpreter {
V(TestNotEqualStrict, OperandType::kReg) \ V(TestNotEqualStrict, OperandType::kReg) \
V(TestLessThan, OperandType::kReg) \ V(TestLessThan, OperandType::kReg) \
V(TestGreaterThan, OperandType::kReg) \ V(TestGreaterThan, OperandType::kReg) \
V(TestLessThanEqual, OperandType::kReg) \ V(TestLessThanOrEqual, OperandType::kReg) \
V(TestGreaterThanEqual, OperandType::kReg) \ V(TestGreaterThanOrEqual, OperandType::kReg) \
V(TestInstanceOf, OperandType::kReg) \ V(TestInstanceOf, OperandType::kReg) \
V(TestIn, OperandType::kReg) \ V(TestIn, OperandType::kReg) \
\ \
......
...@@ -294,15 +294,6 @@ void Interpreter::DoBinaryOp(Runtime::FunctionId function_id, ...@@ -294,15 +294,6 @@ void Interpreter::DoBinaryOp(Runtime::FunctionId function_id,
} }
void Interpreter::DoCompareOp(Token::Value op,
compiler::InterpreterAssembler* assembler) {
// TODO(oth): placeholder until compare path fixed.
// The accumulator should be set to true on success (or false otherwise)
// by the comparisons so it can be used for conditional jumps.
DoLdaTrue(assembler);
}
// Add <src> // Add <src>
// //
// Add register <src> to accumulator. // Add register <src> to accumulator.
...@@ -363,7 +354,7 @@ void Interpreter::DoCall(compiler::InterpreterAssembler* assembler) { ...@@ -363,7 +354,7 @@ void Interpreter::DoCall(compiler::InterpreterAssembler* assembler) {
// //
// Test if the value in the <src> register equals the accumulator. // Test if the value in the <src> register equals the accumulator.
void Interpreter::DoTestEqual(compiler::InterpreterAssembler* assembler) { void Interpreter::DoTestEqual(compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::EQ, assembler); DoBinaryOp(Runtime::kInterpreterEquals, assembler);
} }
...@@ -371,7 +362,7 @@ void Interpreter::DoTestEqual(compiler::InterpreterAssembler* assembler) { ...@@ -371,7 +362,7 @@ void Interpreter::DoTestEqual(compiler::InterpreterAssembler* assembler) {
// //
// Test if the value in the <src> register is not equal to the accumulator. // Test if the value in the <src> register is not equal to the accumulator.
void Interpreter::DoTestNotEqual(compiler::InterpreterAssembler* assembler) { void Interpreter::DoTestNotEqual(compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::NE, assembler); DoBinaryOp(Runtime::kInterpreterNotEquals, assembler);
} }
...@@ -379,7 +370,7 @@ void Interpreter::DoTestNotEqual(compiler::InterpreterAssembler* assembler) { ...@@ -379,7 +370,7 @@ void Interpreter::DoTestNotEqual(compiler::InterpreterAssembler* assembler) {
// //
// Test if the value in the <src> register is strictly equal to the accumulator. // Test if the value in the <src> register is strictly equal to the accumulator.
void Interpreter::DoTestEqualStrict(compiler::InterpreterAssembler* assembler) { void Interpreter::DoTestEqualStrict(compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::EQ_STRICT, assembler); DoBinaryOp(Runtime::kInterpreterStrictEquals, assembler);
} }
...@@ -389,7 +380,7 @@ void Interpreter::DoTestEqualStrict(compiler::InterpreterAssembler* assembler) { ...@@ -389,7 +380,7 @@ void Interpreter::DoTestEqualStrict(compiler::InterpreterAssembler* assembler) {
// accumulator. // accumulator.
void Interpreter::DoTestNotEqualStrict( void Interpreter::DoTestNotEqualStrict(
compiler::InterpreterAssembler* assembler) { compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::NE_STRICT, assembler); DoBinaryOp(Runtime::kInterpreterStrictNotEquals, assembler);
} }
...@@ -397,7 +388,7 @@ void Interpreter::DoTestNotEqualStrict( ...@@ -397,7 +388,7 @@ void Interpreter::DoTestNotEqualStrict(
// //
// Test if the value in the <src> register is less than the accumulator. // Test if the value in the <src> register is less than the accumulator.
void Interpreter::DoTestLessThan(compiler::InterpreterAssembler* assembler) { void Interpreter::DoTestLessThan(compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::LT, assembler); DoBinaryOp(Runtime::kInterpreterLessThan, assembler);
} }
...@@ -405,36 +396,36 @@ void Interpreter::DoTestLessThan(compiler::InterpreterAssembler* assembler) { ...@@ -405,36 +396,36 @@ void Interpreter::DoTestLessThan(compiler::InterpreterAssembler* assembler) {
// //
// Test if the value in the <src> register is greater than the accumulator. // Test if the value in the <src> register is greater than the accumulator.
void Interpreter::DoTestGreaterThan(compiler::InterpreterAssembler* assembler) { void Interpreter::DoTestGreaterThan(compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::GT, assembler); DoBinaryOp(Runtime::kInterpreterGreaterThan, assembler);
} }
// TestLessThanEqual <src> // TestLessThanOrEqual <src>
// //
// Test if the value in the <src> register is less than or equal to the // Test if the value in the <src> register is less than or equal to the
// accumulator. // accumulator.
void Interpreter::DoTestLessThanEqual( void Interpreter::DoTestLessThanOrEqual(
compiler::InterpreterAssembler* assembler) { compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::LTE, assembler); DoBinaryOp(Runtime::kInterpreterLessThanOrEqual, assembler);
} }
// TestGreaterThanEqual <src> // TestGreaterThanOrEqual <src>
// //
// Test if the value in the <src> register is greater than or equal to the // Test if the value in the <src> register is greater than or equal to the
// accumulator. // accumulator.
void Interpreter::DoTestGreaterThanEqual( void Interpreter::DoTestGreaterThanOrEqual(
compiler::InterpreterAssembler* assembler) { compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::GTE, assembler); DoBinaryOp(Runtime::kInterpreterGreaterThanOrEqual, assembler);
} }
// TestIn <src> // TestIn <src>
// //
// Test if the value in the <src> register is in the collection referenced // Test if the object referenced by the register operand is a property of the
// by the accumulator. // object referenced by the accumulator.
void Interpreter::DoTestIn(compiler::InterpreterAssembler* assembler) { void Interpreter::DoTestIn(compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::IN, assembler); DoBinaryOp(Runtime::kHasProperty, assembler);
} }
...@@ -443,7 +434,7 @@ void Interpreter::DoTestIn(compiler::InterpreterAssembler* assembler) { ...@@ -443,7 +434,7 @@ void Interpreter::DoTestIn(compiler::InterpreterAssembler* assembler) {
// Test if the object referenced by the <src> register is an an instance of type // Test if the object referenced by the <src> register is an an instance of type
// referenced by the accumulator. // referenced by the accumulator.
void Interpreter::DoTestInstanceOf(compiler::InterpreterAssembler* assembler) { void Interpreter::DoTestInstanceOf(compiler::InterpreterAssembler* assembler) {
DoCompareOp(Token::Value::INSTANCEOF, assembler); DoBinaryOp(Runtime::kInstanceOf, assembler);
} }
......
...@@ -815,7 +815,7 @@ enum CompareResult { ...@@ -815,7 +815,7 @@ enum CompareResult {
enum class ComparisonResult { enum class ComparisonResult {
kLessThan, // x < y kLessThan, // x < y
kEqual, // x = y kEqual, // x = y
kGreaterThan, // x > x kGreaterThan, // x > y
kUndefined // at least one of x or y was undefined or NaN kUndefined // at least one of x or y was undefined or NaN
}; };
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/runtime/runtime-utils.h"
#include "src/arguments.h"
#include "src/isolate-inl.h"
namespace v8 {
namespace internal {
RUNTIME_FUNCTION(Runtime_InterpreterEquals) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, x, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, y, 1);
Maybe<bool> result = Object::Equals(x, y);
if (result.IsJust()) {
return isolate->heap()->ToBoolean(result.FromJust());
} else {
return isolate->heap()->exception();
}
}
RUNTIME_FUNCTION(Runtime_InterpreterNotEquals) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, x, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, y, 1);
Maybe<bool> result = Object::Equals(x, y);
if (result.IsJust()) {
return isolate->heap()->ToBoolean(!result.FromJust());
} else {
return isolate->heap()->exception();
}
}
RUNTIME_FUNCTION(Runtime_InterpreterLessThan) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, x, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, y, 1);
Maybe<bool> result = Object::LessThan(x, y);
if (result.IsJust()) {
return isolate->heap()->ToBoolean(result.FromJust());
} else {
return isolate->heap()->exception();
}
}
RUNTIME_FUNCTION(Runtime_InterpreterGreaterThan) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, x, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, y, 1);
Maybe<bool> result = Object::GreaterThan(x, y);
if (result.IsJust()) {
return isolate->heap()->ToBoolean(result.FromJust());
} else {
return isolate->heap()->exception();
}
}
RUNTIME_FUNCTION(Runtime_InterpreterLessThanOrEqual) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, x, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, y, 1);
Maybe<bool> result = Object::LessThanOrEqual(x, y);
if (result.IsJust()) {
return isolate->heap()->ToBoolean(result.FromJust());
} else {
return isolate->heap()->exception();
}
}
RUNTIME_FUNCTION(Runtime_InterpreterGreaterThanOrEqual) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, x, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, y, 1);
Maybe<bool> result = Object::GreaterThanOrEqual(x, y);
if (result.IsJust()) {
return isolate->heap()->ToBoolean(result.FromJust());
} else {
return isolate->heap()->exception();
}
}
RUNTIME_FUNCTION(Runtime_InterpreterStrictEquals) {
SealHandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_CHECKED(Object, x, 0);
CONVERT_ARG_CHECKED(Object, y, 1);
return isolate->heap()->ToBoolean(x->StrictEquals(y));
}
RUNTIME_FUNCTION(Runtime_InterpreterStrictNotEquals) {
SealHandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_CHECKED(Object, x, 0);
CONVERT_ARG_CHECKED(Object, y, 1);
return isolate->heap()->ToBoolean(!x->StrictEquals(y));
}
RUNTIME_FUNCTION(Runtime_InterpreterToBoolean) {
SealHandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_CHECKED(Object, x, 0);
return isolate->heap()->ToBoolean(x->BooleanValue());
}
} // namespace internal
} // namespace v8
...@@ -216,6 +216,18 @@ namespace internal { ...@@ -216,6 +216,18 @@ namespace internal {
F(ForInStep, 1, 1) F(ForInStep, 1, 1)
#define FOR_EACH_INTRINSIC_INTERPRETER(F) \
F(InterpreterEquals, 2, 1) \
F(InterpreterNotEquals, 2, 1) \
F(InterpreterStrictEquals, 2, 1) \
F(InterpreterStrictNotEquals, 2, 1) \
F(InterpreterLessThan, 2, 1) \
F(InterpreterGreaterThan, 2, 1) \
F(InterpreterLessThanOrEqual, 2, 1) \
F(InterpreterGreaterThanOrEqual, 2, 1) \
F(InterpreterToBoolean, 1, 1)
#define FOR_EACH_INTRINSIC_FUNCTION(F) \ #define FOR_EACH_INTRINSIC_FUNCTION(F) \
F(FunctionGetName, 1, 1) \ F(FunctionGetName, 1, 1) \
F(FunctionSetName, 2, 1) \ F(FunctionSetName, 2, 1) \
...@@ -1058,6 +1070,7 @@ namespace internal { ...@@ -1058,6 +1070,7 @@ namespace internal {
FOR_EACH_INTRINSIC_DATE(F) \ FOR_EACH_INTRINSIC_DATE(F) \
FOR_EACH_INTRINSIC_DEBUG(F) \ FOR_EACH_INTRINSIC_DEBUG(F) \
FOR_EACH_INTRINSIC_FORIN(F) \ FOR_EACH_INTRINSIC_FORIN(F) \
FOR_EACH_INTRINSIC_INTERPRETER(F) \
FOR_EACH_INTRINSIC_FUNCTION(F) \ FOR_EACH_INTRINSIC_FUNCTION(F) \
FOR_EACH_INTRINSIC_FUTEX(F) \ FOR_EACH_INTRINSIC_FUTEX(F) \
FOR_EACH_INTRINSIC_GENERATOR(F) \ FOR_EACH_INTRINSIC_GENERATOR(F) \
......
...@@ -18,7 +18,7 @@ class BytecodeGeneratorHelper { ...@@ -18,7 +18,7 @@ class BytecodeGeneratorHelper {
public: public:
const char* kFunctionName = "f"; const char* kFunctionName = "f";
const int kLastParamIndex = static const int kLastParamIndex =
-InterpreterFrameConstants::kLastParamFromRegisterPointer / kPointerSize; -InterpreterFrameConstants::kLastParamFromRegisterPointer / kPointerSize;
BytecodeGeneratorHelper() { BytecodeGeneratorHelper() {
...@@ -470,7 +470,7 @@ TEST(PropertyLoads) { ...@@ -470,7 +470,7 @@ TEST(PropertyLoads) {
{"name"}}}; {"name"}}};
for (size_t i = 0; i < arraysize(snippets); i++) { for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array = Handle<BytecodeArray> bytecode_array =
helper.MakeBytecode(snippets[i].code_snippet, "f"); helper.MakeBytecode(snippets[i].code_snippet, helper.kFunctionName);
CheckBytecodeArrayEqual(snippets[i], bytecode_array); CheckBytecodeArrayEqual(snippets[i], bytecode_array);
} }
} }
...@@ -573,7 +573,7 @@ TEST(PropertyStores) { ...@@ -573,7 +573,7 @@ TEST(PropertyStores) {
{"name"}}}; {"name"}}};
for (size_t i = 0; i < arraysize(snippets); i++) { for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array = Handle<BytecodeArray> bytecode_array =
helper.MakeBytecode(snippets[i].code_snippet, "f"); helper.MakeBytecode(snippets[i].code_snippet, helper.kFunctionName);
CheckBytecodeArrayEqual(snippets[i], bytecode_array); CheckBytecodeArrayEqual(snippets[i], bytecode_array);
} }
} }
...@@ -651,7 +651,7 @@ TEST(PropertyCall) { ...@@ -651,7 +651,7 @@ TEST(PropertyCall) {
{"func"}}}; {"func"}}};
for (size_t i = 0; i < arraysize(snippets); i++) { for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array = Handle<BytecodeArray> bytecode_array =
helper.MakeBytecode(snippets[i].code_snippet, "f"); helper.MakeBytecode(snippets[i].code_snippet, helper.kFunctionName);
CheckBytecodeArrayEqual(snippets[i], bytecode_array); CheckBytecodeArrayEqual(snippets[i], bytecode_array);
} }
} }
...@@ -681,7 +681,6 @@ TEST(LoadGlobal) { ...@@ -681,7 +681,6 @@ TEST(LoadGlobal) {
for (size_t i = 0; i < arraysize(snippets); i++) { for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array = Handle<BytecodeArray> bytecode_array =
helper.MakeBytecode(snippets[i].code_snippet, "f"); helper.MakeBytecode(snippets[i].code_snippet, "f");
bytecode_array->Print();
CheckBytecodeArrayEqual(snippets[i], bytecode_array, true); CheckBytecodeArrayEqual(snippets[i], bytecode_array, true);
} }
} }
...@@ -737,7 +736,7 @@ TEST(IfConditions) { ...@@ -737,7 +736,7 @@ TEST(IfConditions) {
Handle<Object> unused = helper.factory()->undefined_value(); Handle<Object> unused = helper.factory()->undefined_value();
ExpectedSnippet<Handle<Object>> snippets[] = { ExpectedSnippet<Handle<Object>> snippets[] = {
{"function f() { if (0) { return 1; } else { return -1; } }", {"function f() { if (0) { return 1; } else { return -1; } } f()",
0, 0,
1, 1,
14, 14,
...@@ -753,7 +752,7 @@ TEST(IfConditions) { ...@@ -753,7 +752,7 @@ TEST(IfConditions) {
B(Return)}, // B(Return)}, //
0, 0,
{unused, unused, unused, unused}}, {unused, unused, unused, unused}},
{"function f() { if ('lucky') { return 1; } else { return -1; } }", {"function f() { if ('lucky') { return 1; } else { return -1; } } f();",
0, 0,
1, 1,
15, 15,
...@@ -768,9 +767,9 @@ TEST(IfConditions) { ...@@ -768,9 +767,9 @@ TEST(IfConditions) {
B(LdaUndefined), // B(LdaUndefined), //
B(Return)}, // B(Return)}, //
1, 1,
{helper.factory()->NewStringFromStaticChars("lucky"), unused, {helper.factory()->NewStringFromStaticChars("lucky"), unused, unused,
unused, unused}}, unused}},
{"function f() { if (false) { return 1; } else { return -1; } }", {"function f() { if (false) { return 1; } else { return -1; } } f();",
0, 0,
1, 1,
13, 13,
...@@ -785,14 +784,15 @@ TEST(IfConditions) { ...@@ -785,14 +784,15 @@ TEST(IfConditions) {
B(Return)}, // B(Return)}, //
0, 0,
{unused, unused, unused, unused}}, {unused, unused, unused, unused}},
{"function f(a) { if (a <= 0) { return 200; } else { return -200; } }", {"function f(a) { if (a <= 0) { return 200; } else { return -200; } }"
"f(99);",
kPointerSize, kPointerSize,
2, 2,
19, 19,
{B(Ldar), R(-5), // {B(Ldar), R(-5), //
B(Star), R(0), // B(Star), R(0), //
B(LdaZero), // B(LdaZero), //
B(TestLessThanEqual), R(0), // B(TestLessThanOrEqual), R(0), //
B(JumpIfFalse), U8(7), // B(JumpIfFalse), U8(7), //
B(LdaConstant), U8(0), // B(LdaConstant), U8(0), //
B(Return), // B(Return), //
...@@ -804,7 +804,8 @@ TEST(IfConditions) { ...@@ -804,7 +804,8 @@ TEST(IfConditions) {
2, 2,
{helper.factory()->NewNumberFromInt(200), {helper.factory()->NewNumberFromInt(200),
helper.factory()->NewNumberFromInt(-200), unused, unused}}, helper.factory()->NewNumberFromInt(-200), unused, unused}},
{"function f(a, b) { if (a in b) { return 200; } }", {"function f(a, b) { if (a in b) { return 200; } }"
"f('prop', { prop: 'yes'});",
kPointerSize, kPointerSize,
3, 3,
17, 17,
...@@ -820,27 +821,11 @@ TEST(IfConditions) { ...@@ -820,27 +821,11 @@ TEST(IfConditions) {
B(Return)}, // B(Return)}, //
1, 1,
{helper.factory()->NewNumberFromInt(200), unused, unused, unused}}, {helper.factory()->NewNumberFromInt(200), unused, unused, unused}},
{"function f(a, b) { if (a instanceof b) { return 200; } }",
kPointerSize,
3,
17,
{B(Ldar), R(-6), //
B(Star), R(0), //
B(Ldar), R(-5), //
B(TestInstanceOf), R(0), //
B(JumpIfFalse), U8(7), //
B(LdaConstant), U8(0), //
B(Return), //
B(Jump), U8(2), // TODO(oth): Unreachable jump after return
B(LdaUndefined), //
B(Return)}, //
1,
{helper.factory()->NewNumberFromInt(200), unused, unused, unused}},
{"function f(z) { var a = 0; var b = 0; if (a === 0.01) { " {"function f(z) { var a = 0; var b = 0; if (a === 0.01) { "
#define X "b = a; a = b; " #define X "b = a; a = b; "
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
#undef X #undef X
" return 200; } else { return -200; } }", " return 200; } else { return -200; } } f(0.001)",
3 * kPointerSize, 3 * kPointerSize,
2, 2,
218, 218,
...@@ -868,11 +853,51 @@ TEST(IfConditions) { ...@@ -868,11 +853,51 @@ TEST(IfConditions) {
{helper.factory()->NewHeapNumber(0.01), {helper.factory()->NewHeapNumber(0.01),
helper.factory()->NewNumberFromInt(200), helper.factory()->NewNumberFromInt(200),
helper.factory()->NewNumberFromInt(199), helper.factory()->NewNumberFromInt(199),
helper.factory()->NewNumberFromInt(-200)}}}; helper.factory()->NewNumberFromInt(-200)}},
{"function f(a, b) {\n"
" if (a == b) { return 1; }\n"
" if (a === b) { return 1; }\n"
" if (a < b) { return 1; }\n"
" if (a > b) { return 1; }\n"
" if (a <= b) { return 1; }\n"
" if (a >= b) { return 1; }\n"
" if (a in b) { return 1; }\n"
" if (a instanceof b) { return 1; }\n"
" /* if (a != b) { return 1; } */" // TODO(oth) Ast visitor yields
" /* if (a !== b) { return 1; } */" // UNARY NOT, rather than !=/!==.
" return 0;\n"
"} f(1, 1);",
kPointerSize,
3,
122,
{
#define IF_CONDITION_RETURN(condition) \
B(Ldar), R(-6), \
B(Star), R(0), \
B(Ldar), R(-5), \
B(condition), R(0), \
B(JumpIfFalse), U8(7), \
B(LdaSmi8), U8(1), \
B(Return), \
B(Jump), U8(2),
IF_CONDITION_RETURN(TestEqual) //
IF_CONDITION_RETURN(TestEqualStrict) //
IF_CONDITION_RETURN(TestLessThan) //
IF_CONDITION_RETURN(TestGreaterThan) //
IF_CONDITION_RETURN(TestLessThanOrEqual) //
IF_CONDITION_RETURN(TestGreaterThanOrEqual) //
IF_CONDITION_RETURN(TestIn) //
IF_CONDITION_RETURN(TestInstanceOf) //
#undef IF_CONDITION_RETURN
B(LdaZero), //
B(Return)}, //
0,
{unused, unused, unused, unused}},
};
for (size_t i = 0; i < arraysize(snippets); i++) { for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array = Handle<BytecodeArray> bytecode_array =
helper.MakeBytecodeForFunction(snippets[i].code_snippet); helper.MakeBytecode(snippets[i].code_snippet, helper.kFunctionName);
CheckBytecodeArrayEqual(snippets[i], bytecode_array); CheckBytecodeArrayEqual(snippets[i], bytecode_array);
} }
} }
......
...@@ -151,6 +151,7 @@ class InterpreterTester { ...@@ -151,6 +151,7 @@ class InterpreterTester {
using v8::internal::BytecodeArray; using v8::internal::BytecodeArray;
using v8::internal::Handle; using v8::internal::Handle;
using v8::internal::LanguageMode;
using v8::internal::Object; using v8::internal::Object;
using v8::internal::Runtime; using v8::internal::Runtime;
using v8::internal::Smi; using v8::internal::Smi;
...@@ -976,3 +977,271 @@ TEST(InterpreterConditionalJumps) { ...@@ -976,3 +977,271 @@ TEST(InterpreterConditionalJumps) {
Handle<Object> return_value = callable().ToHandleChecked(); Handle<Object> return_value = callable().ToHandleChecked();
CHECK_EQ(Smi::cast(*return_value)->value(), 7); 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,
Token::Value::GT, Token::Value::GTE};
template <typename T>
bool CompareC(Token::Value op, T lhs, T rhs, bool types_differed = false) {
switch (op) {
case Token::Value::EQ:
return lhs == rhs;
case Token::Value::NE:
return lhs != rhs;
case Token::Value::EQ_STRICT:
return (lhs == rhs) && !types_differed;
case Token::Value::NE_STRICT:
return (lhs != rhs) || types_differed;
case Token::Value::LT:
return lhs < rhs;
case Token::Value::LTE:
return lhs <= rhs;
case Token::Value::GT:
return lhs > rhs;
case Token::Value::GTE:
return lhs >= rhs;
default:
UNREACHABLE();
return false;
}
}
TEST(InterpreterSmiComparisons) {
// NB Constants cover 31-bit space.
int inputs[] = {v8::internal::kMinInt / 2,
v8::internal::kMinInt / 4,
-108733832,
-999,
-42,
-2,
-1,
0,
+1,
+2,
42,
12345678,
v8::internal::kMaxInt / 4,
v8::internal::kMaxInt / 2};
for (size_t c = 0; c < arraysize(kComparisonTypes); c++) {
Token::Value comparison = kComparisonTypes[c];
for (size_t i = 0; i < arraysize(inputs); i++) {
for (size_t j = 0; j < arraysize(inputs); j++) {
HandleAndZoneScope handles;
BytecodeArrayBuilder builder(handles.main_isolate(),
handles.main_zone());
Register r0(0);
builder.set_locals_count(1);
builder.set_parameter_count(0);
builder.LoadLiteral(Smi::FromInt(inputs[i]))
.StoreAccumulatorInRegister(r0)
.LoadLiteral(Smi::FromInt(inputs[j]))
.CompareOperation(comparison, r0, LanguageMode::SLOPPY)
.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(return_value->IsBoolean());
CHECK_EQ(return_value->BooleanValue(),
CompareC(comparison, inputs[i], inputs[j]));
}
}
}
}
TEST(InterpreterHeapNumberComparisons) {
double inputs[] = {std::numeric_limits<double>::min(),
std::numeric_limits<double>::max(),
-0.001,
0.01,
0.1000001,
1e99,
-1e-99};
for (size_t c = 0; c < arraysize(kComparisonTypes); c++) {
Token::Value comparison = kComparisonTypes[c];
for (size_t i = 0; i < arraysize(inputs); i++) {
for (size_t j = 0; j < arraysize(inputs); j++) {
HandleAndZoneScope handles;
i::Factory* factory = handles.main_isolate()->factory();
BytecodeArrayBuilder builder(handles.main_isolate(),
handles.main_zone());
Register r0(0);
builder.set_locals_count(1);
builder.set_parameter_count(0);
builder.LoadLiteral(factory->NewHeapNumber(inputs[i]))
.StoreAccumulatorInRegister(r0)
.LoadLiteral(factory->NewHeapNumber(inputs[j]))
.CompareOperation(comparison, r0, LanguageMode::SLOPPY)
.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(return_value->IsBoolean());
CHECK_EQ(return_value->BooleanValue(),
CompareC(comparison, inputs[i], inputs[j]));
}
}
}
}
TEST(InterpreterStringComparisons) {
std::string inputs[] = {"A", "abc", "z", "", "Foo!", "Foo"};
for (size_t c = 0; c < arraysize(kComparisonTypes); c++) {
Token::Value comparison = kComparisonTypes[c];
for (size_t i = 0; i < arraysize(inputs); i++) {
for (size_t j = 0; j < arraysize(inputs); j++) {
const char* lhs = inputs[i].c_str();
const char* rhs = inputs[j].c_str();
HandleAndZoneScope handles;
i::Factory* factory = handles.main_isolate()->factory();
BytecodeArrayBuilder builder(handles.main_isolate(),
handles.main_zone());
Register r0(0);
builder.set_locals_count(1);
builder.set_parameter_count(0);
builder.LoadLiteral(factory->NewStringFromAsciiChecked(lhs))
.StoreAccumulatorInRegister(r0)
.LoadLiteral(factory->NewStringFromAsciiChecked(rhs))
.CompareOperation(comparison, r0, LanguageMode::SLOPPY)
.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(return_value->IsBoolean());
CHECK_EQ(return_value->BooleanValue(),
CompareC(comparison, inputs[i], inputs[j]));
}
}
}
}
TEST(InterpreterMixedComparisons) {
// This test compares a HeapNumber with a String. The latter is
// convertible to a HeapNumber so comparison will be between numeric
// values except for the strict comparisons where no conversion is
// performed.
const char* inputs[] = {"-1.77", "-40.333", "0.01", "55.77e5", "2.01"};
i::UnicodeCache unicode_cache;
for (size_t c = 0; c < arraysize(kComparisonTypes); c++) {
Token::Value comparison = kComparisonTypes[c];
for (size_t i = 0; i < arraysize(inputs); i++) {
for (size_t j = 0; j < arraysize(inputs); j++) {
for (int pass = 0; pass < 2; pass++) {
const char* lhs_cstr = inputs[i];
const char* rhs_cstr = inputs[j];
double lhs = StringToDouble(&unicode_cache, lhs_cstr,
i::ConversionFlags::NO_FLAGS);
double rhs = StringToDouble(&unicode_cache, rhs_cstr,
i::ConversionFlags::NO_FLAGS);
HandleAndZoneScope handles;
i::Factory* factory = handles.main_isolate()->factory();
BytecodeArrayBuilder builder(handles.main_isolate(),
handles.main_zone());
Register r0(0);
builder.set_locals_count(1);
builder.set_parameter_count(0);
if (pass == 0) {
// Comparison with HeapNumber on the lhs and String on the rhs
builder.LoadLiteral(factory->NewNumber(lhs))
.StoreAccumulatorInRegister(r0)
.LoadLiteral(factory->NewStringFromAsciiChecked(rhs_cstr))
.CompareOperation(comparison, r0, LanguageMode::SLOPPY)
.Return();
} else {
// Comparison with HeapNumber on the rhs and String on the lhs
builder.LoadLiteral(factory->NewStringFromAsciiChecked(lhs_cstr))
.StoreAccumulatorInRegister(r0)
.LoadLiteral(factory->NewNumber(rhs))
.CompareOperation(comparison, r0, LanguageMode::SLOPPY)
.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(return_value->IsBoolean());
CHECK_EQ(return_value->BooleanValue(),
CompareC(comparison, lhs, rhs, true));
}
}
}
}
}
TEST(InterpreterInstanceOf) {
HandleAndZoneScope handles;
i::Factory* factory = handles.main_isolate()->factory();
Handle<i::String> name = factory->NewStringFromAsciiChecked("cons");
Handle<i::JSFunction> func = factory->NewFunction(name);
Handle<i::JSObject> instance = factory->NewJSObject(func);
Handle<i::Object> other = factory->NewNumber(3.3333);
Handle<i::Object> cases[] = {Handle<i::Object>::cast(instance), other};
for (size_t i = 0; i < arraysize(cases); i++) {
bool expected_value = (i == 0);
BytecodeArrayBuilder builder(handles.main_isolate(), handles.main_zone());
Register r0(0);
builder.set_locals_count(1);
builder.set_parameter_count(0);
builder.LoadLiteral(cases[i]);
builder.StoreAccumulatorInRegister(r0)
.LoadLiteral(func)
.CompareOperation(Token::Value::INSTANCEOF, r0, LanguageMode::SLOPPY)
.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(return_value->IsBoolean());
CHECK_EQ(return_value->BooleanValue(), expected_value);
}
}
TEST(InterpreterTestIn) {
HandleAndZoneScope handles;
i::Factory* factory = handles.main_isolate()->factory();
// Allocate an array
Handle<i::JSArray> array =
factory->NewJSArray(i::ElementsKind::FAST_SMI_ELEMENTS);
// Check for these properties on the array object
const char* properties[] = {"length", "fuzzle", "x", "0"};
for (size_t i = 0; i < arraysize(properties); i++) {
bool expected_value = (i == 0);
BytecodeArrayBuilder builder(handles.main_isolate(), handles.main_zone());
Register r0(0);
builder.set_locals_count(1);
builder.set_parameter_count(0);
builder.LoadLiteral(factory->NewStringFromAsciiChecked(properties[i]))
.StoreAccumulatorInRegister(r0)
.LoadLiteral(Handle<Object>::cast(array))
.CompareOperation(Token::Value::IN, r0, LanguageMode::SLOPPY)
.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(return_value->IsBoolean());
CHECK_EQ(return_value->BooleanValue(), expected_value);
}
}
// Copyright 2014 the V8 project authors. All rights reserved. // Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/v8.h"
#include "src/factory.h"
#include "src/heap/heap.h"
#include "src/heap/heap-inl.h"
#include "src/runtime/runtime.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
namespace interpreter {
class RuntimeInterpreterTest : public TestWithIsolateAndZone {
public:
typedef Object* (*RuntimeMethod)(int, Object**, Isolate*);
RuntimeInterpreterTest() {}
~RuntimeInterpreterTest() override {}
bool TestOperatorWithObjects(RuntimeMethod method, Handle<Object> lhs,
Handle<Object> rhs, bool expected);
bool TestOperator(RuntimeMethod method, int32_t lhs, int32_t rhs,
bool expected);
bool TestOperator(RuntimeMethod method, double lhs, double rhs,
bool expected);
bool TestOperator(RuntimeMethod method, const char* lhs, const char* rhs,
bool expected);
};
bool RuntimeInterpreterTest::TestOperatorWithObjects(RuntimeMethod method,
Handle<Object> lhs,
Handle<Object> rhs,
bool expected) {
Object* args_object[] = {*rhs, *lhs};
Handle<Object> result =
handle(method(2, &args_object[1], isolate()), isolate());
CHECK(result->IsTrue() || result->IsFalse());
return result->IsTrue() == expected;
}
bool RuntimeInterpreterTest::TestOperator(RuntimeMethod method, int32_t lhs,
int32_t rhs, bool expected) {
Handle<Object> x = isolate()->factory()->NewNumberFromInt(lhs);
Handle<Object> y = isolate()->factory()->NewNumberFromInt(rhs);
return TestOperatorWithObjects(method, x, y, expected);
}
bool RuntimeInterpreterTest::TestOperator(RuntimeMethod method, double lhs,
double rhs, bool expected) {
Handle<Object> x = isolate()->factory()->NewNumber(lhs);
Handle<Object> y = isolate()->factory()->NewNumber(rhs);
CHECK_EQ(HeapNumber::cast(*x)->value(), lhs);
CHECK_EQ(HeapNumber::cast(*y)->value(), rhs);
return TestOperatorWithObjects(method, x, y, expected);
}
bool RuntimeInterpreterTest::TestOperator(RuntimeMethod method, const char* lhs,
const char* rhs, bool expected) {
Handle<Object> x = isolate()->factory()->NewStringFromAsciiChecked(lhs);
Handle<Object> y = isolate()->factory()->NewStringFromAsciiChecked(rhs);
return TestOperatorWithObjects(method, x, y, expected);
}
TEST_F(RuntimeInterpreterTest, TestOperatorsWithIntegers) {
int32_t inputs[] = {kMinInt, Smi::kMinValue, -17, -1, 0, 1,
991, Smi::kMaxValue, kMaxInt};
TRACED_FOREACH(int, lhs, inputs) {
TRACED_FOREACH(int, rhs, inputs) {
#define INTEGER_OPERATOR_CHECK(r, op, x, y) \
CHECK(TestOperator(Runtime_Interpreter##r, x, y, x op y))
INTEGER_OPERATOR_CHECK(Equals, ==, lhs, rhs);
INTEGER_OPERATOR_CHECK(NotEquals, !=, lhs, rhs);
INTEGER_OPERATOR_CHECK(StrictEquals, ==, lhs, rhs);
INTEGER_OPERATOR_CHECK(StrictNotEquals, !=, lhs, rhs);
INTEGER_OPERATOR_CHECK(LessThan, <, lhs, rhs);
INTEGER_OPERATOR_CHECK(GreaterThan, >, lhs, rhs);
INTEGER_OPERATOR_CHECK(LessThanOrEqual, <=, lhs, rhs);
INTEGER_OPERATOR_CHECK(GreaterThanOrEqual, >=, lhs, rhs);
#undef INTEGER_OPERATOR_CHECK
}
}
}
TEST_F(RuntimeInterpreterTest, TestOperatorsWithDoubles) {
double inputs[] = {std::numeric_limits<double>::min(),
std::numeric_limits<double>::max(),
-0.001,
0.01,
3.14,
-6.02214086e23};
TRACED_FOREACH(double, lhs, inputs) {
TRACED_FOREACH(double, rhs, inputs) {
#define DOUBLE_OPERATOR_CHECK(r, op, x, y) \
CHECK(TestOperator(Runtime_Interpreter##r, x, y, x op y))
DOUBLE_OPERATOR_CHECK(Equals, ==, lhs, rhs);
DOUBLE_OPERATOR_CHECK(NotEquals, !=, lhs, rhs);
DOUBLE_OPERATOR_CHECK(StrictEquals, ==, lhs, rhs);
DOUBLE_OPERATOR_CHECK(StrictNotEquals, !=, lhs, rhs);
DOUBLE_OPERATOR_CHECK(LessThan, <, lhs, rhs);
DOUBLE_OPERATOR_CHECK(GreaterThan, >, lhs, rhs);
DOUBLE_OPERATOR_CHECK(LessThanOrEqual, <=, lhs, rhs);
DOUBLE_OPERATOR_CHECK(GreaterThanOrEqual, >=, lhs, rhs);
#undef DOUBLE_OPERATOR_CHECK
}
}
}
TEST_F(RuntimeInterpreterTest, TestOperatorsWithString) {
const char* inputs[] = {"abc", "a", "def", "0"};
TRACED_FOREACH(const char*, lhs, inputs) {
TRACED_FOREACH(const char*, rhs, inputs) {
#define STRING_OPERATOR_CHECK(r, op, x, y) \
CHECK(TestOperator(Runtime_Interpreter##r, x, y, \
std::string(x) op std::string(y)))
STRING_OPERATOR_CHECK(Equals, ==, lhs, rhs);
STRING_OPERATOR_CHECK(NotEquals, !=, lhs, rhs);
STRING_OPERATOR_CHECK(StrictEquals, ==, lhs, rhs);
STRING_OPERATOR_CHECK(StrictNotEquals, !=, lhs, rhs);
STRING_OPERATOR_CHECK(LessThan, <, lhs, rhs);
STRING_OPERATOR_CHECK(GreaterThan, >, lhs, rhs);
STRING_OPERATOR_CHECK(LessThanOrEqual, <=, lhs, rhs);
STRING_OPERATOR_CHECK(GreaterThanOrEqual, >=, lhs, rhs);
#undef STRING_OPERATOR_CHECK
}
}
}
TEST_F(RuntimeInterpreterTest, ToBoolean) {
double quiet_nan = std::numeric_limits<double>::quiet_NaN();
std::pair<Handle<Object>, bool> cases[] = {
std::make_pair(isolate()->factory()->NewNumberFromInt(0), false),
std::make_pair(isolate()->factory()->NewNumberFromInt(1), true),
std::make_pair(isolate()->factory()->NewNumberFromInt(100), true),
std::make_pair(isolate()->factory()->NewNumberFromInt(-1), true),
std::make_pair(isolate()->factory()->NewNumber(7.7), true),
std::make_pair(isolate()->factory()->NewNumber(0.00001), true),
std::make_pair(isolate()->factory()->NewNumber(quiet_nan), false),
std::make_pair(isolate()->factory()->NewHeapNumber(0.0), false),
std::make_pair(isolate()->factory()->undefined_value(), false),
std::make_pair(isolate()->factory()->null_value(), false),
std::make_pair(isolate()->factory()->true_value(), true),
std::make_pair(isolate()->factory()->false_value(), false),
std::make_pair(isolate()->factory()->NewStringFromStaticChars(""), false),
std::make_pair(isolate()->factory()->NewStringFromStaticChars("_"), true),
};
for (size_t i = 0; i < arraysize(cases); i++) {
auto& value_expected_tuple = cases[i];
Object* args_object[] = {*value_expected_tuple.first};
Handle<Object> result = handle(
Runtime_InterpreterToBoolean(1, &args_object[0], isolate()), isolate());
CHECK(result->IsBoolean());
CHECK_EQ(result->IsTrue(), value_expected_tuple.second);
}
}
} // Namespace interpreter
} // namespace internal
} // namespace v8
...@@ -108,6 +108,7 @@ ...@@ -108,6 +108,7 @@
'heap/heap-unittest.cc', 'heap/heap-unittest.cc',
'heap/scavenge-job-unittest.cc', 'heap/scavenge-job-unittest.cc',
'run-all-unittests.cc', 'run-all-unittests.cc',
'runtime/runtime-interpreter-unittest.cc',
'test-utils.h', 'test-utils.h',
'test-utils.cc', 'test-utils.cc',
], ],
......
...@@ -922,6 +922,7 @@ ...@@ -922,6 +922,7 @@
'../../src/runtime/runtime-generator.cc', '../../src/runtime/runtime-generator.cc',
'../../src/runtime/runtime-i18n.cc', '../../src/runtime/runtime-i18n.cc',
'../../src/runtime/runtime-internal.cc', '../../src/runtime/runtime-internal.cc',
'../../src/runtime/runtime-interpreter.cc',
'../../src/runtime/runtime-json.cc', '../../src/runtime/runtime-json.cc',
'../../src/runtime/runtime-literals.cc', '../../src/runtime/runtime-literals.cc',
'../../src/runtime/runtime-liveedit.cc', '../../src/runtime/runtime-liveedit.cc',
......
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