[Interpreter] Add CallRuntime support to the interpreter.
Adds support for calling runtime functions from the interpreter. Adds the CallRuntime bytecode which takes a Runtime::FunctionId of the function to call and the arguments in sequential registers. Adds a InterpreterCEntry builtin to enable the interpreter to enter C++ code based on the functionId. Also renames Builtin::PushArgsAndCall to Builtin::InterpreterPushArgsAndCall and groups all the interpreter builtins together. BUG=v8:4280 LOG=N Committed: https://crrev.com/40e8424b744f8b6e3e1d93e20f23487419911dfc Cr-Commit-Position: refs/heads/master@{#31064} Review URL: https://codereview.chromium.org/1362383002 Cr-Commit-Position: refs/heads/master@{#31076}
This commit is contained in:
parent
7b7a8205d9
commit
c991d8f384
@ -966,6 +966,35 @@ void Builtins::Generate_InterpreterExitTrampoline(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_InterpreterPushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- r0 : the number of arguments (not including the receiver)
|
||||
// -- r2 : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- r1 : the target to call (can be any Object).
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ add(r3, r0, Operand(1)); // Add one for receiver.
|
||||
__ mov(r3, Operand(r3, LSL, kPointerSizeLog2));
|
||||
__ sub(r3, r2, r3);
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ b(al, &loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ ldr(r4, MemOperand(r2, -kPointerSize, PostIndex));
|
||||
__ push(r4);
|
||||
__ bind(&loop_check);
|
||||
__ cmp(r2, r3);
|
||||
__ b(gt, &loop_header);
|
||||
|
||||
// Call the target.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_CompileLazy(MacroAssembler* masm) {
|
||||
CallRuntimePassFunction(masm, Runtime::kCompileLazy);
|
||||
GenerateTailCallToReturnedCode(masm);
|
||||
@ -1696,35 +1725,6 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_PushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- r0 : the number of arguments (not including the receiver)
|
||||
// -- r2 : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- r1 : the target to call (can be any Object).
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ add(r3, r0, Operand(1)); // Add one for receiver.
|
||||
__ mov(r3, Operand(r3, LSL, kPointerSizeLog2));
|
||||
__ sub(r3, r2, r3);
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ b(al, &loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ ldr(r4, MemOperand(r2, -kPointerSize, PostIndex));
|
||||
__ push(r4);
|
||||
__ bind(&loop_check);
|
||||
__ cmp(r2, r3);
|
||||
__ b(gt, &loop_header);
|
||||
|
||||
// Call the target.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- r0 : actual number of arguments
|
||||
|
@ -974,14 +974,21 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// fp: frame pointer (restored after C call)
|
||||
// sp: stack pointer (restored as callee's sp after C call)
|
||||
// cp: current context (C callee-saved)
|
||||
|
||||
//
|
||||
// If argv_in_register():
|
||||
// r2: pointer to the first argument
|
||||
ProfileEntryHookStub::MaybeCallEntryHook(masm);
|
||||
|
||||
__ mov(r5, Operand(r1));
|
||||
|
||||
// Compute the argv pointer in a callee-saved register.
|
||||
__ add(r1, sp, Operand(r0, LSL, kPointerSizeLog2));
|
||||
__ sub(r1, r1, Operand(kPointerSize));
|
||||
if (!argv_in_register()) {
|
||||
// Compute the argv pointer in a callee-saved register.
|
||||
__ add(r1, sp, Operand(r0, LSL, kPointerSizeLog2));
|
||||
__ sub(r1, r1, Operand(kPointerSize));
|
||||
} else {
|
||||
// Move argv into the correct register.
|
||||
__ mov(r1, Operand(r2));
|
||||
}
|
||||
|
||||
// Enter the exit frame that transitions from JavaScript to C++.
|
||||
FrameScope scope(masm, StackFrame::MANUAL);
|
||||
@ -1057,8 +1064,12 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// r0:r1: result
|
||||
// sp: stack pointer
|
||||
// fp: frame pointer
|
||||
// Callee-saved register r4 still holds argc.
|
||||
__ LeaveExitFrame(save_doubles(), r4, true);
|
||||
Register argc;
|
||||
if (!argv_in_register()) {
|
||||
// Callee-saved register r4 still holds argc.
|
||||
argc = r4;
|
||||
}
|
||||
__ LeaveExitFrame(save_doubles(), argc, true);
|
||||
__ mov(pc, lr);
|
||||
|
||||
// Handling of exception.
|
||||
|
@ -417,16 +417,27 @@ void MathRoundVariantCallFromOptimizedCodeDescriptor::
|
||||
}
|
||||
|
||||
|
||||
void PushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
void InterpreterPushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
r0, // argument count (including receiver)
|
||||
r0, // argument count (not including receiver)
|
||||
r2, // address of first argument
|
||||
r1 // the target callable to be call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
|
||||
void InterpreterCEntryDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
r0, // argument count (argc)
|
||||
r2, // address of first argument (argv)
|
||||
r1 // the runtime function to call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -1753,7 +1753,7 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_PushArgsAndCall(MacroAssembler* masm) {
|
||||
void Builtins::Generate_InterpreterPushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- x0 : the number of arguments (not including the receiver)
|
||||
// -- x2 : the address of the first argument to be pushed. Subsequent
|
||||
|
@ -1067,6 +1067,8 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// Register parameters:
|
||||
// x0: argc (including receiver, untagged)
|
||||
// x1: target
|
||||
// If argv_in_register():
|
||||
// x11: argv (pointer to first argument)
|
||||
//
|
||||
// The stack on entry holds the arguments and the receiver, with the receiver
|
||||
// at the highest address:
|
||||
@ -1098,9 +1100,11 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// (arg[argc-2]), or just below the receiver in case there are no arguments.
|
||||
// - Adjust for the arg[] array.
|
||||
Register temp_argv = x11;
|
||||
__ Add(temp_argv, jssp, Operand(x0, LSL, kPointerSizeLog2));
|
||||
// - Adjust for the receiver.
|
||||
__ Sub(temp_argv, temp_argv, 1 * kPointerSize);
|
||||
if (!argv_in_register()) {
|
||||
__ Add(temp_argv, jssp, Operand(x0, LSL, kPointerSizeLog2));
|
||||
// - Adjust for the receiver.
|
||||
__ Sub(temp_argv, temp_argv, 1 * kPointerSize);
|
||||
}
|
||||
|
||||
// Enter the exit frame. Reserve three slots to preserve x21-x23 callee-saved
|
||||
// registers.
|
||||
@ -1204,12 +1208,10 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
|
||||
__ LeaveExitFrame(save_doubles(), x10, true);
|
||||
DCHECK(jssp.Is(__ StackPointer()));
|
||||
// Pop or drop the remaining stack slots and return from the stub.
|
||||
// jssp[24]: Arguments array (of size argc), including receiver.
|
||||
// jssp[16]: Preserved x23 (used for target).
|
||||
// jssp[8]: Preserved x22 (used for argc).
|
||||
// jssp[0]: Preserved x21 (used for argv).
|
||||
__ Drop(x11);
|
||||
if (!argv_in_register()) {
|
||||
// Drop the remaining stack slots and return from the stub.
|
||||
__ Drop(x11);
|
||||
}
|
||||
__ AssertFPCRState();
|
||||
__ Ret();
|
||||
|
||||
|
@ -446,16 +446,28 @@ void MathRoundVariantCallFromOptimizedCodeDescriptor::
|
||||
}
|
||||
|
||||
|
||||
void PushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
void InterpreterPushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
x0, // argument count (including receiver)
|
||||
x0, // argument count (not including receiver)
|
||||
x2, // address of first argument
|
||||
x1 // the target callable to be call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
|
||||
void InterpreterCEntryDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
x0, // argument count (argc)
|
||||
x11, // address of first argument (argv)
|
||||
x1 // the runtime function to call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -1445,6 +1445,13 @@ ExternalReference ExternalReference::vector_store_virtual_register(
|
||||
}
|
||||
|
||||
|
||||
ExternalReference ExternalReference::runtime_function_table_address(
|
||||
Isolate* isolate) {
|
||||
return ExternalReference(
|
||||
const_cast<Runtime::Function*>(Runtime::RuntimeFunctionTable(isolate)));
|
||||
}
|
||||
|
||||
|
||||
double power_helper(double x, double y) {
|
||||
int y_int = static_cast<int>(y);
|
||||
if (y == y_int) {
|
||||
|
@ -319,6 +319,8 @@ class Label {
|
||||
|
||||
enum SaveFPRegsMode { kDontSaveFPRegs, kSaveFPRegs };
|
||||
|
||||
enum ArgvMode { kArgvOnStack, kArgvInRegister };
|
||||
|
||||
// Specifies whether to perform icache flush operations on RelocInfo updates.
|
||||
// If FLUSH_ICACHE_IF_NEEDED, the icache will always be flushed if an
|
||||
// instruction was modified. If SKIP_ICACHE_FLUSH the flush will always be
|
||||
@ -992,6 +994,8 @@ class ExternalReference BASE_EMBEDDED {
|
||||
|
||||
static ExternalReference vector_store_virtual_register(Isolate* isolate);
|
||||
|
||||
static ExternalReference runtime_function_table_address(Isolate* isolate);
|
||||
|
||||
Address address() const { return reinterpret_cast<Address>(address_); }
|
||||
|
||||
// Used to check if single stepping is enabled in generated code.
|
||||
|
@ -254,6 +254,7 @@ namespace internal {
|
||||
"Unexpected number of pre-allocated property fields") \
|
||||
V(kUnexpectedFPCRMode, "Unexpected FPCR mode.") \
|
||||
V(kUnexpectedSmi, "Unexpected smi value") \
|
||||
V(kUnexpectedStackPointer, "The stack pointer is not the expected value") \
|
||||
V(kUnexpectedStringFunction, "Unexpected String function") \
|
||||
V(kUnexpectedStringType, "Unexpected string type") \
|
||||
V(kUnexpectedStringWrapperInstanceSize, \
|
||||
|
@ -81,16 +81,12 @@ enum BuiltinExtraArguments {
|
||||
V(ConstructProxy, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(Construct, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
\
|
||||
V(PushArgsAndCall, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
\
|
||||
V(InOptimizationQueue, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(JSConstructStubGeneric, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(JSConstructStubForDerived, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(JSConstructStubApi, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(JSEntryTrampoline, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(JSConstructEntryTrampoline, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(InterpreterEntryTrampoline, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(InterpreterExitTrampoline, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(CompileLazy, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(CompileOptimized, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(CompileOptimizedConcurrent, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
@ -100,6 +96,10 @@ enum BuiltinExtraArguments {
|
||||
V(NotifyStubFailure, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(NotifyStubFailureSaveDoubles, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
\
|
||||
V(InterpreterEntryTrampoline, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(InterpreterExitTrampoline, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(InterpreterPushArgsAndCall, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
\
|
||||
V(LoadIC_Miss, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(KeyedLoadIC_Miss, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
V(StoreIC_Miss, BUILTIN, UNINITIALIZED, kNoExtraICState) \
|
||||
@ -266,8 +266,6 @@ class Builtins {
|
||||
static void Generate_JSConstructStubApi(MacroAssembler* masm);
|
||||
static void Generate_JSEntryTrampoline(MacroAssembler* masm);
|
||||
static void Generate_JSConstructEntryTrampoline(MacroAssembler* masm);
|
||||
static void Generate_InterpreterEntryTrampoline(MacroAssembler* masm);
|
||||
static void Generate_InterpreterExitTrampoline(MacroAssembler* masm);
|
||||
static void Generate_NotifyDeoptimized(MacroAssembler* masm);
|
||||
static void Generate_NotifySoftDeoptimized(MacroAssembler* masm);
|
||||
static void Generate_NotifyLazyDeoptimized(MacroAssembler* masm);
|
||||
@ -287,8 +285,6 @@ class Builtins {
|
||||
// ES6 section 7.3.13 Construct (F, [argumentsList], [newTarget])
|
||||
static void Generate_Construct(MacroAssembler* masm);
|
||||
|
||||
static void Generate_PushArgsAndCall(MacroAssembler* masm);
|
||||
|
||||
static void Generate_FunctionCall(MacroAssembler* masm);
|
||||
static void Generate_FunctionApply(MacroAssembler* masm);
|
||||
static void Generate_ReflectApply(MacroAssembler* masm);
|
||||
@ -304,6 +300,10 @@ class Builtins {
|
||||
static void Generate_InterruptCheck(MacroAssembler* masm);
|
||||
static void Generate_StackCheck(MacroAssembler* masm);
|
||||
|
||||
static void Generate_InterpreterEntryTrampoline(MacroAssembler* masm);
|
||||
static void Generate_InterpreterExitTrampoline(MacroAssembler* masm);
|
||||
static void Generate_InterpreterPushArgsAndCall(MacroAssembler* masm);
|
||||
|
||||
#define DECLARE_CODE_AGE_BUILTIN_GENERATOR(C) \
|
||||
static void Generate_Make##C##CodeYoungAgainEvenMarking( \
|
||||
MacroAssembler* masm); \
|
||||
|
@ -269,9 +269,19 @@ Callable CodeFactory::CallFunction(Isolate* isolate, int argc,
|
||||
|
||||
|
||||
// static
|
||||
Callable CodeFactory::PushArgsAndCall(Isolate* isolate) {
|
||||
return Callable(isolate->builtins()->PushArgsAndCall(),
|
||||
PushArgsAndCallDescriptor(isolate));
|
||||
Callable CodeFactory::InterpreterPushArgsAndCall(Isolate* isolate) {
|
||||
return Callable(isolate->builtins()->InterpreterPushArgsAndCall(),
|
||||
InterpreterPushArgsAndCallDescriptor(isolate));
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
Callable CodeFactory::InterpreterCEntry(Isolate* isolate) {
|
||||
// TODO(rmcilroy): Deal with runtime functions that return two values.
|
||||
// Note: If we ever use fpregs in the interpreter then we will need to
|
||||
// save fpregs too.
|
||||
CEntryStub stub(isolate, 1, kDontSaveFPRegs, kArgvInRegister);
|
||||
return Callable(stub.GetCode(), InterpreterCEntryDescriptor(isolate));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
@ -97,7 +97,8 @@ class CodeFactory final {
|
||||
static Callable CallFunction(Isolate* isolate, int argc,
|
||||
CallFunctionFlags flags);
|
||||
|
||||
static Callable PushArgsAndCall(Isolate* isolate);
|
||||
static Callable InterpreterPushArgsAndCall(Isolate* isolate);
|
||||
static Callable InterpreterCEntry(Isolate* isolate);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
@ -1824,9 +1824,11 @@ std::ostream& operator<<(std::ostream& os, const CompareNilICStub::State& s);
|
||||
class CEntryStub : public PlatformCodeStub {
|
||||
public:
|
||||
CEntryStub(Isolate* isolate, int result_size,
|
||||
SaveFPRegsMode save_doubles = kDontSaveFPRegs)
|
||||
SaveFPRegsMode save_doubles = kDontSaveFPRegs,
|
||||
ArgvMode argv_mode = kArgvOnStack)
|
||||
: PlatformCodeStub(isolate) {
|
||||
minor_key_ = SaveDoublesBits::encode(save_doubles == kSaveFPRegs);
|
||||
minor_key_ = SaveDoublesBits::encode(save_doubles == kSaveFPRegs) |
|
||||
ArgvMode::encode(argv_mode == kArgvInRegister);
|
||||
DCHECK(result_size == 1 || result_size == 2);
|
||||
#if _WIN64 || V8_TARGET_ARCH_PPC
|
||||
minor_key_ = ResultSizeBits::update(minor_key_, result_size);
|
||||
@ -1841,6 +1843,7 @@ class CEntryStub : public PlatformCodeStub {
|
||||
|
||||
private:
|
||||
bool save_doubles() const { return SaveDoublesBits::decode(minor_key_); }
|
||||
bool argv_in_register() const { return ArgvMode::decode(minor_key_); }
|
||||
#if _WIN64 || V8_TARGET_ARCH_PPC
|
||||
int result_size() const { return ResultSizeBits::decode(minor_key_); }
|
||||
#endif // _WIN64
|
||||
@ -1848,7 +1851,8 @@ class CEntryStub : public PlatformCodeStub {
|
||||
bool NeedsImmovableCode() override;
|
||||
|
||||
class SaveDoublesBits : public BitField<bool, 0, 1> {};
|
||||
class ResultSizeBits : public BitField<int, 1, 3> {};
|
||||
class ArgvMode : public BitField<bool, 1, 1> {};
|
||||
class ResultSizeBits : public BitField<int, 2, 3> {};
|
||||
|
||||
DEFINE_NULL_CALL_INTERFACE_DESCRIPTOR();
|
||||
DEFINE_PLATFORM_CODE_STUB(CEntry, PlatformCodeStub);
|
||||
|
@ -285,6 +285,12 @@ void BytecodeGraphBuilder::VisitCall(
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::VisitCallRuntime(
|
||||
const interpreter::BytecodeArrayIterator& iterator) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGraphBuilder::BuildBinaryOp(
|
||||
const Operator* js_op, const interpreter::BytecodeArrayIterator& iterator) {
|
||||
Node* left = environment()->LookupRegister(iterator.GetRegisterOperand(0));
|
||||
|
@ -314,13 +314,30 @@ Node* InterpreterAssembler::LoadTypeFeedbackVector() {
|
||||
}
|
||||
|
||||
|
||||
Node* InterpreterAssembler::CallN(CallDescriptor* descriptor,
|
||||
Node* code_target,
|
||||
Node** args) {
|
||||
Node* stack_pointer_before_call = nullptr;
|
||||
if (FLAG_debug_code) {
|
||||
stack_pointer_before_call = raw_assembler_->LoadStackPointer();
|
||||
}
|
||||
Node* return_val = raw_assembler_->CallN(descriptor, code_target, args);
|
||||
if (FLAG_debug_code) {
|
||||
Node* stack_pointer_after_call = raw_assembler_->LoadStackPointer();
|
||||
AbortIfWordNotEqual(stack_pointer_before_call, stack_pointer_after_call,
|
||||
kUnexpectedStackPointer);
|
||||
}
|
||||
return return_val;
|
||||
}
|
||||
|
||||
|
||||
Node* InterpreterAssembler::CallJS(Node* function, Node* first_arg,
|
||||
Node* arg_count) {
|
||||
Callable builtin = CodeFactory::PushArgsAndCall(isolate());
|
||||
Callable callable = CodeFactory::InterpreterPushArgsAndCall(isolate());
|
||||
CallDescriptor* descriptor = Linkage::GetStubCallDescriptor(
|
||||
isolate(), zone(), builtin.descriptor(), 0, CallDescriptor::kNoFlags);
|
||||
isolate(), zone(), callable.descriptor(), 0, CallDescriptor::kNoFlags);
|
||||
|
||||
Node* code_target = HeapConstant(builtin.code());
|
||||
Node* code_target = HeapConstant(callable.code());
|
||||
|
||||
Node** args = zone()->NewArray<Node*>(4);
|
||||
args[0] = arg_count;
|
||||
@ -328,7 +345,7 @@ Node* InterpreterAssembler::CallJS(Node* function, Node* first_arg,
|
||||
args[2] = function;
|
||||
args[3] = ContextTaggedPointer();
|
||||
|
||||
return raw_assembler_->CallN(descriptor, code_target, args);
|
||||
return CallN(descriptor, code_target, args);
|
||||
}
|
||||
|
||||
|
||||
@ -336,7 +353,7 @@ Node* InterpreterAssembler::CallIC(CallInterfaceDescriptor descriptor,
|
||||
Node* target, Node** args) {
|
||||
CallDescriptor* call_descriptor = Linkage::GetStubCallDescriptor(
|
||||
isolate(), zone(), descriptor, 0, CallDescriptor::kNoFlags);
|
||||
return raw_assembler_->CallN(call_descriptor, target, args);
|
||||
return CallN(call_descriptor, target, args);
|
||||
}
|
||||
|
||||
|
||||
@ -367,6 +384,33 @@ Node* InterpreterAssembler::CallIC(CallInterfaceDescriptor descriptor,
|
||||
}
|
||||
|
||||
|
||||
Node* InterpreterAssembler::CallRuntime(Node* function_id, Node* first_arg,
|
||||
Node* arg_count) {
|
||||
Callable callable = CodeFactory::InterpreterCEntry(isolate());
|
||||
CallDescriptor* descriptor = Linkage::GetStubCallDescriptor(
|
||||
isolate(), zone(), callable.descriptor(), 0, CallDescriptor::kNoFlags);
|
||||
|
||||
Node* code_target = HeapConstant(callable.code());
|
||||
|
||||
// Get the function entry from the function id.
|
||||
Node* function_table = raw_assembler_->ExternalConstant(
|
||||
ExternalReference::runtime_function_table_address(isolate()));
|
||||
Node* function_offset = raw_assembler_->Int32Mul(
|
||||
function_id, Int32Constant(sizeof(Runtime::Function)));
|
||||
Node* function = IntPtrAdd(function_table, function_offset);
|
||||
Node* function_entry = raw_assembler_->Load(
|
||||
kMachPtr, function, Int32Constant(offsetof(Runtime::Function, entry)));
|
||||
|
||||
Node** args = zone()->NewArray<Node*>(4);
|
||||
args[0] = arg_count;
|
||||
args[1] = first_arg;
|
||||
args[2] = function_entry;
|
||||
args[3] = ContextTaggedPointer();
|
||||
|
||||
return CallN(descriptor, code_target, args);
|
||||
}
|
||||
|
||||
|
||||
Node* InterpreterAssembler::CallRuntime(Runtime::FunctionId function_id,
|
||||
Node* arg1) {
|
||||
return raw_assembler_->CallRuntime1(function_id, arg1,
|
||||
@ -464,6 +508,19 @@ void InterpreterAssembler::DispatchTo(Node* new_bytecode_offset) {
|
||||
}
|
||||
|
||||
|
||||
void InterpreterAssembler::AbortIfWordNotEqual(
|
||||
Node* lhs, Node* rhs, BailoutReason bailout_reason) {
|
||||
RawMachineAssembler::Label match, no_match;
|
||||
Node* condition = raw_assembler_->WordEqual(lhs, rhs);
|
||||
raw_assembler_->Branch(condition, &match, &no_match);
|
||||
raw_assembler_->Bind(&no_match);
|
||||
Node* abort_id = SmiTag(Int32Constant(bailout_reason));
|
||||
CallRuntime(Runtime::kAbort, abort_id);
|
||||
Return();
|
||||
raw_assembler_->Bind(&match);
|
||||
}
|
||||
|
||||
|
||||
void InterpreterAssembler::AddEndInput(Node* input) {
|
||||
DCHECK_NOT_NULL(input);
|
||||
end_nodes_.push_back(input);
|
||||
|
@ -110,6 +110,7 @@ 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(Runtime::FunctionId function_id, Node* arg1);
|
||||
Node* CallRuntime(Runtime::FunctionId function_id, Node* arg1, Node* arg2);
|
||||
|
||||
@ -156,6 +157,7 @@ class InterpreterAssembler {
|
||||
Node* BytecodeOperandSignExtended(int operand_index);
|
||||
Node* BytecodeOperandShort(int operand_index);
|
||||
|
||||
Node* CallN(CallDescriptor* descriptor, Node* code_target, Node** args);
|
||||
Node* CallIC(CallInterfaceDescriptor descriptor, Node* target, Node** args);
|
||||
Node* CallJSBuiltin(int context_index, Node* receiver, Node** js_args,
|
||||
int js_arg_count);
|
||||
@ -168,6 +170,9 @@ class InterpreterAssembler {
|
||||
// Starts next instruction dispatch at |new_bytecode_offset|.
|
||||
void DispatchTo(Node* new_bytecode_offset);
|
||||
|
||||
// Abort operations for debug code.
|
||||
void AbortIfWordNotEqual(Node* lhs, Node* rhs, BailoutReason bailout_reason);
|
||||
|
||||
// Adds an end node of the graph.
|
||||
void AddEndInput(Node* input);
|
||||
|
||||
|
@ -708,6 +708,41 @@ void Builtins::Generate_InterpreterExitTrampoline(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_InterpreterPushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- eax : the number of arguments (not including the receiver)
|
||||
// -- ebx : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- edi : the target to call (can be any Object).
|
||||
|
||||
// Pop return address to allow tail-call after pushing arguments.
|
||||
__ Pop(edx);
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ mov(ecx, eax);
|
||||
__ add(ecx, Immediate(1)); // Add one for receiver.
|
||||
__ shl(ecx, kPointerSizeLog2);
|
||||
__ neg(ecx);
|
||||
__ add(ecx, ebx);
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ jmp(&loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ Push(Operand(ebx, 0));
|
||||
__ sub(ebx, Immediate(kPointerSize));
|
||||
__ bind(&loop_check);
|
||||
__ cmp(ebx, ecx);
|
||||
__ j(greater, &loop_header, Label::kNear);
|
||||
|
||||
// Call the target.
|
||||
__ Push(edx); // Re-push return address.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_CompileLazy(MacroAssembler* masm) {
|
||||
CallRuntimePassFunction(masm, Runtime::kCompileLazy);
|
||||
GenerateTailCallToReturnedCode(masm);
|
||||
@ -1626,41 +1661,6 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_PushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- eax : the number of arguments (not including the receiver)
|
||||
// -- ebx : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- edi : the target to call (can be any Object).
|
||||
|
||||
// Pop return address to allow tail-call after pushing arguments.
|
||||
__ Pop(edx);
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ mov(ecx, eax);
|
||||
__ add(ecx, Immediate(1)); // Add one for receiver.
|
||||
__ shl(ecx, kPointerSizeLog2);
|
||||
__ neg(ecx);
|
||||
__ add(ecx, ebx);
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ jmp(&loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ Push(Operand(ebx, 0));
|
||||
__ sub(ebx, Immediate(kPointerSize));
|
||||
__ bind(&loop_check);
|
||||
__ cmp(ebx, ecx);
|
||||
__ j(greater, &loop_header, Label::kNear);
|
||||
|
||||
// Call the target.
|
||||
__ Push(edx); // Re-push return address.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- eax : actual number of arguments
|
||||
|
@ -2425,11 +2425,23 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// esp: stack pointer (restored after C call)
|
||||
// esi: current context (C callee-saved)
|
||||
// edi: JS function of the caller (C callee-saved)
|
||||
//
|
||||
// If argv_in_register():
|
||||
// ecx: pointer to the first argument
|
||||
|
||||
ProfileEntryHookStub::MaybeCallEntryHook(masm);
|
||||
|
||||
// Enter the exit frame that transitions from JavaScript to C++.
|
||||
__ EnterExitFrame(save_doubles());
|
||||
if (argv_in_register()) {
|
||||
DCHECK(!save_doubles());
|
||||
__ EnterApiExitFrame(3);
|
||||
|
||||
// Move argc and argv into the correct registers.
|
||||
__ mov(esi, ecx);
|
||||
__ mov(edi, eax);
|
||||
} else {
|
||||
__ EnterExitFrame(save_doubles());
|
||||
}
|
||||
|
||||
// ebx: pointer to C function (C callee-saved)
|
||||
// ebp: frame pointer (restored after C call)
|
||||
@ -2474,7 +2486,7 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
// Exit the JavaScript to C++ exit frame.
|
||||
__ LeaveExitFrame(save_doubles());
|
||||
__ LeaveExitFrame(save_doubles(), !argv_in_register());
|
||||
__ ret(0);
|
||||
|
||||
// Handling of exception.
|
||||
|
@ -400,16 +400,27 @@ void MathRoundVariantCallFromOptimizedCodeDescriptor::
|
||||
}
|
||||
|
||||
|
||||
void PushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
void InterpreterPushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
eax, // argument count (including receiver)
|
||||
eax, // argument count (not including receiver)
|
||||
ebx, // address of first argument
|
||||
edi // the target callable to be call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
|
||||
void InterpreterCEntryDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
eax, // argument count (argc)
|
||||
ecx, // address of first argument (argv)
|
||||
ebx // the runtime function to call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -974,7 +974,7 @@ void MacroAssembler::EnterApiExitFrame(int argc) {
|
||||
}
|
||||
|
||||
|
||||
void MacroAssembler::LeaveExitFrame(bool save_doubles) {
|
||||
void MacroAssembler::LeaveExitFrame(bool save_doubles, bool pop_arguments) {
|
||||
// Optionally restore all XMM registers.
|
||||
if (save_doubles) {
|
||||
const int offset = -2 * kPointerSize;
|
||||
@ -984,15 +984,20 @@ void MacroAssembler::LeaveExitFrame(bool save_doubles) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the return address from the stack and restore the frame pointer.
|
||||
mov(ecx, Operand(ebp, 1 * kPointerSize));
|
||||
mov(ebp, Operand(ebp, 0 * kPointerSize));
|
||||
if (pop_arguments) {
|
||||
// Get the return address from the stack and restore the frame pointer.
|
||||
mov(ecx, Operand(ebp, 1 * kPointerSize));
|
||||
mov(ebp, Operand(ebp, 0 * kPointerSize));
|
||||
|
||||
// Pop the arguments and the receiver from the caller stack.
|
||||
lea(esp, Operand(esi, 1 * kPointerSize));
|
||||
// Pop the arguments and the receiver from the caller stack.
|
||||
lea(esp, Operand(esi, 1 * kPointerSize));
|
||||
|
||||
// Push the return address to get ready to return.
|
||||
push(ecx);
|
||||
// Push the return address to get ready to return.
|
||||
push(ecx);
|
||||
} else {
|
||||
// Otherwise just leave the exit frame.
|
||||
leave();
|
||||
}
|
||||
|
||||
LeaveExitFrameEpilogue(true);
|
||||
}
|
||||
|
@ -278,8 +278,8 @@ class MacroAssembler: public Assembler {
|
||||
|
||||
// Leave the current exit frame. Expects the return value in
|
||||
// register eax:edx (untouched) and the pointer to the first
|
||||
// argument in register esi.
|
||||
void LeaveExitFrame(bool save_doubles);
|
||||
// argument in register esi (if pop_arguments == true).
|
||||
void LeaveExitFrame(bool save_doubles, bool pop_arguments = true);
|
||||
|
||||
// Leave the current exit frame. Expects the return value in
|
||||
// register eax (untouched).
|
||||
|
@ -38,7 +38,6 @@ class PlatformInterfaceDescriptor;
|
||||
V(CallFunctionWithFeedbackAndVector) \
|
||||
V(CallConstruct) \
|
||||
V(CallTrampoline) \
|
||||
V(PushArgsAndCall) \
|
||||
V(RegExpConstructResult) \
|
||||
V(TransitionElementsKind) \
|
||||
V(AllocateHeapNumber) \
|
||||
@ -70,7 +69,9 @@ class PlatformInterfaceDescriptor;
|
||||
V(ContextOnly) \
|
||||
V(GrowArrayElements) \
|
||||
V(MathRoundVariantCallFromUnoptimizedCode) \
|
||||
V(MathRoundVariantCallFromOptimizedCode)
|
||||
V(MathRoundVariantCallFromOptimizedCode) \
|
||||
V(InterpreterPushArgsAndCall) \
|
||||
V(InterpreterCEntry)
|
||||
|
||||
|
||||
class CallInterfaceDescriptorData {
|
||||
@ -706,11 +707,19 @@ class GrowArrayElementsDescriptor : public CallInterfaceDescriptor {
|
||||
};
|
||||
|
||||
|
||||
class PushArgsAndCallDescriptor : public CallInterfaceDescriptor {
|
||||
class InterpreterPushArgsAndCallDescriptor : public CallInterfaceDescriptor {
|
||||
public:
|
||||
DECLARE_DESCRIPTOR(PushArgsAndCallDescriptor, CallInterfaceDescriptor)
|
||||
DECLARE_DESCRIPTOR(InterpreterPushArgsAndCallDescriptor,
|
||||
CallInterfaceDescriptor)
|
||||
};
|
||||
|
||||
|
||||
class InterpreterCEntryDescriptor : public CallInterfaceDescriptor {
|
||||
public:
|
||||
DECLARE_DESCRIPTOR(InterpreterCEntryDescriptor, CallInterfaceDescriptor)
|
||||
};
|
||||
|
||||
|
||||
#undef DECLARE_DESCRIPTOR
|
||||
|
||||
|
||||
|
@ -161,7 +161,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadLiteral(
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::LoadLiteral(Handle<Object> object) {
|
||||
size_t entry = GetConstantPoolEntry(object);
|
||||
if (FitsInIdxOperand(entry)) {
|
||||
if (FitsInIdx8Operand(entry)) {
|
||||
Output(Bytecode::kLdaConstant, static_cast<uint8_t>(entry));
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
@ -216,7 +216,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::StoreAccumulatorInRegister(
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::LoadGlobal(int slot_index) {
|
||||
DCHECK(slot_index >= 0);
|
||||
if (FitsInIdxOperand(slot_index)) {
|
||||
if (FitsInIdx8Operand(slot_index)) {
|
||||
Output(Bytecode::kLdaGlobal, static_cast<uint8_t>(slot_index));
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
@ -230,7 +230,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadNamedProperty(
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
if (FitsInIdxOperand(feedback_slot)) {
|
||||
if (FitsInIdx8Operand(feedback_slot)) {
|
||||
Output(Bytecode::kLoadIC, object.ToOperand(),
|
||||
static_cast<uint8_t>(feedback_slot));
|
||||
} else {
|
||||
@ -246,7 +246,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::LoadKeyedProperty(
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
if (FitsInIdxOperand(feedback_slot)) {
|
||||
if (FitsInIdx8Operand(feedback_slot)) {
|
||||
Output(Bytecode::kKeyedLoadIC, object.ToOperand(),
|
||||
static_cast<uint8_t>(feedback_slot));
|
||||
} else {
|
||||
@ -263,7 +263,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::StoreNamedProperty(
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
if (FitsInIdxOperand(feedback_slot)) {
|
||||
if (FitsInIdx8Operand(feedback_slot)) {
|
||||
Output(Bytecode::kStoreIC, object.ToOperand(), name.ToOperand(),
|
||||
static_cast<uint8_t>(feedback_slot));
|
||||
} else {
|
||||
@ -280,7 +280,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::StoreKeyedProperty(
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
if (FitsInIdxOperand(feedback_slot)) {
|
||||
if (FitsInIdx8Operand(feedback_slot)) {
|
||||
Output(Bytecode::kKeyedStoreIC, object.ToOperand(), key.ToOperand(),
|
||||
static_cast<uint8_t>(feedback_slot));
|
||||
} else {
|
||||
@ -376,7 +376,7 @@ void BytecodeArrayBuilder::PatchJump(
|
||||
} else {
|
||||
// Update the jump type and operand
|
||||
size_t entry = GetConstantPoolEntry(handle(Smi::FromInt(delta), isolate()));
|
||||
if (FitsInIdxOperand(entry)) {
|
||||
if (FitsInIdx8Operand(entry)) {
|
||||
jump_bytecode = GetJumpWithConstantOperand(jump_bytecode);
|
||||
*jump_location++ = Bytecodes::ToByte(jump_bytecode);
|
||||
*jump_location = static_cast<uint8_t>(entry);
|
||||
@ -414,7 +414,7 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::OutputJump(Bytecode jump_bytecode,
|
||||
Output(jump_bytecode, static_cast<uint8_t>(delta));
|
||||
} else {
|
||||
size_t entry = GetConstantPoolEntry(handle(Smi::FromInt(delta), isolate()));
|
||||
if (FitsInIdxOperand(entry)) {
|
||||
if (FitsInIdx8Operand(entry)) {
|
||||
Output(GetJumpWithConstantOperand(jump_bytecode),
|
||||
static_cast<uint8_t>(entry));
|
||||
} else {
|
||||
@ -467,7 +467,7 @@ void BytecodeArrayBuilder::EnsureReturn() {
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::Call(Register callable,
|
||||
Register receiver,
|
||||
size_t arg_count) {
|
||||
if (FitsInIdxOperand(arg_count)) {
|
||||
if (FitsInIdx8Operand(arg_count)) {
|
||||
Output(Bytecode::kCall, callable.ToOperand(), receiver.ToOperand(),
|
||||
static_cast<uint8_t>(arg_count));
|
||||
} else {
|
||||
@ -477,6 +477,16 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::Call(Register callable,
|
||||
}
|
||||
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::CallRuntime(
|
||||
Runtime::FunctionId function_id, Register first_arg, size_t arg_count) {
|
||||
DCHECK(FitsInIdx16Operand(function_id));
|
||||
DCHECK(FitsInIdx8Operand(arg_count));
|
||||
Output(Bytecode::kCallRuntime, static_cast<uint16_t>(function_id),
|
||||
first_arg.ToOperand(), static_cast<uint8_t>(arg_count));
|
||||
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.
|
||||
@ -597,13 +607,13 @@ Bytecode BytecodeArrayBuilder::BytecodeForCompareOperation(Token::Value op) {
|
||||
|
||||
|
||||
// static
|
||||
bool BytecodeArrayBuilder::FitsInIdxOperand(int value) {
|
||||
bool BytecodeArrayBuilder::FitsInIdx8Operand(int value) {
|
||||
return kMinUInt8 <= value && value <= kMaxUInt8;
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
bool BytecodeArrayBuilder::FitsInIdxOperand(size_t value) {
|
||||
bool BytecodeArrayBuilder::FitsInIdx8Operand(size_t value) {
|
||||
return value <= static_cast<size_t>(kMaxUInt8);
|
||||
}
|
||||
|
||||
@ -614,6 +624,12 @@ bool BytecodeArrayBuilder::FitsInImm8Operand(int value) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
bool BytecodeArrayBuilder::FitsInIdx16Operand(int value) {
|
||||
return kMinUInt16 <= value && value <= kMaxUInt16;
|
||||
}
|
||||
|
||||
|
||||
TemporaryRegisterScope::TemporaryRegisterScope(BytecodeArrayBuilder* builder)
|
||||
: builder_(builder), count_(0), last_register_index_(-1) {}
|
||||
|
||||
|
@ -75,6 +75,12 @@ class BytecodeArrayBuilder {
|
||||
BytecodeArrayBuilder& Call(Register callable, Register receiver,
|
||||
size_t arg_count);
|
||||
|
||||
// Call the runtime function with |function_id|. 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>.
|
||||
BytecodeArrayBuilder& CallRuntime(Runtime::FunctionId function_id,
|
||||
Register first_arg, size_t arg_count);
|
||||
|
||||
// Operators (register == lhs, accumulator = rhs).
|
||||
BytecodeArrayBuilder& BinaryOperation(Token::Value binop, Register reg);
|
||||
|
||||
@ -107,9 +113,12 @@ class BytecodeArrayBuilder {
|
||||
|
||||
static Bytecode BytecodeForBinaryOperation(Token::Value op);
|
||||
static Bytecode BytecodeForCompareOperation(Token::Value op);
|
||||
static bool FitsInIdxOperand(int value);
|
||||
static bool FitsInIdxOperand(size_t value);
|
||||
|
||||
static bool FitsInIdx8Operand(int value);
|
||||
static bool FitsInIdx8Operand(size_t value);
|
||||
static bool FitsInImm8Operand(int value);
|
||||
static bool FitsInIdx16Operand(int value);
|
||||
|
||||
static Bytecode GetJumpWithConstantOperand(Bytecode jump_with_smi8_operand);
|
||||
|
||||
template <size_t N>
|
||||
@ -119,6 +128,7 @@ class BytecodeArrayBuilder {
|
||||
void Output(Bytecode bytecode, uint32_t operand0, uint32_t operand1);
|
||||
void Output(Bytecode bytecode, uint32_t operand0);
|
||||
void Output(Bytecode bytecode);
|
||||
|
||||
BytecodeArrayBuilder& OutputJump(Bytecode jump_bytecode,
|
||||
BytecodeLabel* label);
|
||||
void PatchJump(const ZoneVector<uint8_t>::iterator& jump_target,
|
||||
|
@ -59,6 +59,12 @@ int8_t BytecodeArrayIterator::GetImmediateOperand(int operand_index) const {
|
||||
}
|
||||
|
||||
|
||||
int BytecodeArrayIterator::GetCountOperand(int operand_index) const {
|
||||
uint32_t operand = GetRawOperand(operand_index, OperandType::kCount8);
|
||||
return static_cast<int>(operand);
|
||||
}
|
||||
|
||||
|
||||
int BytecodeArrayIterator::GetIndexOperand(int operand_index) const {
|
||||
OperandSize size =
|
||||
Bytecodes::GetOperandSize(current_bytecode(), operand_index);
|
||||
|
@ -27,6 +27,7 @@ class BytecodeArrayIterator {
|
||||
|
||||
int8_t GetImmediateOperand(int operand_index) const;
|
||||
int GetIndexOperand(int operand_index) const;
|
||||
int GetCountOperand(int operand_index) const;
|
||||
Register GetRegisterOperand(int operand_index) const;
|
||||
Handle<Object> GetConstantForIndexOperand(int operand_index) const;
|
||||
|
||||
|
@ -593,7 +593,30 @@ void BytecodeGenerator::VisitCall(Call* expr) {
|
||||
void BytecodeGenerator::VisitCallNew(CallNew* expr) { UNIMPLEMENTED(); }
|
||||
|
||||
|
||||
void BytecodeGenerator::VisitCallRuntime(CallRuntime* expr) { UNIMPLEMENTED(); }
|
||||
void BytecodeGenerator::VisitCallRuntime(CallRuntime* expr) {
|
||||
if (expr->is_jsruntime()) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
// Evaluate all arguments to the runtime call.
|
||||
ZoneList<Expression*>* args = expr->arguments();
|
||||
TemporaryRegisterScope temporary_register_scope(&builder_);
|
||||
// Ensure we always have a valid first_arg register even if there are no
|
||||
// arguments to pass.
|
||||
Register first_arg = temporary_register_scope.NewRegister();
|
||||
for (int i = 0; i < args->length(); ++i) {
|
||||
Register arg =
|
||||
(i == 0) ? first_arg : temporary_register_scope.NewRegister();
|
||||
Visit(args->at(i));
|
||||
DCHECK_EQ(arg.index() - i, first_arg.index());
|
||||
builder()->StoreAccumulatorInRegister(arg);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
|
||||
void BytecodeGenerator::VisitUnaryOperation(UnaryOperation* expr) {
|
||||
|
@ -67,6 +67,8 @@ namespace interpreter {
|
||||
\
|
||||
/* Call operations. */ \
|
||||
V(Call, OperandType::kReg8, OperandType::kReg8, OperandType::kCount8) \
|
||||
V(CallRuntime, OperandType::kIdx16, OperandType::kReg8, \
|
||||
OperandType::kCount8) \
|
||||
\
|
||||
/* Test Operators */ \
|
||||
V(TestEqual, OperandType::kReg8) \
|
||||
|
@ -334,10 +334,10 @@ void Interpreter::DoMod(compiler::InterpreterAssembler* assembler) {
|
||||
}
|
||||
|
||||
|
||||
// Call <receiver> <arg_count>
|
||||
// Call <callable> <receiver> <arg_count>
|
||||
//
|
||||
// Call a JS function with receiver and |arg_count| arguments in subsequent
|
||||
// registers. The JSfunction or Callable to call is in the accumulator.
|
||||
// Call a JSfunction or Callable in |callable| with receiver and |arg_count|
|
||||
// arguments in subsequent registers.
|
||||
void Interpreter::DoCall(compiler::InterpreterAssembler* assembler) {
|
||||
Node* function_reg = __ BytecodeOperandReg8(0);
|
||||
Node* function = __ LoadRegister(function_reg);
|
||||
@ -350,6 +350,21 @@ void Interpreter::DoCall(compiler::InterpreterAssembler* assembler) {
|
||||
}
|
||||
|
||||
|
||||
// CallRuntime <function_id> <first_arg> <arg_count>
|
||||
//
|
||||
// Call the runtime function |function_id| with first argument in register
|
||||
// |first_arg| and |arg_count| arguments in subsequent registers.
|
||||
void Interpreter::DoCallRuntime(compiler::InterpreterAssembler* assembler) {
|
||||
Node* function_id = __ BytecodeOperandIdx16(0);
|
||||
Node* first_arg_reg = __ BytecodeOperandReg8(1);
|
||||
Node* first_arg = __ RegisterLocation(first_arg_reg);
|
||||
Node* args_count = __ BytecodeOperandCount8(2);
|
||||
Node* result = __ CallRuntime(function_id, first_arg, args_count);
|
||||
__ SetAccumulator(result);
|
||||
__ Dispatch();
|
||||
}
|
||||
|
||||
|
||||
// TestEqual <src>
|
||||
//
|
||||
// Test if the value in the <src> register equals the accumulator.
|
||||
|
@ -963,6 +963,35 @@ void Builtins::Generate_InterpreterExitTrampoline(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_InterpreterPushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- a0 : the number of arguments (not including the receiver)
|
||||
// -- a2 : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- a1 : the target to call (can be any Object).
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ Addu(a3, a0, Operand(1)); // Add one for receiver.
|
||||
__ sll(a3, a3, kPointerSizeLog2);
|
||||
__ Subu(a3, a2, Operand(a3));
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ Branch(&loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ lw(t0, MemOperand(a2));
|
||||
__ Addu(a2, a2, Operand(-kPointerSize));
|
||||
__ push(t0);
|
||||
__ bind(&loop_check);
|
||||
__ Branch(&loop_header, gt, a2, Operand(a3));
|
||||
|
||||
// Call the target.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_CompileLazy(MacroAssembler* masm) {
|
||||
CallRuntimePassFunction(masm, Runtime::kCompileLazy);
|
||||
GenerateTailCallToReturnedCode(masm);
|
||||
@ -1710,35 +1739,6 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_PushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- a0 : the number of arguments (not including the receiver)
|
||||
// -- a2 : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- a1 : the target to call (can be any Object).
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ Addu(a3, a0, Operand(1)); // Add one for receiver.
|
||||
__ sll(a3, a3, kPointerSizeLog2);
|
||||
__ Subu(a3, a2, Operand(a3));
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ Branch(&loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ lw(t0, MemOperand(a2));
|
||||
__ Addu(a2, a2, Operand(-kPointerSize));
|
||||
__ push(t0);
|
||||
__ bind(&loop_check);
|
||||
__ Branch(&loop_header, gt, a2, Operand(a3));
|
||||
|
||||
// Call the target.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
|
||||
// State setup as expected by MacroAssembler::InvokePrologue.
|
||||
// ----------- S t a t e -------------
|
||||
|
@ -1066,13 +1066,20 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// fp: frame pointer (restored after C call)
|
||||
// sp: stack pointer (restored as callee's sp after C call)
|
||||
// cp: current context (C callee-saved)
|
||||
//
|
||||
// If argv_in_register():
|
||||
// a2: pointer to the first argument
|
||||
|
||||
ProfileEntryHookStub::MaybeCallEntryHook(masm);
|
||||
|
||||
// Compute the argv pointer in a callee-saved register.
|
||||
__ sll(s1, a0, kPointerSizeLog2);
|
||||
__ Addu(s1, sp, s1);
|
||||
__ Subu(s1, s1, kPointerSize);
|
||||
if (!argv_in_register()) {
|
||||
// Compute the argv pointer in a callee-saved register.
|
||||
__ sll(s1, a0, kPointerSizeLog2);
|
||||
__ Addu(s1, sp, s1);
|
||||
__ Subu(s1, s1, kPointerSize);
|
||||
} else {
|
||||
__ mov(s1, a2);
|
||||
}
|
||||
|
||||
// Enter the exit frame that transitions from JavaScript to C++.
|
||||
FrameScope scope(masm, StackFrame::MANUAL);
|
||||
@ -1153,8 +1160,12 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// v0:v1: result
|
||||
// sp: stack pointer
|
||||
// fp: frame pointer
|
||||
// s0: still holds argc (callee-saved).
|
||||
__ LeaveExitFrame(save_doubles(), s0, true, EMIT_RETURN);
|
||||
Register argc;
|
||||
if (!argv_in_register()) {
|
||||
// s0: still holds argc (callee-saved).
|
||||
argc = s0;
|
||||
}
|
||||
__ LeaveExitFrame(save_doubles(), argc, true, EMIT_RETURN);
|
||||
|
||||
// Handling of exception.
|
||||
__ bind(&exception_returned);
|
||||
|
@ -392,16 +392,27 @@ void MathRoundVariantCallFromOptimizedCodeDescriptor::
|
||||
}
|
||||
|
||||
|
||||
void PushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
void InterpreterPushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
a0, // argument count (including receiver)
|
||||
a0, // argument count (not including receiver)
|
||||
a2, // address of first argument
|
||||
a1 // the target callable to be call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
|
||||
void InterpreterCEntryDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
a0, // argument count (argc)
|
||||
a2, // address of first argument (argv)
|
||||
a1 // the runtime function to call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -960,6 +960,35 @@ void Builtins::Generate_InterpreterExitTrampoline(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_InterpreterPushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- a0 : the number of arguments (not including the receiver)
|
||||
// -- a2 : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- a1 : the target to call (can be any Object).
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ Addu(a3, a0, Operand(1)); // Add one for receiver.
|
||||
__ sll(a3, a3, kPointerSizeLog2);
|
||||
__ Subu(a3, a2, Operand(a3));
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ Branch(&loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ lw(t0, MemOperand(a2));
|
||||
__ Addu(a2, a2, Operand(-kPointerSize));
|
||||
__ push(t0);
|
||||
__ bind(&loop_check);
|
||||
__ Branch(&loop_header, gt, a2, Operand(a3));
|
||||
|
||||
// Call the target.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_CompileLazy(MacroAssembler* masm) {
|
||||
CallRuntimePassFunction(masm, Runtime::kCompileLazy);
|
||||
GenerateTailCallToReturnedCode(masm);
|
||||
@ -1706,35 +1735,6 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_PushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- a0 : the number of arguments (not including the receiver)
|
||||
// -- a2 : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- a1 : the target to call (can be any Object).
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ Daddu(a3, a0, Operand(1)); // Add one for receiver.
|
||||
__ dsll(a3, a3, kPointerSizeLog2);
|
||||
__ Dsubu(a3, a2, Operand(a3));
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ Branch(&loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ ld(a4, MemOperand(a2));
|
||||
__ Daddu(a2, a2, Operand(-kPointerSize));
|
||||
__ push(a4);
|
||||
__ bind(&loop_check);
|
||||
__ Branch(&loop_header, gt, a2, Operand(a3));
|
||||
|
||||
// Call the target.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
|
||||
// State setup as expected by MacroAssembler::InvokePrologue.
|
||||
// ----------- S t a t e -------------
|
||||
|
@ -1064,13 +1064,20 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// fp: frame pointer (restored after C call)
|
||||
// sp: stack pointer (restored as callee's sp after C call)
|
||||
// cp: current context (C callee-saved)
|
||||
//
|
||||
// If argv_in_register():
|
||||
// a2: pointer to the first argument
|
||||
|
||||
ProfileEntryHookStub::MaybeCallEntryHook(masm);
|
||||
|
||||
// Compute the argv pointer in a callee-saved register.
|
||||
__ dsll(s1, a0, kPointerSizeLog2);
|
||||
__ Daddu(s1, sp, s1);
|
||||
__ Dsubu(s1, s1, kPointerSize);
|
||||
if (!argv_in_register()) {
|
||||
// Compute the argv pointer in a callee-saved register.
|
||||
__ dsll(s1, a0, kPointerSizeLog2);
|
||||
__ Daddu(s1, sp, s1);
|
||||
__ Dsubu(s1, s1, kPointerSize);
|
||||
} else {
|
||||
__ mov(s1, a2);
|
||||
}
|
||||
|
||||
// Enter the exit frame that transitions from JavaScript to C++.
|
||||
FrameScope scope(masm, StackFrame::MANUAL);
|
||||
@ -1150,8 +1157,12 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// v0:v1: result
|
||||
// sp: stack pointer
|
||||
// fp: frame pointer
|
||||
// s0: still holds argc (callee-saved).
|
||||
__ LeaveExitFrame(save_doubles(), s0, true, EMIT_RETURN);
|
||||
Register argc;
|
||||
if (!argv_in_register()) {
|
||||
// s0: still holds argc (callee-saved).
|
||||
argc = s0;
|
||||
}
|
||||
__ LeaveExitFrame(save_doubles(), argc, true, EMIT_RETURN);
|
||||
|
||||
// Handling of exception.
|
||||
__ bind(&exception_returned);
|
||||
|
@ -392,16 +392,27 @@ void MathRoundVariantCallFromOptimizedCodeDescriptor::
|
||||
}
|
||||
|
||||
|
||||
void PushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
void InterpreterPushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
a0, // argument count (including receiver)
|
||||
a0, // argument count (not including receiver)
|
||||
a2, // address of first argument
|
||||
a1 // the target callable to be call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
|
||||
void InterpreterCEntryDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
a0, // argument count (argc)
|
||||
a2, // address of first argument (argv)
|
||||
a1 // the runtime function to call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "src/runtime/runtime.h"
|
||||
|
||||
#include "src/assembler.h"
|
||||
#include "src/contexts.h"
|
||||
#include "src/handles-inl.h"
|
||||
#include "src/heap/heap.h"
|
||||
@ -94,6 +95,31 @@ const Runtime::Function* Runtime::FunctionForId(Runtime::FunctionId id) {
|
||||
}
|
||||
|
||||
|
||||
const Runtime::Function* Runtime::RuntimeFunctionTable(Isolate* isolate) {
|
||||
if (isolate->external_reference_redirector()) {
|
||||
// When running with the simulator we need to provide a table which has
|
||||
// redirected runtime entry addresses.
|
||||
if (!isolate->runtime_state()->redirected_intrinsic_functions()) {
|
||||
size_t function_count = arraysize(kIntrinsicFunctions);
|
||||
Function* redirected_functions = new Function[function_count];
|
||||
memcpy(redirected_functions, kIntrinsicFunctions,
|
||||
sizeof(kIntrinsicFunctions));
|
||||
for (size_t i = 0; i < function_count; i++) {
|
||||
ExternalReference redirected_entry(static_cast<Runtime::FunctionId>(i),
|
||||
isolate);
|
||||
redirected_functions[i].entry = redirected_entry.address();
|
||||
}
|
||||
isolate->runtime_state()->set_redirected_intrinsic_functions(
|
||||
redirected_functions);
|
||||
}
|
||||
|
||||
return isolate->runtime_state()->redirected_intrinsic_functions();
|
||||
} else {
|
||||
return kIntrinsicFunctions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Runtime::FunctionId id) {
|
||||
return os << Runtime::FunctionForId(id)->name;
|
||||
}
|
||||
|
@ -1110,27 +1110,6 @@ FOR_EACH_INTRINSIC_RETURN_OBJECT(F)
|
||||
//---------------------------------------------------------------------------
|
||||
// Runtime provides access to all C++ runtime functions.
|
||||
|
||||
class RuntimeState {
|
||||
public:
|
||||
unibrow::Mapping<unibrow::ToUppercase, 128>* to_upper_mapping() {
|
||||
return &to_upper_mapping_;
|
||||
}
|
||||
unibrow::Mapping<unibrow::ToLowercase, 128>* to_lower_mapping() {
|
||||
return &to_lower_mapping_;
|
||||
}
|
||||
|
||||
private:
|
||||
RuntimeState() {}
|
||||
unibrow::Mapping<unibrow::ToUppercase, 128> to_upper_mapping_;
|
||||
unibrow::Mapping<unibrow::ToLowercase, 128> to_lower_mapping_;
|
||||
|
||||
friend class Isolate;
|
||||
friend class Runtime;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RuntimeState);
|
||||
};
|
||||
|
||||
|
||||
class Runtime : public AllStatic {
|
||||
public:
|
||||
enum FunctionId {
|
||||
@ -1179,6 +1158,9 @@ class Runtime : public AllStatic {
|
||||
// Get the intrinsic function with the given function entry address.
|
||||
static const Function* FunctionForEntry(Address ref);
|
||||
|
||||
// Get the runtime intrinsic function table.
|
||||
static const Function* RuntimeFunctionTable(Isolate* isolate);
|
||||
|
||||
MUST_USE_RESULT static MaybeHandle<Object> DeleteObjectProperty(
|
||||
Isolate* isolate, Handle<JSReceiver> receiver, Handle<Object> key,
|
||||
LanguageMode language_mode);
|
||||
@ -1229,6 +1211,38 @@ class Runtime : public AllStatic {
|
||||
};
|
||||
|
||||
|
||||
class RuntimeState {
|
||||
public:
|
||||
unibrow::Mapping<unibrow::ToUppercase, 128>* to_upper_mapping() {
|
||||
return &to_upper_mapping_;
|
||||
}
|
||||
unibrow::Mapping<unibrow::ToLowercase, 128>* to_lower_mapping() {
|
||||
return &to_lower_mapping_;
|
||||
}
|
||||
|
||||
Runtime::Function* redirected_intrinsic_functions() {
|
||||
return redirected_intrinsic_functions_.get();
|
||||
}
|
||||
|
||||
void set_redirected_intrinsic_functions(
|
||||
Runtime::Function* redirected_intrinsic_functions) {
|
||||
redirected_intrinsic_functions_.Reset(redirected_intrinsic_functions);
|
||||
}
|
||||
|
||||
private:
|
||||
RuntimeState() {}
|
||||
unibrow::Mapping<unibrow::ToUppercase, 128> to_upper_mapping_;
|
||||
unibrow::Mapping<unibrow::ToLowercase, 128> to_lower_mapping_;
|
||||
|
||||
base::SmartArrayPointer<Runtime::Function> redirected_intrinsic_functions_;
|
||||
|
||||
friend class Isolate;
|
||||
friend class Runtime;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RuntimeState);
|
||||
};
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream&, Runtime::FunctionId);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -131,6 +131,8 @@ ExternalReferenceTable::ExternalReferenceTable(Isolate* isolate) {
|
||||
"Isolate::stress_deopt_count_address()");
|
||||
Add(ExternalReference::vector_store_virtual_register(isolate).address(),
|
||||
"Isolate::vector_store_virtual_register()");
|
||||
Add(ExternalReference::runtime_function_table_address(isolate).address(),
|
||||
"Runtime::runtime_function_table_address()");
|
||||
|
||||
// Debug addresses
|
||||
Add(ExternalReference::debug_after_break_target_address(isolate).address(),
|
||||
|
@ -771,6 +771,41 @@ void Builtins::Generate_InterpreterExitTrampoline(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_InterpreterPushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- rax : the number of arguments (not including the receiver)
|
||||
// -- rbx : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- rdi : the target to call (can be any Object).
|
||||
|
||||
// Pop return address to allow tail-call after pushing arguments.
|
||||
__ Pop(rdx);
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ movp(rcx, rax);
|
||||
__ addp(rcx, Immediate(1)); // Add one for receiver.
|
||||
__ shlp(rcx, Immediate(kPointerSizeLog2));
|
||||
__ negp(rcx);
|
||||
__ addp(rcx, rbx);
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ j(always, &loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ Push(Operand(rbx, 0));
|
||||
__ subp(rbx, Immediate(kPointerSize));
|
||||
__ bind(&loop_check);
|
||||
__ cmpp(rbx, rcx);
|
||||
__ j(greater, &loop_header, Label::kNear);
|
||||
|
||||
// Call the target.
|
||||
__ Push(rdx); // Re-push return address.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_CompileLazy(MacroAssembler* masm) {
|
||||
CallRuntimePassFunction(masm, Runtime::kCompileLazy);
|
||||
GenerateTailCallToReturnedCode(masm);
|
||||
@ -1832,41 +1867,6 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Builtins::Generate_PushArgsAndCall(MacroAssembler* masm) {
|
||||
// ----------- S t a t e -------------
|
||||
// -- rax : the number of arguments (not including the receiver)
|
||||
// -- rbx : the address of the first argument to be pushed. Subsequent
|
||||
// arguments should be consecutive above this, in the same order as
|
||||
// they are to be pushed onto the stack.
|
||||
// -- rdi : the target to call (can be any Object).
|
||||
|
||||
// Pop return address to allow tail-call after pushing arguments.
|
||||
__ Pop(rdx);
|
||||
|
||||
// Find the address of the last argument.
|
||||
__ movp(rcx, rax);
|
||||
__ addp(rcx, Immediate(1)); // Add one for receiver.
|
||||
__ shlp(rcx, Immediate(kPointerSizeLog2));
|
||||
__ negp(rcx);
|
||||
__ addp(rcx, rbx);
|
||||
|
||||
// Push the arguments.
|
||||
Label loop_header, loop_check;
|
||||
__ j(always, &loop_check);
|
||||
__ bind(&loop_header);
|
||||
__ Push(Operand(rbx, 0));
|
||||
__ subp(rbx, Immediate(kPointerSize));
|
||||
__ bind(&loop_check);
|
||||
__ cmpp(rbx, rcx);
|
||||
__ j(greater, &loop_header, Label::kNear);
|
||||
|
||||
// Call the target.
|
||||
__ Push(rdx); // Re-push return address.
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
|
||||
void Builtins::Generate_OnStackReplacement(MacroAssembler* masm) {
|
||||
// Lookup the function in the JavaScript frame.
|
||||
__ movp(rax, Operand(rbp, JavaScriptFrameConstants::kFunctionOffset));
|
||||
|
@ -2268,6 +2268,9 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
// rbp: frame pointer of calling JS frame (restored after C call)
|
||||
// rsp: stack pointer (restored after C call)
|
||||
// rsi: current context (restored)
|
||||
//
|
||||
// If argv_in_register():
|
||||
// r15: pointer to the first argument
|
||||
|
||||
ProfileEntryHookStub::MaybeCallEntryHook(masm);
|
||||
|
||||
@ -2277,7 +2280,14 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
#else // _WIN64
|
||||
int arg_stack_space = 0;
|
||||
#endif // _WIN64
|
||||
__ EnterExitFrame(arg_stack_space, save_doubles());
|
||||
if (argv_in_register()) {
|
||||
DCHECK(!save_doubles());
|
||||
__ EnterApiExitFrame(arg_stack_space);
|
||||
// Move argc into r14 (argv is already in r15).
|
||||
__ movp(r14, rax);
|
||||
} else {
|
||||
__ EnterExitFrame(arg_stack_space, save_doubles());
|
||||
}
|
||||
|
||||
// rbx: pointer to builtin function (C callee-saved).
|
||||
// rbp: frame pointer of exit frame (restored after C call).
|
||||
@ -2357,7 +2367,7 @@ void CEntryStub::Generate(MacroAssembler* masm) {
|
||||
}
|
||||
|
||||
// Exit the JavaScript to C++ exit frame.
|
||||
__ LeaveExitFrame(save_doubles());
|
||||
__ LeaveExitFrame(save_doubles(), !argv_in_register());
|
||||
__ ret(0);
|
||||
|
||||
// Handling of exception.
|
||||
|
@ -392,16 +392,27 @@ void MathRoundVariantCallFromOptimizedCodeDescriptor::
|
||||
}
|
||||
|
||||
|
||||
void PushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
void InterpreterPushArgsAndCallDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
rax, // argument count (including receiver)
|
||||
rax, // argument count (not including receiver)
|
||||
rbx, // address of first argument
|
||||
rdi // the target callable to be call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
|
||||
void InterpreterCEntryDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
Register registers[] = {
|
||||
rax, // argument count (argc)
|
||||
r15, // address of first argument (argv)
|
||||
rbx // the runtime function to call
|
||||
};
|
||||
data->InitializePlatformSpecific(arraysize(registers), registers);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
|
@ -3757,7 +3757,7 @@ void MacroAssembler::EnterApiExitFrame(int arg_stack_space) {
|
||||
}
|
||||
|
||||
|
||||
void MacroAssembler::LeaveExitFrame(bool save_doubles) {
|
||||
void MacroAssembler::LeaveExitFrame(bool save_doubles, bool pop_arguments) {
|
||||
// Registers:
|
||||
// r15 : argv
|
||||
if (save_doubles) {
|
||||
@ -3769,15 +3769,21 @@ void MacroAssembler::LeaveExitFrame(bool save_doubles) {
|
||||
movsd(reg, Operand(rbp, offset - ((i + 1) * kDoubleSize)));
|
||||
}
|
||||
}
|
||||
// Get the return address from the stack and restore the frame pointer.
|
||||
movp(rcx, Operand(rbp, kFPOnStackSize));
|
||||
movp(rbp, Operand(rbp, 0 * kPointerSize));
|
||||
|
||||
// Drop everything up to and including the arguments and the receiver
|
||||
// from the caller stack.
|
||||
leap(rsp, Operand(r15, 1 * kPointerSize));
|
||||
if (pop_arguments) {
|
||||
// Get the return address from the stack and restore the frame pointer.
|
||||
movp(rcx, Operand(rbp, kFPOnStackSize));
|
||||
movp(rbp, Operand(rbp, 0 * kPointerSize));
|
||||
|
||||
PushReturnAddressFrom(rcx);
|
||||
// Drop everything up to and including the arguments and the receiver
|
||||
// from the caller stack.
|
||||
leap(rsp, Operand(r15, 1 * kPointerSize));
|
||||
|
||||
PushReturnAddressFrom(rcx);
|
||||
} else {
|
||||
// Otherwise just leave the exit frame.
|
||||
leave();
|
||||
}
|
||||
|
||||
LeaveExitFrameEpilogue(true);
|
||||
}
|
||||
|
@ -342,8 +342,8 @@ class MacroAssembler: public Assembler {
|
||||
|
||||
// Leave the current exit frame. Expects/provides the return value in
|
||||
// register rax:rdx (untouched) and the pointer to the first
|
||||
// argument in register rsi.
|
||||
void LeaveExitFrame(bool save_doubles = false);
|
||||
// argument in register rsi (if pop_arguments == true).
|
||||
void LeaveExitFrame(bool save_doubles = false, bool pop_arguments = true);
|
||||
|
||||
// Leave the current exit frame. Expects/provides the return value in
|
||||
// register rax (untouched).
|
||||
|
@ -27,6 +27,7 @@ class BytecodeGeneratorHelper {
|
||||
i::FLAG_ignition = true;
|
||||
i::FLAG_ignition_filter = StrDup(kFunctionName);
|
||||
i::FLAG_always_opt = false;
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
CcTest::i_isolate()->interpreter()->Initialize();
|
||||
}
|
||||
|
||||
@ -64,6 +65,15 @@ class BytecodeGeneratorHelper {
|
||||
#define U8(x) static_cast<uint8_t>((x) & 0xff)
|
||||
#define R(x) static_cast<uint8_t>(-(x) & 0xff)
|
||||
#define _ static_cast<uint8_t>(0x5a)
|
||||
#if defined(V8_TARGET_LITTLE_ENDIAN)
|
||||
#define U16(x) static_cast<uint8_t>((x) & 0xff), \
|
||||
static_cast<uint8_t>(((x) >> kBitsPerByte) & 0xff)
|
||||
#elif defined(V8_TARGET_BIG_ENDIAN)
|
||||
#define U16(x) static_cast<uint8_t>(((x) >> kBitsPerByte) & 0xff), \
|
||||
static_cast<uint8_t>((x) & 0xff)
|
||||
#else
|
||||
#error Unknown byte ordering
|
||||
#endif
|
||||
|
||||
|
||||
// Structure for containing expected bytecode snippets.
|
||||
@ -684,20 +694,26 @@ TEST(LoadGlobal) {
|
||||
InitializedHandleScope handle_scope;
|
||||
BytecodeGeneratorHelper helper;
|
||||
|
||||
ExpectedSnippet<const char*> snippets[] = {
|
||||
{"var a = 1;\nfunction f() { return a; }\nf()",
|
||||
0, 1, 3,
|
||||
{
|
||||
B(LdaGlobal), _,
|
||||
B(Return)
|
||||
},
|
||||
ExpectedSnippet<int> snippets[] = {
|
||||
{
|
||||
"var a = 1;\nfunction f() { return a; }\nf()",
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
{
|
||||
B(LdaGlobal), _, //
|
||||
B(Return) //
|
||||
},
|
||||
},
|
||||
{"function t() { }\nfunction f() { return t; }\nf()",
|
||||
0, 1, 3,
|
||||
{
|
||||
B(LdaGlobal), _,
|
||||
B(Return)
|
||||
},
|
||||
{
|
||||
"function t() { }\nfunction f() { return t; }\nf()",
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
{
|
||||
B(LdaGlobal), _, //
|
||||
B(Return) //
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -713,34 +729,93 @@ TEST(CallGlobal) {
|
||||
InitializedHandleScope handle_scope;
|
||||
BytecodeGeneratorHelper helper;
|
||||
|
||||
ExpectedSnippet<const char*> snippets[] = {
|
||||
{"function t() { }\nfunction f() { return t(); }\nf()",
|
||||
2 * kPointerSize, 1, 12,
|
||||
{
|
||||
B(LdaUndefined),
|
||||
B(Star), R(1),
|
||||
B(LdaGlobal), _,
|
||||
B(Star), R(0),
|
||||
B(Call), R(0), R(1), U8(0),
|
||||
B(Return)
|
||||
},
|
||||
ExpectedSnippet<int> snippets[] = {
|
||||
{
|
||||
"function t() { }\nfunction f() { return t(); }\nf()",
|
||||
2 * kPointerSize,
|
||||
1,
|
||||
12,
|
||||
{
|
||||
B(LdaUndefined), //
|
||||
B(Star), R(1), //
|
||||
B(LdaGlobal), _, //
|
||||
B(Star), R(0), //
|
||||
B(Call), R(0), R(1), U8(0), //
|
||||
B(Return) //
|
||||
},
|
||||
},
|
||||
{"function t(a, b, c) { }\nfunction f() { return t(1, 2, 3); }\nf()",
|
||||
5 * kPointerSize, 1, 24,
|
||||
{
|
||||
B(LdaUndefined),
|
||||
B(Star), R(1),
|
||||
B(LdaGlobal), _,
|
||||
B(Star), R(0),
|
||||
B(LdaSmi8), U8(1),
|
||||
B(Star), R(2),
|
||||
B(LdaSmi8), U8(2),
|
||||
B(Star), R(3),
|
||||
B(LdaSmi8), U8(3),
|
||||
B(Star), R(4),
|
||||
B(Call), R(0), R(1), U8(3),
|
||||
B(Return)
|
||||
},
|
||||
{
|
||||
"function t(a, b, c) { }\nfunction f() { return t(1, 2, 3); }\nf()",
|
||||
5 * kPointerSize,
|
||||
1,
|
||||
24,
|
||||
{
|
||||
B(LdaUndefined), //
|
||||
B(Star), R(1), //
|
||||
B(LdaGlobal), _, //
|
||||
B(Star), R(0), //
|
||||
B(LdaSmi8), U8(1), //
|
||||
B(Star), R(2), //
|
||||
B(LdaSmi8), U8(2), //
|
||||
B(Star), R(3), //
|
||||
B(LdaSmi8), U8(3), //
|
||||
B(Star), R(4), //
|
||||
B(Call), R(0), R(1), U8(3), //
|
||||
B(Return) //
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
size_t num_snippets = sizeof(snippets) / sizeof(snippets[0]);
|
||||
for (size_t i = 0; i < num_snippets; i++) {
|
||||
Handle<BytecodeArray> bytecode_array =
|
||||
helper.MakeBytecode(snippets[i].code_snippet, "f");
|
||||
CheckBytecodeArrayEqual(snippets[i], bytecode_array, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(CallRuntime) {
|
||||
InitializedHandleScope handle_scope;
|
||||
BytecodeGeneratorHelper helper;
|
||||
|
||||
ExpectedSnippet<int> snippets[] = {
|
||||
{
|
||||
"function f() { %TheHole() }\nf()",
|
||||
1 * kPointerSize,
|
||||
1,
|
||||
7,
|
||||
{
|
||||
B(CallRuntime), U16(Runtime::kTheHole), R(0), U8(0), //
|
||||
B(LdaUndefined), //
|
||||
B(Return) //
|
||||
},
|
||||
},
|
||||
{
|
||||
"function f(a) { return %IsArray(a) }\nf(undefined)",
|
||||
1 * kPointerSize,
|
||||
2,
|
||||
10,
|
||||
{
|
||||
B(Ldar), R(helper.kLastParamIndex), //
|
||||
B(Star), R(0), //
|
||||
B(CallRuntime), U16(Runtime::kIsArray), R(0), U8(1), //
|
||||
B(Return) //
|
||||
},
|
||||
},
|
||||
{
|
||||
"function f() { return %Add(1, 2) }\nf()",
|
||||
2 * kPointerSize,
|
||||
1,
|
||||
14,
|
||||
{
|
||||
B(LdaSmi8), U8(1), //
|
||||
B(Star), R(0), //
|
||||
B(LdaSmi8), U8(2), //
|
||||
B(Star), R(1), //
|
||||
B(CallRuntime), U16(Runtime::kAdd), R(0), U8(2), //
|
||||
B(Return) //
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1289,3 +1289,25 @@ TEST(InterpreterTestIn) {
|
||||
CHECK_EQ(return_value->BooleanValue(), expected_value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(InterpreterCallRuntime) {
|
||||
HandleAndZoneScope handles;
|
||||
|
||||
BytecodeArrayBuilder builder(handles.main_isolate(), handles.main_zone());
|
||||
builder.set_locals_count(2);
|
||||
builder.set_parameter_count(1);
|
||||
builder.LoadLiteral(Smi::FromInt(15))
|
||||
.StoreAccumulatorInRegister(Register(0))
|
||||
.LoadLiteral(Smi::FromInt(40))
|
||||
.StoreAccumulatorInRegister(Register(1))
|
||||
.CallRuntime(Runtime::kAdd, Register(0), 2)
|
||||
.Return();
|
||||
Handle<BytecodeArray> bytecode_array = builder.ToBytecodeArray();
|
||||
|
||||
InterpreterTester tester(handles.main_isolate(), bytecode_array);
|
||||
auto callable = tester.GetCallable<>();
|
||||
|
||||
Handle<Object> return_val = callable().ToHandleChecked();
|
||||
CHECK_EQ(Smi::cast(*return_val), Smi::FromInt(55));
|
||||
}
|
||||
|
@ -503,7 +503,7 @@ TARGET_TEST_F(InterpreterAssemblerTest, LoadObjectField) {
|
||||
}
|
||||
|
||||
|
||||
TARGET_TEST_F(InterpreterAssemblerTest, CallRuntime) {
|
||||
TARGET_TEST_F(InterpreterAssemblerTest, CallRuntime2) {
|
||||
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
|
||||
InterpreterAssemblerForTest m(this, bytecode);
|
||||
Node* arg1 = m.Int32Constant(2);
|
||||
@ -516,6 +516,33 @@ TARGET_TEST_F(InterpreterAssemblerTest, CallRuntime) {
|
||||
}
|
||||
|
||||
|
||||
TARGET_TEST_F(InterpreterAssemblerTest, CallRuntime) {
|
||||
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
|
||||
InterpreterAssemblerForTest m(this, bytecode);
|
||||
Callable builtin = CodeFactory::InterpreterCEntry(isolate());
|
||||
|
||||
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(kMachPtr, function,
|
||||
IsInt32Constant(offsetof(Runtime::Function, entry)));
|
||||
|
||||
Node* call_runtime = m.CallRuntime(function_id, first_arg, arg_count);
|
||||
EXPECT_THAT(call_runtime,
|
||||
m.IsCall(_, IsHeapConstant(builtin.code()), arg_count,
|
||||
first_arg, function_entry,
|
||||
IsParameter(Linkage::kInterpreterContextParameter)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TARGET_TEST_F(InterpreterAssemblerTest, CallIC) {
|
||||
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
|
||||
InterpreterAssemblerForTest m(this, bytecode);
|
||||
@ -536,7 +563,7 @@ TARGET_TEST_F(InterpreterAssemblerTest, CallIC) {
|
||||
TARGET_TEST_F(InterpreterAssemblerTest, CallJS) {
|
||||
TRACED_FOREACH(interpreter::Bytecode, bytecode, kBytecodes) {
|
||||
InterpreterAssemblerForTest m(this, bytecode);
|
||||
Callable builtin = CodeFactory::PushArgsAndCall(isolate());
|
||||
Callable builtin = CodeFactory::InterpreterPushArgsAndCall(isolate());
|
||||
Node* function = m.Int32Constant(0);
|
||||
Node* first_arg = m.Int32Constant(1);
|
||||
Node* arg_count = m.Int32Constant(2);
|
||||
|
@ -51,6 +51,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
|
||||
|
||||
// Call operations.
|
||||
builder.Call(reg, reg, 0);
|
||||
builder.CallRuntime(Runtime::kIsArray, reg, 1);
|
||||
|
||||
// Emit binary operator invocations.
|
||||
builder.BinaryOperation(Token::Value::ADD, reg)
|
||||
|
@ -37,9 +37,6 @@ TEST_F(BytecodeArrayIteratorTest, IteratesBytecodeArray) {
|
||||
Register reg_2 = Register::FromParameterIndex(2, builder.parameter_count());
|
||||
int feedback_slot = 97;
|
||||
|
||||
// TODO(rmcilroy): Add a test for a bytecode with a short operand when
|
||||
// the CallRuntime bytecode is landed.
|
||||
|
||||
builder.LoadLiteral(heap_num_0)
|
||||
.LoadLiteral(heap_num_1)
|
||||
.LoadLiteral(zero)
|
||||
@ -48,6 +45,7 @@ TEST_F(BytecodeArrayIteratorTest, IteratesBytecodeArray) {
|
||||
.LoadAccumulatorWithRegister(reg_0)
|
||||
.LoadNamedProperty(reg_1, feedback_slot, LanguageMode::SLOPPY)
|
||||
.StoreAccumulatorInRegister(reg_2)
|
||||
.CallRuntime(Runtime::kLoadIC_Miss, reg_0, 1)
|
||||
.Return();
|
||||
|
||||
// Test iterator sees the expected output from the builder.
|
||||
@ -92,6 +90,14 @@ TEST_F(BytecodeArrayIteratorTest, IteratesBytecodeArray) {
|
||||
CHECK(!iterator.done());
|
||||
iterator.Advance();
|
||||
|
||||
CHECK_EQ(iterator.current_bytecode(), Bytecode::kCallRuntime);
|
||||
CHECK_EQ(static_cast<Runtime::FunctionId>(iterator.GetIndexOperand(0)),
|
||||
Runtime::kLoadIC_Miss);
|
||||
CHECK_EQ(iterator.GetRegisterOperand(1).index(), reg_0.index());
|
||||
CHECK_EQ(iterator.GetCountOperand(2), 1);
|
||||
CHECK(!iterator.done());
|
||||
iterator.Advance();
|
||||
|
||||
CHECK_EQ(iterator.current_bytecode(), Bytecode::kReturn);
|
||||
CHECK(!iterator.done());
|
||||
iterator.Advance();
|
||||
|
Loading…
Reference in New Issue
Block a user