[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:
parent
b5c80a31ad
commit
41f3e782d9
@ -495,6 +495,12 @@ void BytecodeGraphBuilder::VisitCallRuntime(
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitCallJSRuntime(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitNew(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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>";
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
@ -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++) {
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user