hphp/runtime/vm/func.cpp (991 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present 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/func.h"
#include "hphp/runtime/base/attr.h"
#include "hphp/runtime/ext/extension.h"
#include "hphp/runtime/base/autoload-handler.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/execution-context.h"
#include "hphp/runtime/base/init-fini-node.h"
#include "hphp/runtime/base/intercept.h"
#include "hphp/runtime/base/runtime-option.h"
#include "hphp/runtime/base/static-string-table.h"
#include "hphp/runtime/base/string-data.h"
#include "hphp/runtime/base/type-string.h"
#include "hphp/runtime/server/memory-stats.h"
#include "hphp/runtime/vm/as-shared.h"
#include "hphp/runtime/vm/class.h"
#include "hphp/runtime/vm/cti.h"
#include "hphp/runtime/vm/reified-generics.h"
#include "hphp/runtime/vm/repo-file.h"
#include "hphp/runtime/vm/repo-global-data.h"
#include "hphp/runtime/vm/reverse-data-map.h"
#include "hphp/runtime/vm/source-location.h"
#include "hphp/runtime/vm/treadmill.h"
#include "hphp/runtime/vm/type-constraint.h"
#include "hphp/runtime/vm/unit.h"
#include "hphp/runtime/vm/unit-util.h"
#include "hphp/runtime/vm/jit/mcgen.h"
#include "hphp/runtime/vm/jit/tc.h"
#include "hphp/runtime/vm/jit/types.h"
#include "hphp/system/systemlib.h"
#include "hphp/util/atomic-vector.h"
#include "hphp/util/fixed-vector.h"
#include "hphp/util/functional.h"
#include "hphp/util/struct-log.h"
#include "hphp/util/trace.h"
#include <algorithm>
#include <atomic>
#include <iomanip>
#include <ostream>
#include <string>
#include <vector>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
TRACE_SET_MOD(hhbc);
std::atomic<bool> Func::s_treadmill;
Mutex g_funcsMutex;
/*
* FuncId high water mark and FuncId -> Func* table.
* We can't start with 0 since that's used for special sentinel value
* in TreadHashMap
*/
static std::atomic<FuncId::Int> s_nextFuncId{1};
#ifndef USE_LOWPTR
AtomicLowPtrVector<const Func> Func::s_funcVec{0, nullptr};
static InitFiniNode s_funcVecReinit([]{
UnsafeReinitEmptyAtomicLowPtrVector(
Func::s_funcVec, RuntimeOption::EvalFuncCountHint);
}, InitFiniNode::When::PostRuntimeOptions, "s_funcVec reinit");
#endif
namespace {
inline int numProloguesForNumParams(int numParams) {
// The number of prologues is numParams + 2. The extra 2 are needed for
// the following cases:
// - arguments passed > numParams
// - no arguments passed
return numParams + 2;
}
}
///////////////////////////////////////////////////////////////////////////////
// Creation and destruction.
Func::Func(Unit& unit, const StringData* name, Attr attrs)
: m_name(name)
, m_isPreFunc(false)
, m_hasPrivateAncestor(false)
, m_shouldSampleJit(StructuredLog::coinflip(RuntimeOption::EvalJitSampleRate))
, m_hasForeignThis(false)
, m_registeredInDataMap(false)
, m_unit(&unit)
, m_shared(nullptr)
, m_attrs(attrs)
{
}
Func::Func(
Unit& unit, const StringData* name, Attr attrs,
const StringData *methCallerCls, const StringData *methCallerMeth)
: m_name(name)
, m_methCallerMethName(to_low(methCallerMeth, kMethCallerBit))
, m_u(methCallerCls)
, m_isPreFunc(false)
, m_hasPrivateAncestor(false)
, m_shouldSampleJit(StructuredLog::coinflip(RuntimeOption::EvalJitSampleRate))
, m_hasForeignThis(false)
, m_registeredInDataMap(false)
, m_unit(&unit)
, m_shared(nullptr)
, m_attrs(attrs)
{
assertx(methCallerCls != nullptr);
assertx(methCallerMeth != nullptr);
}
Func::~Func() {
// Should've deregistered in Func::destroy() or Func::freeClone()
assertx(!m_registeredInDataMap);
#ifndef NDEBUG
validate();
m_magic = ~m_magic;
#endif
}
void* Func::allocFuncMem(int numParams) {
int numPrologues = numProloguesForNumParams(numParams);
auto const funcSize =
sizeof(Func) + numPrologues * sizeof(m_prologueTable[0])
- sizeof(m_prologueTable);
MemoryStats::LogAlloc(AllocKind::Func, funcSize);
return lower_malloc(funcSize);
}
void Func::destroy(Func* func) {
if (jit::mcgen::initialized() && RuntimeOption::EvalEnableReusableTC) {
// Free TC-space associated with func
jit::tc::reclaimFunction(func);
}
if (func->m_registeredInDataMap) {
func->deregisterInDataMap();
}
#ifndef USE_LOWPTR
if (!func->m_funcId.isInvalid()) {
assertx(s_funcVec.get(func->m_funcId.toInt()) == func);
s_funcVec.set(func->m_funcId.toInt(), nullptr);
func->m_funcId = {FuncId::Invalid};
}
#endif
if (s_treadmill.load(std::memory_order_acquire)) {
Treadmill::enqueue([func](){
func->~Func();
lower_free(func);
});
return;
}
func->~Func();
lower_free(func);
}
void Func::freeClone() {
assertx(isPreFunc());
assertx(m_cloned.flag.test_and_set());
if (jit::mcgen::initialized() && RuntimeOption::EvalEnableReusableTC) {
// Free TC-space associated with func
jit::tc::reclaimFunction(this);
}
if (m_registeredInDataMap) {
deregisterInDataMap();
}
#ifndef USE_LOWPTR
if (!m_funcId.isInvalid()) {
assertx(s_funcVec.get(m_funcId.toInt()) == this);
s_funcVec.set(m_funcId.toInt(), nullptr);
m_funcId = {FuncId::Invalid};
}
#endif
m_cloned.flag.clear();
}
Func* Func::clone(Class* cls, const StringData* name) const {
auto numParams = this->numParams();
// If this is a PreFunc (i.e., a Func on a PreClass) that is not already
// being used as a regular Func by a Class, and we aren't trying to change
// its name (since the name is part of the template for later clones), we can
// reuse this same Func as the clone.
bool const can_reuse =
m_isPreFunc && !name && !m_cloned.flag.test_and_set();
Func* f = !can_reuse
? new (allocFuncMem(numParams)) Func(*this)
: const_cast<Func*>(this);
f->m_cloned.flag.test_and_set();
f->initPrologues(numParams);
f->m_funcEntry = nullptr;
if (name) f->m_name = name;
f->m_u.setCls(cls);
f->setFullName(numParams);
if (f != this) {
f->m_isPreFunc = false;
f->m_registeredInDataMap = false;
}
#ifndef USE_LOWPTR
f->m_funcId = {FuncId::Invalid};
#endif
f->setNewFuncId();
return f;
}
void Func::rescope(Class* ctx) {
m_u.setCls(ctx);
setFullName(numParams());
}
///////////////////////////////////////////////////////////////////////////////
// Initialization.
void Func::init(int numParams) {
#ifndef NDEBUG
m_magic = kMagic;
#endif
setNewFuncId();
// For methods, we defer setting the full name until m_cls is initialized
if (!preClass()) {
setFullName(numParams);
} else {
m_fullName = nullptr;
}
if (isSpecial(m_name)) {
/*
* We dont want these compiler generated functions to
* appear in backtraces.
*/
m_attrs = m_attrs | AttrNoInjection;
}
assertx(m_name);
initPrologues(numParams);
}
void Func::initPrologues(int numParams) {
int numPrologues = numProloguesForNumParams(numParams);
if (!jit::mcgen::initialized()) {
for (int i = 0; i < numPrologues; i++) {
m_prologueTable[i] = nullptr;
}
return;
}
auto const& stubs = jit::tc::ustubs();
TRACE(4, "initPrologues func %p %d\n", this, numPrologues);
for (int i = 0; i < numPrologues; i++) {
m_prologueTable[i] = stubs.fcallHelperThunk;
}
}
void Func::setFullName(int /*numParams*/) {
assertx(m_name->isStatic());
Class *clazz = cls();
if (clazz) {
m_fullName = (StringData*)kNeedsFullName;
} else {
m_fullName = m_name.get();
// A scoped closure may not have a `cls', but we still need to preserve its
// `methodSlot', which refers to its slot in its `baseCls' (which still
// points to a subclass of Closure).
if (!isMethod()) {
setNamedEntity(NamedEntity::get(m_name));
}
}
}
/* This function is expected to be called after all calls to appendParam
* are complete. After, m_paramCounts is initialized such that the least
* significant bit of this->m_paramCounts indicates whether the last param
* is (non)variadic; and the rest of the bits are the number of params.
*/
void Func::finishedEmittingParams(std::vector<ParamInfo>& fParams) {
assertx(m_paramCounts == 0);
assertx(m_inoutBits == 0);
// Initialize m_paramCounts.
shared()->m_params = fParams;
m_paramCounts = fParams.size() << 1;
if (!(m_attrs & AttrVariadicParam)) {
m_paramCounts |= 1;
}
assertx(numParams() == fParams.size());
// Build m_inoutBits.
for (auto i = 0u; i < fParams.size(); ++i) {
if (LIKELY(!fParams[i].isInOut())) continue;
m_inoutBits |= 1u << std::min(i, kInoutFastCheckBits);
}
}
void Func::registerInDataMap() {
#ifndef USE_LOWPTR
assertx(!m_funcId.isInvalid());
#endif
assertx((!m_isPreFunc || m_cloned.flag.test_and_set()));
assertx(!m_registeredInDataMap);
assertx(mallocEnd());
data_map::register_start(this);
m_registeredInDataMap = true;
}
void Func::deregisterInDataMap() {
assertx(m_registeredInDataMap);
assertx((!m_isPreFunc || m_cloned.flag.test_and_set()));
#ifndef USE_LOWPTR
assertx(!m_funcId.isInvalid());
#endif
data_map::deregister(this);
m_registeredInDataMap = false;
}
bool Func::isMemoizeImplName(const StringData* name) {
return name->size() > 13 && !memcmp(name->data() + name->size() - 13,
"$memoize_impl", 13);
}
const StringData* Func::genMemoizeImplName(const StringData* origName) {
return makeStaticString(folly::sformat("{}$memoize_impl", origName->data()));
}
std::pair<const StringData*, const StringData*> Func::getMethCallerNames(
const StringData* name) {
assertx(name->size() > 11 && !memcmp(name->data(), "MethCaller$", 11));
auto clsMethName = name->slice();
clsMethName.uncheckedAdvance(11);
auto const sep = folly::qfind(clsMethName, folly::StringPiece("$"));
assertx(sep != std::string::npos);
auto cls = clsMethName.uncheckedSubpiece(0, sep);
auto meth = clsMethName.uncheckedSubpiece(sep + 1);
return std::make_pair(makeStaticString(cls), makeStaticString(meth));
}
///////////////////////////////////////////////////////////////////////////////
// FuncId manipulation.
FuncId::Int Func::maxFuncIdNum() {
return s_nextFuncId.load(std::memory_order_relaxed);
}
#ifdef USE_LOWPTR
void Func::setNewFuncId() {
s_nextFuncId.fetch_add(1, std::memory_order_relaxed);
}
const Func* Func::fromFuncId(FuncId id) {
auto const func = id.getFunc();
func->validate();
return func;
}
bool Func::isFuncIdValid(FuncId id) {
return !id.isInvalid() && !id.isDummy();
}
#else
void Func::setNewFuncId() {
assertx(m_funcId.isInvalid());
m_funcId = {s_nextFuncId.fetch_add(1, std::memory_order_relaxed)};
s_funcVec.ensureSize(m_funcId.toInt() + 1);
assertx(s_funcVec.get(m_funcId.toInt()) == nullptr);
s_funcVec.set(m_funcId.toInt(), this);
}
const Func* Func::fromFuncId(FuncId id) {
assertx(id.toInt() < s_nextFuncId);
auto const func = s_funcVec.get(id.toInt());
func->validate();
return func;
}
bool Func::isFuncIdValid(FuncId id) {
if (id.toInt() >= s_nextFuncId) return false;
return s_funcVec.get(id.toInt()) != nullptr;
}
#endif
///////////////////////////////////////////////////////////////////////////////
// Bytecode.
bool Func::isEntry(Offset offset) const {
return offset == 0 || isDVEntry(offset);
}
bool Func::isDVEntry(Offset offset) const {
auto const nparams = numNonVariadicParams();
for (int i = 0; i < nparams; i++) {
const ParamInfo& pi = params()[i];
if (pi.hasDefaultValue() && pi.funcletOff == offset) return true;
}
return false;
}
int Func::getEntryNumParams(Offset offset) const {
if (offset == 0) return numNonVariadicParams();
return getDVEntryNumParams(offset);
}
int Func::getDVEntryNumParams(Offset offset) const {
auto const nparams = numNonVariadicParams();
for (int i = 0; i < nparams; i++) {
const ParamInfo& pi = params()[i];
if (pi.hasDefaultValue() && pi.funcletOff == offset) return i;
}
return -1;
}
Offset Func::getEntryForNumArgs(int numArgsPassed) const {
assertx(numArgsPassed >= 0);
auto const nparams = numNonVariadicParams();
for (unsigned i = numArgsPassed; i < nparams; i++) {
const Func::ParamInfo& pi = params()[i];
if (pi.hasDefaultValue()) {
return pi.funcletOff;
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// Parameters.
bool Func::isInOut(int32_t arg) const {
assertx(arg >= 0);
if (LIKELY(arg < kInoutFastCheckBits)) {
return m_inoutBits & (1ull << arg);
}
if (arg >= numParams()) return false;
return params()[arg].isInOut();
}
bool Func::isReadonly(int32_t arg) const {
assertx(arg >= 0);
if (arg >= numParams()) return false;
return params()[arg].isReadonly();
}
uint32_t Func::numInOutParams() const {
uint32_t count = folly::popcount(m_inoutBits);
if (LIKELY(static_cast<int32_t>(m_inoutBits) >= 0)) {
// The sign bit is not set, so only args 0..31 can possibly be inout.
return count;
}
assertx(numParams() > kInoutFastCheckBits);
for (auto i = numParams() - 1; i >= kInoutFastCheckBits; --i) {
count += params()[i].isInOut() ? 1 : 0;
}
// Minus one for the sign bit.
return count - 1;
}
uint32_t Func::numInOutParamsForArgs(int32_t numArgs) const {
if (!takesInOutParams()) return 0;
uint32_t i = 0;
for (int p = 0; p < numArgs; ++p) i += isInOut(p);
return i;
}
///////////////////////////////////////////////////////////////////////////////
// Locals, iterators, and stack.
Id Func::lookupVarId(const StringData* name) const {
assertx(name != nullptr);
return shared()->m_localNames.findIndex(name);
}
uint32_t Func::numClosureUseLocals() const {
assertx(isClosureBody());
auto const cls = implCls();
return cls->numDeclProperties() - (cls->hasClosureCoeffectsProp() ? 1 : 0);
}
///////////////////////////////////////////////////////////////////////////////
// Persistence.
bool Func::isImmutableFrom(const Class* cls) const {
if (!RuntimeOption::RepoAuthoritative) return false;
assertx(cls && cls->lookupMethod(name()) == this);
if (attrs() & AttrNoOverride) {
return true;
}
if (cls->preClass()->attrs() & AttrNoOverride) {
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// JIT data.
int Func::numPrologues() const {
return numProloguesForNumParams(numParams());
}
void Func::resetPrologue(int numParams) {
auto const& stubs = jit::tc::ustubs();
m_prologueTable[numParams] = stubs.fcallHelperThunk;
}
void Func::resetFuncEntry() {
m_funcEntry = nullptr;
}
///////////////////////////////////////////////////////////////////////////////
// Reified Generics
namespace {
const ReifiedGenericsInfo k_defaultReifiedGenericsInfo{0, false, 0, {}};
} // namespace
const ReifiedGenericsInfo& Func::getReifiedGenericsInfo() const {
if (!shared()->m_allFlags.m_hasReifiedGenerics) return k_defaultReifiedGenericsInfo;
auto const ex = extShared();
assertx(ex);
return ex->m_reifiedGenericsInfo;
}
///////////////////////////////////////////////////////////////////////////////
// Pretty printer.
void Func::print_attrs(std::ostream& out, Attr attrs) {
if (attrs & AttrStatic) { out << " static"; }
if (attrs & AttrPublic) { out << " public"; }
if (attrs & AttrProtected) { out << " protected"; }
if (attrs & AttrPrivate) { out << " private"; }
if (attrs & AttrAbstract) { out << " abstract"; }
if (attrs & AttrFinal) { out << " final"; }
if (attrs & AttrNoOverride){ out << " (nooverride)"; }
if (attrs & AttrInterceptable) { out << " (interceptable)"; }
if (attrs & AttrPersistent) { out << " (persistent)"; }
if (attrs & AttrBuiltin) { out << " (builtin)"; }
if (attrs & AttrIsFoldable) { out << " (foldable)"; }
if (attrs & AttrNoInjection) { out << " (no_injection)"; }
if (attrs & AttrSupportsAsyncEagerReturn) { out << " (can_async_eager_ret)"; }
if (attrs & AttrDynamicallyCallable) { out << " (dyn_callable)"; }
if (attrs & AttrIsMethCaller) { out << " (is_meth_caller)"; }
}
void Func::prettyPrint(std::ostream& out, const PrintOpts& opts) const {
if (opts.name) {
if (preClass() != nullptr) {
out << "Method";
print_attrs(out, m_attrs);
if (isPhpLeafFn()) out << " (leaf)";
if (isMemoizeWrapper()) out << " (memoize_wrapper)";
if (isMemoizeWrapperLSB()) out << " (memoize_wrapper_lsb)";
if (cls() != nullptr) {
out << ' ' << fullName()->data();
} else {
out << ' ' << preClass()->name()->data() << "::" << m_name->data();
}
} else {
out << "Function";
print_attrs(out, m_attrs);
if (isPhpLeafFn()) out << " (leaf)";
if (isMemoizeWrapper()) out << " (memoize_wrapper)";
if (isMemoizeWrapperLSB()) out << " (memoize_wrapper_lsb)";
out << ' ' << m_name->data();
}
out << std::endl;
}
if (opts.metadata) {
const ParamInfoVec& params = shared()->m_params;
for (uint32_t i = 0; i < params.size(); ++i) {
auto const& param = params[i];
out << " Param: " << localVarName(i)->data();
if (param.typeConstraint.hasConstraint()) {
out << " " << param.typeConstraint.displayName(cls(), true);
}
if (param.userType) {
out << " (" << param.userType->data() << ")";
}
if (param.funcletOff != kInvalidOffset) {
out << " DV" << " at " << param.funcletOff;
if (param.phpCode) {
out << " = " << param.phpCode->data();
}
}
out << std::endl;
}
if (returnTypeConstraint().hasConstraint() ||
(returnUserType() && !returnUserType()->empty())) {
out << " Ret: ";
if (returnTypeConstraint().hasConstraint()) {
out << " " << returnTypeConstraint().displayName(cls(), true);
}
if (returnUserType() && !returnUserType()->empty()) {
out << " (" << returnUserType()->data() << ")";
}
out << std::endl;
}
if (repoReturnType().tag() != RepoAuthType::Tag::Cell) {
out << "repoReturnType: " << show(repoReturnType()) << '\n';
}
if (repoAwaitedReturnType().tag() != RepoAuthType::Tag::Cell) {
out << "repoAwaitedReturnType: " << show(repoAwaitedReturnType()) << '\n';
}
out << "maxStackCells: " << maxStackCells() << '\n'
<< "numLocals: " << numLocals() << '\n'
<< "numIterators: " << numIterators() << '\n';
const EHEntVec& ehtab = shared()->m_ehtab;
size_t ehId = 0;
for (auto it = ehtab.begin(); it != ehtab.end(); ++it, ++ehId) {
out << " EH " << ehId << " Catch for " <<
it->m_base << ":" << it->m_past;
if (it->m_parentIndex != -1) {
out << " outer EH " << it->m_parentIndex;
}
if (it->m_iterId != -1) {
out << " iterId " << it->m_iterId;
}
out << " handle at " << it->m_handler;
if (it->m_end != kInvalidOffset) {
out << ":" << it->m_end;
}
if (it->m_parentIndex != -1) {
out << " parentIndex " << it->m_parentIndex;
}
out << std::endl;
}
}
if (opts.startOffset != kInvalidOffset) {
auto startOffset = std::max(0, opts.startOffset);
auto stopOffset = std::min(bclen(), opts.stopOffset);
if (startOffset >= stopOffset) {
return;
}
auto it = at(startOffset);
auto const stop = at(stopOffset);
int prevLineNum = -1;
while (it < stop) {
if (opts.showLines) {
auto const lineNum =
SourceLocation::getLineNumber(getOrLoadLineTable(), offsetOf(it));
if (lineNum != prevLineNum) {
out << " // line " << lineNum << std::endl;
prevLineNum = lineNum;
}
}
out << std::string(opts.indentSize, ' ')
<< std::setw(4) << offsetOf(it) << ": "
<< instrToString(it, this)
<< std::endl;
it += instrLen(it);
}
}
}
///////////////////////////////////////////////////////////////////////////////
// SharedData.
Func::SharedData::SharedData(BCPtr bc, Offset bclen,
PreClass* preClass, int sn, int line1,
int line2, bool isPhpLeafFn)
: m_bc(bc.isPtr() ? BCPtr::FromPtr(allocateBCRegion(bc.ptr(), bclen)) : bc)
, m_preClass(preClass)
, m_line1(line1)
, m_originalFilename(nullptr)
, m_cti_base(0)
, m_numLocals(0)
, m_numIterators(0)
{
m_allFlags.m_isClosureBody = false;
m_allFlags.m_isAsync = false;
m_allFlags.m_isGenerator = false;
m_allFlags.m_isPairGenerator = false;
m_allFlags.m_isGenerated = false;
m_allFlags.m_hasExtendedSharedData = false;
m_allFlags.m_returnByValue = false;
m_allFlags.m_isMemoizeWrapper = false;
m_allFlags.m_isMemoizeWrapperLSB = false;
m_allFlags.m_isPolicyShardedMemoize = false;
m_allFlags.m_isPhpLeafFn = isPhpLeafFn;
m_allFlags.m_hasReifiedGenerics = false;
m_allFlags.m_hasParamsWithMultiUBs = false;
m_allFlags.m_hasReturnWithMultiUBs = false;
m_bclenSmall = std::min<uint32_t>(bclen, kSmallDeltaLimit);
m_line2Delta = std::min<uint32_t>(line2 - line1, kSmallDeltaLimit);
m_sn = std::min<uint32_t>(sn, kSmallDeltaLimit);
}
Func::SharedData::~SharedData() {
if (auto bc = m_bc.copy(); bc.isPtr()) {
freeBCRegion(bc.ptr(), bclen());
}
if (auto table = m_lineTable.copy(); table.isPtr()) {
delete table.ptr();
}
Func::s_extendedLineInfo.erase(this);
if (m_cti_base) free_cti(m_cti_base, m_cti_size);
}
void Func::SharedData::atomicRelease() {
if (UNLIKELY(m_allFlags.m_hasExtendedSharedData)) {
delete (ExtendedSharedData*)this;
} else {
delete this;
}
}
Func::ExtendedSharedData::~ExtendedSharedData() {
}
///////////////////////////////////////////////////////////////////////////////
void logFunc(const Func* func, StructuredLogEntry& ent) {
auto const attrs = attrs_to_vec(AttrContext::Func, func->attrs());
std::set<folly::StringPiece> attrSet(attrs.begin(), attrs.end());
if (func->isMemoizeWrapper()) attrSet.emplace("memoize_wrapper");
if (func->isMemoizeWrapperLSB()) attrSet.emplace("memoize_wrapper_lsb");
if (func->isMemoizeImpl()) attrSet.emplace("memoize_impl");
if (func->isAsync()) attrSet.emplace("async");
if (func->isGenerator()) attrSet.emplace("generator");
if (func->isClosureBody()) attrSet.emplace("closure_body");
if (func->isPairGenerator()) attrSet.emplace("pair_generator");
if (func->hasVariadicCaptureParam()) attrSet.emplace("variadic_param");
if (func->isPhpLeafFn()) attrSet.emplace("leaf_function");
if (func->cls() && func->cls()->isPersistent()) attrSet.emplace("persistent");
ent.setSet("func_attributes", attrSet);
ent.setInt("num_params", func->numNonVariadicParams());
ent.setInt("num_locals", func->numLocals());
ent.setInt("num_iterators", func->numIterators());
ent.setInt("frame_cells", func->numSlotsInFrame());
ent.setInt("max_stack_cells", func->maxStackCells());
}
///////////////////////////////////////////////////////////////////////////////
// Lookup
const StaticString s_DebuggerMain("__DebuggerMain");
void Func::def(Func* func) {
assertx(!func->isMethod());
// Don't define the __debugger_main() function
DEBUGGER_ATTACHED_ONLY(if (func->userAttributes().count(s_DebuggerMain.get())) { return; });
auto const ne = func->getNamedEntity();
Func* f = ne->getCachedFunc();
if (f == nullptr) {
std::unique_lock<Mutex> l(g_funcsMutex);
f = ne->getCachedFunc();
if (f == nullptr) {
auto const persistent = func->isPersistent();
assertx(!persistent || (RuntimeOption::RepoAuthoritative || !SystemLib::s_inited));
if (!ne->m_cachedFunc.bound()) {
ne->m_cachedFunc.bind(
persistent ? rds::Mode::Persistent : rds::Mode::Normal,
rds::LinkName{"Func", func->name()}
);
}
ne->m_cachedFunc.initWith(func);
l.unlock();
DEBUGGER_ATTACHED_ONLY(phpDebuggerDefFuncHook(func));
}
}
// If the function didn't exists before we are good
if (f == nullptr) {
return;
}
// Otherwise check if we match or show the right error message
if (f == func) {
assertx(!RO::RepoAuthoritative ||
(f->isPersistent() && ne->m_cachedFunc.isPersistent()));
return;
}
if (func->attrs() & AttrIsMethCaller) return;
if (f->isBuiltin()) {
assertx(!func->isBuiltin());
raise_error(Strings::REDECLARE_BUILTIN, func->name()->data());
}
raise_error(Strings::FUNCTION_ALREADY_DEFINED, func->name()->data());
}
Func* Func::lookup(const NamedEntity* ne) {
return ne->getCachedFunc();
}
Func* Func::lookup(const StringData* name) {
const NamedEntity* ne = NamedEntity::get(name);
return ne->getCachedFunc();
}
Func* Func::lookupBuiltin(const StringData* name) {
// Builtins are either persistent (the normal case), or defined at the
// beginning of every request (if JitEnableRenameFunction or interception is
// enabled). In either case, they're unique, so they should be present in the
// NamedEntity.
auto const ne = NamedEntity::get(name);
auto const f = ne->getCachedFunc();
return (f && f->isUnique() && f->isBuiltin()) ? f : nullptr;
}
Func* Func::load(const NamedEntity* ne, const StringData* name) {
Func* func = ne->getCachedFunc();
if (LIKELY(func != nullptr)) return func;
if (AutoloadHandler::s_instance->autoloadFunc(
const_cast<StringData*>(name))) {
func = ne->getCachedFunc();
}
return func;
}
Func* Func::load(const StringData* name) {
String normStr;
auto ne = NamedEntity::get(name, true, &normStr);
// Try to fetch from cache
Func* func_ = ne->getCachedFunc();
if (LIKELY(func_ != nullptr)) return func_;
// Normalize the namespace
if (normStr) {
name = normStr.get();
}
// Autoload the function
return AutoloadHandler::s_instance->autoloadFunc(
const_cast<StringData*>(name)
) ? ne->getCachedFunc() : nullptr;
}
///////////////////////////////////////////////////////////////////////////////
// Code locations.
Func::ExtendedLineInfoCache Func::s_extendedLineInfo;
void Func::setLineTable(LineTable lineTable) {
auto& table = shared()->m_lineTable;
table.lock_for_update();
assertx(table.copy().isPtr() && !table.copy().ptr());
table.update_and_unlock(
LineTablePtr::FromPtr(new LineTable{std::move(lineTable)})
);
}
void Func::setLineTable(LineTablePtr::Token token) {
assertx(RO::RepoAuthoritative);
auto& table = shared()->m_lineTable;
table.lock_for_update();
assertx(table.copy().isPtr() && !table.copy().ptr());
table.update_and_unlock(LineTablePtr::FromToken(token));
}
void Func::stashExtendedLineTable(SourceLocTable table) const {
ExtendedLineInfoCache::accessor acc;
if (s_extendedLineInfo.insert(acc, shared())) {
acc->second.sourceLocTable = std::move(table);
}
}
/*
* Return the Unit's SourceLocTable, extracting it from the repo if
* necessary.
*/
const SourceLocTable& Func::getLocTable() const {
auto const sharedData = shared();
{
ExtendedLineInfoCache::const_accessor acc;
if (s_extendedLineInfo.find(acc, sharedData)) {
return acc->second.sourceLocTable;
}
}
static SourceLocTable empty;
return empty;
}
/*
* Return a copy of the Func's line to OffsetRangeVec table.
*/
LineToOffsetRangeVecMap Func::getLineToOffsetRangeVecMap() const {
auto const sharedData = shared();
{
ExtendedLineInfoCache::const_accessor acc;
if (s_extendedLineInfo.find(acc, sharedData)) {
if (!acc->second.lineToOffsetRange.empty()) {
return acc->second.lineToOffsetRange;
}
}
}
LineToOffsetRangeVecMap map;
auto const& srcLocTable = getLocTable();
SourceLocation::generateLineToOffsetRangesMap(srcLocTable, map);
ExtendedLineInfoCache::accessor acc;
if (!s_extendedLineInfo.find(acc, sharedData)) {
always_assert_flog(0, "ExtendedLineInfoCache was not found when it should "
"have been");
}
if (acc->second.lineToOffsetRange.empty()) {
acc->second.lineToOffsetRange = std::move(map);
}
return acc->second.lineToOffsetRange;
}
const LineTable* Func::getLineTable() const {
auto const table = shared()->m_lineTable.copy();
if (table.isPtr()) {
assertx(table.ptr());
return table.ptr();
}
return nullptr;
}
const LineTable& Func::getOrLoadLineTable() const {
if (auto const table = getLineTable()) return *table;
assertx(RO::RepoAuthoritative);
auto& wrapper = shared()->m_lineTable;
wrapper.lock_for_update();
auto const table = wrapper.copy();
if (table.isPtr()) {
wrapper.unlock();
return *table.ptr();
}
auto newTable =
new LineTable{RepoFile::loadLineTable(m_unit->sn(), table.token())};
wrapper.update_and_unlock(LineTablePtr::FromPtr(newTable));
return *newTable;
}
LineTable Func::getOrLoadLineTableCopy() const {
auto const table = shared()->m_lineTable.copy();
if (table.isPtr()) {
assertx(table.ptr());
return *table.ptr();
}
assertx(RO::RepoAuthoritative);
return RepoFile::loadLineTable(m_unit->sn(), table.token());
}
int Func::getLineNumber(Offset offset) const {
auto const findLine = [&] {
// lineMap is an atomically acquired bitwise copy of m_lineMap,
// with no destructor
auto lineMap(shared()->m_lineMap.get());
if (lineMap->empty()) return INT_MIN;
auto const it = std::upper_bound(
lineMap->begin(), lineMap->end(),
offset,
[] (Offset info, const LineInfo& elm) {
return info < elm.first.past;
}
);
if (it != lineMap->end() && it->first.base <= offset) return it->second;
return INT_MIN;
};
auto line = findLine();
if (line != INT_MIN) return line;
// Updating m_lineMap while coverage is enabled can cause the
// treadmill to fill with an enormous number of resized maps.
if (UNLIKELY(g_context && (m_unit->isCoverageEnabled() ||
RID().getCoverage()))) {
return SourceLocation::getLineNumber(getOrLoadLineTable(), offset);
}
shared()->m_lineMap.lock_for_update();
try {
line = findLine();
if (line != INT_MIN) {
shared()->m_lineMap.unlock();
return line;
}
auto const info = SourceLocation::getLineInfo(getOrLoadLineTable(), offset);
auto copy = shared()->m_lineMap.copy();
auto const it = std::upper_bound(
copy.begin(), copy.end(),
info,
[&] (const LineInfo& a, const LineInfo& b) {
return a.first.base < b.first.past;
}
);
assertx(it == copy.end() ||
(it->first.past > offset && it->first.base > offset));
copy.insert(it, info);
auto old = shared()->m_lineMap.update_and_unlock(std::move(copy));
Treadmill::enqueue([old = std::move(old)] () mutable { old.clear(); });
return info.second;
} catch (...) {
shared()->m_lineMap.unlock();
throw;
}
}
bool Func::getSourceLoc(Offset offset, SourceLoc& sLoc) const {
auto const& sourceLocTable = getLocTable();
return SourceLocation::getLoc(sourceLocTable, offset, sLoc);
}
bool Func::getOffsetRange(Offset offset, OffsetRange& range) const {
auto line = getLineNumber(offset);
if (line == -1) return false;
auto map = getLineToOffsetRangeVecMap();
auto it = map.find(line);
if (it != map.end()) {
for (auto o : it->second) {
if (offset >= o.base && offset < o.past) {
range = o;
return true;
}
}
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Bytecode
namespace {
using BytecodeArena = ReadOnlyArena<VMColdAllocator<char>, false, 8>;
static BytecodeArena& bytecode_arena() {
static BytecodeArena arena(RuntimeOption::EvalHHBCArenaChunkSize);
return arena;
}
}
/*
* Export for the admin server.
*/
size_t hhbc_arena_capacity() {
if (!RuntimeOption::RepoAuthoritative) return 0;
return bytecode_arena().capacity();
}
unsigned char*
allocateBCRegion(const unsigned char* bc, size_t bclen) {
g_hhbc_size->addValue(bclen);
auto mem = static_cast<unsigned char*>(
RuntimeOption::RepoAuthoritative ? bytecode_arena().allocate(bclen)
: malloc(bclen));
std::copy(bc, bc + bclen, mem);
return mem;
}
void freeBCRegion(const unsigned char* bc, size_t bclen) {
// Can't free bytecode arena memory.
if (RuntimeOption::RepoAuthoritative) return;
if (debug) {
// poison released bytecode
memset(const_cast<unsigned char*>(bc), 0xff, bclen);
}
free(const_cast<unsigned char*>(bc));
g_hhbc_size->addValue(-int64_t(bclen));
}
PC Func::loadBytecode() {
assertx(RO::RepoAuthoritative);
auto& wrapper = shared()->m_bc;
wrapper.lock_for_update();
auto const bc = wrapper.copy();
if (bc.isPtr()) {
wrapper.unlock();
return bc.ptr();
}
auto const length = bclen();
g_hhbc_size->addValue(length);
auto mem = (unsigned char*)bytecode_arena().allocate(length);
RepoFile::loadBytecode(m_unit->sn(), bc.token(), mem, length);
wrapper.update_and_unlock(BCPtr::FromPtr(mem));
return mem;
}
///////////////////////////////////////////////////////////////////////////////
// Coverage
namespace {
RDS_LOCAL(uint32_t, tl_saved_coverage_index);
RDS_LOCAL_NO_CHECK(Array, tl_called_functions);
rds::Link<uint32_t, rds::Mode::Local> s_coverage_index;
using CoverageLinkMap = tbb::concurrent_hash_map<
const StringData*,
rds::Link<uint32_t, rds::Mode::Local>
>;
struct EmbeddedCoverageLinkMap {
explicit operator bool() const { return inited; }
CoverageLinkMap* operator->() {
assertx(inited);
return reinterpret_cast<CoverageLinkMap*>(&data);
}
CoverageLinkMap& operator*() { assertx(inited); return *operator->(); }
void emplace(uint32_t size) {
assertx(!inited);
new (&data) CoverageLinkMap(size);
inited = true;
}
void clear() {
if (inited) {
operator*().~CoverageLinkMap();
inited = false;
}
}
private:
typename std::aligned_storage<
sizeof(CoverageLinkMap),
alignof(CoverageLinkMap)
>::type data;
bool inited;
};
EmbeddedCoverageLinkMap s_covLinks;
static InitFiniNode s_covLinksReinit([]{
if (RO::RepoAuthoritative || !RO::EvalEnableFuncCoverage) return;
s_covLinks.emplace(RO::EvalFuncCountHint);
}, InitFiniNode::When::PostRuntimeOptions, "s_funcVec reinit");
InitFiniNode s_clear_called_functions([]{
tl_called_functions.nullOut();
}, InitFiniNode::When::RequestFini, "tl_called_functions clear");
}
rds::Handle Func::GetCoverageIndex() {
if (!s_coverage_index.bound()) {
s_coverage_index.bind(rds::Mode::Local, rds::LinkID{"FuncCoverageIndex"});
}
return s_coverage_index.handle();
}
rds::Handle Func::getCoverageHandle() const {
assertx(!RO::RepoAuthoritative && RO::EvalEnableFuncCoverage);
assertx(!isNoInjection() && !isMethCaller());
CoverageLinkMap::const_accessor cnsAcc;
if (s_covLinks->find(cnsAcc, fullName())) {
assertx(cnsAcc->second.bound());
return cnsAcc->second.handle();
}
CoverageLinkMap::accessor acc;
if (s_covLinks->insert(acc, fullName())) {
assertx(!acc->second.bound());
acc->second.bind(
rds::Mode::Local, rds::LinkName{"FuncCoverageFlag", fullName()}
);
}
assertx(acc->second.bound());
return acc->second.handle();
}
void Func::EnableCoverage() {
assertx(g_context);
if (RO::RepoAuthoritative) {
SystemLib::throwInvalidOperationExceptionObject(
"Cannot enable function call coverage in repo authoritative mode"
);
}
if (!RO::EvalEnableFuncCoverage) {
SystemLib::throwInvalidOperationExceptionObject(
"Cannot enable function call coverage (you must set "
"Eval.EnableFuncCoverage = true)"
);
}
if (!tl_called_functions.isNull()) {
SystemLib::throwInvalidOperationExceptionObject(
"Function call coverage already enabled"
);
}
GetCoverageIndex(); // bind the handle
if (!*tl_saved_coverage_index) *tl_saved_coverage_index = 1;
*s_coverage_index = (*tl_saved_coverage_index)++;
tl_called_functions.emplace(Array::CreateDict());
}
std::string show(PrologueID pid) {
auto func = pid.func();
return folly::sformat("{}(id 0x{:#x}) # of args: {}",
func->fullName()->data(),
pid.funcId().toInt(), pid.nargs());
}
Array Func::GetCoverage() {
if (tl_called_functions.isNull()) {
SystemLib::throwInvalidOperationExceptionObject(
"Function call coverage not enabled"
);
}
auto const ret = std::move(*tl_called_functions.get());
*s_coverage_index = 0;
tl_called_functions.destroy();
return ret;
}
void Func::recordCall() const {
if (RO::RepoAuthoritative || !RO::EvalEnableFuncCoverage) return;
if (tl_called_functions.isNull()) return;
if (isNoInjection() || isMethCaller()) return;
auto const path = unit()->isSystemLib()
? empty_string()
: StrNR{unit()->filepath()}.asString();
tl_called_functions->set(fullNameStr().asString(), std::move(path), true);
}
void Func::recordCallNoCheck() const {
assertx(!RO::RepoAuthoritative && RO::EvalEnableFuncCoverage);
assertx(!tl_called_functions.isNull());
assertx(tl_called_functions->isDict());
assertx(!isNoInjection() && !isMethCaller());
assertx(!tl_called_functions->exists(fullNameStr().asString(), true));
auto const path = unit()->isSystemLib()
? empty_string()
: StrNR{unit()->filepath()}.asString();
tl_called_functions->set(fullNameStr().asString(), std::move(path), true);
}
///////////////////////////////////////////////////////////////////////////////
}