/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/compiler/backend/instruction-selector.cc
4 116 строк
157 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// Copyright 2014 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/compiler/backend/instruction-selector.h" #include <limits> #include <optional> #include "include/v8-internal.h" #include "src/base/iterator.h" #include "src/codegen/interface-descriptors-inl.h" #include "src/codegen/machine-type.h" #include "src/codegen/tick-counter.h" #include "src/common/globals.h" #include "src/compiler/backend/instruction-selector-impl.h" #include "src/compiler/backend/instruction.h" #include "src/compiler/compiler-source-position-table.h" #include "src/compiler/globals.h" #include "src/compiler/js-heap-broker.h" #include "src/compiler/state-values-utils.h" #include "src/compiler/turboshaft/index.h" #include "src/compiler/turboshaft/operations.h" #include "src/compiler/turboshaft/opmasks.h" #include "src/compiler/turboshaft/representations.h" #include "src/numbers/conversions-inl.h" #include "src/zone/zone-containers.h" #if V8_ENABLE_WEBASSEMBLY #include "src/wasm/simd-shuffle.h" #endif // V8_ENABLE_WEBASSEMBLY namespace v8 { namespace internal { namespace compiler { #define VISIT_UNSUPPORTED_OP(op) \ void InstructionSelector::Visit##op(OpIndex) { UNIMPLEMENTED(); } using namespace turboshaft; // NOLINT(build/namespaces) namespace { // Here we really want the raw Bits of the mask, but the `.bits()` method is // not constexpr, and so users of this constant need to call it. // TODO(turboshaft): EffectDimensions could probably be defined via // base::Flags<> instead, which should solve this. constexpr EffectDimensions kTurboshaftEffectLevelMask = OpEffects().CanReadMemory().produces; } InstructionSelector::InstructionSelector( Zone* zone, size_t node_count, Linkage* linkage, InstructionSequence* sequence, Graph* schedule, source_position_table_t* source_positions, Frame* frame, InstructionSelector::EnableSwitchJumpTable enable_switch_jump_table, TickCounter* tick_counter, JSHeapBroker* broker, size_t* max_unoptimized_frame_height, size_t* max_pushed_argument_count, InstructionSelector::SourcePositionMode source_position_mode, CpuFeatureSet features, InstructionSelector::EnableScheduling enable_scheduling, InstructionSelector::EnableRootsRelativeAddressing enable_roots_relative_addressing, InstructionSelector::EnableTraceTurboJson trace_turbo, InstructionSelector::EnsureDeterministicNan ensure_deterministic_nan) : OperationMatcher(*schedule), zone_(zone), linkage_(linkage), sequence_(sequence), source_positions_(source_positions), source_position_mode_(source_position_mode), features_(features), schedule_(schedule), current_block_(nullptr), instructions_(zone), continuation_inputs_(sequence->zone()), continuation_outputs_(sequence->zone()), continuation_temps_(sequence->zone()), defined_(static_cast<int>(node_count), zone), used_(static_cast<int>(node_count), zone), effect_level_(node_count, 0, zone), virtual_registers_(node_count, InstructionOperand::kInvalidVirtualRegister, zone), virtual_register_rename_(zone), scheduler_(nullptr), enable_scheduling_(enable_scheduling), enable_roots_relative_addressing_(enable_roots_relative_addressing), enable_switch_jump_table_(enable_switch_jump_table), state_values_cache_(zone), frame_(frame), instruction_selection_failed_(false), instr_origins_(sequence->zone()), trace_turbo_(trace_turbo), ensure_deterministic_nan_(ensure_deterministic_nan), tick_counter_(tick_counter), broker_(broker), max_unoptimized_frame_height_(max_unoptimized_frame_height), max_pushed_argument_count_(max_pushed_argument_count) #if V8_TARGET_ARCH_64_BIT , node_count_(node_count), phi_states_(zone) #endif { turboshaft_use_map_.emplace(*schedule_, zone); protected_loads_to_remove_.emplace(static_cast<int>(node_count), zone); additional_protected_instructions_.emplace(static_cast<int>(node_count), zone); DCHECK_EQ(*max_unoptimized_frame_height, 0); // Caller-initialized. instructions_.reserve(node_count); continuation_inputs_.reserve(5); continuation_outputs_.reserve(2); if (trace_turbo_) { instr_origins_.assign(node_count, {-1, 0}); } } std::optional<BailoutReason> InstructionSelector::SelectInstructions() { // Mark the inputs of all phis in loop headers as used. ZoneVector<Block*> blocks = rpo_order(schedule()); for (const Block* block : blocks) { if (!IsLoopHeader(block)) continue; DCHECK_LE(2u, PredecessorCount(block)); for (OpIndex node : nodes(block)) { const PhiOp* phi = TryCast<PhiOp>(node); if (!phi) continue; // Mark all inputs as used. for (OpIndex input : phi->inputs()) { MarkAsUsed(input); } } } // Visit each basic block in post order. for (const Block* block : base::Reversed(blocks)) { VisitBlock(block); if (instruction_selection_failed()) return BailoutReason::kTurbofanCodeGenerationFailed; } // Schedule the selected instructions. if (UseInstructionScheduling()) { scheduler_ = zone()->template New<InstructionScheduler>(zone(), sequence()); } for (const Block* block : blocks) { InstructionBlock* instruction_block = sequence()->InstructionBlockAt(this->rpo_number(block)); for (size_t i = 0; i < instruction_block->phis().size(); i++) { UpdateRenamesInPhi(instruction_block->PhiAt(i)); } size_t end = instruction_block->code_end(); size_t start = instruction_block->code_start(); DCHECK_LE(end, start); StartBlock(this->rpo_number(block)); if (end != start) { while (start-- > end + 1) { UpdateRenames(instructions_[start]); AddInstruction(instructions_[start]); } UpdateRenames(instructions_[end]); } Instruction* terminator = instructions_[end]; EndBlock(this->rpo_number(block), terminator); } #if DEBUG sequence()->ValidateSSA(); #endif return std::nullopt; } void InstructionSelector::StartBlock(RpoNumber rpo) { if (UseInstructionScheduling()) { DCHECK_NOT_NULL(scheduler_); scheduler_->StartBlock(rpo); } else { sequence()->StartBlock(rpo); } } void InstructionSelector::EndBlock(RpoNumber rpo, Instruction* terminator) { if (UseInstructionScheduling()) { DCHECK_NOT_NULL(scheduler_); scheduler_->EndBlock(rpo, terminator); } else { sequence()->EndBlock(rpo, terminator); } } void InstructionSelector::AddInstruction(Instruction* instr) { if (UseInstructionScheduling()) { DCHECK_NOT_NULL(scheduler_); scheduler_->AddInstruction(instr); } else { sequence()->AddInstruction(instr); } } Instruction* InstructionSelector::Emit(InstructionCode opcode, InstructionOperand output, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; return Emit(opcode, output_count, &output, 0, nullptr, temp_count, temps); } Instruction* InstructionSelector::Emit(InstructionCode opcode, InstructionOperand output, InstructionOperand a, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; return Emit(opcode, output_count, &output, 1, &a, temp_count, temps); } Instruction* InstructionSelector::Emit(InstructionCode opcode, InstructionOperand output, InstructionOperand a, InstructionOperand b, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; InstructionOperand inputs[] = {a, b}; size_t input_count = arraysize(inputs); return Emit(opcode, output_count, &output, input_count, inputs, temp_count, temps); } Instruction* InstructionSelector::Emit(InstructionCode opcode, InstructionOperand output, InstructionOperand a, InstructionOperand b, InstructionOperand c, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; InstructionOperand inputs[] = {a, b, c}; size_t input_count = arraysize(inputs); return Emit(opcode, output_count, &output, input_count, inputs, temp_count, temps); } Instruction* InstructionSelector::Emit( InstructionCode opcode, InstructionOperand output, InstructionOperand a, InstructionOperand b, InstructionOperand c, InstructionOperand d, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; InstructionOperand inputs[] = {a, b, c, d}; size_t input_count = arraysize(inputs); return Emit(opcode, output_count, &output, input_count, inputs, temp_count, temps); } Instruction* InstructionSelector::Emit( InstructionCode opcode, InstructionOperand output, InstructionOperand a, InstructionOperand b, InstructionOperand c, InstructionOperand d, InstructionOperand e, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; InstructionOperand inputs[] = {a, b, c, d, e}; size_t input_count = arraysize(inputs); return Emit(opcode, output_count, &output, input_count, inputs, temp_count, temps); } Instruction* InstructionSelector::Emit( InstructionCode opcode, InstructionOperand output, InstructionOperand a, InstructionOperand b, InstructionOperand c, InstructionOperand d, InstructionOperand e, InstructionOperand f, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; InstructionOperand inputs[] = {a, b, c, d, e, f}; size_t input_count = arraysize(inputs); return Emit(opcode, output_count, &output, input_count, inputs, temp_count, temps); } Instruction* InstructionSelector::Emit( InstructionCode opcode, InstructionOperand output, InstructionOperand a, InstructionOperand b, InstructionOperand c, InstructionOperand d, InstructionOperand e, InstructionOperand f, InstructionOperand g, InstructionOperand h, size_t temp_count, InstructionOperand* temps) { size_t output_count = output.IsInvalid() ? 0 : 1; InstructionOperand inputs[] = {a, b, c, d, e, f, g, h}; size_t input_count = arraysize(inputs); return Emit(opcode, output_count, &output, input_count, inputs, temp_count, temps); } Instruction* InstructionSelector::Emit( InstructionCode opcode, size_t output_count, InstructionOperand* outputs, size_t input_count, InstructionOperand* inputs, size_t temp_count, InstructionOperand* temps) { if (output_count >= Instruction::kMaxOutputCount || input_count >= Instruction::kMaxInputCount || temp_count >= Instruction::kMaxTempCount) { set_instruction_selection_failed(); return nullptr; } Instruction* instr = Instruction::New(instruction_zone(), opcode, output_count, outputs, input_count, inputs, temp_count, temps); return Emit(instr); } Instruction* InstructionSelector::Emit(Instruction* instr) { instructions_.push_back(instr); return instr; } namespace { bool is_exclusive_user_of(const Graph* graph, OpIndex user, OpIndex value) { DCHECK(user.valid()); DCHECK(value.valid()); const Operation& value_op = graph->Get(value); const Operation& user_op = graph->Get(user); size_t use_count = base::count_if( user_op.inputs(), [value](OpIndex input) { return input == value; }); if (V8_UNLIKELY(use_count == 0)) { // We have a special case here: // // value // | // TruncateWord64ToWord32 // | // user // // If emitting user performs the truncation implicitly, we end up calling // CanCover with value and user such that user might have no (direct) uses // of value. There are cases of other unnecessary operations that can lead // to the same situation (e.g. bitwise and, ...). In this case, we still // cover if value has only a single use and this is one of the direct // inputs of user, which also only has a single use (in user). // TODO(nicohartmann@): We might generalize this further if we see use // cases. if (!value_op.saturated_use_count.IsOne()) return false; for (auto input : user_op.inputs()) { const Operation& input_op = graph->Get(input); const size_t indirect_use_count = base::count_if( input_op.inputs(), [value](OpIndex input) { return input == value; }); if (indirect_use_count > 0) { return input_op.saturated_use_count.IsOne(); } } return false; } if (value_op.Is<ProjectionOp>()) { // Projections always have a Tuple use, but it shouldn't count as a use as // far as is_exclusive_user_of is concerned, since no instructions are // emitted for the MakeTupleOp, which is just a Turboshaft "meta operation". // We thus increase the use_count by 1, to attribute the MakeTupleOp use to // the current operation. use_count++; } DCHECK_LE(use_count, graph->Get(value).saturated_use_count.Get()); return (value_op.saturated_use_count.Get() == use_count) && !value_op.saturated_use_count.IsSaturated(); } } // namespace bool InstructionSelector::CanCover(OpIndex user, OpIndex node) const { // 1. Both {user} and {node} must be in the same basic block. if (block(schedule(), node) != current_block_) { return false; } const Operation& op = Get(node); // 2. If node does not produce anything, it can be covered. if (op.Effects().produces.bits() == 0) { return is_exclusive_user_of(schedule(), user, node); } // 3. Otherwise, the {node}'s effect level must match the {user}'s. if (GetEffectLevel(node) != current_effect_level_) { return false; } // 4. Only {node} must have value edges pointing to {user}. return is_exclusive_user_of(schedule(), user, node); } bool InstructionSelector::CanCoverProtectedLoad(OpIndex user, OpIndex node) const { DCHECK(CanCover(user, node)); const Graph* graph = this->turboshaft_graph(); for (OpIndex next = graph->NextIndex(node); next.valid(); next = graph->NextIndex(next)) { if (next == user) break; const Operation& op = graph->Get(next); OpEffects effects = op.Effects(); if (effects.produces.control_flow || effects.required_when_unused) { return false; } } return true; } bool InstructionSelector::IsOnlyUserOfNodeInSameBlock(OpIndex user, OpIndex node) const { Block* bb_user = this->block(schedule(), user); Block* bb_node = this->block(schedule(), node); if (bb_user != bb_node) return false; const Operation& node_op = this->turboshaft_graph()->Get(node); if (node_op.saturated_use_count.Get() == 1) return true; for (OpIndex use : turboshaft_uses(node)) { if (use == user) continue; if (this->block(schedule(), use) == bb_user) return false; } return true; } OptionalOpIndex InstructionSelector::FindProjection(OpIndex node, size_t projection_index) { const Graph* graph = this->turboshaft_graph(); // Projections are always emitted right after the operation. for (OpIndex next = graph->NextIndex(node); next.valid(); next = graph->NextIndex(next)) { const ProjectionOp* projection = graph->Get(next).TryCast<ProjectionOp>(); if (projection == nullptr) break; DCHECK(!projection->saturated_use_count.IsZero()); if (projection->saturated_use_count.IsOne()) { // If the projection has a single use, it is the following tuple, so we // don't return it, since there is no point in emitting it. DCHECK(turboshaft_uses(next).size() == 1 && graph->Get(turboshaft_uses(next)[0]).Is<MakeTupleOp>()); continue; } if (projection->index == projection_index) return next; } // If there is no Projection with index {projection_index} following the // operation, then there shouldn't be any such Projection in the graph. We // verify this in Debug mode. #ifdef DEBUG for (OpIndex use : turboshaft_uses(node)) { if (const ProjectionOp* projection = this->Get(use).TryCast<ProjectionOp>()) { DCHECK_EQ(projection->input(), node); if (projection->index == projection_index) { // If we found the projection, it should have a single use: a Tuple // (which doesn't count as a regular use since it is just an artifact of // the Turboshaft graph). DCHECK(turboshaft_uses(use).size() == 1 && graph->Get(turboshaft_uses(use)[0]).Is<MakeTupleOp>()); } } } #endif // DEBUG return OpIndex::Invalid(); } void InstructionSelector::UpdateRenames(Instruction* instruction) { for (size_t i = 0; i < instruction->InputCount(); i++) { TryRename(instruction->InputAt(i)); } } void InstructionSelector::UpdateRenamesInPhi(PhiInstruction* phi) { for (size_t i = 0; i < phi->operands().size(); i++) { int vreg = phi->operands()[i]; int renamed = GetRename(vreg); if (vreg != renamed) { phi->RenameInput(i, renamed); } } } int InstructionSelector::GetRename(int virtual_register) { int rename = virtual_register; while (true) { if (static_cast<size_t>(rename) >= virtual_register_rename_.size()) break; int next = virtual_register_rename_[rename]; if (next == InstructionOperand::kInvalidVirtualRegister) { break; } rename = next; } return rename; } void InstructionSelector::TryRename(InstructionOperand* op) { if (!op->IsUnallocated()) return; UnallocatedOperand* unalloc = UnallocatedOperand::cast(op); int vreg = unalloc->virtual_register(); int rename = GetRename(vreg); if (rename != vreg) { *unalloc = UnallocatedOperand(*unalloc, rename); } } void InstructionSelector::SetRename(OpIndex node, OpIndex rename) { int vreg = GetVirtualRegister(node); if (static_cast<size_t>(vreg) >= virtual_register_rename_.size()) { int invalid = InstructionOperand::kInvalidVirtualRegister; virtual_register_rename_.resize(vreg + 1, invalid); } virtual_register_rename_[vreg] = GetVirtualRegister(rename); } int InstructionSelector::GetVirtualRegister(OpIndex node) { DCHECK(node.valid()); size_t const id = node.id(); DCHECK_LT(id, virtual_registers_.size()); int virtual_register = virtual_registers_[id]; if (virtual_register == InstructionOperand::kInvalidVirtualRegister) { virtual_register = sequence()->NextVirtualRegister(); virtual_registers_[id] = virtual_register; } return virtual_register; } const std::map<uint32_t, int> InstructionSelector::GetVirtualRegistersForTesting() const { std::map<uint32_t, int> virtual_registers; for (size_t n = 0; n < virtual_registers_.size(); ++n) { if (virtual_registers_[n] != InstructionOperand::kInvalidVirtualRegister) { const uint32_t id = static_cast<uint32_t>(n); virtual_registers.insert(std::make_pair(id, virtual_registers_[n])); } } return virtual_registers; } bool InstructionSelector::IsDefined(OpIndex node) const { DCHECK(node.valid()); return defined_.Contains(node.id()); } void InstructionSelector::MarkAsDefined(OpIndex node) { DCHECK(node.valid()); defined_.Add(node.id()); } bool InstructionSelector::IsUsed(OpIndex node) const { DCHECK(node.valid()); if (!ShouldSkipOptimizationStep() && ShouldSkipOperation(this->Get(node))) { return false; } if (Get(node).IsRequiredWhenUnused()) return true; return used_.Contains(node.id()); } bool InstructionSelector::IsReallyUsed(OpIndex node) const { DCHECK(node.valid()); if (!ShouldSkipOptimizationStep() && ShouldSkipOperation(this->Get(node))) { return false; } return used_.Contains(node.id()); } void InstructionSelector::MarkAsUsed(OpIndex node) { DCHECK(node.valid()); used_.Add(node.id()); } int InstructionSelector::GetEffectLevel(OpIndex node) const { DCHECK(node.valid()); size_t const id = node.id(); DCHECK_LT(id, effect_level_.size()); return effect_level_[id]; } int InstructionSelector::GetEffectLevel(OpIndex node, FlagsContinuation* cont) const { return cont->IsBranch() ? GetEffectLevel(this->block_terminator( this->PredecessorAt(cont->true_block(), 0))) : GetEffectLevel(node); } void InstructionSelector::SetEffectLevel(OpIndex node, int effect_level) { DCHECK(node.valid()); size_t const id = node.id(); DCHECK_LT(id, effect_level_.size()); effect_level_[id] = effect_level; } bool InstructionSelector::CanAddressRelativeToRootsRegister( const ExternalReference& reference) const { // There are three things to consider here: // 1. CanUseRootsRegister: Is kRootRegister initialized? const bool root_register_is_available_and_initialized = CanUseRootsRegister(); if (!root_register_is_available_and_initialized) return false; // 2. enable_roots_relative_addressing_: Can we address everything on the heap // through the root register, i.e. are root-relative addresses to arbitrary // addresses guaranteed not to change between code generation and // execution? if (enable_roots_relative_addressing_) return true; // 3. IsAddressableThroughRootRegister: Is the target address guaranteed to // have a fixed root-relative offset? If so, we can ignore 2. const bool this_root_relative_offset_is_constant = MacroAssemblerBase::IsAddressableThroughRootRegister(isolate(), reference); return this_root_relative_offset_is_constant; } bool InstructionSelector::CanUseRootsRegister() const { return linkage()->GetIncomingDescriptor()->flags() & CallDescriptor::kCanUseRoots; } void InstructionSelector::MarkAsRepresentation(MachineRepresentation rep, const InstructionOperand& op) { UnallocatedOperand unalloc = UnallocatedOperand::cast(op); sequence()->MarkAsRepresentation(rep, unalloc.virtual_register()); } void InstructionSelector::MarkAsRepresentation(MachineRepresentation rep, OpIndex node) { sequence()->MarkAsRepresentation(rep, GetVirtualRegister(node)); } namespace { InstructionOperand OperandForDeopt(Isolate* isolate, OperandGenerator* g, OpIndex input, FrameStateInputKind kind, MachineRepresentation rep) { if (rep == MachineRepresentation::kNone) { return g->TempImmediate(FrameStateDescriptor::kImpossibleValue); } const Operation& op = g->turboshaft_graph()->Get(input); if (const ConstantOp* constant = op.TryCast<ConstantOp>()) { using Kind = ConstantOp::Kind; switch (constant->kind) { case Kind::kWord32: case Kind::kWord64: case Kind::kSmi: case Kind::kFloat32: case Kind::kFloat64: return g->UseImmediate(input); case Kind::kNumber: if (rep == MachineRepresentation::kWord32) { const double d = constant->number().get_scalar(); Tagged<Smi> smi = Smi::FromInt(static_cast<int32_t>(d)); CHECK_EQ(smi.value(), d); return g->UseImmediate(static_cast<int32_t>(smi.ptr())); } return g->UseImmediate(input); case Kind::kHeapObject: case Kind::kCompressedHeapObject: case Kind::kTrustedHeapObject: { if (!CanBeTaggedOrCompressedPointer(rep)) { // If we have inconsistent static and dynamic types, e.g. if we // smi-check a string, we can get here with a heap object that // says it is a smi. In that case, we return an invalid instruction // operand, which will be interpreted as an optimized-out value. // TODO(jarin) Ideally, we should turn the current instruction // into an abort (we should never execute it). return InstructionOperand(); } Handle<HeapObject> object = constant->handle(); RootIndex root_index; if (isolate->roots_table().IsRootHandle(object, &root_index) && root_index == RootIndex::kOptimizedOut) { // For an optimized-out object we return an invalid instruction // operand, so that we take the fast path for optimized-out values. return InstructionOperand(); } return g->UseImmediate(input); } default: UNIMPLEMENTED(); } } else if (const TaggedBitcastOp* bitcast = op.TryCast<Opmask::kTaggedBitcastSmi>()) { const Operation& bitcast_input = g->Get(bitcast->input()); if (const ConstantOp* cst = bitcast_input.TryCast<Opmask::kWord32Constant>()) { if constexpr (Is64()) { return g->UseImmediate64(cst->word32()); } else { return g->UseImmediate(cst->word32()); } } else if (Is64() && bitcast_input.Is<Opmask::kWord64Constant>()) { if (rep == MachineRepresentation::kWord32) { return g->UseImmediate(bitcast_input.Cast<ConstantOp>().word32()); } else { return g->UseImmediate64(bitcast_input.Cast<ConstantOp>().word64()); } } } switch (kind) { case FrameStateInputKind::kStackSlot: return g->UseUniqueSlot(input); case FrameStateInputKind::kAny: // Currently deopts "wrap" other operations, so the deopt's inputs // are potentially needed until the end of the deoptimising code. return g->UseAnyAtEnd(input); } } } // namespace enum class ObjectType { kRegularObject, kStringConcat }; class TurboshaftStateObjectDeduplicator { public: explicit TurboshaftStateObjectDeduplicator(Zone* zone) : objects_ids_mapping_(zone), string_ids_mapping_(zone) {} static constexpr size_t kNotDuplicated = std::numeric_limits<size_t>::max(); size_t GetObjectId(uint32_t old_id, ObjectType type) { auto& ids_map = GetMapForType(type); auto it = ids_map.find(old_id); if (it == ids_map.end()) return kNotDuplicated; return it->second; } size_t InsertObject(uint32_t old_id, ObjectType type) { auto& ids_map = GetMapForType(type); uint32_t new_id = next_id_++; ids_map.insert({old_id, new_id}); return new_id; } void InsertDummyForArgumentsElements() { next_id_++; } private: ZoneAbslFlatHashMap<uint32_t, uint32_t>& GetMapForType(ObjectType type) { switch (type) { case ObjectType::kRegularObject: return objects_ids_mapping_; case ObjectType::kStringConcat: return string_ids_mapping_; } } uint32_t next_id_ = 0; ZoneAbslFlatHashMap<uint32_t, uint32_t> objects_ids_mapping_; ZoneAbslFlatHashMap<uint32_t, uint32_t> string_ids_mapping_; }; struct InstructionSelector::CachedStateValues : public ZoneObject { public: CachedStateValues(Zone* zone, StateValueList* values, size_t values_start, InstructionOperandVector* inputs, size_t inputs_start) : inputs_(inputs->begin() + inputs_start, inputs->end(), zone), values_(values->MakeSlice(values_start)) {} size_t Emit(InstructionOperandVector* inputs, StateValueList* values) { inputs->insert(inputs->end(), inputs_.begin(), inputs_.end()); values->PushCachedSlice(values_); return inputs_.size(); } private: InstructionOperandVector inputs_; StateValueList::Slice values_; }; size_t AddOperandToStateValueDescriptor( InstructionSelector* selector, StateValueList* values, InstructionOperandVector* inputs, OperandGenerator* g, TurboshaftStateObjectDeduplicator* deduplicator, FrameStateData::Iterator* it, FrameStateInputKind kind, Zone* zone) { switch (it->current_instr()) { case FrameStateData::Instr::kUnusedRegister: it->ConsumeUnusedRegister(); values->PushOptimizedOut(); return 0; case FrameStateData::Instr::kInput: { MachineType type; OpIndex input; it->ConsumeInput(&type, &input); const Operation& op = selector->Get(input); if (op.outputs_rep()[0] == RegisterRepresentation::Word64() && type.representation() == MachineRepresentation::kWord32) { // 64 to 32-bit conversion is implicit in turboshaft. // TODO(nicohartmann@): Fix this once we have explicit truncations. UNIMPLEMENTED(); } InstructionOperand instr_op = OperandForDeopt( selector->isolate(), g, input, kind, type.representation()); if (instr_op.kind() == InstructionOperand::INVALID) { // Invalid operand means the value is impossible or optimized-out. values->PushOptimizedOut(); return 0; } else { inputs->push_back(instr_op); values->PushPlain(type); return 1; } } case FrameStateData::Instr::kDematerializedObject: { uint32_t obj_id; uint32_t field_count; it->ConsumeDematerializedObject(&obj_id, &field_count); size_t id = deduplicator->GetObjectId(obj_id, ObjectType::kRegularObject); if (id == TurboshaftStateObjectDeduplicator::kNotDuplicated) { id = deduplicator->InsertObject(obj_id, ObjectType::kRegularObject); size_t entries = 0; StateValueList* nested = values->PushRecursiveField(zone, id); for (uint32_t i = 0; i < field_count; ++i) { entries += AddOperandToStateValueDescriptor( selector, nested, inputs, g, deduplicator, it, kind, zone); } return entries; } else { // Deoptimizer counts duplicate objects for the running id, so we have // to push the input again. deduplicator->InsertObject(obj_id, ObjectType::kRegularObject); values->PushDuplicate(id); return 0; } } case FrameStateData::Instr::kDematerializedObjectReference: { uint32_t obj_id; it->ConsumeDematerializedObjectReference(&obj_id); size_t id = deduplicator->GetObjectId(obj_id, ObjectType::kRegularObject); DCHECK_NE(id, TurboshaftStateObjectDeduplicator::kNotDuplicated); // Deoptimizer counts duplicate objects for the running id, so we have // to push the input again. deduplicator->InsertObject(obj_id, ObjectType::kRegularObject); values->PushDuplicate(id); return 0; } case FrameStateData::Instr::kDematerializedStringConcat: { DCHECK(v8_flags.turboshaft_string_concat_escape_analysis); uint32_t obj_id; it->ConsumeDematerializedStringConcat(&obj_id); size_t id = deduplicator->GetObjectId(obj_id, ObjectType::kStringConcat); if (id == TurboshaftStateObjectDeduplicator::kNotDuplicated) { id = deduplicator->InsertObject(obj_id, ObjectType::kStringConcat); StateValueList* nested = values->PushStringConcat(zone, id); static constexpr int kLeft = 1, kRight = 1; static constexpr int kInputCount = kLeft + kRight; size_t entries = 0; for (uint32_t i = 0; i < kInputCount; i++) { entries += AddOperandToStateValueDescriptor( selector, nested, inputs, g, deduplicator, it, kind, zone); } return entries; } else { // Deoptimizer counts duplicate objects for the running id, so we have // to push the input again. deduplicator->InsertObject(obj_id, ObjectType::kStringConcat); values->PushDuplicate(id); return 0; } } case FrameStateData::Instr::kDematerializedStringConcatReference: { DCHECK(v8_flags.turboshaft_string_concat_escape_analysis); uint32_t obj_id; it->ConsumeDematerializedStringConcatReference(&obj_id); size_t id = deduplicator->GetObjectId(obj_id, ObjectType::kStringConcat); DCHECK_NE(id, TurboshaftStateObjectDeduplicator::kNotDuplicated); // Deoptimizer counts duplicate objects for the running id, so we have // to push the input again. deduplicator->InsertObject(obj_id, ObjectType::kStringConcat); values->PushDuplicate(id); return 0; } case FrameStateData::Instr::kArgumentsElements: { CreateArgumentsType type; it->ConsumeArgumentsElements(&type); values->PushArgumentsElements(type); // The elements backing store of an arguments object participates in the // duplicate object counting, but can itself never appear duplicated. deduplicator->InsertDummyForArgumentsElements(); return 0; } case FrameStateData::Instr::kArgumentsLength: it->ConsumeArgumentsLength(); values->PushArgumentsLength(); return 0; case FrameStateData::Instr::kRestLength: it->ConsumeRestLength(); values->PushRestLength(); return 0; } UNREACHABLE(); } // Returns the number of instruction operands added to inputs. size_t InstructionSelector::AddInputsToFrameStateDescriptor( FrameStateDescriptor* descriptor, OpIndex state_node, OperandGenerator* g, TurboshaftStateObjectDeduplicator* deduplicator, InstructionOperandVector* inputs, FrameStateInputKind kind, Zone* zone) { FrameStateOp& state = schedule()->Get(state_node).template Cast<FrameStateOp>(); const FrameStateInfo& info = state.data->frame_state_info; USE(info); FrameStateData::Iterator it = state.data->iterator(state.state_values()); size_t entries = 0; size_t initial_size = inputs->size(); USE(initial_size); // initial_size is only used for debug. if (descriptor->outer_state()) { entries += AddInputsToFrameStateDescriptor( descriptor->outer_state(), state.parent_frame_state(), g, deduplicator, inputs, kind, zone); } DCHECK_EQ(descriptor->parameters_count(), info.parameter_count()); DCHECK_EQ(descriptor->locals_count(), info.local_count()); DCHECK_EQ(descriptor->stack_count(), info.stack_count()); StateValueList* values_descriptor = descriptor->GetStateValueDescriptors(); DCHECK_EQ(values_descriptor->size(), 0u); values_descriptor->ReserveSize(descriptor->GetSize()); // Function if (descriptor->HasClosure()) { entries += v8::internal::compiler::AddOperandToStateValueDescriptor( this, values_descriptor, inputs, g, deduplicator, &it, FrameStateInputKind::kStackSlot, zone); } else { // Advance the iterator either way. MachineType unused_type; OpIndex unused_input; it.ConsumeInput(&unused_type, &unused_input); } // Parameters for (size_t i = 0; i < descriptor->parameters_count(); ++i) { entries += v8::internal::compiler::AddOperandToStateValueDescriptor( this, values_descriptor, inputs, g, deduplicator, &it, kind, zone); } // Context if (descriptor->HasContext()) { entries += v8::internal::compiler::AddOperandToStateValueDescriptor( this, values_descriptor, inputs, g, deduplicator, &it, FrameStateInputKind::kStackSlot, zone); } else { // Advance the iterator either way. MachineType unused_type; OpIndex unused_input; it.ConsumeInput(&unused_type, &unused_input); } // Locals for (size_t i = 0; i < descriptor->locals_count(); ++i) { entries += v8::internal::compiler::AddOperandToStateValueDescriptor( this, values_descriptor, inputs, g, deduplicator, &it, kind, zone); } // Stack for (size_t i = 0; i < descriptor->stack_count(); ++i) { entries += v8::internal::compiler::AddOperandToStateValueDescriptor( this, values_descriptor, inputs, g, deduplicator, &it, kind, zone); } DCHECK_EQ(initial_size + entries, inputs->size()); return entries; } Instruction* InstructionSelector::EmitWithContinuation( InstructionCode opcode, InstructionOperand a, FlagsContinuation* cont) { return EmitWithContinuation(opcode, 0, nullptr, 1, &a, cont); } Instruction* InstructionSelector::EmitWithContinuation( InstructionCode opcode, InstructionOperand a, InstructionOperand b, FlagsContinuation* cont) { InstructionOperand inputs[] = {a, b}; return EmitWithContinuation(opcode, 0, nullptr, arraysize(inputs), inputs, cont); } Instruction* InstructionSelector::EmitWithContinuation( InstructionCode opcode, InstructionOperand a, InstructionOperand b, InstructionOperand c, FlagsContinuation* cont) { InstructionOperand inputs[] = {a, b, c}; return EmitWithContinuation(opcode, 0, nullptr, arraysize(inputs), inputs, cont); } Instruction* InstructionSelector::EmitWithContinuation( InstructionCode opcode, size_t output_count, InstructionOperand* outputs, size_t input_count, InstructionOperand* inputs, FlagsContinuation* cont) { return EmitWithContinuation(opcode, output_count, outputs, input_count, inputs, 0, nullptr, cont); } Instruction* InstructionSelector::EmitWithContinuation( InstructionCode opcode, size_t output_count, InstructionOperand* outputs, size_t input_count, InstructionOperand* inputs, size_t temp_count, InstructionOperand* temps, FlagsContinuation* cont) { OperandGenerator g(this); opcode = cont->Encode(opcode); continuation_inputs_.resize(0); for (size_t i = 0; i < input_count; i++) { continuation_inputs_.push_back(inputs[i]); } continuation_outputs_.resize(0); for (size_t i = 0; i < output_count; i++) { continuation_outputs_.push_back(outputs[i]); } continuation_temps_.resize(0); for (size_t i = 0; i < temp_count; i++) { continuation_temps_.push_back(temps[i]); } if (cont->IsBranch() || cont->IsConditionalBranch()) { continuation_inputs_.push_back(g.Label(cont->true_block())); continuation_inputs_.push_back(g.Label(cont->false_block())); } else if (cont->IsDeoptimize()) { int immediate_args_count = 0; opcode |= DeoptImmedArgsCountField::encode(immediate_args_count) | DeoptFrameStateOffsetField::encode(static_cast<int>(input_count)); AppendDeoptimizeArguments(&continuation_inputs_, cont->reason(), cont->node_id(), cont->feedback(), cont->frame_state()); } else if (cont->IsSet()) { continuation_outputs_.push_back(g.DefineAsRegister(cont->result())); } else if (cont->IsSelect()) { // The {Select} should put one of two values into the output register, // depending on the result of the condition. The two result values are in // the last two input slots, the {false_value} in {input_count - 2}, and the // true_value in {input_count - 1}. The other inputs are used for the // condition. AddOutputToSelectContinuation(&g, static_cast<int>(input_count) - 2, cont->result()); } else if (cont->IsTrap() || cont->IsConditionalTrap()) { int trap_id = static_cast<int>(cont->trap_id()); continuation_inputs_.push_back(g.UseImmediate(trap_id)); } else { DCHECK(cont->IsNone()); } size_t const emit_inputs_size = continuation_inputs_.size(); auto* emit_inputs = emit_inputs_size ? &continuation_inputs_.front() : nullptr; size_t const emit_outputs_size = continuation_outputs_.size(); auto* emit_outputs = emit_outputs_size ? &continuation_outputs_.front() : nullptr; size_t const emit_temps_size = continuation_temps_.size(); auto* emit_temps = emit_temps_size ? &continuation_temps_.front() : nullptr; return Emit(opcode, emit_outputs_size, emit_outputs, emit_inputs_size, emit_inputs, emit_temps_size, emit_temps); } bool InstructionSelector::IsProtectedLoad(turboshaft::OpIndex node) const { #if V8_ENABLE_WEBASSEMBLY if (Get(node).opcode == turboshaft::Opcode::kSimd128LoadTransform) { return true; } #if V8_ENABLE_WASM_SIMD256_REVEC if (Get(node).opcode == turboshaft::Opcode::kSimd256LoadTransform) { return true; } #endif // V8_ENABLE_WASM_SIMD256_REVEC #endif // V8_ENABLE_WEBASSEMBLY if (!IsLoadOrLoadImmutable(node)) return false; bool traps_on_null; return LoadView(schedule_, node).is_protected(&traps_on_null); } void InstructionSelector::AppendDeoptimizeArguments( InstructionOperandVector* args, DeoptimizeReason reason, uint32_t node_id, FeedbackSource const& feedback, OpIndex frame_state, DeoptimizeKind kind) { OperandGenerator g(this); FrameStateDescriptor* const descriptor = GetFrameStateDescriptor(frame_state); int const state_id = sequence()->AddDeoptimizationEntry( descriptor, kind, reason, node_id, feedback); args->push_back(g.TempImmediate(state_id)); StateObjectDeduplicator deduplicator(instruction_zone()); AddInputsToFrameStateDescriptor(descriptor, frame_state, &g, &deduplicator, args, FrameStateInputKind::kAny, instruction_zone()); } // An internal helper class for generating the operands to calls. // TODO(bmeurer): Get rid of the CallBuffer business and make // InstructionSelector::VisitCall platform independent instead. struct CallBuffer { CallBuffer(Zone* zone, const CallDescriptor* call_descriptor, FrameStateDescriptor* frame_state) : descriptor(call_descriptor), frame_state_descriptor(frame_state), output_nodes(zone), outputs(zone), instruction_args(zone), pushed_nodes(zone) { output_nodes.reserve(call_descriptor->ReturnCount()); outputs.reserve(call_descriptor->ReturnCount()); pushed_nodes.reserve(input_count()); instruction_args.reserve(input_count() + frame_state_value_count()); } const CallDescriptor* descriptor; FrameStateDescriptor* frame_state_descriptor; ZoneVector<PushParameter> output_nodes; InstructionOperandVector outputs; InstructionOperandVector instruction_args; ZoneVector<PushParameter> pushed_nodes; size_t input_count() const { return descriptor->InputCount(); } size_t frame_state_count() const { return descriptor->FrameStateCount(); } size_t frame_state_value_count() const { return (frame_state_descriptor == nullptr) ? 0 : (frame_state_descriptor->GetTotalSize() + 1); // Include deopt id. } }; // TODO(bmeurer): Get rid of the CallBuffer business and make // InstructionSelector::VisitCall platform independent instead. void InstructionSelector::InitializeCallBuffer( OpIndex node, CallBuffer* buffer, CallBufferFlags flags, OpIndex callee, OptionalOpIndex frame_state_opt, base::Vector<const OpIndex> arguments, int return_count, int stack_param_delta) { OperandGenerator g(this); size_t ret_count = buffer->descriptor->ReturnCount(); bool is_tail_call = (flags & kCallTail) != 0; DCHECK_LE(return_count, ret_count); if (ret_count > 0) { // Collect the projections that represent multiple outputs from this call. if (ret_count == 1) { PushParameter result = {node, buffer->descriptor->GetReturnLocation(0)}; buffer->output_nodes.push_back(result); } else { buffer->output_nodes.resize(ret_count); for (size_t i = 0; i < ret_count; ++i) { LinkageLocation location = buffer->descriptor->GetReturnLocation(i); buffer->output_nodes[i] = PushParameter({}, location); } for (OpIndex call_use : turboshaft_uses(node)) { const Operation& use_op = this->Get(call_use); if (use_op.Is<DidntThrowOp>()) { for (OpIndex use : turboshaft_uses(call_use)) { const ProjectionOp& projection = Cast<ProjectionOp>(use); size_t index = projection.index; DCHECK_LT(index, buffer->output_nodes.size()); DCHECK(!buffer->output_nodes[index].node.valid()); buffer->output_nodes[index].node = use; } } else { DCHECK(use_op.Is<CheckExceptionOp>()); } } frame_->EnsureReturnSlots( static_cast<int>(buffer->descriptor->ReturnSlotCount())); } // Filter out the outputs that aren't live because no projection uses them. size_t outputs_needed_by_framestate = buffer->frame_state_descriptor == nullptr ? 0 : buffer->frame_state_descriptor->state_combine() .ConsumedOutputCount(); for (size_t i = 0; i < buffer->output_nodes.size(); i++) { bool output_is_live = buffer->output_nodes[i].node.valid() || i < outputs_needed_by_framestate; if (output_is_live) { LinkageLocation location = buffer->output_nodes[i].location; MachineRepresentation rep = location.GetType().representation(); OpIndex output = buffer->output_nodes[i].node; InstructionOperand op = !output.valid() ? g.TempLocation(location) : g.DefineAsLocation(output, location); MarkAsRepresentation(rep, op); if (!UnallocatedOperand::cast(op).HasFixedSlotPolicy()) { buffer->outputs.push_back(op); buffer->output_nodes[i].node = {}; } } } } // The first argument is always the callee code. bool call_code_immediate = (flags & kCallCodeImmediate) != 0; bool call_address_immediate = (flags & kCallAddressImmediate) != 0; bool call_use_fixed_target_reg = (flags & kCallFixedTargetRegister) != 0; DeoptimizeKind deopt_kind = DeoptimizeKind::kLazy; switch (buffer->descriptor->kind()) { case CallDescriptor::kCallCodeObject: buffer->instruction_args.push_back( (call_code_immediate && this->IsHeapConstant(callee)) ? g.UseImmediate(callee) : call_use_fixed_target_reg ? g.UseFixed(callee, kJavaScriptCallCodeStartRegister) : g.UseRegister(callee)); break; case CallDescriptor::kCallAddress: // TODO(ahaas): Rename kLazyAfterFastCall and similarly called fields on // the isolate to reflect that they are used for every direct call to C++ // and not just for fast API calls. deopt_kind = DeoptimizeKind::kLazyAfterFastCall; buffer->instruction_args.push_back( (call_address_immediate && this->IsExternalConstant(callee)) ? g.UseImmediate(callee) : call_use_fixed_target_reg ? g.UseFixed(callee, kJavaScriptCallCodeStartRegister) : g.UseRegister(callee)); break; #if V8_ENABLE_WEBASSEMBLY case CallDescriptor::kCallWasmCapiFunction: case CallDescriptor::kCallWasmFunction: case CallDescriptor::kCallWasmFunctionIndirect: case CallDescriptor::kCallWasmImportWrapper: buffer->instruction_args.push_back( (call_address_immediate && this->IsRelocatableWasmConstant(callee)) ? g.UseImmediate(callee) : call_use_fixed_target_reg ? g.UseFixed(callee, kJavaScriptCallCodeStartRegister) : g.UseRegister(callee)); break; case CallDescriptor::kResumeWasmContinuation: DCHECK(!call_use_fixed_target_reg); buffer->instruction_args.push_back(g.UseRegister(callee)); break; #endif // V8_ENABLE_WEBASSEMBLY case CallDescriptor::kCallBuiltinPointer: { // The common case for builtin pointers is to have the target in a // register. If we have a constant, we use a register anyway to simplify // related code. LinkageLocation location = buffer->descriptor->GetInputLocation(0); bool location_is_fixed_register = location.IsRegister() && !location.IsAnyRegister(); InstructionOperand op; // If earlier phases specified a particular register, don't override // their choice. if (location_is_fixed_register) { op = g.UseLocation(callee, location); } else if (call_use_fixed_target_reg) { op = g.UseFixed(callee, kJavaScriptCallCodeStartRegister); } else { op = g.UseRegister(callee); } buffer->instruction_args.push_back(op); break; } case CallDescriptor::kCallJSFunction: // TODO(olivf): Implement the required kArchCallJSFunction with // immediate argument on all architectures. #if defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_ARM) || \ defined(V8_TARGET_ARCH_ARM64) || defined(V8_TARGET_ARCH_PPC64) || \ defined(V8_TARGET_ARCH_S390X) || defined(V8_TARGET_ARCH_LOONG64) if (this->IsHeapConstant(callee)) { buffer->instruction_args.push_back(g.UseImmediate(callee)); break; } #endif buffer->instruction_args.push_back( g.UseLocation(callee, buffer->descriptor->GetInputLocation(0))); break; } DCHECK_EQ(1u, buffer->instruction_args.size()); // If the call needs a frame state, we insert the state information as // follows (n is the number of value inputs to the frame state): // arg 1 : deoptimization id. // arg 2 - arg (n + 2) : value inputs to the frame state. size_t frame_state_entries = 0; USE(frame_state_entries); // frame_state_entries is only used for debug. if (buffer->frame_state_descriptor != nullptr) { OpIndex frame_state = frame_state_opt.value(); // If it was a syntactic tail call we need to drop the current frame and // all the frames on top of it that are either inlined extra arguments // or a tail caller frame. if (is_tail_call) { frame_state = Cast<FrameStateOp>(frame_state).parent_frame_state(); buffer->frame_state_descriptor = buffer->frame_state_descriptor->outer_state(); while (buffer->frame_state_descriptor != nullptr && buffer->frame_state_descriptor->type() == FrameStateType::kInlinedExtraArguments) { frame_state = Cast<FrameStateOp>(frame_state).parent_frame_state(); buffer->frame_state_descriptor = buffer->frame_state_descriptor->outer_state(); } } int const state_id = sequence()->AddDeoptimizationEntry( buffer->frame_state_descriptor, deopt_kind, DeoptimizeReason::kUnknown, node.id(), FeedbackSource()); buffer->instruction_args.push_back(g.TempImmediate(state_id)); StateObjectDeduplicator deduplicator(instruction_zone()); frame_state_entries = 1 + AddInputsToFrameStateDescriptor( buffer->frame_state_descriptor, frame_state, &g, &deduplicator, &buffer->instruction_args, FrameStateInputKind::kStackSlot, instruction_zone()); DCHECK_EQ(1 + frame_state_entries, buffer->instruction_args.size()); } size_t input_count = buffer->input_count(); // Split the arguments into pushed_nodes and instruction_args. Pushed // arguments require an explicit push instruction before the call and do // not appear as arguments to the call. Everything else ends up // as an InstructionOperand argument to the call. auto iter(arguments.begin()); size_t pushed_count = 0; for (size_t index = 1; index < input_count; ++iter, ++index) { DCHECK_NE(iter, arguments.end()); LinkageLocation location = buffer->descriptor->GetInputLocation(index); if (is_tail_call) { location = LinkageLocation::ConvertToTailCallerLocation( location, stack_param_delta); } InstructionOperand op = g.UseLocation(*iter, location); UnallocatedOperand unallocated = UnallocatedOperand::cast(op); if (unallocated.HasFixedSlotPolicy() && !is_tail_call) { int stack_index = -unallocated.fixed_slot_index() - 1; // This can insert empty slots before stack_index and will insert enough // slots after stack_index to store the parameter. if (static_cast<size_t>(stack_index) >= buffer->pushed_nodes.size()) { int num_slots = location.GetSizeInPointers(); buffer->pushed_nodes.resize(stack_index + num_slots); } PushParameter param = {*iter, location}; buffer->pushed_nodes[stack_index] = param; pushed_count++; } else { if (location.IsNullRegister()) { EmitMoveFPRToParam(&op, location); } buffer->instruction_args.push_back(op); } } DCHECK_EQ(input_count, buffer->instruction_args.size() + pushed_count - frame_state_entries); USE(pushed_count); if (V8_TARGET_ARCH_STORES_RETURN_ADDRESS_ON_STACK && is_tail_call && stack_param_delta != 0) { // For tail calls that change the size of their parameter list and keep // their return address on the stack, move the return address to just above // the parameters. LinkageLocation saved_return_location = LinkageLocation::ForSavedCallerReturnAddress(); InstructionOperand return_address = g.UsePointerLocation(LinkageLocation::ConvertToTailCallerLocation( saved_return_location, stack_param_delta), saved_return_location); buffer->instruction_args.push_back(return_address); } } void InstructionSelector::UpdateSourcePosition(Instruction* instruction, OpIndex node) { sequence()->SetSourcePosition(instruction, (*source_positions_)[node]); } bool InstructionSelector::IsSourcePositionUsed(OpIndex node) { if (source_position_mode_ == InstructionSelector::kAllSourcePositions) { return true; } const Operation& operation = this->Get(node); // DidntThrow is where the actual call is generated. if (operation.Is<DidntThrowOp>()) return true; if (const LoadOp* load = operation.TryCast<LoadOp>()) { return load->kind.with_trap_handler; } if (const StoreOp* store = operation.TryCast<StoreOp>()) { return store->kind.with_trap_handler; } #if V8_ENABLE_WEBASSEMBLY if (operation.Is<TrapIfOp>()) return true; if (const AtomicRMWOp* rmw = operation.TryCast<AtomicRMWOp>()) { return rmw->memory_access_kind == MemoryAccessKind::kProtectedByTrapHandler; } if (const Simd128LoadTransformOp* lt = operation.TryCast<Simd128LoadTransformOp>()) { return lt->load_kind.with_trap_handler; } #if V8_ENABLE_WASM_SIMD256_REVEC if (const Simd256LoadTransformOp* lt = operation.TryCast<Simd256LoadTransformOp>()) { return lt->load_kind.with_trap_handler; } #endif // V8_ENABLE_WASM_SIMD256_REVEC if (const Simd128LaneMemoryOp* lm = operation.TryCast<Simd128LaneMemoryOp>()) { return lm->kind.with_trap_handler; } if (const Simd128LoadPairDeinterleaveOp* dl = operation.TryCast<Simd128LoadPairDeinterleaveOp>()) { return dl->load_kind.with_trap_handler; } #endif if (additional_protected_instructions_->Contains(node.id())) { return true; } return false; } bool InstructionSelector::IsCommutative(turboshaft::OpIndex node) const { const turboshaft::Operation& op = Get(node); if (const auto word_binop = op.TryCast<turboshaft::WordBinopOp>()) { return turboshaft::WordBinopOp::IsCommutative(word_binop->kind); } else if (const auto overflow_binop = op.TryCast<turboshaft::OverflowCheckedBinopOp>()) { return turboshaft::OverflowCheckedBinopOp::IsCommutative( overflow_binop->kind); } else if (const auto float_binop = op.TryCast<turboshaft::FloatBinopOp>()) { return !ensure_deterministic_nan_ && turboshaft::FloatBinopOp::IsCommutative(float_binop->kind); } else if (const auto comparison = op.TryCast<turboshaft::ComparisonOp>()) { return turboshaft::ComparisonOp::IsCommutative(comparison->kind); } return false; } namespace { bool increment_effect_level_for_node(InstructionSelector* selector, OpIndex node) { // We need to increment the effect level if the operation consumes any of the // dimensions of the {kTurboshaftEffectLevelMask}. const Operation& op = selector->Get(node); if (op.Is<RetainOp>()) { // Retain has CanWrite effect so that it's not reordered before the last // read it protects, but it shouldn't increment the effect level, since // doing a Load(x) after a Retain(x) is safe as long as there is not call // (or something that can trigger GC) in between Retain(x) and Load(x), and // if there were, then this call would increment the effect level, which // would prevent covering in the ISEL. return false; } return (op.Effects().consumes.bits() & kTurboshaftEffectLevelMask.bits()) != 0; } } // namespace void InstructionSelector::VisitBlock(const Block* block) { DCHECK(!current_block_); current_block_ = block; auto current_num_instructions = [&] { DCHECK_GE(kMaxInt, instructions_.size()); return static_cast<int>(instructions_.size()); }; int current_block_end = current_num_instructions(); int effect_level = 0; for (OpIndex node : this->nodes(block)) { SetEffectLevel(node, effect_level); if (increment_effect_level_for_node(this, node)) { ++effect_level; } } // We visit the control first, then the nodes in the block, so the block's // control input should be on the same effect level as the last node. if (OpIndex terminator = this->block_terminator(block); terminator.valid()) { SetEffectLevel(terminator, effect_level); current_effect_level_ = effect_level; } auto FinishEmittedInstructions = [&](OpIndex node, int instruction_start) { if (instruction_selection_failed()) return false; if (current_num_instructions() == instruction_start) return true; std::reverse(instructions_.begin() + instruction_start, instructions_.end()); if (!node.valid()) return true; if (!source_positions_) return true; SourcePosition source_position; #if V8_ENABLE_WEBASSEMBLY && V8_TARGET_ARCH_X64 if (const Simd128UnaryOp* op = TryCast<Opmask::kSimd128F64x2PromoteLowF32x4>(node); V8_UNLIKELY(op)) { // On x64 there exists an optimization that folds // `kF64x2PromoteLowF32x4` and `kS128Load64Zero` together into a single // instruction. If the instruction causes an out-of-bounds memory // access exception, then the stack trace has to show the source // position of the `kS128Load64Zero` and not of the // `kF64x2PromoteLowF32x4`. if (CanOptimizeF64x2PromoteLowF32x4(node)) { node = op->input(); } } #endif // V8_ENABLE_WEBASSEMBLY && V8_TARGET_ARCH_X64 source_position = (*source_positions_)[node]; if (source_position.IsKnown() && IsSourcePositionUsed(node)) { sequence()->SetSourcePosition(instructions_.back(), source_position); } return true; }; // Generate code for the block control "top down", but schedule the code // "bottom up". VisitControl(block); if (!FinishEmittedInstructions(this->block_terminator(block), current_block_end)) { return; } // Visit code in reverse control flow order, because architecture-specific // matching may cover more than one node at a time. for (OpIndex node : base::Reversed(this->nodes(block))) { int current_node_end = current_num_instructions(); if (protected_loads_to_remove_->Contains(node.id()) && !IsReallyUsed(node)) { MarkAsDefined(node); } if (!IsUsed(node)) { // Skip nodes that are unused, while marking them as Defined so that it's // clear that these unused nodes have been visited and will not be Defined // later. MarkAsDefined(node); } else if (!IsDefined(node)) { // Generate code for this node "top down", but schedule the code "bottom // up". current_effect_level_ = GetEffectLevel(node); VisitNode(node); // Nodes that don't produce a value won't be marked as Defined by // VisitNode (simply because nodes are marked as defined when instruction // selectors do something like `DefinedAsRegister(node)`, which isn't done // for non-value nodes like Store). So, we mark every visitted nodes as // Defined here, so that it's clear when processing the next nodes (which // are earlier in the graph), that this use has already been processed // (which is useful for instance when trying to cover inputs, like in // `TryPrepareScheduleFirstProjection`). MarkAsDefined(node); if (!FinishEmittedInstructions(node, current_node_end)) return; } if (trace_turbo_) { instr_origins_[node.id()] = {current_num_instructions(), current_node_end}; } } // We're done with the block. InstructionBlock* instruction_block = sequence()->InstructionBlockAt(this->rpo_number(block)); if (current_num_instructions() == current_block_end) { // Avoid empty block: insert a {kArchNop} instruction. Emit(Instruction::New(sequence()->zone(), kArchNop)); } instruction_block->set_code_start(current_num_instructions()); instruction_block->set_code_end(current_block_end); current_block_ = nullptr; } FlagsCondition InstructionSelector::GetComparisonFlagCondition( const ComparisonOp& op) const { switch (op.kind) { case ComparisonOp::Kind::kEqual: return kEqual; case ComparisonOp::Kind::kSignedLessThan: return kSignedLessThan; case ComparisonOp::Kind::kSignedLessThanOrEqual: return kSignedLessThanOrEqual; case ComparisonOp::Kind::kUnsignedLessThan: return kUnsignedLessThan; case ComparisonOp::Kind::kUnsignedLessThanOrEqual: return kUnsignedLessThanOrEqual; } } void InstructionSelector::MarkPairProjectionsAsWord32(OpIndex node) { OptionalOpIndex projection0 = FindProjection(node, 0); if (projection0.valid()) { MarkAsWord32(projection0.value()); } OptionalOpIndex projection1 = FindProjection(node, 1); if (projection1.valid()) { MarkAsWord32(projection1.value()); } } void InstructionSelector::ConsumeEqualZero(OpIndex* user, OpIndex* value, FlagsContinuation* cont) { // Try to combine with comparisons against 0 by simply inverting the branch. while (const ComparisonOp* equal = TryCast<Opmask::kComparisonEqual>(*value)) { if (equal->rep == RegisterRepresentation::Word32()) { if (!MatchIntegralZero(equal->right())) return; #ifdef V8_COMPRESS_POINTERS } else if (equal->rep == RegisterRepresentation::Tagged()) { static_assert(RegisterRepresentation::Tagged().MapTaggedToWord() == RegisterRepresentation::Word32()); if (!MatchSmiZero(equal->right())) return; #endif // V8_COMPRESS_POINTERS } else { return; } if (!CanCover(*user, *value)) return; *user = *value; *value = equal->left(); cont->Negate(); } } #if V8_ENABLE_WEBASSEMBLY void InstructionSelector::VisitI8x16RelaxedSwizzle(OpIndex node) { return VisitI8x16Swizzle(node); } #endif // V8_ENABLE_WEBASSEMBLY void InstructionSelector::VisitStackPointerGreaterThan(OpIndex node) { FlagsContinuation cont = FlagsContinuation::ForSet(kStackPointerGreaterThanCondition, node); VisitStackPointerGreaterThan(node, &cont); } void InstructionSelector::VisitLoadStackCheckOffset(OpIndex node) { OperandGenerator g(this); Emit(kArchStackCheckOffset, g.DefineAsRegister(node)); } void InstructionSelector::VisitLoadFramePointer(OpIndex node) { OperandGenerator g(this); Emit(kArchFramePointer, g.DefineAsRegister(node)); } #if V8_ENABLE_WEBASSEMBLY void InstructionSelector::VisitLoadStackPointer(OpIndex node) { OperandGenerator g(this); Emit(kArchStackPointer, g.DefineAsRegister(node)); } void InstructionSelector::VisitWasmFXArgBuffer(OpIndex node) { OperandGenerator g(this); LinkageLocation arg_buffer = LinkageLocation::ForRegister( WasmFXSuspendDescriptor::GetRegisterParameter(2).code()); Emit(kArchNop, g.DefineAsLocation(node, arg_buffer)); } #endif // V8_ENABLE_WEBASSEMBLY void InstructionSelector::VisitLoadParentFramePointer(OpIndex node) { OperandGenerator g(this); Emit(kArchParentFramePointer, g.DefineAsRegister(node)); } void InstructionSelector::VisitLoadRootRegister(OpIndex node) { OperandGenerator g(this); Emit(kArchRootPointer, g.DefineAsRegister(node)); } void InstructionSelector::VisitFloat64Acos(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Acos); } void InstructionSelector::VisitFloat64Acosh(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Acosh); } void InstructionSelector::VisitFloat64Asin(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Asin); } void InstructionSelector::VisitFloat64Asinh(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Asinh); } void InstructionSelector::VisitFloat64Atan(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Atan); } void InstructionSelector::VisitFloat64Atanh(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Atanh); } void InstructionSelector::VisitFloat64Atan2(OpIndex node) { VisitFloat64Ieee754Binop(node, kIeee754Float64Atan2); } void InstructionSelector::VisitFloat64Cbrt(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Cbrt); } void InstructionSelector::VisitFloat64Cos(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Cos); } void InstructionSelector::VisitFloat64Cosh(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Cosh); } void InstructionSelector::VisitFloat64Exp(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Exp); } void InstructionSelector::VisitFloat64Expm1(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Expm1); } void InstructionSelector::VisitFloat64Log(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Log); } void InstructionSelector::VisitFloat64Log1p(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Log1p); } void InstructionSelector::VisitFloat64Log2(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Log2); } void InstructionSelector::VisitFloat64Log10(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Log10); } void InstructionSelector::VisitFloat64Pow(OpIndex node) { VisitFloat64Ieee754Binop(node, kIeee754Float64Pow); } void InstructionSelector::VisitFloat64Sin(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Sin); } void InstructionSelector::VisitFloat64Sinh(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Sinh); } void InstructionSelector::VisitFloat64Tan(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Tan); } void InstructionSelector::VisitFloat64Tanh(OpIndex node) { VisitFloat64Ieee754Unop(node, kIeee754Float64Tanh); } void InstructionSelector::MarkAsTableSwitchTarget( const turboshaft::Block* block) { sequence() ->InstructionBlockAt(this->rpo_number(block)) ->set_table_switch_target(true); } void InstructionSelector::EmitTableSwitch( const SwitchInfo& sw, InstructionOperand const& index_operand) { OperandGenerator g(this); size_t input_count = 2 + sw.value_range(); DCHECK_LE(sw.value_range(), std::numeric_limits<size_t>::max() - 2); auto* inputs = zone()->template AllocateArray<InstructionOperand>(input_count); inputs[0] = index_operand; InstructionOperand default_operand = g.Label(sw.default_branch()); std::fill(&inputs[1], &inputs[input_count], default_operand); for (const CaseInfo& c : sw.CasesUnsorted()) { size_t value = c.value - sw.min_value(); DCHECK_LE(0u, value); DCHECK_LT(value + 2, input_count); inputs[value + 2] = g.Label(c.branch); MarkAsTableSwitchTarget(c.branch); } // If the default operand still exists in the cases, to fill gaps, then we // need to mark the default block as table switch target. if (std::find(&inputs[2], &inputs[input_count], default_operand) != &inputs[input_count]) { MarkAsTableSwitchTarget(sw.default_branch()); } Emit(kArchTableSwitch, 0, nullptr, input_count, inputs, 0, nullptr); } void InstructionSelector::EmitBinarySearchSwitch( const SwitchInfo& sw, InstructionOperand const& value_operand) { OperandGenerator g(this); size_t input_count = 2 + sw.case_count() * 2; DCHECK_LE(sw.case_count(), (std::numeric_limits<size_t>::max() - 2) / 2); auto* inputs = zone()->template AllocateArray<InstructionOperand>(input_count); inputs[0] = value_operand; inputs[1] = g.Label(sw.default_branch()); std::vector<CaseInfo> cases = sw.CasesSortedByValue(); for (size_t index = 0; index < cases.size(); ++index) { const CaseInfo& c = cases[index]; inputs[index * 2 + 2 + 0] = g.TempImmediate(c.value); inputs[index * 2 + 2 + 1] = g.Label(c.branch); } Emit(kArchBinarySearchSwitch, 0, nullptr, input_count, inputs, 0, nullptr); } void InstructionSelector::VisitBitcastTaggedToWord(OpIndex node) { EmitIdentity(node); } void InstructionSelector::VisitBitcastWordToTagged(OpIndex node) { OperandGenerator g(this); Emit(kArchNop, g.DefineSameAsFirst(node), g.Use(this->Get(node).Cast<TaggedBitcastOp>().input())); } void InstructionSelector::VisitBitcastSmiToWord(OpIndex node) { // TODO(dmercadier): using EmitIdentity here is not ideal, because users of // {node} will then use its input, which may not have the Word32 // representation. This might in turn lead to the register allocator wrongly // tracking Tagged values that are in fact just Smis. However, using // Emit(kArchNop) hurts performance because it inserts a gap move which cannot // always be eliminated because the operands may have different sizes (and the // move is then truncating or extending). As a temporary work-around until the // register allocator is fixed, we use Emit(kArchNop) in DEBUG mode to silence // the register allocator verifier. // Loong64/RISCV64 port needs this gap move to sign-extend the Smis in 64-bit // registers. #if defined(DEBUG) || defined(V8_TARGET_ARCH_LOONG64) || \ defined(V8_TARGET_ARCH_RISCV64) OperandGenerator g(this); Emit(kArchNop, g.DefineSameAsFirst(node), g.Use(this->Get(node).Cast<TaggedBitcastOp>().input())); #else EmitIdentity(node); #endif } // 32 bit targets do not implement the following instructions. #if V8_TARGET_ARCH_32_BIT VISIT_UNSUPPORTED_OP(Word64And) VISIT_UNSUPPORTED_OP(Word64Or) VISIT_UNSUPPORTED_OP(Word64Xor) VISIT_UNSUPPORTED_OP(Word64Shl) VISIT_UNSUPPORTED_OP(Word64Shr) VISIT_UNSUPPORTED_OP(Word64Sar) VISIT_UNSUPPORTED_OP(Word64Rol) VISIT_UNSUPPORTED_OP(Word64Ror) VISIT_UNSUPPORTED_OP(Word64Clz) VISIT_UNSUPPORTED_OP(Word64Ctz) VISIT_UNSUPPORTED_OP(Word64ReverseBits) VISIT_UNSUPPORTED_OP(Word64Popcnt) VISIT_UNSUPPORTED_OP(Word64Equal) VISIT_UNSUPPORTED_OP(Int64Add) VISIT_UNSUPPORTED_OP(Int64Sub) VISIT_UNSUPPORTED_OP(Int64Mul) VISIT_UNSUPPORTED_OP(Int64MulHigh) VISIT_UNSUPPORTED_OP(Uint64MulHigh) VISIT_UNSUPPORTED_OP(Int64Div) VISIT_UNSUPPORTED_OP(Int64Mod) VISIT_UNSUPPORTED_OP(Uint64Div) VISIT_UNSUPPORTED_OP(Uint64Mod) VISIT_UNSUPPORTED_OP(Int64AddWithOverflow) VISIT_UNSUPPORTED_OP(Int64MulWithOverflow) VISIT_UNSUPPORTED_OP(Int64SubWithOverflow) VISIT_UNSUPPORTED_OP(Int64LessThan) VISIT_UNSUPPORTED_OP(Int64LessThanOrEqual) VISIT_UNSUPPORTED_OP(Uint64LessThan) VISIT_UNSUPPORTED_OP(Uint64LessThanOrEqual) VISIT_UNSUPPORTED_OP(BitcastWord32ToWord64) VISIT_UNSUPPORTED_OP(ChangeInt32ToInt64) VISIT_UNSUPPORTED_OP(ChangeInt64ToFloat64) VISIT_UNSUPPORTED_OP(ChangeUint32ToUint64) VISIT_UNSUPPORTED_OP(ChangeFloat64ToInt64) VISIT_UNSUPPORTED_OP(ChangeFloat64ToUint64) VISIT_UNSUPPORTED_OP(TruncateFloat64ToInt64) VISIT_UNSUPPORTED_OP(TruncateInt64ToInt32) VISIT_UNSUPPORTED_OP(TryTruncateFloat32ToInt64) VISIT_UNSUPPORTED_OP(TryTruncateFloat64ToInt64) VISIT_UNSUPPORTED_OP(TryTruncateFloat32ToUint64) VISIT_UNSUPPORTED_OP(TryTruncateFloat64ToUint64) VISIT_UNSUPPORTED_OP(TryTruncateFloat64ToInt32) VISIT_UNSUPPORTED_OP(TryTruncateFloat64ToUint32) VISIT_UNSUPPORTED_OP(RoundInt64ToFloat32) VISIT_UNSUPPORTED_OP(RoundInt64ToFloat64) VISIT_UNSUPPORTED_OP(RoundUint64ToFloat32) VISIT_UNSUPPORTED_OP(RoundUint64ToFloat64) VISIT_UNSUPPORTED_OP(BitcastFloat64ToInt64) VISIT_UNSUPPORTED_OP(BitcastInt64ToFloat64) VISIT_UNSUPPORTED_OP(SignExtendWord8ToInt64) VISIT_UNSUPPORTED_OP(SignExtendWord16ToInt64) VISIT_UNSUPPORTED_OP(SignExtendWord32ToInt64) #endif // V8_TARGET_ARCH_32_BIT // 64 bit targets do not implement the following instructions. #if V8_TARGET_ARCH_64_BIT VISIT_UNSUPPORTED_OP(Int32PairAdd) VISIT_UNSUPPORTED_OP(Int32PairSub) VISIT_UNSUPPORTED_OP(Int32PairMul) VISIT_UNSUPPORTED_OP(Word32PairShl) VISIT_UNSUPPORTED_OP(Word32PairShr) VISIT_UNSUPPORTED_OP(Word32PairSar) #endif // V8_TARGET_ARCH_64_BIT #if !V8_TARGET_ARCH_IA32 && !V8_TARGET_ARCH_ARM && !V8_TARGET_ARCH_RISCV32 void InstructionSelector::VisitWord32AtomicPairLoad(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairStore(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairAdd(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairSub(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairAnd(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairOr(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairXor(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairExchange(OpIndex node) { UNIMPLEMENTED(); } void InstructionSelector::VisitWord32AtomicPairCompareExchange(OpIndex node) { UNIMPLEMENTED(); } #endif // !V8_TARGET_ARCH_IA32 && !V8_TARGET_ARCH_ARM // && !V8_TARGET_ARCH_RISCV32 #if !V8_TARGET_ARCH_X64 && !V8_TARGET_ARCH_ARM64 && !V8_TARGET_ARCH_MIPS64 && \ !V8_TARGET_ARCH_S390X && !V8_TARGET_ARCH_PPC64 && \ !V8_TARGET_ARCH_RISCV64 && !V8_TARGET_ARCH_LOONG64 VISIT_UNSUPPORTED_OP(Word64AtomicLoad) VISIT_UNSUPPORTED_OP(Word64AtomicStore) VISIT_UNSUPPORTED_OP(Word64AtomicAdd) VISIT_UNSUPPORTED_OP(Word64AtomicSub) VISIT_UNSUPPORTED_OP(Word64AtomicAnd) VISIT_UNSUPPORTED_OP(Word64AtomicOr) VISIT_UNSUPPORTED_OP(Word64AtomicXor) VISIT_UNSUPPORTED_OP(Word64AtomicExchange) VISIT_UNSUPPORTED_OP(Word64AtomicCompareExchange) #endif // !V8_TARGET_ARCH_X64 && !V8_TARGET_ARCH_ARM64 && !V8_TARGET_ARCH_PPC64 // !V8_TARGET_ARCH_MIPS64 && !V8_TARGET_ARCH_S390X && // !V8_TARGET_ARCH_RISCV64 && !V8_TARGET_ARCH_LOONG64 #if !V8_TARGET_ARCH_IA32 && !V8_TARGET_ARCH_ARM && !V8_TARGET_ARCH_RISCV32 // This is only needed on 32-bit to split the 64-bit value into two operands. IF_WASM(VISIT_UNSUPPORTED_OP, I64x2SplatI32Pair) IF_WASM(VISIT_UNSUPPORTED_OP, I64x2ReplaceLaneI32Pair) #endif // !V8_TARGET_ARCH_IA32 && !V8_TARGET_ARCH_ARM && // !V8_TARGET_ARCH_RISCV32 #if !V8_TARGET_ARCH_X64 && !V8_TARGET_ARCH_S390X && !V8_TARGET_ARCH_PPC64 #if !V8_TARGET_ARCH_ARM64 #if !V8_TARGET_ARCH_MIPS64 && !V8_TARGET_ARCH_LOONG64 && \ !V8_TARGET_ARCH_RISCV32 && !V8_TARGET_ARCH_RISCV64 IF_WASM(VISIT_UNSUPPORTED_OP, I64x2Splat) IF_WASM(VISIT_UNSUPPORTED_OP, I64x2ExtractLane) IF_WASM(VISIT_UNSUPPORTED_OP, I64x2ReplaceLane) #endif // !V8_TARGET_ARCH_MIPS64 && !V8_TARGET_ARCH_LOONG64 && // !V8_TARGET_ARCH_RISCV64 && !V8_TARGET_ARCH_RISCV32 #endif // !V8_TARGET_ARCH_ARM64 #endif // !V8_TARGET_ARCH_X64 && !V8_TARGET_ARCH_S390X && !V8_TARGET_ARCH_PPC64 #if !V8_TARGET_ARCH_ARM64 IF_WASM(VISIT_UNSUPPORTED_OP, Simd128LoadPairDeinterleave) IF_WASM(VISIT_UNSUPPORTED_OP, I8x16AddReduce) IF_WASM(VISIT_UNSUPPORTED_OP, I16x8AddReduce) IF_WASM(VISIT_UNSUPPORTED_OP, I32x4AddReduce) IF_WASM(VISIT_UNSUPPORTED_OP, I64x2AddReduce) IF_WASM(VISIT_UNSUPPORTED_OP, F32x4AddReduce) IF_WASM(VISIT_UNSUPPORTED_OP, F64x2AddReduce) IF_WASM(VISIT_UNSUPPORTED_OP, I8x1Shuffle) IF_WASM(VISIT_UNSUPPORTED_OP, I8x2Shuffle) IF_WASM(VISIT_UNSUPPORTED_OP, I8x4Shuffle) IF_WASM(VISIT_UNSUPPORTED_OP, I8x8Shuffle) IF_WASM(VISIT_UNSUPPORTED_OP, MemoryCopy) IF_WASM(VISIT_UNSUPPORTED_OP, MemoryFill) IF_WASM(VISIT_UNSUPPORTED_OP, I32x4AddPairwise) IF_WASM(VISIT_UNSUPPORTED_OP, I8x16MoveLane) IF_WASM(VISIT_UNSUPPORTED_OP, I16x8MoveLane) IF_WASM(VISIT_UNSUPPORTED_OP, I32x4MoveLane) IF_WASM(VISIT_UNSUPPORTED_OP, I64x2MoveLane) IF_WASM(VISIT_UNSUPPORTED_OP, F32x4MoveLane) IF_WASM(VISIT_UNSUPPORTED_OP, F64x2MoveLane) #endif // !V8_TARGET_ARCH_ARM64 IF_WASM(VISIT_UNSUPPORTED_OP, F16x8MoveLane) void InstructionSelector::VisitParameter(OpIndex node) { const ParameterOp& parameter = Cast<ParameterOp>(node); const int index = parameter.parameter_index; OperandGenerator g(this); if (linkage()->GetParameterLocation(index).IsNullRegister()) { EmitMoveParamToFPR(node, index); } else { InstructionOperand op = linkage()->ParameterHasSecondaryLocation(index) ? g.DefineAsDualLocation( node, linkage()->GetParameterLocation(index), linkage()->GetParameterSecondaryLocation(index)) : g.DefineAsLocation(node, linkage()->GetParameterLocation(index)); Emit(kArchNop, op); } } namespace { LinkageLocation ExceptionLocation() { return LinkageLocation::ForRegister(kReturnRegister0.code(), MachineType::TaggedPointer()); } constexpr InstructionCode EncodeCallDescriptorFlags( InstructionCode opcode, CallDescriptor::Flags flags) { // Note: Not all bits of `flags` are preserved. static_assert(CallDescriptor::kFlagsBitsEncodedInInstructionCode == MiscField::kSize); DCHECK(Instruction::IsCallWithDescriptorFlags(opcode)); return opcode | MiscField::encode(flags & MiscField::kMax); } } // namespace void InstructionSelector::VisitIfException(OpIndex node) { OperandGenerator g(this); Emit(kArchNop, g.DefineAsLocation(node, ExceptionLocation())); } void InstructionSelector::VisitOsrValue(OpIndex node) { const OsrValueOp& osr_value = Cast<OsrValueOp>(node); OperandGenerator g(this); Emit(kArchNop, g.DefineAsLocation( node, linkage()->GetOsrValueLocation(osr_value.index))); } void InstructionSelector::VisitPhi(OpIndex node) { const Operation& op = Get(node); DCHECK_EQ(op.input_count, PredecessorCount(current_block_)); PhiInstruction* phi = instruction_zone()->template New<PhiInstruction>( instruction_zone(), GetVirtualRegister(node), static_cast<size_t>(op.input_count)); sequence()->InstructionBlockAt(rpo_number(current_block_))->AddPhi(phi); for (size_t i = 0; i < op.input_count; ++i) { OpIndex input = op.input(i); MarkAsUsed(input); phi->SetInput(i, GetVirtualRegister(input)); } } void InstructionSelector::VisitProjection(OpIndex node) { const ProjectionOp& projection = this->Get(node).Cast<ProjectionOp>(); const Operation& value_op = this->Get(projection.input()); if (value_op.Is<OverflowCheckedBinopOp>() || value_op.Is<OverflowCheckedUnaryOp>() || value_op.Is<TryChangeOp>() || value_op.Is<Word32PairBinopOp>()) { if (projection.index == 0u) { EmitIdentity(node); } else { DCHECK_EQ(1u, projection.index); MarkAsUsed(projection.input()); } } else if (value_op.Is<DidntThrowOp>()) { // Nothing to do here? } else if (value_op.Is<CallOp>()) { // Call projections need to be behind the call's DidntThrow. UNREACHABLE(); } else if (value_op.Is<AtomicWord32PairOp>()) { // Nothing to do here. #if V8_ENABLE_WEBASSEMBLY } else if (value_op.Is<Simd128LoadPairDeinterleaveOp>()) { MarkAsUsed(projection.input()); #endif // V8_ENABLE_WEBASSEMBLY } else { UNIMPLEMENTED(); } } bool InstructionSelector::CanDoBranchIfOverflowFusion(OpIndex binop) { const Graph* graph = this->turboshaft_graph(); DCHECK(graph->Get(binop).template Is<OverflowCheckedBinopOp>() || graph->Get(binop).template Is<OverflowCheckedUnaryOp>()); // Getting the 1st projection. Projections are always emitted right after the // operation, in ascending order. OpIndex projection0_index = graph->NextIndex(binop); const ProjectionOp& projection0 = graph->Get(projection0_index).Cast<ProjectionOp>(); DCHECK_EQ(projection0.index, 0); if (IsDefined(projection0_index)) { // In Turboshaft, this can only happen if {projection0_index} has already // been eagerly scheduled somewhere else, like in // TryPrepareScheduleFirstProjection. return true; } if (projection0.saturated_use_count.IsOne()) { // If the projection has a single use, it is the following tuple, so we // don't care about the value, and can do branch-if-overflow fusion. DCHECK(turboshaft_uses(projection0_index).size() == 1 && graph->Get(turboshaft_uses(projection0_index)[0]).Is<MakeTupleOp>()); return true; } if (this->block(schedule_, binop) != current_block_) { // {binop} is not supposed to be defined in the current block, so let's not // pull it in this block (the checks would need to be stronger, and it's // unlikely that it's doable because of effect levels and all). return false; } // We now need to make sure that all uses of {projection0} are already // defined, which will imply that it's fine to define {projection0} and // {binop} now. for (OpIndex use : turboshaft_uses(projection0_index)) { if (this->Get(use).template Is<MakeTupleOp>()) { // The Tuple won't have any uses since it would have to be accessed // through Projections, and Projections on Tuples return the original // Projection instead (see Assembler::ReduceProjection in // turboshaft/assembler.h). DCHECK(this->Get(use).saturated_use_count.IsZero()); continue; } if (IsDefined(use)) continue; if (this->block(schedule_, use) != current_block_) { // {use} is in a later block, so it should already have been visited. Note // that operations that don't produce values are not marked as Defined, // like Return for instance, so it's possible that {use} has been visited // but the previous `IsDefined` check didn't match. #ifdef DEBUG if (this->block(schedule_, use)->index() < current_block_->index()) { // If {use} is in a previous block, then it has to be a loop Phi that // uses {projection0} as its backedge input. In that case, it's fine to // schedule the binop right now, even though it's after the use of its // 1st projection (since the use is conceptually after rather than // before because it goes through a backedge). DCHECK(this->Get(use).template Is<PhiOp>()); DCHECK_EQ(this->Get(use).template Cast<PhiOp>().input(1), projection0_index); } #endif continue; } if (this->Get(use).template Is<PhiOp>()) { DCHECK_EQ(this->block(schedule_, use), current_block_); // If {projection0} is used by a Phi in the current block, then it has to // be a loop phi, and {projection0} has to be its backedge value. This // doesn't prevent scheduling {projection0} now, since anyways it // necessarily needs to be scheduled after the Phi. DCHECK(current_block_->IsLoop()); continue; } // {use} is not defined yet (and is not a special case), which means that // {projection0} has a use that comes before {binop}, and we thus can't fuse // binop with a branch to do a branch-if-overflow. return false; } VisitProjection(projection0_index); return true; } void InstructionSelector::VisitConstant(OpIndex node) { // We must emit a NOP here because every live range needs a defining // instruction in the register allocator. OperandGenerator g(this); Emit(kArchNop, g.DefineAsConstant(node)); } void InstructionSelector::UpdateMaxPushedArgumentCount(size_t count) { *max_pushed_argument_count_ = std::max(count, *max_pushed_argument_count_); } void InstructionSelector::VisitCall( OpIndex node, Block* exception_handler, base::Vector<EffectHandler> effect_handlers) { OperandGenerator g(this); const CallOp& call_op = Cast<CallOp>(node); const CallDescriptor* call_descriptor = call_op.descriptor->descriptor; SaveFPRegsMode mode = call_descriptor->NeedsCallerSavedFPRegisters() ? SaveFPRegsMode::kSave : SaveFPRegsMode::kIgnore; if (call_descriptor->NeedsCallerSavedRegisters()) { Emit(kArchSaveCallerRegisters | MiscField::encode(static_cast<int>(mode)), g.NoOutput()); } FrameStateDescriptor* frame_state_descriptor = nullptr; if (call_descriptor->NeedsFrameState()) { frame_state_descriptor = GetFrameStateDescriptor(call_op.frame_state().value()); } CallBuffer buffer(zone(), call_descriptor, frame_state_descriptor); CallDescriptor::Flags flags = call_descriptor->flags(); // Compute InstructionOperands for inputs and outputs. // TODO(turbofan): on some architectures it's probably better to use // the code object in a register if there are multiple uses of it. // Improve constant pool and the heuristics in the register allocator // for where to emit constants. CallBufferFlags call_buffer_flags(kCallCodeImmediate | kCallAddressImmediate); if (flags & CallDescriptor::kFixedTargetRegister) { call_buffer_flags |= kCallFixedTargetRegister; } InitializeCallBuffer(node, &buffer, call_buffer_flags, call_op.callee(), call_op.frame_state(), call_op.arguments(), static_cast<int>(call_op.results_rep().size())); EmitPrepareArguments(&buffer.pushed_nodes, call_descriptor, node); UpdateMaxPushedArgumentCount(buffer.pushed_nodes.size()); InstructionOperandVector temps(zone()); #if V8_ENABLE_WEBASSEMBLY if (call_descriptor->IsIndirectWasmFunctionCall()) { buffer.instruction_args.push_back( g.UseImmediate64(call_descriptor->signature_hash())); } #endif if (call_descriptor->RequiresEntrypointTagForCall()) { DCHECK(!call_descriptor->IsJSFunctionCall()); buffer.instruction_args.push_back( g.TempImmediate(call_descriptor->shifted_tag())); } else if (call_descriptor->IsJSFunctionCall()) { // For JSFunctions we need to know the number of pushed parameters during // code generation. uint32_t parameter_count = static_cast<uint32_t>(buffer.pushed_nodes.size()); buffer.instruction_args.push_back(g.TempImmediate(parameter_count)); } // Pass label of exception handler block. bool lazy_deopt_on_throw = call_op.descriptor->lazy_deopt_on_throw == LazyDeoptOnThrow::kYes; if (exception_handler) { flags |= CallDescriptor::kHasExceptionHandler; buffer.instruction_args.push_back(g.Label(exception_handler)); } else if (lazy_deopt_on_throw) { flags |= CallDescriptor::kHasExceptionHandler; buffer.instruction_args.push_back( g.UseImmediate(kLazyDeoptOnThrowSentinel)); } if (!effect_handlers.empty()) { flags |= CallDescriptor::kHasEffectHandler; for (auto& handler : effect_handlers) { buffer.instruction_args.push_back(g.Label(handler.block)); buffer.instruction_args.push_back(g.UseImmediate(handler.tag_index)); } buffer.instruction_args.push_back( g.UseImmediate(static_cast<int>(effect_handlers.size()))); } else { // This bit had a different meaning before isel, so ensure that it is // cleared: flags &= ~CallDescriptor::kHasEffectHandler; } // Select the appropriate opcode based on the call type. InstructionCode opcode; switch (call_descriptor->kind()) { case CallDescriptor::kCallAddress: { int gp_param_count = static_cast<int>(call_descriptor->GPParameterCount()); int fp_param_count = static_cast<int>(call_descriptor->FPParameterCount()); #if ABI_USES_FUNCTION_DESCRIPTORS // Highest fp_param_count bit is used on AIX to indicate if a CFunction // call has function descriptor or not. static_assert(FPParamField::kSize == kHasFunctionDescriptorBitShift + 1); if (!call_descriptor->NoFunctionDescriptor()) { fp_param_count |= 1 << kHasFunctionDescriptorBitShift; } #endif // We store the param counts as a separate input because they need too // many bits to be encoded in the opcode. buffer.instruction_args.push_back( g.UseImmediate(ParamField::encode(gp_param_count) | FPParamField::encode(fp_param_count))); opcode = EncodeCallDescriptorFlags(kArchCallCFunction, flags); break; } case CallDescriptor::kCallCodeObject: opcode = EncodeCallDescriptorFlags(kArchCallCodeObject, flags); break; case CallDescriptor::kCallJSFunction: opcode = EncodeCallDescriptorFlags(kArchCallJSFunction, flags); break; #if V8_ENABLE_WEBASSEMBLY case CallDescriptor::kCallWasmCapiFunction: case CallDescriptor::kCallWasmFunction: case CallDescriptor::kCallWasmImportWrapper: DCHECK(this->IsRelocatableWasmConstant(call_op.callee())); opcode = EncodeCallDescriptorFlags(kArchCallWasmFunction, flags); break; case CallDescriptor::kCallWasmFunctionIndirect: DCHECK(!this->IsRelocatableWasmConstant(call_op.callee())); opcode = EncodeCallDescriptorFlags(kArchCallWasmFunctionIndirect, flags); break; case CallDescriptor::kResumeWasmContinuation: // Should be called via Builtin::kWasmFXResume. UNREACHABLE(); #endif // V8_ENABLE_WEBASSEMBLY case CallDescriptor::kCallBuiltinPointer: opcode = EncodeCallDescriptorFlags(kArchCallBuiltinPointer, flags); break; } // Emit the call instruction. size_t const output_count = buffer.outputs.size(); auto* outputs = output_count ? &buffer.outputs.front() : nullptr; Instruction* call_instr = Emit(opcode, output_count, outputs, buffer.instruction_args.size(), &buffer.instruction_args.front()); if (instruction_selection_failed()) return; call_instr->MarkAsCall(); EmitPrepareResults(&(buffer.output_nodes), call_descriptor, node); if (call_descriptor->NeedsCallerSavedRegisters()) { Emit( kArchRestoreCallerRegisters | MiscField::encode(static_cast<int>(mode)), g.NoOutput()); } } void InstructionSelector::VisitTailCall(OpIndex node) { OperandGenerator g(this); const TailCallOp& call_op = Cast<TailCallOp>(node); auto caller = linkage()->GetIncomingDescriptor(); auto callee = call_op.descriptor->descriptor; DCHECK(caller->CanTailCall(callee)); const int stack_param_delta = callee->GetStackParameterDelta(caller); CallBuffer buffer(zone(), callee, nullptr); // Compute InstructionOperands for inputs and outputs. CallBufferFlags flags(kCallCodeImmediate | kCallTail); if (IsTailCallAddressImmediate()) { flags |= kCallAddressImmediate; } if (callee->flags() & CallDescriptor::kFixedTargetRegister) { flags |= kCallFixedTargetRegister; } InitializeCallBuffer(node, &buffer, flags, call_op.callee(), OptionalOpIndex::Nullopt(), call_op.arguments(), static_cast<int>(call_op.outputs_rep().size()), stack_param_delta); UpdateMaxPushedArgumentCount(stack_param_delta); // Select the appropriate opcode based on the call type. InstructionCode opcode; InstructionOperandVector temps(zone()); switch (callee->kind()) { case CallDescriptor::kCallCodeObject: opcode = kArchTailCallCodeObject; break; case CallDescriptor::kCallAddress: DCHECK(!caller->IsJSFunctionCall()); opcode = kArchTailCallAddress; break; #if V8_ENABLE_WEBASSEMBLY case CallDescriptor::kCallWasmFunction: DCHECK(!caller->IsJSFunctionCall()); DCHECK(this->IsRelocatableWasmConstant(call_op.callee())); opcode = kArchTailCallWasm; break; case CallDescriptor::kCallWasmFunctionIndirect: DCHECK(!caller->IsJSFunctionCall()); DCHECK(!this->IsRelocatableWasmConstant(call_op.callee())); opcode = kArchTailCallWasmIndirect; break; #endif // V8_ENABLE_WEBASSEMBLY default: UNREACHABLE(); } opcode = EncodeCallDescriptorFlags(opcode, callee->flags()); Emit(kArchPrepareTailCall, g.NoOutput()); #if V8_ENABLE_WEBASSEMBLY if (callee->IsIndirectWasmFunctionCall()) { buffer.instruction_args.push_back( g.UseImmediate64(callee->signature_hash())); } #endif if (callee->RequiresEntrypointTagForCall()) { buffer.instruction_args.push_back(g.TempImmediate(callee->shifted_tag())); } // Add an immediate operand that represents the offset to the first slot // that is unused with respect to the stack pointer that has been updated // for the tail call instruction. Backends that pad arguments can write the // padding value at this offset from the stack. const int optional_padding_offset = callee->GetOffsetToFirstUnusedStackSlot() - 1; buffer.instruction_args.push_back(g.TempImmediate(optional_padding_offset)); const int first_unused_slot_offset = kReturnAddressStackSlotCount + stack_param_delta; buffer.instruction_args.push_back(g.TempImmediate(first_unused_slot_offset)); // Emit the tailcall instruction. Emit(opcode, 0, nullptr, buffer.instruction_args.size(), &buffer.instruction_args.front(), temps.size(), temps.empty() ? nullptr : &temps.front()); } void InstructionSelector::VisitGoto(Block* target) { // jump to the next block. OperandGenerator g(this); Emit(kArchJmp, g.NoOutput(), g.Label(target)); } void InstructionSelector::VisitReturn(OpIndex node) { const ReturnOp& ret = schedule()->Get(node).Cast<ReturnOp>(); OperandGenerator g(this); const size_t return_count = linkage()->GetIncomingDescriptor()->ReturnCount(); const int input_count = return_count == 0 ? 1 : (1 + static_cast<int>(ret.return_values().size())); DCHECK_GE(input_count, 1); auto value_locations = zone()->template AllocateArray<InstructionOperand>(input_count); const Operation& pop_count = schedule()->Get(ret.pop_count()); if (pop_count.Is<Opmask::kWord32Constant>() || pop_count.Is<Opmask::kWord64Constant>()) { value_locations[0] = g.UseImmediate(ret.pop_count()); } else { value_locations[0] = g.UseRegister(ret.pop_count()); } for (size_t i = 0, return_value_idx = 0; i < return_count; ++i) { LinkageLocation loc = linkage()->GetReturnLocation(i); // Return values passed via frame slots have already been stored // on the stack by the GrowableStacksReducer. if (loc.IsCallerFrameSlot() && ret.spill_caller_frame_slots) { continue; } value_locations[return_value_idx + 1] = g.UseLocation(ret.return_values()[return_value_idx], loc); return_value_idx++; } Emit(kArchRet, 0, nullptr, input_count, value_locations); } void InstructionSelector::VisitBranch(OpIndex branch_node, Block* tbranch, Block* fbranch) { const BranchOp& branch = Cast<BranchOp>(branch_node); TryPrepareScheduleFirstProjection(branch.condition()); #if V8_ENABLE_WEBASSEMBLY FlagsContinuation cont = FlagsContinuation::ForHintedBranch( kNotEqual, tbranch, fbranch, branch.hint); #else FlagsContinuation cont = FlagsContinuation::ForBranch(kNotEqual, tbranch, fbranch); #endif // V8_ENABLE_WEBASSEMBLY VisitWordCompareZero(branch_node, branch.condition(), &cont); } // When a DeoptimizeIf/DeoptimizeUnless/Branch depends on a BinopOverflow, the // InstructionSelector can sometimes generate a fuse instruction covering both // the BinopOverflow and the DeoptIf/Branch, and the final emitted code will // look like: // // r = BinopOverflow // jo branch_target/deopt_target // // When this fusing fails, the final code looks like: // // r = BinopOverflow // o = sete // sets overflow bit // cmp o, 0 // jnz branch_target/deopt_target // // To be able to fuse tue BinopOverflow and the DeoptIf/Branch, the 1st // projection (Projection[0], which contains the actual result) must already be // scheduled (and a few other conditions must be satisfied, see // InstructionSelectorXXX::VisitWordCompareZero). // TryPrepareScheduleFirstProjection is thus called from // VisitDeoptimizeIf/VisitBranch and detects if the 1st // projection could be scheduled now, and, if so, defines it. void InstructionSelector::TryPrepareScheduleFirstProjection( OpIndex maybe_projection) { // The DeoptimizeIf/Branch condition is not a projection. const ProjectionOp* projection = TryCast<ProjectionOp>(maybe_projection); if (!projection) return; if (projection->index != 1u) { // The DeoptimizeIf/Branch isn't on the Projection[1] // (ie, not on the overflow bit of a BinopOverflow). return; } DCHECK_EQ(projection->input_count, 1); OpIndex node = projection->input(); if (block(schedule_, node) != current_block_) { // The projection input is not in the current block, so it shouldn't be // emitted now, so we don't need to eagerly schedule its Projection[0]. return; } auto* binop = TryCast<OverflowCheckedBinopOp>(node); auto* unop = TryCast<OverflowCheckedUnaryOp>(node); if (binop == nullptr && unop == nullptr) return; if (binop) { DCHECK(binop->kind == OverflowCheckedBinopOp::Kind::kSignedAdd || binop->kind == OverflowCheckedBinopOp::Kind::kSignedSub || binop->kind == OverflowCheckedBinopOp::Kind::kSignedMul); } else { DCHECK_EQ(unop->kind, OverflowCheckedUnaryOp::Kind::kAbs); } OptionalOpIndex result = FindProjection(node, 0); if (!result.valid() || IsDefined(result.value())) { // No Projection(0), or it's already defined. return; } if (block(schedule_, result.value()) != current_block_) { // {result} wasn't planned to be scheduled in {current_block_}. To // avoid adding checks to see if it can still be scheduled now, we // just bail out. return; } // Checking if all uses of {result} that are in the current block have // already been Defined. // We also ignore Phi uses: if {result} is used in a Phi in the block in // which it is defined, this means that this block is a loop header, and // {result} back into it through the back edge. In this case, it's // normal to schedule {result} before the Phi that uses it. for (OpIndex use : turboshaft_uses(result.value())) { // We ignore MakeTupleOp uses, since MakeTupleOp don't lead to emitted // machine instructions and are just Turboshaft "meta operations". if (!Is<MakeTupleOp>(use) && !IsDefined(use) && block(schedule_, use) == current_block_ && !Is<PhiOp>(use)) { return; } } // Visiting the projection now. Note that this relies on the fact that // VisitProjection doesn't Emit something: if it did, then we could be // Emitting something after a Branch, which is invalid (Branch can only // be at the end of a block, and the end of a block must always be a // block terminator). (remember that we emit operation in reverse order, // so because we are doing TryPrepareScheduleFirstProjection before // actually emitting the Branch, it would be after in the final // instruction sequence, not before) VisitProjection(result.value()); } void InstructionSelector::VisitDeoptimizeIf(OpIndex node) { const DeoptimizeIfOp& deopt = Cast<DeoptimizeIfOp>(node); TryPrepareScheduleFirstProjection(deopt.condition()); FlagsContinuation cont = FlagsContinuation::ForDeoptimize( deopt.negated ? kEqual : kNotEqual, deopt.parameters->reason(), node.id(), deopt.parameters->feedback(), deopt.frame_state()); VisitWordCompareZero(node, deopt.condition(), &cont); } void InstructionSelector::VisitSelect(OpIndex node) { const SelectOp& select = Cast<SelectOp>(node); DCHECK_EQ(select.input_count, 3); FlagsContinuation cont = FlagsContinuation::ForSelect( kNotEqual, node, select.vtrue(), select.vfalse()); VisitWordCompareZero(node, select.cond(), &cont); } void InstructionSelector::VisitTrapIf(OpIndex node) { #if V8_ENABLE_WEBASSEMBLY const TrapIfOp& trap_if = Cast<TrapIfOp>(node); // FrameStates are only used for wasm traps inlined in JS. In that case the // trap node will be lowered (replaced) before instruction selection. // Therefore any TrapIf node has only one input. DCHECK_EQ(trap_if.input_count, 1); FlagsContinuation cont = FlagsContinuation::ForTrap( trap_if.negated ? kEqual : kNotEqual, trap_if.trap_id); VisitWordCompareZero(node, trap_if.condition(), &cont); #else UNREACHABLE(); #endif } void InstructionSelector::EmitIdentity(OpIndex node) { const Operation& op = Get(node); MarkAsUsed(op.input(0)); MarkAsDefined(node); SetRename(node, op.input(0)); } void InstructionSelector::VisitDeoptimize(DeoptimizeReason reason, uint32_t node_id, FeedbackSource const& feedback, OpIndex frame_state) { InstructionOperandVector args(instruction_zone()); AppendDeoptimizeArguments(&args, reason, node_id, feedback, frame_state); Emit(kArchDeoptimize, 0, nullptr, args.size(), &args.front(), 0, nullptr); } void InstructionSelector::VisitDebugBreak(OpIndex node) { OperandGenerator g(this); Emit(kArchDebugBreak, g.NoOutput()); } void InstructionSelector::VisitUnreachable(OpIndex node) { OperandGenerator g(this); Emit(kArchDebugBreak, g.NoOutput()); } void InstructionSelector::VisitStaticAssert(OpIndex node) { const StaticAssertOp& op = Cast<StaticAssertOp>(node); DCHECK_EQ(op.input_count, 1); OpIndex asserted = op.condition(); UnparkedScopeIfNeeded scope(broker_); AllowHandleDereference allow_handle_dereference; StdoutStream os; os << Get(asserted); FATAL( "Expected Turbofan static assert to hold, but got non-true input:\n " "%s", op.source); } void InstructionSelector::VisitComment(OpIndex node) { OperandGenerator g(this); const CommentOp& comment = this->turboshaft_graph()->Get(node).template Cast<CommentOp>(); using ptrsize_int_t = std::conditional_t<kSystemPointerSize == 8, int64_t, int32_t>; InstructionOperand operand = sequence()->AddImmediate( Constant{reinterpret_cast<ptrsize_int_t>(comment.message)}); Emit(kArchComment, 0, nullptr, 1, &operand); } void InstructionSelector::VisitPause(OpIndex node) { Emit(kArchPause, 0, nullptr, 0, nullptr); } void InstructionSelector::VisitRetain(OpIndex node) { const RetainOp& retain = Cast<RetainOp>(node); OperandGenerator g(this); DCHECK_EQ(retain.input_count, 1); Emit(kArchNop, g.NoOutput(), g.UseAny(retain.retained())); } void InstructionSelector::VisitControl(const Block* block) { #ifdef DEBUG // SSA deconstruction requires targets of branches not to have phis. // Edge split form guarantees this property, but is more strict. if (auto successors = SuccessorBlocks(block->LastOperation(*turboshaft_graph())); successors.size() > 1) { for (Block* successor : successors) { if (successor->HasPhis(*turboshaft_graph())) { std::ostringstream str; str << "You might have specified merged variables for a label with " << "only one predecessor." << std::endl << "# Current Block: " << successor->index() << std::endl; FATAL("%s", str.str().c_str()); } } } #endif // DEBUG const Operation& op = block->LastOperation(*schedule()); OpIndex node = schedule()->Index(op); int instruction_end = static_cast<int>(instructions_.size()); switch (op.opcode) { case Opcode::kGoto: VisitGoto(op.Cast<GotoOp>().destination); break; case Opcode::kReturn: VisitReturn(node); break; case Opcode::kTailCall: VisitTailCall(node); break; case Opcode::kDeoptimize: { const DeoptimizeOp& deoptimize = op.Cast<DeoptimizeOp>(); VisitDeoptimize(deoptimize.parameters->reason(), node.id(), deoptimize.parameters->feedback(), deoptimize.frame_state()); break; } case Opcode::kBranch: { const BranchOp& branch = op.Cast<BranchOp>(); Block* tbranch = branch.if_true; Block* fbranch = branch.if_false; if (tbranch == fbranch) { VisitGoto(tbranch); } else { VisitBranch(node, tbranch, fbranch); } break; } case Opcode::kSwitch: { const SwitchOp& swtch = op.Cast<SwitchOp>(); int32_t min_value = std::numeric_limits<int32_t>::max(); int32_t max_value = std::numeric_limits<int32_t>::min(); ZoneVector<CaseInfo> cases(swtch.cases.size(), zone()); for (size_t i = 0; i < swtch.cases.size(); ++i) { const SwitchOp::Case& c = swtch.cases[i]; cases[i] = CaseInfo{c.value, 0, c.destination}; if (min_value > c.value) min_value = c.value; if (max_value < c.value) max_value = c.value; } SwitchInfo sw(std::move(cases), min_value, max_value, swtch.default_case); return VisitSwitch(node, sw); } case Opcode::kCheckException: { const CheckExceptionOp& check = op.Cast<CheckExceptionOp>(); VisitCall(check.throwing_operation(), check.catch_block, check.effect_handlers); VisitGoto(check.didnt_throw_block); return; } case Opcode::kUnreachable: return VisitUnreachable(node); case Opcode::kStaticAssert: return VisitStaticAssert(node); default: { const std::string op_string = op.ToString(); PrintF("\033[31mNo ISEL support for: %s\033[m\n", op_string.c_str()); FATAL("Unexpected operation #%d:%s", node.id(), op_string.c_str()); } } if (trace_turbo_) { DCHECK(node.valid()); int instruction_start = static_cast<int>(instructions_.size()); instr_origins_[node.id()] = {instruction_start, instruction_end}; } } void InstructionSelector::VisitNode(OpIndex node) { tick_counter_->TickAndMaybeEnterSafepoint(); const Operation& op = this->Get(node); using Opcode = Opcode; using Rep = RegisterRepresentation; switch (op.opcode) { case Opcode::kBranch: case Opcode::kGoto: case Opcode::kReturn: case Opcode::kTailCall: case Opcode::kUnreachable: case Opcode::kDeoptimize: case Opcode::kSwitch: case Opcode::kCheckException: // Those are already handled in VisitControl. DCHECK(op.IsBlockTerminator()); break; case Opcode::kParameter: { // Parameters should always be scheduled to the first block. DCHECK_EQ(this->rpo_number(this->block(schedule(), node)).ToInt(), 0); MachineType type = linkage()->GetParameterType(op.Cast<ParameterOp>().parameter_index); MarkAsRepresentation(type.representation(), node); return VisitParameter(node); } case Opcode::kChange: { const ChangeOp& change = op.Cast<ChangeOp>(); MarkAsRepresentation(change.to.machine_representation(), node); switch (change.kind) { case ChangeOp::Kind::kFloatConversion: if (change.from == Rep::Float64()) { DCHECK_EQ(change.to, Rep::Float32()); return VisitTruncateFloat64ToFloat32(node); } else { DCHECK_EQ(change.from, Rep::Float32()); DCHECK_EQ(change.to, Rep::Float64()); return VisitChangeFloat32ToFloat64(node); } case ChangeOp::Kind::kSignedFloatTruncateOverflowToMin: case ChangeOp::Kind::kUnsignedFloatTruncateOverflowToMin: { using A = ChangeOp::Assumption; bool is_signed = change.kind == ChangeOp::Kind::kSignedFloatTruncateOverflowToMin; switch (multi(change.from, change.to, is_signed, change.assumption)) { case multi(Rep::Float32(), Rep::Word32(), true, A::kNoOverflow): case multi(Rep::Float32(), Rep::Word32(), true, A::kNoAssumption): return VisitTruncateFloat32ToInt32(node); case multi(Rep::Float32(), Rep::Word32(), false, A::kNoOverflow): case multi(Rep::Float32(), Rep::Word32(), false, A::kNoAssumption): return VisitTruncateFloat32ToUint32(node); case multi(Rep::Float64(), Rep::Word32(), true, A::kReversible): return VisitChangeFloat64ToInt32(node); case multi(Rep::Float64(), Rep::Word32(), false, A::kReversible): return VisitChangeFloat64ToUint32(node); case multi(Rep::Float64(), Rep::Word32(), true, A::kNoOverflow): return VisitRoundFloat64ToInt32(node); case multi(Rep::Float64(), Rep::Word32(), false, A::kNoAssumption): case multi(Rep::Float64(), Rep::Word32(), false, A::kNoOverflow): return VisitTruncateFloat64ToUint32(node); case multi(Rep::Float64(), Rep::Word64(), true, A::kReversible): return VisitChangeFloat64ToInt64(node); case multi(Rep::Float64(), Rep::Word64(), false, A::kReversible): return VisitChangeFloat64ToUint64(node); case multi(Rep::Float64(), Rep::Word64(), true, A::kNoOverflow): case multi(Rep::Float64(), Rep::Word64(), true, A::kNoAssumption): return VisitTruncateFloat64ToInt64(node); default: // Invalid combination. UNREACHABLE(); } UNREACHABLE(); } case ChangeOp::Kind::kJSFloatTruncate: DCHECK_EQ(change.from, Rep::Float64()); DCHECK_EQ(change.to, Rep::Word32()); return VisitTruncateFloat64ToWord32(node); case ChangeOp::Kind::kJSFloat16TruncateWithBitcast: DCHECK_EQ(Rep::Float64(), change.from); DCHECK_EQ(Rep::Word32(), change.to); return VisitTruncateFloat64ToFloat16RawBits(node); case ChangeOp::Kind::kJSFloat16ChangeWithBitcast: DCHECK_EQ(Rep::Word32(), change.from); DCHECK_EQ(Rep::Float64(), change.to); return VisitChangeFloat16RawBitsToFloat64(node); case ChangeOp::Kind::kSignedToFloat: if (change.from == Rep::Word32()) { if (change.to == Rep::Float32()) { return VisitRoundInt32ToFloat32(node); } else { DCHECK_EQ(change.to, Rep::Float64()); DCHECK_EQ(change.assumption, ChangeOp::Assumption::kNoAssumption); return VisitChangeInt32ToFloat64(node); } } else { DCHECK_EQ(change.from, Rep::Word64()); if (change.to == Rep::Float32()) { return VisitRoundInt64ToFloat32(node); } else { DCHECK_EQ(change.to, Rep::Float64()); if (change.assumption == ChangeOp::Assumption::kReversible) { return VisitChangeInt64ToFloat64(node); } else { return VisitRoundInt64ToFloat64(node); } } } UNREACHABLE(); case ChangeOp::Kind::kUnsignedToFloat: switch (multi(change.from, change.to)) { case multi(Rep::Word32(), Rep::Float32()): return VisitRoundUint32ToFloat32(node); case multi(Rep::Word32(), Rep::Float64()): return VisitChangeUint32ToFloat64(node); case multi(Rep::Word64(), Rep::Float32()): return VisitRoundUint64ToFloat32(node); case multi(Rep::Word64(), Rep::Float64()): return VisitRoundUint64ToFloat64(node); default: UNREACHABLE(); } case ChangeOp::Kind::kExtractHighHalf: DCHECK_EQ(change.from, Rep::Float64()); DCHECK_EQ(change.to, Rep::Word32()); return VisitFloat64ExtractHighWord32(node); case ChangeOp::Kind::kExtractLowHalf: DCHECK_EQ(change.from, Rep::Float64()); DCHECK_EQ(change.to, Rep::Word32()); return VisitFloat64ExtractLowWord32(node); case ChangeOp::Kind::kZeroExtend: DCHECK_EQ(change.from, Rep::Word32()); DCHECK_EQ(change.to, Rep::Word64()); return VisitChangeUint32ToUint64(node); case ChangeOp::Kind::kSignExtend: DCHECK_EQ(change.from, Rep::Word32()); DCHECK_EQ(change.to, Rep::Word64()); return VisitChangeInt32ToInt64(node); case ChangeOp::Kind::kTruncate: DCHECK_EQ(change.from, Rep::Word64()); DCHECK_EQ(change.to, Rep::Word32()); MarkAsWord32(node); return VisitTruncateInt64ToInt32(node); case ChangeOp::Kind::kBitcast: switch (multi(change.from, change.to)) { case multi(Rep::Word32(), Rep::Word64()): return VisitBitcastWord32ToWord64(node); case multi(Rep::Word32(), Rep::Float32()): return VisitBitcastInt32ToFloat32(node); case multi(Rep::Word64(), Rep::Float64()): return VisitBitcastInt64ToFloat64(node); case multi(Rep::Float32(), Rep::Word32()): return VisitBitcastFloat32ToInt32(node); case multi(Rep::Float64(), Rep::Word64()): return VisitBitcastFloat64ToInt64(node); default: UNREACHABLE(); } } UNREACHABLE(); } case Opcode::kTryChange: { const TryChangeOp& try_change = op.Cast<TryChangeOp>(); MarkAsRepresentation(try_change.to.machine_representation(), node); DCHECK(try_change.kind == TryChangeOp::Kind::kSignedFloatTruncateOverflowUndefined || try_change.kind == TryChangeOp::Kind::kUnsignedFloatTruncateOverflowUndefined); const bool is_signed = try_change.kind == TryChangeOp::Kind::kSignedFloatTruncateOverflowUndefined; switch (multi(try_change.from, try_change.to, is_signed)) { case multi(Rep::Float64(), Rep::Word64(), true): return VisitTryTruncateFloat64ToInt64(node); case multi(Rep::Float64(), Rep::Word64(), false): return VisitTryTruncateFloat64ToUint64(node); case multi(Rep::Float64(), Rep::Word32(), true): return VisitTryTruncateFloat64ToInt32(node); case multi(Rep::Float64(), Rep::Word32(), false): return VisitTryTruncateFloat64ToUint32(node); case multi(Rep::Float32(), Rep::Word64(), true): return VisitTryTruncateFloat32ToInt64(node); case multi(Rep::Float32(), Rep::Word64(), false): return VisitTryTruncateFloat32ToUint64(node); default: UNREACHABLE(); } UNREACHABLE(); } case Opcode::kConstant: { const ConstantOp& constant = op.Cast<ConstantOp>(); switch (constant.kind) { case ConstantOp::Kind::kWord32: case ConstantOp::Kind::kWord64: case ConstantOp::Kind::kSmi: case ConstantOp::Kind::kTaggedIndex: case ConstantOp::Kind::kExternal: break; case ConstantOp::Kind::kFloat32: MarkAsFloat32(node); break; case ConstantOp::Kind::kFloat64: MarkAsFloat64(node); break; case ConstantOp::Kind::kHeapObject: case ConstantOp::Kind::kTrustedHeapObject: MarkAsTagged(node); break; case ConstantOp::Kind::kCompressedHeapObject: MarkAsCompressed(node); break; case ConstantOp::Kind::kNumber: if (!IsSmiDouble(constant.number().get_scalar())) MarkAsTagged(node); break; case ConstantOp::Kind::kRelocatableWasmCall: case ConstantOp::Kind::kRelocatableWasmStubCall: case ConstantOp::Kind::kRelocatableWasmCanonicalSignatureId: case ConstantOp::Kind::kRelocatableWasmIndirectCallTarget: break; } VisitConstant(node); break; } case Opcode::kWordUnary: { const WordUnaryOp& unop = op.Cast<WordUnaryOp>(); if (unop.rep == WordRepresentation::Word32()) { MarkAsWord32(node); switch (unop.kind) { case WordUnaryOp::Kind::kReverseBytes: return VisitWord32ReverseBytes(node); case WordUnaryOp::Kind::kCountLeadingZeros: return VisitWord32Clz(node); case WordUnaryOp::Kind::kCountTrailingZeros: return VisitWord32Ctz(node); case WordUnaryOp::Kind::kPopCount: return VisitWord32Popcnt(node); case WordUnaryOp::Kind::kSignExtend8: return VisitSignExtendWord8ToInt32(node); case WordUnaryOp::Kind::kSignExtend16: return VisitSignExtendWord16ToInt32(node); } } else { DCHECK_EQ(unop.rep, WordRepresentation::Word64()); MarkAsWord64(node); switch (unop.kind) { case WordUnaryOp::Kind::kReverseBytes: return VisitWord64ReverseBytes(node); case WordUnaryOp::Kind::kCountLeadingZeros: return VisitWord64Clz(node); case WordUnaryOp::Kind::kCountTrailingZeros: return VisitWord64Ctz(node); case WordUnaryOp::Kind::kPopCount: return VisitWord64Popcnt(node); case WordUnaryOp::Kind::kSignExtend8: return VisitSignExtendWord8ToInt64(node); case WordUnaryOp::Kind::kSignExtend16: return VisitSignExtendWord16ToInt64(node); } } UNREACHABLE(); } case Opcode::kWordBinop: { const WordBinopOp& binop = op.Cast<WordBinopOp>(); if (binop.rep == WordRepresentation::Word32()) { MarkAsWord32(node); switch (binop.kind) { case WordBinopOp::Kind::kAdd: return VisitInt32Add(node); case WordBinopOp::Kind::kMul: return VisitInt32Mul(node); case WordBinopOp::Kind::kSignedMulOverflownBits: return VisitInt32MulHigh(node); case WordBinopOp::Kind::kUnsignedMulOverflownBits: return VisitUint32MulHigh(node); case WordBinopOp::Kind::kBitwiseAnd: return VisitWord32And(node); case WordBinopOp::Kind::kBitwiseOr: return VisitWord32Or(node); case WordBinopOp::Kind::kBitwiseXor: return VisitWord32Xor(node); case WordBinopOp::Kind::kSub: return VisitInt32Sub(node); case WordBinopOp::Kind::kSignedDiv: return VisitInt32Div(node); case WordBinopOp::Kind::kUnsignedDiv: return VisitUint32Div(node); case WordBinopOp::Kind::kSignedMod: return VisitInt32Mod(node); case WordBinopOp::Kind::kUnsignedMod: return VisitUint32Mod(node); } } else { DCHECK_EQ(binop.rep, WordRepresentation::Word64()); MarkAsWord64(node); switch (binop.kind) { case WordBinopOp::Kind::kAdd: return VisitInt64Add(node); case WordBinopOp::Kind::kMul: return VisitInt64Mul(node); case WordBinopOp::Kind::kSignedMulOverflownBits: return VisitInt64MulHigh(node); case WordBinopOp::Kind::kUnsignedMulOverflownBits: return VisitUint64MulHigh(node); case WordBinopOp::Kind::kBitwiseAnd: return VisitWord64And(node); case WordBinopOp::Kind::kBitwiseOr: return VisitWord64Or(node); case WordBinopOp::Kind::kBitwiseXor: return VisitWord64Xor(node); case WordBinopOp::Kind::kSub: return VisitInt64Sub(node); case WordBinopOp::Kind::kSignedDiv: return VisitInt64Div(node); case WordBinopOp::Kind::kUnsignedDiv: return VisitUint64Div(node); case WordBinopOp::Kind::kSignedMod: return VisitInt64Mod(node); case WordBinopOp::Kind::kUnsignedMod: return VisitUint64Mod(node); } } UNREACHABLE(); } case Opcode::kFloatUnary: { const auto& unop = op.Cast<FloatUnaryOp>(); if (unop.rep == Rep::Float32()) { MarkAsFloat32(node); switch (unop.kind) { case FloatUnaryOp::Kind::kAbs: return VisitFloat32Abs(node); case FloatUnaryOp::Kind::kNegate: return VisitFloat32Neg(node); case FloatUnaryOp::Kind::kRoundDown: return VisitFloat32RoundDown(node); case FloatUnaryOp::Kind::kRoundUp: return VisitFloat32RoundUp(node); case FloatUnaryOp::Kind::kRoundToZero: return VisitFloat32RoundTruncate(node); case FloatUnaryOp::Kind::kRoundTiesEven: return VisitFloat32RoundTiesEven(node); case FloatUnaryOp::Kind::kSqrt: return VisitFloat32Sqrt(node); // Those operations are only supported on 64 bit. case FloatUnaryOp::Kind::kSilenceNaN: case FloatUnaryOp::Kind::kLog: case FloatUnaryOp::Kind::kLog2: case FloatUnaryOp::Kind::kLog10: case FloatUnaryOp::Kind::kLog1p: case FloatUnaryOp::Kind::kCbrt: case FloatUnaryOp::Kind::kExp: case FloatUnaryOp::Kind::kExpm1: case FloatUnaryOp::Kind::kSin: case FloatUnaryOp::Kind::kCos: case FloatUnaryOp::Kind::kSinh: case FloatUnaryOp::Kind::kCosh: case FloatUnaryOp::Kind::kAcos: case FloatUnaryOp::Kind::kAsin: case FloatUnaryOp::Kind::kAsinh: case FloatUnaryOp::Kind::kAcosh: case FloatUnaryOp::Kind::kTan: case FloatUnaryOp::Kind::kTanh: case FloatUnaryOp::Kind::kAtan: case FloatUnaryOp::Kind::kAtanh: UNREACHABLE(); } } else { DCHECK_EQ(unop.rep, Rep::Float64()); MarkAsFloat64(node); switch (unop.kind) { case FloatUnaryOp::Kind::kAbs: return VisitFloat64Abs(node); case FloatUnaryOp::Kind::kNegate: return VisitFloat64Neg(node); case FloatUnaryOp::Kind::kSilenceNaN: return VisitFloat64SilenceNaN(node); case FloatUnaryOp::Kind::kRoundDown: return VisitFloat64RoundDown(node); case FloatUnaryOp::Kind::kRoundUp: return VisitFloat64RoundUp(node); case FloatUnaryOp::Kind::kRoundToZero: return VisitFloat64RoundTruncate(node); case FloatUnaryOp::Kind::kRoundTiesEven: return VisitFloat64RoundTiesEven(node); case FloatUnaryOp::Kind::kLog: return VisitFloat64Log(node); case FloatUnaryOp::Kind::kLog2: return VisitFloat64Log2(node); case FloatUnaryOp::Kind::kLog10: return VisitFloat64Log10(node); case FloatUnaryOp::Kind::kLog1p: return VisitFloat64Log1p(node); case FloatUnaryOp::Kind::kSqrt: return VisitFloat64Sqrt(node); case FloatUnaryOp::Kind::kCbrt: return VisitFloat64Cbrt(node); case FloatUnaryOp::Kind::kExp: return VisitFloat64Exp(node); case FloatUnaryOp::Kind::kExpm1: return VisitFloat64Expm1(node); case FloatUnaryOp::Kind::kSin: return VisitFloat64Sin(node); case FloatUnaryOp::Kind::kCos: return VisitFloat64Cos(node); case FloatUnaryOp::Kind::kSinh: return VisitFloat64Sinh(node); case FloatUnaryOp::Kind::kCosh: return VisitFloat64Cosh(node); case FloatUnaryOp::Kind::kAcos: return VisitFloat64Acos(node); case FloatUnaryOp::Kind::kAsin: return VisitFloat64Asin(node); case FloatUnaryOp::Kind::kAsinh: return VisitFloat64Asinh(node); case FloatUnaryOp::Kind::kAcosh: return VisitFloat64Acosh(node); case FloatUnaryOp::Kind::kTan: return VisitFloat64Tan(node); case FloatUnaryOp::Kind::kTanh: return VisitFloat64Tanh(node); case FloatUnaryOp::Kind::kAtan: return VisitFloat64Atan(node); case FloatUnaryOp::Kind::kAtanh: return VisitFloat64Atanh(node); } } UNREACHABLE(); } case Opcode::kFloatBinop: { const auto& binop = op.Cast<FloatBinopOp>(); if (binop.rep == Rep::Float32()) { MarkAsFloat32(node); switch (binop.kind) { case FloatBinopOp::Kind::kAdd: return VisitFloat32Add(node); case FloatBinopOp::Kind::kSub: return VisitFloat32Sub(node); case FloatBinopOp::Kind::kMul: return VisitFloat32Mul(node); case FloatBinopOp::Kind::kDiv: return VisitFloat32Div(node); case FloatBinopOp::Kind::kMin: return VisitFloat32Min(node); case FloatBinopOp::Kind::kMax: return VisitFloat32Max(node); case FloatBinopOp::Kind::kMod: case FloatBinopOp::Kind::kPower: case FloatBinopOp::Kind::kAtan2: UNREACHABLE(); } } else { DCHECK_EQ(binop.rep, Rep::Float64()); MarkAsFloat64(node); switch (binop.kind) { case FloatBinopOp::Kind::kAdd: return VisitFloat64Add(node); case FloatBinopOp::Kind::kSub: return VisitFloat64Sub(node); case FloatBinopOp::Kind::kMul: return VisitFloat64Mul(node); case FloatBinopOp::Kind::kDiv: return VisitFloat64Div(node); case FloatBinopOp::Kind::kMod: return VisitFloat64Mod(node); case FloatBinopOp::Kind::kMin: return VisitFloat64Min(node); case FloatBinopOp::Kind::kMax: return VisitFloat64Max(node); case FloatBinopOp::Kind::kPower: return VisitFloat64Pow(node); case FloatBinopOp::Kind::kAtan2: return VisitFloat64Atan2(node); } } UNREACHABLE(); } case Opcode::kOverflowCheckedBinop: { const auto& binop = op.Cast<OverflowCheckedBinopOp>(); if (binop.rep == WordRepresentation::Word32()) { MarkAsWord32(node); switch (binop.kind) { case OverflowCheckedBinopOp::Kind::kSignedAdd: return VisitInt32AddWithOverflow(node); case OverflowCheckedBinopOp::Kind::kSignedMul: return VisitInt32MulWithOverflow(node); case OverflowCheckedBinopOp::Kind::kSignedSub: return VisitInt32SubWithOverflow(node); } } else { DCHECK_EQ(binop.rep, WordRepresentation::Word64()); MarkAsWord64(node); switch (binop.kind) { case OverflowCheckedBinopOp::Kind::kSignedAdd: return VisitInt64AddWithOverflow(node); case OverflowCheckedBinopOp::Kind::kSignedMul: return VisitInt64MulWithOverflow(node); case OverflowCheckedBinopOp::Kind::kSignedSub: return VisitInt64SubWithOverflow(node); } } UNREACHABLE(); } case Opcode::kOverflowCheckedUnary: { const auto& unop = op.Cast<OverflowCheckedUnaryOp>(); if (unop.rep == WordRepresentation::Word32()) { MarkAsWord32(node); switch (unop.kind) { case OverflowCheckedUnaryOp::Kind::kAbs: return VisitInt32AbsWithOverflow(node); } } else { DCHECK_EQ(unop.rep, WordRepresentation::Word64()); MarkAsWord64(node); switch (unop.kind) { case OverflowCheckedUnaryOp::Kind::kAbs: return VisitInt64AbsWithOverflow(node); } } UNREACHABLE(); } case Opcode::kShift: { const auto& shift = op.Cast<ShiftOp>(); if (shift.rep == RegisterRepresentation::Word32()) { MarkAsWord32(node); switch (shift.kind) { case ShiftOp::Kind::kShiftRightArithmeticShiftOutZeros: case ShiftOp::Kind::kShiftRightArithmetic: return VisitWord32Sar(node); case ShiftOp::Kind::kShiftRightLogical: return VisitWord32Shr(node); case ShiftOp::Kind::kShiftLeft: return VisitWord32Shl(node); case ShiftOp::Kind::kRotateRight: return VisitWord32Ror(node); case ShiftOp::Kind::kRotateLeft: return VisitWord32Rol(node); } } else { DCHECK_EQ(shift.rep, RegisterRepresentation::Word64()); MarkAsWord64(node); switch (shift.kind) { case ShiftOp::Kind::kShiftRightArithmeticShiftOutZeros: case ShiftOp::Kind::kShiftRightArithmetic: return VisitWord64Sar(node); case ShiftOp::Kind::kShiftRightLogical: return VisitWord64Shr(node); case ShiftOp::Kind::kShiftLeft: return VisitWord64Shl(node); case ShiftOp::Kind::kRotateRight: return VisitWord64Ror(node); case ShiftOp::Kind::kRotateLeft: return VisitWord64Rol(node); } } UNREACHABLE(); } case Opcode::kCall: // Process the call at `DidntThrow`, when we know if exceptions are caught // or not. break; case Opcode::kDidntThrow: if (current_block_->begin() == node) { DCHECK_EQ(current_block_->PredecessorCount(), 1); DCHECK(current_block_->LastPredecessor() ->LastOperation(*this->turboshaft_graph()) .Is<CheckExceptionOp>()); // In this case, the Call has been generated at the `CheckException` // already. } else { VisitCall(op.Cast<DidntThrowOp>().throwing_operation()); } EmitIdentity(node); break; case Opcode::kFrameConstant: { const auto& constant = op.Cast<FrameConstantOp>(); using Kind = FrameConstantOp::Kind; OperandGenerator g(this); switch (constant.kind) { case Kind::kStackCheckOffset: Emit(kArchStackCheckOffset, g.DefineAsRegister(node)); break; case Kind::kFramePointer: Emit(kArchFramePointer, g.DefineAsRegister(node)); break; case Kind::kParentFramePointer: Emit(kArchParentFramePointer, g.DefineAsRegister(node)); break; } break; } case Opcode::kStackPointerGreaterThan: return VisitStackPointerGreaterThan(node); case Opcode::kComparison: { const ComparisonOp& comparison = op.Cast<ComparisonOp>(); using Kind = ComparisonOp::Kind; switch (multi(comparison.kind, comparison.rep)) { case multi(Kind::kEqual, Rep::Word32()): return VisitWord32Equal(node); case multi(Kind::kEqual, Rep::Word64()): return VisitWord64Equal(node); case multi(Kind::kEqual, Rep::Float32()): return VisitFloat32Equal(node); case multi(Kind::kEqual, Rep::Float64()): return VisitFloat64Equal(node); case multi(Kind::kEqual, Rep::Tagged()): if constexpr (Is64() && !COMPRESS_POINTERS_BOOL) { return VisitWord64Equal(node); } return VisitWord32Equal(node); case multi(Kind::kSignedLessThan, Rep::Word32()): return VisitInt32LessThan(node); case multi(Kind::kSignedLessThan, Rep::Word64()): return VisitInt64LessThan(node); case multi(Kind::kSignedLessThan, Rep::Float32()): return VisitFloat32LessThan(node); case multi(Kind::kSignedLessThan, Rep::Float64()): return VisitFloat64LessThan(node); case multi(Kind::kSignedLessThanOrEqual, Rep::Word32()): return VisitInt32LessThanOrEqual(node); case multi(Kind::kSignedLessThanOrEqual, Rep::Word64()): return VisitInt64LessThanOrEqual(node); case multi(Kind::kSignedLessThanOrEqual, Rep::Float32()): return VisitFloat32LessThanOrEqual(node); case multi(Kind::kSignedLessThanOrEqual, Rep::Float64()): return VisitFloat64LessThanOrEqual(node); case multi(Kind::kUnsignedLessThan, Rep::Word32()): return VisitUint32LessThan(node); case multi(Kind::kUnsignedLessThan, Rep::Word64()): return VisitUint64LessThan(node); case multi(Kind::kUnsignedLessThanOrEqual, Rep::Word32()): return VisitUint32LessThanOrEqual(node); case multi(Kind::kUnsignedLessThanOrEqual, Rep::Word64()): return VisitUint64LessThanOrEqual(node); default: UNREACHABLE(); } UNREACHABLE(); } case Opcode::kLoad: { const LoadOp& load = op.Cast<LoadOp>(); MachineType loaded_type = load.machine_type(); MarkAsRepresentation(loaded_type.representation(), node); if (load.kind.maybe_unaligned) { DCHECK(!load.kind.with_trap_handler); DCHECK(!load.kind.is_atomic); if (loaded_type.representation() == MachineRepresentation::kWord8 || InstructionSelector::AlignmentRequirements() .IsUnalignedLoadSupported(loaded_type.representation())) { return VisitLoad(node); } else { return VisitUnalignedLoad(node); } } else if (load.kind.is_atomic) { if (load.result_rep == Rep::Word32()) { return VisitWord32AtomicLoad(node); } else if (load.result_rep == Rep::Word64()) { return VisitWord64AtomicLoad(node); } else if (load.result_rep == Rep::Tagged()) { return kTaggedSize == 4 ? VisitWord32AtomicLoad(node) : VisitWord64AtomicLoad(node); } } else if (load.kind.with_trap_handler) { DCHECK(!load.kind.maybe_unaligned); return VisitProtectedLoad(node); } else { return VisitLoad(node); } UNREACHABLE(); } case Opcode::kStore: { const StoreOp& store = op.Cast<StoreOp>(); MachineRepresentation rep = store.stored_rep.ToMachineType().representation(); if (store.kind.maybe_unaligned) { DCHECK(!store.kind.with_trap_handler); DCHECK_EQ(store.write_barrier, WriteBarrierKind::kNoWriteBarrier); if (rep == MachineRepresentation::kWord8 || InstructionSelector::AlignmentRequirements() .IsUnalignedStoreSupported(rep)) { return VisitStore(node); } else { return VisitUnalignedStore(node); } } else if (store.kind.is_atomic) { if (store.stored_rep.SizeInBytes() == 8) { return VisitWord64AtomicStore(node); } else { DCHECK_LE(store.stored_rep.SizeInBytes(), 4); return VisitWord32AtomicStore(node); } } else if (store.kind.with_trap_handler) { DCHECK(!store.kind.maybe_unaligned); return VisitProtectedStore(node); } else { return VisitStore(node); } UNREACHABLE(); } case Opcode::kTaggedBitcast: { const TaggedBitcastOp& cast = op.Cast<TaggedBitcastOp>(); switch (multi(cast.from, cast.to)) { case multi(Rep::Tagged(), Rep::Word32()): MarkAsWord32(node); if constexpr (Is64()) { DCHECK_EQ(cast.kind, TaggedBitcastOp::Kind::kSmi); DCHECK(SmiValuesAre31Bits()); return VisitBitcastSmiToWord(node); } else { return VisitBitcastTaggedToWord(node); } case multi(Rep::Tagged(), Rep::Word64()): MarkAsWord64(node); return VisitBitcastTaggedToWord(node); case multi(Rep::Word32(), Rep::Tagged()): case multi(Rep::Word64(), Rep::Tagged()): if (cast.kind == TaggedBitcastOp::Kind::kSmi) { MarkAsRepresentation(MachineRepresentation::kTaggedSigned, node); return EmitIdentity(node); } else { MarkAsTagged(node); return VisitBitcastWordToTagged(node); } case multi(Rep::Compressed(), Rep::Word32()): MarkAsWord32(node); if (cast.kind == TaggedBitcastOp::Kind::kSmi) { return VisitBitcastSmiToWord(node); } else { return VisitBitcastTaggedToWord(node); } default: UNIMPLEMENTED(); } } case Opcode::kPhi: MarkAsRepresentation(op.Cast<PhiOp>().rep, node); return VisitPhi(node); case Opcode::kProjection: return VisitProjection(node); case Opcode::kDeoptimizeIf: return VisitDeoptimizeIf(node); #if V8_ENABLE_WEBASSEMBLY case Opcode::kTrapIf: return VisitTrapIf(node); case Opcode::kWasmFXArgBuffer: return VisitWasmFXArgBuffer(node); #endif // V8_ENABLE_WEBASSEMBLY case Opcode::kCatchBlockBegin: MarkAsTagged(node); return VisitIfException(node); case Opcode::kRetain: return VisitRetain(node); case Opcode::kOsrValue: MarkAsTagged(node); return VisitOsrValue(node); case Opcode::kStackSlot: return VisitStackSlot(node); case Opcode::kFrameState: // FrameState is covered as part of calls. UNREACHABLE(); case Opcode::kLoadRootRegister: return VisitLoadRootRegister(node); case Opcode::kAssumeMap: // AssumeMap is used as a hint for optimization phases but does not // produce any code. return; case Opcode::kDebugBreak: return VisitDebugBreak(node); case Opcode::kAbortCSADcheck: return VisitAbortCSADcheck(node); #ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT case Opcode::kSwitchSandboxMode: return VisitSwitchSandboxMode(node); #endif // V8_ENABLE_SANDBOX_HARDWARE_SUPPORT case Opcode::kSelect: { const SelectOp& select = op.Cast<SelectOp>(); // If there is a Select, then it should only be one that is supported by // the machine, and it should be meant to be implementation with cmove. DCHECK_EQ(select.implem, SelectOp::Implementation::kForceCMove); MarkAsRepresentation(select.rep, node); return VisitSelect(node); } case Opcode::kWord32PairBinop: { const Word32PairBinopOp& binop = op.Cast<Word32PairBinopOp>(); MarkAsWord32(node); MarkPairProjectionsAsWord32(node); switch (binop.kind) { case Word32PairBinopOp::Kind::kAdd: return VisitInt32PairAdd(node); case Word32PairBinopOp::Kind::kSub: return VisitInt32PairSub(node); case Word32PairBinopOp::Kind::kMul: return VisitInt32PairMul(node); case Word32PairBinopOp::Kind::kShiftLeft: return VisitWord32PairShl(node); case Word32PairBinopOp::Kind::kShiftRightLogical: return VisitWord32PairShr(node); case Word32PairBinopOp::Kind::kShiftRightArithmetic: return VisitWord32PairSar(node); } UNREACHABLE(); } case Opcode::kAtomicWord32Pair: { const AtomicWord32PairOp& atomic_op = op.Cast<AtomicWord32PairOp>(); if (atomic_op.kind != AtomicWord32PairOp::Kind::kStore) { MarkAsWord32(node); MarkPairProjectionsAsWord32(node); } switch (atomic_op.kind) { case AtomicWord32PairOp::Kind::kAdd: return VisitWord32AtomicPairAdd(node); case AtomicWord32PairOp::Kind::kAnd: return VisitWord32AtomicPairAnd(node); case AtomicWord32PairOp::Kind::kCompareExchange: return VisitWord32AtomicPairCompareExchange(node); case AtomicWord32PairOp::Kind::kExchange: return VisitWord32AtomicPairExchange(node); case AtomicWord32PairOp::Kind::kLoad: return VisitWord32AtomicPairLoad(node); case AtomicWord32PairOp::Kind::kOr: return VisitWord32AtomicPairOr(node); case AtomicWord32PairOp::Kind::kSub: return VisitWord32AtomicPairSub(node); case AtomicWord32PairOp::Kind::kXor: return VisitWord32AtomicPairXor(node); case AtomicWord32PairOp::Kind::kStore: return VisitWord32AtomicPairStore(node); } } case Opcode::kBitcastWord32PairToFloat64: return MarkAsFloat64(node), VisitBitcastWord32PairToFloat64(node); case Opcode::kAtomicRMW: { const AtomicRMWOp& atomic_op = op.Cast<AtomicRMWOp>(); MarkAsRepresentation(atomic_op.memory_rep.ToRegisterRepresentation(), node); if (atomic_op.in_out_rep == Rep::Word32()) { switch (atomic_op.bin_op) { case AtomicRMWOp::BinOp::kAdd: return VisitWord32AtomicAdd(node); case AtomicRMWOp::BinOp::kSub: return VisitWord32AtomicSub(node); case AtomicRMWOp::BinOp::kAnd: return VisitWord32AtomicAnd(node); case AtomicRMWOp::BinOp::kOr: return VisitWord32AtomicOr(node); case AtomicRMWOp::BinOp::kXor: return VisitWord32AtomicXor(node); case AtomicRMWOp::BinOp::kExchange: return VisitWord32AtomicExchange(node); case AtomicRMWOp::BinOp::kCompareExchange: return VisitWord32AtomicCompareExchange(node); } } else if (atomic_op.in_out_rep == Rep::Word64()) { switch (atomic_op.bin_op) { case AtomicRMWOp::BinOp::kAdd: return VisitWord64AtomicAdd(node); case AtomicRMWOp::BinOp::kSub: return VisitWord64AtomicSub(node); case AtomicRMWOp::BinOp::kAnd: return VisitWord64AtomicAnd(node); case AtomicRMWOp::BinOp::kOr: return VisitWord64AtomicOr(node); case AtomicRMWOp::BinOp::kXor: return VisitWord64AtomicXor(node); case AtomicRMWOp::BinOp::kExchange: return VisitWord64AtomicExchange(node); case AtomicRMWOp::BinOp::kCompareExchange: return VisitWord64AtomicCompareExchange(node); } } else { CHECK_EQ(atomic_op.in_out_rep, Rep::Tagged()); if (atomic_op.bin_op == AtomicRMWOp::BinOp::kExchange) { return VisitTaggedAtomicExchange(node); } CHECK_EQ(atomic_op.bin_op, AtomicRMWOp::BinOp::kCompareExchange); return VisitTaggedAtomicCompareExchange(node); } UNREACHABLE(); } case Opcode::kMemoryBarrier: return VisitMemoryBarrier(node); case Opcode::kPause: return VisitPause(node); case Opcode::kComment: return VisitComment(node); #ifdef V8_ENABLE_WEBASSEMBLY case Opcode::kSimd128Constant: { const Simd128ConstantOp& constant = op.Cast<Simd128ConstantOp>(); MarkAsSimd128(node); if (constant.IsZero()) return VisitS128Zero(node); return VisitS128Const(node); } case Opcode::kSimd128Unary: { const Simd128UnaryOp& unary = op.Cast<Simd128UnaryOp>(); MarkAsSimd128(node); switch (unary.kind) { #define VISIT_SIMD_UNARY(kind) \ case Simd128UnaryOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_128_UNARY_OPCODE(VISIT_SIMD_UNARY) #undef VISIT_SIMD_UNARY } } case Opcode::kSimd128Reduce: { const Simd128ReduceOp& reduce = op.Cast<Simd128ReduceOp>(); MarkAsSimd128(node); switch (reduce.kind) { case Simd128ReduceOp::Kind::kI8x16AddReduce: return VisitI8x16AddReduce(node); case Simd128ReduceOp::Kind::kI16x8AddReduce: return VisitI16x8AddReduce(node); case Simd128ReduceOp::Kind::kI32x4AddReduce: return VisitI32x4AddReduce(node); case Simd128ReduceOp::Kind::kI64x2AddReduce: return VisitI64x2AddReduce(node); case Simd128ReduceOp::Kind::kF32x4AddReduce: return VisitF32x4AddReduce(node); case Simd128ReduceOp::Kind::kF64x2AddReduce: return VisitF64x2AddReduce(node); } } case Opcode::kSimd128Binop: { const Simd128BinopOp& binop = op.Cast<Simd128BinopOp>(); MarkAsSimd128(node); switch (binop.kind) { #define VISIT_SIMD_BINOP(kind) \ case Simd128BinopOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_128_BINARY_OPCODE(VISIT_SIMD_BINOP) #undef VISIT_SIMD_BINOP } } case Opcode::kSimd128Shift: { const Simd128ShiftOp& shift = op.Cast<Simd128ShiftOp>(); MarkAsSimd128(node); switch (shift.kind) { #define VISIT_SIMD_SHIFT(kind) \ case Simd128ShiftOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_128_SHIFT_OPCODE(VISIT_SIMD_SHIFT) #undef VISIT_SIMD_SHIFT } } case Opcode::kSimd128Test: { const Simd128TestOp& test = op.Cast<Simd128TestOp>(); MarkAsWord32(node); switch (test.kind) { #define VISIT_SIMD_TEST(kind) \ case Simd128TestOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_128_TEST_OPCODE(VISIT_SIMD_TEST) #undef VISIT_SIMD_TEST } } case Opcode::kSimd128Splat: { const Simd128SplatOp& splat = op.Cast<Simd128SplatOp>(); MarkAsSimd128(node); switch (splat.kind) { #define VISIT_SIMD_SPLAT(kind) \ case Simd128SplatOp::Kind::k##kind: \ return Visit##kind##Splat(node); FOREACH_SIMD_128_SPLAT_OPCODE(VISIT_SIMD_SPLAT) #undef VISIT_SIMD_SPLAT } } case Opcode::kSimd128Shuffle: { MarkAsSimd128(node); const Simd128ShuffleOp& shuffle = op.Cast<Simd128ShuffleOp>(); switch (shuffle.kind) { case Simd128ShuffleOp::Kind::kI8x1: return VisitI8x1Shuffle(node); case Simd128ShuffleOp::Kind::kI8x2: return VisitI8x2Shuffle(node); case Simd128ShuffleOp::Kind::kI8x4: return VisitI8x4Shuffle(node); case Simd128ShuffleOp::Kind::kI8x8: return VisitI8x8Shuffle(node); case Simd128ShuffleOp::Kind::kI8x16: return VisitI8x16Shuffle(node); } } case Opcode::kSimd128ReplaceLane: { const Simd128ReplaceLaneOp& replace = op.Cast<Simd128ReplaceLaneOp>(); MarkAsSimd128(node); switch (replace.kind) { case Simd128ReplaceLaneOp::Kind::kI8x16: return VisitI8x16ReplaceLane(node); case Simd128ReplaceLaneOp::Kind::kI16x8: return VisitI16x8ReplaceLane(node); case Simd128ReplaceLaneOp::Kind::kI32x4: return VisitI32x4ReplaceLane(node); case Simd128ReplaceLaneOp::Kind::kI64x2: return VisitI64x2ReplaceLane(node); case Simd128ReplaceLaneOp::Kind::kF16x8: return VisitF16x8ReplaceLane(node); case Simd128ReplaceLaneOp::Kind::kF32x4: return VisitF32x4ReplaceLane(node); case Simd128ReplaceLaneOp::Kind::kF64x2: return VisitF64x2ReplaceLane(node); } } case Opcode::kSimd128MoveLane: { const Simd128MoveLaneOp& move = op.Cast<Simd128MoveLaneOp>(); MarkAsSimd128(node); switch (move.kind) { case Simd128MoveLaneOp::Kind::kI8x16: return VisitI8x16MoveLane(node); case Simd128MoveLaneOp::Kind::kI16x8: return VisitI16x8MoveLane(node); case Simd128MoveLaneOp::Kind::kI32x4: return VisitI32x4MoveLane(node); case Simd128MoveLaneOp::Kind::kI64x2: return VisitI64x2MoveLane(node); case Simd128MoveLaneOp::Kind::kF16x8: UNIMPLEMENTED(); case Simd128MoveLaneOp::Kind::kF32x4: return VisitF32x4MoveLane(node); case Simd128MoveLaneOp::Kind::kF64x2: return VisitF64x2MoveLane(node); } } case Opcode::kSimd128ExtractLane: { const Simd128ExtractLaneOp& extract = op.Cast<Simd128ExtractLaneOp>(); switch (extract.kind) { case Simd128ExtractLaneOp::Kind::kI8x16S: MarkAsWord32(node); return VisitI8x16ExtractLaneS(node); case Simd128ExtractLaneOp::Kind::kI8x16U: MarkAsWord32(node); return VisitI8x16ExtractLaneU(node); case Simd128ExtractLaneOp::Kind::kI16x8S: MarkAsWord32(node); return VisitI16x8ExtractLaneS(node); case Simd128ExtractLaneOp::Kind::kI16x8U: MarkAsWord32(node); return VisitI16x8ExtractLaneU(node); case Simd128ExtractLaneOp::Kind::kI32x4: MarkAsWord32(node); return VisitI32x4ExtractLane(node); case Simd128ExtractLaneOp::Kind::kI64x2: MarkAsWord64(node); return VisitI64x2ExtractLane(node); case Simd128ExtractLaneOp::Kind::kF16x8: MarkAsFloat32(node); return VisitF16x8ExtractLane(node); case Simd128ExtractLaneOp::Kind::kF32x4: MarkAsFloat32(node); return VisitF32x4ExtractLane(node); case Simd128ExtractLaneOp::Kind::kF64x2: MarkAsFloat64(node); return VisitF64x2ExtractLane(node); } } case Opcode::kSimd128LoadTransform: MarkAsSimd128(node); return VisitLoadTransform(node); case Opcode::kSimd128LaneMemory: { const Simd128LaneMemoryOp& memory = op.Cast<Simd128LaneMemoryOp>(); MarkAsSimd128(node); DCHECK_EQ(memory.offset, 0); if (memory.mode == Simd128LaneMemoryOp::Mode::kLoad) { return VisitLoadLane(node); } else { DCHECK_EQ(memory.mode, Simd128LaneMemoryOp::Mode::kStore); return VisitStoreLane(node); } } case Opcode::kSimd128Ternary: { const Simd128TernaryOp& ternary = op.Cast<Simd128TernaryOp>(); MarkAsSimd128(node); switch (ternary.kind) { #define VISIT_SIMD_TERNARY(kind) \ case Simd128TernaryOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_128_TERNARY_OPCODE(VISIT_SIMD_TERNARY) #undef VISIT_SIMD_TERNARY } } case Opcode::kSimd128LoadPairDeinterleave: { OptionalOpIndex projection0 = FindProjection(node, 0); DCHECK(projection0.valid()); MarkAsSimd128(projection0.value()); OptionalOpIndex projection1 = FindProjection(node, 1); DCHECK(projection1.valid()); MarkAsSimd128(projection1.value()); return VisitSimd128LoadPairDeinterleave(node); } // SIMD256 #if V8_ENABLE_WASM_SIMD256_REVEC case Opcode::kSimd256Constant: { const Simd256ConstantOp& constant = op.Cast<Simd256ConstantOp>(); MarkAsSimd256(node); if (constant.IsZero()) return VisitS256Zero(node); return VisitS256Const(node); } case Opcode::kSimd256Extract128Lane: { MarkAsSimd128(node); return VisitExtractF128(node); } case Opcode::kSimd256LoadTransform: { MarkAsSimd256(node); return VisitSimd256LoadTransform(node); } case Opcode::kSimd256Unary: { const Simd256UnaryOp& unary = op.Cast<Simd256UnaryOp>(); MarkAsSimd256(node); switch (unary.kind) { #define VISIT_SIMD_256_UNARY(kind) \ case Simd256UnaryOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_256_UNARY_OPCODE(VISIT_SIMD_256_UNARY) #undef VISIT_SIMD_256_UNARY } } case Opcode::kSimd256Binop: { const Simd256BinopOp& binop = op.Cast<Simd256BinopOp>(); MarkAsSimd256(node); switch (binop.kind) { #define VISIT_SIMD_BINOP(kind) \ case Simd256BinopOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_256_BINARY_OPCODE(VISIT_SIMD_BINOP) #undef VISIT_SIMD_BINOP } } case Opcode::kSimd256Shift: { const Simd256ShiftOp& shift = op.Cast<Simd256ShiftOp>(); MarkAsSimd256(node); switch (shift.kind) { #define VISIT_SIMD_SHIFT(kind) \ case Simd256ShiftOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_256_SHIFT_OPCODE(VISIT_SIMD_SHIFT) #undef VISIT_SIMD_SHIFT } } case Opcode::kSimd256Ternary: { const Simd256TernaryOp& ternary = op.Cast<Simd256TernaryOp>(); MarkAsSimd256(node); switch (ternary.kind) { #define VISIT_SIMD_256_TERNARY(kind) \ case Simd256TernaryOp::Kind::k##kind: \ return Visit##kind(node); FOREACH_SIMD_256_TERNARY_OPCODE(VISIT_SIMD_256_TERNARY) #undef VISIT_SIMD_256_UNARY } } case Opcode::kSimd256Splat: { const Simd256SplatOp& splat = op.Cast<Simd256SplatOp>(); MarkAsSimd256(node); switch (splat.kind) { #define VISIT_SIMD_SPLAT(kind) \ case Simd256SplatOp::Kind::k##kind: \ return Visit##kind##Splat(node); FOREACH_SIMD_256_SPLAT_OPCODE(VISIT_SIMD_SPLAT) #undef VISIT_SIMD_SPLAT } } #ifdef V8_TARGET_ARCH_X64 case Opcode::kSimd256Shufd: { MarkAsSimd256(node); return VisitSimd256Shufd(node); } case Opcode::kSimd256Shufps: { MarkAsSimd256(node); return VisitSimd256Shufps(node); } case Opcode::kSimd256Unpack: { MarkAsSimd256(node); return VisitSimd256Unpack(node); } case Opcode::kSimdPack128To256: { MarkAsSimd256(node); return VisitSimdPack128To256(node); } #endif // V8_TARGET_ARCH_X64 #endif // V8_ENABLE_WASM_SIMD256_REVEC case Opcode::kLoadStackPointer: return VisitLoadStackPointer(node); case Opcode::kSetStackPointer: this->frame_->set_invalidates_sp(); return VisitSetStackPointer(node); case Opcode::kMemoryCopy: return VisitMemoryCopy(node); case Opcode::kMemoryFill: return VisitMemoryFill(node); #endif // V8_ENABLE_WEBASSEMBLY #define UNREACHABLE_CASE(op) case Opcode::k##op: TURBOSHAFT_JS_OPERATION_LIST(UNREACHABLE_CASE) TURBOSHAFT_SIMPLIFIED_OPERATION_LIST(UNREACHABLE_CASE) TURBOSHAFT_WASM_OPERATION_LIST(UNREACHABLE_CASE) TURBOSHAFT_OTHER_OPERATION_LIST(UNREACHABLE_CASE) UNREACHABLE_CASE(PendingLoopPhi) UNREACHABLE_CASE(MakeTuple) UNREACHABLE_CASE(Dead) UNREACHABLE(); #undef UNREACHABLE_CASE } } #if V8_TARGET_ARCH_64_BIT bool InstructionSelector::ZeroExtendsWord32ToWord64(OpIndex node, int recursion_depth) { // To compute whether a Node sets its upper 32 bits to zero, there are three // cases. // 1. Phi node, with a computed result already available in phi_states_: // Read the value from phi_states_. // 2. Phi node, with no result available in phi_states_ yet: // Recursively check its inputs, and store the result in phi_states_. // 3. Anything else: // Call the architecture-specific ZeroExtendsWord32ToWord64NoPhis. // Limit recursion depth to avoid the possibility of stack overflow on very // large functions. const int kMaxRecursionDepth = 100; if (const PhiOp* phi = TryCast<PhiOp>(node)) { if (recursion_depth == 0) { if (phi_states_.empty()) { // This vector is lazily allocated because the majority of compilations // never use it. phi_states_ = ZoneVector<Upper32BitsState>( node_count_, Upper32BitsState::kNotYetChecked, zone()); } } Upper32BitsState current = phi_states_[node.id()]; if (current != Upper32BitsState::kNotYetChecked) { return current == Upper32BitsState::kZero; } // If further recursion is prevented, we can't make any assumptions about // the output of this phi node. if (recursion_depth >= kMaxRecursionDepth) { return false; } // Optimistically mark the current node as zero-extended so that we skip it // if we recursively visit it again due to a cycle. If this optimistic guess // is wrong, it will be corrected in MarkNodeAsNotZeroExtended. phi_states_[node.id()] = Upper32BitsState::kZero; for (int i = 0; i < phi->input_count; ++i) { OpIndex input = phi->input(i); if (!ZeroExtendsWord32ToWord64(input, recursion_depth + 1)) { MarkNodeAsNotZeroExtended(node); return false; } } return true; } return ZeroExtendsWord32ToWord64NoPhis(node); } void InstructionSelector::MarkNodeAsNotZeroExtended(OpIndex node) { if (phi_states_[node.id()] == Upper32BitsState::kMayBeNonZero) return; phi_states_[node.id()] = Upper32BitsState::kMayBeNonZero; ZoneVector<OpIndex> worklist(zone_); worklist.push_back(node); while (!worklist.empty()) { node = worklist.back(); worklist.pop_back(); // We may have previously marked some uses of this node as zero-extended, // but that optimistic guess was proven incorrect. for (OpIndex use : turboshaft_uses(node)) { if (phi_states_[use.id()] == Upper32BitsState::kZero) { phi_states_[use.id()] = Upper32BitsState::kMayBeNonZero; worklist.push_back(use); } } } } #endif // V8_TARGET_ARCH_64_BIT namespace { FrameStateDescriptor* GetFrameStateDescriptorInternal( Zone* zone, Graph* graph, const FrameStateOp& state) { const FrameStateInfo& state_info = state.data->frame_state_info; uint16_t parameters = state_info.parameter_count(); uint16_t max_arguments = state_info.max_arguments(); int locals = state_info.local_count(); int stack = state_info.stack_count(); FrameStateDescriptor* outer_state = nullptr; if (state.inlined) { outer_state = GetFrameStateDescriptorInternal( zone, graph, graph->Get(state.parent_frame_state()).template Cast<FrameStateOp>()); } #if V8_ENABLE_WEBASSEMBLY if (state_info.type() == FrameStateType::kJSToWasmBuiltinContinuation) { auto function_info = static_cast<const JSToWasmFrameStateFunctionInfo*>( state_info.function_info()); return zone->New<JSToWasmFrameStateDescriptor>( zone, state_info.type(), state_info.bailout_id(), state_info.state_combine(), parameters, locals, stack, state_info.shared_info(), outer_state, function_info->signature()); } #endif // V8_ENABLE_WEBASSEMBLY return zone->New<FrameStateDescriptor>( zone, state_info.type(), state_info.bailout_id(), state_info.state_combine(), parameters, max_arguments, locals, stack, state_info.shared_info(), state_info.bytecode_array(), outer_state, state_info.function_info()->wasm_liftoff_frame_size(), state_info.function_info()->wasm_function_index()); } } // namespace FrameStateDescriptor* InstructionSelector::GetFrameStateDescriptor( OpIndex node) { const FrameStateOp& state = this->turboshaft_graph()->Get(node).template Cast<FrameStateOp>(); auto* desc = GetFrameStateDescriptorInternal(instruction_zone(), this->turboshaft_graph(), state); *max_unoptimized_frame_height_ = std::max(*max_unoptimized_frame_height_, desc->total_conservative_frame_size_in_bytes() + (desc->max_arguments() * kSystemPointerSize)); return desc; } #if V8_ENABLE_WEBASSEMBLY // static void InstructionSelector::SwapShuffleInputs(SimdShuffleView& view) { view.SwapInputs(); } #endif // V8_ENABLE_WEBASSEMBLY InstructionSelector InstructionSelector::ForTurboshaft( Zone* zone, size_t node_count, Linkage* linkage, InstructionSequence* sequence, Graph* graph, Frame* frame, EnableSwitchJumpTable enable_switch_jump_table, TickCounter* tick_counter, JSHeapBroker* broker, size_t* max_unoptimized_frame_height, size_t* max_pushed_argument_count, SourcePositionMode source_position_mode, CpuFeatureSet features, EnableScheduling enable_scheduling, EnableRootsRelativeAddressing enable_roots_relative_addressing, EnableTraceTurboJson trace_turbo, EnsureDeterministicNan ensure_deterministic_nan) { return InstructionSelector( zone, node_count, linkage, sequence, graph, &graph->source_positions(), frame, enable_switch_jump_table, tick_counter, broker, max_unoptimized_frame_height, max_pushed_argument_count, source_position_mode, features, enable_scheduling, enable_roots_relative_addressing, trace_turbo, ensure_deterministic_nan); } #undef VISIT_UNSUPPORTED_OP } // namespace compiler } // namespace internal } // namespace v8