/* +----------------------------------------------------------------------+ | 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/target-cache.h" #include "hphp/runtime/base/complex_types.h" #include "hphp/runtime/base/execution_context.h" #include "hphp/runtime/base/types.h" #include "hphp/runtime/base/strings.h" #include "hphp/runtime/vm/unit.h" #include "hphp/runtime/vm/class.h" #include "hphp/runtime/vm/runtime.h" #include "hphp/runtime/vm/jit/annotation.h" #include "hphp/runtime/vm/jit/translator-inline.h" #include "hphp/runtime/base/stats.h" #include "hphp/util/trace.h" #include "hphp/util/base.h" #include "hphp/util/maphuge.h" #include #include #include using namespace HPHP::MethodLookup; using namespace HPHP::Util; using std::string; /* * The targetcache module provides a set of per-request caches. */ namespace HPHP { /* * Put this where the compiler has a chance to inline it. */ inline const Func* Class::wouldCall(const Func* prev) const { if (LIKELY(m_methods.size() > prev->methodSlot())) { const Func* cand = m_methods[prev->methodSlot()]; /* If this class has the same func at the same method slot we're good to go. No need to recheck permissions, since we already checked them first time around */ if (LIKELY(cand == prev)) return cand; if (prev->attrs() & AttrPrivate) { /* If the previously called function was private, then the context class must be prev->cls() - so its definitely accessible. So if this derives from prev->cls() its the function that would be picked. Note that we can only get here if there is a same named function deeper in the class hierarchy */ if (this->classof(prev->cls())) return prev; } if (cand->name() == prev->name()) { /* * We have the same name - so its probably the right function. * If its not public, check that both funcs were originally * defined in the same base class. */ if ((cand->attrs() & AttrPublic) || cand->baseCls() == prev->baseCls()) { return cand; } } } return nullptr; } namespace Transl { namespace TargetCache { TRACE_SET_MOD(targetcache); static StaticString s___call(LITSTR_INIT("__call")); // Shorthand. typedef CacheHandle Handle; // Helper for lookup failures. msg should be a printf-style static // format with one %s parameter, which name will be substituted into. void undefinedError(const char* msg, const char* name) { raise_error(msg, name); } // Targetcache memory. See the comment in target-cache.h __thread void* tl_targetCaches = nullptr; __thread HphpArray* s_constants = nullptr; static const size_t kPreAllocatedBytes = 64; size_t s_frontier = kPreAllocatedBytes; static_assert(sizeof(TargetCacheHeader) <= kPreAllocatedBytes, "TargetCacheHeader doesn't fit in kPreAllocatedBytes"); size_t s_persistent_frontier = 0; size_t s_persistent_start = 0; static size_t s_next_bit; static size_t s_bits_to_go; static int s_tc_fd; // Mapping from names to targetcache locations. Protected by the translator // write lease. typedef tbb::concurrent_hash_map HandleMapIS; typedef tbb::concurrent_hash_map HandleMapCS; // handleMaps[NSConstant]['FOO'] is the cache associated with the constant // FOO, eg. handleMaps is a rare instance of shared, mutable state across // the request threads in the translator: it is essentially a lazily // constructed link table for tl_targetCaches. HandleMapIS handleMapsIS[NumInsensitive]; HandleMapCS handleMapsCS[NumCaseSensitive]; // Vector of cache handles typedef std::vector HandleVector; // Set of FuncCache handles for dynamic function callsites, used for // invalidation when a function is renamed. HandleVector funcCacheEntries; static Mutex s_handleMutex(false /*recursive*/, RankLeaf); inline Handle ptrToHandle(const void* ptr) { ptrdiff_t retval = uintptr_t(ptr) - uintptr_t(tl_targetCaches); assert(retval < RuntimeOption::EvalJitTargetCacheSize); return retval; } template class HandleInfo { public: typedef HandleMapIS Map; static Map &getHandleMap(int where) { return handleMapsIS[where]; } }; template <> class HandleInfo { public: typedef HandleMapCS Map; static Map &getHandleMap(int where) { return handleMapsCS[where - FirstCaseSensitive]; } }; #define getHMap(where) \ HandleInfo= FirstCaseSensitive>::getHandleMap(where) static size_t allocBitImpl(const StringData* name, PHPNameSpace ns) { ASSERT_NOT_IMPLEMENTED(ns == NSInvalid || ns >= FirstCaseSensitive); HandleMapCS& map = HandleInfo::getHandleMap(ns); HandleMapCS::const_accessor a; if (name != nullptr && ns != NSInvalid && map.find(a, name)) { return a->second; } Lock l(s_handleMutex); if (name != nullptr && ns != NSInvalid && map.find(a, name)) { // Retry under the lock. return a->second; } if (!s_bits_to_go) { static const int kNumBytes = 512; static const int kNumBytesMask = kNumBytes - 1; s_next_bit = s_frontier * CHAR_BIT; // allocate at least kNumBytes bytes, and make sure we end // on a 64 byte aligned boundary. int bytes = ((~s_frontier + 1) & kNumBytesMask) + kNumBytes; s_bits_to_go = bytes * CHAR_BIT; s_frontier += bytes; } s_bits_to_go--; if (name != nullptr && ns != NSInvalid) { if (!name->isStatic()) name = StringData::GetStaticString(name); if (!map.insert(HandleMapCS::value_type(name, s_next_bit))) NOT_REACHED(); } return s_next_bit++; } size_t allocBit() { return allocBitImpl(nullptr, NSInvalid); } Handle bitOffToHandleAndMask(size_t bit, uint8_t &mask) { static_assert(!(8 % CHAR_BIT), "Unexpected size of char"); mask = (uint8_t)1 << (bit % 8); size_t off = bit / CHAR_BIT; off -= off % (8 / CHAR_BIT); return off; } bool testBit(size_t bit) { Handle handle = bit / CHAR_BIT; unsigned char mask = 1 << (bit % CHAR_BIT); return *(unsigned char*)handleToPtr(handle) & mask; } bool testBit(Handle handle, uint32_t mask) { assert(!(mask & (mask - 1))); return *(uint32_t*)handleToPtr(handle) & mask; } bool testAndSetBit(size_t bit) { Handle handle = bit / CHAR_BIT; unsigned char mask = 1 << (bit % CHAR_BIT); bool ret = *(unsigned char*)handleToPtr(handle) & mask; *(unsigned char*)handleToPtr(handle) |= mask; return ret; } bool testAndSetBit(Handle handle, uint32_t mask) { assert(!(mask & (mask - 1))); bool ret = *(uint32_t*)handleToPtr(handle) & mask; *(uint32_t*)handleToPtr(handle) |= mask; return ret; } bool isPersistentHandle(Handle handle) { return handle >= (unsigned)s_persistent_start; } bool classIsPersistent(const Class* cls) { return (RuntimeOption::RepoAuthoritative && cls && isPersistentHandle(cls->m_cachedOffset)); } static Handle allocLocked(bool persistent, int numBytes, int align) { s_handleMutex.assertOwnedBySelf(); align = Util::roundUpToPowerOfTwo(align); size_t &frontier = persistent ? s_persistent_frontier : s_frontier; frontier += align - 1; frontier &= ~(align - 1); frontier += numBytes; always_assert(frontier < (persistent ? RuntimeOption::EvalJitTargetCacheSize : s_persistent_start)); return frontier - numBytes; } // namedAlloc -- // Many targetcache entries (Func, Class, Constant, ...) have // request-unique values. There is no reason to allocate more than // one item for all such calls in a request. // // handleMaps acts as a de-facto dynamic link table that lives // across requests; the translator can write out code that assumes // that a given named entity's location in tl_targetCaches is // stable from request to request. template Handle namedAlloc(PHPNameSpace where, const StringData* name, int numBytes, int align) { assert(!name || (where >= 0 && where < NumNameSpaces)); typedef HandleInfo HI; typename HI::Map& map = HI::getHandleMap(where); typename HI::Map::const_accessor a; if (name && map.find(a, name)) { TRACE(2, "TargetCache: hit \"%s\", %d\n", name->data(), int(a->second)); return a->second; } Lock l(s_handleMutex); if (name && map.find(a, name)) { // Retry under the lock TRACE(2, "TargetCache: hit \"%s\", %d\n", name->data(), int(a->second)); return a->second; } Handle retval = allocLocked(where == NSPersistent, numBytes, align); if (name) { if (!name->isStatic()) name = StringData::GetStaticString(name); if (!map.insert(typename HI::Map::value_type(name, retval))) NOT_REACHED(); TRACE(1, "TargetCache: inserted \"%s\", %d\n", name->data(), int(retval)); } else if (where == NSDynFunction) { funcCacheEntries.push_back(retval); } return retval; } template Handle namedAlloc(PHPNameSpace where, const StringData* name, int numBytes, int align); template Handle namedAlloc(PHPNameSpace where, const StringData* name, int numBytes, int align); void invalidateForRename(const StringData* name) { assert(name); Lock l(s_handleMutex); for (HandleVector::iterator i = funcCacheEntries.begin(); i != funcCacheEntries.end(); ++i) { FuncCache::invalidate(*i, name); } } void initPersistentCache() { Lock l(s_handleMutex); if (s_tc_fd) return; char tmpName[] = "/tmp/tcXXXXXX"; s_tc_fd = mkstemp(tmpName); always_assert(s_tc_fd != -1); unlink(tmpName); s_persistent_start = RuntimeOption::EvalJitTargetCacheSize * 3 / 4; s_persistent_start -= s_persistent_start & (4 * 1024 - 1); ftruncate(s_tc_fd, RuntimeOption::EvalJitTargetCacheSize - s_persistent_start); s_persistent_frontier = s_persistent_start; } void threadInit() { if (!s_tc_fd) { initPersistentCache(); } tl_targetCaches = mmap(nullptr, RuntimeOption::EvalJitTargetCacheSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); always_assert(tl_targetCaches != MAP_FAILED); hintHuge(tl_targetCaches, RuntimeOption::EvalJitTargetCacheSize); void *shared_base = (char*)tl_targetCaches + s_persistent_start; /* * map the upper portion of the target cache to a shared area * This is used for persistent classes and functions, so they * are always defined, and always visible to all threads. */ void *mem = mmap(shared_base, RuntimeOption::EvalJitTargetCacheSize - s_persistent_start, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, s_tc_fd, 0); always_assert(mem == shared_base); } void threadExit() { munmap(tl_targetCaches, RuntimeOption::EvalJitTargetCacheSize); } static const bool zeroViaMemset = true; void requestInit() { assert(tl_targetCaches); assert(!s_constants); TRACE(1, "TargetCache: @%p\n", tl_targetCaches); if (zeroViaMemset) { TRACE(1, "TargetCache: bzeroing %zd bytes: %p\n", s_frontier, tl_targetCaches); memset(tl_targetCaches, 0, s_frontier); } } void requestExit() { if (!zeroViaMemset) { flush(); } s_constants = nullptr; // it will be swept } void flush() { TRACE(1, "TargetCache: MADV_DONTNEED %zd bytes: %p\n", s_frontier, tl_targetCaches); if (madvise(tl_targetCaches, s_frontier, MADV_DONTNEED) < 0) { not_reached(); } } static inline bool stringMatches(const StringData* rowString, const StringData* sd) { return rowString && (rowString == sd || rowString->data() == sd->data() || (rowString->hash() == sd->hash() && rowString->same(sd))); } //============================================================================= // FuncCache template<> inline int FuncCache::hashKey(const StringData* sd) { return sd->hash(); } template<> const Func* FuncCache::lookup(Handle handle, StringData *sd, const void* /* ignored */) { FuncCache* thiz = cacheAtHandle(handle); Func* func; Pair* pair = thiz->keyToPair(sd); const StringData* pairSd = pair->m_key; if (!stringMatches(pairSd, sd)) { // Miss. Does it actually exist? func = Unit::lookupFunc(sd); if (UNLIKELY(!func)) { VMRegAnchor _; func = Unit::loadFunc(sd); if (!func) { undefinedError("Undefined function: %s", sd->data()); } } func->validate(); pair->m_key = func->name(); // use a static name pair->m_value = func; } // DecRef the string here; more compact than doing so in callers. decRefStr(sd); assert(stringMatches(pair->m_key, pair->m_value->name())); pair->m_value->validate(); return pair->m_value; } //============================================================================= // FixedFuncCache const Func* FixedFuncCache::lookupUnknownFunc(StringData* name) { VMRegAnchor _; Func* func = Unit::loadFunc(name); if (UNLIKELY(!func)) { undefinedError("Undefined function: %s", name->data()); } return func; } //============================================================================= // MethodCache template<> inline int MethodCache::hashKey(uintptr_t c) { pointer_hash h; return h(reinterpret_cast(c)); } /* * This is flagged NEVER_INLINE because if gcc inlines it, it will * hoist a bunch of initialization code (callee-saved regs pushes, * making a frame, and rsp adjustment) above the fast path. When not * inlined, gcc is generating a jmp to this function instead of a * call. */ HOT_FUNC_VM NEVER_INLINE void methodCacheSlowPath(MethodCache::Pair* mce, ActRec* ar, StringData* name, Class* cls) { assert(ar->hasThis()); assert(ar->getThis()->getVMClass() == cls); assert(IMPLIES(mce->m_key, mce->m_value)); try { bool isMagicCall = mce->m_key & 0x1u; bool isStatic; const Func* func; auto* storedClass = reinterpret_cast(mce->m_key & ~0x3u); if (storedClass == cls) { isStatic = mce->m_key & 0x2u; func = mce->m_value; } else { if (LIKELY(storedClass != nullptr && ((func = cls->wouldCall(mce->m_value)) != nullptr) && !isMagicCall)) { Stats::inc(Stats::TgtCache_MethodHit, func != nullptr); isMagicCall = false; } else { Class* ctx = arGetContextClass((ActRec*)ar->m_savedRbp); Stats::inc(Stats::TgtCache_MethodMiss); TRACE(2, "MethodCache: miss class %p name %s!\n", cls, name->data()); auto const& objMethod = MethodLookup::CallType::ObjMethod; func = g_vmContext->lookupMethodCtx(cls, name, ctx, objMethod, false); if (UNLIKELY(!func)) { isMagicCall = true; func = cls->lookupMethod(s___call.get()); if (UNLIKELY(!func)) { // Do it again, but raise the error this time. (void) g_vmContext->lookupMethodCtx(cls, name, ctx, objMethod, true); NOT_REACHED(); } } else { isMagicCall = false; } } isStatic = func->attrs() & AttrStatic; mce->m_key = uintptr_t(cls) | (uintptr_t(isStatic) << 1) | uintptr_t(isMagicCall); mce->m_value = func; } assert(func); func->validate(); ar->m_func = func; if (UNLIKELY(isStatic && !func->isClosureBody())) { decRefObj(ar->getThis()); if (debug) ar->setThis(nullptr); // suppress assert in setClass ar->setClass(cls); } assert(!ar->hasVarEnv() && !ar->hasInvName()); if (UNLIKELY(isMagicCall)) { ar->setInvName(name); assert(name->isStatic()); // No incRef needed. } } catch (...) { /* * Barf. * * If the slow lookup fails, we're going to rewind to the state * before the FPushObjMethodD that dumped us here. In this state, * the object is still on the stack, but for efficiency reasons, * we've smashed this TypedValue* with the ActRec we were trying * to push. * * Reconstitute the virtual object before rethrowing. */ TypedValue* shouldBeObj = reinterpret_cast(ar) + kNumActRecCells - 1; ObjectData* arThis = ar->getThis(); shouldBeObj->m_type = KindOfObject; shouldBeObj->m_data.pobj = arThis; // There used to be a half-built ActRec on the stack that we need the // unwinder to ignore. We overwrote 1/3 of it with the code above, but // because of the emitMarker() in LdObjMethod we need the other two slots // to not have any TypedValues. tvWriteNull(shouldBeObj-1); tvWriteNull(shouldBeObj-2); throw; } } template<> HOT_FUNC_VM void MethodCache::lookup(Handle handle, ActRec* ar, const void* extraKey) { assert(ar->hasThis()); auto* cls = ar->getThis()->getVMClass(); auto* pair = MethodCache::cacheAtHandle(handle)->keyToPair(uintptr_t(cls)); /* * The MethodCache line consists of a Class* key (stored as a * uintptr_t) and a Func*. The low bit of the key is set if the * function call is a magic call (in which case the cached Func* is * the __call function). The second lowest bit of the key is set if * the cached Func has AttrStatic. * * For this fast path, we just check if the key is bitwise equal to * the Class* on the object. If either of the special bits are set * in the key we'll bail to the slow path. */ if (LIKELY(pair->m_key == reinterpret_cast(cls))) { ar->m_func = pair->m_value; } else { auto* name = static_cast(extraKey); methodCacheSlowPath(pair, ar, const_cast(name), cls); } } //============================================================================= // GlobalCache // | - BoxedGlobalCache template inline TypedValue* GlobalCache::lookupImpl(StringData *name, bool allowCreate) { bool hit ATTRIBUTE_UNUSED; TypedValue* retval; if (!m_tv) { hit = false; VarEnv* ve = g_vmContext->m_globalVarEnv; assert(ve->isGlobalScope()); if (allowCreate) { m_tv = ve->lookupAddRawPointer(name); } else { m_tv = ve->lookupRawPointer(name); if (!m_tv) { retval = 0; goto miss; } } } else { hit = true; } retval = tvDerefIndirect(m_tv); if (retval->m_type == KindOfUninit) { if (!allowCreate) { retval = 0; goto miss; } else { tvWriteNull(retval); } } if (isBoxed && retval->m_type != KindOfRef) { tvBox(retval); } if (!isBoxed && retval->m_type == KindOfRef) { retval = retval->m_data.pref->tv(); } assert(!isBoxed || retval->m_type == KindOfRef); assert(!allowCreate || retval); miss: // decRef the name if we consumed it. If we didn't get a global, we // need to leave the name for the caller to use before decrefing (to // emit warnings). if (retval) decRefStr(name); TRACE(5, "%sGlobalCache::lookup(\"%s\") tv@%p %p -> (%s) %p t%d\n", isBoxed ? "Boxed" : "", name->data(), m_tv, retval, hit ? "hit" : "miss", retval ? retval->m_data.pref : 0, retval ? retval->m_type : 0); return retval; } TypedValue* GlobalCache::lookup(Handle handle, StringData* name) { GlobalCache* thiz = (GlobalCache*)GlobalCache::cacheAtHandle(handle); TypedValue* retval = thiz->lookupImpl(name, false /* allowCreate */); assert(!retval || retval->m_type != KindOfRef); return retval; } TypedValue* GlobalCache::lookupCreate(Handle handle, StringData* name) { GlobalCache* thiz = (GlobalCache*)GlobalCache::cacheAtHandle(handle); TypedValue* retval = thiz->lookupImpl(name, true /* allowCreate */); assert(retval->m_type != KindOfRef); return retval; } TypedValue* GlobalCache::lookupCreateAddr(void* cacheAddr, StringData* name) { GlobalCache* thiz = (GlobalCache*)cacheAddr; TypedValue* retval = thiz->lookupImpl(name, true /* allowCreate */); assert(retval->m_type != KindOfRef); return retval; } TypedValue* BoxedGlobalCache::lookup(Handle handle, StringData* name) { BoxedGlobalCache* thiz = (BoxedGlobalCache*) BoxedGlobalCache::cacheAtHandle(handle); TypedValue* retval = thiz->lookupImpl(name, false /* allowCreate */); assert(!retval || retval->m_type == KindOfRef); return retval; } TypedValue* BoxedGlobalCache::lookupCreate(Handle handle, StringData* name) { BoxedGlobalCache* thiz = (BoxedGlobalCache*) BoxedGlobalCache::cacheAtHandle(handle); TypedValue* retval = thiz->lookupImpl(name, true /* allowCreate */); assert(retval->m_type == KindOfRef); return retval; } static CacheHandle allocFuncOrClass(const unsigned* handlep, bool persistent) { if (UNLIKELY(!*handlep)) { Lock l(s_handleMutex); if (!*handlep) { *const_cast(handlep) = allocLocked(persistent, sizeof(void*), sizeof(void*)); } } return *handlep; } CacheHandle allocKnownClass(const Class* cls) { const NamedEntity* ne = cls->preClass()->namedEntity(); if (ne->m_cachedClassOffset) return ne->m_cachedClassOffset; return allocKnownClass(ne, (!SystemLib::s_inited || RuntimeOption::RepoAuthoritative) && cls->verifyPersistent()); } CacheHandle allocKnownClass(const NamedEntity* ne, bool persistent) { return allocFuncOrClass(&ne->m_cachedClassOffset, persistent); } CacheHandle allocKnownClass(const StringData* name) { return allocKnownClass(Unit::GetNamedEntity(name), false); } CacheHandle allocClassInitProp(const StringData* name) { return namedAlloc(name, sizeof(Class::PropInitVec*), sizeof(Class::PropInitVec*)); } CacheHandle allocClassInitSProp(const StringData* name) { return namedAlloc(name, sizeof(TypedValue*), sizeof(TypedValue*)); } CacheHandle allocFixedFunction(const NamedEntity* ne, bool persistent) { return allocFuncOrClass(&ne->m_cachedFuncOffset, persistent); } CacheHandle allocFixedFunction(const StringData* name) { return allocFixedFunction(Unit::GetNamedEntity(name), false); } CacheHandle allocTypedef(const NamedEntity* ne) { if (ne->m_cachedTypedefOffset) { return ne->m_cachedTypedefOffset; } Lock l(s_handleMutex); // TODO(#2103214): support persistent const_cast(ne)->m_cachedTypedefOffset = allocLocked(false /* persistent */, sizeof(TypedefReq), alignof(TypedefReq)); return ne->m_cachedTypedefOffset; } template Class* lookupKnownClass(Class** cache, const StringData* clsName, bool isClass) { if (!checkOnly) { Stats::inc(Stats::TgtCache_KnownClsHit, -1); Stats::inc(Stats::TgtCache_KnownClsMiss, 1); } Class* cls = *cache; assert(!cls); // the caller should already have checked assert(clsName->data()[0] != '\\'); // namespace names should be done earlier AutoloadHandler::s_instance->invokeHandler( StrNR(const_cast(clsName))); cls = *cache; if (checkOnly) { // If the class still doesn't exist, return flags causing the // attribute check in the translated code that called us to fail. return (Class*)(uintptr_t)(cls ? cls->attrs() : (isClass ? (AttrTrait | AttrInterface) : AttrNone)); } else if (UNLIKELY(!cls)) { undefinedError(Strings::UNKNOWN_CLASS, clsName->data()); } return cls; } template Class* lookupKnownClass(Class**, const StringData*, bool); template Class* lookupKnownClass(Class**, const StringData*, bool); //============================================================================= // ClassCache template<> inline int ClassCache::hashKey(StringData* sd) { return sd->hash(); } template<> const Class* ClassCache::lookup(Handle handle, StringData *name, const void* unused) { ClassCache* thiz = cacheAtHandle(handle); Pair *pair = thiz->keyToPair(name); const StringData* pairSd = pair->m_key; if (!stringMatches(pairSd, name)) { TRACE(1, "ClassCache miss: %s\n", name->data()); const NamedEntity *ne = Unit::GetNamedEntity(name); Class *c = Unit::lookupClass(ne); if (UNLIKELY(!c)) { String normName = normalizeNS(name); if (normName) { return lookup(handle, normName.get(), unused); } else { c = Unit::loadMissingClass(ne, name); } if (UNLIKELY(!c)) { undefinedError(Strings::UNKNOWN_CLASS, name->data()); } } if (pair->m_key) decRefStr(pair->m_key); pair->m_key = name; name->incRefCount(); pair->m_value = c; } else { TRACE(1, "ClassCache hit: %s\n", name->data()); } return pair->m_value; } /* * Constants are raw TypedValues read from TLS storage by emitted code. * We must represent the undefined value as KindOfUninit == 0. Constant * definition is hooked in the runtime to allocate and update these * structures. */ CacheHandle allocConstant(uint32_t* handlep, bool persistent) { if (UNLIKELY(!*handlep)) { Lock l(s_handleMutex); if (!*handlep) { *handlep = allocLocked(persistent, sizeof(TypedValue), sizeof(TypedValue)); } } return *handlep; } CacheHandle allocStatic() { return namedAlloc(nullptr, sizeof(TypedValue*), sizeof(TypedValue*)); } CacheHandle allocClassConstant(StringData* name) { return namedAlloc(name, sizeof(TypedValue), sizeof(TypedValue)); } TypedValue* lookupClassConstant(TypedValue* cache, const NamedEntity* ne, const StringData* cls, const StringData* cns) { Stats::inc(Stats::TgtCache_ClsCnsHit, -1); Stats::inc(Stats::TgtCache_ClsCnsMiss, 1); TypedValue* clsCns; clsCns = g_vmContext->lookupClsCns(ne, cls, cns); *cache = *clsCns; return cache; } TypedValue lookupClassConstantTv(TypedValue* cache, const NamedEntity* ne, const StringData* cls, const StringData* cns) { return *lookupClassConstant(cache, ne, cls, cns); } //============================================================================= // *SPropCache // TypedValue* SPropCache::lookup(Handle handle, const Class *cls, const StringData *name) { // The fast path is in-TC. If we get here, we have already missed. SPropCache* thiz = cacheAtHandle(handle); Stats::inc(Stats::TgtCache_SPropMiss); Stats::inc(Stats::TgtCache_SPropHit, -1); assert(cls && name); assert(!thiz->m_tv); TRACE(3, "SPropCache miss: %s::$%s\n", cls->name()->data(), name->data()); // This is valid only if the lookup comes from an in-class method Class *ctx = const_cast(cls); if (debug) { VMRegAnchor _; assert(ctx == arGetContextClass((ActRec*)vmfp())); } bool visible, accessible; TypedValue* val; val = cls->getSProp(ctx, name, visible, accessible); if (UNLIKELY(!visible)) { string methodName; string_printf(methodName, "%s::$%s", cls->name()->data(), name->data()); undefinedError("Invalid static property access: %s", methodName.c_str()); } // We only cache in class references, thus we can always cache them // once the property is known to exist assert(accessible); thiz->m_tv = val; TRACE(3, "SPropCache::lookup(\"%s::$%s\") %p -> %p t%d\n", cls->name()->data(), name->data(), val, val->m_data.pref, val->m_type); assert(val->m_type >= MinDataType && val->m_type < MaxNumDataTypes); return val; } template TypedValue* SPropCache::lookupSProp(const Class *cls, const StringData *name, Class* ctx) { bool visible, accessible; TypedValue* val; val = cls->getSProp(ctx, name, visible, accessible); if (UNLIKELY(!visible || !accessible)) { if (!raiseOnError) return NULL; string propertyName; string_printf(propertyName, "%s::%s", cls->name()->data(), name->data()); undefinedError("Invalid static property access: %s", propertyName.c_str()); } return val; } template TypedValue* SPropCache::lookupSProp(const Class *cls, const StringData *name, Class* ctx); template TypedValue* SPropCache::lookupSProp(const Class *cls, const StringData *name, Class* ctx); template TypedValue* SPropCache::lookupIR(Handle handle, const Class *cls, const StringData *name, Class* ctx) { // The fast path is in-TC. If we get here, we have already missed. SPropCache* thiz = cacheAtHandle(handle); Stats::inc(Stats::TgtCache_SPropMiss); Stats::inc(Stats::TgtCache_SPropHit, -1); assert(cls && name); assert(!thiz->m_tv); TRACE(3, "SPropCache miss: %s::$%s\n", cls->name()->data(), name->data()); TypedValue* val = lookupSProp(cls, name, ctx); if (!val) { assert(!raiseOnError); return NULL; } thiz->m_tv = val; TRACE(3, "SPropCache::lookup(\"%s::$%s\") %p -> %p t%d\n", cls->name()->data(), name->data(), val, val->m_data.pref, val->m_type); assert(val->m_type >= MinDataType && val->m_type < MaxNumDataTypes); return val; } template TypedValue* SPropCache::lookupIR(Handle handle, const Class *cls, const StringData *name, Class* ctx); template TypedValue* SPropCache::lookupIR(Handle handle, const Class *cls, const StringData *name, Class* ctx); //============================================================================= // StaticMethodCache // template static inline CacheHandle allocStaticMethodCache(const StringData* clsName, const StringData* methName, const char* ctxName) { // Implementation detail of FPushClsMethodD/F: we use "C::M:ctx" as // the key for invoking static method "M" on class "C". This // composes such a key. "::" is semi-arbitrary, though whatever we // choose must delimit possible class and method names, so we might // as well ape the source syntax const StringData* joinedName = StringData::GetStaticString(String(clsName->data()) + String("::") + String(methName->data()) + String(":") + String(ctxName)); return namedAlloc(joinedName, sizeof(T), sizeof(T)); } CacheHandle StaticMethodCache::alloc(const StringData* clsName, const StringData* methName, const char* ctxName) { return allocStaticMethodCache( clsName, methName, ctxName); } CacheHandle StaticMethodFCache::alloc(const StringData* clsName, const StringData* methName, const char* ctxName) { return allocStaticMethodCache( clsName, methName, ctxName); } const Func* StaticMethodCache::lookupIR(Handle handle, const NamedEntity *ne, const StringData* clsName, const StringData* methName, TypedValue* vmfp, TypedValue* vmsp) { StaticMethodCache* thiz = static_cast (handleToPtr(handle)); Stats::inc(Stats::TgtCache_StaticMethodMiss); Stats::inc(Stats::TgtCache_StaticMethodHit, -1); TRACE(1, "miss %s :: %s caller %p\n", clsName->data(), methName->data(), __builtin_return_address(0)); ActRec* ar = reinterpret_cast(vmsp - kNumActRecCells); const Func* f; VMExecutionContext* ec = g_vmContext; const Class* cls = Unit::loadClass(ne, clsName); if (UNLIKELY(!cls)) { raise_error(Strings::UNKNOWN_CLASS, clsName->data()); } LookupResult res = ec->lookupClsMethod(f, cls, methName, nullptr, // there may be an active this, // but we can just fall through // in that case. (ActRec*)vmfp, false /*raise*/); if (LIKELY(res == LookupResult::MethodFoundNoThis && !f->isAbstract() && f->isStatic())) { f->validate(); TRACE(1, "fill %s :: %s -> %p\n", clsName->data(), methName->data(), f); // Do the | here instead of on every call. thiz->m_cls = (Class*)(uintptr_t(cls) | 1); thiz->m_func = f; ar->setClass(const_cast(cls)); return f; } assert(res != LookupResult::MethodFoundWithThis); // Not possible: no this. // Indicate to the IR that it should take even slower path return nullptr; } const Func* StaticMethodCache::lookup(Handle handle, const NamedEntity *ne, const StringData* clsName, const StringData* methName) { StaticMethodCache* thiz = static_cast (handleToPtr(handle)); Stats::inc(Stats::TgtCache_StaticMethodMiss); Stats::inc(Stats::TgtCache_StaticMethodHit, -1); TRACE(1, "miss %s :: %s caller %p\n", clsName->data(), methName->data(), __builtin_return_address(0)); VMRegAnchor _; // needed for lookupClsMethod. ActRec* ar = reinterpret_cast(vmsp() - kNumActRecCells); const Func* f; VMExecutionContext* ec = g_vmContext; const Class* cls = Unit::loadClass(ne, clsName); if (UNLIKELY(!cls)) { raise_error(Strings::UNKNOWN_CLASS, clsName->data()); } LookupResult res = ec->lookupClsMethod(f, cls, methName, nullptr, // there may be an active this, // but we can just fall through // in that case. ec->getFP(), false /*raise*/); if (LIKELY(res == LookupResult::MethodFoundNoThis && !f->isAbstract() && f->isStatic())) { f->validate(); TRACE(1, "fill %s :: %s -> %p\n", clsName->data(), methName->data(), f); // Do the | here instead of on every call. thiz->m_cls = (Class*)(uintptr_t(cls) | 1); thiz->m_func = f; ar->setClass(const_cast(cls)); return f; } assert(res != LookupResult::MethodFoundWithThis); // Not possible: no this. // We've already sync'ed regs; this is some hard case, we might as well // just let the interpreter handle this entirely. assert(toOp(*vmpc()) == OpFPushClsMethodD); Stats::inc(Stats::Instr_InterpOneFPushClsMethodD); Stats::inc(Stats::Instr_TC, -1); ec->opFPushClsMethodD(); // Return whatever func the instruction produced; if nothing was // possible we'll either have fataled or thrown. assert(ar->m_func); ar->m_func->validate(); // Don't update the cache; this case was too scary to memoize. TRACE(1, "unfillable miss %s :: %s -> %p\n", clsName->data(), methName->data(), ar->m_func); // Indicate to the caller that there is no work to do. return nullptr; } const Func* StaticMethodFCache::lookupIR(Handle handle, const Class* cls, const StringData* methName, TypedValue* vmfp) { assert(cls); StaticMethodFCache* thiz = static_cast (handleToPtr(handle)); Stats::inc(Stats::TgtCache_StaticMethodFMiss); Stats::inc(Stats::TgtCache_StaticMethodFHit, -1); const Func* f; VMExecutionContext* ec = g_vmContext; LookupResult res = ec->lookupClsMethod(f, cls, methName, nullptr, (ActRec*)vmfp, false /*raise*/); assert(res != LookupResult::MethodFoundWithThis); // Not possible: no this. if (LIKELY(res == LookupResult::MethodFoundNoThis && !f->isAbstract())) { // We called lookupClsMethod with a NULL this and got back a // method that may or may not be static. This implies that // lookupClsMethod, given the same class and the same method name, // will never return MagicCall*Found or MethodNotFound. It will // always return the same f and if we do give it a this it will // return MethodFoundWithThis iff (this->instanceof(cls) && // !f->isStatic()). this->instanceof(cls) is always true for // FPushClsMethodF because it is only used for self:: and parent:: // calls. So, if we store f and its staticness we can handle calls // with and without this completely in assembly. f->validate(); thiz->m_func = f; thiz->m_static = f->isStatic(); TRACE(1, "fill staticfcache %s :: %s -> %p\n", cls->name()->data(), methName->data(), f); Stats::inc(Stats::TgtCache_StaticMethodFFill); return f; } return nullptr; } } } } // HPHP::Transl::TargetCache