Jit/lir/operand.h (450 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #pragma once #include "Jit/lir/x86_64.h" #include "Jit/log.h" #include <cstdint> #include <memory> #include <ostream> #include <tuple> #include <unordered_set> #include <variant> // This file defines operand classes in LIR. // OperandBase class is the base of the two types of operands: // - Operand: a normal operand that has type, size, and value, which // is used for instruction outputs and immediate input operand. // - LinkedOperand: this type of operand can only be input of an // instruction, which links to an output operand in a different // instruction, representing a def-use relationship. namespace jit { namespace lir { class BasicBlock; class Instruction; class OperandBase; class Operand; class LinkedOperand; class MemoryIndirect; // base class of operand // defines the interface that all the operands must have. class OperandBase { public: explicit OperandBase(Instruction* parent) : parent_instr_(parent) {} OperandBase(const OperandBase& ob) : parent_instr_(ob.parent_instr_), last_use_(ob.last_use_) {} virtual ~OperandBase() {} /* operand types: * - None: the operand is not used. * - Vreg: the operand is in a virtual register (not yet * allocated to a physical location); * - Reg: the operand is allocated to a physical register; * - Stack: the operand is allocated to a memory stack slot; * - Mem: the operand is allocated to a memory address; * - Ind: the operand is a memory indirect reference * - Imm: the operand is an immediate value; * - Lbl: the operand refers to a basic block. */ #define FOREACH_OPERAND_TYPE(X) \ X(None) \ X(Vreg) \ X(Reg) \ X(Stack) \ X(Mem) \ X(Ind) \ X(Imm) \ X(Label) enum Type { #define OPERAND_DECL_TYPE(v, ...) k##v, FOREACH_OPERAND_TYPE(OPERAND_DECL_TYPE) #undef OPERAND_DECL_TYPE }; #define OPERAND_DECL_TYPE_TEST(v, ...) \ bool is##v() const { \ return type() == Type::k##v; \ } FOREACH_OPERAND_TYPE(OPERAND_DECL_TYPE_TEST) #undef OPERAND_DECL_TYPE_TEST virtual uint64_t getConstant() const = 0; virtual double getFPConstant() const = 0; virtual int getPhyRegister() const = 0; virtual int getStackSlot() const = 0; virtual int getPhyRegOrStackSlot() const = 0; virtual void* getMemoryAddress() const = 0; virtual MemoryIndirect* getMemoryIndirect() const = 0; virtual BasicBlock* getBasicBlock() const = 0; // get the def operand of the current operand // if the current operand is a def (of type Operand), return itself. virtual Operand* getDefine() = 0; virtual const Operand* getDefine() const = 0; #define FOREACH_OPERAND_DATA_TYPE(X) \ X(8bit) \ X(16bit) \ X(32bit) \ X(64bit) \ X(Double) \ X(Object) enum DataType : unsigned char { #define DECL_DATA_TYPE_ENUM(s, ...) k##s, FOREACH_OPERAND_DATA_TYPE(DECL_DATA_TYPE_ENUM) #undef DECL_DATA_TYPE_ENUM }; virtual DataType dataType() const = 0; int sizeInBits() const { auto s = dataType(); switch (s) { case k8bit: return 8; case k16bit: return 16; case k32bit: return 32; case k64bit: case kDouble: case kObject: return 64; default: Py_UNREACHABLE(); } } const char* getSizeName() const { switch (dataType()) { #define DATA_TYPE_NAME_CASE(s, ...) \ case k##s: \ return #s; FOREACH_OPERAND_DATA_TYPE(DATA_TYPE_NAME_CASE) #undef DATA_TYPE_NAME_CASE } Py_UNREACHABLE(); } virtual Type type() const = 0; virtual bool isLinked() const = 0; Instruction* instr() { return parent_instr_; } const Instruction* instr() const { return parent_instr_; } void releaseFromInstr() { parent_instr_ = nullptr; } void assignToInstr(Instruction* instr) { parent_instr_ = instr; } bool isFp() const { return dataType() == kDouble; } bool isXmm() const { return PhyLocation(getPhyRegister()).is_fp_register(); } bool isLastUse() const { return last_use_; } void setLastUse() { last_use_ = true; } private: Instruction* parent_instr_; bool last_use_{false}; }; // memory reference: [base_reg + index_reg * (2^index_multiplier) + offset] class MemoryIndirect { public: explicit MemoryIndirect(Instruction* parent) : parent_(parent) {} void setMemoryIndirect(PhyLocation base, int32_t offset = 0) { setMemoryIndirect(base, PhyLocation::REG_INVALID, 0, offset); } void setMemoryIndirect( PhyLocation base, PhyLocation index_reg, uint8_t multipler) { setMemoryIndirect(base, index_reg, multipler, 0); } void setMemoryIndirect(Instruction* base, int32_t offset) { setMemoryIndirect(base, nullptr, 0, offset); } void setMemoryIndirect( std::variant<Instruction*, PhyLocation> base, std::variant<Instruction*, PhyLocation> index, uint8_t multipler, int32_t offset) { setBaseIndex(base_reg_, base); setBaseIndex(index_reg_, index); multipler_ = multipler; offset_ = offset; } OperandBase* getBaseRegOperand() { return base_reg_.get(); } OperandBase* getIndexRegOperand() { return index_reg_.get(); } OperandBase* getBaseRegOperand() const { return base_reg_.get(); } OperandBase* getIndexRegOperand() const { return index_reg_.get(); } uint8_t getMultipiler() const { return multipler_; } int32_t getOffset() const { return offset_; } private: Instruction* parent_{nullptr}; std::unique_ptr<OperandBase> base_reg_; std::unique_ptr<OperandBase> index_reg_; uint8_t multipler_{0}; int32_t offset_{0}; void setBaseIndex( std::unique_ptr<OperandBase>& base_index_opnd, Instruction* base_index); void setBaseIndex( std::unique_ptr<OperandBase>& base_index_opnd, PhyLocation base_index); void setBaseIndex( std::unique_ptr<OperandBase>& base_index_opnd, std::variant<Instruction*, PhyLocation> base_index); }; // operand class class Operand : public OperandBase { public: explicit Operand(Instruction* parent) : OperandBase(parent) {} // Only copies simple fields (type and data type) from operand. // The value_ field is not copied. Operand(Instruction* parent, Operand* operand) : OperandBase(parent), type_(operand->type_), data_type_(operand->data_type_) {} virtual ~Operand() {} Operand(Instruction* parent, DataType data_type, Type type, uint64_t data) : OperandBase(parent), type_(type), data_type_(data_type) { value_ = data; } Operand(Instruction* parent, Type type, double data) : OperandBase(parent), type_(type), data_type_(kDouble) { value_ = bit_cast<uint64_t>(data); } uint64_t getConstant() const override { return std::get<uint64_t>(value_); } double getFPConstant() const override { auto value = std::get<uint64_t>(value_); return bit_cast<double>(value); } void setConstant(uint64_t n, DataType data_type = k64bit) { type_ = kImm; value_ = n; data_type_ = data_type; } void setFPConstant(double n) { type_ = kImm; data_type_ = kDouble; value_ = bit_cast<uint64_t>(n); } int getPhyRegister() const override { JIT_DCHECK( type_ == kReg, "Unable to get physical register from the operand."); return std::get<int>(value_); } void setPhyRegister(int reg) { type_ = kReg; value_ = reg; } int getStackSlot() const override { JIT_DCHECK(type_ == kStack, "Unable to get a memory stack slot."); return std::get<int>(value_); } void setStackSlot(int slot) { type_ = kStack; value_ = slot; } void setPhyRegOrStackSlot(int loc) { if (loc < 0) { setStackSlot(loc); } else { setPhyRegister(loc); } } int getPhyRegOrStackSlot() const override { switch (type_) { case kReg: return getPhyRegister(); case kStack: return getStackSlot(); default: JIT_DCHECK( false, "Unable to get a physical register or a memory stack slot from the " "operand"); break; } return -1; } void* getMemoryAddress() const override { JIT_DCHECK(type_ == kMem, "Unable to get a memory address."); return std::get<void*>(value_); } void setMemoryAddress(void* addr) { type_ = kMem; value_ = addr; } MemoryIndirect* getMemoryIndirect() const override { JIT_DCHECK(type_ == kInd, "Unable to get a memory indirect."); return std::get<std::unique_ptr<MemoryIndirect>>(value_).get(); } template <typename... Args> void setMemoryIndirect(Args&&... args) { type_ = kInd; auto ind = std::make_unique<MemoryIndirect>(instr()); ind->setMemoryIndirect(std::forward<Args>(args)...); value_ = std::move(ind); } void setVirtualRegister() { type_ = kVreg; } BasicBlock* getBasicBlock() const override { JIT_DCHECK(type_ == kLabel, "Unable to get a basic block address."); return std::get<BasicBlock*>(value_); } const Operand* getDefine() const override { return this; } Operand* getDefine() override { return this; } void setBasicBlock(BasicBlock* block) { type_ = kLabel; data_type_ = kObject; value_ = block; } DataType dataType() const override { return data_type_; } void setDataType(DataType data_type) { data_type_ = data_type; } Type type() const override { return type_; } void setNone() { type_ = kNone; } bool isLinked() const override { return false; } int numUses() const { return uses_.size(); } void addUse(LinkedOperand* use); void removeUse(LinkedOperand* use); private: Type type_{kNone}; DataType data_type_{kObject}; std::variant< uint64_t, int, void*, BasicBlock*, std::unique_ptr<MemoryIndirect>> value_; std::unordered_set<LinkedOperand*> uses_; }; // Linked operand // This type of operand is essentially a pointer to the instruction. // The operand takes the value of the output of the instruction. class LinkedOperand : public OperandBase { public: LinkedOperand(Instruction* parent, Instruction* def); virtual ~LinkedOperand() {} bool isLinked() const override { return true; } Operand* getLinkedOperand() { return def_opnd_; } const Operand* getLinkedOperand() const { return def_opnd_; } Instruction* getLinkedInstr() { return def_opnd_->instr(); } const Instruction* getLinkedInstr() const { return def_opnd_->instr(); } void setLinkedInstr(Instruction* def); uint64_t getConstant() const override { return def_opnd_->getConstant(); } double getFPConstant() const override { return def_opnd_->getFPConstant(); } int getPhyRegister() const override { return def_opnd_->getPhyRegister(); } int getStackSlot() const override { return def_opnd_->getStackSlot(); } int getPhyRegOrStackSlot() const override { return def_opnd_->getPhyRegOrStackSlot(); } void* getMemoryAddress() const override { return def_opnd_->getMemoryAddress(); } MemoryIndirect* getMemoryIndirect() const override { return def_opnd_->getMemoryIndirect(); } BasicBlock* getBasicBlock() const override { return def_opnd_->getBasicBlock(); } Operand* getDefine() override { return def_opnd_; } const Operand* getDefine() const override { return def_opnd_; } DataType dataType() const override { return def_opnd_->dataType(); } Type type() const override { return def_opnd_->type(); } private: friend class Operand; Operand* def_opnd_; }; // OperandArg reqresents different operand data types, and is used as // arguments to BasicBlock::allocateInstr* instructions. The latter // will create the operands accordingly for the instructions after // allocating them. template <typename Type, bool Output> struct OperandArg { explicit OperandArg(Type v, OperandBase::DataType dt = OperandBase::kObject) : value(v), data_type(dt) {} Type value; OperandBase::DataType data_type{OperandBase::kObject}; static constexpr bool is_output = Output; }; template <bool Output> struct OperandArg<MemoryIndirect, Output> { using Reg = std::variant<Instruction*, PhyLocation>; explicit OperandArg(Reg b, OperandBase::DataType dt = OperandBase::kObject) : base(b), data_type(dt) {} explicit OperandArg( Reg b, int32_t off, OperandBase::DataType dt = OperandBase::kObject) : base(b), offset(off), data_type(dt) {} OperandArg(Reg b, Reg i, OperandBase::DataType dt = OperandBase::kObject) : base(b), index(i), data_type(dt) {} OperandArg( Reg b, Reg i, int32_t off, OperandBase::DataType dt = OperandBase::kObject) : base(b), index(i), offset(off), data_type(dt) {} OperandArg( Reg b, Reg i, uint8_t m, int32_t off, OperandBase::DataType dt = OperandBase::kObject) : base(b), index(i), multiplier(m), offset(off), data_type(dt) {} Reg base{PhyLocation::REG_INVALID}; Reg index{PhyLocation::REG_INVALID}; uint8_t multiplier{0}; int32_t offset{0}; OperandBase::DataType data_type{OperandBase::kObject}; static constexpr bool is_output = Output; }; template <> struct OperandArg<void*, true> { OperandArg(const OperandBase::DataType& dt = OperandBase::kObject) : data_type(dt) {} OperandBase::DataType data_type{OperandBase::kObject}; static constexpr bool is_output = true; }; #define DECLARE_TYPE_ARG(__T, __V, __O) using __T = OperandArg<__V, __O>; DECLARE_TYPE_ARG(PhyReg, PhyLocation, false) DECLARE_TYPE_ARG(Imm, uint64_t, false) DECLARE_TYPE_ARG(FPImm, double, false) DECLARE_TYPE_ARG(Stk, PhyLocation, false) DECLARE_TYPE_ARG(PhyRegStack, PhyLocation, false) DECLARE_TYPE_ARG(Lbl, BasicBlock*, false) DECLARE_TYPE_ARG(VReg, Instruction*, false) DECLARE_TYPE_ARG(Ind, MemoryIndirect, false) DECLARE_TYPE_ARG(OutPhyReg, PhyLocation, true) DECLARE_TYPE_ARG(OutImm, uint64_t, true) DECLARE_TYPE_ARG(OutFPImm, double, true) DECLARE_TYPE_ARG(OutStk, PhyLocation, true) DECLARE_TYPE_ARG(OutPhyRegStack, PhyLocation, true) DECLARE_TYPE_ARG(OutLbl, BasicBlock*, true) DECLARE_TYPE_ARG(OutDbl, double, true); DECLARE_TYPE_ARG(OutInd, MemoryIndirect, true); DECLARE_TYPE_ARG(OutVReg, void*, true); } // namespace lir } // namespace jit