90b7170582
No emitted bytecode relies on Continuation being stored in local 0 anymore. Stop using local 0 for this purpose and compute offset to the Continuation at JIT time. 16 bytes of memory freed. At this point all locals of Continuation construction wrapper share the same indices with their respective locals of Continuation body, which should allow further optimizations.
1183 linhas
33 KiB
C++
Arquivo Executável
1183 linhas
33 KiB
C++
Arquivo Executável
/*
|
|
+----------------------------------------------------------------------+
|
|
| 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 <algorithm>
|
|
#include <cstring>
|
|
#include <forward_list>
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#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<std::string> 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<Type> 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<name>::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<bool HasExtra, Opcode opc> struct name { \
|
|
template<class... Args> \
|
|
static rettype go(IRExtraData* vp, Args&&...) { not_reached(); } \
|
|
}; \
|
|
template<Opcode opc> struct name<true,opc> { \
|
|
template<class... Args> \
|
|
static rettype go(IRExtraData* vp, Args&&... args) { \
|
|
return targetFunction( \
|
|
static_cast<typename IRExtraDataType<opc>::type*>(vp), \
|
|
std::forward<Args>(args)... \
|
|
); \
|
|
} \
|
|
};
|
|
|
|
template<
|
|
class RetType,
|
|
template<bool, Opcode> class Dispatcher,
|
|
class... Args
|
|
>
|
|
RetType dispatchExtra(Opcode opc, IRExtraData* data, Args&&... args) {
|
|
#define O(opcode, dstinfo, srcinfo, flags) \
|
|
case opcode: \
|
|
return Dispatcher< \
|
|
OpHasExtraData<opcode>::value, \
|
|
opcode \
|
|
>::go(data, std::forward<Args>(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<class T>
|
|
typename std::enable_if<
|
|
has_cseHash<T,size_t () const>::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<class T>
|
|
typename std::enable_if<
|
|
has_cseEquals<T,bool (T const&) const>::value ||
|
|
has_cseEquals<T,bool (T) const>::value,
|
|
bool
|
|
>::type cseEqualsExtraImpl(T* t, IRExtraData* o) {
|
|
return t->cseEquals(*static_cast<T*>(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<class T>
|
|
typename std::enable_if<
|
|
has_clone<T,T* (Arena&) const>::value,
|
|
T*
|
|
>::type cloneExtraImpl(T* t, Arena& arena) {
|
|
return t->clone(arena);
|
|
}
|
|
|
|
// Use the copy constructor if no clone() function was supplied.
|
|
template<class T>
|
|
typename std::enable_if<
|
|
!has_clone<T,T* (Arena&) const>::value,
|
|
T*
|
|
>::type cloneExtraImpl(T* t, Arena& arena) {
|
|
return new (arena) T(*t);
|
|
}
|
|
|
|
template<class T>
|
|
typename std::enable_if<
|
|
has_show<T,std::string () const>::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<size_t,HashDispatcher>(opc, data);
|
|
}
|
|
|
|
MAKE_DISPATCHER(EqualsDispatcher, bool, cseEqualsExtraImpl);
|
|
bool cseEqualsExtra(Opcode opc, IRExtraData* data, IRExtraData* other) {
|
|
return dispatchExtra<bool,EqualsDispatcher>(opc, data, other);
|
|
}
|
|
|
|
MAKE_DISPATCHER(CloneDispatcher, IRExtraData*, cloneExtraImpl);
|
|
IRExtraData* cloneExtra(Opcode opc, IRExtraData* data, Arena& a) {
|
|
return dispatchExtra<IRExtraData*,CloneDispatcher>(opc, data, a);
|
|
}
|
|
|
|
MAKE_DISPATCHER(ShowDispatcher, std::string, showExtraImpl);
|
|
|
|
} // namespace
|
|
|
|
std::string showExtra(Opcode opc, const IRExtraData* data) {
|
|
return dispatchExtra<std::string,ShowDispatcher>(opc,
|
|
const_cast<IRExtraData*>(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] <stkptr>, <value>
|
|
return srcNo == 1;
|
|
}
|
|
if (m_op == StProp || m_op == StPropNT || m_op == StMem || m_op == StMemNT) {
|
|
// StProp[NT]|StMem[NT] <base>, <offset>, <value>
|
|
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<SSATmp*>(m_dst, m_numDsts);
|
|
}
|
|
|
|
Range<const SSATmp*> IRInstruction::dsts() const {
|
|
return Range<const SSATmp*>(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<ConstData>()->as<bool>();
|
|
}
|
|
|
|
int64_t SSATmp::getValInt() const {
|
|
assert(isConst());
|
|
assert(m_inst->typeParam().equals(Type::Int));
|
|
return m_inst->extra<ConstData>()->as<int64_t>();
|
|
}
|
|
|
|
int64_t SSATmp::getValRawInt() const {
|
|
assert(isConst());
|
|
return m_inst->extra<ConstData>()->as<int64_t>();
|
|
}
|
|
|
|
double SSATmp::getValDbl() const {
|
|
assert(isConst());
|
|
assert(m_inst->typeParam().equals(Type::Dbl));
|
|
return m_inst->extra<ConstData>()->as<double>();
|
|
}
|
|
|
|
const StringData* SSATmp::getValStr() const {
|
|
assert(isConst());
|
|
assert(m_inst->typeParam().equals(Type::StaticStr));
|
|
return m_inst->extra<ConstData>()->as<const StringData*>();
|
|
}
|
|
|
|
const ArrayData* SSATmp::getValArr() const {
|
|
assert(isConst());
|
|
// TODO: Task #2124292, Reintroduce StaticArr
|
|
assert(m_inst->typeParam().subtypeOf(Type::Arr));
|
|
return m_inst->extra<ConstData>()->as<const ArrayData*>();
|
|
}
|
|
|
|
const Func* SSATmp::getValFunc() const {
|
|
assert(isConst());
|
|
assert(m_inst->typeParam().equals(Type::Func));
|
|
return m_inst->extra<ConstData>()->as<const Func*>();
|
|
}
|
|
|
|
const Class* SSATmp::getValClass() const {
|
|
assert(isConst());
|
|
assert(m_inst->typeParam().equals(Type::Cls));
|
|
return m_inst->extra<ConstData>()->as<const Class*>();
|
|
}
|
|
|
|
const NamedEntity* SSATmp::getValNamedEntity() const {
|
|
assert(isConst());
|
|
assert(m_inst->typeParam().equals(Type::NamedEntity));
|
|
return m_inst->extra<ConstData>()->as<const NamedEntity*>();
|
|
}
|
|
|
|
uintptr_t SSATmp::getValBits() const {
|
|
assert(isConst());
|
|
return m_inst->extra<ConstData>()->as<uintptr_t>();
|
|
}
|
|
|
|
Variant SSATmp::getValVariant() const {
|
|
switch (m_inst->typeParam().toDataType()) {
|
|
case KindOfUninit:
|
|
case KindOfNull:
|
|
return uninit_null();
|
|
case KindOfBoolean:
|
|
return m_inst->extra<ConstData>()->as<bool>();
|
|
case KindOfInt64:
|
|
return m_inst->extra<ConstData>()->as<int64_t>();
|
|
case KindOfDouble:
|
|
return m_inst->extra<ConstData>()->as<double>();
|
|
case KindOfString:
|
|
case KindOfStaticString:
|
|
return (litstr)m_inst->extra<ConstData>()
|
|
->as<const StringData*>()->data();
|
|
case KindOfArray:
|
|
return Array(ArrayData::GetScalarArray(m_inst->extra<ConstData>()
|
|
->as<ArrayData*>()));
|
|
case KindOfObject:
|
|
return m_inst->extra<ConstData>()->as<const Object*>();
|
|
default:
|
|
assert(false);
|
|
return uninit_null();
|
|
}
|
|
}
|
|
|
|
TCA SSATmp::getValTCA() const {
|
|
assert(isConst());
|
|
assert(m_inst->typeParam().equals(Type::TCA));
|
|
return m_inst->extra<ConstData>()->as<TCA>();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}}
|
|
|