[Interpreter] Add support for JS runtime calls.

Adds support for calling JS runtime functions. Also changes the bytecode
array builder to allow calling functions with an invalid argument
register if the call takes no arguments.

Adds the bytecode CallJSRuntime.

BUG=v8:4280
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#31774}
This commit is contained in:
rmcilroy 2015-11-04 01:21:26 -08:00 committed by Commit bot
parent b5c80a31ad
commit 41f3e782d9
12 changed files with 140 additions and 53 deletions

View File

@ -495,6 +495,12 @@ void BytecodeGraphBuilder::VisitCallRuntime(
}
void BytecodeGraphBuilder::VisitCallJSRuntime(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();
}
void BytecodeGraphBuilder::VisitNew(
const interpreter::BytecodeArrayIterator& iterator) {
UNIMPLEMENTED();

View File

@ -226,8 +226,12 @@ Node* InterpreterAssembler::BytecodeOperandIdx(int operand_index) {
Node* InterpreterAssembler::BytecodeOperandReg(int operand_index) {
DCHECK_EQ(interpreter::OperandType::kReg8,
interpreter::Bytecodes::GetOperandType(bytecode_, operand_index));
#ifdef DEBUG
interpreter::OperandType operand_type =
interpreter::Bytecodes::GetOperandType(bytecode_, operand_index);
DCHECK(operand_type == interpreter::OperandType::kReg8 ||
operand_type == interpreter::OperandType::kMaybeReg8);
#endif
return BytecodeOperandSignExtended(operand_index);
}

View File

@ -782,6 +782,10 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::Call(Register callable,
BytecodeArrayBuilder& BytecodeArrayBuilder::New(Register constructor,
Register first_arg,
size_t arg_count) {
if (!first_arg.is_valid()) {
DCHECK_EQ(0, arg_count);
first_arg = Register(0);
}
DCHECK(FitsInIdx8Operand(arg_count));
Output(Bytecode::kNew, constructor.ToOperand(), first_arg.ToOperand(),
static_cast<uint8_t>(arg_count));
@ -793,12 +797,27 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CallRuntime(
Runtime::FunctionId function_id, Register first_arg, size_t arg_count) {
DCHECK(FitsInIdx16Operand(function_id));
DCHECK(FitsInIdx8Operand(arg_count));
if (!first_arg.is_valid()) {
DCHECK_EQ(0, arg_count);
first_arg = Register(0);
}
Output(Bytecode::kCallRuntime, static_cast<uint16_t>(function_id),
first_arg.ToOperand(), static_cast<uint8_t>(arg_count));
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::CallJSRuntime(int context_index,
Register receiver,
size_t arg_count) {
DCHECK(FitsInIdx16Operand(context_index));
DCHECK(FitsInIdx8Operand(arg_count));
Output(Bytecode::kCallJSRuntime, static_cast<uint16_t>(context_index),
receiver.ToOperand(), static_cast<uint8_t>(arg_count));
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::Delete(Register object,
LanguageMode language_mode) {
Output(BytecodeForDelete(language_mode), object.ToOperand());
@ -910,6 +929,11 @@ bool BytecodeArrayBuilder::OperandIsValid(Bytecode bytecode, int operand_index,
case OperandType::kImm8:
case OperandType::kIdx8:
return static_cast<uint8_t>(operand_value) == operand_value;
case OperandType::kMaybeReg8:
if (operand_value == 0) {
return true;
}
// Fall-through to kReg8 case.
case OperandType::kReg8: {
Register reg = Register::FromOperand(static_cast<uint8_t>(operand_value));
if (reg.is_function_context() || reg.is_function_closure()) {

View File

@ -150,6 +150,12 @@ class BytecodeArrayBuilder {
BytecodeArrayBuilder& CallRuntime(Runtime::FunctionId function_id,
Register first_arg, size_t arg_count);
// Call the JS runtime function with |context_index|. The the receiver should
// be in |receiver| and all subsequent arguments should be in registers
// <receiver + 1> to <receiver + 1 + arg_count>.
BytecodeArrayBuilder& CallJSRuntime(int context_index, Register receiver,
size_t arg_count);
// Operators (register holds the lhs value, accumulator holds the rhs value).
BytecodeArrayBuilder& BinaryOperation(Token::Value binop, Register reg,
Strength strength);

View File

@ -76,7 +76,11 @@ int BytecodeArrayIterator::GetIndexOperand(int operand_index) const {
Register BytecodeArrayIterator::GetRegisterOperand(int operand_index) const {
uint32_t operand = GetRawOperand(operand_index, OperandType::kReg8);
OperandType operand_type =
Bytecodes::GetOperandType(current_bytecode(), operand_index);
DCHECK(operand_type == OperandType::kReg8 ||
operand_type == OperandType::kMaybeReg8);
uint32_t operand = GetRawOperand(operand_index, operand_type);
return Register::FromOperand(operand);
}

View File

@ -1475,6 +1475,10 @@ void BytecodeGenerator::VisitProperty(Property* expr) {
Register BytecodeGenerator::VisitArguments(ZoneList<Expression*>* args) {
if (args->length() == 0) {
return Register();
}
// Visit arguments and place in a contiguous block of temporary
// registers. Return the first temporary register corresponding to
// the first argument.
@ -1554,10 +1558,8 @@ void BytecodeGenerator::VisitCall(Call* expr) {
// Evaluate all arguments to the function call and store in sequential
// registers.
if (args->length() > 0) {
Register arg = VisitArguments(args);
CHECK(arg.index() == receiver.index() + 1);
}
Register arg = VisitArguments(args);
CHECK(args->length() == 0 || arg.index() == receiver.index() + 1);
// TODO(rmcilroy): Deal with possible direct eval here?
// TODO(rmcilroy): Use CallIC to allow call type feedback.
@ -1572,40 +1574,33 @@ void BytecodeGenerator::VisitCallNew(CallNew* expr) {
builder()->StoreAccumulatorInRegister(constructor);
ZoneList<Expression*>* args = expr->arguments();
if (args->length() > 0) {
Register first_arg = VisitArguments(args);
builder()->New(constructor, first_arg, args->length());
} else {
// The second argument here will be ignored as there are zero
// arguments. Using the constructor register avoids avoid
// allocating a temporary just to fill the operands.
builder()->New(constructor, constructor, 0);
}
Register first_arg = VisitArguments(args);
builder()->New(constructor, first_arg, args->length());
execution_result()->SetResultInAccumulator();
}
void BytecodeGenerator::VisitCallRuntime(CallRuntime* expr) {
if (expr->is_jsruntime()) {
UNIMPLEMENTED();
}
// TODO(rmcilroy): support multiple return values.
DCHECK_LE(expr->function()->result_size, 1);
Runtime::FunctionId function_id = expr->function()->function_id;
// Evaluate all arguments to the runtime call.
ZoneList<Expression*>* args = expr->arguments();
Register first_arg;
if (args->length() > 0) {
first_arg = VisitArguments(args);
} else {
// Allocation here is just to fullfil the requirement that there
// is a register operand for the start of the arguments though
// there are zero when this is generated.
first_arg = execution_result()->NewRegister();
Register receiver;
if (expr->is_jsruntime()) {
// Allocate a register for the receiver and load it with undefined.
execution_result()->PrepareForConsecutiveAllocations(args->length() + 1);
receiver = execution_result()->NextConsecutiveRegister();
builder()->LoadUndefined().StoreAccumulatorInRegister(receiver);
}
// Evaluate all arguments to the runtime call.
Register first_arg = VisitArguments(args);
if (expr->is_jsruntime()) {
DCHECK(args->length() == 0 || first_arg.index() == receiver.index() + 1);
builder()->CallJSRuntime(expr->context_index(), receiver, args->length());
} else {
// TODO(rmcilroy): support multiple return values.
DCHECK_LE(expr->function()->result_size, 1);
Runtime::FunctionId function_id = expr->function()->function_id;
builder()->CallRuntime(function_id, first_arg, args->length());
}
builder()->CallRuntime(function_id, first_arg, args->length());
execution_result()->SetResultInAccumulator();
}

View File

@ -231,7 +231,8 @@ std::ostream& Bytecodes::Decode(std::ostream& os, const uint8_t* bytecode_start,
case interpreter::OperandType::kImm8:
os << "#" << static_cast<int>(static_cast<int8_t>(*operand_start));
break;
case interpreter::OperandType::kReg8: {
case interpreter::OperandType::kReg8:
case interpreter::OperandType::kMaybeReg8: {
Register reg = Register::FromOperand(*operand_start);
if (reg.is_function_context()) {
os << "<context>";

View File

@ -16,18 +16,19 @@ namespace internal {
namespace interpreter {
// The list of operand types used by bytecodes.
#define OPERAND_TYPE_LIST(V) \
\
/* None operand. */ \
V(None, OperandSize::kNone) \
\
/* Byte operands. */ \
V(Count8, OperandSize::kByte) \
V(Imm8, OperandSize::kByte) \
V(Idx8, OperandSize::kByte) \
V(Reg8, OperandSize::kByte) \
\
/* Short operands. */ \
#define OPERAND_TYPE_LIST(V) \
\
/* None operand. */ \
V(None, OperandSize::kNone) \
\
/* Byte operands. */ \
V(Count8, OperandSize::kByte) \
V(Imm8, OperandSize::kByte) \
V(Idx8, OperandSize::kByte) \
V(Reg8, OperandSize::kByte) \
V(MaybeReg8, OperandSize::kByte) \
\
/* Short operands. */ \
V(Idx16, OperandSize::kShort)
// The list of bytecodes which are interpreted by the interpreter.
@ -121,11 +122,13 @@ namespace interpreter {
\
/* Call operations */ \
V(Call, OperandType::kReg8, OperandType::kReg8, OperandType::kCount8) \
V(CallRuntime, OperandType::kIdx16, OperandType::kReg8, \
V(CallRuntime, OperandType::kIdx16, OperandType::kMaybeReg8, \
OperandType::kCount8) \
V(CallJSRuntime, OperandType::kIdx16, OperandType::kReg8, \
OperandType::kCount8) \
\
/* New operator */ \
V(New, OperandType::kReg8, OperandType::kReg8, OperandType::kCount8) \
V(New, OperandType::kReg8, OperandType::kMaybeReg8, OperandType::kCount8) \
\
/* Test Operators */ \
V(TestEqual, OperandType::kReg8) \

View File

@ -914,6 +914,30 @@ void Interpreter::DoCallRuntime(compiler::InterpreterAssembler* assembler) {
}
// CallJSRuntime <context_index> <receiver> <arg_count>
//
// Call the JS runtime function that has the |context_index| with the receiver
// in register |receiver| and |arg_count| arguments in subsequent registers.
void Interpreter::DoCallJSRuntime(compiler::InterpreterAssembler* assembler) {
Node* context_index = __ BytecodeOperandIdx(0);
Node* receiver_reg = __ BytecodeOperandReg(1);
Node* first_arg = __ RegisterLocation(receiver_reg);
Node* args_count = __ BytecodeOperandCount(2);
// Get the function to call from the native context.
Node* context = __ GetContext();
Node* global = __ LoadContextSlot(context, Context::GLOBAL_OBJECT_INDEX);
Node* native_context =
__ LoadObjectField(global, JSGlobalObject::kNativeContextOffset);
Node* function = __ LoadContextSlot(native_context, context_index);
// Call the function.
Node* result = __ CallJS(function, first_arg, args_count);
__ SetAccumulator(result);
__ Dispatch();
}
// New <constructor> <arg_count>
//
// Call operator new with |constructor| and the first argument in

View File

@ -1547,10 +1547,10 @@ TEST(CallRuntime) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
ExpectedSnippet<int> snippets[] = {
ExpectedSnippet<InstanceType> snippets[] = {
{
"function f() { %TheHole() }\nf()",
1 * kPointerSize,
0,
1,
7,
{
@ -1585,6 +1585,24 @@ TEST(CallRuntime) {
B(Return) //
},
},
{
"function f() { return %spread_iterable([1]) }\nf()",
2 * kPointerSize,
1,
16,
{
B(LdaUndefined), //
B(Star), R(0), //
B(LdaConstant), U8(0), //
B(CreateArrayLiteral), U8(0), U8(3), //
B(Star), R(1), //
B(CallJSRuntime), U16(Context::SPREAD_ITERABLE_INDEX), R(0), //
U8(1), //
B(Return), //
},
1,
{InstanceType::FIXED_ARRAY_TYPE},
},
};
for (size_t i = 0; i < arraysize(snippets); i++) {

View File

@ -317,6 +317,7 @@ TARGET_TEST_F(InterpreterAssemblerTest, BytecodeOperand) {
EXPECT_THAT(m.BytecodeOperandImm(i),
m.IsBytecodeOperandSignExtended(offset));
break;
case interpreter::OperandType::kMaybeReg8:
case interpreter::OperandType::kReg8:
EXPECT_THAT(m.BytecodeOperandReg(i),
m.IsBytecodeOperandSignExtended(offset));

View File

@ -99,8 +99,9 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.CreateObjectLiteral(0, 0);
// Call operations.
builder.Call(reg, reg, 0);
builder.CallRuntime(Runtime::kIsArray, reg, 1);
builder.Call(reg, reg, 0)
.CallRuntime(Runtime::kIsArray, reg, 1)
.CallJSRuntime(Context::SPREAD_ITERABLE_INDEX, reg, 1);
// Emit binary operator invocations.
builder.BinaryOperation(Token::Value::ADD, reg, Strength::WEAK)