From 7f335950f9da84818d6dd78e5dbc3ae5ee7ed6aa Mon Sep 17 00:00:00 2001 From: bsimmers Date: Sun, 19 May 2013 17:08:29 -0700 Subject: [PATCH] Move more IR classes into their own headers --- hphp/runtime/vm/translator/hopt/block.h | 184 ++++ hphp/runtime/vm/translator/hopt/cfg.h | 2 +- hphp/runtime/vm/translator/hopt/cse.h | 4 +- hphp/runtime/vm/translator/hopt/extradata.h | 355 +++++++ hphp/runtime/vm/translator/hopt/ir.cpp | 13 + hphp/runtime/vm/translator/hopt/ir.h | 906 +----------------- hphp/runtime/vm/translator/hopt/irfactory.cpp | 2 +- .../vm/translator/hopt/irinstruction.h | 311 ++++++ hphp/runtime/vm/translator/hopt/ssatmp.h | 102 ++ hphp/runtime/vm/translator/hopt/trace.h | 1 + hphp/runtime/vm/translator/hopt/type.cpp | 2 + .../vm/translator/hopt/vectortranslator.cpp | 55 +- 12 files changed, 1028 insertions(+), 909 deletions(-) create mode 100644 hphp/runtime/vm/translator/hopt/block.h create mode 100644 hphp/runtime/vm/translator/hopt/extradata.h create mode 100644 hphp/runtime/vm/translator/hopt/irinstruction.h create mode 100644 hphp/runtime/vm/translator/hopt/ssatmp.h diff --git a/hphp/runtime/vm/translator/hopt/block.h b/hphp/runtime/vm/translator/hopt/block.h new file mode 100644 index 000000000..1d6e56475 --- /dev/null +++ b/hphp/runtime/vm/translator/hopt/block.h @@ -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(&*(--it)); + } + + // return the first instruction in the block. + IRInstruction* front() const { + assert(!m_instrs.empty()); + return const_cast(&*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 + 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 + 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 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 BlockList; +typedef std::forward_list BlockPtrList; + + + +}} + +#endif diff --git a/hphp/runtime/vm/translator/hopt/cfg.h b/hphp/runtime/vm/translator/hopt/cfg.h index 576bd2763..07328afca 100644 --- a/hphp/runtime/vm/translator/hopt/cfg.h +++ b/hphp/runtime/vm/translator/hopt/cfg.h @@ -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 { diff --git a/hphp/runtime/vm/translator/hopt/cse.h b/hphp/runtime/vm/translator/hopt/cse.h index a6f20febf..19346d297 100644 --- a/hphp/runtime/vm/translator/hopt/cse.h +++ b/hphp/runtime/vm/translator/hopt/cse.h @@ -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 diff --git a/hphp/runtime/vm/translator/hopt/extradata.h b/hphp/runtime/vm/translator/hopt/extradata.h new file mode 100644 index 000000000..c357130c6 --- /dev/null +++ b/hphp/runtime/vm/translator/hopt/extradata.h @@ -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 struct OpHasExtraData { enum { value = 0 }; }; +template 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(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(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()(locId); } + std::string show() const { return folly::to(locId); } + + uint32_t locId; +}; + +struct ConstData : IRExtraData { + template + 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::value, + "Constant data wasn't a pod?"); + std::memcpy(&m_dataBits, &data, sizeof data); + } + + template + 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()(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(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(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(numArgs, invName ? " M" : ""); + } +}; + +/* + * Stack offsets. + */ +struct StackOffset : IRExtraData { + explicit StackOffset(int32_t offset) : offset(offset) {} + + std::string show() const { return folly::to(offset); } + + bool cseEquals(StackOffset o) const { return offset == o.offset; } + size_t cseHash() const { return std::hash()(offset); } + + int32_t offset; +}; + +/* + * Bytecode offsets. + */ +struct BCOffset : IRExtraData { + explicit BCOffset(Offset offset) : offset(offset) {} + std::string show() const { return folly::to(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( + 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(pc, ",", after); } + + Offset pc, after; +}; + +////////////////////////////////////////////////////////////////////// + +#define X(op, data) \ + template<> struct IRExtraDataType { typedef data type; }; \ + template<> struct OpHasExtraData { enum { value = 1 }; }; \ + static_assert(boost::has_trivial_destructor::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 struct AssertExtraTypes { + static void doassert() { + assert(!"called getExtra on an opcode without extra data"); + } +}; + +template struct AssertExtraTypes { + static void doassert() { + typedef typename IRExtraDataType::type ExtraType; + if (!std::is_same::value) { + assert(!"getExtra 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 void assert_opcode_extra(Opcode opc) { +#define O(opcode, dstinfo, srcinfo, flags) \ + case opcode: \ + AssertExtraTypes< \ + OpHasExtraData::value,opcode,T \ + >::doassert(); \ + break; + switch (opc) { IR_OPCODES default: not_reached(); } +#undef O +} + +std::string showExtra(Opcode opc, const IRExtraData* data); + +////////////////////////////////////////////////////////////////////// + +}} + +#endif diff --git a/hphp/runtime/vm/translator/hopt/ir.cpp b/hphp/runtime/vm/translator/hopt/ir.cpp index 23c9cd6a4..93ad46944 100644 --- a/hphp/runtime/vm/translator/hopt/ir.cpp +++ b/hphp/runtime/vm/translator/hopt/ir.cpp @@ -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; diff --git a/hphp/runtime/vm/translator/hopt/ir.h b/hphp/runtime/vm/translator/hopt/ir.h index fe2ec1b22..86785df5c 100644 --- a/hphp/runtime/vm/translator/hopt/ir.h +++ b/hphp/runtime/vm/translator/hopt/ir.h @@ -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 struct OpHasExtraData { enum { value = 0 }; }; -template 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(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(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()(locId); } - std::string show() const { return folly::to(locId); } - - uint32_t locId; -}; - -struct ConstData : IRExtraData { - template - 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::value, - "Constant data wasn't a pod?"); - std::memcpy(&m_dataBits, &data, sizeof data); - } - - template - 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()(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(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(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(numArgs, invName ? " M" : ""); - } -}; - -/* - * Stack offsets. - */ -struct StackOffset : IRExtraData { - explicit StackOffset(int32_t offset) : offset(offset) {} - - std::string show() const { return folly::to(offset); } - - bool cseEquals(StackOffset o) const { return offset == o.offset; } - size_t cseHash() const { return std::hash()(offset); } - - int32_t offset; -}; - -/* - * Bytecode offsets. - */ -struct BCOffset : IRExtraData { - explicit BCOffset(Offset offset) : offset(offset) {} - std::string show() const { return folly::to(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( - 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(pc, ",", after); } - - Offset pc, after; -}; - -////////////////////////////////////////////////////////////////////// - -#define X(op, data) \ - template<> struct IRExtraDataType { typedef data type; }; \ - template<> struct OpHasExtraData { enum { value = 1 }; }; \ - static_assert(boost::has_trivial_destructor::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 struct AssertExtraTypes { - static void doassert() { - assert(!"called getExtra on an opcode without extra data"); - } -}; - -template struct AssertExtraTypes { - static void doassert() { - typedef typename IRExtraDataType::type ExtraType; - if (!std::is_same::value) { - assert(!"getExtra 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 void assert_opcode_extra(Opcode opc) { -#define O(opcode, dstinfo, srcinfo, flags) \ - case opcode: \ - AssertExtraTypes< \ - OpHasExtraData::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 SrcRange; typedef Range 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 - const typename IRExtraDataType::type* getExtra() const { - assert(opc == op() && "getExtra type error"); - assert(m_extra != nullptr); - return static_cast::type*>(m_extra); - } - - template - typename IRExtraDataType::type* getExtra() { - assert(opc == op() && "getExtra type error"); - return static_cast::type*>(m_extra); - } - - /* - * Return access to extra-data of type T. Requires that - * IRExtraDataType::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 const T* getExtra() const { - auto opcode = op(); - if (debug) assert_opcode_extra(opcode); - return static_cast(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 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::m_listNode> - IRInstructionHookOption; -typedef boost::intrusive::list - 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 - 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& srcs); Type baseType; Type valType; @@ -2082,161 +1365,6 @@ private: typedef folly::Range 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(&*(--it)); - } - - // return the first instruction in the block. - IRInstruction* front() const { - assert(!m_instrs.empty()); - return const_cast(&*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 - 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 - 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 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 BlockList; -typedef std::forward_list 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); }} diff --git a/hphp/runtime/vm/translator/hopt/irfactory.cpp b/hphp/runtime/vm/translator/hopt/irfactory.cpp index a81cea3dd..38ee14dfe 100644 --- a/hphp/runtime/vm/translator/hopt/irfactory.cpp +++ b/hphp/runtime/vm/translator/hopt/irfactory.cpp @@ -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 { diff --git a/hphp/runtime/vm/translator/hopt/irinstruction.h b/hphp/runtime/vm/translator/hopt/irinstruction.h new file mode 100644 index 000000000..423eba8a2 --- /dev/null +++ b/hphp/runtime/vm/translator/hopt/irinstruction.h @@ -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 + const typename IRExtraDataType::type* getExtra() const { + assert(opc == op() && "getExtra type error"); + assert(m_extra != nullptr); + return static_cast::type*>(m_extra); + } + + template + typename IRExtraDataType::type* getExtra() { + assert(opc == op() && "getExtra type error"); + return static_cast::type*>(m_extra); + } + + /* + * Return access to extra-data of type T. Requires that + * IRExtraDataType::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 const T* getExtra() const { + auto opcode = op(); + if (debug) assert_opcode_extra(opcode); + return static_cast(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 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::m_listNode> + IRInstructionHookOption; +typedef boost::intrusive::list + InstructionList; + +}} + +#endif diff --git a/hphp/runtime/vm/translator/hopt/ssatmp.h b/hphp/runtime/vm/translator/hopt/ssatmp.h new file mode 100644 index 000000000..0b1c26387 --- /dev/null +++ b/hphp/runtime/vm/translator/hopt/ssatmp.h @@ -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 diff --git a/hphp/runtime/vm/translator/hopt/trace.h b/hphp/runtime/vm/translator/hopt/trace.h index eef2f6292..d8b5298cb 100644 --- a/hphp/runtime/vm/translator/hopt/trace.h +++ b/hphp/runtime/vm/translator/hopt/trace.h @@ -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 { diff --git a/hphp/runtime/vm/translator/hopt/type.cpp b/hphp/runtime/vm/translator/hopt/type.cpp index 6d774703b..97a3a32a8 100644 --- a/hphp/runtime/vm/translator/hopt/type.cpp +++ b/hphp/runtime/vm/translator/hopt/type.cpp @@ -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; diff --git a/hphp/runtime/vm/translator/hopt/vectortranslator.cpp b/hphp/runtime/vm/translator/hopt/vectortranslator.cpp index 6b7f0228c..a3c368143 100644 --- a/hphp/runtime/vm/translator/hopt/vectortranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/vectortranslator.cpp @@ -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& 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 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 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 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 -static void getSrcs(std::vector& srcVec, SSATmp* src, Srcs... srcs) { - srcVec.push_back(src); - getSrcs(srcVec, srcs...); -} -static void getSrcs(std::vector& srcVec) {} - template SSATmp* HhbcTranslator::VectorTranslator::genStk(Opcode opc, Srcs... srcs) { assert(opcodeHasFlags(opc, HasStackVersion)); assert(!opcodeHasFlags(opc, ModifiesStack)); - std::vector srcVec; - getSrcs(srcVec, srcs...); + std::vector srcVec({srcs...}); SSATmp* base = srcVec[vectorBaseIdx(opc)]; /* If the base is a pointer to a stack cell and the operation might change