[Interpreter] Add support for calling runtime functions which return a pair.
Adds support for calling runtime functions which return a pair of values. Adds the bytecode CallRuntimePair. Also adds support to TurboFan for calling stubs which return multiple values. BUG=v8:4280 LOG=N Review URL: https://codereview.chromium.org/1568493002 Cr-Commit-Position: refs/heads/master@{#33181}
This commit is contained in:
parent
d006f617d4
commit
1a063d944d
@ -359,11 +359,10 @@ Callable CodeFactory::InterpreterPushArgsAndConstruct(Isolate* isolate) {
|
||||
|
||||
|
||||
// static
|
||||
Callable CodeFactory::InterpreterCEntry(Isolate* isolate) {
|
||||
// TODO(rmcilroy): Deal with runtime functions that return two values.
|
||||
Callable CodeFactory::InterpreterCEntry(Isolate* isolate, int result_size) {
|
||||
// Note: If we ever use fpregs in the interpreter then we will need to
|
||||
// save fpregs too.
|
||||
CEntryStub stub(isolate, 1, kDontSaveFPRegs, kArgvInRegister);
|
||||
CEntryStub stub(isolate, result_size, kDontSaveFPRegs, kArgvInRegister);
|
||||
return Callable(stub.GetCode(), InterpreterCEntryDescriptor(isolate));
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ class CodeFactory final {
|
||||
|
||||
static Callable InterpreterPushArgsAndCall(Isolate* isolate);
|
||||
static Callable InterpreterPushArgsAndConstruct(Isolate* isolate);
|
||||
static Callable InterpreterCEntry(Isolate* isolate);
|
||||
static Callable InterpreterCEntry(Isolate* isolate, int result_size = 1);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
@ -1199,6 +1199,12 @@ void BytecodeGraphBuilder::VisitCallRuntime(
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitCallRuntimeForPair(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
|
||||
Node* BytecodeGraphBuilder::ProcessCallNewArguments(
|
||||
const Operator* call_new_op, interpreter::Register callee,
|
||||
interpreter::Register first_arg, size_t arity) {
|
||||
|
@ -148,6 +148,12 @@ Node* InterpreterAssembler::StoreRegister(Node* value, Node* reg_index) {
|
||||
}
|
||||
|
||||
|
||||
Node* InterpreterAssembler::NextRegister(Node* reg_index) {
|
||||
// Register indexes are negative, so the next index is minus one.
|
||||
return IntPtrAdd(reg_index, Int32Constant(-1));
|
||||
}
|
||||
|
||||
|
||||
Node* InterpreterAssembler::BytecodeOperand(int operand_index) {
|
||||
DCHECK_LT(operand_index, interpreter::Bytecodes::NumberOfOperands(bytecode_));
|
||||
DCHECK_EQ(interpreter::OperandSize::kByte,
|
||||
@ -297,6 +303,7 @@ Node* InterpreterAssembler::BytecodeOperandIdx(int operand_index) {
|
||||
Node* InterpreterAssembler::BytecodeOperandReg(int operand_index) {
|
||||
switch (interpreter::Bytecodes::GetOperandType(bytecode_, operand_index)) {
|
||||
case interpreter::OperandType::kReg8:
|
||||
case interpreter::OperandType::kRegPair8:
|
||||
case interpreter::OperandType::kMaybeReg8:
|
||||
DCHECK_EQ(
|
||||
interpreter::OperandSize::kByte,
|
||||
@ -555,11 +562,11 @@ Node* InterpreterAssembler::CallIC(CallInterfaceDescriptor descriptor,
|
||||
|
||||
|
||||
Node* InterpreterAssembler::CallRuntime(Node* function_id, Node* first_arg,
|
||||
Node* arg_count) {
|
||||
Callable callable = CodeFactory::InterpreterCEntry(isolate());
|
||||
Node* arg_count, int result_size) {
|
||||
Callable callable = CodeFactory::InterpreterCEntry(isolate(), result_size);
|
||||
CallDescriptor* descriptor = Linkage::GetStubCallDescriptor(
|
||||
isolate(), zone(), callable.descriptor(), 0, CallDescriptor::kNoFlags);
|
||||
|
||||
isolate(), zone(), callable.descriptor(), 0, CallDescriptor::kNoFlags,
|
||||
Operator::kNoProperties, MachineType::AnyTagged(), result_size);
|
||||
Node* code_target = HeapConstant(callable.code());
|
||||
|
||||
// Get the function entry from the function id.
|
||||
|
@ -67,6 +67,9 @@ class InterpreterAssembler {
|
||||
Node* StoreRegister(Node* value, interpreter::Register reg);
|
||||
Node* StoreRegister(Node* value, Node* reg_index);
|
||||
|
||||
// Returns the next consecutive register.
|
||||
Node* NextRegister(Node* reg_index);
|
||||
|
||||
// Returns the location in memory of the register |reg_index| in the
|
||||
// interpreter register file.
|
||||
Node* RegisterLocation(Node* reg_index);
|
||||
@ -130,7 +133,8 @@ class InterpreterAssembler {
|
||||
Node* arg2, Node* arg3, Node* arg4, Node* arg5);
|
||||
|
||||
// Call runtime function.
|
||||
Node* CallRuntime(Node* function_id, Node* first_arg, Node* arg_count);
|
||||
Node* CallRuntime(Node* function_id, Node* first_arg, Node* arg_count,
|
||||
int return_size = 1);
|
||||
Node* CallRuntime(Runtime::FunctionId function_id, Node* arg1);
|
||||
Node* CallRuntime(Runtime::FunctionId function_id, Node* arg1, Node* arg2);
|
||||
Node* CallRuntime(Runtime::FunctionId function_id, Node* arg1, Node* arg2,
|
||||
|
@ -430,21 +430,28 @@ CallDescriptor* Linkage::GetInterpreterDispatchDescriptor(Zone* zone) {
|
||||
CallDescriptor* Linkage::GetStubCallDescriptor(
|
||||
Isolate* isolate, Zone* zone, const CallInterfaceDescriptor& descriptor,
|
||||
int stack_parameter_count, CallDescriptor::Flags flags,
|
||||
Operator::Properties properties, MachineType return_type) {
|
||||
Operator::Properties properties, MachineType return_type,
|
||||
size_t return_count) {
|
||||
const int register_parameter_count = descriptor.GetRegisterParameterCount();
|
||||
const int js_parameter_count =
|
||||
register_parameter_count + stack_parameter_count;
|
||||
const int context_count = 1;
|
||||
const size_t return_count = 1;
|
||||
const size_t parameter_count =
|
||||
static_cast<size_t>(js_parameter_count + context_count);
|
||||
|
||||
LocationSignature::Builder locations(zone, return_count, parameter_count);
|
||||
MachineSignature::Builder types(zone, return_count, parameter_count);
|
||||
|
||||
// Add return location.
|
||||
locations.AddReturn(regloc(kReturnRegister0));
|
||||
types.AddReturn(return_type);
|
||||
// Add returns.
|
||||
if (locations.return_count_ > 0) {
|
||||
locations.AddReturn(regloc(kReturnRegister0));
|
||||
}
|
||||
if (locations.return_count_ > 1) {
|
||||
locations.AddReturn(regloc(kReturnRegister1));
|
||||
}
|
||||
for (size_t i = 0; i < return_count; i++) {
|
||||
types.AddReturn(return_type);
|
||||
}
|
||||
|
||||
// Add parameters in registers and on the stack.
|
||||
for (int i = 0; i < js_parameter_count; i++) {
|
||||
|
@ -319,7 +319,8 @@ class Linkage : public ZoneObject {
|
||||
Isolate* isolate, Zone* zone, const CallInterfaceDescriptor& descriptor,
|
||||
int stack_parameter_count, CallDescriptor::Flags flags,
|
||||
Operator::Properties properties = Operator::kNoProperties,
|
||||
MachineType return_type = MachineType::AnyTagged());
|
||||
MachineType return_type = MachineType::AnyTagged(),
|
||||
size_t return_count = 1);
|
||||
|
||||
// Creates a call descriptor for simplified C calls that is appropriate
|
||||
// for the host platform. This simplified calling convention only supports
|
||||
|
@ -1055,6 +1055,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::New(Register constructor,
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::CallRuntime(
|
||||
Runtime::FunctionId function_id, Register first_arg, size_t arg_count) {
|
||||
DCHECK_EQ(1, Runtime::FunctionForId(function_id)->result_size);
|
||||
DCHECK(FitsInIdx16Operand(function_id));
|
||||
DCHECK(FitsInIdx8Operand(arg_count));
|
||||
if (!first_arg.is_valid()) {
|
||||
@ -1067,6 +1068,23 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CallRuntime(
|
||||
}
|
||||
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::CallRuntimeForPair(
|
||||
Runtime::FunctionId function_id, Register first_arg, size_t arg_count,
|
||||
Register first_return) {
|
||||
DCHECK_EQ(2, Runtime::FunctionForId(function_id)->result_size);
|
||||
DCHECK(FitsInIdx16Operand(function_id));
|
||||
DCHECK(FitsInIdx8Operand(arg_count));
|
||||
if (!first_arg.is_valid()) {
|
||||
DCHECK_EQ(0u, arg_count);
|
||||
first_arg = Register(0);
|
||||
}
|
||||
Output(Bytecode::kCallRuntimeForPair, static_cast<uint16_t>(function_id),
|
||||
first_arg.ToOperand(), static_cast<uint8_t>(arg_count),
|
||||
first_return.ToOperand());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::CallJSRuntime(int context_index,
|
||||
Register receiver,
|
||||
size_t arg_count) {
|
||||
@ -1196,6 +1214,21 @@ bool BytecodeArrayBuilder::TemporaryRegisterIsLive(Register reg) const {
|
||||
}
|
||||
|
||||
|
||||
bool BytecodeArrayBuilder::RegisterIsValid(Register reg) const {
|
||||
if (reg.is_function_context() || reg.is_function_closure() ||
|
||||
reg.is_new_target()) {
|
||||
return true;
|
||||
} else if (reg.is_parameter()) {
|
||||
int parameter_index = reg.ToParameterIndex(parameter_count_);
|
||||
return parameter_index >= 0 && parameter_index < parameter_count_;
|
||||
} else if (reg.index() < fixed_register_count()) {
|
||||
return true;
|
||||
} else {
|
||||
return TemporaryRegisterIsLive(reg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool BytecodeArrayBuilder::OperandIsValid(Bytecode bytecode, int operand_index,
|
||||
uint32_t operand_value) const {
|
||||
OperandType operand_type = Bytecodes::GetOperandType(bytecode, operand_index);
|
||||
@ -1214,38 +1247,22 @@ bool BytecodeArrayBuilder::OperandIsValid(Bytecode bytecode, int operand_index,
|
||||
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() ||
|
||||
reg.is_new_target()) {
|
||||
return true;
|
||||
} else if (reg.is_parameter()) {
|
||||
int parameter_index = reg.ToParameterIndex(parameter_count_);
|
||||
return parameter_index >= 0 && parameter_index < parameter_count_;
|
||||
} else if (reg.index() < fixed_register_count()) {
|
||||
return true;
|
||||
} else {
|
||||
return TemporaryRegisterIsLive(reg);
|
||||
}
|
||||
case OperandType::kReg8:
|
||||
return RegisterIsValid(
|
||||
Register::FromOperand(static_cast<uint8_t>(operand_value)));
|
||||
case OperandType::kRegPair8: {
|
||||
Register reg0 =
|
||||
Register::FromOperand(static_cast<uint8_t>(operand_value));
|
||||
Register reg1 = Register(reg0.index() + 1);
|
||||
return RegisterIsValid(reg0) && RegisterIsValid(reg1);
|
||||
}
|
||||
case OperandType::kReg16: {
|
||||
case OperandType::kReg16:
|
||||
if (bytecode != Bytecode::kExchange &&
|
||||
bytecode != Bytecode::kExchangeWide) {
|
||||
return false;
|
||||
}
|
||||
Register reg =
|
||||
Register::FromWideOperand(static_cast<uint16_t>(operand_value));
|
||||
if (reg.is_function_context() || reg.is_function_closure() ||
|
||||
reg.is_new_target()) {
|
||||
return false;
|
||||
} else if (reg.is_parameter()) {
|
||||
return false;
|
||||
} else if (reg.index() < fixed_register_count()) {
|
||||
return true;
|
||||
} else {
|
||||
return TemporaryRegisterIsLive(reg);
|
||||
}
|
||||
}
|
||||
return RegisterIsValid(
|
||||
Register::FromWideOperand(static_cast<uint16_t>(operand_value)));
|
||||
}
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
|
@ -167,6 +167,14 @@ class BytecodeArrayBuilder final {
|
||||
BytecodeArrayBuilder& CallRuntime(Runtime::FunctionId function_id,
|
||||
Register first_arg, size_t arg_count);
|
||||
|
||||
// Call the runtime function with |function_id| that returns a pair of values.
|
||||
// The first argument should be in |first_arg| and all subsequent arguments
|
||||
// should be in registers <first_arg + 1> to <first_arg + 1 + arg_count>. The
|
||||
// return values will be returned in <first_return> and <first_return + 1>.
|
||||
BytecodeArrayBuilder& CallRuntimeForPair(Runtime::FunctionId function_id,
|
||||
Register first_arg, size_t arg_count,
|
||||
Register first_return);
|
||||
|
||||
// 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>.
|
||||
@ -293,6 +301,8 @@ class BytecodeArrayBuilder final {
|
||||
bool NeedToBooleanCast();
|
||||
bool IsRegisterInAccumulator(Register reg);
|
||||
|
||||
bool RegisterIsValid(Register reg) const;
|
||||
|
||||
// Temporary register management.
|
||||
int BorrowTemporaryRegister();
|
||||
int BorrowTemporaryRegisterNotInRange(int start_index, int end_index);
|
||||
|
@ -88,6 +88,7 @@ Register BytecodeArrayIterator::GetRegisterOperand(int operand_index) const {
|
||||
OperandType operand_type =
|
||||
Bytecodes::GetOperandType(current_bytecode(), operand_index);
|
||||
DCHECK(operand_type == OperandType::kReg8 ||
|
||||
operand_type == OperandType::kRegPair8 ||
|
||||
operand_type == OperandType::kMaybeReg8 ||
|
||||
operand_type == OperandType::kReg16);
|
||||
uint32_t operand = GetRawOperand(operand_index, operand_type);
|
||||
|
@ -1690,8 +1690,6 @@ void BytecodeGenerator::VisitCallRuntime(CallRuntime* expr) {
|
||||
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());
|
||||
}
|
||||
|
@ -293,6 +293,17 @@ std::ostream& Bytecodes::Decode(std::ostream& os, const uint8_t* bytecode_start,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case interpreter::OperandType::kRegPair8: {
|
||||
Register reg = Register::FromOperand(*operand_start);
|
||||
if (reg.is_parameter()) {
|
||||
int parameter_index = reg.ToParameterIndex(parameter_count);
|
||||
DCHECK_NE(parameter_index, 0);
|
||||
os << "a" << parameter_index - 1 << "-" << parameter_index;
|
||||
} else {
|
||||
os << "r" << reg.index() << "-" << reg.index() + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case interpreter::OperandType::kReg16: {
|
||||
Register reg =
|
||||
Register::FromWideOperand(ReadUnalignedUInt16(operand_start));
|
||||
|
@ -27,6 +27,7 @@ namespace interpreter {
|
||||
V(Idx8, OperandSize::kByte) \
|
||||
V(MaybeReg8, OperandSize::kByte) \
|
||||
V(Reg8, OperandSize::kByte) \
|
||||
V(RegPair8, OperandSize::kByte) \
|
||||
\
|
||||
/* Short operands. */ \
|
||||
V(Count16, OperandSize::kShort) \
|
||||
@ -145,6 +146,8 @@ namespace interpreter {
|
||||
OperandType::kIdx16) \
|
||||
V(CallRuntime, OperandType::kIdx16, OperandType::kMaybeReg8, \
|
||||
OperandType::kCount8) \
|
||||
V(CallRuntimeForPair, OperandType::kIdx16, OperandType::kMaybeReg8, \
|
||||
OperandType::kCount8, OperandType::kRegPair8) \
|
||||
V(CallJSRuntime, OperandType::kIdx16, OperandType::kReg8, \
|
||||
OperandType::kCount8) \
|
||||
\
|
||||
|
@ -1086,6 +1086,33 @@ void Interpreter::DoCallRuntime(compiler::InterpreterAssembler* assembler) {
|
||||
}
|
||||
|
||||
|
||||
// CallRuntimeForPair <function_id> <first_arg> <arg_count> <first_return>
|
||||
//
|
||||
// Call the runtime function |function_id| which returns a pair, with the
|
||||
// first argument in register |first_arg| and |arg_count| arguments in
|
||||
// subsequent registers. Returns the result in <first_return> and
|
||||
// <first_return + 1>
|
||||
void Interpreter::DoCallRuntimeForPair(
|
||||
compiler::InterpreterAssembler* assembler) {
|
||||
// Call the runtime function.
|
||||
Node* function_id = __ BytecodeOperandIdx(0);
|
||||
Node* first_arg_reg = __ BytecodeOperandReg(1);
|
||||
Node* first_arg = __ RegisterLocation(first_arg_reg);
|
||||
Node* args_count = __ BytecodeOperandCount(2);
|
||||
Node* result_pair = __ CallRuntime(function_id, first_arg, args_count, 2);
|
||||
|
||||
// Store the results in <first_return> and <first_return + 1>
|
||||
Node* first_return_reg = __ BytecodeOperandReg(3);
|
||||
Node* second_return_reg = __ NextRegister(first_return_reg);
|
||||
Node* result0 = __ Projection(0, result_pair);
|
||||
Node* result1 = __ Projection(1, result_pair);
|
||||
__ StoreRegister(result0, first_return_reg);
|
||||
__ StoreRegister(result1, second_return_reg);
|
||||
|
||||
__ Dispatch();
|
||||
}
|
||||
|
||||
|
||||
// CallJSRuntime <context_index> <receiver> <arg_count>
|
||||
//
|
||||
// Call the JS runtime function that has the |context_index| with the receiver
|
||||
|
@ -350,6 +350,7 @@ TARGET_TEST_F(InterpreterAssemblerTest, BytecodeOperand) {
|
||||
break;
|
||||
case interpreter::OperandType::kMaybeReg8:
|
||||
case interpreter::OperandType::kReg8:
|
||||
case interpreter::OperandType::kRegPair8:
|
||||
EXPECT_THAT(m.BytecodeOperandReg(i),
|
||||
m.IsBytecodeOperandSignExtended(offset));
|
||||
break;
|
||||
@ -592,29 +593,33 @@ TARGET_TEST_F(InterpreterAssemblerTest, CallRuntime2) {
|
||||
|
||||
|
||||
TARGET_TEST_F(InterpreterAssemblerTest, CallRuntime) {
|
||||
const int kResultSizes[] = {1, 2};
|
||||
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
|
||||
InterpreterAssemblerForTest m(this, bytecode);
|
||||
Callable builtin = CodeFactory::InterpreterCEntry(isolate());
|
||||
TRACED_FOREACH(int, result_size, kResultSizes) {
|
||||
InterpreterAssemblerForTest m(this, bytecode);
|
||||
Callable builtin = CodeFactory::InterpreterCEntry(isolate(), result_size);
|
||||
|
||||
Node* function_id = m.Int32Constant(0);
|
||||
Node* first_arg = m.Int32Constant(1);
|
||||
Node* arg_count = m.Int32Constant(2);
|
||||
Node* function_id = m.Int32Constant(0);
|
||||
Node* first_arg = m.Int32Constant(1);
|
||||
Node* arg_count = m.Int32Constant(2);
|
||||
|
||||
Matcher<Node*> function_table = IsExternalConstant(
|
||||
ExternalReference::runtime_function_table_address(isolate()));
|
||||
Matcher<Node*> function = IsIntPtrAdd(
|
||||
function_table,
|
||||
IsInt32Mul(function_id, IsInt32Constant(sizeof(Runtime::Function))));
|
||||
Matcher<Node*> function_entry =
|
||||
m.IsLoad(MachineType::Pointer(), function,
|
||||
IsInt32Constant(offsetof(Runtime::Function, entry)));
|
||||
Matcher<Node*> function_table = IsExternalConstant(
|
||||
ExternalReference::runtime_function_table_address(isolate()));
|
||||
Matcher<Node*> function = IsIntPtrAdd(
|
||||
function_table,
|
||||
IsInt32Mul(function_id, IsInt32Constant(sizeof(Runtime::Function))));
|
||||
Matcher<Node*> function_entry =
|
||||
m.IsLoad(MachineType::Pointer(), function,
|
||||
IsInt32Constant(offsetof(Runtime::Function, entry)));
|
||||
|
||||
Node* call_runtime = m.CallRuntime(function_id, first_arg, arg_count);
|
||||
EXPECT_THAT(
|
||||
call_runtime,
|
||||
IsCall(_, IsHeapConstant(builtin.code()), arg_count, first_arg,
|
||||
function_entry,
|
||||
IsParameter(Linkage::kInterpreterContextParameter), _, _));
|
||||
Node* call_runtime =
|
||||
m.CallRuntime(function_id, first_arg, arg_count, result_size);
|
||||
EXPECT_THAT(
|
||||
call_runtime,
|
||||
IsCall(_, IsHeapConstant(builtin.code()), arg_count, first_arg,
|
||||
function_entry,
|
||||
IsParameter(Linkage::kInterpreterContextParameter), _, _));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
|
||||
builder.Call(reg, reg, 0, 0)
|
||||
.Call(reg, reg, 0, 1024)
|
||||
.CallRuntime(Runtime::kIsArray, reg, 1)
|
||||
.CallRuntimeForPair(Runtime::kLoadLookupSlot, reg, 1, reg)
|
||||
.CallJSRuntime(Context::SPREAD_ITERABLE_INDEX, reg, 1);
|
||||
|
||||
// Emit binary operator invocations.
|
||||
|
Loading…
Reference in New Issue
Block a user