71859e5566
Will be needed for array_filter/array_map etc This sets things up so that if we define a builtin in systemlib, we rename the corresponding c++ builtin with the prefix __builtin_, so its still available (in case the php builtin wants to delegate some edge cases, and to make it easy to run comparisons between the php and c++ implementations). Also did a little reorganization to get rid of Func::isPHPBuiltin, and use an Attr to identify functions as builtins. C++ builtins can still be identified by checking the Func::info() method. This is needed to allow builtin methods defined in php (such as array_map) to lookup their arguments in the correct context.
1153 linhas
36 KiB
C++
1153 linhas
36 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- 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 <iostream>
|
|
#include <boost/scoped_ptr.hpp>
|
|
|
|
#include "hphp/runtime/base/base_includes.h"
|
|
#include "hphp/util/util.h"
|
|
#include "hphp/util/trace.h"
|
|
#include "hphp/util/debug.h"
|
|
#include "hphp/runtime/base/strings.h"
|
|
#include "hphp/runtime/vm/core_types.h"
|
|
#include "hphp/runtime/vm/runtime.h"
|
|
#include "hphp/runtime/vm/repo.h"
|
|
#include "hphp/runtime/vm/translator/targetcache.h"
|
|
#include "hphp/runtime/eval/runtime/file_repository.h"
|
|
#include "hphp/runtime/vm/translator/translator-x64.h"
|
|
#include "hphp/runtime/vm/blob_helper.h"
|
|
#include "hphp/runtime/vm/func_inline.h"
|
|
#include "hphp/system/lib/systemlib.h"
|
|
|
|
namespace HPHP {
|
|
|
|
static const Trace::Module TRACEMOD = Trace::bcinterp;
|
|
const StringData* Func::s___call = StringData::GetStaticString("__call");
|
|
const StringData* Func::s___callStatic =
|
|
StringData::GetStaticString("__callStatic");
|
|
|
|
//=============================================================================
|
|
// Func.
|
|
|
|
static void decl_incompat(const PreClass* implementor,
|
|
const Func* imeth) {
|
|
const char* name = imeth->name()->data();
|
|
raise_error("Declaration of %s::%s() must be compatible with "
|
|
"that of %s::%s()", implementor->name()->data(), name,
|
|
imeth->cls()->preClass()->name()->data(), name);
|
|
}
|
|
|
|
// Check compatibility vs interface and abstract declarations
|
|
void Func::parametersCompat(const PreClass* preClass, const Func* imeth) const {
|
|
const Func::ParamInfoVec& params = this->params();
|
|
const Func::ParamInfoVec& iparams = imeth->params();
|
|
// Verify that meth has at least as many parameters as imeth.
|
|
if ((params.size() < iparams.size())) {
|
|
decl_incompat(preClass, imeth);
|
|
}
|
|
// Verify that the typehints for meth's parameters are compatible with
|
|
// imeth's corresponding parameter typehints.
|
|
unsigned firstOptional = 0;
|
|
for (unsigned i = 0; i < iparams.size(); ++i) {
|
|
if (!params[i].typeConstraint().compat(iparams[i].typeConstraint())) {
|
|
decl_incompat(preClass, imeth);
|
|
}
|
|
if (!iparams[i].hasDefaultValue()) {
|
|
// The leftmost of imeth's contiguous trailing optional parameters
|
|
// must start somewhere to the right of this parameter.
|
|
firstOptional = i + 1;
|
|
}
|
|
}
|
|
// Verify that meth provides defaults, starting with the parameter that
|
|
// corresponds to the leftmost of imeth's contiguous trailing optional
|
|
// parameters.
|
|
for (unsigned i = firstOptional; i < params.size(); ++i) {
|
|
if (!params[i].hasDefaultValue()) {
|
|
decl_incompat(preClass, imeth);
|
|
}
|
|
}
|
|
}
|
|
|
|
static Func::FuncId s_nextFuncId = 0;
|
|
void Func::setFuncId(FuncId id) {
|
|
assert(m_funcId == InvalidId);
|
|
assert(id != InvalidId);
|
|
m_funcId = id;
|
|
}
|
|
|
|
void Func::setNewFuncId() {
|
|
assert(m_funcId == InvalidId);
|
|
m_funcId = __sync_fetch_and_add(&s_nextFuncId, 1);
|
|
}
|
|
|
|
void Func::setFullName() {
|
|
assert(m_name->isStatic());
|
|
if (m_cls) {
|
|
m_fullName = StringData::GetStaticString(
|
|
std::string(m_cls->name()->data()) + "::" + m_name->data());
|
|
} else {
|
|
m_fullName = m_name;
|
|
m_namedEntity = Unit::GetNamedEntity(m_name);
|
|
}
|
|
if (RuntimeOption::DynamicInvokeFunctions.size()) {
|
|
if (RuntimeOption::DynamicInvokeFunctions.find(m_fullName->data()) !=
|
|
RuntimeOption::DynamicInvokeFunctions.end()) {
|
|
m_attrs = Attr(m_attrs | AttrDynamicInvoke);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Func::initPrologues(int numParams, bool isGenerator) {
|
|
m_funcBody = (TCA)HPHP::Transl::funcBodyHelperThunk;
|
|
|
|
int maxNumPrologues = Func::getMaxNumPrologues(numParams);
|
|
int numPrologues =
|
|
maxNumPrologues > kNumFixedPrologues ? maxNumPrologues
|
|
: kNumFixedPrologues;
|
|
|
|
TRACE(2, "initPrologues func %p %d\n", this, numPrologues);
|
|
for (int i = 0; i < numPrologues; i++) {
|
|
m_prologueTable[i] = (TCA)HPHP::Transl::fcallHelperThunk;
|
|
}
|
|
}
|
|
|
|
void Func::init(int numParams, bool isGenerator) {
|
|
// For methods, we defer setting the full name until m_cls is initialized
|
|
m_maybeIntercepted = -1;
|
|
if (!preClass()) {
|
|
setNewFuncId();
|
|
setFullName();
|
|
} else {
|
|
m_fullName = 0;
|
|
}
|
|
if (isSpecial(m_name)) {
|
|
/*
|
|
* i) We dont want these compiler generated functions to
|
|
* appear in backtraces.
|
|
*
|
|
* ii) 86sinit and 86pinit construct NameValueTableWrappers
|
|
* on the stack. So we MUST NOT allow those to leak into
|
|
* the backtrace (since the backtrace will outlive the
|
|
* variables).
|
|
*/
|
|
m_attrs = m_attrs | AttrNoInjection;
|
|
}
|
|
#ifdef DEBUG
|
|
m_magic = kMagic;
|
|
#endif
|
|
assert(m_name);
|
|
initPrologues(numParams, isGenerator);
|
|
}
|
|
|
|
void* Func::allocFuncMem(
|
|
const StringData* name, int numParams, bool needsNextClonedClosure) {
|
|
int maxNumPrologues = Func::getMaxNumPrologues(numParams);
|
|
int numExtraPrologues =
|
|
maxNumPrologues > kNumFixedPrologues ?
|
|
maxNumPrologues - kNumFixedPrologues :
|
|
0;
|
|
size_t funcSize = sizeof(Func) + numExtraPrologues * sizeof(unsigned char*);
|
|
if (needsNextClonedClosure) {
|
|
funcSize += sizeof(Func*);
|
|
}
|
|
void* mem = Util::low_malloc(funcSize);
|
|
if (needsNextClonedClosure) {
|
|
// make room for nextClonedClosure to work
|
|
Func** startOfFunc = (Func**) mem;
|
|
*startOfFunc = nullptr;
|
|
return startOfFunc + 1;
|
|
}
|
|
return mem;
|
|
}
|
|
|
|
Func::Func(Unit& unit, Id id, int line1, int line2,
|
|
Offset base, Offset past, const StringData* name,
|
|
Attr attrs, bool top, const StringData* docComment, int numParams,
|
|
bool isGenerator)
|
|
: m_unit(&unit)
|
|
, m_cls(nullptr)
|
|
, m_baseCls(nullptr)
|
|
, m_name(name)
|
|
, m_namedEntity(nullptr)
|
|
, m_refBitVec(nullptr)
|
|
, m_cachedOffset(0)
|
|
, m_maxStackCells(0)
|
|
, m_numParams(0)
|
|
, m_attrs(attrs)
|
|
, m_funcId(InvalidId)
|
|
, m_hasPrivateAncestor(false)
|
|
{
|
|
m_shared = new SharedData(nullptr, id, base, past, line1, line2,
|
|
top, docComment);
|
|
init(numParams, isGenerator);
|
|
}
|
|
|
|
// Class method
|
|
Func::Func(Unit& unit, PreClass* preClass, int line1, int line2, Offset base,
|
|
Offset past, const StringData* name, Attr attrs,
|
|
bool top, const StringData* docComment, int numParams,
|
|
bool isGenerator)
|
|
: m_unit(&unit)
|
|
, m_cls(nullptr)
|
|
, m_baseCls(nullptr)
|
|
, m_name(name)
|
|
, m_namedEntity(nullptr)
|
|
, m_refBitVec(nullptr)
|
|
, m_cachedOffset(0)
|
|
, m_maxStackCells(0)
|
|
, m_numParams(0)
|
|
, m_attrs(attrs)
|
|
, m_funcId(InvalidId)
|
|
, m_hasPrivateAncestor(false)
|
|
{
|
|
Id id = -1;
|
|
m_shared = new SharedData(preClass, id, base, past, line1, line2,
|
|
top, docComment);
|
|
init(numParams, isGenerator);
|
|
}
|
|
|
|
Func::~Func() {
|
|
if (m_fullName != nullptr && m_maybeIntercepted != -1) {
|
|
unregister_intercept_flag(fullNameRef(), &m_maybeIntercepted);
|
|
}
|
|
#ifdef DEBUG
|
|
validate();
|
|
m_magic = ~m_magic;
|
|
#endif
|
|
}
|
|
|
|
void Func::destroy(Func* func) {
|
|
void* mem = func;
|
|
if (func->isClosureBody() || func->isGeneratorFromClosure()) {
|
|
Func** startOfFunc = (Func**) mem;
|
|
mem = startOfFunc - 1; // move back by a pointer
|
|
}
|
|
func->~Func();
|
|
Util::low_free(mem);
|
|
}
|
|
|
|
Func* Func::clone() const {
|
|
Func* f = new (allocFuncMem(
|
|
m_name,
|
|
m_numParams,
|
|
isClosureBody() || isGeneratorFromClosure()
|
|
)) Func(*this);
|
|
f->initPrologues(m_numParams, isGenerator());
|
|
f->m_funcId = InvalidId;
|
|
return f;
|
|
}
|
|
|
|
const Func* Func::cloneAndSetClass(Class* cls) const {
|
|
if (const Func* ret = findCachedClone(cls)) {
|
|
return ret;
|
|
}
|
|
|
|
static Mutex s_clonedFuncListMutex;
|
|
Lock l(s_clonedFuncListMutex);
|
|
// Check again now that I'm the writer
|
|
if (const Func* ret = findCachedClone(cls)) {
|
|
return ret;
|
|
}
|
|
|
|
Func* clonedFunc = clone();
|
|
clonedFunc->setNewFuncId();
|
|
clonedFunc->setCls(cls);
|
|
|
|
// Save it so we don't have to keep cloning it and retranslating
|
|
Func** nextFunc = &this->nextClonedClosure();
|
|
while (*nextFunc) {
|
|
nextFunc = &nextFunc[0]->nextClonedClosure();
|
|
}
|
|
*nextFunc = clonedFunc;
|
|
|
|
return clonedFunc;
|
|
}
|
|
|
|
const Func* Func::findCachedClone(Class* cls) const {
|
|
const Func* nextFunc = this;
|
|
while (nextFunc) {
|
|
if (nextFunc->cls() == cls) {
|
|
return nextFunc;
|
|
}
|
|
nextFunc = nextFunc->nextClonedClosure();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Func::rename(const StringData* name) {
|
|
m_name = name;
|
|
setFullName();
|
|
// load the renamed function
|
|
Unit::loadFunc(this);
|
|
}
|
|
|
|
bool Func::checkIterScope(Offset o, Id iterId, bool& itRef) const {
|
|
const EHEntVec& ehtab = shared()->m_ehtab;
|
|
assert(o >= base() && o < past());
|
|
for (unsigned i = 0, n = ehtab.size(); i < n; i++) {
|
|
const EHEnt* eh = &ehtab[i];
|
|
if (eh->m_ehtype == EHEnt::EHType_Fault &&
|
|
eh->m_base <= o && o < eh->m_past &&
|
|
eh->m_iterId == iterId) {
|
|
itRef = eh->m_itRef;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const EHEnt* Func::findEH(Offset o) const {
|
|
assert(o >= base() && o < past());
|
|
const EHEnt* eh = nullptr;
|
|
unsigned int i;
|
|
|
|
const EHEntVec& ehtab = shared()->m_ehtab;
|
|
for (i = 0; i < ehtab.size(); i++) {
|
|
if (ehtab[i].m_base <= o && o < ehtab[i].m_past) {
|
|
eh = &ehtab[i];
|
|
}
|
|
}
|
|
return eh;
|
|
}
|
|
|
|
const FPIEnt* Func::findFPI(Offset o) const {
|
|
assert(o >= base() && o < past());
|
|
const FPIEnt* fe = nullptr;
|
|
unsigned int i;
|
|
|
|
const FPIEntVec& fpitab = shared()->m_fpitab;
|
|
for (i = 0; i < fpitab.size(); i++) {
|
|
/*
|
|
* We consider the "FCall" instruction part of the FPI region, but
|
|
* the corresponding push is not considered part of it. (This
|
|
* means all offsets in the FPI region will have the partial
|
|
* ActRec on the stack.)
|
|
*/
|
|
if (fpitab[i].m_fpushOff < o && o <= fpitab[i].m_fcallOff) {
|
|
fe = &fpitab[i];
|
|
}
|
|
}
|
|
return fe;
|
|
}
|
|
|
|
const FPIEnt* Func::findPrecedingFPI(Offset o) const {
|
|
assert(o >= base() && o < past());
|
|
const FPIEntVec& fpitab = shared()->m_fpitab;
|
|
assert(fpitab.size());
|
|
const FPIEnt* fe = &fpitab[0];
|
|
unsigned int i;
|
|
for (i = 1; i < fpitab.size(); i++) {
|
|
const FPIEnt* cur = &fpitab[i];
|
|
if (o > cur->m_fcallOff &&
|
|
fe->m_fcallOff < cur->m_fcallOff) {
|
|
fe = cur;
|
|
}
|
|
}
|
|
assert(fe);
|
|
return fe;
|
|
}
|
|
|
|
bool Func::isClonedClosure() const {
|
|
if (!isClosureBody()) return false;
|
|
if (!cls()) return true;
|
|
return cls()->lookupMethod(name()) != this;
|
|
}
|
|
|
|
bool Func::isNameBindingImmutable(const Unit* fromUnit) const {
|
|
if (RuntimeOption::EvalJitEnableRenameFunction ||
|
|
m_attrs & AttrDynamicInvoke) {
|
|
return false;
|
|
}
|
|
|
|
if (isBuiltin()) {
|
|
return true;
|
|
}
|
|
|
|
if (isUnique() && RuntimeOption::RepoAuthoritative) {
|
|
return true;
|
|
}
|
|
|
|
// Defined at top level, in the same unit as the caller. This precludes
|
|
// conditionally defined functions and cross-module calls -- both phenomena
|
|
// can change name->Func mappings during the lifetime of a TC.
|
|
return top() && (fromUnit == m_unit);
|
|
}
|
|
|
|
bool Func::byRef(int32_t arg) const {
|
|
// Super special case. A handful of builtins are varargs functions where the
|
|
// (not formally declared) varargs are pass-by-reference. psychedelic-kitten
|
|
if (arg >= m_numParams && info() &&
|
|
(info()->attribute & (ClassInfo::RefVariableArguments |
|
|
ClassInfo::MixedVariableArguments))) {
|
|
return true;
|
|
}
|
|
int qword = arg / kBitsPerQword;
|
|
int bit = arg % kBitsPerQword;
|
|
bool retval = arg < m_numParams && (m_refBitVec[qword] & (1ull << bit)) != 0;
|
|
return retval;
|
|
}
|
|
|
|
bool Func::mustBeRef(int32_t arg) const {
|
|
// return true if the argument is required to be a reference
|
|
// (and thus should be an lvalue)
|
|
if (arg >= m_numParams && info() &&
|
|
((info()->attribute & (ClassInfo::RefVariableArguments |
|
|
ClassInfo::MixedVariableArguments)) ==
|
|
ClassInfo::RefVariableArguments)) {
|
|
return true;
|
|
}
|
|
int qword = arg / kBitsPerQword;
|
|
int bit = arg % kBitsPerQword;
|
|
bool retval = arg < m_numParams && (m_refBitVec[qword] & (1ull << bit)) != 0;
|
|
return retval;
|
|
}
|
|
|
|
void Func::appendParam(bool ref, const Func::ParamInfo& info,
|
|
std::vector<ParamInfo>& pBuilder) {
|
|
int qword = m_numParams / kBitsPerQword;
|
|
int bit = m_numParams % kBitsPerQword;
|
|
// Grow args, if necessary.
|
|
if ((m_numParams++ & (kBitsPerQword - 1)) == 0) {
|
|
assert(shared()->m_refBitVec == m_refBitVec);
|
|
shared()->m_refBitVec = m_refBitVec = (uint64_t*)
|
|
realloc(shared()->m_refBitVec,
|
|
// E.g., 65th m_numParams -> 2 qwords
|
|
(1 + m_numParams / kBitsPerQword) * sizeof(uint64_t));
|
|
|
|
// The new word is either zerod or set to 1, depending on whether
|
|
// we are one of the special builtins that takes variadic
|
|
// reference arguments. This is for use in the translator.
|
|
shared()->m_refBitVec[m_numParams / kBitsPerQword] =
|
|
(m_attrs & AttrVariadicByRef) ? -1ull : 0;
|
|
}
|
|
assert(!!(shared()->m_refBitVec[qword] & (uint64_t(1) << bit)) ==
|
|
!!(m_attrs & AttrVariadicByRef));
|
|
shared()->m_refBitVec[qword] &= ~(1ull << bit);
|
|
shared()->m_refBitVec[qword] |= uint64_t(ref) << bit;
|
|
pBuilder.push_back(info);
|
|
}
|
|
|
|
Id Func::lookupVarId(const StringData* name) const {
|
|
assert(name != nullptr);
|
|
return shared()->m_localNames.findIndex(name);
|
|
}
|
|
|
|
void Func::prettyPrint(std::ostream& out) const {
|
|
if (isPseudoMain()) {
|
|
out << "Pseudo-main";
|
|
} else if (preClass() != nullptr) {
|
|
out << "Method ";
|
|
if (m_attrs & AttrStatic) { out << "static "; }
|
|
if (m_attrs & AttrPublic) { out << "public "; }
|
|
if (m_attrs & AttrProtected) { out << "protected "; }
|
|
if (m_attrs & AttrPrivate) { out << "private "; }
|
|
if (m_attrs & AttrAbstract) { out << "abstract "; }
|
|
if (m_attrs & AttrFinal) { out << "final "; }
|
|
if (m_attrs & AttrPhpLeafFn) { out << "(leaf) "; }
|
|
if (cls() != nullptr) {
|
|
out << fullName()->data();
|
|
} else {
|
|
out << preClass()->name()->data() << "::" << m_name->data();
|
|
}
|
|
} else {
|
|
out << "Function " << m_name->data();
|
|
}
|
|
|
|
if (m_attrs & AttrHot) out << " (hot)";
|
|
|
|
out << " at " << base();
|
|
if (shared()->m_id != -1) {
|
|
out << " (ID " << shared()->m_id << ")";
|
|
}
|
|
out << std::endl;
|
|
const ParamInfoVec& params = shared()->m_params;
|
|
for (uint i = 0; i < params.size(); ++i) {
|
|
if (params[i].funcletOff() != InvalidAbsoluteOffset) {
|
|
out << " DV for parameter " << i << " at " << params[i].funcletOff()
|
|
<< " = " << params[i].phpCode()->data() << std::endl;
|
|
}
|
|
}
|
|
const EHEntVec& ehtab = shared()->m_ehtab;
|
|
for (EHEntVec::const_iterator it = ehtab.begin(); it != ehtab.end(); ++it) {
|
|
bool catcher = it->m_ehtype == EHEnt::EHType_Catch;
|
|
out << " EH " << (catcher ? "Catch" : "Fault") << " 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 << " itRef " << (it->m_itRef ? "true" : "false");
|
|
}
|
|
if (catcher) {
|
|
out << std::endl;
|
|
for (EHEnt::CatchVec::const_iterator it2 = it->m_catches.begin();
|
|
it2 != it->m_catches.end(); ++it2) {
|
|
out << " Handle " << m_unit->lookupLitstrId(it2->first)->data()
|
|
<< " at " << it2->second;
|
|
}
|
|
} else {
|
|
out << " to " << it->m_fault;
|
|
}
|
|
if (it->m_parentIndex != -1) {
|
|
out << " parentIndex " << it->m_parentIndex;
|
|
}
|
|
out << std::endl;
|
|
}
|
|
}
|
|
|
|
HphpArray* Func::getStaticLocals() const {
|
|
return g_vmContext->getFuncStaticCtx(this);
|
|
}
|
|
|
|
void Func::getFuncInfo(ClassInfo::MethodInfo* mi) const {
|
|
assert(mi);
|
|
if (info() != nullptr) {
|
|
// Very large operator=() invocation.
|
|
*mi = *info();
|
|
// Deep copy the vectors of mi-owned pointers.
|
|
cloneMembers(mi->parameters);
|
|
cloneMembers(mi->staticVariables);
|
|
} else {
|
|
// hphpc sets the ClassInfo::VariableArguments attribute if the method
|
|
// contains a call to func_get_arg, func_get_args, or func_num_args. We
|
|
// don't do this in the VM currently and hopefully we never will need to.
|
|
int attr = 0;
|
|
if (m_attrs & AttrReference) attr |= ClassInfo::IsReference;
|
|
if (m_attrs & AttrAbstract) attr |= ClassInfo::IsAbstract;
|
|
if (m_attrs & AttrFinal) attr |= ClassInfo::IsFinal;
|
|
if (m_attrs & AttrProtected) attr |= ClassInfo::IsProtected;
|
|
if (m_attrs & AttrPrivate) attr |= ClassInfo::IsPrivate;
|
|
if (m_attrs & AttrStatic) attr |= ClassInfo::IsStatic;
|
|
if (!(attr & ClassInfo::IsProtected || attr & ClassInfo::IsPrivate)) {
|
|
attr |= ClassInfo::IsPublic;
|
|
}
|
|
if (preClass() &&
|
|
(!strcasecmp(m_name->data(), "__construct") ||
|
|
(!(preClass()->attrs() & AttrTrait) &&
|
|
!strcasecmp(m_name->data(), preClass()->name()->data()) &&
|
|
!preClass()->hasMethod(String("__construct").get())))) {
|
|
attr |= ClassInfo::IsConstructor;
|
|
}
|
|
if (attr == 0) attr = ClassInfo::IsNothing;
|
|
mi->attribute = (ClassInfo::Attribute)attr;
|
|
mi->name = m_name->data();
|
|
mi->file = m_unit->filepath()->data();
|
|
mi->line1 = line1();
|
|
mi->line2 = line2();
|
|
if (docComment() && !docComment()->empty()) {
|
|
mi->docComment = docComment()->data();
|
|
}
|
|
// Get the parameter info
|
|
for (unsigned i = 0; i < unsigned(m_numParams); ++i) {
|
|
ClassInfo::ParameterInfo* pi = new ClassInfo::ParameterInfo;
|
|
attr = 0;
|
|
if (byRef(i)) {
|
|
attr |= ClassInfo::IsReference;
|
|
}
|
|
if (attr == 0) {
|
|
attr = ClassInfo::IsNothing;
|
|
}
|
|
const ParamInfoVec& params = shared()->m_params;
|
|
const ParamInfo& fpi = params[i];
|
|
pi->attribute = (ClassInfo::Attribute)attr;
|
|
pi->name = shared()->m_localNames[i]->data();
|
|
if (params.size() <= i || !fpi.hasDefaultValue()) {
|
|
pi->value = nullptr;
|
|
pi->valueText = "";
|
|
} else {
|
|
if (fpi.hasScalarDefaultValue()) {
|
|
// Most of the time the default value is scalar, so we can
|
|
// avoid evaling in the common case
|
|
pi->value = strdup(f_serialize(
|
|
tvAsVariant((TypedValue*)&fpi.defaultValue())).get()->data());
|
|
} else {
|
|
// Eval PHP code to get default value, and serialize the result. Note
|
|
// that access of undefined class constants can cause the eval() to
|
|
// fatal. Zend lets such fatals propagate, so don't bother catching
|
|
// exceptions here.
|
|
CVarRef v = g_vmContext->getEvaledArg(fpi.phpCode());
|
|
pi->value = strdup(f_serialize(v).get()->data());
|
|
}
|
|
// This is a raw char*, but its lifetime should be at least as long
|
|
// as the the Func*. At this writing, it's a merged anon string
|
|
// owned by ParamInfo.
|
|
pi->valueText = fpi.phpCode()->data();
|
|
}
|
|
pi->type = fpi.typeConstraint().exists() ?
|
|
fpi.typeConstraint().typeName()->data() : "";
|
|
for (UserAttributeMap::const_iterator it = fpi.userAttributes().begin();
|
|
it != fpi.userAttributes().end(); ++it) {
|
|
// convert the typedvalue to a cvarref and push into pi.
|
|
auto userAttr = new ClassInfo::UserAttributeInfo;
|
|
assert(it->first->isStatic());
|
|
userAttr->name = const_cast<StringData*>(it->first);
|
|
userAttr->setStaticValue(tvAsCVarRef(&it->second));
|
|
pi->userAttrs.push_back(userAttr);
|
|
}
|
|
mi->parameters.push_back(pi);
|
|
}
|
|
// XXX ConstantInfo is abused to store static variable metadata, and
|
|
// although ConstantInfo::callbacks provides a mechanism for registering
|
|
// callbacks, it does not pass enough information through for the callback
|
|
// functions to know the function context whence the callbacks came.
|
|
// Furthermore, the callback mechanism isn't employed in a fashion that
|
|
// would allow repeated introspection to reflect updated values.
|
|
// Supporting introspection of static variable values will require
|
|
// different plumbing than currently exists in ConstantInfo.
|
|
const SVInfoVec& staticVars = shared()->m_staticVars;
|
|
for (SVInfoVec::const_iterator it = staticVars.begin();
|
|
it != staticVars.end(); ++it) {
|
|
ClassInfo::ConstantInfo* ci = new ClassInfo::ConstantInfo;
|
|
ci->name = *(String*)(&(*it).name);
|
|
if ((*it).phpCode != nullptr) {
|
|
ci->valueLen = (*it).phpCode->size();
|
|
ci->valueText = (*it).phpCode->data();
|
|
} else {
|
|
ci->valueLen = 0;
|
|
ci->valueText = "";
|
|
}
|
|
|
|
mi->staticVariables.push_back(ci);
|
|
}
|
|
}
|
|
}
|
|
|
|
Func::SharedData::SharedData(PreClass* preClass, Id id,
|
|
Offset base, Offset past, int line1, int line2,
|
|
bool top, const StringData* docComment)
|
|
: m_preClass(preClass), m_id(id), m_base(base),
|
|
m_numLocals(0), m_numIterators(0),
|
|
m_past(past), m_line1(line1), m_line2(line2),
|
|
m_info(nullptr), m_refBitVec(nullptr), m_builtinFuncPtr(nullptr),
|
|
m_docComment(docComment), m_top(top), m_isClosureBody(false),
|
|
m_isGenerator(false), m_isGeneratorFromClosure(false),
|
|
m_hasGeneratorAsBody(false), m_originalFilename(nullptr) {
|
|
}
|
|
|
|
Func::SharedData::~SharedData() {
|
|
if (m_refBitVec) {
|
|
free(m_refBitVec);
|
|
}
|
|
}
|
|
|
|
void Func::SharedData::atomicRelease() {
|
|
delete this;
|
|
}
|
|
|
|
Func** Func::getCachedAddr() {
|
|
assert(!isMethod());
|
|
return getCachedFuncAddr(m_cachedOffset);
|
|
}
|
|
|
|
void Func::setCached() {
|
|
setCachedFunc(this, isDebuggerAttached());
|
|
}
|
|
|
|
const Func* Func::getGeneratorBody(const StringData* name) const {
|
|
if (isNonClosureMethod()) {
|
|
return cls()->lookupMethod(name);
|
|
} else {
|
|
return Unit::lookupFunc(name);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// FuncEmitter.
|
|
|
|
FuncEmitter::FuncEmitter(UnitEmitter& ue, int sn, Id id, const StringData* n)
|
|
: m_ue(ue)
|
|
, m_pce(nullptr)
|
|
, m_sn(sn)
|
|
, m_id(id)
|
|
, m_name(n)
|
|
, m_numLocals(0)
|
|
, m_numUnnamedLocals(0)
|
|
, m_activeUnnamedLocals(0)
|
|
, m_numIterators(0)
|
|
, m_nextFreeIterator(0)
|
|
, m_retTypeConstraint(nullptr)
|
|
, m_returnType(KindOfInvalid)
|
|
, m_top(false)
|
|
, m_isClosureBody(false)
|
|
, m_isGenerator(false)
|
|
, m_isGeneratorFromClosure(false)
|
|
, m_hasGeneratorAsBody(false)
|
|
, m_containsCalls(false)
|
|
, m_info(nullptr)
|
|
, m_builtinFuncPtr(nullptr)
|
|
, m_originalFilename(nullptr)
|
|
{}
|
|
|
|
FuncEmitter::FuncEmitter(UnitEmitter& ue, int sn, const StringData* n,
|
|
PreClassEmitter* pce)
|
|
: m_ue(ue)
|
|
, m_pce(pce)
|
|
, m_sn(sn)
|
|
, m_name(n)
|
|
, m_numLocals(0)
|
|
, m_numUnnamedLocals(0)
|
|
, m_activeUnnamedLocals(0)
|
|
, m_numIterators(0)
|
|
, m_nextFreeIterator(0)
|
|
, m_retTypeConstraint(nullptr)
|
|
, m_returnType(KindOfInvalid)
|
|
, m_top(false)
|
|
, m_isClosureBody(false)
|
|
, m_isGenerator(false)
|
|
, m_isGeneratorFromClosure(false)
|
|
, m_hasGeneratorAsBody(false)
|
|
, m_containsCalls(false)
|
|
, m_info(nullptr)
|
|
, m_builtinFuncPtr(nullptr)
|
|
, m_originalFilename(nullptr)
|
|
{}
|
|
|
|
FuncEmitter::~FuncEmitter() {
|
|
}
|
|
|
|
void FuncEmitter::init(int line1, int line2, Offset base, Attr attrs, bool top,
|
|
const StringData* docComment) {
|
|
m_line1 = line1;
|
|
m_line2 = line2;
|
|
m_base = base;
|
|
m_attrs = attrs;
|
|
m_top = top;
|
|
m_docComment = docComment;
|
|
if (!SystemLib::s_inited) {
|
|
m_attrs = m_attrs | AttrBuiltin;
|
|
if (!pce()) m_attrs = m_attrs | AttrSkipFrame;
|
|
}
|
|
}
|
|
|
|
void FuncEmitter::finish(Offset past, bool load) {
|
|
m_past = past;
|
|
sortEHTab();
|
|
sortFPITab(load);
|
|
}
|
|
|
|
EHEnt& FuncEmitter::addEHEnt() {
|
|
m_ehtab.push_back(EHEnt());
|
|
return m_ehtab.back();
|
|
}
|
|
|
|
FPIEnt& FuncEmitter::addFPIEnt() {
|
|
m_fpitab.push_back(FPIEnt());
|
|
return m_fpitab.back();
|
|
}
|
|
|
|
Id FuncEmitter::newLocal() {
|
|
return m_numLocals++;
|
|
}
|
|
|
|
void FuncEmitter::appendParam(const StringData* name, const ParamInfo& info) {
|
|
allocVarId(name);
|
|
m_params.push_back(info);
|
|
}
|
|
|
|
void FuncEmitter::allocVarId(const StringData* name) {
|
|
assert(name != nullptr);
|
|
// Unnamed locals are segregated (they all come after the named locals).
|
|
assert(m_numUnnamedLocals == 0);
|
|
UNUSED Id id;
|
|
if (m_localNames.find(name) == m_localNames.end()) {
|
|
id = newLocal();
|
|
assert(id == (int)m_localNames.size());
|
|
m_localNames.add(name, name);
|
|
}
|
|
}
|
|
|
|
Id FuncEmitter::lookupVarId(const StringData* name) const {
|
|
assert(this->hasVar(name));
|
|
return m_localNames.find(name)->second;
|
|
}
|
|
|
|
bool FuncEmitter::hasVar(const StringData* name) const {
|
|
assert(name != nullptr);
|
|
return m_localNames.find(name) != m_localNames.end();
|
|
}
|
|
|
|
Id FuncEmitter::allocIterator() {
|
|
assert(m_numIterators >= m_nextFreeIterator);
|
|
Id id = m_nextFreeIterator++;
|
|
if (m_numIterators < m_nextFreeIterator) {
|
|
m_numIterators = m_nextFreeIterator;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
void FuncEmitter::freeIterator(Id id) {
|
|
--m_nextFreeIterator;
|
|
assert(id == m_nextFreeIterator);
|
|
}
|
|
|
|
void FuncEmitter::setNumIterators(Id numIterators) {
|
|
assert(m_numIterators == 0);
|
|
m_numIterators = numIterators;
|
|
}
|
|
|
|
Id FuncEmitter::allocUnnamedLocal() {
|
|
++m_activeUnnamedLocals;
|
|
if (m_activeUnnamedLocals > m_numUnnamedLocals) {
|
|
newLocal();
|
|
++m_numUnnamedLocals;
|
|
}
|
|
return m_numLocals - m_numUnnamedLocals + (m_activeUnnamedLocals - 1);
|
|
}
|
|
|
|
void FuncEmitter::freeUnnamedLocal(Id id) {
|
|
assert(m_activeUnnamedLocals > 0);
|
|
--m_activeUnnamedLocals;
|
|
}
|
|
|
|
void FuncEmitter::setNumLocals(Id numLocals) {
|
|
assert(numLocals >= m_numLocals);
|
|
m_numLocals = numLocals;
|
|
}
|
|
|
|
void FuncEmitter::addStaticVar(Func::SVInfo svInfo) {
|
|
m_staticVars.push_back(svInfo);
|
|
}
|
|
|
|
namespace {
|
|
|
|
/*
|
|
* Ordering on EHEnts where e1 < e2 iff
|
|
*
|
|
* a) e1 and e2 do not overlap, and e1 comes first
|
|
* b) e1 encloses e2
|
|
* c) e1 and e2 have the same region, but e1 is a Catch funclet and
|
|
* e2 is a Fault funclet.
|
|
*/
|
|
struct EHEntComp {
|
|
bool operator()(const EHEnt& e1, const EHEnt& e2) const {
|
|
if (e1.m_base == e2.m_base) {
|
|
if (e1.m_past == e2.m_past) {
|
|
return e1.m_ehtype == EHEnt::EHType_Catch;
|
|
}
|
|
return e1.m_past > e2.m_past;
|
|
}
|
|
return e1.m_base < e2.m_base;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
void FuncEmitter::sortEHTab() {
|
|
std::sort(m_ehtab.begin(), m_ehtab.end(), EHEntComp());
|
|
|
|
for (unsigned int i = 0; i < m_ehtab.size(); i++) {
|
|
m_ehtab[i].m_parentIndex = -1;
|
|
for (int j = i - 1; j >= 0; j--) {
|
|
if (m_ehtab[j].m_past >= m_ehtab[i].m_past) {
|
|
// parent EHEnt better enclose this one.
|
|
assert(m_ehtab[j].m_base <= m_ehtab[i].m_base);
|
|
m_ehtab[i].m_parentIndex = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FuncEmitter::sortFPITab(bool load) {
|
|
// Sort it and fill in parent info
|
|
std::sort(m_fpitab.begin(), m_fpitab.end(), FPIEntComp());
|
|
for (unsigned int i = 0; i < m_fpitab.size(); i++) {
|
|
m_fpitab[i].m_parentIndex = -1;
|
|
m_fpitab[i].m_fpiDepth = 1;
|
|
for (int j = i - 1; j >= 0; j--) {
|
|
if (m_fpitab[j].m_fcallOff > m_fpitab[i].m_fcallOff) {
|
|
m_fpitab[i].m_parentIndex = j;
|
|
m_fpitab[i].m_fpiDepth = m_fpitab[j].m_fpiDepth + 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!load) {
|
|
// m_fpOff does not include the space taken up by locals, iterators and
|
|
// the AR itself. Fix it here.
|
|
m_fpitab[i].m_fpOff += m_numLocals
|
|
+ m_numIterators * kNumIterCells
|
|
+ (m_fpitab[i].m_fpiDepth) * kNumActRecCells;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FuncEmitter::addUserAttribute(const StringData* name, TypedValue tv) {
|
|
m_userAttributes[name] = tv;
|
|
}
|
|
|
|
void FuncEmitter::commit(RepoTxn& txn) const {
|
|
Repo& repo = Repo::get();
|
|
FuncRepoProxy& frp = repo.frp();
|
|
int repoId = m_ue.repoId();
|
|
int64_t usn = m_ue.sn();
|
|
|
|
frp.insertFunc(repoId)
|
|
.insert(*this, txn, usn, m_sn, m_pce ? m_pce->id() : -1, m_name, m_top);
|
|
}
|
|
|
|
Func* FuncEmitter::create(Unit& unit, PreClass* preClass /* = NULL */) const {
|
|
Attr attrs = m_attrs;
|
|
if (attrs & AttrPersistent &&
|
|
((RuntimeOption::EvalJitEnableRenameFunction &&
|
|
!isdigit(m_name->data()[0])) ||
|
|
(!RuntimeOption::RepoAuthoritative && SystemLib::s_inited))) {
|
|
attrs = Attr(attrs & ~AttrPersistent);
|
|
}
|
|
if (RuntimeOption::EvalJitEnableRenameFunction &&
|
|
!m_name->empty() &&
|
|
!Func::isSpecial(m_name) &&
|
|
!m_isClosureBody &&
|
|
!m_isGenerator) {
|
|
// intercepted functions need to pass all args through
|
|
// to the interceptee
|
|
attrs = attrs | AttrMayUseVV;
|
|
}
|
|
|
|
if (!m_containsCalls) attrs = Attr(attrs | AttrPhpLeafFn);
|
|
|
|
Func* f = (m_pce == nullptr)
|
|
? m_ue.newFunc(this, unit, m_id, m_line1, m_line2, m_base,
|
|
m_past, m_name, attrs, m_top, m_docComment,
|
|
m_params.size(), m_isClosureBody | m_isGeneratorFromClosure,
|
|
m_isGenerator)
|
|
: m_ue.newFunc(this, unit, preClass, m_line1, m_line2, m_base,
|
|
m_past, m_name, attrs, m_top, m_docComment,
|
|
m_params.size(), m_isClosureBody | m_isGeneratorFromClosure,
|
|
m_isGenerator);
|
|
f->shared()->m_info = m_info;
|
|
f->shared()->m_returnType = m_returnType;
|
|
std::vector<Func::ParamInfo> pBuilder;
|
|
for (unsigned i = 0; i < m_params.size(); ++i) {
|
|
Func::ParamInfo pi;
|
|
pi.setFuncletOff(m_params[i].funcletOff());
|
|
pi.setDefaultValue(m_params[i].defaultValue());
|
|
pi.setPhpCode(m_params[i].phpCode());
|
|
pi.setTypeConstraint(m_params[i].typeConstraint());
|
|
pi.setUserAttributes(m_params[i].userAttributes());
|
|
pi.setBuiltinType(m_params[i].builtinType());
|
|
pi.setUserType(m_params[i].userType());
|
|
f->appendParam(m_params[i].ref(), pi, pBuilder);
|
|
}
|
|
f->shared()->m_params = pBuilder;
|
|
f->shared()->m_localNames.create(m_localNames);
|
|
f->shared()->m_numLocals = m_numLocals;
|
|
f->shared()->m_numIterators = m_numIterators;
|
|
f->m_maxStackCells = m_maxStackCells;
|
|
assert(m_maxStackCells > 0 && "You probably didn't set m_maxStackCells");
|
|
f->shared()->m_staticVars = m_staticVars;
|
|
f->shared()->m_ehtab = m_ehtab;
|
|
f->shared()->m_fpitab = m_fpitab;
|
|
f->shared()->m_isClosureBody = m_isClosureBody;
|
|
f->shared()->m_isGenerator = m_isGenerator;
|
|
f->shared()->m_isGeneratorFromClosure = m_isGeneratorFromClosure;
|
|
f->shared()->m_hasGeneratorAsBody = m_hasGeneratorAsBody;
|
|
f->shared()->m_userAttributes = m_userAttributes;
|
|
f->shared()->m_builtinFuncPtr = m_builtinFuncPtr;
|
|
f->shared()->m_nativeFuncPtr = m_nativeFuncPtr;
|
|
f->shared()->m_retTypeConstraint = m_retTypeConstraint;
|
|
f->shared()->m_originalFilename = m_originalFilename;
|
|
return f;
|
|
}
|
|
|
|
void FuncEmitter::setBuiltinFunc(const ClassInfo::MethodInfo* info,
|
|
BuiltinFunction bif, BuiltinFunction nif,
|
|
Offset base) {
|
|
assert(info);
|
|
assert(bif);
|
|
m_info = info;
|
|
m_builtinFuncPtr = bif;
|
|
m_nativeFuncPtr = nif;
|
|
m_base = base;
|
|
m_top = true;
|
|
m_docComment = StringData::GetStaticString(info->docComment);
|
|
m_line1 = 0;
|
|
m_line2 = 0;
|
|
m_attrs = AttrBuiltin | AttrSkipFrame;
|
|
// TODO: Task #1137917: See if we can avoid marking most builtins with
|
|
// "MayUseVV" and still make things work
|
|
m_attrs = m_attrs | AttrMayUseVV;
|
|
if (info->attribute & (ClassInfo::RefVariableArguments |
|
|
ClassInfo::MixedVariableArguments)) {
|
|
m_attrs = m_attrs | AttrVariadicByRef;
|
|
}
|
|
if (info->attribute & ClassInfo::IsReference) {
|
|
m_attrs = m_attrs | AttrReference;
|
|
}
|
|
if (info->attribute & ClassInfo::NoInjection) {
|
|
m_attrs = m_attrs | AttrNoInjection;
|
|
}
|
|
if (pce()) {
|
|
if (info->attribute & ClassInfo::IsStatic) {
|
|
m_attrs = m_attrs | AttrStatic;
|
|
}
|
|
if (info->attribute & ClassInfo::IsFinal) {
|
|
m_attrs = m_attrs | AttrFinal;
|
|
}
|
|
if (info->attribute & ClassInfo::IsAbstract) {
|
|
m_attrs = m_attrs | AttrAbstract;
|
|
}
|
|
if (info->attribute & ClassInfo::IsPrivate) {
|
|
m_attrs = m_attrs | AttrPrivate;
|
|
} else if (info->attribute & ClassInfo::IsProtected) {
|
|
m_attrs = m_attrs | AttrProtected;
|
|
} else {
|
|
m_attrs = m_attrs | AttrPublic;
|
|
}
|
|
} else if (info->attribute & ClassInfo::AllowOverride) {
|
|
m_attrs = m_attrs | AttrAllowOverride;
|
|
}
|
|
|
|
m_returnType = info->returnType;
|
|
for (unsigned i = 0; i < info->parameters.size(); ++i) {
|
|
// For builtin only, we use a dummy ParamInfo
|
|
FuncEmitter::ParamInfo pi;
|
|
pi.setRef((bool)(info->parameters[i]->attribute & ClassInfo::IsReference));
|
|
pi.setBuiltinType(info->parameters[i]->argType);
|
|
appendParam(StringData::GetStaticString(info->parameters[i]->name), pi);
|
|
}
|
|
}
|
|
|
|
template<class SerDe>
|
|
void FuncEmitter::serdeMetaData(SerDe& sd) {
|
|
// NOTE: name, top, and a few other fields currently serialized
|
|
// outside of this.
|
|
sd(m_line1)
|
|
(m_line2)
|
|
(m_base)
|
|
(m_past)
|
|
(m_attrs)
|
|
(m_returnType)
|
|
(m_docComment)
|
|
(m_numLocals)
|
|
(m_numIterators)
|
|
(m_maxStackCells)
|
|
(m_isClosureBody)
|
|
(m_isGenerator)
|
|
(m_isGeneratorFromClosure)
|
|
(m_hasGeneratorAsBody)
|
|
(m_containsCalls)
|
|
|
|
(m_params)
|
|
(m_localNames)
|
|
(m_staticVars)
|
|
(m_ehtab)
|
|
(m_fpitab)
|
|
(m_userAttributes)
|
|
(m_retTypeConstraint)
|
|
(m_originalFilename)
|
|
;
|
|
}
|
|
|
|
//=============================================================================
|
|
// FuncRepoProxy.
|
|
|
|
FuncRepoProxy::FuncRepoProxy(Repo& repo)
|
|
: RepoProxy(repo)
|
|
#define FRP_OP(c, o) \
|
|
, m_##o##Local(repo, RepoIdLocal), m_##o##Central(repo, RepoIdCentral)
|
|
FRP_OPS
|
|
#undef FRP_OP
|
|
{
|
|
#define FRP_OP(c, o) \
|
|
m_##o[RepoIdLocal] = &m_##o##Local; \
|
|
m_##o[RepoIdCentral] = &m_##o##Central;
|
|
FRP_OPS
|
|
#undef FRP_OP
|
|
}
|
|
|
|
FuncRepoProxy::~FuncRepoProxy() {
|
|
}
|
|
|
|
void FuncRepoProxy::createSchema(int repoId, RepoTxn& txn) {
|
|
std::stringstream ssCreate;
|
|
ssCreate << "CREATE TABLE " << m_repo.table(repoId, "Func")
|
|
<< "(unitSn INTEGER, funcSn INTEGER, preClassId INTEGER,"
|
|
" name TEXT, top INTEGER,"
|
|
" extraData BLOB,"
|
|
" PRIMARY KEY (unitSn, funcSn));";
|
|
txn.exec(ssCreate.str());
|
|
}
|
|
|
|
void FuncRepoProxy::InsertFuncStmt
|
|
::insert(const FuncEmitter& fe,
|
|
RepoTxn& txn, int64_t unitSn, int funcSn,
|
|
Id preClassId, const StringData* name,
|
|
bool top) {
|
|
if (!prepared()) {
|
|
std::stringstream ssInsert;
|
|
ssInsert << "INSERT INTO " << m_repo.table(m_repoId, "Func")
|
|
<< " VALUES(@unitSn, @funcSn, @preClassId, @name, "
|
|
" @top, @extraData);";
|
|
txn.prepare(*this, ssInsert.str());
|
|
}
|
|
|
|
BlobEncoder extraBlob;
|
|
RepoTxnQuery query(txn, *this);
|
|
query.bindInt64("@unitSn", unitSn);
|
|
query.bindInt("@funcSn", funcSn);
|
|
query.bindId("@preClassId", preClassId);
|
|
query.bindStaticString("@name", name);
|
|
query.bindBool("@top", top);
|
|
const_cast<FuncEmitter&>(fe).serdeMetaData(extraBlob);
|
|
query.bindBlob("@extraData", extraBlob, /* static */ true);
|
|
query.exec();
|
|
}
|
|
|
|
void FuncRepoProxy::GetFuncsStmt
|
|
::get(UnitEmitter& ue) {
|
|
RepoTxn txn(m_repo);
|
|
if (!prepared()) {
|
|
std::stringstream ssSelect;
|
|
ssSelect << "SELECT funcSn,preClassId,name,top,extraData "
|
|
"FROM "
|
|
<< m_repo.table(m_repoId, "Func")
|
|
<< " WHERE unitSn == @unitSn ORDER BY funcSn ASC;";
|
|
txn.prepare(*this, ssSelect.str());
|
|
}
|
|
RepoTxnQuery query(txn, *this);
|
|
query.bindInt64("@unitSn", ue.sn());
|
|
do {
|
|
query.step();
|
|
if (query.row()) {
|
|
int funcSn; /**/ query.getInt(0, funcSn);
|
|
Id preClassId; /**/ query.getId(1, preClassId);
|
|
StringData* name; /**/ query.getStaticString(2, name);
|
|
bool top; /**/ query.getBool(3, top);
|
|
BlobDecoder extraBlob = /**/ query.getBlob(4);
|
|
|
|
FuncEmitter* fe;
|
|
if (preClassId < 0) {
|
|
fe = ue.newFuncEmitter(name, top);
|
|
} else {
|
|
PreClassEmitter* pce = ue.pce(preClassId);
|
|
fe = ue.newMethodEmitter(name, pce);
|
|
bool added UNUSED = pce->addMethod(fe);
|
|
assert(added);
|
|
}
|
|
assert(fe->sn() == funcSn);
|
|
fe->setTop(top);
|
|
fe->serdeMetaData(extraBlob);
|
|
fe->finish(fe->past(), true);
|
|
ue.recordFunction(fe);
|
|
}
|
|
} while (!query.done());
|
|
txn.commit();
|
|
}
|
|
|
|
} // HPHP::VM
|