Arquivos
hhvm/hphp/runtime/vm/jit/targetcache.cpp
T
Jordan DeLong 67bf707768 Fixes and improvements for type aliases
Replace NameDef with a new struct of runtime-resolved typedef
information.  This needs to include more than Class* or Typedef*,
because we might have nullable type aliases, or a non-nullable alias
to a nullable typedef, or vice versa.  Switch to the new
TypeAnnotation stuff in TypedefStatement instead of just strings so
support for this isn't weird (shapes are outside of this for now
though---see the hack in parser.cpp).  Also fixes support for type
aliases to mixed.
2013-06-27 15:14:22 -07:00

1152 linhas
38 KiB
C++

/*
+----------------------------------------------------------------------+
| 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/targetcache.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 <string>
#include <stdio.h>
#include <sys/mman.h>
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 targetcache.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<const StringData*, Handle,
StringDataHashICompare>
HandleMapIS;
typedef tbb::concurrent_hash_map<const StringData*, Handle,
StringDataHashCompare>
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<Handle> 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 <bool sensitive>
class HandleInfo {
public:
typedef HandleMapIS Map;
static Map &getHandleMap(int where) {
return handleMapsIS[where];
}
};
template <>
class HandleInfo<true> {
public:
typedef HandleMapCS Map;
static Map &getHandleMap(int where) {
return handleMapsCS[where - FirstCaseSensitive];
}
};
#define getHMap(where) \
HandleInfo<where >= FirstCaseSensitive>::getHandleMap(where)
static size_t allocBitImpl(const StringData* name, PHPNameSpace ns) {
ASSERT_NOT_IMPLEMENTED(ns == NSInvalid || ns >= FirstCaseSensitive);
HandleMapCS& map = HandleInfo<true>::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<bool sensitive>
Handle
namedAlloc(PHPNameSpace where, const StringData* name,
int numBytes, int align) {
assert(!name || (where >= 0 && where < NumNameSpaces));
typedef HandleInfo<sensitive> 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<true>(PHPNameSpace where, const StringData* name,
int numBytes, int align);
template
Handle namedAlloc<false>(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<Class> h;
return h(reinterpret_cast<const Class*>(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<Class*>(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<TypedValue*>(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<uintptr_t>(cls))) {
ar->m_func = pair->m_value;
} else {
auto* name = static_cast<const StringData*>(extraKey);
methodCacheSlowPath(pair, ar, const_cast<StringData*>(name), cls);
}
}
//=============================================================================
// GlobalCache
// | - BoxedGlobalCache
template<bool isBoxed>
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<false>(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<false>(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<false>(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<true>(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<true>(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<unsigned*>(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<NSClsInitProp>(name, sizeof(Class::PropInitVec*),
sizeof(Class::PropInitVec*));
}
CacheHandle allocClassInitSProp(const StringData* name) {
return namedAlloc<NSClsInitSProp>(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<NamedEntity*>(ne)->m_cachedTypedefOffset =
allocLocked(false /* persistent */,
sizeof(TypedefReq),
alignof(TypedefReq));
return ne->m_cachedTypedefOffset;
}
template<bool checkOnly>
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<StringData*>(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<true>(Class**, const StringData*, bool);
template Class* lookupKnownClass<false>(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<NSInvalid>(nullptr, sizeof(TypedValue*),
sizeof(TypedValue*));
}
CacheHandle allocClassConstant(StringData* name) {
return namedAlloc<NSClassConstant>(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<Class*>(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<bool raiseOnError>
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<true>(const Class *cls,
const StringData *name,
Class* ctx);
template TypedValue* SPropCache::lookupSProp<false>(const Class *cls,
const StringData *name,
Class* ctx);
template<bool raiseOnError>
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<raiseOnError>(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<true>(Handle handle,
const Class *cls,
const StringData *name,
Class* ctx);
template TypedValue* SPropCache::lookupIR<false>(Handle handle,
const Class *cls,
const StringData *name,
Class* ctx);
//=============================================================================
// StaticMethodCache
//
template<typename T, PHPNameSpace ns>
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<ns>(joinedName, sizeof(T), sizeof(T));
}
CacheHandle
StaticMethodCache::alloc(const StringData* clsName,
const StringData* methName,
const char* ctxName) {
return allocStaticMethodCache<StaticMethodCache, NSStaticMethod>(
clsName, methName, ctxName);
}
CacheHandle
StaticMethodFCache::alloc(const StringData* clsName,
const StringData* methName,
const char* ctxName) {
return allocStaticMethodCache<StaticMethodFCache, NSStaticMethodF>(
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<StaticMethodCache*>
(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<ActRec*>(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<Class*>(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<StaticMethodCache*>
(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<ActRec*>(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<Class*>(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(*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<StaticMethodFCache*>
(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