Move more IR classes into their own headers
Esse commit está contido em:
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_VM_BLOCK_H_
|
||||
#define incl_HPHP_VM_BLOCK_H_
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/irinstruction.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
/*
|
||||
* A Block refers to a basic block: single-entry, single-exit, list of
|
||||
* instructions. The instruction list is an intrusive list, so each
|
||||
* instruction can only be in one block at a time. Likewise, a block
|
||||
* can only be owned by one trace at a time.
|
||||
*
|
||||
* Block owns the InstructionList, but exposes several list methods itself
|
||||
* so usually you can use Block directly. These methods also update
|
||||
* IRInstruction::m_block transparently.
|
||||
*/
|
||||
struct Block : boost::noncopyable {
|
||||
typedef InstructionList::iterator iterator;
|
||||
typedef InstructionList::const_iterator const_iterator;
|
||||
|
||||
// Execution frequency hint; codegen will put Unlikely blocks in astubs.
|
||||
enum Hint { Neither, Likely, Unlikely };
|
||||
|
||||
Block(unsigned id, const Func* func, IRInstruction* label)
|
||||
: m_trace(nullptr)
|
||||
, m_func(func)
|
||||
, m_next(nullptr)
|
||||
, m_id(id)
|
||||
, m_preds(nullptr)
|
||||
, m_hint(Neither)
|
||||
{
|
||||
push_back(label);
|
||||
}
|
||||
|
||||
IRInstruction* getLabel() const {
|
||||
assert(front()->op() == DefLabel);
|
||||
return front();
|
||||
}
|
||||
|
||||
uint32_t getId() const { return m_id; }
|
||||
Trace* getTrace() const { return m_trace; }
|
||||
void setTrace(Trace* t) { m_trace = t; }
|
||||
void setHint(Hint hint) { m_hint = hint; }
|
||||
Hint getHint() const { return m_hint; }
|
||||
|
||||
void addEdge(IRInstruction* jmp);
|
||||
void removeEdge(IRInstruction* jmp);
|
||||
|
||||
bool isMain() const;
|
||||
|
||||
// return the last instruction in the block
|
||||
IRInstruction* back() const {
|
||||
assert(!m_instrs.empty());
|
||||
auto it = m_instrs.end();
|
||||
return const_cast<IRInstruction*>(&*(--it));
|
||||
}
|
||||
|
||||
// return the first instruction in the block.
|
||||
IRInstruction* front() const {
|
||||
assert(!m_instrs.empty());
|
||||
return const_cast<IRInstruction*>(&*m_instrs.begin());
|
||||
}
|
||||
|
||||
// return the fallthrough block. Should be nullptr if the last
|
||||
// instruction is a Terminal.
|
||||
Block* getNext() const { return m_next; }
|
||||
void setNext(Block* b) { m_next = b; }
|
||||
|
||||
// return the target block if the last instruction is a branch.
|
||||
Block* getTaken() const {
|
||||
return back()->getTaken();
|
||||
}
|
||||
|
||||
// return the postorder number of this block. (updated each time
|
||||
// sortBlocks() is called.
|
||||
unsigned postId() const { return m_postid; }
|
||||
void setPostId(unsigned id) { m_postid = id; }
|
||||
|
||||
// insert inst after this block's label, return an iterator to the
|
||||
// newly inserted instruction.
|
||||
iterator prepend(IRInstruction* inst) {
|
||||
assert(front()->op() == DefLabel);
|
||||
auto it = begin();
|
||||
return insert(++it, inst);
|
||||
}
|
||||
|
||||
// return iterator to first instruction after the label
|
||||
iterator skipLabel() { auto it = begin(); return ++it; }
|
||||
|
||||
// return iterator to last instruction
|
||||
iterator backIter() { auto it = end(); return --it; }
|
||||
|
||||
// return an iterator to a specific instruction
|
||||
iterator iteratorTo(IRInstruction* inst) {
|
||||
assert(inst->getBlock() == this);
|
||||
return m_instrs.iterator_to(*inst);
|
||||
}
|
||||
|
||||
// visit each src that provides a value to label->dsts[i]. body
|
||||
// should take an IRInstruction* and an SSATmp*.
|
||||
template<typename L>
|
||||
void forEachSrc(unsigned i, L body) {
|
||||
for (const EdgeData* n = m_preds; n; n = n->next) {
|
||||
assert(n->jmp->op() == Jmp_ && n->jmp->getTaken() == this);
|
||||
body(n->jmp, n->jmp->getSrc(i));
|
||||
}
|
||||
}
|
||||
|
||||
// return the first src providing a value to label->dsts[i] for
|
||||
// which body(src) returns true, or nullptr if none are found.
|
||||
template<typename L>
|
||||
SSATmp* findSrc(unsigned i, L body) {
|
||||
for (const EdgeData* n = m_preds; n; n = n->next) {
|
||||
SSATmp* src = n->jmp->getSrc(i);
|
||||
if (body(src)) return src;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// list-compatible interface; these delegate to m_instrs but also update
|
||||
// inst.m_block
|
||||
InstructionList& getInstrs() { return m_instrs; }
|
||||
bool empty() const { return m_instrs.empty(); }
|
||||
iterator begin() { return m_instrs.begin(); }
|
||||
iterator end() { return m_instrs.end(); }
|
||||
const_iterator begin() const { return m_instrs.begin(); }
|
||||
const_iterator end() const { return m_instrs.end(); }
|
||||
|
||||
iterator insert(iterator pos, IRInstruction* inst) {
|
||||
inst->setBlock(this);
|
||||
return m_instrs.insert(pos, *inst);
|
||||
}
|
||||
void splice(iterator pos, Block* from, iterator begin, iterator end) {
|
||||
assert(from != this);
|
||||
for (auto i = begin; i != end; ++i) (*i).setBlock(this);
|
||||
m_instrs.splice(pos, from->getInstrs(), begin, end);
|
||||
}
|
||||
void push_back(IRInstruction* inst) {
|
||||
inst->setBlock(this);
|
||||
return m_instrs.push_back(*inst);
|
||||
}
|
||||
template <class Predicate> void remove_if(Predicate p) {
|
||||
m_instrs.remove_if(p);
|
||||
}
|
||||
void erase(iterator pos) {
|
||||
m_instrs.erase(pos);
|
||||
}
|
||||
|
||||
private:
|
||||
InstructionList m_instrs; // instructions in this block
|
||||
Trace* m_trace; // owner of this block.
|
||||
const Func* m_func; // which func are we in
|
||||
Block* m_next; // fall-through path; null if back()->isTerminal().
|
||||
const unsigned m_id; // factory-assigned unique id of this block
|
||||
unsigned m_postid; // postorder number of this block
|
||||
EdgeData* m_preds; // head of list of predecessor Jmps
|
||||
Hint m_hint; // execution frequency hint
|
||||
};
|
||||
typedef std::list<Block*> BlockList;
|
||||
typedef std::forward_list<Block*> BlockPtrList;
|
||||
|
||||
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -17,7 +17,7 @@
|
||||
#ifndef incl_HPHP_VM_CFG_H_
|
||||
#define incl_HPHP_VM_CFG_H_
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/block.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/trace.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/cfg.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/ssatmp.h"
|
||||
|
||||
namespace HPHP {
|
||||
namespace JIT {
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
/*
|
||||
* Hashtable used for common subexpression elimination. The table maps
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_VM_EXTRADATA_H_
|
||||
#define incl_HPHP_VM_EXTRADATA_H_
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Some IRInstructions with compile-time-only constants may carry
|
||||
* along extra data in the form of one of these structures.
|
||||
*
|
||||
* Note that this isn't really appropriate for compile-time constants
|
||||
* that are actually representing user values (we want them to be
|
||||
* visible to optimization passes, allocatable to registers, etc),
|
||||
* just compile-time metadata.
|
||||
*
|
||||
* These types must:
|
||||
*
|
||||
* - Derive from IRExtraData (for overloading purposes)
|
||||
* - Be arena-allocatable (no non-trivial destructors)
|
||||
* - Either CopyConstructible, or implement a clone member
|
||||
* function that takes an arena to clone to
|
||||
*
|
||||
* In addition, for extra data used with a cse-able instruction:
|
||||
*
|
||||
* - Implement an cseEquals() member that indicates equality for CSE
|
||||
* purposes.
|
||||
* - Implement a cseHash() method.
|
||||
*
|
||||
* Finally, optionally they may implement a show() method for use in
|
||||
* debug printouts.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Traits that returns the type of the extra C++ data structure for a
|
||||
* given instruction, if it has one, along with some other information
|
||||
* about the type.
|
||||
*/
|
||||
template<Opcode op> struct OpHasExtraData { enum { value = 0 }; };
|
||||
template<Opcode op> struct IRExtraDataType;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct IRExtraData {};
|
||||
|
||||
struct LdSSwitchData : IRExtraData {
|
||||
struct Elm {
|
||||
const StringData* str;
|
||||
Offset dest;
|
||||
};
|
||||
|
||||
explicit LdSSwitchData() = default;
|
||||
LdSSwitchData(const LdSSwitchData&) = delete;
|
||||
LdSSwitchData& operator=(const LdSSwitchData&) = delete;
|
||||
|
||||
LdSSwitchData* clone(Arena& arena) const {
|
||||
LdSSwitchData* target = new (arena) LdSSwitchData;
|
||||
target->func = func;
|
||||
target->numCases = numCases;
|
||||
target->defaultOff = defaultOff;
|
||||
target->cases = new (arena) Elm[numCases];
|
||||
std::copy(cases, cases + numCases, const_cast<Elm*>(target->cases));
|
||||
return target;
|
||||
}
|
||||
|
||||
const Func* func;
|
||||
int64_t numCases;
|
||||
const Elm* cases;
|
||||
Offset defaultOff;
|
||||
};
|
||||
|
||||
struct JmpSwitchData : IRExtraData {
|
||||
JmpSwitchData* clone(Arena& arena) const {
|
||||
JmpSwitchData* sd = new (arena) JmpSwitchData;
|
||||
sd->func = func;
|
||||
sd->base = base;
|
||||
sd->bounded = bounded;
|
||||
sd->cases = cases;
|
||||
sd->defaultOff = defaultOff;
|
||||
sd->targets = new (arena) Offset[cases];
|
||||
std::copy(targets, targets + cases, const_cast<Offset*>(sd->targets));
|
||||
return sd;
|
||||
}
|
||||
|
||||
const Func* func;
|
||||
int64_t base; // base of switch case
|
||||
bool bounded; // whether switch is bounded or not
|
||||
int32_t cases; // number of cases
|
||||
Offset defaultOff; // offset of default case
|
||||
Offset* targets; // offsets for all targets
|
||||
};
|
||||
|
||||
struct MarkerData : IRExtraData {
|
||||
uint32_t bcOff; // the bytecode offset in unit
|
||||
int32_t stackOff; // stack off from start of trace
|
||||
const Func* func; // which func are we in
|
||||
};
|
||||
|
||||
struct LocalId : IRExtraData {
|
||||
explicit LocalId(uint32_t id)
|
||||
: locId(id)
|
||||
{}
|
||||
|
||||
bool cseEquals(LocalId o) const { return locId == o.locId; }
|
||||
size_t cseHash() const { return std::hash<uint32_t>()(locId); }
|
||||
std::string show() const { return folly::to<std::string>(locId); }
|
||||
|
||||
uint32_t locId;
|
||||
};
|
||||
|
||||
struct ConstData : IRExtraData {
|
||||
template<class T>
|
||||
explicit ConstData(T data)
|
||||
: m_dataBits(0)
|
||||
{
|
||||
static_assert(sizeof(T) <= sizeof m_dataBits,
|
||||
"Constant data was larger than supported");
|
||||
static_assert(std::is_pod<T>::value,
|
||||
"Constant data wasn't a pod?");
|
||||
std::memcpy(&m_dataBits, &data, sizeof data);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T as() const {
|
||||
T ret;
|
||||
std::memcpy(&ret, &m_dataBits, sizeof ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool cseEquals(ConstData o) const { return m_dataBits == o.m_dataBits; }
|
||||
size_t cseHash() const { return std::hash<uintptr_t>()(m_dataBits); }
|
||||
|
||||
private:
|
||||
uintptr_t m_dataBits;
|
||||
};
|
||||
|
||||
struct CreateContData : IRExtraData {
|
||||
const Func* origFunc;
|
||||
const Func* genFunc;
|
||||
};
|
||||
|
||||
/*
|
||||
* EdgeData is linked list node that tracks the set of Jmp_'s that pass values
|
||||
* to a particular block. Each such Jmp_ has one node, and the block points
|
||||
* to the list head.
|
||||
*/
|
||||
struct EdgeData : IRExtraData {
|
||||
IRInstruction* jmp; // owner of this edge
|
||||
EdgeData* next; // next edge to same target
|
||||
};
|
||||
|
||||
/*
|
||||
* Information for the REQ_BIND_JMPCC stubs we create when a tracelet
|
||||
* ends with conditional jumps.
|
||||
*/
|
||||
struct ReqBindJccData : IRExtraData {
|
||||
Offset taken;
|
||||
Offset notTaken;
|
||||
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(taken, ',', notTaken);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Information for a conditional side exit based on a type check of a
|
||||
* local or stack cell.
|
||||
*/
|
||||
struct SideExitGuardData : IRExtraData {
|
||||
uint32_t checkedSlot;
|
||||
Offset taken;
|
||||
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(checkedSlot, ',', taken);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Compile-time metadata about an ActRec allocation.
|
||||
*/
|
||||
struct ActRecInfo : IRExtraData {
|
||||
const StringData* invName; // may be nullptr
|
||||
int32_t numArgs;
|
||||
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(numArgs, invName ? " M" : "");
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Stack offsets.
|
||||
*/
|
||||
struct StackOffset : IRExtraData {
|
||||
explicit StackOffset(int32_t offset) : offset(offset) {}
|
||||
|
||||
std::string show() const { return folly::to<std::string>(offset); }
|
||||
|
||||
bool cseEquals(StackOffset o) const { return offset == o.offset; }
|
||||
size_t cseHash() const { return std::hash<int32_t>()(offset); }
|
||||
|
||||
int32_t offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* Bytecode offsets.
|
||||
*/
|
||||
struct BCOffset : IRExtraData {
|
||||
explicit BCOffset(Offset offset) : offset(offset) {}
|
||||
std::string show() const { return folly::to<std::string>(offset); }
|
||||
Offset offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* DefInlineFP is present when we need to create a frame for inlining.
|
||||
* This instruction also carries some metadata used by tracebuilder to
|
||||
* track state during an inlined call.
|
||||
*/
|
||||
struct DefInlineFPData : IRExtraData {
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(
|
||||
target->fullName()->data(), "(),", retSPOff, ',', retBCOff
|
||||
);
|
||||
}
|
||||
|
||||
const Func* target;
|
||||
Offset retBCOff;
|
||||
Offset retSPOff;
|
||||
};
|
||||
|
||||
/*
|
||||
* FCallArray offsets
|
||||
*/
|
||||
struct CallArrayData : IRExtraData {
|
||||
explicit CallArrayData(Offset pcOffset, Offset aft)
|
||||
: pc(pcOffset), after(aft) {}
|
||||
|
||||
std::string show() const { return folly::to<std::string>(pc, ",", after); }
|
||||
|
||||
Offset pc, after;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define X(op, data) \
|
||||
template<> struct IRExtraDataType<op> { typedef data type; }; \
|
||||
template<> struct OpHasExtraData<op> { enum { value = 1 }; }; \
|
||||
static_assert(boost::has_trivial_destructor<data>::value, \
|
||||
"IR extra data type must be trivially destructible")
|
||||
|
||||
X(JmpSwitchDest, JmpSwitchData);
|
||||
X(LdSSwitchDestFast, LdSSwitchData);
|
||||
X(LdSSwitchDestSlow, LdSSwitchData);
|
||||
X(Marker, MarkerData);
|
||||
X(RaiseUninitLoc, LocalId);
|
||||
X(GuardLoc, LocalId);
|
||||
X(CheckLoc, LocalId);
|
||||
X(AssertLoc, LocalId);
|
||||
X(OverrideLoc, LocalId);
|
||||
X(LdLocAddr, LocalId);
|
||||
X(DecRefLoc, LocalId);
|
||||
X(LdLoc, LocalId);
|
||||
X(StLoc, LocalId);
|
||||
X(StLocNT, LocalId);
|
||||
X(DefConst, ConstData);
|
||||
X(LdConst, ConstData);
|
||||
X(Jmp_, EdgeData);
|
||||
X(SpillFrame, ActRecInfo);
|
||||
X(GuardStk, StackOffset);
|
||||
X(CheckStk, StackOffset);
|
||||
X(CastStk, StackOffset);
|
||||
X(AssertStk, StackOffset);
|
||||
X(ReDefSP, StackOffset);
|
||||
X(ReDefGeneratorSP, StackOffset);
|
||||
X(DefSP, StackOffset);
|
||||
X(LdStack, StackOffset);
|
||||
X(LdStackAddr, StackOffset);
|
||||
X(DecRefStack, StackOffset);
|
||||
X(DefInlineFP, DefInlineFPData);
|
||||
X(ReqBindJmp, BCOffset);
|
||||
X(ReqBindJmpNoIR, BCOffset);
|
||||
X(ReqRetranslateNoIR, BCOffset);
|
||||
X(InlineCreateCont, CreateContData);
|
||||
X(CallArray, CallArrayData);
|
||||
X(ReqBindJmpGt, ReqBindJccData);
|
||||
X(ReqBindJmpGte, ReqBindJccData);
|
||||
X(ReqBindJmpLt, ReqBindJccData);
|
||||
X(ReqBindJmpLte, ReqBindJccData);
|
||||
X(ReqBindJmpEq, ReqBindJccData);
|
||||
X(ReqBindJmpNeq, ReqBindJccData);
|
||||
X(ReqBindJmpSame, ReqBindJccData);
|
||||
X(ReqBindJmpNSame, ReqBindJccData);
|
||||
X(ReqBindJmpInstanceOfBitmask, ReqBindJccData);
|
||||
X(ReqBindJmpNInstanceOfBitmask, ReqBindJccData);
|
||||
X(ReqBindJmpZero, ReqBindJccData);
|
||||
X(ReqBindJmpNZero, ReqBindJccData);
|
||||
X(SideExitGuardLoc, SideExitGuardData);
|
||||
X(SideExitGuardStk, SideExitGuardData);
|
||||
|
||||
#undef X
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<bool hasExtra, Opcode opc, class T> struct AssertExtraTypes {
|
||||
static void doassert() {
|
||||
assert(!"called getExtra on an opcode without extra data");
|
||||
}
|
||||
};
|
||||
|
||||
template<Opcode opc, class T> struct AssertExtraTypes<true,opc,T> {
|
||||
static void doassert() {
|
||||
typedef typename IRExtraDataType<opc>::type ExtraType;
|
||||
if (!std::is_same<ExtraType,T>::value) {
|
||||
assert(!"getExtra<T> was called with an extra data "
|
||||
"type that doesn't match the opcode type");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Asserts that Opcode opc has extradata and it is of type T.
|
||||
template<class T> void assert_opcode_extra(Opcode opc) {
|
||||
#define O(opcode, dstinfo, srcinfo, flags) \
|
||||
case opcode: \
|
||||
AssertExtraTypes< \
|
||||
OpHasExtraData<opcode>::value,opcode,T \
|
||||
>::doassert(); \
|
||||
break;
|
||||
switch (opc) { IR_OPCODES default: not_reached(); }
|
||||
#undef O
|
||||
}
|
||||
|
||||
std::string showExtra(Opcode opc, const IRExtraData* data);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "hphp/runtime/vm/runtime.h"
|
||||
#include "hphp/runtime/base/stats.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/cse.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/irinstruction.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/irfactory.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/linearscan.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/print.h"
|
||||
@@ -994,6 +995,18 @@ int32_t spillValueCells(IRInstruction* spillStack) {
|
||||
return numSrcs - 2;
|
||||
}
|
||||
|
||||
bool isConvIntOrPtrToBool(IRInstruction* instr) {
|
||||
switch (instr->op()) {
|
||||
case ConvIntToBool:
|
||||
return true;
|
||||
case ConvCellToBool:
|
||||
return instr->getSrc(0)->type().subtypeOfAny(
|
||||
Type::Func, Type::Cls, Type::FuncCls, Type::VarEnv, Type::TCA);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BlockList sortCfg(Trace* trace, const IRFactory& factory) {
|
||||
assert(trace->isMain());
|
||||
BlockList blocks;
|
||||
|
||||
@@ -59,6 +59,9 @@ using HPHP::Transl::PhysReg;
|
||||
using HPHP::Transl::ConditionCode;
|
||||
|
||||
struct IRInstruction;
|
||||
struct SSATmp;
|
||||
struct Block;
|
||||
struct Trace;
|
||||
|
||||
class FailedIRGen : public std::exception {
|
||||
public:
|
||||
@@ -599,335 +602,6 @@ enum Opcode : uint16_t {
|
||||
IR_NUM_OPCODES
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Some IRInstructions with compile-time-only constants may carry
|
||||
* along extra data in the form of one of these structures.
|
||||
*
|
||||
* Note that this isn't really appropriate for compile-time constants
|
||||
* that are actually representing user values (we want them to be
|
||||
* visible to optimization passes, allocatable to registers, etc),
|
||||
* just compile-time metadata.
|
||||
*
|
||||
* These types must:
|
||||
*
|
||||
* - Derive from IRExtraData (for overloading purposes)
|
||||
* - Be arena-allocatable (no non-trivial destructors)
|
||||
* - Either CopyConstructible, or implement a clone member
|
||||
* function that takes an arena to clone to
|
||||
*
|
||||
* In addition, for extra data used with a cse-able instruction:
|
||||
*
|
||||
* - Implement an cseEquals() member that indicates equality for CSE
|
||||
* purposes.
|
||||
* - Implement a cseHash() method.
|
||||
*
|
||||
* Finally, optionally they may implement a show() method for use in
|
||||
* debug printouts.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Traits that returns the type of the extra C++ data structure for a
|
||||
* given instruction, if it has one, along with some other information
|
||||
* about the type.
|
||||
*/
|
||||
template<Opcode op> struct OpHasExtraData { enum { value = 0 }; };
|
||||
template<Opcode op> struct IRExtraDataType;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct IRExtraData {};
|
||||
|
||||
struct LdSSwitchData : IRExtraData {
|
||||
struct Elm {
|
||||
const StringData* str;
|
||||
Offset dest;
|
||||
};
|
||||
|
||||
explicit LdSSwitchData() = default;
|
||||
LdSSwitchData(const LdSSwitchData&) = delete;
|
||||
LdSSwitchData& operator=(const LdSSwitchData&) = delete;
|
||||
|
||||
LdSSwitchData* clone(Arena& arena) const {
|
||||
LdSSwitchData* target = new (arena) LdSSwitchData;
|
||||
target->func = func;
|
||||
target->numCases = numCases;
|
||||
target->defaultOff = defaultOff;
|
||||
target->cases = new (arena) Elm[numCases];
|
||||
std::copy(cases, cases + numCases, const_cast<Elm*>(target->cases));
|
||||
return target;
|
||||
}
|
||||
|
||||
const Func* func;
|
||||
int64_t numCases;
|
||||
const Elm* cases;
|
||||
Offset defaultOff;
|
||||
};
|
||||
|
||||
struct JmpSwitchData : IRExtraData {
|
||||
JmpSwitchData* clone(Arena& arena) const {
|
||||
JmpSwitchData* sd = new (arena) JmpSwitchData;
|
||||
sd->func = func;
|
||||
sd->base = base;
|
||||
sd->bounded = bounded;
|
||||
sd->cases = cases;
|
||||
sd->defaultOff = defaultOff;
|
||||
sd->targets = new (arena) Offset[cases];
|
||||
std::copy(targets, targets + cases, const_cast<Offset*>(sd->targets));
|
||||
return sd;
|
||||
}
|
||||
|
||||
const Func* func;
|
||||
int64_t base; // base of switch case
|
||||
bool bounded; // whether switch is bounded or not
|
||||
int32_t cases; // number of cases
|
||||
Offset defaultOff; // offset of default case
|
||||
Offset* targets; // offsets for all targets
|
||||
};
|
||||
|
||||
struct MarkerData : IRExtraData {
|
||||
uint32_t bcOff; // the bytecode offset in unit
|
||||
int32_t stackOff; // stack off from start of trace
|
||||
const Func* func; // which func are we in
|
||||
};
|
||||
|
||||
struct LocalId : IRExtraData {
|
||||
explicit LocalId(uint32_t id)
|
||||
: locId(id)
|
||||
{}
|
||||
|
||||
bool cseEquals(LocalId o) const { return locId == o.locId; }
|
||||
size_t cseHash() const { return std::hash<uint32_t>()(locId); }
|
||||
std::string show() const { return folly::to<std::string>(locId); }
|
||||
|
||||
uint32_t locId;
|
||||
};
|
||||
|
||||
struct ConstData : IRExtraData {
|
||||
template<class T>
|
||||
explicit ConstData(T data)
|
||||
: m_dataBits(0)
|
||||
{
|
||||
static_assert(sizeof(T) <= sizeof m_dataBits,
|
||||
"Constant data was larger than supported");
|
||||
static_assert(std::is_pod<T>::value,
|
||||
"Constant data wasn't a pod?");
|
||||
std::memcpy(&m_dataBits, &data, sizeof data);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T as() const {
|
||||
T ret;
|
||||
std::memcpy(&ret, &m_dataBits, sizeof ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool cseEquals(ConstData o) const { return m_dataBits == o.m_dataBits; }
|
||||
size_t cseHash() const { return std::hash<uintptr_t>()(m_dataBits); }
|
||||
|
||||
private:
|
||||
uintptr_t m_dataBits;
|
||||
};
|
||||
|
||||
struct CreateContData : IRExtraData {
|
||||
const Func* origFunc;
|
||||
const Func* genFunc;
|
||||
};
|
||||
|
||||
/*
|
||||
* EdgeData is linked list node that tracks the set of Jmp_'s that pass values
|
||||
* to a particular block. Each such Jmp_ has one node, and the block points
|
||||
* to the list head.
|
||||
*/
|
||||
struct EdgeData : IRExtraData {
|
||||
IRInstruction* jmp; // owner of this edge
|
||||
EdgeData* next; // next edge to same target
|
||||
};
|
||||
|
||||
/*
|
||||
* Information for the REQ_BIND_JMPCC stubs we create when a tracelet
|
||||
* ends with conditional jumps.
|
||||
*/
|
||||
struct ReqBindJccData : IRExtraData {
|
||||
Offset taken;
|
||||
Offset notTaken;
|
||||
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(taken, ',', notTaken);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Information for a conditional side exit based on a type check of a
|
||||
* local or stack cell.
|
||||
*/
|
||||
struct SideExitGuardData : IRExtraData {
|
||||
uint32_t checkedSlot;
|
||||
Offset taken;
|
||||
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(checkedSlot, ',', taken);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Compile-time metadata about an ActRec allocation.
|
||||
*/
|
||||
struct ActRecInfo : IRExtraData {
|
||||
const StringData* invName; // may be nullptr
|
||||
int32_t numArgs;
|
||||
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(numArgs, invName ? " M" : "");
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Stack offsets.
|
||||
*/
|
||||
struct StackOffset : IRExtraData {
|
||||
explicit StackOffset(int32_t offset) : offset(offset) {}
|
||||
|
||||
std::string show() const { return folly::to<std::string>(offset); }
|
||||
|
||||
bool cseEquals(StackOffset o) const { return offset == o.offset; }
|
||||
size_t cseHash() const { return std::hash<int32_t>()(offset); }
|
||||
|
||||
int32_t offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* Bytecode offsets.
|
||||
*/
|
||||
struct BCOffset : IRExtraData {
|
||||
explicit BCOffset(Offset offset) : offset(offset) {}
|
||||
std::string show() const { return folly::to<std::string>(offset); }
|
||||
Offset offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* DefInlineFP is present when we need to create a frame for inlining.
|
||||
* This instruction also carries some metadata used by tracebuilder to
|
||||
* track state during an inlined call.
|
||||
*/
|
||||
struct DefInlineFPData : IRExtraData {
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(
|
||||
target->fullName()->data(), "(),", retSPOff, ',', retBCOff
|
||||
);
|
||||
}
|
||||
|
||||
const Func* target;
|
||||
Offset retBCOff;
|
||||
Offset retSPOff;
|
||||
};
|
||||
|
||||
/*
|
||||
* FCallArray offsets
|
||||
*/
|
||||
struct CallArrayData : IRExtraData {
|
||||
explicit CallArrayData(Offset pcOffset, Offset aft)
|
||||
: pc(pcOffset), after(aft) {}
|
||||
|
||||
std::string show() const { return folly::to<std::string>(pc, ",", after); }
|
||||
|
||||
Offset pc, after;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define X(op, data) \
|
||||
template<> struct IRExtraDataType<op> { typedef data type; }; \
|
||||
template<> struct OpHasExtraData<op> { enum { value = 1 }; }; \
|
||||
static_assert(boost::has_trivial_destructor<data>::value, \
|
||||
"IR extra data type must be trivially destructible")
|
||||
|
||||
X(JmpSwitchDest, JmpSwitchData);
|
||||
X(LdSSwitchDestFast, LdSSwitchData);
|
||||
X(LdSSwitchDestSlow, LdSSwitchData);
|
||||
X(Marker, MarkerData);
|
||||
X(RaiseUninitLoc, LocalId);
|
||||
X(GuardLoc, LocalId);
|
||||
X(CheckLoc, LocalId);
|
||||
X(AssertLoc, LocalId);
|
||||
X(OverrideLoc, LocalId);
|
||||
X(LdLocAddr, LocalId);
|
||||
X(DecRefLoc, LocalId);
|
||||
X(LdLoc, LocalId);
|
||||
X(StLoc, LocalId);
|
||||
X(StLocNT, LocalId);
|
||||
X(DefConst, ConstData);
|
||||
X(LdConst, ConstData);
|
||||
X(Jmp_, EdgeData);
|
||||
X(SpillFrame, ActRecInfo);
|
||||
X(GuardStk, StackOffset);
|
||||
X(CheckStk, StackOffset);
|
||||
X(CastStk, StackOffset);
|
||||
X(AssertStk, StackOffset);
|
||||
X(ReDefSP, StackOffset);
|
||||
X(ReDefGeneratorSP, StackOffset);
|
||||
X(DefSP, StackOffset);
|
||||
X(LdStack, StackOffset);
|
||||
X(LdStackAddr, StackOffset);
|
||||
X(DecRefStack, StackOffset);
|
||||
X(DefInlineFP, DefInlineFPData);
|
||||
X(ReqBindJmp, BCOffset);
|
||||
X(ReqBindJmpNoIR, BCOffset);
|
||||
X(ReqRetranslateNoIR, BCOffset);
|
||||
X(InlineCreateCont, CreateContData);
|
||||
X(CallArray, CallArrayData);
|
||||
X(ReqBindJmpGt, ReqBindJccData);
|
||||
X(ReqBindJmpGte, ReqBindJccData);
|
||||
X(ReqBindJmpLt, ReqBindJccData);
|
||||
X(ReqBindJmpLte, ReqBindJccData);
|
||||
X(ReqBindJmpEq, ReqBindJccData);
|
||||
X(ReqBindJmpNeq, ReqBindJccData);
|
||||
X(ReqBindJmpSame, ReqBindJccData);
|
||||
X(ReqBindJmpNSame, ReqBindJccData);
|
||||
X(ReqBindJmpInstanceOfBitmask, ReqBindJccData);
|
||||
X(ReqBindJmpNInstanceOfBitmask, ReqBindJccData);
|
||||
X(ReqBindJmpZero, ReqBindJccData);
|
||||
X(ReqBindJmpNZero, ReqBindJccData);
|
||||
X(SideExitGuardLoc, SideExitGuardData);
|
||||
X(SideExitGuardStk, SideExitGuardData);
|
||||
|
||||
#undef X
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<bool hasExtra, Opcode opc, class T> struct AssertExtraTypes {
|
||||
static void doassert() {
|
||||
assert(!"called getExtra on an opcode without extra data");
|
||||
}
|
||||
};
|
||||
|
||||
template<Opcode opc, class T> struct AssertExtraTypes<true,opc,T> {
|
||||
static void doassert() {
|
||||
typedef typename IRExtraDataType<opc>::type ExtraType;
|
||||
if (!std::is_same<ExtraType,T>::value) {
|
||||
assert(!"getExtra<T> was called with an extra data "
|
||||
"type that doesn't match the opcode type");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Asserts that Opcode opc has extradata and it is of type T.
|
||||
template<class T> void assert_opcode_extra(Opcode opc) {
|
||||
#define O(opcode, dstinfo, srcinfo, flags) \
|
||||
case opcode: \
|
||||
AssertExtraTypes< \
|
||||
OpHasExtraData<opcode>::value,opcode,T \
|
||||
>::doassert(); \
|
||||
break;
|
||||
switch (opc) { IR_OPCODES default: not_reached(); }
|
||||
#undef O
|
||||
}
|
||||
|
||||
std::string showExtra(Opcode opc, const IRExtraData* data);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* A "query op" is any instruction returning Type::Bool that is both
|
||||
* branch-fusable and negateable.
|
||||
@@ -1627,290 +1301,6 @@ using folly::Range;
|
||||
typedef Range<SSATmp**> SrcRange;
|
||||
typedef Range<SSATmp*> DstRange;
|
||||
|
||||
/*
|
||||
* IRInstructions must be arena-allocatable.
|
||||
* (Destructors are not called when they come from IRFactory.)
|
||||
*/
|
||||
struct IRInstruction {
|
||||
enum Id { kTransient = 0xffffffff };
|
||||
|
||||
/*
|
||||
* Create an IRInstruction for the opcode `op'.
|
||||
*
|
||||
* IRInstruction creation is usually done through IRFactory or
|
||||
* TraceBuilder rather than directly.
|
||||
*/
|
||||
explicit IRInstruction(Opcode op,
|
||||
uint32_t numSrcs = 0,
|
||||
SSATmp** srcs = nullptr)
|
||||
: m_op(op)
|
||||
, m_typeParam(Type::None)
|
||||
, m_numSrcs(numSrcs)
|
||||
, m_numDsts(0)
|
||||
, m_id(kTransient)
|
||||
, m_srcs(srcs)
|
||||
, m_dst(nullptr)
|
||||
, m_taken(nullptr)
|
||||
, m_block(nullptr)
|
||||
, m_extra(nullptr)
|
||||
{}
|
||||
|
||||
IRInstruction(const IRInstruction&) = delete;
|
||||
IRInstruction& operator=(const IRInstruction&) = delete;
|
||||
|
||||
/*
|
||||
* Construct an IRInstruction as a deep copy of `inst', using
|
||||
* arena to allocate memory for its srcs/dests.
|
||||
*/
|
||||
explicit IRInstruction(Arena& arena, const IRInstruction* inst, Id id);
|
||||
|
||||
/*
|
||||
* Initialize the source list for this IRInstruction. We must not
|
||||
* have already had our sources initialized before this function is
|
||||
* called.
|
||||
*
|
||||
* Memory for `srcs' is owned outside of this class and must outlive
|
||||
* it.
|
||||
*/
|
||||
void initializeSrcs(uint32_t numSrcs, SSATmp** srcs) {
|
||||
assert(!m_srcs && !m_numSrcs);
|
||||
m_numSrcs = numSrcs;
|
||||
m_srcs = srcs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return access to extra-data on this instruction, for the
|
||||
* specified opcode type.
|
||||
*
|
||||
* Pre: op() == opc
|
||||
*/
|
||||
template<Opcode opc>
|
||||
const typename IRExtraDataType<opc>::type* getExtra() const {
|
||||
assert(opc == op() && "getExtra type error");
|
||||
assert(m_extra != nullptr);
|
||||
return static_cast<typename IRExtraDataType<opc>::type*>(m_extra);
|
||||
}
|
||||
|
||||
template<Opcode opc>
|
||||
typename IRExtraDataType<opc>::type* getExtra() {
|
||||
assert(opc == op() && "getExtra type error");
|
||||
return static_cast<typename IRExtraDataType<opc>::type*>(m_extra);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return access to extra-data of type T. Requires that
|
||||
* IRExtraDataType<opc>::type is T for this instruction's opcode.
|
||||
*
|
||||
* It's normally preferable to use the version of this function that
|
||||
* takes the opcode instead of this one. This is for writing code
|
||||
* that is supposed to be able to handle multiple opcode types that
|
||||
* share the same kind of extra data.
|
||||
*/
|
||||
template<class T> const T* getExtra() const {
|
||||
auto opcode = op();
|
||||
if (debug) assert_opcode_extra<T>(opcode);
|
||||
return static_cast<const T*>(m_extra);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether or not this opcode has an associated extra data
|
||||
* struct.
|
||||
*/
|
||||
bool hasExtra() const;
|
||||
|
||||
/*
|
||||
* Set the extra-data for this IRInstruction to the given pointer.
|
||||
* Lifetime is must outlast this IRInstruction (and any of its
|
||||
* clones).
|
||||
*/
|
||||
void setExtra(IRExtraData* data) { assert(!m_extra); m_extra = data; }
|
||||
|
||||
/*
|
||||
* Return the raw extradata pointer, for pretty-printing.
|
||||
*/
|
||||
const IRExtraData* rawExtra() const { return m_extra; }
|
||||
|
||||
/*
|
||||
* Clear the extra data pointer in a IRInstruction. Used during
|
||||
* IRFactory::gen to avoid having dangling IRExtraData*'s into stack
|
||||
* memory.
|
||||
*/
|
||||
void clearExtra() { m_extra = nullptr; }
|
||||
|
||||
/*
|
||||
* Replace an instruction in place with a Nop. This sometimes may
|
||||
* be a result of a simplification pass.
|
||||
*/
|
||||
void convertToNop();
|
||||
|
||||
/*
|
||||
* Replace a branch with a Jmp; used when we have proven the branch
|
||||
* is always taken.
|
||||
*/
|
||||
void convertToJmp();
|
||||
|
||||
/*
|
||||
* Replace an instruction in place with a Mov. Used when we have
|
||||
* proven that the instruction's side effects are not needed.
|
||||
*
|
||||
* TODO: replace with become
|
||||
*/
|
||||
void convertToMov();
|
||||
|
||||
/*
|
||||
* Turns this instruction into the target instruction, without
|
||||
* changing stable fields (id, current block, list fields). The
|
||||
* existing destination SSATmp(s) will continue to think they came
|
||||
* from this instruction.
|
||||
*
|
||||
* The target instruction may be transient---we'll clone anything we
|
||||
* need to keep, using factory for any needed memory.
|
||||
*
|
||||
* Note: if you want to use this to replace a CSE-able instruction
|
||||
* you're probably going to have a bad time. For now it's a
|
||||
* precondition that the current instruction can't CSE.
|
||||
*
|
||||
* Pre: other->isTransient() || numDsts() == other->numDsts()
|
||||
* Pre: !canCSE()
|
||||
*/
|
||||
void become(IRFactory*, IRInstruction* other);
|
||||
|
||||
/*
|
||||
* Deep-copy an IRInstruction, using factory to allocate memory for
|
||||
* the IRInstruction itself, and its srcs/dests.
|
||||
*/
|
||||
IRInstruction* clone(IRFactory* factory) const;
|
||||
|
||||
Opcode op() const { return m_op; }
|
||||
void setOpcode(Opcode newOpc) { m_op = newOpc; }
|
||||
Type getTypeParam() const { return m_typeParam; }
|
||||
void setTypeParam(Type t) { m_typeParam = t; }
|
||||
uint32_t getNumSrcs() const { return m_numSrcs; }
|
||||
void setNumSrcs(uint32_t i) {
|
||||
assert(i <= m_numSrcs);
|
||||
m_numSrcs = i;
|
||||
}
|
||||
SSATmp* getSrc(uint32_t i) const;
|
||||
void setSrc(uint32_t i, SSATmp* newSrc);
|
||||
SrcRange getSrcs() const {
|
||||
return SrcRange(m_srcs, m_numSrcs);
|
||||
}
|
||||
unsigned getNumDsts() const { return m_numDsts; }
|
||||
SSATmp* getDst() const {
|
||||
assert(!naryDst());
|
||||
return m_dst;
|
||||
}
|
||||
void setDst(SSATmp* newDst) {
|
||||
assert(hasDst());
|
||||
m_dst = newDst;
|
||||
m_numDsts = newDst ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the ith dest of this instruction. i == 0 is treated specially: if
|
||||
* the instruction has no dests, getDst(0) will return nullptr, and if the
|
||||
* instruction is not naryDest, getDst(0) will return the single dest.
|
||||
*/
|
||||
SSATmp* getDst(unsigned i) const;
|
||||
DstRange getDsts();
|
||||
Range<const SSATmp*> getDsts() const;
|
||||
void setDsts(unsigned numDsts, SSATmp* newDsts) {
|
||||
assert(naryDst());
|
||||
m_numDsts = numDsts;
|
||||
m_dst = newDsts;
|
||||
}
|
||||
|
||||
/*
|
||||
* Instruction id is stable and useful as an array index.
|
||||
*/
|
||||
uint32_t getId() const {
|
||||
assert(m_id != kTransient);
|
||||
return m_id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the instruction is in a transient state. That
|
||||
* is, it's allocated on the stack and we haven't yet committed to
|
||||
* inserting it in any blocks.
|
||||
*/
|
||||
bool isTransient() const { return m_id == kTransient; }
|
||||
|
||||
Block* getBlock() const { return m_block; }
|
||||
void setBlock(Block* b) { m_block = b; }
|
||||
Trace* getTrace() const;
|
||||
void setTaken(Block* b);
|
||||
Block* getTaken() const { return m_taken; }
|
||||
|
||||
bool isControlFlowInstruction() const { return m_taken != nullptr; }
|
||||
bool isBlockEnd() const { return m_taken || isTerminal(); }
|
||||
|
||||
/*
|
||||
* Comparison and hashing for the purposes of CSE-equality.
|
||||
*
|
||||
* Pre: canCSE()
|
||||
*/
|
||||
bool cseEquals(IRInstruction* inst) const;
|
||||
size_t cseHash() const;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
/*
|
||||
* Helper accessors for the OpcodeFlag bits for this instruction.
|
||||
*
|
||||
* Note that these wrappers have additional logic beyond just
|
||||
* checking the corresponding flags bit---you should generally use
|
||||
* these when you have an actual IRInstruction instead of just an
|
||||
* Opcode enum value.
|
||||
*/
|
||||
bool canCSE() const;
|
||||
bool hasDst() const;
|
||||
bool naryDst() const;
|
||||
bool hasMemEffects() const;
|
||||
bool isRematerializable() const;
|
||||
bool isNative() const;
|
||||
bool consumesReferences() const;
|
||||
bool consumesReference(int srcNo) const;
|
||||
bool producesReference() const;
|
||||
bool mayModifyRefs() const;
|
||||
bool mayRaiseError() const;
|
||||
bool isEssential() const;
|
||||
bool isTerminal() const;
|
||||
bool isPassthrough() const;
|
||||
SSATmp* getPassthroughValue() const;
|
||||
bool killsSources() const;
|
||||
bool killsSource(int srcNo) const;
|
||||
|
||||
bool modifiesStack() const;
|
||||
SSATmp* modifiedStkPtr() const;
|
||||
// hasMainDst provides raw access to the HasDest flag, for instructions with
|
||||
// ModifiesStack set.
|
||||
bool hasMainDst() const;
|
||||
|
||||
private:
|
||||
bool mayReenterHelper() const;
|
||||
|
||||
private:
|
||||
Opcode m_op;
|
||||
Type m_typeParam;
|
||||
uint16_t m_numSrcs;
|
||||
uint16_t m_numDsts;
|
||||
const Id m_id;
|
||||
SSATmp** m_srcs;
|
||||
SSATmp* m_dst; // if HasDest or NaryDest
|
||||
Block* m_taken; // for branches, guards, and jmp
|
||||
Block* m_block; // block that owns this instruction
|
||||
IRExtraData* m_extra;
|
||||
public:
|
||||
boost::intrusive::list_member_hook<> m_listNode; // for InstructionList
|
||||
};
|
||||
|
||||
typedef boost::intrusive::member_hook<IRInstruction,
|
||||
boost::intrusive::list_member_hook<>,
|
||||
&IRInstruction::m_listNode>
|
||||
IRInstructionHookOption;
|
||||
typedef boost::intrusive::list<IRInstruction, IRInstructionHookOption>
|
||||
InstructionList;
|
||||
|
||||
/*
|
||||
* Given an SSATmp of type Cls, try to find the name of the class.
|
||||
* Returns nullptr if can't find it.
|
||||
@@ -1930,100 +1320,17 @@ Type outputType(const IRInstruction*, int dstId = 0);
|
||||
*/
|
||||
void assertOperandTypes(const IRInstruction*);
|
||||
|
||||
class SSATmp {
|
||||
public:
|
||||
uint32_t getId() const { return m_id; }
|
||||
IRInstruction* inst() const { return m_inst; }
|
||||
void setInstruction(IRInstruction* i) { m_inst = i; }
|
||||
Type type() const { return m_type; }
|
||||
void setType(Type t) { m_type = t; }
|
||||
bool isBoxed() const { return type().isBoxed(); }
|
||||
bool isString() const { return isA(Type::Str); }
|
||||
bool isArray() const { return isA(Type::Arr); }
|
||||
std::string toString() const;
|
||||
|
||||
// XXX: false for Null, etc. Would rather it returns whether we
|
||||
// have a compile-time constant value.
|
||||
bool isConst() const {
|
||||
return m_inst->op() == DefConst ||
|
||||
m_inst->op() == LdConst;
|
||||
}
|
||||
|
||||
/*
|
||||
* For SSATmps with a compile-time constant value, the following
|
||||
* functions allow accessing it.
|
||||
*
|
||||
* Pre: inst() &&
|
||||
* (inst()->op() == DefConst ||
|
||||
* inst()->op() == LdConst)
|
||||
*/
|
||||
bool getValBool() const;
|
||||
int64_t getValInt() const;
|
||||
int64_t getValRawInt() const;
|
||||
double getValDbl() const;
|
||||
const StringData* getValStr() const;
|
||||
const ArrayData* getValArr() const;
|
||||
const Func* getValFunc() const;
|
||||
const Class* getValClass() const;
|
||||
const NamedEntity* getValNamedEntity() const;
|
||||
uintptr_t getValBits() const;
|
||||
Variant getValVariant() const;
|
||||
TCA getValTCA() const;
|
||||
|
||||
/*
|
||||
* Returns: Type::subtypeOf(type(), tag).
|
||||
*
|
||||
* This should be used for most checks on the types of IRInstruction
|
||||
* sources.
|
||||
*/
|
||||
bool isA(Type tag) const {
|
||||
return type().subtypeOf(tag);
|
||||
}
|
||||
|
||||
/*
|
||||
* The maximum number of registers this SSATmp may need allocated.
|
||||
* This is based on the type of the temporary (some types never have
|
||||
* regs, some have two, etc).
|
||||
*/
|
||||
int numNeededRegs() const;
|
||||
|
||||
private:
|
||||
friend class IRFactory;
|
||||
friend class TraceBuilder;
|
||||
|
||||
// May only be created via IRFactory. Note that this class is never
|
||||
// destructed, so don't add complex members.
|
||||
SSATmp(uint32_t opndId, IRInstruction* i, int dstId = 0)
|
||||
: m_inst(i)
|
||||
, m_type(outputType(i, dstId))
|
||||
, m_id(opndId)
|
||||
{}
|
||||
SSATmp(const SSATmp&);
|
||||
SSATmp& operator=(const SSATmp&);
|
||||
|
||||
IRInstruction* m_inst;
|
||||
Type m_type; // type when defined
|
||||
const uint32_t m_id;
|
||||
};
|
||||
|
||||
int vectorBaseIdx(Opcode opc);
|
||||
int vectorKeyIdx(Opcode opc);
|
||||
int vectorValIdx(Opcode opc);
|
||||
inline int vectorBaseIdx(const IRInstruction* inst) {
|
||||
return vectorBaseIdx(inst->op());
|
||||
}
|
||||
inline int vectorKeyIdx(const IRInstruction* inst) {
|
||||
return vectorKeyIdx(inst->op());
|
||||
}
|
||||
inline int vectorValIdx(const IRInstruction* inst) {
|
||||
return vectorValIdx(inst->op());
|
||||
}
|
||||
int vectorBaseIdx(const IRInstruction* inst);
|
||||
int vectorKeyIdx(const IRInstruction* inst);
|
||||
int vectorValIdx(const IRInstruction* inst);
|
||||
|
||||
struct VectorEffects {
|
||||
static bool supported(Opcode op);
|
||||
static bool supported(const IRInstruction* inst) {
|
||||
return supported(inst->op());
|
||||
}
|
||||
static bool supported(const IRInstruction* inst);
|
||||
|
||||
/*
|
||||
* VectorEffects::get is used to allow multiple different consumers to deal
|
||||
@@ -2041,34 +1348,10 @@ struct VectorEffects {
|
||||
StoreLocFunc storeLocValue,
|
||||
SetLocTypeFunc setLocType);
|
||||
|
||||
explicit VectorEffects(const IRInstruction* inst) {
|
||||
int keyIdx = vectorKeyIdx(inst);
|
||||
int valIdx = vectorValIdx(inst);
|
||||
init(inst->op(),
|
||||
inst->getSrc(vectorBaseIdx(inst))->type(),
|
||||
keyIdx == -1 ? Type::None : inst->getSrc(keyIdx)->type(),
|
||||
valIdx == -1 ? Type::None : inst->getSrc(valIdx)->type());
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
VectorEffects(Opcode opc, const Container& srcs) {
|
||||
int keyIdx = vectorKeyIdx(opc);
|
||||
int valIdx = vectorValIdx(opc);
|
||||
init(opc,
|
||||
srcs[vectorBaseIdx(opc)]->type(),
|
||||
keyIdx == -1 ? Type::None : srcs[keyIdx]->type(),
|
||||
valIdx == -1 ? Type::None : srcs[valIdx]->type());
|
||||
}
|
||||
|
||||
VectorEffects(Opcode op, Type base, Type key, Type val) {
|
||||
init(op, base, key, val);
|
||||
}
|
||||
|
||||
VectorEffects(Opcode op, SSATmp* base, SSATmp* key, SSATmp* val) {
|
||||
auto typeOrNone =
|
||||
[](SSATmp* val){ return val ? val->type() : Type::None; };
|
||||
init(op, typeOrNone(base), typeOrNone(key), typeOrNone(val));
|
||||
}
|
||||
explicit VectorEffects(const IRInstruction* inst);
|
||||
VectorEffects(Opcode op, Type base, Type key, Type val);
|
||||
VectorEffects(Opcode op, SSATmp* base, SSATmp* key, SSATmp* val);
|
||||
VectorEffects(Opcode opc, const std::vector<SSATmp*>& srcs);
|
||||
|
||||
Type baseType;
|
||||
Type valType;
|
||||
@@ -2082,161 +1365,6 @@ private:
|
||||
|
||||
typedef folly::Range<TCA> TcaRange;
|
||||
|
||||
/*
|
||||
* A Block refers to a basic block: single-entry, single-exit, list of
|
||||
* instructions. The instruction list is an intrusive list, so each
|
||||
* instruction can only be in one block at a time. Likewise, a block
|
||||
* can only be owned by one trace at a time.
|
||||
*
|
||||
* Block owns the InstructionList, but exposes several list methods itself
|
||||
* so usually you can use Block directly. These methods also update
|
||||
* IRInstruction::m_block transparently.
|
||||
*/
|
||||
struct Block : boost::noncopyable {
|
||||
typedef InstructionList::iterator iterator;
|
||||
typedef InstructionList::const_iterator const_iterator;
|
||||
|
||||
// Execution frequency hint; codegen will put Unlikely blocks in astubs.
|
||||
enum Hint { Neither, Likely, Unlikely };
|
||||
|
||||
Block(unsigned id, const Func* func, IRInstruction* label)
|
||||
: m_trace(nullptr)
|
||||
, m_func(func)
|
||||
, m_next(nullptr)
|
||||
, m_id(id)
|
||||
, m_preds(nullptr)
|
||||
, m_hint(Neither)
|
||||
{
|
||||
push_back(label);
|
||||
}
|
||||
|
||||
IRInstruction* getLabel() const {
|
||||
assert(front()->op() == DefLabel);
|
||||
return front();
|
||||
}
|
||||
|
||||
uint32_t getId() const { return m_id; }
|
||||
Trace* getTrace() const { return m_trace; }
|
||||
void setTrace(Trace* t) { m_trace = t; }
|
||||
void setHint(Hint hint) { m_hint = hint; }
|
||||
Hint getHint() const { return m_hint; }
|
||||
|
||||
void addEdge(IRInstruction* jmp);
|
||||
void removeEdge(IRInstruction* jmp);
|
||||
|
||||
bool isMain() const;
|
||||
|
||||
// return the last instruction in the block
|
||||
IRInstruction* back() const {
|
||||
assert(!m_instrs.empty());
|
||||
auto it = m_instrs.end();
|
||||
return const_cast<IRInstruction*>(&*(--it));
|
||||
}
|
||||
|
||||
// return the first instruction in the block.
|
||||
IRInstruction* front() const {
|
||||
assert(!m_instrs.empty());
|
||||
return const_cast<IRInstruction*>(&*m_instrs.begin());
|
||||
}
|
||||
|
||||
// return the fallthrough block. Should be nullptr if the last
|
||||
// instruction is a Terminal.
|
||||
Block* getNext() const { return m_next; }
|
||||
void setNext(Block* b) { m_next = b; }
|
||||
|
||||
// return the target block if the last instruction is a branch.
|
||||
Block* getTaken() const {
|
||||
return back()->getTaken();
|
||||
}
|
||||
|
||||
// return the postorder number of this block. (updated each time
|
||||
// sortBlocks() is called.
|
||||
unsigned postId() const { return m_postid; }
|
||||
void setPostId(unsigned id) { m_postid = id; }
|
||||
|
||||
// insert inst after this block's label, return an iterator to the
|
||||
// newly inserted instruction.
|
||||
iterator prepend(IRInstruction* inst) {
|
||||
assert(front()->op() == DefLabel);
|
||||
auto it = begin();
|
||||
return insert(++it, inst);
|
||||
}
|
||||
|
||||
// return iterator to first instruction after the label
|
||||
iterator skipLabel() { auto it = begin(); return ++it; }
|
||||
|
||||
// return iterator to last instruction
|
||||
iterator backIter() { auto it = end(); return --it; }
|
||||
|
||||
// return an iterator to a specific instruction
|
||||
iterator iteratorTo(IRInstruction* inst) {
|
||||
assert(inst->getBlock() == this);
|
||||
return m_instrs.iterator_to(*inst);
|
||||
}
|
||||
|
||||
// visit each src that provides a value to label->dsts[i]. body
|
||||
// should take an IRInstruction* and an SSATmp*.
|
||||
template<typename L>
|
||||
void forEachSrc(unsigned i, L body) {
|
||||
for (const EdgeData* n = m_preds; n; n = n->next) {
|
||||
assert(n->jmp->op() == Jmp_ && n->jmp->getTaken() == this);
|
||||
body(n->jmp, n->jmp->getSrc(i));
|
||||
}
|
||||
}
|
||||
|
||||
// return the first src providing a value to label->dsts[i] for
|
||||
// which body(src) returns true, or nullptr if none are found.
|
||||
template<typename L>
|
||||
SSATmp* findSrc(unsigned i, L body) {
|
||||
for (const EdgeData* n = m_preds; n; n = n->next) {
|
||||
SSATmp* src = n->jmp->getSrc(i);
|
||||
if (body(src)) return src;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// list-compatible interface; these delegate to m_instrs but also update
|
||||
// inst.m_block
|
||||
InstructionList& getInstrs() { return m_instrs; }
|
||||
bool empty() const { return m_instrs.empty(); }
|
||||
iterator begin() { return m_instrs.begin(); }
|
||||
iterator end() { return m_instrs.end(); }
|
||||
const_iterator begin() const { return m_instrs.begin(); }
|
||||
const_iterator end() const { return m_instrs.end(); }
|
||||
|
||||
iterator insert(iterator pos, IRInstruction* inst) {
|
||||
inst->setBlock(this);
|
||||
return m_instrs.insert(pos, *inst);
|
||||
}
|
||||
void splice(iterator pos, Block* from, iterator begin, iterator end) {
|
||||
assert(from != this);
|
||||
for (auto i = begin; i != end; ++i) (*i).setBlock(this);
|
||||
m_instrs.splice(pos, from->getInstrs(), begin, end);
|
||||
}
|
||||
void push_back(IRInstruction* inst) {
|
||||
inst->setBlock(this);
|
||||
return m_instrs.push_back(*inst);
|
||||
}
|
||||
template <class Predicate> void remove_if(Predicate p) {
|
||||
m_instrs.remove_if(p);
|
||||
}
|
||||
void erase(iterator pos) {
|
||||
m_instrs.erase(pos);
|
||||
}
|
||||
|
||||
private:
|
||||
InstructionList m_instrs; // instructions in this block
|
||||
Trace* m_trace; // owner of this block.
|
||||
const Func* m_func; // which func are we in
|
||||
Block* m_next; // fall-through path; null if back()->isTerminal().
|
||||
const unsigned m_id; // factory-assigned unique id of this block
|
||||
unsigned m_postid; // postorder number of this block
|
||||
EdgeData* m_preds; // head of list of predecessor Jmps
|
||||
Hint m_hint; // execution frequency hint
|
||||
};
|
||||
typedef std::list<Block*> BlockList;
|
||||
typedef std::forward_list<Block*> BlockPtrList;
|
||||
|
||||
/*
|
||||
* Remove any instruction if live[iid] == false
|
||||
*/
|
||||
@@ -2254,17 +1382,7 @@ void optimizeTrace(Trace*, IRFactory* irFactory);
|
||||
*/
|
||||
int32_t spillValueCells(IRInstruction* spillStack);
|
||||
|
||||
inline bool isConvIntOrPtrToBool(IRInstruction* instr) {
|
||||
switch (instr->op()) {
|
||||
case ConvIntToBool:
|
||||
return true;
|
||||
case ConvCellToBool:
|
||||
return instr->getSrc(0)->type().subtypeOfAny(
|
||||
Type::Func, Type::Cls, Type::FuncCls, Type::VarEnv, Type::TCA);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool isConvIntOrPtrToBool(IRInstruction* instr);
|
||||
|
||||
}}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/irfactory.h"
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/cfg.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/block.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_VM_IRINSTRUCTION_H_
|
||||
#define incl_HPHP_VM_IRINSTRUCTION_H_
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/extradata.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
/*
|
||||
* IRInstructions must be arena-allocatable.
|
||||
* (Destructors are not called when they come from IRFactory.)
|
||||
*/
|
||||
struct IRInstruction {
|
||||
enum Id { kTransient = 0xffffffff };
|
||||
|
||||
/*
|
||||
* Create an IRInstruction for the opcode `op'.
|
||||
*
|
||||
* IRInstruction creation is usually done through IRFactory or
|
||||
* TraceBuilder rather than directly.
|
||||
*/
|
||||
explicit IRInstruction(Opcode op,
|
||||
uint32_t numSrcs = 0,
|
||||
SSATmp** srcs = nullptr)
|
||||
: m_op(op)
|
||||
, m_typeParam(Type::None)
|
||||
, m_numSrcs(numSrcs)
|
||||
, m_numDsts(0)
|
||||
, m_id(kTransient)
|
||||
, m_srcs(srcs)
|
||||
, m_dst(nullptr)
|
||||
, m_taken(nullptr)
|
||||
, m_block(nullptr)
|
||||
, m_extra(nullptr)
|
||||
{}
|
||||
|
||||
IRInstruction(const IRInstruction&) = delete;
|
||||
IRInstruction& operator=(const IRInstruction&) = delete;
|
||||
|
||||
/*
|
||||
* Construct an IRInstruction as a deep copy of `inst', using
|
||||
* arena to allocate memory for its srcs/dests.
|
||||
*/
|
||||
explicit IRInstruction(Arena& arena, const IRInstruction* inst, Id id);
|
||||
|
||||
/*
|
||||
* Initialize the source list for this IRInstruction. We must not
|
||||
* have already had our sources initialized before this function is
|
||||
* called.
|
||||
*
|
||||
* Memory for `srcs' is owned outside of this class and must outlive
|
||||
* it.
|
||||
*/
|
||||
void initializeSrcs(uint32_t numSrcs, SSATmp** srcs) {
|
||||
assert(!m_srcs && !m_numSrcs);
|
||||
m_numSrcs = numSrcs;
|
||||
m_srcs = srcs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return access to extra-data on this instruction, for the
|
||||
* specified opcode type.
|
||||
*
|
||||
* Pre: op() == opc
|
||||
*/
|
||||
template<Opcode opc>
|
||||
const typename IRExtraDataType<opc>::type* getExtra() const {
|
||||
assert(opc == op() && "getExtra type error");
|
||||
assert(m_extra != nullptr);
|
||||
return static_cast<typename IRExtraDataType<opc>::type*>(m_extra);
|
||||
}
|
||||
|
||||
template<Opcode opc>
|
||||
typename IRExtraDataType<opc>::type* getExtra() {
|
||||
assert(opc == op() && "getExtra type error");
|
||||
return static_cast<typename IRExtraDataType<opc>::type*>(m_extra);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return access to extra-data of type T. Requires that
|
||||
* IRExtraDataType<opc>::type is T for this instruction's opcode.
|
||||
*
|
||||
* It's normally preferable to use the version of this function that
|
||||
* takes the opcode instead of this one. This is for writing code
|
||||
* that is supposed to be able to handle multiple opcode types that
|
||||
* share the same kind of extra data.
|
||||
*/
|
||||
template<class T> const T* getExtra() const {
|
||||
auto opcode = op();
|
||||
if (debug) assert_opcode_extra<T>(opcode);
|
||||
return static_cast<const T*>(m_extra);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether or not this opcode has an associated extra data
|
||||
* struct.
|
||||
*/
|
||||
bool hasExtra() const;
|
||||
|
||||
/*
|
||||
* Set the extra-data for this IRInstruction to the given pointer.
|
||||
* Lifetime is must outlast this IRInstruction (and any of its
|
||||
* clones).
|
||||
*/
|
||||
void setExtra(IRExtraData* data) { assert(!m_extra); m_extra = data; }
|
||||
|
||||
/*
|
||||
* Return the raw extradata pointer, for pretty-printing.
|
||||
*/
|
||||
const IRExtraData* rawExtra() const { return m_extra; }
|
||||
|
||||
/*
|
||||
* Clear the extra data pointer in a IRInstruction. Used during
|
||||
* IRFactory::gen to avoid having dangling IRExtraData*'s into stack
|
||||
* memory.
|
||||
*/
|
||||
void clearExtra() { m_extra = nullptr; }
|
||||
|
||||
/*
|
||||
* Replace an instruction in place with a Nop. This sometimes may
|
||||
* be a result of a simplification pass.
|
||||
*/
|
||||
void convertToNop();
|
||||
|
||||
/*
|
||||
* Replace a branch with a Jmp; used when we have proven the branch
|
||||
* is always taken.
|
||||
*/
|
||||
void convertToJmp();
|
||||
|
||||
/*
|
||||
* Replace an instruction in place with a Mov. Used when we have
|
||||
* proven that the instruction's side effects are not needed.
|
||||
*
|
||||
* TODO: replace with become
|
||||
*/
|
||||
void convertToMov();
|
||||
|
||||
/*
|
||||
* Turns this instruction into the target instruction, without
|
||||
* changing stable fields (id, current block, list fields). The
|
||||
* existing destination SSATmp(s) will continue to think they came
|
||||
* from this instruction.
|
||||
*
|
||||
* The target instruction may be transient---we'll clone anything we
|
||||
* need to keep, using factory for any needed memory.
|
||||
*
|
||||
* Note: if you want to use this to replace a CSE-able instruction
|
||||
* you're probably going to have a bad time. For now it's a
|
||||
* precondition that the current instruction can't CSE.
|
||||
*
|
||||
* Pre: other->isTransient() || numDsts() == other->numDsts()
|
||||
* Pre: !canCSE()
|
||||
*/
|
||||
void become(IRFactory*, IRInstruction* other);
|
||||
|
||||
/*
|
||||
* Deep-copy an IRInstruction, using factory to allocate memory for
|
||||
* the IRInstruction itself, and its srcs/dests.
|
||||
*/
|
||||
IRInstruction* clone(IRFactory* factory) const;
|
||||
|
||||
Opcode op() const { return m_op; }
|
||||
void setOpcode(Opcode newOpc) { m_op = newOpc; }
|
||||
Type getTypeParam() const { return m_typeParam; }
|
||||
void setTypeParam(Type t) { m_typeParam = t; }
|
||||
uint32_t getNumSrcs() const { return m_numSrcs; }
|
||||
void setNumSrcs(uint32_t i) {
|
||||
assert(i <= m_numSrcs);
|
||||
m_numSrcs = i;
|
||||
}
|
||||
SSATmp* getSrc(uint32_t i) const;
|
||||
void setSrc(uint32_t i, SSATmp* newSrc);
|
||||
SrcRange getSrcs() const {
|
||||
return SrcRange(m_srcs, m_numSrcs);
|
||||
}
|
||||
unsigned getNumDsts() const { return m_numDsts; }
|
||||
SSATmp* getDst() const {
|
||||
assert(!naryDst());
|
||||
return m_dst;
|
||||
}
|
||||
void setDst(SSATmp* newDst) {
|
||||
assert(hasDst());
|
||||
m_dst = newDst;
|
||||
m_numDsts = newDst ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the ith dest of this instruction. i == 0 is treated specially: if
|
||||
* the instruction has no dests, getDst(0) will return nullptr, and if the
|
||||
* instruction is not naryDest, getDst(0) will return the single dest.
|
||||
*/
|
||||
SSATmp* getDst(unsigned i) const;
|
||||
DstRange getDsts();
|
||||
Range<const SSATmp*> getDsts() const;
|
||||
void setDsts(unsigned numDsts, SSATmp* newDsts) {
|
||||
assert(naryDst());
|
||||
m_numDsts = numDsts;
|
||||
m_dst = newDsts;
|
||||
}
|
||||
|
||||
/*
|
||||
* Instruction id is stable and useful as an array index.
|
||||
*/
|
||||
uint32_t getId() const {
|
||||
assert(m_id != kTransient);
|
||||
return m_id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the instruction is in a transient state. That
|
||||
* is, it's allocated on the stack and we haven't yet committed to
|
||||
* inserting it in any blocks.
|
||||
*/
|
||||
bool isTransient() const { return m_id == kTransient; }
|
||||
|
||||
Block* getBlock() const { return m_block; }
|
||||
void setBlock(Block* b) { m_block = b; }
|
||||
Trace* getTrace() const;
|
||||
void setTaken(Block* b);
|
||||
Block* getTaken() const { return m_taken; }
|
||||
|
||||
bool isControlFlowInstruction() const { return m_taken != nullptr; }
|
||||
bool isBlockEnd() const { return m_taken || isTerminal(); }
|
||||
|
||||
/*
|
||||
* Comparison and hashing for the purposes of CSE-equality.
|
||||
*
|
||||
* Pre: canCSE()
|
||||
*/
|
||||
bool cseEquals(IRInstruction* inst) const;
|
||||
size_t cseHash() const;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
/*
|
||||
* Helper accessors for the OpcodeFlag bits for this instruction.
|
||||
*
|
||||
* Note that these wrappers have additional logic beyond just
|
||||
* checking the corresponding flags bit---you should generally use
|
||||
* these when you have an actual IRInstruction instead of just an
|
||||
* Opcode enum value.
|
||||
*/
|
||||
bool canCSE() const;
|
||||
bool hasDst() const;
|
||||
bool naryDst() const;
|
||||
bool hasMemEffects() const;
|
||||
bool isRematerializable() const;
|
||||
bool isNative() const;
|
||||
bool consumesReferences() const;
|
||||
bool consumesReference(int srcNo) const;
|
||||
bool producesReference() const;
|
||||
bool mayModifyRefs() const;
|
||||
bool mayRaiseError() const;
|
||||
bool isEssential() const;
|
||||
bool isTerminal() const;
|
||||
bool isPassthrough() const;
|
||||
SSATmp* getPassthroughValue() const;
|
||||
bool killsSources() const;
|
||||
bool killsSource(int srcNo) const;
|
||||
|
||||
bool modifiesStack() const;
|
||||
SSATmp* modifiedStkPtr() const;
|
||||
// hasMainDst provides raw access to the HasDest flag, for instructions with
|
||||
// ModifiesStack set.
|
||||
bool hasMainDst() const;
|
||||
|
||||
private:
|
||||
bool mayReenterHelper() const;
|
||||
|
||||
private:
|
||||
Opcode m_op;
|
||||
Type m_typeParam;
|
||||
uint16_t m_numSrcs;
|
||||
uint16_t m_numDsts;
|
||||
const Id m_id;
|
||||
SSATmp** m_srcs;
|
||||
SSATmp* m_dst; // if HasDest or NaryDest
|
||||
Block* m_taken; // for branches, guards, and jmp
|
||||
Block* m_block; // block that owns this instruction
|
||||
IRExtraData* m_extra;
|
||||
public:
|
||||
boost::intrusive::list_member_hook<> m_listNode; // for InstructionList
|
||||
};
|
||||
|
||||
typedef boost::intrusive::member_hook<IRInstruction,
|
||||
boost::intrusive::list_member_hook<>,
|
||||
&IRInstruction::m_listNode>
|
||||
IRInstructionHookOption;
|
||||
typedef boost::intrusive::list<IRInstruction, IRInstructionHookOption>
|
||||
InstructionList;
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_VM_SSATMP_H_
|
||||
#define incl_HPHP_VM_SSATMP_H_
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
class SSATmp {
|
||||
public:
|
||||
uint32_t getId() const { return m_id; }
|
||||
IRInstruction* inst() const { return m_inst; }
|
||||
void setInstruction(IRInstruction* i) { m_inst = i; }
|
||||
Type type() const { return m_type; }
|
||||
void setType(Type t) { m_type = t; }
|
||||
bool isBoxed() const { return type().isBoxed(); }
|
||||
bool isString() const { return isA(Type::Str); }
|
||||
bool isArray() const { return isA(Type::Arr); }
|
||||
std::string toString() const;
|
||||
|
||||
// XXX: false for Null, etc. Would rather it returns whether we
|
||||
// have a compile-time constant value.
|
||||
bool isConst() const {
|
||||
return m_inst->op() == DefConst ||
|
||||
m_inst->op() == LdConst;
|
||||
}
|
||||
|
||||
/*
|
||||
* For SSATmps with a compile-time constant value, the following
|
||||
* functions allow accessing it.
|
||||
*
|
||||
* Pre: inst() &&
|
||||
* (inst()->op() == DefConst ||
|
||||
* inst()->op() == LdConst)
|
||||
*/
|
||||
bool getValBool() const;
|
||||
int64_t getValInt() const;
|
||||
int64_t getValRawInt() const;
|
||||
double getValDbl() const;
|
||||
const StringData* getValStr() const;
|
||||
const ArrayData* getValArr() const;
|
||||
const Func* getValFunc() const;
|
||||
const Class* getValClass() const;
|
||||
const NamedEntity* getValNamedEntity() const;
|
||||
uintptr_t getValBits() const;
|
||||
Variant getValVariant() const;
|
||||
TCA getValTCA() const;
|
||||
|
||||
/*
|
||||
* Returns: Type::subtypeOf(type(), tag).
|
||||
*
|
||||
* This should be used for most checks on the types of IRInstruction
|
||||
* sources.
|
||||
*/
|
||||
bool isA(Type tag) const {
|
||||
return type().subtypeOf(tag);
|
||||
}
|
||||
|
||||
/*
|
||||
* The maximum number of registers this SSATmp may need allocated.
|
||||
* This is based on the type of the temporary (some types never have
|
||||
* regs, some have two, etc).
|
||||
*/
|
||||
int numNeededRegs() const;
|
||||
|
||||
private:
|
||||
friend class IRFactory;
|
||||
friend class TraceBuilder;
|
||||
|
||||
// May only be created via IRFactory. Note that this class is never
|
||||
// destructed, so don't add complex members.
|
||||
SSATmp(uint32_t opndId, IRInstruction* i, int dstId = 0)
|
||||
: m_inst(i)
|
||||
, m_type(outputType(i, dstId))
|
||||
, m_id(opndId)
|
||||
{}
|
||||
SSATmp(const SSATmp&);
|
||||
SSATmp& operator=(const SSATmp&);
|
||||
|
||||
IRInstruction* m_inst;
|
||||
Type m_type; // type when defined
|
||||
const uint32_t m_id;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef incl_HPHP_VM_TRACE_H_
|
||||
#define incl_HPHP_VM_TRACE_H_
|
||||
|
||||
#include "hphp/runtime/vm/translator/hopt/block.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#include "hphp/util/trace.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/irinstruction.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/ssatmp.h"
|
||||
|
||||
using namespace HPHP::Transl;
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
#include "hphp/runtime/base/strings.h"
|
||||
#include "hphp/runtime/vm/member_operations.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/hhbctranslator.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/ir.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/irinstruction.h"
|
||||
#include "hphp/runtime/vm/translator/translator-x64.h"
|
||||
|
||||
// These files do ugly things with macros so include them last
|
||||
@@ -38,6 +39,9 @@ static bool wantPropSpecializedWarnings() {
|
||||
bool VectorEffects::supported(Opcode op) {
|
||||
return opcodeHasFlags(op, VectorProp | VectorElem);
|
||||
}
|
||||
bool VectorEffects::supported(const IRInstruction* inst) {
|
||||
return supported(inst->op());
|
||||
}
|
||||
|
||||
void VectorEffects::get(const IRInstruction* inst,
|
||||
StoreLocFunc storeLocalValue,
|
||||
@@ -86,6 +90,35 @@ Opcode canonicalOp(Opcode op) {
|
||||
}
|
||||
}
|
||||
|
||||
VectorEffects::VectorEffects(const IRInstruction* inst) {
|
||||
int keyIdx = vectorKeyIdx(inst);
|
||||
int valIdx = vectorValIdx(inst);
|
||||
init(inst->op(),
|
||||
inst->getSrc(vectorBaseIdx(inst))->type(),
|
||||
keyIdx == -1 ? Type::None : inst->getSrc(keyIdx)->type(),
|
||||
valIdx == -1 ? Type::None : inst->getSrc(valIdx)->type());
|
||||
}
|
||||
|
||||
VectorEffects::VectorEffects(Opcode op, Type base, Type key, Type val) {
|
||||
init(op, base, key, val);
|
||||
}
|
||||
|
||||
VectorEffects::VectorEffects(Opcode op,
|
||||
SSATmp* base, SSATmp* key, SSATmp* val) {
|
||||
auto typeOrNone =
|
||||
[](SSATmp* val){ return val ? val->type() : Type::None; };
|
||||
init(op, typeOrNone(base), typeOrNone(key), typeOrNone(val));
|
||||
}
|
||||
|
||||
VectorEffects::VectorEffects(Opcode opc, const std::vector<SSATmp*>& srcs) {
|
||||
int keyIdx = vectorKeyIdx(opc);
|
||||
int valIdx = vectorValIdx(opc);
|
||||
init(opc,
|
||||
srcs[vectorBaseIdx(opc)]->type(),
|
||||
keyIdx == -1 ? Type::None : srcs[keyIdx]->type(),
|
||||
valIdx == -1 ? Type::None : srcs[valIdx]->type());
|
||||
}
|
||||
|
||||
void VectorEffects::init(Opcode op, const Type origBase,
|
||||
const Type key, const Type origVal) {
|
||||
baseType = origBase;
|
||||
@@ -193,6 +226,9 @@ int vectorBaseIdx(Opcode opc) {
|
||||
: opcodeHasFlags(opc, VectorElem) ? 1
|
||||
: bad_value<int>();
|
||||
}
|
||||
int vectorBaseIdx(const IRInstruction* inst) {
|
||||
return vectorBaseIdx(inst->op());
|
||||
}
|
||||
|
||||
// vectorKeyIdx returns the src index for inst's key operand.
|
||||
int vectorKeyIdx(Opcode opc) {
|
||||
@@ -204,6 +240,9 @@ int vectorKeyIdx(Opcode opc) {
|
||||
: opcodeHasFlags(opc, VectorElem) ? 2
|
||||
: bad_value<int>();
|
||||
}
|
||||
int vectorKeyIdx(const IRInstruction* inst) {
|
||||
return vectorKeyIdx(inst->op());
|
||||
}
|
||||
|
||||
// vectorValIdx returns the src index for inst's value operand.
|
||||
int vectorValIdx(Opcode opc) {
|
||||
@@ -229,6 +268,9 @@ int vectorValIdx(Opcode opc) {
|
||||
: bad_value<int>();
|
||||
}
|
||||
}
|
||||
int vectorValIdx(const IRInstruction* inst) {
|
||||
return vectorValIdx(inst->op());
|
||||
}
|
||||
|
||||
HhbcTranslator::VectorTranslator::VectorTranslator(
|
||||
const NormalizedInstruction& ni,
|
||||
@@ -245,20 +287,11 @@ HhbcTranslator::VectorTranslator::VectorTranslator(
|
||||
{
|
||||
}
|
||||
|
||||
/* Copy varargs SSATmp*s into a vector */
|
||||
template<typename... Srcs>
|
||||
static void getSrcs(std::vector<SSATmp*>& srcVec, SSATmp* src, Srcs... srcs) {
|
||||
srcVec.push_back(src);
|
||||
getSrcs(srcVec, srcs...);
|
||||
}
|
||||
static void getSrcs(std::vector<SSATmp*>& srcVec) {}
|
||||
|
||||
template<typename... Srcs>
|
||||
SSATmp* HhbcTranslator::VectorTranslator::genStk(Opcode opc, Srcs... srcs) {
|
||||
assert(opcodeHasFlags(opc, HasStackVersion));
|
||||
assert(!opcodeHasFlags(opc, ModifiesStack));
|
||||
std::vector<SSATmp*> srcVec;
|
||||
getSrcs(srcVec, srcs...);
|
||||
std::vector<SSATmp*> srcVec({srcs...});
|
||||
SSATmp* base = srcVec[vectorBaseIdx(opc)];
|
||||
|
||||
/* If the base is a pointer to a stack cell and the operation might change
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário