Jit/lir/instruction.h (490 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#pragma once
#include "Jit/lir/operand.h"
#include "Jit/util.h"
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace jit {
namespace hir {
class Instr;
}
namespace lir {
class BasicBlock;
enum class FlagEffects {
kNone,
kSet,
kInvalidate,
};
#define FOREACH_INSTR_TYPE(X) \
/* name, <inputs live across> <flag effects>, <opnd_size_type>, <out use \
* reg>, <in use reg>, <is essential> */ \
/* Bind is not used to generate any machine code. Its sole */ \
/* purpose is to associate a physical register with a predefined */ \
/* value to virtual register for register allocator. */ \
X(Bind) \
X(Nop) \
X(Call, false, FlagEffects::kInvalidate, kAlways64, 1, {}, 1) \
X(VectorCall, true, FlagEffects::kInvalidate, kAlways64, 1, {1}, 1) \
X(Guard, true, FlagEffects::kInvalidate, kDefault, 1, {0, 0, 1, 1}, 1) \
X(DeoptPatchpoint, true, FlagEffects::kInvalidate, kDefault, 0, {1, 1}, 1) \
X(Sext) \
X(Zext) \
X(Negate, false, FlagEffects::kSet, kOut) \
X(Invert, false, FlagEffects::kNone, kOut) \
X(Add, false, FlagEffects::kSet, kOut, 1, {1}) \
X(Sub, false, FlagEffects::kSet, kOut, 1, {1}) \
X(And, false, FlagEffects::kSet, kOut, 1, {1}) \
X(Xor, false, FlagEffects::kSet, kOut, 1, {1}) \
X(Div, false, FlagEffects::kSet, kDefault, 1, {1}) \
X(DivUn, false, FlagEffects::kSet, kDefault, 1, {1}) \
X(Mul, false, FlagEffects::kSet, kOut, 1, {1}) \
X(Or, false, FlagEffects::kSet, kOut, 1, {1}) \
X(Fadd, true, FlagEffects::kNone, kAlways64, 1, {1, 1}) \
X(Fsub, true, FlagEffects::kNone, kAlways64, 1, {1, 1}) \
X(Fmul, true, FlagEffects::kNone, kAlways64, 1, {1, 1}) \
X(Fdiv, true, FlagEffects::kNone, kAlways64, 1, {1, 1}) \
X(LShift, false, FlagEffects::kSet) \
X(RShift, false, FlagEffects::kSet) \
X(RShiftUn, false, FlagEffects::kSet) \
X(Test, false, FlagEffects::kSet, kOut, 0, {1, 1}) \
X(Equal, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(NotEqual, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(GreaterThanSigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(LessThanSigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(GreaterThanEqualSigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(LessThanEqualSigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(GreaterThanUnsigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(LessThanUnsigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(GreaterThanEqualUnsigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(LessThanEqualUnsigned, false, FlagEffects::kSet, kDefault, 1, {1, 1}) \
X(Cmp, false, FlagEffects::kSet, kOut, 1, {1, 1}) \
X(Lea, false, FlagEffects::kNone, kAlways64, 1, {1, 1}) \
X(LoadArg, false, FlagEffects::kNone, kAlways64) \
X(Exchange, false, FlagEffects::kNone, kAlways64, 1, {1, 1}) \
X(Move, false, FlagEffects::kNone, kOut) \
X(Push, false, FlagEffects::kNone, kDefault, 1, {}, 1) \
X(Pop, false, FlagEffects::kNone, kDefault, 0, {}, 1) \
X(Cdq, false, FlagEffects::kNone, kDefault, 1, {}, 1) \
X(Cwd, false, FlagEffects::kNone, kDefault, 1, {}, 1) \
X(Cqo, false, FlagEffects::kNone, kDefault, 1, {}, 1) \
X(BatchDecref, false, FlagEffects::kInvalidate, kDefault, 1, {1}) \
X(Branch) \
X(BranchNZ) \
X(BranchZ) \
X(BranchA) \
X(BranchB) \
X(BranchAE) \
X(BranchBE) \
X(BranchG) \
X(BranchL) \
X(BranchGE) \
X(BranchLE) \
X(BranchC) \
X(BranchNC) \
X(BitTest, false, FlagEffects::kSet, kDefault, 1, {1}) \
X(Inc, false, FlagEffects::kSet) \
X(Dec, false, FlagEffects::kSet) \
X(CondBranch, false, FlagEffects::kInvalidate, kDefault, 0, {1}) \
X(Phi) \
X(Return, false, FlagEffects::kInvalidate) \
X(MovZX) \
X(MovSX) \
X(MovSXD) \
X(YieldInitial, true, FlagEffects::kInvalidate, kDefault, 0, {}, 1) \
X(YieldFrom, true, FlagEffects::kInvalidate, kDefault, 0, {}, 1) \
X(YieldFromSkipInitialSend, \
true, \
FlagEffects::kInvalidate, \
kDefault, \
0, \
{}, \
1) \
X(YieldValue, true, FlagEffects::kInvalidate, kDefault, 0, {}, 1)
// Instruction class defines instructions in LIR.
// Every instruction can have no more than one output, but arbitrary
// number of inputs. The instruction logically has no output also
// has an output data member with the type kNone.
class Instruction {
public:
// instruction type
enum Opcode : int {
kNone = -1,
#define INSTR_DECL_TYPE(v, ...) k##v,
FOREACH_INSTR_TYPE(INSTR_DECL_TYPE)
#undef INSTR_DECL_TYPE
};
static constexpr const char* kOpcodeNames[] = {
#define INSTR_DECL_TYPE(v, ...) #v,
FOREACH_INSTR_TYPE(INSTR_DECL_TYPE)
#undef INSTR_DECL_TYPE
};
#define COUNT_INSTR(...) +1
static constexpr size_t kNumOpcodes = FOREACH_INSTR_TYPE(COUNT_INSTR);
#undef COUNT_INSTR
#define DECL_OPCODE_TEST(v, ...) \
bool is##v() const { \
return opcode() == k##v; \
}
FOREACH_INSTR_TYPE(DECL_OPCODE_TEST)
#undef DECL_OPCODE_TEST
Instruction(BasicBlock* basic_block, Opcode opcode, const hir::Instr* origin);
// Only copies simple fields (opcode_, basic_block_, origin_) from instr.
// The output_ only has its simple fields copied.
// The inputs are not copied.
Instruction(BasicBlock* bb, Instruction* instr, const hir::Instr* origin);
int id() const {
return id_;
}
Operand* output() {
return &output_;
}
const Operand* output() const {
return &output_;
}
const hir::Instr* origin() const {
return origin_;
}
size_t getNumInputs() const {
return inputs_.size();
}
void setNumInputs(size_t n) {
inputs_.resize(n);
}
size_t getNumOutputs() const {
return output_.type() == OperandBase::kNone ? 0 : 1;
}
OperandBase* getInput(size_t i) {
return inputs_.at(i).get();
}
const OperandBase* getInput(size_t i) const {
return inputs_.at(i).get();
}
Operand* allocateImmediateInput(
uint64_t n,
OperandBase::DataType data_type = OperandBase::k64bit);
Operand* allocateFPImmediateInput(double n);
LinkedOperand* allocateLinkedInput(Instruction* def_instr);
Operand* allocatePhyRegisterInput(int loc) {
return allocateOperand(&Operand::setPhyRegister, loc);
}
Operand* allocateStackInput(int stack) {
return allocateOperand(&Operand::setStackSlot, stack);
}
Operand* allocatePhyRegOrStackInput(int loc) {
return allocateOperand(&Operand::setPhyRegOrStackSlot, loc);
}
Operand* allocateAddressInput(void* address) {
return allocateOperand(&Operand::setMemoryAddress, address);
}
Operand* allocateLabelInput(BasicBlock* block) {
return allocateOperand(&Operand::setBasicBlock, block);
}
template <typename... Args>
Operand* allocateMemoryIndirectInput(Args&&... args) {
auto operand = std::make_unique<Operand>(this);
auto opnd = operand.get();
operand->setMemoryIndirect(std::forward<Args>(args)...);
inputs_.push_back(std::move(operand));
return opnd;
}
// add operands to the instruction. The arguments can be one
// of the following:
// - [Out]PhyReg(phyreg, size): a physical register
// - [Out]Imm(imm, size): an immediate
// - [Out]Stack(slot, size): a stack slot
// - [Out]PhyRegStack(phyreg/stack, size): a physical register or stack slot
// - [Out]Lbl(Basicblock): a basic block target
// - VReg(instr), OutVReg(size): a virtual register
// the arguments with the names prefixed with `Out` are output operands.
// the output operand must be the first argument of this function.
template <typename FirstT, typename... T>
Instruction* addOperands(FirstT&& first_arg, T&&... args) {
static_assert(
!(std::decay_t<decltype(args)>::is_output || ... || false),
"output must be the first argument.");
using FT = std::decay_t<FirstT>;
if constexpr (std::is_same_v<FT, PhyRegStack>) {
allocatePhyRegOrStackInput(first_arg.value)
->setDataType(first_arg.data_type);
} else if constexpr (std::is_same_v<FT, Imm>) {
allocateImmediateInput(first_arg.value)->setDataType(first_arg.data_type);
} else if constexpr (std::is_same_v<FT, FPImm>) {
allocateFPImmediateInput(first_arg.value)
->setDataType(OperandBase::kDouble);
} else if constexpr (std::is_same_v<FT, Lbl>) {
allocateLabelInput(first_arg.value);
} else if constexpr (std::is_same_v<FT, VReg>) {
allocateLinkedInput(first_arg.value);
} else if constexpr (std::is_same_v<FT, Ind>) {
allocateMemoryIndirectInput(
first_arg.base,
first_arg.index,
first_arg.multiplier,
first_arg.offset);
} else if constexpr (std::is_same_v<FT, OutPhyRegStack>) {
output()->setPhyRegOrStackSlot(first_arg.value);
output()->setDataType(first_arg.data_type);
} else if constexpr (std::is_same_v<FT, OutImm>) {
output()->setConstant(first_arg.value);
output()->setDataType(first_arg.data_type);
} else if constexpr (std::is_same_v<FT, OutFPImm>) {
output()->setFPConstant(first_arg.value);
output()->setDataType(OperandBase::kDouble);
} else if constexpr (std::is_same_v<FT, OutLbl>) {
output()->setBasicBlock(first_arg.value);
} else if constexpr (std::is_same_v<FT, OutVReg>) {
output()->setVirtualRegister();
output()->setDataType(first_arg.data_type);
} else if constexpr (std::is_same_v<FT, OutInd>) {
output()->setMemoryIndirect(
first_arg.base,
first_arg.index,
first_arg.multiplier,
first_arg.offset);
} else {
static_assert(!sizeof(FT*), "Bad argument type.");
}
return addOperands(std::forward<T>(args)...);
}
constexpr Instruction* addOperands() {
return this;
}
void setbasicblock(BasicBlock* bb) {
basic_block_ = bb;
}
BasicBlock* basicblock() {
return basic_block_;
}
const BasicBlock* basicblock() const {
return basic_block_;
}
Opcode opcode() const {
return opcode_;
}
std::string opname() const {
return kOpcodeNames[opcode_];
}
void setOpcode(Opcode opcode) {
opcode_ = opcode;
}
template <typename Func>
void foreachInputOperand(const Func& f) const {
for (size_t i = 0; i < this->getNumInputs(); i++) {
auto operand = getInput(i);
f(operand);
}
}
template <typename Func>
void foreachInputOperand(const Func& f) {
for (size_t i = 0; i < this->getNumInputs(); i++) {
auto operand = getInput(i);
f(operand);
}
}
// replace the input operand at index with operand.
void replaceInputOperand(size_t index, std::unique_ptr<OperandBase> operand) {
inputs_[index] = std::move(operand);
}
std::unique_ptr<OperandBase> removeInputOperand(size_t index) {
auto opnd = std::move(inputs_.at(index));
inputs_.erase(inputs_.begin() + index);
return opnd;
}
// Release the input operand at index from the instruction without
// deallocating it. The original index of inputs_ will be left with
// a null std::unique_ptr, which is supposed be removed from inputs_
// by an operation to follow.
std::unique_ptr<OperandBase> releaseInputOperand(size_t index) {
auto& operand = inputs_.at(index);
operand->releaseFromInstr();
return std::move(inputs_.at(index));
}
OperandBase* appendInputOperand(std::unique_ptr<OperandBase> operand) {
auto opnd = operand.get();
opnd->assignToInstr(this);
inputs_.push_back(std::move(operand));
return opnd;
}
OperandBase* prependInputOperand(std::unique_ptr<OperandBase> operand) {
auto opnd = operand.get();
opnd->assignToInstr(this);
inputs_.insert(inputs_.begin(), std::move(operand));
return opnd;
}
// get the operand associated to a given predecessor in a phi instruction
// returns nullptr if not found.
OperandBase* getOperandByPredecessor(const BasicBlock* pred) {
auto index = getOperandIndexByPredecessor(pred);
return index == -1 ? nullptr : inputs_.at(index).get();
}
int getOperandIndexByPredecessor(const BasicBlock* pred) const {
JIT_DCHECK(opcode_ == kPhi, "The current instruction must be Phi.");
size_t num_inputs = getNumInputs();
for (size_t i = 0; i < num_inputs; i += 2) {
if (getInput(i)->getBasicBlock() == pred) {
return i + 1;
}
}
return -1;
}
const OperandBase* getOperandByPredecessor(const BasicBlock* pred) const {
return const_cast<Instruction*>(this)->getOperandByPredecessor(pred);
}
bool getOutputPhyRegUse() const;
bool getInputPhyRegUse(size_t i) const;
// Should input registers live across the instruction until it
// finish execution? Some instructions need this such as Guard
// instruction, whose its inputs may be needed to reify the frame
// upon deopt. Other instructions do not need it, such as Add, etc.,
// so the input registers can be used for other purposes e.g., allocated for
// the output even before they finish execution.
bool inputsLiveAcross() const;
bool isCompare() const {
switch (opcode_) {
case kEqual:
case kNotEqual:
case kGreaterThanSigned:
case kLessThanSigned:
case kGreaterThanEqualSigned:
case kLessThanEqualSigned:
case kGreaterThanUnsigned:
case kLessThanUnsigned:
case kGreaterThanEqualUnsigned:
case kLessThanEqualUnsigned:
return true;
default:
return false;
}
}
bool isBranchCC() const {
switch (opcode_) {
case kBranchC:
case kBranchNC:
case kBranchZ:
case kBranchNZ:
case kBranchA:
case kBranchB:
case kBranchBE:
case kBranchAE:
case kBranchL:
case kBranchG:
case kBranchLE:
case kBranchGE:
return true;
default:
return false;
}
}
bool isAnyBranch() const {
return (opcode_ == kCondBranch) || isBranchCC();
}
bool isTerminator() const {
switch (opcode_) {
case kReturn:
return true;
default:
return false;
}
}
bool isAnyYield() const {
switch (opcode_) {
case kYieldFrom:
case kYieldFromSkipInitialSend:
case kYieldInitial:
case kYieldValue:
return true;
default:
return false;
}
}
#define CASE_FLIP(op1, op2) \
case op1: \
return op2; \
case op2: \
return op1;
// negate the branch condition:
// e.g. A >= B -> !(A < B)
static Opcode negateBranchCC(Opcode opcode) {
switch (opcode) {
CASE_FLIP(kBranchC, kBranchNC)
CASE_FLIP(kBranchZ, kBranchNZ)
CASE_FLIP(kBranchA, kBranchBE)
CASE_FLIP(kBranchB, kBranchAE)
CASE_FLIP(kBranchL, kBranchGE)
CASE_FLIP(kBranchG, kBranchLE)
default:
JIT_CHECK(false, "Not a conditional branch opcode.");
}
}
// flipping the direction of comparison:
// e.g. A >= B -> B <= A
static Opcode flipBranchCCDirection(Opcode opcode) {
switch (opcode) {
CASE_FLIP(kBranchA, kBranchB)
CASE_FLIP(kBranchAE, kBranchBE)
CASE_FLIP(kBranchL, kBranchG)
CASE_FLIP(kBranchLE, kBranchGE)
default:
JIT_CHECK(false, "Unable to flip direction for opcode.");
}
}
static Opcode flipComparisonDirection(Opcode opcode) {
switch (opcode) {
CASE_FLIP(kGreaterThanEqualSigned, kLessThanEqualSigned)
CASE_FLIP(kGreaterThanEqualUnsigned, kLessThanEqualUnsigned)
CASE_FLIP(kGreaterThanSigned, kLessThanSigned)
CASE_FLIP(kGreaterThanUnsigned, kLessThanUnsigned)
case kEqual:
return kEqual;
case kNotEqual:
return kNotEqual;
default:
JIT_CHECK(false, "Unable to flip direction for comparison opcode.");
}
}
#undef CASE_FLIP
static Opcode compareToBranchCC(Opcode opcode) {
switch (opcode) {
case kEqual:
return kBranchZ;
case kNotEqual:
return kBranchNZ;
case kGreaterThanUnsigned:
return kBranchA;
case kLessThanUnsigned:
return kBranchB;
case kGreaterThanEqualUnsigned:
return kBranchAE;
case kLessThanEqualUnsigned:
return kBranchBE;
case kGreaterThanSigned:
return kBranchG;
case kLessThanSigned:
return kBranchL;
case kGreaterThanEqualSigned:
return kBranchGE;
case kLessThanEqualSigned:
return kBranchLE;
default:
JIT_CHECK(false, "Not a compare opcode.");
}
}
void print() const;
private:
int id_;
Opcode opcode_;
Operand output_;
std::vector<std::unique_ptr<OperandBase>> inputs_;
BasicBlock* basic_block_;
const hir::Instr* origin_;
template <typename FType, typename... AType>
Operand* allocateOperand(FType&& set_func, AType&&... arg) {
auto operand = std::make_unique<Operand>(this);
auto opnd = operand.get();
(opnd->*set_func)(std::forward<AType>(arg)...);
inputs_.push_back(std::move(operand));
return opnd;
}
// used in parser, expect unique id
void setId(int id) {
id_ = id;
}
friend class Parser;
};
// Instruction Guard specific
enum InstrGuardKind { kNotZero, kNotNegative, kAlwaysFail, kIs, kHasType };
// an instruction property type specifying how its operand sizes
// are determined.
enum OperandSizeType {
kDefault, // every operand uses its own size
kAlways64, // every operand uses 64-bit size
kOut // every operand uses output size or first input operand (when there is
// no output)
};
// This class defines instruction properties for different types of
// instructions.
class InstrProperty {
public:
struct InstrInfo;
static InstrInfo& getProperties(Instruction::Opcode opcode);
static InstrInfo& getProperties(const Instruction* instr) {
return getProperties(instr->opcode());
}
private:
static std::vector<InstrInfo> prop_map_;
};
// Initialize instruction properties
#define BEGIN_INSTR_PROPERTY_FIELD struct InstrProperty::InstrInfo {
#define END_INSTR_PROPERTY_FIELD \
} \
;
#define FIELD_DEFAULT(__t, __n, __d) __t __n{__d};
#define FIELD_NO_DEFAULT(__t, __n) __t __n;
// clang-format off
// This table contains definitions of all the instruction property field.
// `is_essential` indicates that a given instruction can have memory effects not
// captured by its operands. We maintain the invariant that all instructions
// without operands have `may_store` set.
BEGIN_INSTR_PROPERTY_FIELD
FIELD_NO_DEFAULT(std::string, name)
FIELD_DEFAULT(bool, inputs_live_across, false)
FIELD_DEFAULT(FlagEffects, flag_effects, FlagEffects::kNone)
FIELD_DEFAULT(OperandSizeType, opnd_size_type, kDefault)
FIELD_DEFAULT(bool, output_phy_use, true)
FIELD_DEFAULT(std::vector<int>, input_phy_uses, std::vector<int>{})
FIELD_DEFAULT(bool, is_essential, false)
END_INSTR_PROPERTY_FIELD
// clang-format on
} // namespace lir
} // namespace jit