| // 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 <limits> | |
| #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/objects-inl.h" | |
| #include "src/zone/zone.h" | |
| namespace v8 { | |
| namespace internal { | |
| namespace interpreter { | |
| using compiler::CodeAssemblerState; | |
| using compiler::Node; | |
| template <class T> | |
| using TNode = compiler::TNode<T>; | |
| InterpreterAssembler::InterpreterAssembler(CodeAssemblerState* state, | |
| Bytecode bytecode, | |
| OperandScale operand_scale) | |
| : CodeStubAssembler(state), | |
| bytecode_(bytecode), | |
| operand_scale_(operand_scale), | |
| VARIABLE_CONSTRUCTOR(interpreted_frame_pointer_, | |
| MachineType::PointerRepresentation()), | |
| VARIABLE_CONSTRUCTOR( | |
| bytecode_array_, MachineRepresentation::kTagged, | |
| Parameter(InterpreterDispatchDescriptor::kBytecodeArray)), | |
| VARIABLE_CONSTRUCTOR( | |
| bytecode_offset_, MachineType::PointerRepresentation(), | |
| Parameter(InterpreterDispatchDescriptor::kBytecodeOffset)), | |
| VARIABLE_CONSTRUCTOR( | |
| dispatch_table_, MachineType::PointerRepresentation(), | |
| Parameter(InterpreterDispatchDescriptor::kDispatchTable)), | |
| VARIABLE_CONSTRUCTOR( | |
| accumulator_, MachineRepresentation::kTagged, | |
| Parameter(InterpreterDispatchDescriptor::kAccumulator)), | |
| accumulator_use_(AccumulatorUse::kNone), | |
| made_call_(false), | |
| reloaded_frame_ptr_(false), | |
| bytecode_array_valid_(true), | |
| disable_stack_check_across_call_(false), | |
| stack_pointer_before_call_(nullptr) { | |
| #ifdef V8_TRACE_IGNITION | |
| TraceBytecode(Runtime::kInterpreterTraceBytecodeEntry); | |
| #endif | |
| RegisterCallGenerationCallbacks([this] { CallPrologue(); }, | |
| [this] { CallEpilogue(); }); | |
| // Save the bytecode offset immediately if bytecode will make a call along the | |
| // critical path, or it is a return bytecode. | |
| if (Bytecodes::MakesCallAlongCriticalPath(bytecode) || | |
| Bytecodes::Returns(bytecode)) { | |
| SaveBytecodeOffset(); | |
| } | |
| } | |
| InterpreterAssembler::~InterpreterAssembler() { | |
| // If the following check fails the handler does not use the | |
| // accumulator in the way described in the bytecode definitions in | |
| // bytecodes.h. | |
| DCHECK_EQ(accumulator_use_, Bytecodes::GetAccumulatorUse(bytecode_)); | |
| UnregisterCallGenerationCallbacks(); | |
| } | |
| Node* InterpreterAssembler::GetInterpretedFramePointer() { | |
| if (!interpreted_frame_pointer_.IsBound()) { | |
| interpreted_frame_pointer_.Bind(LoadParentFramePointer()); | |
| } else if (Bytecodes::MakesCallAlongCriticalPath(bytecode_) && made_call_ && | |
| !reloaded_frame_ptr_) { | |
| interpreted_frame_pointer_.Bind(LoadParentFramePointer()); | |
| reloaded_frame_ptr_ = true; | |
| } | |
| return interpreted_frame_pointer_.value(); | |
| } | |
| Node* InterpreterAssembler::BytecodeOffset() { | |
| if (Bytecodes::MakesCallAlongCriticalPath(bytecode_) && made_call_ && | |
| (bytecode_offset_.value() == | |
| Parameter(InterpreterDispatchDescriptor::kBytecodeOffset))) { | |
| bytecode_offset_.Bind(ReloadBytecodeOffset()); | |
| } | |
| return bytecode_offset_.value(); | |
| } | |
| Node* InterpreterAssembler::ReloadBytecodeOffset() { | |
| Node* offset = LoadAndUntagRegister(Register::bytecode_offset()); | |
| if (operand_scale() != OperandScale::kSingle) { | |
| // Add one to the offset such that it points to the actual bytecode rather | |
| // than the Wide / ExtraWide prefix bytecode. | |
| offset = IntPtrAdd(offset, IntPtrConstant(1)); | |
| } | |
| return offset; | |
| } | |
| void InterpreterAssembler::SaveBytecodeOffset() { | |
| Node* offset = BytecodeOffset(); | |
| if (operand_scale() != OperandScale::kSingle) { | |
| // Subtract one from the offset such that it points to the Wide / ExtraWide | |
| // prefix bytecode. | |
| offset = IntPtrSub(BytecodeOffset(), IntPtrConstant(1)); | |
| } | |
| StoreAndTagRegister(offset, Register::bytecode_offset()); | |
| } | |
| Node* InterpreterAssembler::BytecodeArrayTaggedPointer() { | |
| // Force a re-load of the bytecode array after every call in case the debugger | |
| // has been activated. | |
| if (!bytecode_array_valid_) { | |
| bytecode_array_.Bind(LoadRegister(Register::bytecode_array())); | |
| bytecode_array_valid_ = true; | |
| } | |
| return bytecode_array_.value(); | |
| } | |
| Node* InterpreterAssembler::DispatchTableRawPointer() { | |
| if (Bytecodes::MakesCallAlongCriticalPath(bytecode_) && made_call_ && | |
| (dispatch_table_.value() == | |
| Parameter(InterpreterDispatchDescriptor::kDispatchTable))) { | |
| dispatch_table_.Bind(ExternalConstant( | |
| ExternalReference::interpreter_dispatch_table_address(isolate()))); | |
| } | |
| return dispatch_table_.value(); | |
| } | |
| Node* InterpreterAssembler::GetAccumulatorUnchecked() { | |
| return accumulator_.value(); | |
| } | |
| Node* InterpreterAssembler::GetAccumulator() { | |
| DCHECK(Bytecodes::ReadsAccumulator(bytecode_)); | |
| accumulator_use_ = accumulator_use_ | AccumulatorUse::kRead; | |
| return TaggedPoisonOnSpeculation(GetAccumulatorUnchecked()); | |
| } | |
| void InterpreterAssembler::SetAccumulator(Node* value) { | |
| DCHECK(Bytecodes::WritesAccumulator(bytecode_)); | |
| accumulator_use_ = accumulator_use_ | AccumulatorUse::kWrite; | |
| accumulator_.Bind(value); | |
| } | |
| Node* InterpreterAssembler::GetContext() { | |
| return LoadRegister(Register::current_context()); | |
| } | |
| void InterpreterAssembler::SetContext(Node* value) { | |
| StoreRegister(value, Register::current_context()); | |
| } | |
| Node* InterpreterAssembler::GetContextAtDepth(Node* context, Node* depth) { | |
| Variable cur_context(this, MachineRepresentation::kTaggedPointer); | |
| cur_context.Bind(context); | |
| Variable cur_depth(this, MachineRepresentation::kWord32); | |
| cur_depth.Bind(depth); | |
| Label context_found(this); | |
| Variable* context_search_loop_variables[2] = {&cur_depth, &cur_context}; | |
| Label context_search(this, 2, context_search_loop_variables); | |
| // Fast path if the depth is 0. | |
| Branch(Word32Equal(depth, Int32Constant(0)), &context_found, &context_search); | |
| // Loop until the depth is 0. | |
| BIND(&context_search); | |
| { | |
| cur_depth.Bind(Int32Sub(cur_depth.value(), Int32Constant(1))); | |
| cur_context.Bind( | |
| LoadContextElement(cur_context.value(), Context::PREVIOUS_INDEX)); | |
| Branch(Word32Equal(cur_depth.value(), Int32Constant(0)), &context_found, | |
| &context_search); | |
| } | |
| BIND(&context_found); | |
| return cur_context.value(); | |
| } | |
| void InterpreterAssembler::GotoIfHasContextExtensionUpToDepth(Node* context, | |
| Node* depth, | |
| Label* target) { | |
| Variable cur_context(this, MachineRepresentation::kTaggedPointer); | |
| cur_context.Bind(context); | |
| Variable cur_depth(this, MachineRepresentation::kWord32); | |
| cur_depth.Bind(depth); | |
| Variable* context_search_loop_variables[2] = {&cur_depth, &cur_context}; | |
| Label context_search(this, 2, context_search_loop_variables); | |
| // Loop until the depth is 0. | |
| Goto(&context_search); | |
| BIND(&context_search); | |
| { | |
| // TODO(leszeks): We only need to do this check if the context had a sloppy | |
| // eval, we could pass in a context chain bitmask to figure out which | |
| // contexts actually need to be checked. | |
| Node* extension_slot = | |
| LoadContextElement(cur_context.value(), Context::EXTENSION_INDEX); | |
| // Jump to the target if the extension slot is not a hole. | |
| GotoIf(WordNotEqual(extension_slot, TheHoleConstant()), target); | |
| cur_depth.Bind(Int32Sub(cur_depth.value(), Int32Constant(1))); | |
| cur_context.Bind( | |
| LoadContextElement(cur_context.value(), Context::PREVIOUS_INDEX)); | |
| GotoIf(Word32NotEqual(cur_depth.value(), Int32Constant(0)), | |
| &context_search); | |
| } | |
| } | |
| Node* InterpreterAssembler::RegisterLocation(Node* reg_index) { | |
| return WordPoisonOnSpeculation( | |
| IntPtrAdd(GetInterpretedFramePointer(), RegisterFrameOffset(reg_index))); | |
| } | |
| Node* InterpreterAssembler::RegisterLocation(Register reg) { | |
| return RegisterLocation(IntPtrConstant(reg.ToOperand())); | |
| } | |
| Node* InterpreterAssembler::RegisterFrameOffset(Node* index) { | |
| return TimesPointerSize(index); | |
| } | |
| Node* InterpreterAssembler::LoadRegister(Node* reg_index) { | |
| return Load(MachineType::AnyTagged(), GetInterpretedFramePointer(), | |
| RegisterFrameOffset(reg_index), LoadSensitivity::kCritical); | |
| } | |
| Node* InterpreterAssembler::LoadRegister(Register reg) { | |
| return Load(MachineType::AnyTagged(), GetInterpretedFramePointer(), | |
| IntPtrConstant(reg.ToOperand() << kPointerSizeLog2)); | |
| } | |
| Node* InterpreterAssembler::LoadAndUntagRegister(Register reg) { | |
| return LoadAndUntagSmi(GetInterpretedFramePointer(), reg.ToOperand() | |
| << kPointerSizeLog2); | |
| } | |
| Node* InterpreterAssembler::LoadRegisterAtOperandIndex(int operand_index) { | |
| return LoadRegister( | |
| BytecodeOperandReg(operand_index, LoadSensitivity::kSafe)); | |
| } | |
| std::pair<Node*, Node*> InterpreterAssembler::LoadRegisterPairAtOperandIndex( | |
| int operand_index) { | |
| DCHECK_EQ(OperandType::kRegPair, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| Node* first_reg_index = | |
| BytecodeOperandReg(operand_index, LoadSensitivity::kSafe); | |
| Node* second_reg_index = NextRegister(first_reg_index); | |
| return std::make_pair(LoadRegister(first_reg_index), | |
| LoadRegister(second_reg_index)); | |
| } | |
| InterpreterAssembler::RegListNodePair | |
| InterpreterAssembler::GetRegisterListAtOperandIndex(int operand_index) { | |
| DCHECK(Bytecodes::IsRegisterListOperandType( | |
| Bytecodes::GetOperandType(bytecode_, operand_index))); | |
| DCHECK_EQ(OperandType::kRegCount, | |
| Bytecodes::GetOperandType(bytecode_, operand_index + 1)); | |
| Node* base_reg = RegisterLocation( | |
| BytecodeOperandReg(operand_index, LoadSensitivity::kSafe)); | |
| Node* reg_count = BytecodeOperandCount(operand_index + 1); | |
| return RegListNodePair(base_reg, reg_count); | |
| } | |
| Node* InterpreterAssembler::LoadRegisterFromRegisterList( | |
| const RegListNodePair& reg_list, int index) { | |
| Node* location = RegisterLocationInRegisterList(reg_list, index); | |
| // Location is already poisoned on speculation, so no need to poison here. | |
| return Load(MachineType::AnyTagged(), location); | |
| } | |
| Node* InterpreterAssembler::RegisterLocationInRegisterList( | |
| const RegListNodePair& reg_list, int index) { | |
| CSA_ASSERT(this, | |
| Uint32GreaterThan(reg_list.reg_count(), Int32Constant(index))); | |
| Node* offset = RegisterFrameOffset(IntPtrConstant(index)); | |
| // Register indexes are negative, so subtract index from base location to get | |
| // location. | |
| return IntPtrSub(reg_list.base_reg_location(), offset); | |
| } | |
| void InterpreterAssembler::StoreRegister(Node* value, Register reg) { | |
| StoreNoWriteBarrier( | |
| MachineRepresentation::kTagged, GetInterpretedFramePointer(), | |
| IntPtrConstant(reg.ToOperand() << kPointerSizeLog2), value); | |
| } | |
| void InterpreterAssembler::StoreRegister(Node* value, Node* reg_index) { | |
| StoreNoWriteBarrier(MachineRepresentation::kTagged, | |
| GetInterpretedFramePointer(), | |
| RegisterFrameOffset(reg_index), value); | |
| } | |
| void InterpreterAssembler::StoreAndTagRegister(Node* value, Register reg) { | |
| int offset = reg.ToOperand() << kPointerSizeLog2; | |
| StoreAndTagSmi(GetInterpretedFramePointer(), offset, value); | |
| } | |
| void InterpreterAssembler::StoreRegisterAtOperandIndex(Node* value, | |
| int operand_index) { | |
| StoreRegister(value, | |
| BytecodeOperandReg(operand_index, LoadSensitivity::kSafe)); | |
| } | |
| void InterpreterAssembler::StoreRegisterPairAtOperandIndex(Node* value1, | |
| Node* value2, | |
| int operand_index) { | |
| DCHECK_EQ(OperandType::kRegOutPair, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| Node* first_reg_index = | |
| BytecodeOperandReg(operand_index, LoadSensitivity::kSafe); | |
| StoreRegister(value1, first_reg_index); | |
| Node* second_reg_index = NextRegister(first_reg_index); | |
| StoreRegister(value2, second_reg_index); | |
| } | |
| void InterpreterAssembler::StoreRegisterTripleAtOperandIndex( | |
| Node* value1, Node* value2, Node* value3, int operand_index) { | |
| DCHECK_EQ(OperandType::kRegOutTriple, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| Node* first_reg_index = | |
| BytecodeOperandReg(operand_index, LoadSensitivity::kSafe); | |
| StoreRegister(value1, first_reg_index); | |
| Node* second_reg_index = NextRegister(first_reg_index); | |
| StoreRegister(value2, second_reg_index); | |
| Node* third_reg_index = NextRegister(second_reg_index); | |
| StoreRegister(value3, third_reg_index); | |
| } | |
| Node* InterpreterAssembler::NextRegister(Node* reg_index) { | |
| // Register indexes are negative, so the next index is minus one. | |
| return IntPtrAdd(reg_index, IntPtrConstant(-1)); | |
| } | |
| Node* InterpreterAssembler::OperandOffset(int operand_index) { | |
| return IntPtrConstant( | |
| Bytecodes::GetOperandOffset(bytecode_, operand_index, operand_scale())); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandUnsignedByte( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_)); | |
| DCHECK_EQ(OperandSize::kByte, Bytecodes::GetOperandSize( | |
| bytecode_, operand_index, operand_scale())); | |
| Node* operand_offset = OperandOffset(operand_index); | |
| return Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(), | |
| IntPtrAdd(BytecodeOffset(), operand_offset), needs_poisoning); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandSignedByte( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_)); | |
| DCHECK_EQ(OperandSize::kByte, Bytecodes::GetOperandSize( | |
| bytecode_, operand_index, operand_scale())); | |
| Node* operand_offset = OperandOffset(operand_index); | |
| return Load(MachineType::Int8(), BytecodeArrayTaggedPointer(), | |
| IntPtrAdd(BytecodeOffset(), operand_offset), needs_poisoning); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandReadUnaligned( | |
| int relative_offset, MachineType result_type, | |
| LoadSensitivity needs_poisoning) { | |
| static const int kMaxCount = 4; | |
| DCHECK(!TargetSupportsUnalignedAccess()); | |
| int count; | |
| switch (result_type.representation()) { | |
| case MachineRepresentation::kWord16: | |
| count = 2; | |
| break; | |
| case MachineRepresentation::kWord32: | |
| count = 4; | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| MachineType msb_type = | |
| result_type.IsSigned() ? MachineType::Int8() : MachineType::Uint8(); | |
| #if V8_TARGET_LITTLE_ENDIAN | |
| const int kStep = -1; | |
| int msb_offset = count - 1; | |
| #elif V8_TARGET_BIG_ENDIAN | |
| const int kStep = 1; | |
| int msb_offset = 0; | |
| #else | |
| #error "Unknown Architecture" | |
| #endif | |
| // Read the most signicant bytecode into bytes[0] and then in order | |
| // down to least significant in bytes[count - 1]. | |
| DCHECK_LE(count, kMaxCount); | |
| Node* bytes[kMaxCount]; | |
| for (int i = 0; i < count; i++) { | |
| MachineType machine_type = (i == 0) ? msb_type : MachineType::Uint8(); | |
| Node* offset = IntPtrConstant(relative_offset + msb_offset + i * kStep); | |
| Node* array_offset = IntPtrAdd(BytecodeOffset(), offset); | |
| bytes[i] = Load(machine_type, BytecodeArrayTaggedPointer(), array_offset, | |
| needs_poisoning); | |
| } | |
| // Pack LSB to MSB. | |
| Node* result = bytes[--count]; | |
| for (int i = 1; --count >= 0; i++) { | |
| Node* shift = Int32Constant(i * kBitsPerByte); | |
| Node* value = Word32Shl(bytes[count], shift); | |
| result = Word32Or(value, result); | |
| } | |
| return result; | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandUnsignedShort( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_)); | |
| DCHECK_EQ( | |
| OperandSize::kShort, | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale())); | |
| int operand_offset = | |
| Bytecodes::GetOperandOffset(bytecode_, operand_index, operand_scale()); | |
| if (TargetSupportsUnalignedAccess()) { | |
| return Load(MachineType::Uint16(), BytecodeArrayTaggedPointer(), | |
| IntPtrAdd(BytecodeOffset(), IntPtrConstant(operand_offset)), | |
| needs_poisoning); | |
| } else { | |
| return BytecodeOperandReadUnaligned(operand_offset, MachineType::Uint16(), | |
| needs_poisoning); | |
| } | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandSignedShort( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_)); | |
| DCHECK_EQ( | |
| OperandSize::kShort, | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale())); | |
| int operand_offset = | |
| Bytecodes::GetOperandOffset(bytecode_, operand_index, operand_scale()); | |
| if (TargetSupportsUnalignedAccess()) { | |
| return Load(MachineType::Int16(), BytecodeArrayTaggedPointer(), | |
| IntPtrAdd(BytecodeOffset(), IntPtrConstant(operand_offset)), | |
| needs_poisoning); | |
| } else { | |
| return BytecodeOperandReadUnaligned(operand_offset, MachineType::Int16(), | |
| needs_poisoning); | |
| } | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandUnsignedQuad( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_)); | |
| DCHECK_EQ(OperandSize::kQuad, Bytecodes::GetOperandSize( | |
| bytecode_, operand_index, operand_scale())); | |
| int operand_offset = | |
| Bytecodes::GetOperandOffset(bytecode_, operand_index, operand_scale()); | |
| if (TargetSupportsUnalignedAccess()) { | |
| return Load(MachineType::Uint32(), BytecodeArrayTaggedPointer(), | |
| IntPtrAdd(BytecodeOffset(), IntPtrConstant(operand_offset)), | |
| needs_poisoning); | |
| } else { | |
| return BytecodeOperandReadUnaligned(operand_offset, MachineType::Uint32(), | |
| needs_poisoning); | |
| } | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandSignedQuad( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_)); | |
| DCHECK_EQ(OperandSize::kQuad, Bytecodes::GetOperandSize( | |
| bytecode_, operand_index, operand_scale())); | |
| int operand_offset = | |
| Bytecodes::GetOperandOffset(bytecode_, operand_index, operand_scale()); | |
| if (TargetSupportsUnalignedAccess()) { | |
| return Load(MachineType::Int32(), BytecodeArrayTaggedPointer(), | |
| IntPtrAdd(BytecodeOffset(), IntPtrConstant(operand_offset)), | |
| needs_poisoning); | |
| } else { | |
| return BytecodeOperandReadUnaligned(operand_offset, MachineType::Int32(), | |
| needs_poisoning); | |
| } | |
| } | |
| Node* InterpreterAssembler::BytecodeSignedOperand( | |
| int operand_index, OperandSize operand_size, | |
| LoadSensitivity needs_poisoning) { | |
| DCHECK(!Bytecodes::IsUnsignedOperandType( | |
| Bytecodes::GetOperandType(bytecode_, operand_index))); | |
| switch (operand_size) { | |
| case OperandSize::kByte: | |
| return BytecodeOperandSignedByte(operand_index, needs_poisoning); | |
| case OperandSize::kShort: | |
| return BytecodeOperandSignedShort(operand_index, needs_poisoning); | |
| case OperandSize::kQuad: | |
| return BytecodeOperandSignedQuad(operand_index, needs_poisoning); | |
| case OperandSize::kNone: | |
| UNREACHABLE(); | |
| } | |
| return nullptr; | |
| } | |
| Node* InterpreterAssembler::BytecodeUnsignedOperand( | |
| int operand_index, OperandSize operand_size, | |
| LoadSensitivity needs_poisoning) { | |
| DCHECK(Bytecodes::IsUnsignedOperandType( | |
| Bytecodes::GetOperandType(bytecode_, operand_index))); | |
| switch (operand_size) { | |
| case OperandSize::kByte: | |
| return BytecodeOperandUnsignedByte(operand_index, needs_poisoning); | |
| case OperandSize::kShort: | |
| return BytecodeOperandUnsignedShort(operand_index, needs_poisoning); | |
| case OperandSize::kQuad: | |
| return BytecodeOperandUnsignedQuad(operand_index, needs_poisoning); | |
| case OperandSize::kNone: | |
| UNREACHABLE(); | |
| } | |
| return nullptr; | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandCount(int operand_index) { | |
| DCHECK_EQ(OperandType::kRegCount, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| return BytecodeUnsignedOperand(operand_index, operand_size); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandFlag(int operand_index) { | |
| DCHECK_EQ(OperandType::kFlag8, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| DCHECK_EQ(operand_size, OperandSize::kByte); | |
| return BytecodeUnsignedOperand(operand_index, operand_size); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandUImm(int operand_index) { | |
| DCHECK_EQ(OperandType::kUImm, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| return BytecodeUnsignedOperand(operand_index, operand_size); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandUImmWord(int operand_index) { | |
| return ChangeUint32ToWord(BytecodeOperandUImm(operand_index)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandUImmSmi(int operand_index) { | |
| return SmiFromInt32(BytecodeOperandUImm(operand_index)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandImm(int operand_index) { | |
| DCHECK_EQ(OperandType::kImm, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| return BytecodeSignedOperand(operand_index, operand_size); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandImmIntPtr(int operand_index) { | |
| return ChangeInt32ToIntPtr(BytecodeOperandImm(operand_index)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandImmSmi(int operand_index) { | |
| return SmiFromInt32(BytecodeOperandImm(operand_index)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandIdxInt32(int operand_index) { | |
| DCHECK_EQ(OperandType::kIdx, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| return BytecodeUnsignedOperand(operand_index, operand_size); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandIdx(int operand_index) { | |
| return ChangeUint32ToWord(BytecodeOperandIdxInt32(operand_index)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandIdxSmi(int operand_index) { | |
| return SmiTag(BytecodeOperandIdx(operand_index)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandConstantPoolIdx( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK_EQ(OperandType::kIdx, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| return ChangeUint32ToWord( | |
| BytecodeUnsignedOperand(operand_index, operand_size, needs_poisoning)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandReg( | |
| int operand_index, LoadSensitivity needs_poisoning) { | |
| DCHECK(Bytecodes::IsRegisterOperandType( | |
| Bytecodes::GetOperandType(bytecode_, operand_index))); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| return ChangeInt32ToIntPtr( | |
| BytecodeSignedOperand(operand_index, operand_size, needs_poisoning)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandRuntimeId(int operand_index) { | |
| DCHECK_EQ(OperandType::kRuntimeId, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| DCHECK_EQ(operand_size, OperandSize::kShort); | |
| return BytecodeUnsignedOperand(operand_index, operand_size); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandNativeContextIndex( | |
| int operand_index) { | |
| DCHECK_EQ(OperandType::kNativeContextIndex, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| return ChangeUint32ToWord( | |
| BytecodeUnsignedOperand(operand_index, operand_size)); | |
| } | |
| Node* InterpreterAssembler::BytecodeOperandIntrinsicId(int operand_index) { | |
| DCHECK_EQ(OperandType::kIntrinsicId, | |
| Bytecodes::GetOperandType(bytecode_, operand_index)); | |
| OperandSize operand_size = | |
| Bytecodes::GetOperandSize(bytecode_, operand_index, operand_scale()); | |
| DCHECK_EQ(operand_size, OperandSize::kByte); | |
| return BytecodeUnsignedOperand(operand_index, operand_size); | |
| } | |
| Node* InterpreterAssembler::LoadConstantPoolEntry(Node* index) { | |
| TNode<FixedArray> constant_pool = CAST(LoadObjectField( | |
| BytecodeArrayTaggedPointer(), BytecodeArray::kConstantPoolOffset)); | |
| return LoadFixedArrayElement(constant_pool, UncheckedCast<IntPtrT>(index), | |
| LoadSensitivity::kCritical); | |
| } | |
| Node* InterpreterAssembler::LoadAndUntagConstantPoolEntry(Node* index) { | |
| return SmiUntag(LoadConstantPoolEntry(index)); | |
| } | |
| Node* InterpreterAssembler::LoadConstantPoolEntryAtOperandIndex( | |
| int operand_index) { | |
| Node* index = | |
| BytecodeOperandConstantPoolIdx(operand_index, LoadSensitivity::kSafe); | |
| return LoadConstantPoolEntry(index); | |
| } | |
| Node* InterpreterAssembler::LoadAndUntagConstantPoolEntryAtOperandIndex( | |
| int operand_index) { | |
| return SmiUntag(LoadConstantPoolEntryAtOperandIndex(operand_index)); | |
| } | |
| TNode<FeedbackVector> InterpreterAssembler::LoadFeedbackVector() { | |
| TNode<JSFunction> function = CAST(LoadRegister(Register::function_closure())); | |
| return CodeStubAssembler::LoadFeedbackVector(function); | |
| } | |
| void InterpreterAssembler::CallPrologue() { | |
| if (!Bytecodes::MakesCallAlongCriticalPath(bytecode_)) { | |
| // Bytecodes that make a call along the critical path save the bytecode | |
| // offset in the bytecode handler's prologue. For other bytecodes, if | |
| // there are multiple calls in the bytecode handler, you need to spill | |
| // before each of them, unless SaveBytecodeOffset has explicitly been called | |
| // in a path that dominates _all_ of those calls (which we don't track). | |
| SaveBytecodeOffset(); | |
| } | |
| if (FLAG_debug_code && !disable_stack_check_across_call_) { | |
| DCHECK_NULL(stack_pointer_before_call_); | |
| stack_pointer_before_call_ = LoadStackPointer(); | |
| } | |
| bytecode_array_valid_ = false; | |
| made_call_ = true; | |
| } | |
| 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, | |
| AbortReason::kUnexpectedStackPointer); | |
| } | |
| } | |
| void InterpreterAssembler::IncrementCallCount(Node* feedback_vector, | |
| Node* slot_id) { | |
| Comment("increment call count"); | |
| TNode<Smi> call_count = | |
| CAST(LoadFeedbackVectorSlot(feedback_vector, slot_id, kPointerSize)); | |
| // The lowest {FeedbackNexus::CallCountField::kShift} bits of the call | |
| // count are used as flags. To increment the call count by 1 we hence | |
| // have to increment by 1 << {FeedbackNexus::CallCountField::kShift}. | |
| Node* new_count = SmiAdd( | |
| call_count, SmiConstant(1 << FeedbackNexus::CallCountField::kShift)); | |
| // Count is Smi, so we don't need a write barrier. | |
| StoreFeedbackVectorSlot(feedback_vector, slot_id, new_count, | |
| SKIP_WRITE_BARRIER, kPointerSize); | |
| } | |
| void InterpreterAssembler::CollectCallableFeedback(Node* target, Node* context, | |
| Node* feedback_vector, | |
| Node* slot_id) { | |
| Label extra_checks(this, Label::kDeferred), done(this); | |
| // Check if we have monomorphic {target} feedback already. | |
| TNode<MaybeObject> feedback = | |
| LoadFeedbackVectorSlot(feedback_vector, slot_id); | |
| Comment("check if monomorphic"); | |
| TNode<BoolT> is_monomorphic = IsWeakReferenceTo(feedback, CAST(target)); | |
| GotoIf(is_monomorphic, &done); | |
| // Check if it is a megamorphic {target}. | |
| Comment("check if megamorphic"); | |
| Node* is_megamorphic = WordEqual( | |
| feedback, HeapConstant(FeedbackVector::MegamorphicSentinel(isolate()))); | |
| Branch(is_megamorphic, &done, &extra_checks); | |
| BIND(&extra_checks); | |
| { | |
| Label initialize(this), mark_megamorphic(this); | |
| Comment("check if weak reference"); | |
| Node* is_uninitialized = WordEqual( | |
| feedback, | |
| HeapConstant(FeedbackVector::UninitializedSentinel(isolate()))); | |
| GotoIf(is_uninitialized, &initialize); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(feedback)); | |
| // If the weak reference is cleared, we have a new chance to become | |
| // monomorphic. | |
| Comment("check if weak reference is cleared"); | |
| Branch(IsClearedWeakHeapObject(feedback), &initialize, &mark_megamorphic); | |
| BIND(&initialize); | |
| { | |
| // Check if {target} is a JSFunction in the current native context. | |
| Comment("check if function in same native context"); | |
| GotoIf(TaggedIsSmi(target), &mark_megamorphic); | |
| // Check if the {target} is a JSFunction or JSBoundFunction | |
| // in the current native context. | |
| VARIABLE(var_current, MachineRepresentation::kTagged, target); | |
| Label loop(this, &var_current), done_loop(this); | |
| Goto(&loop); | |
| BIND(&loop); | |
| { | |
| Label if_boundfunction(this), if_function(this); | |
| Node* current = var_current.value(); | |
| CSA_ASSERT(this, TaggedIsNotSmi(current)); | |
| Node* current_instance_type = LoadInstanceType(current); | |
| GotoIf(InstanceTypeEqual(current_instance_type, JS_BOUND_FUNCTION_TYPE), | |
| &if_boundfunction); | |
| Branch(InstanceTypeEqual(current_instance_type, JS_FUNCTION_TYPE), | |
| &if_function, &mark_megamorphic); | |
| BIND(&if_function); | |
| { | |
| // Check that the JSFunction {current} is in the current native | |
| // context. | |
| Node* current_context = | |
| LoadObjectField(current, JSFunction::kContextOffset); | |
| Node* current_native_context = LoadNativeContext(current_context); | |
| Branch(WordEqual(LoadNativeContext(context), current_native_context), | |
| &done_loop, &mark_megamorphic); | |
| } | |
| BIND(&if_boundfunction); | |
| { | |
| // Continue with the [[BoundTargetFunction]] of {target}. | |
| var_current.Bind(LoadObjectField( | |
| current, JSBoundFunction::kBoundTargetFunctionOffset)); | |
| Goto(&loop); | |
| } | |
| } | |
| BIND(&done_loop); | |
| StoreWeakReferenceInFeedbackVector(feedback_vector, slot_id, | |
| CAST(target)); | |
| ReportFeedbackUpdate(feedback_vector, slot_id, "Call:Initialize"); | |
| Goto(&done); | |
| } | |
| BIND(&mark_megamorphic); | |
| { | |
| // MegamorphicSentinel is an immortal immovable object so | |
| // write-barrier is not needed. | |
| Comment("transition to megamorphic"); | |
| DCHECK(Heap::RootIsImmortalImmovable(Heap::kmegamorphic_symbolRootIndex)); | |
| StoreFeedbackVectorSlot( | |
| feedback_vector, slot_id, | |
| HeapConstant(FeedbackVector::MegamorphicSentinel(isolate())), | |
| SKIP_WRITE_BARRIER); | |
| ReportFeedbackUpdate(feedback_vector, slot_id, | |
| "Call:TransitionMegamorphic"); | |
| Goto(&done); | |
| } | |
| } | |
| BIND(&done); | |
| } | |
| void InterpreterAssembler::CollectCallFeedback(Node* target, Node* context, | |
| Node* feedback_vector, | |
| Node* slot_id) { | |
| // Increment the call count. | |
| IncrementCallCount(feedback_vector, slot_id); | |
| // Collect the callable {target} feedback. | |
| CollectCallableFeedback(target, context, feedback_vector, slot_id); | |
| } | |
| void InterpreterAssembler::CallJSAndDispatch( | |
| Node* function, Node* context, const RegListNodePair& args, | |
| ConvertReceiverMode receiver_mode) { | |
| DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_)); | |
| DCHECK(Bytecodes::IsCallOrConstruct(bytecode_) || | |
| bytecode_ == Bytecode::kInvokeIntrinsic); | |
| DCHECK_EQ(Bytecodes::GetReceiverMode(bytecode_), receiver_mode); | |
| Node* args_count; | |
| if (receiver_mode == ConvertReceiverMode::kNullOrUndefined) { | |
| // The receiver is implied, so it is not in the argument list. | |
| args_count = args.reg_count(); | |
| } else { | |
| // Subtract the receiver from the argument count. | |
| Node* receiver_count = Int32Constant(1); | |
| args_count = Int32Sub(args.reg_count(), receiver_count); | |
| } | |
| Callable callable = CodeFactory::InterpreterPushArgsThenCall( | |
| isolate(), receiver_mode, InterpreterPushArgsMode::kOther); | |
| Node* code_target = HeapConstant(callable.code()); | |
| TailCallStubThenBytecodeDispatch(callable.descriptor(), code_target, context, | |
| args_count, args.base_reg_location(), | |
| function); | |
| // TailCallStubThenDispatch updates accumulator with result. | |
| accumulator_use_ = accumulator_use_ | AccumulatorUse::kWrite; | |
| } | |
| template <class... TArgs> | |
| void InterpreterAssembler::CallJSAndDispatch(Node* function, Node* context, | |
| Node* arg_count, | |
| ConvertReceiverMode receiver_mode, | |
| TArgs... args) { | |
| DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_)); | |
| DCHECK(Bytecodes::IsCallOrConstruct(bytecode_) || | |
| bytecode_ == Bytecode::kInvokeIntrinsic); | |
| DCHECK_EQ(Bytecodes::GetReceiverMode(bytecode_), receiver_mode); | |
| Callable callable = CodeFactory::Call(isolate()); | |
| Node* code_target = HeapConstant(callable.code()); | |
| if (receiver_mode == ConvertReceiverMode::kNullOrUndefined) { | |
| // The first argument parameter (the receiver) is implied to be undefined. | |
| TailCallStubThenBytecodeDispatch( | |
| callable.descriptor(), code_target, context, function, arg_count, | |
| static_cast<Node*>(UndefinedConstant()), args...); | |
| } else { | |
| TailCallStubThenBytecodeDispatch(callable.descriptor(), code_target, | |
| context, function, arg_count, args...); | |
| } | |
| // TailCallStubThenDispatch updates accumulator with result. | |
| accumulator_use_ = accumulator_use_ | AccumulatorUse::kWrite; | |
| } | |
| // Instantiate CallJSAndDispatch() for argument counts used by interpreter | |
| // generator. | |
| template V8_EXPORT_PRIVATE void InterpreterAssembler::CallJSAndDispatch( | |
| Node* function, Node* context, Node* arg_count, | |
| ConvertReceiverMode receiver_mode); | |
| template V8_EXPORT_PRIVATE void InterpreterAssembler::CallJSAndDispatch( | |
| Node* function, Node* context, Node* arg_count, | |
| ConvertReceiverMode receiver_mode, Node*); | |
| template V8_EXPORT_PRIVATE void InterpreterAssembler::CallJSAndDispatch( | |
| Node* function, Node* context, Node* arg_count, | |
| ConvertReceiverMode receiver_mode, Node*, Node*); | |
| template V8_EXPORT_PRIVATE void InterpreterAssembler::CallJSAndDispatch( | |
| Node* function, Node* context, Node* arg_count, | |
| ConvertReceiverMode receiver_mode, Node*, Node*, Node*); | |
| void InterpreterAssembler::CallJSWithSpreadAndDispatch( | |
| Node* function, Node* context, const RegListNodePair& args, Node* slot_id, | |
| Node* feedback_vector) { | |
| DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_)); | |
| DCHECK_EQ(Bytecodes::GetReceiverMode(bytecode_), ConvertReceiverMode::kAny); | |
| CollectCallFeedback(function, context, feedback_vector, slot_id); | |
| Comment("call using CallWithSpread builtin"); | |
| Callable callable = CodeFactory::InterpreterPushArgsThenCall( | |
| isolate(), ConvertReceiverMode::kAny, | |
| InterpreterPushArgsMode::kWithFinalSpread); | |
| Node* code_target = HeapConstant(callable.code()); | |
| Node* receiver_count = Int32Constant(1); | |
| Node* args_count = Int32Sub(args.reg_count(), receiver_count); | |
| TailCallStubThenBytecodeDispatch(callable.descriptor(), code_target, context, | |
| args_count, args.base_reg_location(), | |
| function); | |
| // TailCallStubThenDispatch updates accumulator with result. | |
| accumulator_use_ = accumulator_use_ | AccumulatorUse::kWrite; | |
| } | |
| Node* InterpreterAssembler::Construct(Node* target, Node* context, | |
| Node* new_target, | |
| const RegListNodePair& args, | |
| Node* slot_id, Node* feedback_vector) { | |
| DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_)); | |
| VARIABLE(var_result, MachineRepresentation::kTagged); | |
| VARIABLE(var_site, MachineRepresentation::kTagged); | |
| Label extra_checks(this, Label::kDeferred), return_result(this, &var_result), | |
| construct(this), construct_array(this, &var_site); | |
| // Increment the call count. | |
| IncrementCallCount(feedback_vector, slot_id); | |
| // Check if we have monomorphic {new_target} feedback already. | |
| TNode<MaybeObject> feedback = | |
| LoadFeedbackVectorSlot(feedback_vector, slot_id); | |
| Branch(IsWeakReferenceTo(feedback, CAST(new_target)), &construct, | |
| &extra_checks); | |
| BIND(&extra_checks); | |
| { | |
| Label check_allocation_site(this), check_initialized(this), | |
| initialize(this), mark_megamorphic(this); | |
| // Check if it is a megamorphic {new_target}.. | |
| Comment("check if megamorphic"); | |
| Node* is_megamorphic = WordEqual( | |
| feedback, HeapConstant(FeedbackVector::MegamorphicSentinel(isolate()))); | |
| GotoIf(is_megamorphic, &construct); | |
| Comment("check if weak reference"); | |
| GotoIfNot(IsWeakOrClearedHeapObject(feedback), &check_allocation_site); | |
| // If the weak reference is cleared, we have a new chance to become | |
| // monomorphic. | |
| Comment("check if weak reference is cleared"); | |
| Branch(IsClearedWeakHeapObject(feedback), &initialize, &mark_megamorphic); | |
| BIND(&check_allocation_site); | |
| { | |
| // Check if it is an AllocationSite. | |
| Comment("check if allocation site"); | |
| TNode<HeapObject> strong_feedback = CAST(feedback); | |
| GotoIfNot(IsAllocationSite(strong_feedback), &check_initialized); | |
| // Make sure that {target} and {new_target} are the Array constructor. | |
| Node* array_function = LoadContextElement(LoadNativeContext(context), | |
| Context::ARRAY_FUNCTION_INDEX); | |
| GotoIfNot(WordEqual(target, array_function), &mark_megamorphic); | |
| GotoIfNot(WordEqual(new_target, array_function), &mark_megamorphic); | |
| var_site.Bind(strong_feedback); | |
| Goto(&construct_array); | |
| } | |
| BIND(&check_initialized); | |
| { | |
| // Check if it is uninitialized. | |
| Comment("check if uninitialized"); | |
| Node* is_uninitialized = | |
| WordEqual(feedback, LoadRoot(Heap::kuninitialized_symbolRootIndex)); | |
| Branch(is_uninitialized, &initialize, &mark_megamorphic); | |
| } | |
| BIND(&initialize); | |
| { | |
| Comment("check if function in same native context"); | |
| GotoIf(TaggedIsSmi(new_target), &mark_megamorphic); | |
| // Check if the {new_target} is a JSFunction or JSBoundFunction | |
| // in the current native context. | |
| VARIABLE(var_current, MachineRepresentation::kTagged, new_target); | |
| Label loop(this, &var_current), done_loop(this); | |
| Goto(&loop); | |
| BIND(&loop); | |
| { | |
| Label if_boundfunction(this), if_function(this); | |
| Node* current = var_current.value(); | |
| CSA_ASSERT(this, TaggedIsNotSmi(current)); | |
| Node* current_instance_type = LoadInstanceType(current); | |
| GotoIf(InstanceTypeEqual(current_instance_type, JS_BOUND_FUNCTION_TYPE), | |
| &if_boundfunction); | |
| Branch(InstanceTypeEqual(current_instance_type, JS_FUNCTION_TYPE), | |
| &if_function, &mark_megamorphic); | |
| BIND(&if_function); | |
| { | |
| // Check that the JSFunction {current} is in the current native | |
| // context. | |
| Node* current_context = | |
| LoadObjectField(current, JSFunction::kContextOffset); | |
| Node* current_native_context = LoadNativeContext(current_context); | |
| Branch(WordEqual(LoadNativeContext(context), current_native_context), | |
| &done_loop, &mark_megamorphic); | |
| } | |
| BIND(&if_boundfunction); | |
| { | |
| // Continue with the [[BoundTargetFunction]] of {current}. | |
| var_current.Bind(LoadObjectField( | |
| current, JSBoundFunction::kBoundTargetFunctionOffset)); | |
| Goto(&loop); | |
| } | |
| } | |
| BIND(&done_loop); | |
| // Create an AllocationSite if {target} and {new_target} refer | |
| // to the current native context's Array constructor. | |
| Label create_allocation_site(this), store_weak_reference(this); | |
| GotoIfNot(WordEqual(target, new_target), &store_weak_reference); | |
| Node* array_function = LoadContextElement(LoadNativeContext(context), | |
| Context::ARRAY_FUNCTION_INDEX); | |
| Branch(WordEqual(target, array_function), &create_allocation_site, | |
| &store_weak_reference); | |
| BIND(&create_allocation_site); | |
| { | |
| var_site.Bind(CreateAllocationSiteInFeedbackVector(feedback_vector, | |
| SmiTag(slot_id))); | |
| ReportFeedbackUpdate(feedback_vector, slot_id, | |
| "Construct:CreateAllocationSite"); | |
| Goto(&construct_array); | |
| } | |
| BIND(&store_weak_reference); | |
| { | |
| StoreWeakReferenceInFeedbackVector(feedback_vector, slot_id, | |
| CAST(new_target)); | |
| ReportFeedbackUpdate(feedback_vector, slot_id, | |
| "Construct:StoreWeakReference"); | |
| Goto(&construct); | |
| } | |
| } | |
| BIND(&mark_megamorphic); | |
| { | |
| // MegamorphicSentinel is an immortal immovable object so | |
| // write-barrier is not needed. | |
| Comment("transition to megamorphic"); | |
| DCHECK(Heap::RootIsImmortalImmovable(Heap::kmegamorphic_symbolRootIndex)); | |
| StoreFeedbackVectorSlot( | |
| feedback_vector, slot_id, | |
| HeapConstant(FeedbackVector::MegamorphicSentinel(isolate())), | |
| SKIP_WRITE_BARRIER); | |
| ReportFeedbackUpdate(feedback_vector, slot_id, | |
| "Construct:TransitionMegamorphic"); | |
| Goto(&construct); | |
| } | |
| } | |
| BIND(&construct_array); | |
| { | |
| // TODO(bmeurer): Introduce a dedicated builtin to deal with the Array | |
| // constructor feedback collection inside of Ignition. | |
| Comment("call using ConstructArray builtin"); | |
| Callable callable = CodeFactory::InterpreterPushArgsThenConstruct( | |
| isolate(), InterpreterPushArgsMode::kArrayFunction); | |
| Node* code_target = HeapConstant(callable.code()); | |
| var_result.Bind(CallStub(callable.descriptor(), code_target, context, | |
| args.reg_count(), new_target, target, | |
| var_site.value(), args.base_reg_location())); | |
| Goto(&return_result); | |
| } | |
| BIND(&construct); | |
| { | |
| // TODO(bmeurer): Remove the generic type_info parameter from the Construct. | |
| Comment("call using Construct builtin"); | |
| Callable callable = CodeFactory::InterpreterPushArgsThenConstruct( | |
| isolate(), InterpreterPushArgsMode::kOther); | |
| Node* code_target = HeapConstant(callable.code()); | |
| var_result.Bind(CallStub(callable.descriptor(), code_target, context, | |
| args.reg_count(), new_target, target, | |
| UndefinedConstant(), args.base_reg_location())); | |
| Goto(&return_result); | |
| } | |
| BIND(&return_result); | |
| return var_result.value(); | |
| } | |
| Node* InterpreterAssembler::ConstructWithSpread(Node* target, Node* context, | |
| Node* new_target, | |
| const RegListNodePair& args, | |
| Node* slot_id, | |
| Node* feedback_vector) { | |
| // TODO(bmeurer): Unify this with the Construct bytecode feedback | |
| // above once we have a way to pass the AllocationSite to the Array | |
| // constructor _and_ spread the last argument at the same time. | |
| DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_)); | |
| Label extra_checks(this, Label::kDeferred), construct(this); | |
| // Increment the call count. | |
| IncrementCallCount(feedback_vector, slot_id); | |
| // Check if we have monomorphic {new_target} feedback already. | |
| TNode<MaybeObject> feedback = | |
| LoadFeedbackVectorSlot(feedback_vector, slot_id); | |
| Branch(IsWeakReferenceTo(feedback, CAST(new_target)), &construct, | |
| &extra_checks); | |
| BIND(&extra_checks); | |
| { | |
| Label check_initialized(this), initialize(this), mark_megamorphic(this); | |
| // Check if it is a megamorphic {new_target}. | |
| Comment("check if megamorphic"); | |
| Node* is_megamorphic = WordEqual( | |
| feedback, HeapConstant(FeedbackVector::MegamorphicSentinel(isolate()))); | |
| GotoIf(is_megamorphic, &construct); | |
| Comment("check if weak reference"); | |
| GotoIfNot(IsWeakOrClearedHeapObject(feedback), &check_initialized); | |
| // If the weak reference is cleared, we have a new chance to become | |
| // monomorphic. | |
| Comment("check if weak reference is cleared"); | |
| Branch(IsClearedWeakHeapObject(feedback), &initialize, &mark_megamorphic); | |
| BIND(&check_initialized); | |
| { | |
| // Check if it is uninitialized. | |
| Comment("check if uninitialized"); | |
| Node* is_uninitialized = | |
| WordEqual(feedback, LoadRoot(Heap::kuninitialized_symbolRootIndex)); | |
| Branch(is_uninitialized, &initialize, &mark_megamorphic); | |
| } | |
| BIND(&initialize); | |
| { | |
| Comment("check if function in same native context"); | |
| GotoIf(TaggedIsSmi(new_target), &mark_megamorphic); | |
| // Check if the {new_target} is a JSFunction or JSBoundFunction | |
| // in the current native context. | |
| VARIABLE(var_current, MachineRepresentation::kTagged, new_target); | |
| Label loop(this, &var_current), done_loop(this); | |
| Goto(&loop); | |
| BIND(&loop); | |
| { | |
| Label if_boundfunction(this), if_function(this); | |
| Node* current = var_current.value(); | |
| CSA_ASSERT(this, TaggedIsNotSmi(current)); | |
| Node* current_instance_type = LoadInstanceType(current); | |
| GotoIf(InstanceTypeEqual(current_instance_type, JS_BOUND_FUNCTION_TYPE), | |
| &if_boundfunction); | |
| Branch(InstanceTypeEqual(current_instance_type, JS_FUNCTION_TYPE), | |
| &if_function, &mark_megamorphic); | |
| BIND(&if_function); | |
| { | |
| // Check that the JSFunction {current} is in the current native | |
| // context. | |
| Node* current_context = | |
| LoadObjectField(current, JSFunction::kContextOffset); | |
| Node* current_native_context = LoadNativeContext(current_context); | |
| Branch(WordEqual(LoadNativeContext(context), current_native_context), | |
| &done_loop, &mark_megamorphic); | |
| } | |
| BIND(&if_boundfunction); | |
| { | |
| // Continue with the [[BoundTargetFunction]] of {current}. | |
| var_current.Bind(LoadObjectField( | |
| current, JSBoundFunction::kBoundTargetFunctionOffset)); | |
| Goto(&loop); | |
| } | |
| } | |
| BIND(&done_loop); | |
| StoreWeakReferenceInFeedbackVector(feedback_vector, slot_id, | |
| CAST(new_target)); | |
| ReportFeedbackUpdate(feedback_vector, slot_id, | |
| "ConstructWithSpread:Initialize"); | |
| Goto(&construct); | |
| } | |
| BIND(&mark_megamorphic); | |
| { | |
| // MegamorphicSentinel is an immortal immovable object so | |
| // write-barrier is not needed. | |
| Comment("transition to megamorphic"); | |
| DCHECK(Heap::RootIsImmortalImmovable(Heap::kmegamorphic_symbolRootIndex)); | |
| StoreFeedbackVectorSlot( | |
| feedback_vector, slot_id, | |
| HeapConstant(FeedbackVector::MegamorphicSentinel(isolate())), | |
| SKIP_WRITE_BARRIER); | |
| ReportFeedbackUpdate(feedback_vector, slot_id, | |
| "ConstructWithSpread:TransitionMegamorphic"); | |
| Goto(&construct); | |
| } | |
| } | |
| BIND(&construct); | |
| Comment("call using ConstructWithSpread builtin"); | |
| Callable callable = CodeFactory::InterpreterPushArgsThenConstruct( | |
| isolate(), InterpreterPushArgsMode::kWithFinalSpread); | |
| Node* code_target = HeapConstant(callable.code()); | |
| return CallStub(callable.descriptor(), code_target, context, args.reg_count(), | |
| new_target, target, UndefinedConstant(), | |
| args.base_reg_location()); | |
| } | |
| Node* InterpreterAssembler::CallRuntimeN(Node* function_id, Node* context, | |
| const RegListNodePair& args, | |
| int result_size) { | |
| DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_)); | |
| DCHECK(Bytecodes::IsCallRuntime(bytecode_)); | |
| 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, ChangeUint32ToWord(function_offset)); | |
| Node* function_entry = | |
| Load(MachineType::Pointer(), function, | |
| IntPtrConstant(offsetof(Runtime::Function, entry))); | |
| return CallStubR(callable.descriptor(), result_size, code_target, context, | |
| args.reg_count(), args.base_reg_location(), function_entry); | |
| } | |
| void InterpreterAssembler::UpdateInterruptBudget(Node* weight, bool backward) { | |
| Comment("[ UpdateInterruptBudget"); | |
| Node* budget_offset = | |
| IntPtrConstant(BytecodeArray::kInterruptBudgetOffset - kHeapObjectTag); | |
| // Assert that the weight is positive (negative weights should be implemented | |
| // as backward updates). | |
| CSA_ASSERT(this, Int32GreaterThanOrEqual(weight, Int32Constant(0))); | |
| // Update budget by |weight| and check if it reaches zero. | |
| Variable new_budget(this, MachineRepresentation::kWord32); | |
| Node* old_budget = | |
| Load(MachineType::Int32(), BytecodeArrayTaggedPointer(), budget_offset); | |
| // Make sure we include the current bytecode in the budget calculation. | |
| Node* budget_after_bytecode = | |
| Int32Sub(old_budget, Int32Constant(CurrentBytecodeSize())); | |
| if (backward) { | |
| new_budget.Bind(Int32Sub(budget_after_bytecode, weight)); | |
| Node* condition = | |
| Int32GreaterThanOrEqual(new_budget.value(), Int32Constant(0)); | |
| Label ok(this), interrupt_check(this, Label::kDeferred); | |
| Branch(condition, &ok, &interrupt_check); | |
| // Perform interrupt and reset budget. | |
| BIND(&interrupt_check); | |
| { | |
| CallRuntime(Runtime::kInterrupt, GetContext()); | |
| new_budget.Bind(Int32Constant(Interpreter::InterruptBudget())); | |
| Goto(&ok); | |
| } | |
| BIND(&ok); | |
| } else { | |
| // For a forward jump, we know we only increase the interrupt budget, so | |
| // no need to check if it's below zero. | |
| new_budget.Bind(Int32Add(budget_after_bytecode, weight)); | |
| } | |
| // Update budget. | |
| StoreNoWriteBarrier(MachineRepresentation::kWord32, | |
| BytecodeArrayTaggedPointer(), budget_offset, | |
| new_budget.value()); | |
| Comment("] UpdateInterruptBudget"); | |
| } | |
| Node* InterpreterAssembler::Advance() { return Advance(CurrentBytecodeSize()); } | |
| Node* InterpreterAssembler::Advance(int delta) { | |
| return Advance(IntPtrConstant(delta)); | |
| } | |
| Node* InterpreterAssembler::Advance(Node* delta, bool backward) { | |
| #ifdef V8_TRACE_IGNITION | |
| TraceBytecode(Runtime::kInterpreterTraceBytecodeExit); | |
| #endif | |
| Node* next_offset = backward ? IntPtrSub(BytecodeOffset(), delta) | |
| : IntPtrAdd(BytecodeOffset(), delta); | |
| bytecode_offset_.Bind(next_offset); | |
| return next_offset; | |
| } | |
| Node* InterpreterAssembler::Jump(Node* delta, bool backward) { | |
| DCHECK(!Bytecodes::IsStarLookahead(bytecode_, operand_scale_)); | |
| UpdateInterruptBudget(TruncateIntPtrToInt32(delta), backward); | |
| Node* new_bytecode_offset = Advance(delta, backward); | |
| Node* target_bytecode = LoadBytecode(new_bytecode_offset); | |
| return DispatchToBytecode(target_bytecode, new_bytecode_offset); | |
| } | |
| Node* InterpreterAssembler::Jump(Node* delta) { return Jump(delta, false); } | |
| Node* InterpreterAssembler::JumpBackward(Node* delta) { | |
| return Jump(delta, true); | |
| } | |
| void InterpreterAssembler::JumpConditional(Node* condition, Node* delta) { | |
| Label match(this), 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); | |
| } | |
| Node* InterpreterAssembler::LoadBytecode(Node* bytecode_offset) { | |
| Node* bytecode = | |
| Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(), bytecode_offset); | |
| return ChangeUint32ToWord(bytecode); | |
| } | |
| Node* InterpreterAssembler::StarDispatchLookahead(Node* target_bytecode) { | |
| Label do_inline_star(this), done(this); | |
| Variable var_bytecode(this, MachineType::PointerRepresentation()); | |
| var_bytecode.Bind(target_bytecode); | |
| Node* star_bytecode = IntPtrConstant(static_cast<int>(Bytecode::kStar)); | |
| Node* is_star = WordEqual(target_bytecode, star_bytecode); | |
| Branch(is_star, &do_inline_star, &done); | |
| BIND(&do_inline_star); | |
| { | |
| InlineStar(); | |
| var_bytecode.Bind(LoadBytecode(BytecodeOffset())); | |
| Goto(&done); | |
| } | |
| BIND(&done); | |
| return var_bytecode.value(); | |
| } | |
| void InterpreterAssembler::InlineStar() { | |
| Bytecode previous_bytecode = bytecode_; | |
| AccumulatorUse previous_acc_use = accumulator_use_; | |
| bytecode_ = Bytecode::kStar; | |
| accumulator_use_ = AccumulatorUse::kNone; | |
| #ifdef V8_TRACE_IGNITION | |
| TraceBytecode(Runtime::kInterpreterTraceBytecodeEntry); | |
| #endif | |
| StoreRegister(GetAccumulator(), | |
| BytecodeOperandReg(0, LoadSensitivity::kSafe)); | |
| DCHECK_EQ(accumulator_use_, Bytecodes::GetAccumulatorUse(bytecode_)); | |
| Advance(); | |
| bytecode_ = previous_bytecode; | |
| accumulator_use_ = previous_acc_use; | |
| } | |
| Node* InterpreterAssembler::Dispatch() { | |
| Comment("========= Dispatch"); | |
| DCHECK_IMPLIES(Bytecodes::MakesCallAlongCriticalPath(bytecode_), made_call_); | |
| Node* target_offset = Advance(); | |
| Node* target_bytecode = LoadBytecode(target_offset); | |
| if (Bytecodes::IsStarLookahead(bytecode_, operand_scale_)) { | |
| target_bytecode = StarDispatchLookahead(target_bytecode); | |
| } | |
| return DispatchToBytecode(target_bytecode, BytecodeOffset()); | |
| } | |
| Node* InterpreterAssembler::DispatchToBytecode(Node* target_bytecode, | |
| Node* new_bytecode_offset) { | |
| if (FLAG_trace_ignition_dispatches) { | |
| TraceBytecodeDispatch(target_bytecode); | |
| } | |
| Node* target_code_entry = | |
| Load(MachineType::Pointer(), DispatchTableRawPointer(), | |
| TimesPointerSize(target_bytecode)); | |
| return DispatchToBytecodeHandlerEntry(target_code_entry, new_bytecode_offset, | |
| target_bytecode); | |
| } | |
| Node* InterpreterAssembler::DispatchToBytecodeHandler(Node* handler, | |
| Node* bytecode_offset, | |
| Node* target_bytecode) { | |
| // TODO(ishell): Add CSA::CodeEntryPoint(code). | |
| Node* handler_entry = | |
| IntPtrAdd(BitcastTaggedToWord(handler), | |
| IntPtrConstant(Code::kHeaderSize - kHeapObjectTag)); | |
| return DispatchToBytecodeHandlerEntry(handler_entry, bytecode_offset, | |
| target_bytecode); | |
| } | |
| Node* InterpreterAssembler::DispatchToBytecodeHandlerEntry( | |
| Node* handler_entry, Node* bytecode_offset, Node* target_bytecode) { | |
| // Propagate speculation poisoning. | |
| Node* poisoned_handler_entry = WordPoisonOnSpeculation(handler_entry); | |
| return TailCallBytecodeDispatch( | |
| InterpreterDispatchDescriptor{}, poisoned_handler_entry, | |
| GetAccumulatorUnchecked(), bytecode_offset, BytecodeArrayTaggedPointer(), | |
| DispatchTableRawPointer()); | |
| } | |
| void InterpreterAssembler::DispatchWide(OperandScale operand_scale) { | |
| // Dispatching a wide bytecode requires treating the prefix | |
| // bytecode a base pointer into the dispatch table and dispatching | |
| // the bytecode that follows relative to this base. | |
| // | |
| // Indices 0-255 correspond to bytecodes with operand_scale == 0 | |
| // Indices 256-511 correspond to bytecodes with operand_scale == 1 | |
| // Indices 512-767 correspond to bytecodes with operand_scale == 2 | |
| DCHECK_IMPLIES(Bytecodes::MakesCallAlongCriticalPath(bytecode_), made_call_); | |
| Node* next_bytecode_offset = Advance(1); | |
| Node* next_bytecode = LoadBytecode(next_bytecode_offset); | |
| if (FLAG_trace_ignition_dispatches) { | |
| TraceBytecodeDispatch(next_bytecode); | |
| } | |
| Node* base_index; | |
| switch (operand_scale) { | |
| case OperandScale::kDouble: | |
| base_index = IntPtrConstant(1 << kBitsPerByte); | |
| break; | |
| case OperandScale::kQuadruple: | |
| base_index = IntPtrConstant(2 << kBitsPerByte); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| Node* target_index = IntPtrAdd(base_index, next_bytecode); | |
| Node* target_code_entry = | |
| Load(MachineType::Pointer(), DispatchTableRawPointer(), | |
| TimesPointerSize(target_index)); | |
| DispatchToBytecodeHandlerEntry(target_code_entry, next_bytecode_offset, | |
| next_bytecode); | |
| } | |
| void InterpreterAssembler::UpdateInterruptBudgetOnReturn() { | |
| // TODO(rmcilroy): Investigate whether it is worth supporting self | |
| // optimization of primitive functions like FullCodegen. | |
| // Update profiling count by the number of bytes between the end of the | |
| // current bytecode and the start of the first one, to simulate backedge to | |
| // start of function. | |
| // | |
| // With headers and current offset, the bytecode array layout looks like: | |
| // | |
| // <---------- simulated backedge ---------- | |
| // | header | first bytecode | .... | return bytecode | | |
| // |<------ current offset -------> | |
| // ^ tagged bytecode array pointer | |
| // | |
| // UpdateInterruptBudget already handles adding the bytecode size to the | |
| // length of the back-edge, so we just have to correct for the non-zero offset | |
| // of the first bytecode. | |
| const int kFirstBytecodeOffset = BytecodeArray::kHeaderSize - kHeapObjectTag; | |
| Node* profiling_weight = Int32Sub(TruncateIntPtrToInt32(BytecodeOffset()), | |
| Int32Constant(kFirstBytecodeOffset)); | |
| UpdateInterruptBudget(profiling_weight, true); | |
| } | |
| Node* InterpreterAssembler::LoadOSRNestingLevel() { | |
| return LoadObjectField(BytecodeArrayTaggedPointer(), | |
| BytecodeArray::kOSRNestingLevelOffset, | |
| MachineType::Int8()); | |
| } | |
| void InterpreterAssembler::Abort(AbortReason abort_reason) { | |
| disable_stack_check_across_call_ = true; | |
| Node* abort_id = SmiConstant(abort_reason); | |
| CallRuntime(Runtime::kAbort, GetContext(), abort_id); | |
| disable_stack_check_across_call_ = false; | |
| } | |
| void InterpreterAssembler::AbortIfWordNotEqual(Node* lhs, Node* rhs, | |
| AbortReason abort_reason) { | |
| Label ok(this), abort(this, Label::kDeferred); | |
| Branch(WordEqual(lhs, rhs), &ok, &abort); | |
| BIND(&abort); | |
| Abort(abort_reason); | |
| Goto(&ok); | |
| BIND(&ok); | |
| } | |
| void InterpreterAssembler::MaybeDropFrames(Node* context) { | |
| Node* restart_fp_address = | |
| ExternalConstant(ExternalReference::debug_restart_fp_address(isolate())); | |
| Node* restart_fp = Load(MachineType::Pointer(), restart_fp_address); | |
| Node* null = IntPtrConstant(0); | |
| Label ok(this), drop_frames(this); | |
| Branch(IntPtrEqual(restart_fp, null), &ok, &drop_frames); | |
| BIND(&drop_frames); | |
| // We don't expect this call to return since the frame dropper tears down | |
| // the stack and jumps into the function on the target frame to restart it. | |
| CallStub(CodeFactory::FrameDropperTrampoline(isolate()), context, restart_fp); | |
| Abort(AbortReason::kUnexpectedReturnFromFrameDropper); | |
| Goto(&ok); | |
| BIND(&ok); | |
| } | |
| void InterpreterAssembler::TraceBytecode(Runtime::FunctionId function_id) { | |
| CallRuntime(function_id, GetContext(), BytecodeArrayTaggedPointer(), | |
| SmiTag(BytecodeOffset()), GetAccumulatorUnchecked()); | |
| } | |
| void InterpreterAssembler::TraceBytecodeDispatch(Node* target_bytecode) { | |
| Node* counters_table = ExternalConstant( | |
| ExternalReference::interpreter_dispatch_counters(isolate())); | |
| Node* source_bytecode_table_index = IntPtrConstant( | |
| static_cast<int>(bytecode_) * (static_cast<int>(Bytecode::kLast) + 1)); | |
| Node* counter_offset = | |
| TimesPointerSize(IntPtrAdd(source_bytecode_table_index, target_bytecode)); | |
| Node* old_counter = | |
| Load(MachineType::IntPtr(), counters_table, counter_offset); | |
| Label counter_ok(this), counter_saturated(this, Label::kDeferred); | |
| Node* counter_reached_max = WordEqual( | |
| old_counter, IntPtrConstant(std::numeric_limits<uintptr_t>::max())); | |
| Branch(counter_reached_max, &counter_saturated, &counter_ok); | |
| BIND(&counter_ok); | |
| { | |
| Node* new_counter = IntPtrAdd(old_counter, IntPtrConstant(1)); | |
| StoreNoWriteBarrier(MachineType::PointerRepresentation(), counters_table, | |
| counter_offset, new_counter); | |
| Goto(&counter_saturated); | |
| } | |
| BIND(&counter_saturated); | |
| } | |
| // static | |
| bool InterpreterAssembler::TargetSupportsUnalignedAccess() { | |
| #if V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64 | |
| return false; | |
| #elif V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_S390 || \ | |
| V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_PPC | |
| return true; | |
| #else | |
| #error "Unknown Architecture" | |
| #endif | |
| } | |
| void InterpreterAssembler::AbortIfRegisterCountInvalid( | |
| Node* parameters_and_registers, Node* formal_parameter_count, | |
| Node* register_count) { | |
| Node* array_size = LoadAndUntagFixedArrayBaseLength(parameters_and_registers); | |
| Label ok(this), abort(this, Label::kDeferred); | |
| Branch(UintPtrLessThanOrEqual( | |
| IntPtrAdd(formal_parameter_count, register_count), array_size), | |
| &ok, &abort); | |
| BIND(&abort); | |
| Abort(AbortReason::kInvalidParametersAndRegistersInGenerator); | |
| Goto(&ok); | |
| BIND(&ok); | |
| } | |
| Node* InterpreterAssembler::ExportParametersAndRegisterFile( | |
| TNode<FixedArray> array, const RegListNodePair& registers, | |
| TNode<Int32T> formal_parameter_count) { | |
| // Store the formal parameters (without receiver) followed by the | |
| // registers into the generator's internal parameters_and_registers field. | |
| TNode<IntPtrT> formal_parameter_count_intptr = | |
| ChangeInt32ToIntPtr(formal_parameter_count); | |
| Node* register_count = ChangeUint32ToWord(registers.reg_count()); | |
| if (FLAG_debug_code) { | |
| CSA_ASSERT(this, IntPtrEqual(registers.base_reg_location(), | |
| RegisterLocation(Register(0)))); | |
| AbortIfRegisterCountInvalid(array, formal_parameter_count_intptr, | |
| register_count); | |
| } | |
| { | |
| Variable var_index(this, MachineType::PointerRepresentation()); | |
| var_index.Bind(IntPtrConstant(0)); | |
| // Iterate over parameters and write them into the array. | |
| Label loop(this, &var_index), done_loop(this); | |
| Node* reg_base = IntPtrAdd( | |
| IntPtrConstant(Register::FromParameterIndex(0, 1).ToOperand() - 1), | |
| formal_parameter_count_intptr); | |
| Goto(&loop); | |
| BIND(&loop); | |
| { | |
| Node* index = var_index.value(); | |
| GotoIfNot(UintPtrLessThan(index, formal_parameter_count_intptr), | |
| &done_loop); | |
| Node* reg_index = IntPtrSub(reg_base, index); | |
| Node* value = LoadRegister(reg_index); | |
| StoreFixedArrayElement(array, index, value); | |
| var_index.Bind(IntPtrAdd(index, IntPtrConstant(1))); | |
| Goto(&loop); | |
| } | |
| BIND(&done_loop); | |
| } | |
| { | |
| // Iterate over register file and write values into array. | |
| // The mapping of register to array index must match that used in | |
| // BytecodeGraphBuilder::VisitResumeGenerator. | |
| Variable var_index(this, MachineType::PointerRepresentation()); | |
| var_index.Bind(IntPtrConstant(0)); | |
| Label loop(this, &var_index), done_loop(this); | |
| Goto(&loop); | |
| BIND(&loop); | |
| { | |
| Node* index = var_index.value(); | |
| GotoIfNot(UintPtrLessThan(index, register_count), &done_loop); | |
| Node* reg_index = | |
| IntPtrSub(IntPtrConstant(Register(0).ToOperand()), index); | |
| Node* value = LoadRegister(reg_index); | |
| Node* array_index = IntPtrAdd(formal_parameter_count_intptr, index); | |
| StoreFixedArrayElement(array, array_index, value); | |
| var_index.Bind(IntPtrAdd(index, IntPtrConstant(1))); | |
| Goto(&loop); | |
| } | |
| BIND(&done_loop); | |
| } | |
| return array; | |
| } | |
| Node* InterpreterAssembler::ImportRegisterFile( | |
| TNode<FixedArray> array, const RegListNodePair& registers, | |
| TNode<Int32T> formal_parameter_count) { | |
| TNode<IntPtrT> formal_parameter_count_intptr = | |
| ChangeInt32ToIntPtr(formal_parameter_count); | |
| TNode<UintPtrT> register_count = ChangeUint32ToWord(registers.reg_count()); | |
| if (FLAG_debug_code) { | |
| CSA_ASSERT(this, IntPtrEqual(registers.base_reg_location(), | |
| RegisterLocation(Register(0)))); | |
| AbortIfRegisterCountInvalid(array, formal_parameter_count_intptr, | |
| register_count); | |
| } | |
| TVARIABLE(IntPtrT, var_index, IntPtrConstant(0)); | |
| // Iterate over array and write values into register file. Also erase the | |
| // array contents to not keep them alive artificially. | |
| Label loop(this, &var_index), done_loop(this); | |
| Goto(&loop); | |
| BIND(&loop); | |
| { | |
| TNode<IntPtrT> index = var_index.value(); | |
| GotoIfNot(UintPtrLessThan(index, register_count), &done_loop); | |
| TNode<IntPtrT> array_index = | |
| IntPtrAdd(formal_parameter_count_intptr, index); | |
| TNode<Object> value = LoadFixedArrayElement(array, array_index); | |
| TNode<IntPtrT> reg_index = | |
| IntPtrSub(IntPtrConstant(Register(0).ToOperand()), index); | |
| StoreRegister(value, reg_index); | |
| StoreFixedArrayElement(array, array_index, | |
| LoadRoot(Heap::kStaleRegisterRootIndex)); | |
| var_index = IntPtrAdd(index, IntPtrConstant(1)); | |
| Goto(&loop); | |
| } | |
| BIND(&done_loop); | |
| return array; | |
| } | |
| int InterpreterAssembler::CurrentBytecodeSize() const { | |
| return Bytecodes::Size(bytecode_, operand_scale_); | |
| } | |
| void InterpreterAssembler::ToNumberOrNumeric(Object::Conversion mode) { | |
| Node* object = GetAccumulator(); | |
| Node* context = GetContext(); | |
| Variable var_type_feedback(this, MachineRepresentation::kTaggedSigned); | |
| Variable var_result(this, MachineRepresentation::kTagged); | |
| Label if_done(this), if_objectissmi(this), if_objectisheapnumber(this), | |
| if_objectisother(this, Label::kDeferred); | |
| GotoIf(TaggedIsSmi(object), &if_objectissmi); | |
| Branch(IsHeapNumber(object), &if_objectisheapnumber, &if_objectisother); | |
| BIND(&if_objectissmi); | |
| { | |
| var_result.Bind(object); | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kSignedSmall)); | |
| Goto(&if_done); | |
| } | |
| BIND(&if_objectisheapnumber); | |
| { | |
| var_result.Bind(object); | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kNumber)); | |
| Goto(&if_done); | |
| } | |
| BIND(&if_objectisother); | |
| { | |
| auto builtin = Builtins::kNonNumberToNumber; | |
| if (mode == Object::Conversion::kToNumeric) { | |
| builtin = Builtins::kNonNumberToNumeric; | |
| // Special case for collecting BigInt feedback. | |
| Label not_bigint(this); | |
| GotoIfNot(IsBigInt(object), ¬_bigint); | |
| { | |
| var_result.Bind(object); | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kBigInt)); | |
| Goto(&if_done); | |
| } | |
| BIND(¬_bigint); | |
| } | |
| // Convert {object} by calling out to the appropriate builtin. | |
| var_result.Bind(CallBuiltin(builtin, context, object)); | |
| var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kAny)); | |
| Goto(&if_done); | |
| } | |
| BIND(&if_done); | |
| // Record the type feedback collected for {object}. | |
| Node* slot_index = BytecodeOperandIdx(0); | |
| Node* feedback_vector = LoadFeedbackVector(); | |
| UpdateFeedback(var_type_feedback.value(), feedback_vector, slot_index); | |
| SetAccumulator(var_result.value()); | |
| Dispatch(); | |
| } | |
| void InterpreterAssembler::DeserializeLazyAndDispatch() { | |
| Node* context = GetContext(); | |
| Node* bytecode_offset = BytecodeOffset(); | |
| Node* bytecode = LoadBytecode(bytecode_offset); | |
| Node* target_handler = | |
| CallRuntime(Runtime::kInterpreterDeserializeLazy, context, | |
| SmiTag(bytecode), SmiConstant(operand_scale())); | |
| DispatchToBytecodeHandler(target_handler, bytecode_offset, bytecode); | |
| } | |
| } // namespace interpreter | |
| } // namespace internal | |
| } // namespace v8 |