blob: 440e879c4869fbd7b0e52b9035167e49ffd81168 [file] [log] [blame]
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/interpreter/interpreter-assembler.h"
#include <ostream>
#include "src/code-factory.h"
#include "src/frames.h"
#include "src/interface-descriptors.h"
#include "src/interpreter/bytecodes.h"
#include "src/interpreter/interpreter.h"
#include "src/machine-type.h"
#include "src/macro-assembler.h"
#include "src/zone.h"
namespace v8 {
namespace internal {
namespace interpreter {
using compiler::Node;
InterpreterAssembler::InterpreterAssembler(Isolate* isolate, Zone* zone,
Bytecode bytecode)
: compiler::CodeStubAssembler(
isolate, zone, InterpreterDispatchDescriptor(isolate),
Code::ComputeFlags(Code::STUB), Bytecodes::ToString(bytecode), 0),
bytecode_(bytecode),
accumulator_(this, MachineRepresentation::kTagged),
context_(this, MachineRepresentation::kTagged),
bytecode_array_(this, MachineRepresentation::kTagged),
disable_stack_check_across_call_(false),
stack_pointer_before_call_(nullptr) {
accumulator_.Bind(
Parameter(InterpreterDispatchDescriptor::kAccumulatorParameter));
context_.Bind(Parameter(InterpreterDispatchDescriptor::kContextParameter));
bytecode_array_.Bind(
Parameter(InterpreterDispatchDescriptor::kBytecodeArrayParameter));
if (FLAG_trace_ignition) {
TraceBytecode(Runtime::kInterpreterTraceBytecodeEntry);
}
}
InterpreterAssembler::~InterpreterAssembler() {}
Node* InterpreterAssembler::GetAccumulator() { return accumulator_.value(); }
void InterpreterAssembler::SetAccumulator(Node* value) {
accumulator_.Bind(value);
}
Node* InterpreterAssembler::GetContext() { return context_.value(); }
void InterpreterAssembler::SetContext(Node* value) {
StoreRegister(value, Register::current_context());
context_.Bind(value);
}
Node* InterpreterAssembler::BytecodeOffset() {
return Parameter(InterpreterDispatchDescriptor::kBytecodeOffsetParameter);
}
Node* InterpreterAssembler::RegisterFileRawPointer() {
return Parameter(InterpreterDispatchDescriptor::kRegisterFileParameter);
}
Node* InterpreterAssembler::BytecodeArrayTaggedPointer() {
return bytecode_array_.value();
}
Node* InterpreterAssembler::DispatchTableRawPointer() {
return Parameter(InterpreterDispatchDescriptor::kDispatchTableParameter);
}
Node* InterpreterAssembler::RegisterLocation(Node* reg_index) {
return IntPtrAdd(RegisterFileRawPointer(), RegisterFrameOffset(reg_index));
}
Node* InterpreterAssembler::LoadRegister(int offset) {
return Load(MachineType::AnyTagged(), RegisterFileRawPointer(),
Int32Constant(offset));
}
Node* InterpreterAssembler::LoadRegister(Register reg) {
return LoadRegister(reg.ToOperand() << kPointerSizeLog2);
}
Node* InterpreterAssembler::RegisterFrameOffset(Node* index) {
return WordShl(index, kPointerSizeLog2);
}
Node* InterpreterAssembler::LoadRegister(Node* reg_index) {
return Load(MachineType::AnyTagged(), RegisterFileRawPointer(),
RegisterFrameOffset(reg_index));
}
Node* InterpreterAssembler::StoreRegister(Node* value, int offset) {
return StoreNoWriteBarrier(MachineRepresentation::kTagged,
RegisterFileRawPointer(), Int32Constant(offset),
value);
}
Node* InterpreterAssembler::StoreRegister(Node* value, Register reg) {
return StoreRegister(value, reg.ToOperand() << kPointerSizeLog2);
}
Node* InterpreterAssembler::StoreRegister(Node* value, Node* reg_index) {
return StoreNoWriteBarrier(MachineRepresentation::kTagged,
RegisterFileRawPointer(),
RegisterFrameOffset(reg_index), value);
}
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, Bytecodes::NumberOfOperands(bytecode_));
DCHECK_EQ(OperandSize::kByte,
Bytecodes::GetOperandSize(bytecode_, operand_index));
return Load(
MachineType::Uint8(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), Int32Constant(Bytecodes::GetOperandOffset(
bytecode_, operand_index))));
}
Node* InterpreterAssembler::BytecodeOperandSignExtended(int operand_index) {
DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_));
DCHECK_EQ(OperandSize::kByte,
Bytecodes::GetOperandSize(bytecode_, operand_index));
Node* load = Load(
MachineType::Int8(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), Int32Constant(Bytecodes::GetOperandOffset(
bytecode_, operand_index))));
// Ensure that we sign extend to full pointer size
if (kPointerSize == 8) {
load = ChangeInt32ToInt64(load);
}
return load;
}
Node* InterpreterAssembler::BytecodeOperandShort(int operand_index) {
DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_));
DCHECK_EQ(OperandSize::kShort,
Bytecodes::GetOperandSize(bytecode_, operand_index));
if (TargetSupportsUnalignedAccess()) {
return Load(
MachineType::Uint16(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), Int32Constant(Bytecodes::GetOperandOffset(
bytecode_, operand_index))));
} else {
int offset = Bytecodes::GetOperandOffset(bytecode_, operand_index);
Node* first_byte = Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), Int32Constant(offset)));
Node* second_byte =
Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), Int32Constant(offset + 1)));
#if V8_TARGET_LITTLE_ENDIAN
return WordOr(WordShl(second_byte, kBitsPerByte), first_byte);
#elif V8_TARGET_BIG_ENDIAN
return WordOr(WordShl(first_byte, kBitsPerByte), second_byte);
#else
#error "Unknown Architecture"
#endif
}
}
Node* InterpreterAssembler::BytecodeOperandShortSignExtended(
int operand_index) {
DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_));
DCHECK_EQ(OperandSize::kShort,
Bytecodes::GetOperandSize(bytecode_, operand_index));
int operand_offset = Bytecodes::GetOperandOffset(bytecode_, operand_index);
Node* load;
if (TargetSupportsUnalignedAccess()) {
load = Load(MachineType::Int16(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), Int32Constant(operand_offset)));
} else {
#if V8_TARGET_LITTLE_ENDIAN
Node* hi_byte_offset = Int32Constant(operand_offset + 1);
Node* lo_byte_offset = Int32Constant(operand_offset);
#elif V8_TARGET_BIG_ENDIAN
Node* hi_byte_offset = Int32Constant(operand_offset);
Node* lo_byte_offset = Int32Constant(operand_offset + 1);
#else
#error "Unknown Architecture"
#endif
Node* hi_byte = Load(MachineType::Int8(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), hi_byte_offset));
Node* lo_byte = Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(),
IntPtrAdd(BytecodeOffset(), lo_byte_offset));
hi_byte = Word32Shl(hi_byte, Int32Constant(kBitsPerByte));
load = Word32Or(hi_byte, lo_byte);
}
// Ensure that we sign extend to full pointer size
if (kPointerSize == 8) {
load = ChangeInt32ToInt64(load);
}
return load;
}
Node* InterpreterAssembler::BytecodeOperandCount(int operand_index) {
switch (Bytecodes::GetOperandSize(bytecode_, operand_index)) {
case OperandSize::kByte:
DCHECK_EQ(OperandType::kRegCount8,
Bytecodes::GetOperandType(bytecode_, operand_index));
return BytecodeOperand(operand_index);
case OperandSize::kShort:
DCHECK_EQ(OperandType::kRegCount16,
Bytecodes::GetOperandType(bytecode_, operand_index));
return BytecodeOperandShort(operand_index);
case OperandSize::kNone:
UNREACHABLE();
}
return nullptr;
}
Node* InterpreterAssembler::BytecodeOperandImm(int operand_index) {
DCHECK_EQ(OperandType::kImm8,
Bytecodes::GetOperandType(bytecode_, operand_index));
return BytecodeOperandSignExtended(operand_index);
}
Node* InterpreterAssembler::BytecodeOperandIdx(int operand_index) {
switch (Bytecodes::GetOperandSize(bytecode_, operand_index)) {
case OperandSize::kByte:
DCHECK_EQ(OperandType::kIdx8,
Bytecodes::GetOperandType(bytecode_, operand_index));
return BytecodeOperand(operand_index);
case OperandSize::kShort:
DCHECK_EQ(OperandType::kIdx16,
Bytecodes::GetOperandType(bytecode_, operand_index));
return BytecodeOperandShort(operand_index);
case OperandSize::kNone:
UNREACHABLE();
}
return nullptr;
}
Node* InterpreterAssembler::BytecodeOperandReg(int operand_index) {
OperandType operand_type =
Bytecodes::GetOperandType(bytecode_, operand_index);
if (Bytecodes::IsRegisterOperandType(operand_type)) {
OperandSize operand_size = Bytecodes::SizeOfOperand(operand_type);
if (operand_size == OperandSize::kByte) {
return BytecodeOperandSignExtended(operand_index);
} else if (operand_size == OperandSize::kShort) {
return BytecodeOperandShortSignExtended(operand_index);
}
}
UNREACHABLE();
return nullptr;
}
Node* InterpreterAssembler::LoadConstantPoolEntry(Node* index) {
Node* constant_pool = LoadObjectField(BytecodeArrayTaggedPointer(),
BytecodeArray::kConstantPoolOffset);
Node* entry_offset =
IntPtrAdd(IntPtrConstant(FixedArray::kHeaderSize - kHeapObjectTag),
WordShl(index, kPointerSizeLog2));
return Load(MachineType::AnyTagged(), constant_pool, entry_offset);
}
Node* InterpreterAssembler::LoadFixedArrayElement(Node* fixed_array,
int index) {
Node* entry_offset =
IntPtrAdd(IntPtrConstant(FixedArray::kHeaderSize - kHeapObjectTag),
WordShl(Int32Constant(index), kPointerSizeLog2));
return Load(MachineType::AnyTagged(), fixed_array, entry_offset);
}
Node* InterpreterAssembler::LoadObjectField(Node* object, int offset) {
return Load(MachineType::AnyTagged(), object,
IntPtrConstant(offset - kHeapObjectTag));
}
Node* InterpreterAssembler::LoadContextSlot(Node* context, int slot_index) {
return Load(MachineType::AnyTagged(), context,
IntPtrConstant(Context::SlotOffset(slot_index)));
}
Node* InterpreterAssembler::LoadContextSlot(Node* context, Node* slot_index) {
Node* offset =
IntPtrAdd(WordShl(slot_index, kPointerSizeLog2),
Int32Constant(Context::kHeaderSize - kHeapObjectTag));
return Load(MachineType::AnyTagged(), context, offset);
}
Node* InterpreterAssembler::StoreContextSlot(Node* context, Node* slot_index,
Node* value) {
Node* offset =
IntPtrAdd(WordShl(slot_index, kPointerSizeLog2),
Int32Constant(Context::kHeaderSize - kHeapObjectTag));
return Store(MachineRepresentation::kTagged, context, offset, value);
}
Node* InterpreterAssembler::LoadTypeFeedbackVector() {
Node* function = Load(
MachineType::AnyTagged(), RegisterFileRawPointer(),
IntPtrConstant(InterpreterFrameConstants::kFunctionFromRegisterPointer));
Node* shared_info =
LoadObjectField(function, JSFunction::kSharedFunctionInfoOffset);
Node* vector =
LoadObjectField(shared_info, SharedFunctionInfo::kFeedbackVectorOffset);
return vector;
}
void InterpreterAssembler::CallPrologue() {
StoreRegister(SmiTag(BytecodeOffset()),
InterpreterFrameConstants::kBytecodeOffsetFromRegisterPointer);
StoreRegister(BytecodeArrayTaggedPointer(),
InterpreterFrameConstants::kBytecodeArrayFromRegisterPointer);
if (FLAG_debug_code && !disable_stack_check_across_call_) {
DCHECK(stack_pointer_before_call_ == nullptr);
stack_pointer_before_call_ = LoadStackPointer();
}
}
void InterpreterAssembler::CallEpilogue() {
if (FLAG_debug_code && !disable_stack_check_across_call_) {
Node* stack_pointer_after_call = LoadStackPointer();
Node* stack_pointer_before_call = stack_pointer_before_call_;
stack_pointer_before_call_ = nullptr;
AbortIfWordNotEqual(stack_pointer_before_call, stack_pointer_after_call,
kUnexpectedStackPointer);
}
// Restore bytecode array from stack frame in case the debugger has swapped us
// to the patched debugger bytecode array.
bytecode_array_.Bind(LoadRegister(
InterpreterFrameConstants::kBytecodeArrayFromRegisterPointer));
}
Node* InterpreterAssembler::CallJS(Node* function, Node* context,
Node* first_arg, Node* arg_count,
TailCallMode tail_call_mode) {
Callable callable =
CodeFactory::InterpreterPushArgsAndCall(isolate(), tail_call_mode);
Node* code_target = HeapConstant(callable.code());
return CallStub(callable.descriptor(), code_target, context, arg_count,
first_arg, function);
}
Node* InterpreterAssembler::CallConstruct(Node* constructor, Node* context,
Node* new_target, Node* first_arg,
Node* arg_count) {
Callable callable = CodeFactory::InterpreterPushArgsAndConstruct(isolate());
Node* code_target = HeapConstant(callable.code());
return CallStub(callable.descriptor(), code_target, context, arg_count,
new_target, constructor, first_arg);
}
Node* InterpreterAssembler::CallRuntimeN(Node* function_id, Node* context,
Node* first_arg, Node* arg_count,
int result_size) {
Callable callable = CodeFactory::InterpreterCEntry(isolate(), result_size);
Node* code_target = HeapConstant(callable.code());
// Get the function entry from the function id.
Node* function_table = ExternalConstant(
ExternalReference::runtime_function_table_address(isolate()));
Node* function_offset =
Int32Mul(function_id, Int32Constant(sizeof(Runtime::Function)));
Node* function = IntPtrAdd(function_table, function_offset);
Node* function_entry =
Load(MachineType::Pointer(), function,
Int32Constant(offsetof(Runtime::Function, entry)));
return CallStub(callable.descriptor(), code_target, context, arg_count,
first_arg, function_entry, result_size);
}
void InterpreterAssembler::UpdateInterruptBudget(Node* weight) {
CodeStubAssembler::Label ok(this);
CodeStubAssembler::Label interrupt_check(this);
CodeStubAssembler::Label end(this);
Node* budget_offset =
IntPtrConstant(BytecodeArray::kInterruptBudgetOffset - kHeapObjectTag);
// Update budget by |weight| and check if it reaches zero.
Node* old_budget =
Load(MachineType::Int32(), BytecodeArrayTaggedPointer(), budget_offset);
Node* new_budget = Int32Add(old_budget, weight);
Node* condition = Int32GreaterThanOrEqual(new_budget, Int32Constant(0));
Branch(condition, &ok, &interrupt_check);
// Perform interrupt and reset budget.
Bind(&interrupt_check);
CallRuntime(Runtime::kInterrupt, GetContext());
StoreNoWriteBarrier(MachineRepresentation::kWord32,
BytecodeArrayTaggedPointer(), budget_offset,
Int32Constant(Interpreter::InterruptBudget()));
Goto(&end);
// Update budget.
Bind(&ok);
StoreNoWriteBarrier(MachineRepresentation::kWord32,
BytecodeArrayTaggedPointer(), budget_offset, new_budget);
Goto(&end);
Bind(&end);
}
Node* InterpreterAssembler::Advance(int delta) {
return IntPtrAdd(BytecodeOffset(), Int32Constant(delta));
}
Node* InterpreterAssembler::Advance(Node* delta) {
return IntPtrAdd(BytecodeOffset(), delta);
}
void InterpreterAssembler::Jump(Node* delta) {
UpdateInterruptBudget(delta);
DispatchTo(Advance(delta));
}
void InterpreterAssembler::JumpConditional(Node* condition, Node* delta) {
CodeStubAssembler::Label match(this);
CodeStubAssembler::Label no_match(this);
Branch(condition, &match, &no_match);
Bind(&match);
Jump(delta);
Bind(&no_match);
Dispatch();
}
void InterpreterAssembler::JumpIfWordEqual(Node* lhs, Node* rhs, Node* delta) {
JumpConditional(WordEqual(lhs, rhs), delta);
}
void InterpreterAssembler::JumpIfWordNotEqual(Node* lhs, Node* rhs,
Node* delta) {
JumpConditional(WordNotEqual(lhs, rhs), delta);
}
void InterpreterAssembler::Dispatch() {
DispatchTo(Advance(Bytecodes::Size(bytecode_)));
}
void InterpreterAssembler::DispatchTo(Node* new_bytecode_offset) {
Node* target_bytecode = Load(
MachineType::Uint8(), BytecodeArrayTaggedPointer(), new_bytecode_offset);
// TODO(rmcilroy): Create a code target dispatch table to avoid conversion
// from code object on every dispatch.
Node* target_code_object =
Load(MachineType::Pointer(), DispatchTableRawPointer(),
Word32Shl(target_bytecode, Int32Constant(kPointerSizeLog2)));
DispatchToBytecodeHandler(target_code_object, new_bytecode_offset);
}
void InterpreterAssembler::DispatchToBytecodeHandler(Node* handler,
Node* bytecode_offset) {
if (FLAG_trace_ignition) {
TraceBytecode(Runtime::kInterpreterTraceBytecodeExit);
}
InterpreterDispatchDescriptor descriptor(isolate());
Node* args[] = {GetAccumulator(), RegisterFileRawPointer(),
bytecode_offset, BytecodeArrayTaggedPointer(),
DispatchTableRawPointer(), GetContext()};
TailCall(descriptor, handler, args, 0);
}
void InterpreterAssembler::InterpreterReturn() {
// TODO(rmcilroy): Investigate whether it is worth supporting self
// optimization of primitive functions like FullCodegen.
// Update profiling count by -BytecodeOffset to simulate backedge to start of
// function.
Node* profiling_weight =
Int32Sub(Int32Constant(kHeapObjectTag + BytecodeArray::kHeaderSize),
BytecodeOffset());
UpdateInterruptBudget(profiling_weight);
Node* exit_trampoline_code_object =
HeapConstant(isolate()->builtins()->InterpreterExitTrampoline());
DispatchToBytecodeHandler(exit_trampoline_code_object);
}
void InterpreterAssembler::StackCheck() {
CodeStubAssembler::Label end(this);
CodeStubAssembler::Label ok(this);
CodeStubAssembler::Label stack_guard(this);
Node* sp = LoadStackPointer();
Node* stack_limit = Load(
MachineType::Pointer(),
ExternalConstant(ExternalReference::address_of_stack_limit(isolate())));
Node* condition = UintPtrGreaterThanOrEqual(sp, stack_limit);
Branch(condition, &ok, &stack_guard);
Bind(&stack_guard);
CallRuntime(Runtime::kStackGuard, GetContext());
Goto(&end);
Bind(&ok);
Goto(&end);
Bind(&end);
}
void InterpreterAssembler::Abort(BailoutReason bailout_reason) {
disable_stack_check_across_call_ = true;
Node* abort_id = SmiTag(Int32Constant(bailout_reason));
Node* ret_value = CallRuntime(Runtime::kAbort, GetContext(), abort_id);
disable_stack_check_across_call_ = false;
// Unreached, but keeps turbofan happy.
Return(ret_value);
}
void InterpreterAssembler::AbortIfWordNotEqual(Node* lhs, Node* rhs,
BailoutReason bailout_reason) {
CodeStubAssembler::Label match(this);
CodeStubAssembler::Label no_match(this);
Node* condition = WordEqual(lhs, rhs);
Branch(condition, &match, &no_match);
Bind(&no_match);
Abort(bailout_reason);
Bind(&match);
}
void InterpreterAssembler::TraceBytecode(Runtime::FunctionId function_id) {
CallRuntime(function_id, GetContext(), BytecodeArrayTaggedPointer(),
SmiTag(BytecodeOffset()), GetAccumulator());
}
// static
bool InterpreterAssembler::TargetSupportsUnalignedAccess() {
#if V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
return false;
#elif V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_PPC
return CpuFeatures::IsSupported(UNALIGNED_ACCESSES);
#elif V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_X87
return true;
#else
#error "Unknown Architecture"
#endif
}
} // namespace interpreter
} // namespace internal
} // namespace v8