// 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. #ifndef V8_COMPILER_INSTRUCTION_SELECTOR_IMPL_H_ #define V8_COMPILER_INSTRUCTION_SELECTOR_IMPL_H_ #include "src/compiler/instruction-selector.h" #include "src/compiler/instruction.h" #include "src/compiler/linkage.h" #include "src/compiler/schedule.h" #include "src/macro-assembler.h" namespace v8 { namespace internal { namespace compiler { // Helper struct containing data about a table or lookup switch. struct SwitchInfo { int32_t min_value; // minimum value of {case_values} int32_t max_value; // maximum value of {case_values} size_t value_range; // |max_value - min_value| + 1 size_t case_count; // number of cases int32_t* case_values; // actual case values, unsorted BasicBlock** case_branches; // basic blocks corresponding to case values BasicBlock* default_branch; // default branch target }; // A helper class for the instruction selector that simplifies construction of // Operands. This class implements a base for architecture-specific helpers. class OperandGenerator { public: explicit OperandGenerator(InstructionSelector* selector) : selector_(selector) {} InstructionOperand NoOutput() { return InstructionOperand(); // Generates an invalid operand. } InstructionOperand DefineAsRegister(Node* node) { return Define(node, UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, GetVReg(node))); } InstructionOperand DefineSameAsFirst(Node* node) { return Define(node, UnallocatedOperand(UnallocatedOperand::SAME_AS_FIRST_INPUT, GetVReg(node))); } InstructionOperand DefineAsFixed(Node* node, Register reg) { return Define(node, UnallocatedOperand(UnallocatedOperand::FIXED_REGISTER, reg.code(), GetVReg(node))); } template <typename FPRegType> InstructionOperand DefineAsFixed(Node* node, FPRegType reg) { return Define(node, UnallocatedOperand(UnallocatedOperand::FIXED_FP_REGISTER, reg.code(), GetVReg(node))); } InstructionOperand DefineAsConstant(Node* node) { return DefineAsConstant(node, ToConstant(node)); } InstructionOperand DefineAsConstant(Node* node, Constant constant) { selector()->MarkAsDefined(node); int virtual_register = GetVReg(node); sequence()->AddConstant(virtual_register, constant); return ConstantOperand(virtual_register); } InstructionOperand DefineAsLocation(Node* node, LinkageLocation location) { return Define(node, ToUnallocatedOperand(location, GetVReg(node))); } InstructionOperand DefineAsDualLocation(Node* node, LinkageLocation primary_location, LinkageLocation secondary_location) { return Define(node, ToDualLocationUnallocatedOperand( primary_location, secondary_location, GetVReg(node))); } InstructionOperand Use(Node* node) { return Use(node, UnallocatedOperand(UnallocatedOperand::NONE, UnallocatedOperand::USED_AT_START, GetVReg(node))); } InstructionOperand UseAnyAtEnd(Node* node) { return Use(node, UnallocatedOperand(UnallocatedOperand::ANY, UnallocatedOperand::USED_AT_END, GetVReg(node))); } InstructionOperand UseAny(Node* node) { return Use(node, UnallocatedOperand(UnallocatedOperand::ANY, UnallocatedOperand::USED_AT_START, GetVReg(node))); } InstructionOperand UseRegister(Node* node) { return Use(node, UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, UnallocatedOperand::USED_AT_START, GetVReg(node))); } InstructionOperand UseUniqueSlot(Node* node) { return Use(node, UnallocatedOperand(UnallocatedOperand::MUST_HAVE_SLOT, GetVReg(node))); } // Use register or operand for the node. If a register is chosen, it won't // alias any temporary or output registers. InstructionOperand UseUnique(Node* node) { return Use(node, UnallocatedOperand(UnallocatedOperand::NONE, GetVReg(node))); } // Use a unique register for the node that does not alias any temporary or // output registers. InstructionOperand UseUniqueRegister(Node* node) { return Use(node, UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, GetVReg(node))); } InstructionOperand UseFixed(Node* node, Register reg) { return Use(node, UnallocatedOperand(UnallocatedOperand::FIXED_REGISTER, reg.code(), GetVReg(node))); } template <typename FPRegType> InstructionOperand UseFixed(Node* node, FPRegType reg) { return Use(node, UnallocatedOperand(UnallocatedOperand::FIXED_FP_REGISTER, reg.code(), GetVReg(node))); } InstructionOperand UseExplicit(LinkageLocation location) { MachineRepresentation rep = InstructionSequence::DefaultRepresentation(); if (location.IsRegister()) { return ExplicitOperand(LocationOperand::REGISTER, rep, location.AsRegister()); } else { return ExplicitOperand(LocationOperand::STACK_SLOT, rep, location.GetLocation()); } } InstructionOperand UseImmediate(int immediate) { return sequence()->AddImmediate(Constant(immediate)); } InstructionOperand UseImmediate(Node* node) { return sequence()->AddImmediate(ToConstant(node)); } InstructionOperand UseNegatedImmediate(Node* node) { return sequence()->AddImmediate(ToNegatedConstant(node)); } InstructionOperand UseLocation(Node* node, LinkageLocation location) { return Use(node, ToUnallocatedOperand(location, GetVReg(node))); } // Used to force gap moves from the from_location to the to_location // immediately before an instruction. InstructionOperand UsePointerLocation(LinkageLocation to_location, LinkageLocation from_location) { UnallocatedOperand casted_from_operand = UnallocatedOperand::cast(TempLocation(from_location)); selector_->Emit(kArchNop, casted_from_operand); return ToUnallocatedOperand(to_location, casted_from_operand.virtual_register()); } InstructionOperand TempRegister() { return UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, UnallocatedOperand::USED_AT_START, sequence()->NextVirtualRegister()); } int AllocateVirtualRegister() { return sequence()->NextVirtualRegister(); } InstructionOperand DefineSameAsFirstForVreg(int vreg) { return UnallocatedOperand(UnallocatedOperand::SAME_AS_FIRST_INPUT, vreg); } InstructionOperand DefineAsRegistertForVreg(int vreg) { return UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg); } InstructionOperand UseRegisterForVreg(int vreg) { return UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, UnallocatedOperand::USED_AT_START, vreg); } InstructionOperand TempDoubleRegister() { UnallocatedOperand op = UnallocatedOperand( UnallocatedOperand::MUST_HAVE_REGISTER, UnallocatedOperand::USED_AT_START, sequence()->NextVirtualRegister()); sequence()->MarkAsRepresentation(MachineRepresentation::kFloat64, op.virtual_register()); return op; } InstructionOperand TempRegister(Register reg) { return UnallocatedOperand(UnallocatedOperand::FIXED_REGISTER, reg.code(), InstructionOperand::kInvalidVirtualRegister); } InstructionOperand TempImmediate(int32_t imm) { return sequence()->AddImmediate(Constant(imm)); } InstructionOperand TempLocation(LinkageLocation location) { return ToUnallocatedOperand(location, sequence()->NextVirtualRegister()); } InstructionOperand Label(BasicBlock* block) { return sequence()->AddImmediate( Constant(RpoNumber::FromInt(block->rpo_number()))); } protected: InstructionSelector* selector() const { return selector_; } InstructionSequence* sequence() const { return selector()->sequence(); } Zone* zone() const { return selector()->instruction_zone(); } private: int GetVReg(Node* node) const { return selector_->GetVirtualRegister(node); } static Constant ToConstant(const Node* node) { switch (node->opcode()) { case IrOpcode::kInt32Constant: return Constant(OpParameter<int32_t>(node)); case IrOpcode::kInt64Constant: return Constant(OpParameter<int64_t>(node)); case IrOpcode::kFloat32Constant: return Constant(OpParameter<float>(node)); case IrOpcode::kRelocatableInt32Constant: case IrOpcode::kRelocatableInt64Constant: return Constant(OpParameter<RelocatablePtrConstantInfo>(node)); case IrOpcode::kFloat64Constant: case IrOpcode::kNumberConstant: return Constant(OpParameter<double>(node)); case IrOpcode::kExternalConstant: case IrOpcode::kComment: return Constant(OpParameter<ExternalReference>(node)); case IrOpcode::kHeapConstant: return Constant(OpParameter<Handle<HeapObject>>(node)); default: break; } UNREACHABLE(); return Constant(static_cast<int32_t>(0)); } static Constant ToNegatedConstant(const Node* node) { switch (node->opcode()) { case IrOpcode::kInt32Constant: return Constant(-OpParameter<int32_t>(node)); case IrOpcode::kInt64Constant: return Constant(-OpParameter<int64_t>(node)); default: break; } UNREACHABLE(); return Constant(static_cast<int32_t>(0)); } UnallocatedOperand Define(Node* node, UnallocatedOperand operand) { DCHECK_NOT_NULL(node); DCHECK_EQ(operand.virtual_register(), GetVReg(node)); selector()->MarkAsDefined(node); return operand; } UnallocatedOperand Use(Node* node, UnallocatedOperand operand) { DCHECK_NOT_NULL(node); DCHECK_EQ(operand.virtual_register(), GetVReg(node)); selector()->MarkAsUsed(node); return operand; } UnallocatedOperand ToDualLocationUnallocatedOperand( LinkageLocation primary_location, LinkageLocation secondary_location, int virtual_register) { // We only support the primary location being a register and the secondary // one a slot. DCHECK(primary_location.IsRegister() && secondary_location.IsCalleeFrameSlot()); int reg_id = primary_location.AsRegister(); int slot_id = secondary_location.AsCalleeFrameSlot(); return UnallocatedOperand(reg_id, slot_id, virtual_register); } UnallocatedOperand ToUnallocatedOperand(LinkageLocation location, int virtual_register) { if (location.IsAnyRegister()) { // any machine register. return UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, virtual_register); } if (location.IsCallerFrameSlot()) { // a location on the caller frame. return UnallocatedOperand(UnallocatedOperand::FIXED_SLOT, location.AsCallerFrameSlot(), virtual_register); } if (location.IsCalleeFrameSlot()) { // a spill location on this (callee) frame. return UnallocatedOperand(UnallocatedOperand::FIXED_SLOT, location.AsCalleeFrameSlot(), virtual_register); } // a fixed register. if (IsFloatingPoint(location.GetType().representation())) { return UnallocatedOperand(UnallocatedOperand::FIXED_FP_REGISTER, location.AsRegister(), virtual_register); } return UnallocatedOperand(UnallocatedOperand::FIXED_REGISTER, location.AsRegister(), virtual_register); } InstructionSelector* selector_; }; // The flags continuation is a way to combine a branch or a materialization // of a boolean value with an instruction that sets the flags register. // The whole instruction is treated as a unit by the register allocator, and // thus no spills or moves can be introduced between the flags-setting // instruction and the branch or set it should be combined with. class FlagsContinuation final { public: FlagsContinuation() : mode_(kFlags_none) {} // Creates a new flags continuation from the given condition and true/false // blocks. FlagsContinuation(FlagsCondition condition, BasicBlock* true_block, BasicBlock* false_block) : mode_(kFlags_branch), condition_(condition), true_block_(true_block), false_block_(false_block) { DCHECK_NOT_NULL(true_block); DCHECK_NOT_NULL(false_block); } // Creates a new flags continuation for an eager deoptimization exit. static FlagsContinuation ForDeoptimize(FlagsCondition condition, DeoptimizeKind kind, DeoptimizeReason reason, Node* frame_state) { return FlagsContinuation(condition, kind, reason, frame_state); } // Creates a new flags continuation for a boolean value. static FlagsContinuation ForSet(FlagsCondition condition, Node* result) { return FlagsContinuation(condition, result); } // Creates a new flags continuation for a wasm trap. static FlagsContinuation ForTrap(FlagsCondition condition, Runtime::FunctionId trap_id, Node* result) { return FlagsContinuation(condition, trap_id, result); } bool IsNone() const { return mode_ == kFlags_none; } bool IsBranch() const { return mode_ == kFlags_branch; } bool IsDeoptimize() const { return mode_ == kFlags_deoptimize; } bool IsSet() const { return mode_ == kFlags_set; } bool IsTrap() const { return mode_ == kFlags_trap; } FlagsCondition condition() const { DCHECK(!IsNone()); return condition_; } DeoptimizeKind kind() const { DCHECK(IsDeoptimize()); return kind_; } DeoptimizeReason reason() const { DCHECK(IsDeoptimize()); return reason_; } Node* frame_state() const { DCHECK(IsDeoptimize()); return frame_state_or_result_; } Node* result() const { DCHECK(IsSet()); return frame_state_or_result_; } Runtime::FunctionId trap_id() const { DCHECK(IsTrap()); return trap_id_; } BasicBlock* true_block() const { DCHECK(IsBranch()); return true_block_; } BasicBlock* false_block() const { DCHECK(IsBranch()); return false_block_; } void Negate() { DCHECK(!IsNone()); condition_ = NegateFlagsCondition(condition_); } void Commute() { DCHECK(!IsNone()); condition_ = CommuteFlagsCondition(condition_); } void Overwrite(FlagsCondition condition) { condition_ = condition; } void OverwriteAndNegateIfEqual(FlagsCondition condition) { DCHECK(condition_ == kEqual || condition_ == kNotEqual); bool negate = condition_ == kEqual; condition_ = condition; if (negate) Negate(); } void OverwriteUnsignedIfSigned() { switch (condition_) { case kSignedLessThan: condition_ = kUnsignedLessThan; break; case kSignedLessThanOrEqual: condition_ = kUnsignedLessThanOrEqual; break; case kSignedGreaterThan: condition_ = kUnsignedGreaterThan; break; case kSignedGreaterThanOrEqual: condition_ = kUnsignedGreaterThanOrEqual; break; default: break; } } // Encodes this flags continuation into the given opcode. InstructionCode Encode(InstructionCode opcode) { opcode |= FlagsModeField::encode(mode_); if (mode_ != kFlags_none) { opcode |= FlagsConditionField::encode(condition_); } return opcode; } private: FlagsContinuation(FlagsCondition condition, DeoptimizeKind kind, DeoptimizeReason reason, Node* frame_state) : mode_(kFlags_deoptimize), condition_(condition), kind_(kind), reason_(reason), frame_state_or_result_(frame_state) { DCHECK_NOT_NULL(frame_state); } FlagsContinuation(FlagsCondition condition, Node* result) : mode_(kFlags_set), condition_(condition), frame_state_or_result_(result) { DCHECK_NOT_NULL(result); } FlagsContinuation(FlagsCondition condition, Runtime::FunctionId trap_id, Node* result) : mode_(kFlags_trap), condition_(condition), frame_state_or_result_(result), trap_id_(trap_id) { DCHECK_NOT_NULL(result); } FlagsMode const mode_; FlagsCondition condition_; DeoptimizeKind kind_; // Only valid if mode_ == kFlags_deoptimize DeoptimizeReason reason_; // Only valid if mode_ == kFlags_deoptimize Node* frame_state_or_result_; // Only valid if mode_ == kFlags_deoptimize // or mode_ == kFlags_set. BasicBlock* true_block_; // Only valid if mode_ == kFlags_branch. BasicBlock* false_block_; // Only valid if mode_ == kFlags_branch. Runtime::FunctionId trap_id_; // Only valid if mode_ == kFlags_trap. }; } // namespace compiler } // namespace internal } // namespace v8 #endif // V8_COMPILER_INSTRUCTION_SELECTOR_IMPL_H_