static void recordNameAndArgs(const SrcKey& sk, const StringData* name, int numArgs) { CallRecord cr; cr.m_type = EncodedNameAndArgs; cr.m_encodedName = encodeCallAndArgs(name, numArgs); s_callDB.insert(std::make_pair(sk, cr)); }
static void recordFunc(const SrcKey sk, const Func* func) { FTRACE(2, "annotation: recordFunc: {}@{} {}\n", sk.unit()->filepath()->data(), sk.offset(), func->fullName()->data()); s_callDB.insert(std::make_pair(sk, func)); }
static void recordFunc(NormalizedInstruction& i, const SrcKey& sk, const Func* func) { FTRACE(2, "annotation: recordFunc: {}@{} {}\n", i.m_unit->filepath()->data(), sk.offset(), func->fullName()->data()); CallRecord cr; cr.m_type = Function; cr.m_func = func; s_callDB.insert(std::make_pair(sk, cr)); i.directCall = true; }
namespace Transl { static const Trace::Module TRACEMOD = Trace::trans; /* * A mapping from FCall instructions to the statically-known StringData* * that they're calling. Used to accelerate our FCall translations. */ enum CallRecordType { EncodedNameAndArgs, Function }; struct CallRecord { CallRecordType m_type; union { const StringData* m_encodedName; const Func* m_func; }; }; typedef hphp_hash_map<SrcKey, CallRecord, SrcKey> CallDB; static CallDB s_callDB; /* record the max number of args to enable invalidation */ static int s_maxNumArgs; int getMaxEncodedArgs() { return s_maxNumArgs; } const StringData* encodeCallAndArgs(const StringData* name, int numArgs) { char numArgsBuf[16]; if (numArgs > s_maxNumArgs) s_maxNumArgs = numArgs; snprintf(numArgsBuf, 15, "@%d@", numArgs); String s = String(numArgsBuf) + String(name->data()); return StringData::GetStaticString(s.get()); } static void decodeNameAndArgs(const StringData* enc, string& outName, int& outNumArgs) { const char* numArgs = strchr(enc->data(), '@'); assert(numArgs && *numArgs =='@'); numArgs++; outNumArgs = atoi(numArgs); const char* name = strchr(numArgs, '@'); assert(name && *name == '@'); name++; outName = name; } static void recordNameAndArgs(const SrcKey& sk, const StringData* name, int numArgs) { CallRecord cr; cr.m_type = EncodedNameAndArgs; cr.m_encodedName = encodeCallAndArgs(name, numArgs); s_callDB.insert(std::make_pair(sk, cr)); } static void recordFunc(NormalizedInstruction& i, const SrcKey& sk, const Func* func) { FTRACE(2, "annotation: recordFunc: {}@{} {}\n", i.m_unit->filepath()->data(), sk.offset(), func->fullName()->data()); CallRecord cr; cr.m_type = Function; cr.m_func = func; s_callDB.insert(std::make_pair(sk, cr)); i.directCall = true; } static void recordActRecPush(NormalizedInstruction& i, const Unit* unit, const StringData* name, const StringData* clsName, bool staticCall) { const SrcKey& sk = i.source; FTRACE(2, "annotation: recordActRecPush: {}@{} {}{}{} ({}static)\n", unit->filepath()->data(), sk.offset(), clsName ? clsName->data() : "", clsName ? "::" : "", name, !staticCall ? "non" : ""); SrcKey next(sk); next.advance(unit); const FPIEnt *fpi = curFunc()->findFPI(next.offset()); assert(fpi); assert(name->isStatic()); assert(sk.offset() == fpi->m_fpushOff); SrcKey fcall = sk; fcall.m_offset = fpi->m_fcallOff; assert(isFCallStar(*unit->at(fcall.offset()))); if (clsName) { const Class* cls = Unit::lookupUniqueClass(clsName); bool magic = false; const Func* func = lookupImmutableMethod(cls, name, magic, staticCall); if (func) { recordFunc(i, fcall, func); } return; } const Func* func = Unit::lookupFunc(name); if (func && func->isNameBindingImmutable(unit)) { // this will never go into a call cache, so we dont need to // encode the args. it will be used in OpFCall below to // set the i->funcd. recordFunc(i, fcall, func); } else { // It's not enough to remember the function name; we also need to encode // the number of arguments and current flag disposition. int numArgs = getImm(unit->at(sk.offset()), 0).u_IVA; recordNameAndArgs(fcall, name, numArgs); } } void annotate(NormalizedInstruction* i) { switch(i->op()) { case OpFPushObjMethodD: case OpFPushClsMethodD: case OpFPushClsMethodF: case OpFPushFuncD: { // When we push predictable action records, we can use a simpler // translation for their corresponding FCall. const StringData* className = nullptr; const StringData* funcName = nullptr; if (i->op() == OpFPushFuncD) { funcName = curUnit()->lookupLitstrId(i->imm[1].u_SA); } else if (i->op() == OpFPushObjMethodD) { if (i->inputs[0]->valueType() != KindOfObject) break; const Class* cls = i->inputs[0]->rtt.valueClass(); if (!cls) break; funcName = curUnit()->lookupLitstrId(i->imm[1].u_SA); className = cls->name(); } else if (i->op() == OpFPushClsMethodF) { if (!i->inputs[1]->isString() || i->inputs[1]->rtt.valueString() == nullptr || i->inputs[0]->valueType() != KindOfClass) { break; } const Class* cls = i->inputs[0]->rtt.valueClass(); if (!cls) break; funcName = i->inputs[1]->rtt.valueString(); className = cls->name(); } else { assert(i->op() == OpFPushClsMethodD); funcName = curUnit()->lookupLitstrId(i->imm[1].u_SA); className = curUnit()->lookupLitstrId(i->imm[2].u_SA); } assert(funcName->isStatic()); recordActRecPush(*i, curUnit(), funcName, className, i->op() == OpFPushClsMethodD || i->op() == OpFPushClsMethodF); } break; case OpFCall: case OpFCallArray: { CallRecord callRec; if (mapGet(s_callDB, i->source, &callRec)) { if (callRec.m_type == Function) { i->funcd = callRec.m_func; } else { assert(callRec.m_type == EncodedNameAndArgs); i->funcName = callRec.m_encodedName; } } else { i->funcName = nullptr; } } break; default: break; } } const StringData* fcallToFuncName(const NormalizedInstruction* i) { CallRecord callRec; if (mapGet(s_callDB, i->source, &callRec)) { if (callRec.m_type == Function) { return callRec.m_func->name(); } string name; int numArgs; decodeNameAndArgs(callRec.m_encodedName, name, numArgs); return StringData::GetStaticString(name.c_str()); } return nullptr; } } } }
void recordFunc(const SrcKey& sk, const Func* func) { CallRecord cr; cr.m_type = Function; cr.m_func = func; s_callDB.insert(std::make_pair(sk, cr)); }
namespace Transl { static const Trace::Module TRACEMOD = Trace::trans; /* * A mapping from FCall instructions to the statically-known StringData* * that they're calling. Used to accelerate our FCall translations. */ enum CallRecordType { EncodedNameAndArgs, Function }; struct CallRecord { CallRecordType m_type; union { const StringData* m_encodedName; const Func* m_func; }; }; typedef hphp_hash_map<SrcKey, CallRecord, SrcKey> CallDB; static CallDB s_callDB; /* record the max number of args to enable invalidation */ static int s_maxNumArgs; int getMaxEncodedArgs() { return s_maxNumArgs; } const StringData* encodeCallAndArgs(const StringData* name, int numArgs) { char numArgsBuf[16]; if (numArgs > s_maxNumArgs) s_maxNumArgs = numArgs; snprintf(numArgsBuf, 15, "@%d@", numArgs); String s = String(numArgsBuf) + String(name->data()); return StringData::GetStaticString(s.get()); } void recordNameAndArgs(const SrcKey& sk, const StringData* name, int numArgs) { CallRecord cr; cr.m_type = EncodedNameAndArgs; cr.m_encodedName = encodeCallAndArgs(name, numArgs); s_callDB.insert(std::make_pair(sk, cr)); } void recordFunc(const SrcKey& sk, const Func* func) { CallRecord cr; cr.m_type = Function; cr.m_func = func; s_callDB.insert(std::make_pair(sk, cr)); } static void recordActRecPush(const SrcKey& sk, const Unit* unit, const FPIEnt* fpi, const StringData* name, const StringData* clsName, bool staticCall) { // sk is the address of a FPush* of the function whose static name // is name. The boundaries of FPI regions are such that we can't quite // find the FCall that matches this FuncD without decoding forward to // the end; this is not ideal, but is hopefully affordable at translation // time. ASSERT(name->isStatic()); ASSERT(sk.offset() == fpi->m_fpushOff); SrcKey fcall; SrcKey next(sk); next.advance(unit); do { if (*unit->at(next.offset()) == OpFCall) { // Remember the last FCall in the region; the region might end // with UnboxR, e.g. fcall = next; } next.advance(unit); } while (next.offset() <= fpi->m_fcallOff); ASSERT(*unit->at(fcall.offset()) == OpFCall); if (clsName) { const Class* cls = Unit::lookupClass(clsName); bool magic = false; const Func* func = lookupImmutableMethod(cls, name, magic, staticCall); if (func) { recordFunc(fcall, func); } return; } const Func* func = Unit::lookupFunc(name); if (func && func->isNameBindingImmutable(unit)) { // this will never go into a call cache, so we dont need to // encode the args. it will be used in OpFCall below to // set the i->funcd. recordFunc(fcall, func); } else { // It's not enough to remember the function name; we also need to encode // the number of arguments and current flag disposition. int numArgs = getImm(unit->at(sk.offset()), 0).u_IVA; recordNameAndArgs(fcall, name, numArgs); } } void annotate(NormalizedInstruction* i) { switch(i->op()) { case OpFPushObjMethodD: case OpFPushClsMethodD: case OpFPushClsMethodF: case OpFPushFuncD: { // When we push predictable action records, we can use a simpler // translation for their corresponding FCall. SrcKey next(i->source); next.advance(curUnit()); const StringData* className = NULL; const StringData* funcName = NULL; if (i->op() == OpFPushFuncD) { funcName = curUnit()->lookupLitstrId(i->imm[1].u_SA); } else if (i->op() == OpFPushObjMethodD) { if (i->inputs[0]->valueType() != KindOfObject) break; const Class* cls = i->inputs[0]->rtt.valueClass(); if (!cls) break; funcName = curUnit()->lookupLitstrId(i->imm[1].u_SA); className = cls->name(); } else if (i->op() == OpFPushClsMethodF) { if (i->inputs[1]->rtt.valueString() == NULL || i->inputs[0]->valueType() != KindOfClass) { break; } const Class* cls = i->inputs[0]->rtt.valueClass(); if (!cls) break; funcName = i->inputs[1]->rtt.valueString(); className = cls->name(); } else { ASSERT(i->op() == OpFPushClsMethodD); funcName = curUnit()->lookupLitstrId(i->imm[1].u_SA); className = curUnit()->lookupLitstrId(i->imm[2].u_SA); } ASSERT(funcName->isStatic()); const FPIEnt *fe = curFunc()->findFPI(next.m_offset); ASSERT(fe); recordActRecPush(i->source, curUnit(), fe, funcName, className, i->op() == OpFPushClsMethodD || i->op() == OpFPushClsMethodF); } break; case OpFCall: { CallRecord callRec; if (mapGet(s_callDB, i->source, &callRec)) { if (callRec.m_type == Function) { i->funcd = callRec.m_func; } else { ASSERT(callRec.m_type == EncodedNameAndArgs); i->funcName = callRec.m_encodedName; } } else { i->funcName = NULL; } } break; default: break; } } } } }
namespace JIT { TRACE_SET_MOD(trans); const StaticString s_empty(""); /* * A mapping from FCall instructions to the statically-known Func* that they're * calling. Used to accelerate our FCall translations. */ typedef hphp_hash_map<SrcKey,const Func*,SrcKey::Hasher> CallDB; static CallDB s_callDB; static void recordFunc(const SrcKey sk, const Func* func) { FTRACE(2, "annotation: recordFunc: {}@{}{} {}\n", sk.unit()->filepath()->data(), sk.offset(), sk.resumed() ? "r" : "", func->fullName()->data()); s_callDB.insert(std::make_pair(sk, func)); } static const Func* lookupDirectFunc(SrcKey const sk, const StringData* fname, const StringData* clsName, bool staticCall) { if (clsName && !clsName->isame(s_empty.get())) { auto const cls = Unit::lookupUniqueClass(clsName); bool magic = false; auto const ctx = sk.func()->cls(); return lookupImmutableMethod(cls, fname, magic, staticCall, ctx); } auto const func = Unit::lookupFunc(fname); if (func && func->isNameBindingImmutable(sk.unit())) { return func; } return nullptr; } static void recordActRecPush(const SrcKey sk, const StringData* name, const StringData* clsName, bool staticCall) { auto unit = sk.unit(); FTRACE(2, "annotation: recordActRecPush: {}@{} {}{}{} ({}static)\n", unit->filepath()->data(), sk.offset(), clsName ? clsName->data() : "", clsName ? "::" : "", name, !staticCall ? "non" : ""); SrcKey next(sk); next.advance(unit); const FPIEnt *fpi = sk.func()->findFPI(next.offset()); assert(fpi); assert(name->isStatic()); assert(sk.offset() == fpi->m_fpushOff); auto const fcall = SrcKey { sk.func(), fpi->m_fcallOff, sk.resumed() }; assert(isFCallStar(*reinterpret_cast<const Op*>(unit->at(fcall.offset())))); auto const func = lookupDirectFunc(sk, name, clsName, staticCall); if (func) { recordFunc(fcall, func); } } void annotate(NormalizedInstruction* i) { switch(i->op()) { case OpFPushObjMethodD: case OpFPushClsMethodD: case OpFPushClsMethodF: case OpFPushCtorD: case OpFPushCtor: case OpFPushFuncD: { if (RuntimeOption::RepoAuthoritative && Repo::global().UsedHHBBC) { break; } // When we push predictable activation records, we can use a simpler // translation for their corresponding FCall. const StringData* className = nullptr; const StringData* funcName = nullptr; if (i->op() == OpFPushFuncD) { funcName = i->m_unit->lookupLitstrId(i->imm[1].u_SA); } else if (i->op() == OpFPushObjMethodD) { if (i->inputs[0]->valueType() != KindOfObject) break; const Class* cls = i->inputs[0]->rtt.valueClass(); if (!cls) break; funcName = i->m_unit->lookupLitstrId(i->imm[1].u_SA); className = cls->name(); } else if (i->op() == OpFPushClsMethodF) { if (!i->inputs[1]->isString() || i->inputs[1]->rtt.valueString() == nullptr || i->inputs[0]->valueType() != KindOfClass) { break; } const Class* cls = i->inputs[0]->rtt.valueClass(); if (!cls) break; funcName = i->inputs[1]->rtt.valueString(); className = cls->name(); } else if (i->op() == OpFPushClsMethodD) { funcName = i->m_unit->lookupLitstrId(i->imm[1].u_SA); className = i->m_unit->lookupLitstrId(i->imm[2].u_SA); } else if (i->op() == OpFPushCtorD) { className = i->m_unit->lookupLitstrId(i->imm[1].u_SA); const Class* cls = Unit::lookupUniqueClass(className); if (!cls) break; auto const ctor = cls->getCtor(); funcName = ctor->name(); className = ctor->cls()->name(); } else { assert(i->op() == OpFPushCtor); const Class* cls = i->inputs[0]->rtt.valueClass(); if (!cls) break; auto const ctor = cls->getCtor(); funcName = ctor->name(); className = ctor->cls()->name(); } assert(funcName->isStatic()); recordActRecPush(i->source, funcName, className, i->op() == OpFPushClsMethodD || i->op() == OpFPushClsMethodF); } break; case OpFCall: case OpFCallArray: { if (RuntimeOption::RepoAuthoritative && Repo::global().UsedHHBBC) { break; } if (auto const func = folly::get_ptr(s_callDB, i->source)) { i->funcd = *func; } } break; default: break; case Op::FCallD: { auto const fpi = i->func()->findFPI(i->source.offset()); auto const pushOp = i->m_unit->getOpcode(fpi->m_fpushOff); auto const clsName = i->m_unit->lookupLitstrId(i->imm[1].u_SA); auto const funcName = i->m_unit->lookupLitstrId(i->imm[2].u_SA); auto const isStatic = pushOp == Op::FPushClsMethodD || pushOp == Op::FPushClsMethodF || pushOp == Op::FPushClsMethod; /* * Currently we don't attempt any of this for FPushClsMethod * because lookupImmutableMethod is only for situations that * don't involve LSB. */ auto const func = pushOp == Op::FPushClsMethod ? nullptr : lookupDirectFunc(i->source, funcName, clsName, isStatic); if (func) { FTRACE(1, "found direct func (%s) for FCallD\n", func->fullName()->data()); i->funcd = func; } break; } } } const StringData* fcallToFuncName(const NormalizedInstruction* i) { if (i->op() == Op::FCallD) { return i->m_unit->lookupLitstrId(i->imm[2].u_SA); } if (RuntimeOption::RepoAuthoritative && Repo::global().UsedHHBBC) { return nullptr; } if (auto const func = folly::get_ptr(s_callDB, i->source)) { return (*func)->name(); } return nullptr; } } }