/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 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. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/vm/jit/ir.h" #include #include #include #include #include #include #include "folly/Format.h" #include "folly/Traits.h" #include "hphp/util/trace.h" #include "hphp/runtime/base/string_data.h" #include "hphp/runtime/vm/runtime.h" #include "hphp/runtime/base/stats.h" #include "hphp/runtime/vm/jit/cse.h" #include "hphp/runtime/vm/jit/ir-instruction.h" #include "hphp/runtime/vm/jit/ir-factory.h" #include "hphp/runtime/vm/jit/linear-scan.h" #include "hphp/runtime/vm/jit/print.h" #include "hphp/runtime/vm/jit/simplifier.h" #include "hphp/runtime/vm/jit/trace.h" // Include last to localize effects to this file #include "hphp/util/assert_throw.h" namespace HPHP { namespace JIT { using namespace HPHP::Transl; #define IRT(name, ...) const Type Type::name(Type::k##name); IR_TYPES #undef IRT std::string Type::toString() const { // Try to find an exact match to a predefined type # define IRT(name, ...) if (*this == name) return #name; IR_TYPES # undef IRT if (strictSubtypeOf(Type::Obj)) { return folly::format("Obj<{}>", m_class->name()->data()).str(); } // Concat all of the primitive types in the custom union type std::vector types; # define IRT(name, ...) if (name.subtypeOf(*this)) types.push_back(#name); IRT_PRIMITIVE # undef IRT return folly::format("{{{}}}", folly::join('|', types)).str(); } std::string Type::debugString(Type t) { return t.toString(); } Type Type::fromString(const std::string& str) { static hphp_string_map types; static bool init = false; if (UNLIKELY(!init)) { # define IRT(name, ...) types[#name] = name; IR_TYPES # undef IRT init = true; } return mapGet(types, str, Type::None); } TRACE_SET_MOD(hhir); namespace { #define NF 0 #define C CanCSE #define E Essential #define N CallsNative #define PRc ProducesRC #define CRc ConsumesRC #define Refs MayModifyRefs #define Er MayRaiseError #define Mem MemEffects #define T Terminal #define P Passthrough #define K KillsSources #define StkFlags(f) HasStackVersion|(f) #define VProp VectorProp #define VElem VectorElem #define ND 0 #define D(n) HasDest #define DofS(n) HasDest #define DUnbox(n) HasDest #define DBox(n) HasDest #define DParam HasDest #define DArith HasDest #define DMulti NaryDest #define DSetElem HasDest #define DStk(x) ModifiesStack|(x) #define DPtrToParam HasDest #define DBuiltin HasDest #define DSubtract(n,t) HasDest struct { const char* name; uint64_t flags; } OpInfo[] = { #define O(name, dsts, srcs, flags) \ { #name, \ (OpHasExtraData::value ? HasExtra : 0) | \ dsts | (flags) \ }, IR_OPCODES #undef O { 0 } }; #undef NF #undef C #undef E #undef PRc #undef CRc #undef Refs #undef Er #undef Mem #undef T #undef P #undef K #undef StkFlags #undef VProp #undef VElem #undef ND #undef D #undef DofS #undef DUnbox #undef DBox #undef DParam #undef DArith #undef DMulti #undef DSetElem #undef DStk #undef DPtrToParam #undef DBuiltin #undef DSubtract ////////////////////////////////////////////////////////////////////// /* * dispatchExtra translates from runtime values for the Opcode enum * into compile time types. The goal is to call a `targetFunction' * that is overloaded on the extra data type structs. * * The purpose of the MAKE_DISPATCHER layer is to weed out Opcode * values that have no associated extra data. * * Basically this is doing dynamic dispatch without a vtable in * IRExtraData, instead using the Opcode tag from the associated * instruction to discriminate the runtime type. * * Note: functions made with this currently only make sense to call if * it's already known that the opcode has extra data. If you call it * for one that doesn't, you'll get an abort. Generally hasExtra() * should be checked first. */ #define MAKE_DISPATCHER(name, rettype, targetFunction) \ template struct name { \ template \ static rettype go(IRExtraData* vp, Args&&...) { not_reached(); } \ }; \ template struct name { \ template \ static rettype go(IRExtraData* vp, Args&&... args) { \ return targetFunction( \ static_cast::type*>(vp), \ std::forward(args)... \ ); \ } \ }; template< class RetType, template class Dispatcher, class... Args > RetType dispatchExtra(Opcode opc, IRExtraData* data, Args&&... args) { #define O(opcode, dstinfo, srcinfo, flags) \ case opcode: \ return Dispatcher< \ OpHasExtraData::value, \ opcode \ >::go(data, std::forward(args)...); switch (opc) { IR_OPCODES default: not_reached(); } #undef O not_reached(); } FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_cseHash, cseHash); FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_cseEquals, cseEquals); FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_clone, clone); FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_show, show); template typename std::enable_if< has_cseHash::value, size_t >::type cseHashExtraImpl(T* t) { return t->cseHash(); } size_t cseHashExtraImpl(IRExtraData*) { // This probably means an instruction was marked CanCSE but its // extra data had no hash function. always_assert(!"attempted to hash extra data that didn't " "provide a hash function"); } template typename std::enable_if< has_cseEquals::value || has_cseEquals::value, bool >::type cseEqualsExtraImpl(T* t, IRExtraData* o) { return t->cseEquals(*static_cast(o)); } bool cseEqualsExtraImpl(IRExtraData*, IRExtraData*) { // This probably means an instruction was marked CanCSE but its // extra data had no equals function. always_assert(!"attempted to compare extra data that didn't " "provide an equals function"); } // Clone using a data-specific clone function. template typename std::enable_if< has_clone::value, T* >::type cloneExtraImpl(T* t, Arena& arena) { return t->clone(arena); } // Use the copy constructor if no clone() function was supplied. template typename std::enable_if< !has_clone::value, T* >::type cloneExtraImpl(T* t, Arena& arena) { return new (arena) T(*t); } template typename std::enable_if< has_show::value, std::string >::type showExtraImpl(T* t) { return t->show(); } std::string showExtraImpl(const IRExtraData*) { return "..."; } MAKE_DISPATCHER(HashDispatcher, size_t, cseHashExtraImpl); size_t cseHashExtra(Opcode opc, IRExtraData* data) { return dispatchExtra(opc, data); } MAKE_DISPATCHER(EqualsDispatcher, bool, cseEqualsExtraImpl); bool cseEqualsExtra(Opcode opc, IRExtraData* data, IRExtraData* other) { return dispatchExtra(opc, data, other); } MAKE_DISPATCHER(CloneDispatcher, IRExtraData*, cloneExtraImpl); IRExtraData* cloneExtra(Opcode opc, IRExtraData* data, Arena& a) { return dispatchExtra(opc, data, a); } MAKE_DISPATCHER(ShowDispatcher, std::string, showExtraImpl); } // namespace std::string showExtra(Opcode opc, const IRExtraData* data) { return dispatchExtra(opc, const_cast(data)); } ////////////////////////////////////////////////////////////////////// std::string BCMarker::show() const { assert(valid()); return folly::format("--- bc {}, spOff {} ({})", bcOff, spOff, func->fullName()->data()).str(); } bool BCMarker::valid() const { return func != nullptr && bcOff >= func->base() && bcOff < func->past() && spOff <= func->numSlotsInFrame() + func->maxStackCells(); } IRInstruction::IRInstruction(Arena& arena, const IRInstruction* inst, Id id) : m_op(inst->m_op) , m_typeParam(inst->m_typeParam) , m_numSrcs(inst->m_numSrcs) , m_numDsts(inst->m_numDsts) , m_id(id) , m_srcs(m_numSrcs ? new (arena) SSATmp*[m_numSrcs] : nullptr) , m_dst(nullptr) , m_marker(inst->m_marker) , m_extra(inst->m_extra ? cloneExtra(op(), inst->m_extra, arena) : nullptr) { std::copy(inst->m_srcs, inst->m_srcs + inst->m_numSrcs, m_srcs); setTaken(inst->taken()); } const char* opcodeName(Opcode opcode) { return OpInfo[opcode].name; } bool opcodeHasFlags(Opcode opcode, uint64_t flags) { return OpInfo[opcode].flags & flags; } Opcode getStackModifyingOpcode(Opcode opc) { assert(opcodeHasFlags(opc, HasStackVersion)); opc = Opcode(opc + 1); assert(opcodeHasFlags(opc, ModifiesStack)); return opc; } IRTrace* IRInstruction::trace() const { return block()->trace(); } bool IRInstruction::hasExtra() const { return opcodeHasFlags(op(), HasExtra) && m_extra; } // Instructions with ModifiesStack are always naryDst regardless of // the inner dest. bool IRInstruction::hasDst() const { return opcodeHasFlags(op(), HasDest) && !opcodeHasFlags(op(), ModifiesStack); } bool IRInstruction::naryDst() const { return opcodeHasFlags(op(), NaryDest | ModifiesStack); } bool IRInstruction::isNative() const { return opcodeHasFlags(op(), CallsNative); } bool IRInstruction::producesReference() const { return opcodeHasFlags(op(), ProducesRC); } bool IRInstruction::hasMemEffects() const { return opcodeHasFlags(op(), MemEffects) || mayReenterHelper(); } bool IRInstruction::canCSE() const { auto canCSE = opcodeHasFlags(op(), CanCSE); // Make sure that instructions that are CSE'able can't produce a reference // count or consume reference counts. CheckType/AssertType are special // because they can refine a maybeCounted type to a notCounted type, so they // logically consume and produce a reference without doing any work. assert(!canCSE || !consumesReferences() || m_op == CheckType || m_op == AssertType); return canCSE && !mayReenterHelper(); } bool IRInstruction::consumesReferences() const { return opcodeHasFlags(op(), ConsumesRC); } bool IRInstruction::consumesReference(int srcNo) const { if (!consumesReferences()) { return false; } // CheckType/AssertType consume a reference if we're guarding from a // maybeCounted type to a notCounted type. if (m_op == CheckType || m_op == AssertType) { assert(srcNo == 0); return src(0)->type().maybeCounted() && typeParam().notCounted(); } // SpillStack consumes inputs 2 and onward if (m_op == SpillStack) return srcNo >= 2; // Call consumes inputs 3 and onward if (m_op == Call) return srcNo >= 3; // StRetVal only consumes input 1 if (m_op == StRetVal) return srcNo == 1; if (m_op == StLoc || m_op == StLocNT) { // StLoc[NT] , return srcNo == 1; } if (m_op == StProp || m_op == StPropNT || m_op == StMem || m_op == StMemNT) { // StProp[NT]|StMem[NT] , , return srcNo == 2; } if (m_op == ArraySet || m_op == ArraySetRef) { // Only consumes the reference to its input array return srcNo == 1; } return true; } bool IRInstruction::mayModifyRefs() const { Opcode opc = op(); // DecRefNZ does not have side effects other than decrementing the ref // count. Therefore, its MayModifyRefs should be false. if (opc == DecRef) { auto type = src(0)->type(); if (isControlFlow()) { // If the decref has a target label, then it exits if the destructor // has to be called, so it does not have any side effects on the main // trace. return false; } if (!type.canRunDtor()) { return false; } } return opcodeHasFlags(opc, MayModifyRefs) || mayReenterHelper(); } bool IRInstruction::mayRaiseError() const { return opcodeHasFlags(op(), MayRaiseError) || mayReenterHelper(); } bool IRInstruction::isEssential() const { Opcode opc = op(); if (opc == DecRefNZ) { // If the source of a DecRefNZ is not an IncRef, mark it as essential // because we won't remove its source as well as itself. // If the ref count optimization is turned off, mark all DecRefNZ as // essential. if (!RuntimeOption::EvalHHIREnableRefCountOpt || src(0)->inst()->op() != IncRef) { return true; } } return isControlFlow() || opcodeHasFlags(opc, Essential) || mayReenterHelper(); } bool IRInstruction::isTerminal() const { return opcodeHasFlags(op(), Terminal); } bool IRInstruction::isPassthrough() const { return opcodeHasFlags(op(), Passthrough); } /* * Returns true if the instruction loads into a SSATmp representing a * PHP value (a subtype of Gen). Note that this function returns * false for instructions that load internal meta-data, such as Func*, * Class*, etc. */ bool IRInstruction::isLoad() const { switch (m_op) { case LdStack: case LdLoc: case LdMem: case LdProp: case LdRef: case LdThis: case LdStaticLocCached: case LookupCns: case LookupClsCns: case CGetProp: case VGetProp: case VGetPropStk: case ArrayGet: case VectorGet: case PairGet: case MapGet: case StableMapGet: case CGetElem: case VGetElem: case VGetElemStk: case ArrayIdx: return true; default: return false; } } bool IRInstruction::storesCell(uint32_t srcIdx) const { switch (m_op) { case StRetVal: case StLoc: case StLocNT: return srcIdx == 1; case StMem: case StMemNT: case StProp: case StPropNT: return srcIdx == 2; case SpillStack: return srcIdx >= 2 && srcIdx < numSrcs(); case Call: return srcIdx >= 3 && srcIdx < numSrcs(); case CallBuiltin: return srcIdx >= 1 && srcIdx < numSrcs(); default: return false; } } SSATmp* IRInstruction::getPassthroughValue() const { assert(isPassthrough()); assert(m_op == IncRef || m_op == CheckType || m_op == AssertType || m_op == Mov); return src(0); } bool IRInstruction::killsSources() const { return opcodeHasFlags(op(), KillsSources); } bool IRInstruction::killsSource(int idx) const { if (!killsSources()) return false; switch (m_op) { case DecRef: case ConvObjToArr: case ConvCellToArr: case ConvCellToBool: case ConvObjToDbl: case ConvStrToDbl: case ConvCellToDbl: case ConvObjToInt: case ConvCellToInt: case ConvCellToObj: case ConvObjToStr: case ConvCellToStr: assert(idx == 0); return true; case ArraySet: case ArraySetRef: return idx == 1; default: not_reached(); break; } } bool IRInstruction::modifiesStack() const { return opcodeHasFlags(op(), ModifiesStack); } SSATmp* IRInstruction::modifiedStkPtr() const { assert(modifiesStack()); assert(VectorEffects::supported(this)); SSATmp* sp = dst(hasMainDst() ? 1 : 0); assert(sp->isA(Type::StkPtr)); return sp; } bool IRInstruction::hasMainDst() const { return opcodeHasFlags(op(), HasDest); } bool IRInstruction::mayReenterHelper() const { if (isCmpOp(op())) { return cmpOpTypesMayReenter(op(), src(0)->type(), src(1)->type()); } // Not necessarily actually false; this is just a helper for other // bits. return false; } SSATmp* IRInstruction::dst(unsigned i) const { if (i == 0 && m_numDsts == 0) return nullptr; assert(i < m_numDsts); assert(naryDst() || i == 0); return hasDst() ? dst() : &m_dst[i]; } DstRange IRInstruction::dsts() { return Range(m_dst, m_numDsts); } Range IRInstruction::dsts() const { return Range(m_dst, m_numDsts); } const StringData* findClassName(SSATmp* cls) { assert(cls->isA(Type::Cls)); if (cls->isConst()) { return cls->getValClass()->preClass()->name(); } // Try to get the class name from a LdCls IRInstruction* clsInst = cls->inst(); if (clsInst->op() == LdCls || clsInst->op() == LdClsCached) { SSATmp* clsName = clsInst->src(0); assert(clsName->isA(Type::Str)); if (clsName->isConst()) { return clsName->getValStr(); } } return nullptr; } bool isQueryOp(Opcode opc) { switch (opc) { case OpGt: case OpGte: case OpLt: case OpLte: case OpEq: case OpNeq: case OpSame: case OpNSame: case InstanceOfBitmask: case NInstanceOfBitmask: case IsType: case IsNType: return true; default: return false; } } bool isCmpOp(Opcode opc) { switch (opc) { case OpGt: case OpGte: case OpLt: case OpLte: case OpEq: case OpNeq: case OpSame: case OpNSame: return true; default: return false; } } bool isQueryJmpOp(Opcode opc) { switch (opc) { case JmpGt: case JmpGte: case JmpLt: case JmpLte: case JmpEq: case JmpNeq: case JmpSame: case JmpNSame: case JmpInstanceOfBitmask: case JmpNInstanceOfBitmask: case JmpIsType: case JmpIsNType: case JmpZero: case JmpNZero: return true; default: return false; } } Opcode queryToJmpOp(Opcode opc) { assert(isQueryOp(opc)); switch (opc) { case OpGt: return JmpGt; case OpGte: return JmpGte; case OpLt: return JmpLt; case OpLte: return JmpLte; case OpEq: return JmpEq; case OpNeq: return JmpNeq; case OpSame: return JmpSame; case OpNSame: return JmpNSame; case InstanceOfBitmask: return JmpInstanceOfBitmask; case NInstanceOfBitmask: return JmpNInstanceOfBitmask; case IsType: return JmpIsType; case IsNType: return JmpIsNType; default: always_assert(0); } } Opcode queryJmpToQueryOp(Opcode opc) { assert(isQueryJmpOp(opc)); switch (opc) { case JmpGt: return OpGt; case JmpGte: return OpGte; case JmpLt: return OpLt; case JmpLte: return OpLte; case JmpEq: return OpEq; case JmpNeq: return OpNeq; case JmpSame: return OpSame; case JmpNSame: return OpNSame; case JmpInstanceOfBitmask: return InstanceOfBitmask; case JmpNInstanceOfBitmask: return NInstanceOfBitmask; case JmpIsType: return IsType; case JmpIsNType: return IsNType; default: always_assert(0); } } Opcode jmpToReqBindJmp(Opcode opc) { switch (opc) { case JmpGt: return ReqBindJmpGt; case JmpGte: return ReqBindJmpGte; case JmpLt: return ReqBindJmpLt; case JmpLte: return ReqBindJmpLte; case JmpEq: return ReqBindJmpEq; case JmpNeq: return ReqBindJmpNeq; case JmpSame: return ReqBindJmpSame; case JmpNSame: return ReqBindJmpNSame; case JmpInstanceOfBitmask: return ReqBindJmpInstanceOfBitmask; case JmpNInstanceOfBitmask: return ReqBindJmpNInstanceOfBitmask; case JmpZero: return ReqBindJmpZero; case JmpNZero: return ReqBindJmpNZero; default: always_assert(0); } } Opcode negateQueryOp(Opcode opc) { assert(isQueryOp(opc)); switch (opc) { case OpGt: return OpLte; case OpGte: return OpLt; case OpLt: return OpGte; case OpLte: return OpGt; case OpEq: return OpNeq; case OpNeq: return OpEq; case OpSame: return OpNSame; case OpNSame: return OpSame; case InstanceOfBitmask: return NInstanceOfBitmask; case NInstanceOfBitmask: return InstanceOfBitmask; case IsType: return IsNType; case IsNType: return IsType; default: always_assert(0); } } Opcode commuteQueryOp(Opcode opc) { assert(isQueryOp(opc)); switch (opc) { case OpGt: return OpLt; case OpGte: return OpLte; case OpLt: return OpGt; case OpLte: return OpGte; case OpEq: return OpEq; case OpNeq: return OpNeq; case OpSame: return OpSame; case OpNSame: return OpNSame; default: always_assert(0); } } // Objects compared with strings may involve calling a user-defined // __toString function. bool cmpOpTypesMayReenter(Opcode op, Type t0, Type t1) { if (op == OpNSame || op == OpSame) return false; assert(!t0.equals(Type::Gen) && !t1.equals(Type::Gen)); return (t0.maybe(Type::Obj) && t1.maybe(Type::Str)) || (t0.maybe(Type::Str) && t1.maybe(Type::Obj)); } bool isRefCounted(SSATmp* tmp) { return tmp->type().maybeCounted() && !tmp->isConst(); } void IRInstruction::convertToNop() { IRInstruction nop(Nop, marker()); // copy all but m_id, m_taken, m_listNode m_op = nop.m_op; m_typeParam = nop.m_typeParam; m_numSrcs = nop.m_numSrcs; m_srcs = nop.m_srcs; m_numDsts = nop.m_numDsts; m_dst = nop.m_dst; setTaken(nullptr); m_extra = nullptr; } void IRInstruction::convertToJmp() { assert(isControlFlow()); assert(IMPLIES(block(), block()->back() == this)); m_op = Jmp_; m_typeParam = Type::None; m_numSrcs = 0; m_numDsts = 0; m_srcs = nullptr; m_dst = nullptr; // Instructions in the simplifier don't have blocks yet. if (block()) block()->setNext(nullptr); } void IRInstruction::convertToMov() { assert(!isControlFlow()); m_op = Mov; m_typeParam = Type::None; assert(m_numSrcs == 1); assert(m_numDsts == 1); } void IRInstruction::become(IRFactory* factory, IRInstruction* other) { assert(other->isTransient() || m_numDsts == other->m_numDsts); auto& arena = factory->arena(); // Copy all but m_id, m_taken.from, m_listNode, m_marker, and don't clone // dests---the whole point of become() is things still point to us. m_op = other->m_op; m_typeParam = other->m_typeParam; m_numSrcs = other->m_numSrcs; m_extra = other->m_extra ? cloneExtra(m_op, other->m_extra, arena) : nullptr; m_srcs = new (arena) SSATmp*[m_numSrcs]; std::copy(other->m_srcs, other->m_srcs + m_numSrcs, m_srcs); setTaken(other->taken()); } IRInstruction* IRInstruction::clone(IRFactory* factory) const { return factory->cloneInstruction(this); } SSATmp* IRInstruction::src(uint32_t i) const { if (i >= numSrcs()) return nullptr; return m_srcs[i]; } void IRInstruction::setSrc(uint32_t i, SSATmp* newSrc) { assert(i < numSrcs()); m_srcs[i] = newSrc; } bool Block::isMainExit() const { return isMain() && isExit(); } bool Block::isMain() const { return m_trace->isMain(); } bool Block::isExit() const { return !taken() && !next(); } bool IRInstruction::cseEquals(IRInstruction* inst) const { assert(canCSE()); if (m_op != inst->m_op || m_typeParam != inst->m_typeParam || m_numSrcs != inst->m_numSrcs) { return false; } for (uint32_t i = 0; i < numSrcs(); i++) { if (src(i) != inst->src(i)) { return false; } } if (hasExtra() && !cseEqualsExtra(op(), m_extra, inst->m_extra)) { return false; } /* * Don't CSE on m_taken---it's ok to use the destination of some * earlier guarded load even though the instruction we may have * generated here would've exited to a different trace. * * For example, we use this to cse LdThis regardless of its label. */ return true; } size_t IRInstruction::cseHash() const { assert(canCSE()); size_t srcHash = 0; for (unsigned i = 0; i < numSrcs(); ++i) { srcHash = CSEHash::hashCombine(srcHash, src(i)); } if (hasExtra()) { srcHash = CSEHash::hashCombine(srcHash, cseHashExtra(op(), m_extra)); } return CSEHash::hashCombine(srcHash, m_op, m_typeParam); } std::string IRInstruction::toString() const { std::ostringstream str; print(str, this); return str.str(); } namespace { int typeNeededRegs(Type t) { assert(!t.equals(Type::Bottom)); if (t.subtypeOfAny(Type::None, Type::Null, Type::ActRec, Type::RetAddr, Type::Nullptr)) { // These don't need a register because their values are static or unused. // // RetAddr doesn't take any register because currently we only target x86, // which takes the return address from the stack. This knowledge should be // moved to a machine-specific section once we target other architectures. return 0; } if (t.maybe(Type::Nullptr)) { return typeNeededRegs(t - Type::Nullptr); } if (t.subtypeOf(Type::Ctx) || t.isPtr()) { // Ctx and PtrTo* may be statically unknown but always need just 1 register. return 1; } if (t.subtypeOf(Type::FuncCtx)) { // 2 registers regardless of union status: 1 for the Func* and 1 // for the {Obj|Cctx}, differentiated by the low bit. return 2; } if (!t.isUnion()) { // Not a union type and not a special case: 1 register. assert(IMPLIES(t.subtypeOf(Type::Gen), t.isKnownDataType())); return 1; } assert(t.subtypeOf(Type::Gen)); return t.needsReg() ? 2 : 1; } } int SSATmp::numNeededRegs() const { return typeNeededRegs(type()); } bool SSATmp::getValBool() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::Bool)); return m_inst->extra()->as(); } int64_t SSATmp::getValInt() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::Int)); return m_inst->extra()->as(); } int64_t SSATmp::getValRawInt() const { assert(isConst()); return m_inst->extra()->as(); } double SSATmp::getValDbl() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::Dbl)); return m_inst->extra()->as(); } const StringData* SSATmp::getValStr() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::StaticStr)); return m_inst->extra()->as(); } const ArrayData* SSATmp::getValArr() const { assert(isConst()); // TODO: Task #2124292, Reintroduce StaticArr assert(m_inst->typeParam().subtypeOf(Type::Arr)); return m_inst->extra()->as(); } const Func* SSATmp::getValFunc() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::Func)); return m_inst->extra()->as(); } const Class* SSATmp::getValClass() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::Cls)); return m_inst->extra()->as(); } const NamedEntity* SSATmp::getValNamedEntity() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::NamedEntity)); return m_inst->extra()->as(); } uintptr_t SSATmp::getValBits() const { assert(isConst()); return m_inst->extra()->as(); } Variant SSATmp::getValVariant() const { switch (m_inst->typeParam().toDataType()) { case KindOfUninit: case KindOfNull: return uninit_null(); case KindOfBoolean: return m_inst->extra()->as(); case KindOfInt64: return m_inst->extra()->as(); case KindOfDouble: return m_inst->extra()->as(); case KindOfString: case KindOfStaticString: return (litstr)m_inst->extra() ->as()->data(); case KindOfArray: return Array(ArrayData::GetScalarArray(m_inst->extra() ->as())); case KindOfObject: return m_inst->extra()->as(); default: assert(false); return uninit_null(); } } TCA SSATmp::getValTCA() const { assert(isConst()); assert(m_inst->typeParam().equals(Type::TCA)); return m_inst->extra()->as(); } std::string SSATmp::toString() const { std::ostringstream out; print(out, this); return out.str(); } std::string IRTrace::toString() const { std::ostringstream out; print(out, this, nullptr); return out.str(); } int32_t spillValueCells(IRInstruction* spillStack) { assert(spillStack->op() == SpillStack); int32_t numSrcs = spillStack->numSrcs(); return numSrcs - 2; } bool isConvIntOrPtrToBool(IRInstruction* instr) { switch (instr->op()) { case ConvIntToBool: return true; case ConvCellToBool: return instr->src(0)->type().subtypeOfAny( Type::Func, Type::Cls, Type::FuncCls, Type::VarEnv, Type::TCA); default: return false; } } BlockList rpoSortCfg(IRTrace* trace, const IRFactory& factory) { assert(trace->isMain()); BlockList blocks; blocks.reserve(factory.numBlocks()); unsigned next_id = 0; postorderWalk( [&](Block* block) { block->setPostId(next_id++); blocks.push_back(block); }, factory.numBlocks(), trace->front() ); std::reverse(blocks.begin(), blocks.end()); assert(blocks.size() <= factory.numBlocks()); assert(next_id <= factory.numBlocks()); return blocks; } bool isRPOSorted(const BlockList& blocks) { int id = 0; for (auto it = blocks.rbegin(); it != blocks.rend(); ++it) { if ((*it)->postId() != id++) return false; } return true; } /* * Find the immediate dominator of each block using Cooper, Harvey, and * Kennedy's "A Simple, Fast Dominance Algorithm", returned as a vector * of postorder ids, indexed by postorder id. */ IdomVector findDominators(const BlockList& blocks) { assert(isRPOSorted(blocks)); // Calculate immediate dominators with the iterative two-finger algorithm. // When it terminates, idom[post-id] will contain the post-id of the // immediate dominator of each block. idom[start] will be -1. This is // the general algorithm but it will only loop twice for loop-free graphs. auto const num_blocks = blocks.size(); IdomVector idom(num_blocks, -1); auto start = blocks.begin(); int start_id = (*start)->postId(); idom[start_id] = start_id; start++; for (bool changed = true; changed; ) { changed = false; // for each block after start, in reverse postorder for (auto it = start; it != blocks.end(); it++) { Block* block = *it; int b = block->postId(); // new_idom = any already-processed predecessor auto edge_it = block->preds().begin(); int new_idom = edge_it->from()->postId(); while (idom[new_idom] == -1) new_idom = (++edge_it)->from()->postId(); // for all other already-processed predecessors p of b for (auto& edge : block->preds()) { auto p = edge.from()->postId(); if (p != new_idom && idom[p] != -1) { // find earliest common predecessor of p and new_idom // (higher postIds are earlier in flow and in dom-tree). int b1 = p, b2 = new_idom; do { while (b1 < b2) b1 = idom[b1]; while (b2 < b1) b2 = idom[b2]; } while (b1 != b2); new_idom = b1; } } if (idom[b] != new_idom) { idom[b] = new_idom; changed = true; } } } idom[start_id] = -1; // start has no idom. return idom; } bool dominates(const Block* b1, const Block* b2, const IdomVector& idoms) { int p1 = b1->postId(); int p2 = b2->postId(); for (int i = p2; i != -1; i = idoms[i]) { if (i == p1) return true; } return false; } DomChildren findDomChildren(const BlockList& blocks) { IdomVector idom = findDominators(blocks); DomChildren children(blocks.size(), BlockList()); for (Block* block : blocks) { int idom_id = idom[block->postId()]; if (idom_id != -1) children[idom_id].push_back(block); } return children; } bool hasInternalFlow(IRTrace* trace) { for (Block* block : trace->blocks()) { if (Block* taken = block->taken()) { if (taken->trace() == trace) return true; } } return false; } }}