Implement StaticLocInit in hhir
This is almost the same as tx64's implementation, with one substantial difference: the targetcache slots hold RefData* instead of TypedValue*. This is ok because cache handles aren't shared between translations. I also had to rework some code in dce to handle consuming a reference through a phi node.
Esse commit está contido em:
@@ -775,6 +775,10 @@ D:T = LdRaw<T> S0:Ptr S1:ConstInt
|
||||
describes the offset from the base, and the size. The value in D is
|
||||
assumed to be of type T.
|
||||
|
||||
D:BoxedCell = LdStaticLocCached S0:ConstCacheHandle -> L
|
||||
|
||||
Load static local value from the targetcache from handle S0. If the
|
||||
slot is uninitialized, branch to L.
|
||||
|
||||
8. Allocation
|
||||
|
||||
@@ -1085,6 +1089,22 @@ RaiseUndefProp S0:Obj S1:ConstStr
|
||||
|
||||
Raise a notice for an undefined property named S1 on the class of S0.
|
||||
|
||||
D:BoxedCell = StaticLocInit S0:ConstStr S1:StkPtr S2:Cell
|
||||
|
||||
Get boxed value to initialize static local named S0 in frame S1. If
|
||||
the static local has not yet been initialized, its value will be set
|
||||
to S2. The returned value is not stored in the frame S1.
|
||||
|
||||
D:BoxedCell = StaticLocInitCached S0:ConstStr S1:StkPtr S2:Cell
|
||||
S3:ConstCacheHandle
|
||||
|
||||
Get boxed value to initialize static local named S0 in frame S1. If
|
||||
the static local has not yet been initialized, its value will be set
|
||||
to S2. The returned value is stored in the targetcache at handle S3,
|
||||
but is not stored in the frame S1. This instruction assumes that the
|
||||
current function is not a closure body or a generator from a
|
||||
closure.
|
||||
|
||||
Print
|
||||
AddElem
|
||||
AddNewElem
|
||||
|
||||
@@ -333,6 +333,8 @@ CALL_OPCODE(ThrowNonObjProp)
|
||||
CALL_OPCODE(RaiseUndefProp)
|
||||
CALL_OPCODE(RaiseError)
|
||||
CALL_OPCODE(IncStatGrouped)
|
||||
CALL_OPCODE(StaticLocInit)
|
||||
CALL_OPCODE(StaticLocInitCached)
|
||||
|
||||
// Vector instruction helpers
|
||||
CALL_OPCODE(BaseG)
|
||||
@@ -3690,6 +3692,15 @@ void CodeGenerator::cgStRaw(IRInstruction* inst) {
|
||||
}
|
||||
}
|
||||
|
||||
void CodeGenerator::cgLdStaticLocCached(IRInstruction* inst) {
|
||||
auto ch = inst->getSrc(0)->getValRawInt();
|
||||
auto outReg = inst->getDst()->getReg();
|
||||
|
||||
m_as.loadq (rVmTl[ch], outReg);
|
||||
m_as.testq (outReg, outReg);
|
||||
emitFwdJcc(m_as, CC_Z, inst->getTaken());
|
||||
}
|
||||
|
||||
// If label is set and type is not Gen, this method generates a check
|
||||
// that bails to the label if the loaded typed value doesn't match type.
|
||||
void CodeGenerator::cgLoadTypedValue(PhysReg base,
|
||||
|
||||
@@ -83,6 +83,8 @@ static_assert(sizeof(DceFlags) == 1, "sizeof(DceFlags) should be 1 byte");
|
||||
|
||||
// DCE state indexed by instr->getIId.
|
||||
typedef StateVector<IRInstruction, DceFlags> DceState;
|
||||
typedef hphp_hash_set<const SSATmp*, pointer_hash<SSATmp>> SSASet;
|
||||
typedef StateVector<SSATmp, SSASet> SSACache;
|
||||
typedef std::list<const IRInstruction*> WorkList;
|
||||
|
||||
void removeDeadInstructions(Trace* trace, const DceState& state) {
|
||||
@@ -331,6 +333,72 @@ void sinkIncRefs(Trace* trace, IRFactory* irFactory, DceState& state) {
|
||||
copyProp(trace);
|
||||
}
|
||||
|
||||
// Assuming that the 'consumer' instruction consumes 'src', trace back through
|
||||
// src's instruction to the real origin of the value. Currently this traces
|
||||
// through GuardType and DefLabel.
|
||||
void consumeIncRef(const IRInstruction* consumer, const SSATmp* src,
|
||||
DceState& state, SSACache& ssas, SSASet visitedSrcs) {
|
||||
assert(!visitedSrcs.count(src) && "Cycle detected in dataflow graph");
|
||||
auto const& cache = ssas[src];
|
||||
if (!cache.empty()) {
|
||||
// We've already traced this path. Use the cache.
|
||||
for (const SSATmp* cached : cache) {
|
||||
consumeIncRef(consumer, cached, state, ssas, SSASet());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const IRInstruction* srcInst = src->getInstruction();
|
||||
visitedSrcs.insert(src);
|
||||
if (srcInst->getOpcode() == GuardType &&
|
||||
srcInst->getTypeParam().maybeCounted()) {
|
||||
// srcInst is a GuardType that guards to a refcounted type. We need to
|
||||
// trace through to its source. If the GuardType guards to a non-refcounted
|
||||
// type then the reference is consumed by GuardType itself.
|
||||
consumeIncRef(consumer, srcInst->getSrc(0), state, ssas, visitedSrcs);
|
||||
} else if (srcInst->getOpcode() == DefLabel) {
|
||||
// srcInst is a DefLabel that may be a join node. We need to find
|
||||
// the dst index of src in srcInst and trace through to each jump
|
||||
// providing a value for it.
|
||||
for (unsigned i = 0, n = srcInst->getNumDsts(); i < n; ++i) {
|
||||
if (srcInst->getDst(i) == src) {
|
||||
srcInst->getBlock()->forEachSrc(i,
|
||||
[&](IRInstruction* jmp, SSATmp* val) {
|
||||
consumeIncRef(consumer, val, state, ssas, visitedSrcs);
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// src is the canonical representation of everything in visitedSrcs. Put
|
||||
// that knowledge in the cache.
|
||||
for (const SSATmp* visited : visitedSrcs) {
|
||||
// We don't need to store the fact that src is its own canonical
|
||||
// representation.
|
||||
if (visited != src) {
|
||||
ssas[visited].insert(src);
|
||||
}
|
||||
}
|
||||
|
||||
if (srcInst->getOpcode() == IncRef) {
|
||||
// <inst> consumes <srcInst> which is an IncRef, so we mark <srcInst> as
|
||||
// REFCOUNT_CONSUMED.
|
||||
if (consumer->getTrace()->isMain() || !srcInst->getTrace()->isMain()) {
|
||||
// <srcInst> is consumed from its own trace.
|
||||
state[srcInst].setCountConsumed();
|
||||
} else {
|
||||
// <srcInst> is consumed off trace.
|
||||
if (!state[srcInst].countConsumed()) {
|
||||
// mark <srcInst> as REFCOUNT_CONSUMED_OFF_TRACE unless it is
|
||||
// also consumed from its own trace.
|
||||
state[srcInst].setCountConsumedOffTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Publicly exported functions:
|
||||
@@ -363,6 +431,7 @@ void eliminateDeadCode(Trace* trace, IRFactory* irFactory) {
|
||||
// work list; this will also mark reachable exit traces. All
|
||||
// other instructions marked dead.
|
||||
DceState state(irFactory, DceFlags());
|
||||
SSACache ssaOriginals(irFactory, SSASet());
|
||||
WorkList wl = initInstructions(blocks, state);
|
||||
|
||||
// process the worklist
|
||||
@@ -380,26 +449,11 @@ void eliminateDeadCode(Trace* trace, IRFactory* irFactory) {
|
||||
state[srcInst].setLive();
|
||||
wl.push_back(srcInst);
|
||||
}
|
||||
// <inst> consumes <srcInst> which is an IncRef, so we mark <srcInst> as
|
||||
// REFCOUNT_CONSUMED. If the source instruction is a GuardType and guards
|
||||
// to a maybeCounted type, we need to trace through to the source for
|
||||
// refcounting purposes.
|
||||
while (srcInst->getOpcode() == GuardType &&
|
||||
srcInst->getTypeParam().maybeCounted()) {
|
||||
srcInst = srcInst->getSrc(0)->getInstruction();
|
||||
}
|
||||
if (inst->consumesReference(i) && srcInst->getOpcode() == IncRef) {
|
||||
if (inst->getTrace()->isMain() || !srcInst->getTrace()->isMain()) {
|
||||
// <srcInst> is consumed from its own trace.
|
||||
state[srcInst].setCountConsumed();
|
||||
} else {
|
||||
// <srcInst> is consumed off trace.
|
||||
if (!state[srcInst].countConsumed()) {
|
||||
// mark <srcInst> as REFCOUNT_CONSUMED_OFF_TRACE unless it is
|
||||
// also consumed from its own trace.
|
||||
state[srcInst].setCountConsumedOffTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// If inst consumes this source, find the true source instruction and
|
||||
// mark it as consumed if it's an IncRef.
|
||||
if (inst->consumesReference(i)) {
|
||||
consumeIncRef(inst, src, state, ssaOriginals, SSASet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,8 +590,37 @@ void HhbcTranslator::emitTraitExists(const StringData* traitName) {
|
||||
emitClassExists(traitName);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitStaticLocInit(uint32_t varId, uint32_t listStrId) {
|
||||
emitInterpOneOrPunt(Type::None, 1);
|
||||
void HhbcTranslator::emitStaticLocInit(uint32_t locId, uint32_t litStrId) {
|
||||
const StringData* name = lookupStringId(litStrId);
|
||||
LocalId id(locId);
|
||||
SSATmp* value = popC();
|
||||
SSATmp* box;
|
||||
|
||||
// Closures and generators from closures don't satisfy the "one static per
|
||||
// source location" rule that the inline fastpath requires
|
||||
if (getCurFunc()->isClosureBody() || getCurFunc()->isGeneratorFromClosure()) {
|
||||
box = m_tb->gen(StaticLocInit, cns(name), m_tb->getFp(), value);
|
||||
} else {
|
||||
SSATmp* ch =
|
||||
m_tb->genDefConst(TargetCache::allocStatic(), Type::CacheHandle);
|
||||
SSATmp* cachedBox = nullptr;
|
||||
box = m_tb->cond(getCurFunc(),
|
||||
[&](Block* taken) {
|
||||
// Careful: cachedBox is only ok to use in the 'next' branch.
|
||||
cachedBox = m_tb->gen(LdStaticLocCached, taken, ch);
|
||||
},
|
||||
[&] { // next: The local is already initialized
|
||||
return m_tb->gen(IncRef, cachedBox);
|
||||
},
|
||||
[&] { // taken: We missed in the cache
|
||||
m_tb->hint(Block::Unlikely);
|
||||
return m_tb->gen(StaticLocInitCached,
|
||||
cns(name), m_tb->getFp(), value, ch);
|
||||
}
|
||||
);
|
||||
}
|
||||
m_tb->gen(StLoc, &id, m_tb->getFp(), box);
|
||||
m_tb->gen(DecRef, value);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitReqDoc(const StringData* name) {
|
||||
|
||||
@@ -851,26 +851,28 @@ static void printConst(std::ostream& os, IRInstruction* inst) {
|
||||
}
|
||||
} else if (t.isNull()) {
|
||||
os << t.toString();
|
||||
} else if (t == Type::Func) {
|
||||
} else if (t.subtypeOf(Type::Func)) {
|
||||
auto func = c->as<const Func*>();
|
||||
os << "Func(" << (func ? func->fullName()->data() : "0") << ")";
|
||||
} else if (t == Type::Cls) {
|
||||
} else if (t.subtypeOf(Type::Cls)) {
|
||||
auto cls = c->as<const Class*>();
|
||||
os << "Cls(" << (cls ? cls->name()->data() : "0") << ")";
|
||||
} else if (t == Type::NamedEntity) {
|
||||
} else if (t.subtypeOf(Type::NamedEntity)) {
|
||||
auto ne = c->as<const NamedEntity*>();
|
||||
os << "NamedEntity(" << ne << ")";
|
||||
} else if (t == Type::TCA) {
|
||||
} else if (t.subtypeOf(Type::TCA)) {
|
||||
TCA tca = c->as<TCA>();
|
||||
char* nameRaw = Util::getNativeFunctionName(tca);
|
||||
SCOPE_EXIT { free(nameRaw); };
|
||||
std::string name(nameRaw);
|
||||
boost::algorithm::trim(name);
|
||||
os << folly::format("TCA: {}({})", tca, name);
|
||||
} else if (t == Type::None) {
|
||||
} else if (t.subtypeOf(Type::None)) {
|
||||
os << "None:" << c->as<int64_t>();
|
||||
} else if (t.isPtr()) {
|
||||
os << folly::format("{}({:#x})", t.toString(), c->as<uint64_t>());
|
||||
} else if (t.subtypeOf(Type::CacheHandle)) {
|
||||
os << folly::format("CacheHandle({:#x})", c->as<int64_t>());
|
||||
} else {
|
||||
not_reached();
|
||||
}
|
||||
|
||||
@@ -321,6 +321,12 @@ O(StLocNT, ND, S(StkPtr) S(Gen), E|Mem|CRc) \
|
||||
O(StRef, DBox(1), SUnk, E|Mem|CRc|Refs) \
|
||||
O(StRefNT, DBox(1), SUnk, E|Mem|CRc) \
|
||||
O(StRaw, ND, SUnk, E|Mem) \
|
||||
O(LdStaticLocCached, D(BoxedCell), C(CacheHandle), NF) \
|
||||
O(StaticLocInit, D(BoxedCell), CStr S(StkPtr) S(Cell), PRc|E|N|Mem) \
|
||||
O(StaticLocInitCached, D(BoxedCell), CStr \
|
||||
S(StkPtr) \
|
||||
S(Cell) \
|
||||
C(CacheHandle), PRc|E|N|Mem) \
|
||||
O(SpillStack, D(StkPtr), SUnk, E|Mem|CRc) \
|
||||
O(ExitTrace, ND, SUnk, T|E) \
|
||||
O(ExitTraceCc, ND, SUnk, T|E) \
|
||||
@@ -938,7 +944,8 @@ Opcode getStackModifyingOpcode(Opcode opc);
|
||||
IRT(StkPtr, 1ULL << 48) /* any pointer into VM stack: VmSP or VmFP*/ \
|
||||
IRT(TCA, 1ULL << 49) \
|
||||
IRT(ActRec, 1ULL << 50) \
|
||||
IRT(None, 1ULL << 51)
|
||||
IRT(None, 1ULL << 51) \
|
||||
IRT(CacheHandle, 1ULL << 52) /* TargetCache::CacheHandle */
|
||||
|
||||
// The definitions for these are in ir.cpp
|
||||
#define IRT_UNIONS \
|
||||
@@ -2150,9 +2157,10 @@ struct Block : boost::noncopyable {
|
||||
return m_instrs.iterator_to(*inst);
|
||||
}
|
||||
|
||||
// visit each src that provides a value to label->dsts[i]
|
||||
template <class Body>
|
||||
void forEachSrc(unsigned i, Body body) {
|
||||
// visit each src that provides a value to label->dsts[i]. body
|
||||
// should take an IRInstruction* and an SSATmp*.
|
||||
template<typename L>
|
||||
void forEachSrc(unsigned i, L body) {
|
||||
for (const EdgeData* n = m_preds; n; n = n->next) {
|
||||
assert(n->jmp->getOpcode() == Jmp_ && n->jmp->getTaken() == this);
|
||||
body(n->jmp, n->jmp->getSrc(i));
|
||||
@@ -2161,8 +2169,8 @@ struct Block : boost::noncopyable {
|
||||
|
||||
// return the first src providing a value to label->dsts[i] for
|
||||
// which body(src) returns true, or nullptr if none are found.
|
||||
SSATmp* findSrc(unsigned i,
|
||||
std::function<bool(SSATmp*)> body) {
|
||||
template<typename L>
|
||||
SSATmp* findSrc(unsigned i, L body) {
|
||||
for (const EdgeData* n = m_preds; n; n = n->next) {
|
||||
SSATmp* src = n->jmp->getSrc(i);
|
||||
if (body(src)) return src;
|
||||
|
||||
@@ -1143,7 +1143,7 @@ TranslatorX64::irTranslateColAddElemC(const Tracelet& t,
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateStaticLocInit(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(StaticLocInit, i.imm[0].u_IVA, i.imm[1].u_SA);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,10 @@ static CallMap s_callMap({
|
||||
{RaiseError, (TCA)raise_error_sd, DNone, SSync, {{SSA, 0}}},
|
||||
{IncStatGrouped, (TCA)Stats::incStatGrouped, DNone, SNone,
|
||||
{{SSA, 0}, {SSA, 1}, {SSA, 2}}},
|
||||
{StaticLocInit, (TCA)staticLocInit, DSSA, SNone,
|
||||
{{SSA, 0}, {SSA, 1}, {TV, 2}}},
|
||||
{StaticLocInitCached, (TCA)staticLocInitCached, DSSA, SNone,
|
||||
{{SSA, 0}, {SSA, 1}, {TV, 2}, {SSA, 3}}},
|
||||
|
||||
/* Switch helpers */
|
||||
{LdSwitchDblIndex, (TCA)switchDoubleHelper, DSSA, SSync,
|
||||
|
||||
@@ -272,6 +272,12 @@ public:
|
||||
return gen(DefConst, typeForConst(val), &cdata);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
SSATmp* genDefConst(T val, Type type) {
|
||||
ConstData cdata(val);
|
||||
return gen(DefConst, type, &cdata);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
SSATmp* genLdConst(T val) {
|
||||
ConstData cdata(val);
|
||||
|
||||
@@ -106,10 +106,11 @@ CacheHandle ptrToHandle(const void*);
|
||||
|
||||
TCA fcallHelper(ActRec* ar);
|
||||
|
||||
static inline void*
|
||||
template<typename T = void>
|
||||
static inline T*
|
||||
handleToPtr(CacheHandle h) {
|
||||
assert(h < RuntimeOption::EvalJitTargetCacheSize);
|
||||
return (char*)tl_targetCaches + h;
|
||||
return (T*)((char*)tl_targetCaches + h);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
|
||||
@@ -110,4 +110,46 @@ void VerifyParamTypeSlow(const Class* cls,
|
||||
VerifyParamTypeFail(param);
|
||||
}
|
||||
|
||||
template<bool useTargetCache>
|
||||
RefData* staticLocInitImpl(StringData* name, ActRec* fp, TypedValue val,
|
||||
TargetCache::CacheHandle ch) {
|
||||
assert(useTargetCache == (bool)ch);
|
||||
HphpArray* map;
|
||||
if (useTargetCache) {
|
||||
// If we have a cache handle, we know the current func isn't a
|
||||
// closure or generator closure so we can directly grab its static
|
||||
// locals map.
|
||||
const Func* func = fp->m_func;
|
||||
assert(!(func->isClosureBody() || func->isGeneratorFromClosure()));
|
||||
map = func->getStaticLocals();
|
||||
} else {
|
||||
map = get_static_locals(fp);
|
||||
}
|
||||
|
||||
TypedValue *mapVal = map->nvGet(name);
|
||||
if (!mapVal) {
|
||||
map->nvSet(name, &val, false);
|
||||
mapVal = map->nvGet(name);
|
||||
}
|
||||
if (mapVal->m_type != KindOfRef) {
|
||||
tvBox(mapVal);
|
||||
}
|
||||
assert(mapVal->m_type == KindOfRef);
|
||||
RefData* ret = mapVal->m_data.pref;
|
||||
if (useTargetCache) {
|
||||
*TargetCache::handleToPtr<RefData*>(ch) = ret;
|
||||
}
|
||||
ret->incRefCount();
|
||||
return ret;
|
||||
}
|
||||
|
||||
RefData* staticLocInit(StringData* name, ActRec* fp, TypedValue val) {
|
||||
return staticLocInitImpl<false>(name, fp, val, 0);
|
||||
}
|
||||
|
||||
RefData* staticLocInitCached(StringData* name, ActRec* fp, TypedValue val,
|
||||
TargetCache::CacheHandle ch) {
|
||||
return staticLocInitImpl<true>(name, fp, val, ch);
|
||||
}
|
||||
|
||||
} } }
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "runtime/base/types.h"
|
||||
#include "runtime/vm/translator/abi-x64.h"
|
||||
#include "runtime/vm/translator/targetcache.h"
|
||||
|
||||
namespace HPHP { namespace VM { namespace Transl {
|
||||
|
||||
@@ -73,6 +74,10 @@ int64_t switchDoubleHelper(int64_t val, int64_t base, int64_t nTargets);
|
||||
int64_t switchStringHelper(StringData* s, int64_t base, int64_t nTargets);
|
||||
int64_t switchObjHelper(ObjectData* o, int64_t base, int64_t nTargets);
|
||||
|
||||
RefData* staticLocInit(StringData* name, ActRec* fp, TypedValue val);
|
||||
RefData* staticLocInitCached(StringData* name, ActRec* fp, TypedValue val,
|
||||
TargetCache::CacheHandle ch);
|
||||
|
||||
} } }
|
||||
|
||||
#endif
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário