AutoloadHandler::Result AutoloadHandler::loadFromMap(const String& name, const String& kind, bool toLower, const T &checkExists) { assert(!m_map.isNull()); while (true) { CVarRef &type_map = m_map.get()->get(kind); auto const typeMapCell = type_map.asCell(); if (typeMapCell->m_type != KindOfArray) return Failure; String canonicalName = toLower ? f_strtolower(name) : name; CVarRef &file = typeMapCell->m_data.parr->get(canonicalName); bool ok = false; if (file.isString()) { String fName = file.toCStrRef().get(); if (fName.get()->data()[0] != '/') { if (!m_map_root.empty()) { fName = m_map_root + fName; } } try { Transl::VMRegAnchor _; bool initial; VMExecutionContext* ec = g_vmContext; Unit* u = ec->evalInclude(fName.get(), nullptr, &initial); if (u) { if (initial) { TypedValue retval; ec->invokeFunc(&retval, u->getMain(), null_array, nullptr, nullptr, nullptr, nullptr, ExecutionContext::InvokePseudoMain); tvRefcountedDecRef(&retval); } ok = true; } } catch (...) {} } if (ok && checkExists(name)) { return Success; } CVarRef &func = m_map.get()->get(s_failure); if (func.isNull()) return Failure; // can throw, otherwise // - true means the map was updated. try again // - false means we should stop applying autoloaders (only affects classes) // - anything else means keep going Variant action = vm_call_user_func(func, make_packed_array(kind, name)); auto const actionCell = action.asCell(); if (actionCell->m_type == KindOfBoolean) { if (actionCell->m_data.num) continue; return StopAutoloading; } return ContinueAutoloading; } }
const Func* StaticMethodCache::lookup(RDS::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)); Transl::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. arGetContextClass(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(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; }
int DebuggerProxy::getRealStackDepth() { TRACE(2, "DebuggerProxy::getRealStackDepth\n"); int depth = 0; VMExecutionContext* context = g_vmContext; ActRec *fp = context->getFP(); if (!fp) return 0; while (fp != nullptr) { fp = context->getPrevVMState(fp, nullptr, nullptr); depth++; } return depth; }
int DebuggerProxy::getStackDepth() { TRACE(2, "DebuggerProxy::getStackDepth\n"); int depth = 0; VMExecutionContext* context = g_vmContext; ActRec *fp = context->getFP(); if (!fp) return 0; ActRec *prev = fp->arGetSfp(); while (fp != prev) { fp = prev; prev = fp->arGetSfp(); depth++; } return depth; }
const Func* StaticMethodCache::lookupIR(RDS::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. arGetContextClass((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; }
void TranslatorX64::fCallArrayHelper(const Offset pcOff, const Offset pcNext) { DECLARE_FRAME_POINTER(framePtr); ActRec* fp = (ActRec*)framePtr->m_savedRbp; VMExecutionContext *ec = g_vmContext; ec->m_fp = fp; ec->m_stack.top() = sp; ec->m_pc = curUnit()->at(pcOff); PC pc = curUnit()->at(pcNext); tl_regState = REGSTATE_CLEAN; bool runFunc = ec->doFCallArray(pc); sp = ec->m_stack.top(); tl_regState = REGSTATE_DIRTY; if (!runFunc) return; ec->m_fp->m_savedRip = framePtr->m_savedRip; // smash our return and frame pointer chain framePtr->m_savedRip = (uint64_t)ec->m_fp->m_func->getFuncBody(); framePtr->m_savedRbp = (uint64_t)ec->m_fp; }
const uchar* InstPointInfo::lookupPC() { TRACE(2, "InstPointInfo::lookupPC\n"); VMExecutionContext* context = g_vmContext; if (m_locType == LocHere) { // Instrument to current location ActRec *fp = context->getFP(); if (!fp) { return nullptr; } PC pc = context->getPC(); HPHP::Unit *unit = fp->m_func->unit(); if (!unit) { return nullptr; } m_file = unit->filepath()->data(); m_line = unit->getLineNumber(unit->offsetOf(pc)); return pc; } // TODO for file and line return nullptr; }
const Func* StaticMethodFCache::lookupIR(RDS::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, arGetContextClass((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; }
Class* Unit::defClass(PreClass* preClass, bool failIsFatal /* = true */) { Class* const* clsList = preClass->namedEntity()->clsList(); Class* top = *clsList; if (top) { Class *cls = top->getCached(); if (cls) { // Raise a fatal unless the existing class definition is identical to the // one this invocation would create. if (cls->preClass() != preClass) { if (failIsFatal) { raise_error("Class already declared: %s", preClass->name()->data()); } return NULL; } return cls; } } // Get a compatible Class, and add it to the list of defined classes. Class* parent = NULL; for (;;) { // Search for a compatible extant class. Searching from most to least // recently created may have better locality than alternative search orders. // In addition, its the only simple way to make this work lock free... for (Class* class_ = top; class_ != NULL; class_ = class_->m_nextClass) { if (class_->preClass() != preClass) continue; Class::Avail avail = class_->avail(parent, failIsFatal /*tryAutoload*/); if (LIKELY(avail == Class::AvailTrue)) { class_->setCached(); DEBUGGER_ATTACHED_ONLY(phpDefClassHook(class_)); return class_; } if (avail == Class::AvailFail) { if (failIsFatal) { raise_error("unknown class %s", parent->name()->data()); } return NULL; } ASSERT(avail == Class::AvailFalse); } // Create a new class. if (!parent && preClass->parent()->size() != 0) { parent = Unit::getClass(preClass->parent(), failIsFatal); if (parent == NULL) { if (failIsFatal) { raise_error("unknown class %s", preClass->parent()->data()); } return NULL; } } VMExecutionContext* ec = g_vmContext; ActRec* fp = ec->getFP(); PC pc = ec->getPC(); bool needsFrame = ec->m_stack.top() && (!fp || fp->m_func->unit() != preClass->unit()); if (needsFrame) { /* we can be called from Unit::merge, which hasnt yet setup the frame (because often it doesnt need to). Set up a fake frame here, in case of errors. But note that mergeUnit is called for systemlib etc before the stack has been setup. So dont do anything if m_stack.top() is NULL */ ActRec &tmp = *ec->m_stack.allocA(); tmp.m_savedRbp = (uint64_t)fp; tmp.m_savedRip = 0; tmp.m_func = preClass->unit()->getMain(); tmp.m_soff = preClass->getOffset() - tmp.m_func->base(); tmp.setThis(NULL); tmp.m_varEnv = 0; tmp.initNumArgs(0); ec->m_fp = &tmp; ec->m_pc = preClass->unit()->at(preClass->getOffset()); ec->pushLocalsAndIterators(tmp.m_func); } ClassPtr newClass(Class::newClass(preClass, parent)); if (needsFrame) { ec->m_stack.top() = (Cell*)(ec->m_fp+1); ec->m_fp = fp; ec->m_pc = pc; } Lock l(Unit::s_classesMutex); /* We could re-enter via Unit::getClass() or class_->avail(), so no need for *clsList to be volatile */ if (UNLIKELY(top != *clsList)) { top = *clsList; continue; } if (top) { newClass->m_cachedOffset = top->m_cachedOffset; } else { newClass->m_cachedOffset = Transl::TargetCache::allocKnownClass(preClass->name()); } newClass->m_nextClass = top; Util::compiler_membar(); *const_cast<Class**>(clsList) = newClass.get(); newClass.get()->incAtomicCount(); newClass.get()->setCached(); DEBUGGER_ATTACHED_ONLY(phpDefClassHook(newClass.get())); return newClass.get(); } }