2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/func-emitter.h"
19 #include "hphp/parser/parser.h"
21 #include "hphp/runtime/ext/extension.h"
22 #include "hphp/runtime/base/unit-cache.h"
23 #include "hphp/runtime/base/rds.h"
24 #include "hphp/runtime/base/strings.h"
26 #include "hphp/runtime/vm/blob-helper.h"
27 #include "hphp/runtime/vm/bytecode.h"
28 #include "hphp/runtime/vm/func-inline.h"
29 #include "hphp/runtime/vm/native.h"
30 #include "hphp/runtime/vm/repo.h"
31 #include "hphp/runtime/vm/runtime.h"
33 #include "hphp/runtime/vm/jit/mc-generator.h"
34 #include "hphp/runtime/vm/jit/types.h"
36 #include "hphp/runtime/vm/verifier/cfg.h"
38 #include "hphp/system/systemlib.h"
40 #include "hphp/util/atomic-vector.h"
41 #include "hphp/util/debug.h"
42 #include "hphp/util/trace.h"
43 #include "hphp/runtime/ext_zend_compat/hhvm/zend-wrap-func.h"
46 ///////////////////////////////////////////////////////////////////////////////
49 FuncEmitter::FuncEmitter(UnitEmitter
& ue
, int sn
, Id id
, const StringData
* n
)
56 , returnType(folly::none
)
57 , retUserType(nullptr)
58 , isClosureBody(false)
61 , isPairGenerator(false)
62 , isMemoizeImpl(false)
63 , isMemoizeWrapper(false)
64 , hasMemoizeSharedProp(false)
65 , containsCalls(false)
67 , originalFilename(nullptr)
68 , memoizePropName(nullptr)
69 , memoizeSharedPropIndex(0)
71 , m_numUnnamedLocals(0)
72 , m_activeUnnamedLocals(0)
74 , m_nextFreeIterator(0)
76 , m_builtinFuncPtr(nullptr)
77 , m_nativeFuncPtr(nullptr)
78 , m_ehTabSorted(false)
81 FuncEmitter::FuncEmitter(UnitEmitter
& ue
, int sn
, const StringData
* n
,
89 , returnType(folly::none
)
90 , retUserType(nullptr)
91 , isClosureBody(false)
94 , isPairGenerator(false)
95 , isMemoizeImpl(false)
96 , isMemoizeWrapper(false)
97 , hasMemoizeSharedProp(false)
98 , containsCalls(false)
100 , originalFilename(nullptr)
101 , memoizePropName(nullptr)
102 , memoizeSharedPropIndex(0)
104 , m_numUnnamedLocals(0)
105 , m_activeUnnamedLocals(0)
107 , m_nextFreeIterator(0)
109 , m_builtinFuncPtr(nullptr)
110 , m_nativeFuncPtr(nullptr)
111 , m_ehTabSorted(false)
114 FuncEmitter::~FuncEmitter() {
118 ///////////////////////////////////////////////////////////////////////////////
119 // Initialization and execution.
121 void FuncEmitter::init(int l1
, int l2
, Offset base_
, Attr attrs_
, bool top_
,
122 const StringData
* docComment_
) {
128 docComment
= docComment_
;
130 if (!isPseudoMain()) {
131 if (!SystemLib::s_inited
) {
132 assert(attrs
& AttrBuiltin
);
134 if ((attrs
& AttrBuiltin
) && !pce()) {
135 attrs
|= AttrSkipFrame
;
140 void FuncEmitter::finish(Offset past_
, bool load
) {
146 void FuncEmitter::commit(RepoTxn
& txn
) const {
147 Repo
& repo
= Repo::get();
148 FuncRepoProxy
& frp
= repo
.frp();
149 int repoId
= m_ue
.m_repoId
;
150 int64_t usn
= m_ue
.m_sn
;
152 frp
.insertFunc
[repoId
]
153 .insert(*this, txn
, usn
, m_sn
, m_pce
? m_pce
->id() : -1, name
, top
);
156 static std::vector
<EHEnt
> toFixed(const std::vector
<EHEntEmitter
>& vec
) {
157 std::vector
<EHEnt
> ret
;
158 for (auto const& ehe
: vec
) {
160 e
.m_type
= ehe
.m_type
;
161 e
.m_itRef
= ehe
.m_itRef
;
162 e
.m_base
= ehe
.m_base
;
163 e
.m_past
= ehe
.m_past
;
164 e
.m_iterId
= ehe
.m_iterId
;
165 e
.m_parentIndex
= ehe
.m_parentIndex
;
166 e
.m_fault
= ehe
.m_fault
;
167 e
.m_catches
= ehe
.m_catches
;
168 ret
.emplace_back(std::move(e
));
173 Func
* FuncEmitter::create(Unit
& unit
, PreClass
* preClass
/* = NULL */) const {
174 bool isGenerated
= isdigit(name
->data()[0]) ||
175 ParserBase::IsClosureName(name
->toCppString());
177 Attr attrs
= this->attrs
;
178 if (preClass
&& preClass
->attrs() & AttrInterface
) {
179 attrs
|= AttrAbstract
;
181 if (attrs
& AttrPersistent
&&
182 ((RuntimeOption::EvalJitEnableRenameFunction
&& !isGenerated
) ||
183 (!RuntimeOption::RepoAuthoritative
&& SystemLib::s_inited
) ||
184 attrs
& AttrInterceptable
)) {
185 if (attrs
& AttrBuiltin
) {
186 SystemLib::s_anyNonPersistentBuiltins
= true;
188 attrs
= Attr(attrs
& ~AttrPersistent
);
190 if (!RuntimeOption::RepoAuthoritative
) {
191 // In non-RepoAuthoritative mode, any function could get a VarEnv because
192 // of evalPHPDebugger.
193 attrs
|= AttrMayUseVV
;
194 } else if (RuntimeOption::EvalJitEnableRenameFunction
&&
196 !Func::isSpecial(name
) &&
198 // intercepted functions need to pass all args through
199 // to the interceptee
200 attrs
|= AttrMayUseVV
;
202 if (isVariadic()) { attrs
|= AttrVariadicParam
; }
204 if (!containsCalls
) { attrs
|= AttrPhpLeafFn
; }
206 assert(!m_pce
== !preClass
);
207 auto f
= m_ue
.newFunc(this, unit
, name
, attrs
, params
.size());
209 f
->m_isPreFunc
= !!preClass
;
211 bool const needsExtendedSharedData
=
215 (attrs
& AttrNative
) ||
216 line2
- line1
>= Func::kSmallDeltaLimit
||
217 past
- base
>= Func::kSmallDeltaLimit
;
220 needsExtendedSharedData
221 ? new Func::ExtendedSharedData(preClass
, base
, past
, line1
, line2
,
223 : new Func::SharedData(preClass
, base
, past
,
224 line1
, line2
, top
, docComment
)
227 f
->init(params
.size());
229 if (auto const ex
= f
->extShared()) {
230 ex
->m_hasExtendedSharedData
= true;
231 ex
->m_builtinFuncPtr
= m_builtinFuncPtr
;
232 ex
->m_nativeFuncPtr
= m_nativeFuncPtr
;
236 ex
->m_returnByValue
= false;
239 std::vector
<Func::ParamInfo
> fParams
;
240 bool usesDoubles
= false;
241 for (unsigned i
= 0; i
< params
.size(); ++i
) {
242 Func::ParamInfo pi
= params
[i
];
243 if (pi
.builtinType
== KindOfDouble
) usesDoubles
= true;
244 if (pi
.isVariadic()) {
245 pi
.builtinType
= KindOfArray
;
247 f
->appendParam(params
[i
].byRef
, pi
, fParams
);
250 f
->shared()->m_returnType
= returnType
;
251 f
->shared()->m_localNames
.create(m_localNames
);
252 f
->shared()->m_numLocals
= m_numLocals
;
253 f
->shared()->m_numIterators
= m_numIterators
;
254 f
->m_maxStackCells
= maxStackCells
;
255 f
->shared()->m_staticVars
= staticVars
;
256 f
->shared()->m_ehtab
= toFixed(ehtab
);
257 f
->shared()->m_fpitab
= fpitab
;
258 f
->shared()->m_isClosureBody
= isClosureBody
;
259 f
->shared()->m_isAsync
= isAsync
;
260 f
->shared()->m_isGenerator
= isGenerator
;
261 f
->shared()->m_isPairGenerator
= isPairGenerator
;
262 f
->shared()->m_userAttributes
= userAttributes
;
263 f
->shared()->m_retTypeConstraint
= retTypeConstraint
;
264 f
->shared()->m_retUserType
= retUserType
;
265 f
->shared()->m_originalFilename
= originalFilename
;
266 f
->shared()->m_isGenerated
= isGenerated
;
268 if (attrs
& AttrNative
) {
269 auto const ex
= f
->extShared();
271 auto const& info
= Native::GetBuiltinFunction(
273 m_pce
? m_pce
->name() : nullptr,
277 auto const nif
= info
.ptr
;
279 Attr dummy
= AttrNone
;
280 int nativeAttrs
= parseNativeAttributes(dummy
);
281 if (nativeAttrs
& Native::AttrZendCompat
) {
282 ex
->m_nativeFuncPtr
= nif
;
283 ex
->m_builtinFuncPtr
= zend_wrap_func
;
285 if (parseNativeAttributes(dummy
) & Native::AttrActRec
) {
286 ex
->m_builtinFuncPtr
= nif
;
287 ex
->m_nativeFuncPtr
= nullptr;
289 ex
->m_nativeFuncPtr
= nif
;
290 ex
->m_builtinFuncPtr
= Native::getWrapper(m_pce
, usesDoubles
);
292 if (info
.sig
.ret
== Native::NativeSig::Type::MixedTV
) {
293 ex
->m_returnByValue
= true;
296 (attrs
& AttrNumArgs
? 1 : 0) +
297 (isMethod() ? 1 : 0);
298 assert(info
.sig
.args
.size() == params
.size() + extra
);
299 for (auto i
= params
.size(); i
--; ) {
300 switch (info
.sig
.args
[extra
+ i
]) {
301 case Native::NativeSig::Type::ObjectArg
:
302 case Native::NativeSig::Type::StringArg
:
303 case Native::NativeSig::Type::ArrayArg
:
304 case Native::NativeSig::Type::ResourceArg
:
305 case Native::NativeSig::Type::OutputArg
:
306 case Native::NativeSig::Type::MixedTV
:
307 fParams
[i
].nativeArg
= true;
316 ex
->m_builtinFuncPtr
= Native::unimplementedWrapper
;
320 f
->finishedEmittingParams(fParams
);
324 template<class SerDe
>
325 void FuncEmitter::serdeMetaData(SerDe
& sd
) {
326 // NOTE: name, top, and a few other fields currently serialized
357 ///////////////////////////////////////////////////////////////////////////////
358 // Locals, iterators, and parameters.
360 void FuncEmitter::allocVarId(const StringData
* name
) {
361 assert(name
!= nullptr);
362 // Unnamed locals are segregated (they all come after the named locals).
363 assert(m_numUnnamedLocals
== 0);
365 if (m_localNames
.find(name
) == m_localNames
.end()) {
366 id
= (m_numLocals
++);
367 assert(id
== (int)m_localNames
.size());
368 m_localNames
.add(name
, name
);
372 Id
FuncEmitter::allocIterator() {
373 assert(m_numIterators
>= m_nextFreeIterator
);
374 Id id
= m_nextFreeIterator
++;
375 if (m_numIterators
< m_nextFreeIterator
) {
376 m_numIterators
= m_nextFreeIterator
;
381 Id
FuncEmitter::allocUnnamedLocal() {
382 ++m_activeUnnamedLocals
;
383 if (m_activeUnnamedLocals
> m_numUnnamedLocals
) {
385 ++m_numUnnamedLocals
;
387 return m_numLocals
- m_numUnnamedLocals
+ (m_activeUnnamedLocals
- 1);
391 ///////////////////////////////////////////////////////////////////////////////
394 EHEntEmitter
& FuncEmitter::addEHEnt() {
395 assert(!m_ehTabSorted
396 || "should only mark the ehtab as sorted after adding all of them");
397 ehtab
.push_back(EHEntEmitter());
398 ehtab
.back().m_parentIndex
= 7777;
402 FPIEnt
& FuncEmitter::addFPIEnt() {
403 fpitab
.push_back(FPIEnt());
404 return fpitab
.back();
410 * Ordering on EHEnts where e1 < e2 iff
412 * a) e1 and e2 do not overlap, and e1 comes first
414 * c) e1 and e2 have the same region, but e1 is a Catch funclet and
415 * e2 is a Fault funclet.
418 bool operator()(const EHEntEmitter
& e1
, const EHEntEmitter
& e2
) const {
419 if (e1
.m_base
== e2
.m_base
) {
420 if (e1
.m_past
== e2
.m_past
) {
421 static_assert(!static_cast<uint8_t>(EHEnt::Type::Catch
),
422 "Catch should be the smallest type");
423 return e1
.m_type
< e2
.m_type
;
425 return e1
.m_past
> e2
.m_past
;
427 return e1
.m_base
< e2
.m_base
;
433 void FuncEmitter::sortEHTab() {
434 if (m_ehTabSorted
) return;
436 std::sort(ehtab
.begin(), ehtab
.end(), EHEntComp());
438 for (unsigned int i
= 0; i
< ehtab
.size(); i
++) {
439 ehtab
[i
].m_parentIndex
= -1;
440 for (int j
= i
- 1; j
>= 0; j
--) {
441 if (ehtab
[j
].m_past
>= ehtab
[i
].m_past
) {
442 // parent EHEnt better enclose this one.
443 assert(ehtab
[j
].m_base
<= ehtab
[i
].m_base
);
444 ehtab
[i
].m_parentIndex
= j
;
453 void FuncEmitter::sortFPITab(bool load
) {
454 // Sort it and fill in parent info
456 begin(fpitab
), end(fpitab
),
457 [&] (const FPIEnt
& a
, const FPIEnt
& b
) {
458 return a
.m_fpushOff
< b
.m_fpushOff
;
461 for (unsigned int i
= 0; i
< fpitab
.size(); i
++) {
462 fpitab
[i
].m_parentIndex
= -1;
463 fpitab
[i
].m_fpiDepth
= 1;
464 for (int j
= i
- 1; j
>= 0; j
--) {
465 if (fpitab
[j
].m_fcallOff
> fpitab
[i
].m_fcallOff
) {
466 fpitab
[i
].m_parentIndex
= j
;
467 fpitab
[i
].m_fpiDepth
= fpitab
[j
].m_fpiDepth
+ 1;
472 // m_fpOff does not include the space taken up by locals, iterators and
473 // the AR itself. Fix it here.
474 fpitab
[i
].m_fpOff
+= m_numLocals
475 + m_numIterators
* kNumIterCells
476 + (fpitab
[i
].m_fpiDepth
) * kNumActRecCells
;
481 void FuncEmitter::setEHTabIsSorted() {
482 m_ehTabSorted
= true;
486 for (size_t i
= 0; i
< ehtab
.size(); ++i
) {
489 // Base offsets must be monotonically increasing.
490 always_assert(curBase
<= eh
.m_base
);
493 // Parent should come before, and must enclose this guy.
494 always_assert(eh
.m_parentIndex
== -1 || eh
.m_parentIndex
< i
);
495 if (eh
.m_parentIndex
!= -1) {
496 auto& parent
= ehtab
[eh
.m_parentIndex
];
497 always_assert(parent
.m_base
<= eh
.m_base
&&
498 parent
.m_past
>= eh
.m_past
);
504 ///////////////////////////////////////////////////////////////////////////////
507 /* <<__Native>> user attribute causes systemlib declarations
508 * to hook internal (C++) implementation of funcs/methods
510 * The Native attribute may have the following sub-options
511 * "ActRec": The internal function takes a fixed prototype
512 * TypedValue* funcname(ActRec *ar);
513 * Note that systemlib declaration must still be hack annotated
514 * "NoFCallBuiltin": Prevent FCallBuiltin optimization
515 * Effectively forces functions to generate an ActRec
516 * "NoInjection": Do not include this frame in backtraces
517 * "ZendCompat": Use zend compat wrapper
519 * e.g. <<__Native("ActRec")>> function foo():mixed;
521 static const StaticString
522 s_native("__Native"),
524 s_nofcallbuiltin("NoFCallBuiltin"),
525 s_variadicbyref("VariadicByRef"),
526 s_noinjection("NoInjection"),
527 s_zendcompat("ZendCompat"),
528 s_numargs("NumArgs"),
529 s_opcodeimpl("OpCodeImpl");
531 int FuncEmitter::parseNativeAttributes(Attr
& attrs_
) const {
532 int ret
= Native::AttrNone
;
534 auto it
= userAttributes
.find(s_native
.get());
535 assert(it
!= userAttributes
.end());
536 const TypedValue userAttr
= it
->second
;
537 assert(userAttr
.m_type
== KindOfArray
);
538 for (ArrayIter
it(userAttr
.m_data
.parr
); it
; ++it
) {
539 Variant userAttrVal
= it
.second();
540 if (userAttrVal
.isString()) {
541 String userAttrStrVal
= userAttrVal
.toString();
542 if (userAttrStrVal
.get()->isame(s_actrec
.get())) {
543 ret
|= Native::AttrActRec
;
544 attrs_
|= AttrMayUseVV
;
545 } else if (userAttrStrVal
.get()->isame(s_nofcallbuiltin
.get())) {
546 attrs_
|= AttrNoFCallBuiltin
;
547 } else if (userAttrStrVal
.get()->isame(s_variadicbyref
.get())) {
548 attrs_
|= AttrVariadicByRef
;
549 } else if (userAttrStrVal
.get()->isame(s_noinjection
.get())) {
550 attrs_
|= AttrNoInjection
;
551 } else if (userAttrStrVal
.get()->isame(s_zendcompat
.get())) {
552 ret
|= Native::AttrZendCompat
;
553 // ZendCompat implies ActRec, no FCallBuiltin
554 attrs_
|= AttrMayUseVV
| AttrNoFCallBuiltin
;
555 ret
|= Native::AttrActRec
;
556 } else if (userAttrStrVal
.get()->isame(s_numargs
.get())) {
557 attrs_
|= AttrNumArgs
;
558 } else if (userAttrStrVal
.get()->isame(s_opcodeimpl
.get())) {
559 ret
|= Native::AttrOpCodeImpl
;
566 void FuncEmitter::setBuiltinFunc(const ClassInfo::MethodInfo
* info
,
567 BuiltinFunction bif
, BuiltinFunction nif
,
571 Attr attrs_
= AttrBuiltin
;
572 if (info
->attribute
& ClassInfo::RefVariableArguments
) {
573 attrs_
|= AttrVariadicByRef
;
575 if (info
->attribute
& ClassInfo::IsReference
) {
576 attrs_
|= AttrReference
;
578 if (info
->attribute
& ClassInfo::NoInjection
) {
579 attrs_
|= AttrNoInjection
;
581 if (info
->attribute
& ClassInfo::NoFCallBuiltin
) {
582 attrs_
|= AttrNoFCallBuiltin
;
584 if (info
->attribute
& ClassInfo::ParamCoerceModeNull
) {
585 attrs_
|= AttrParamCoerceModeNull
;
586 } else if (info
->attribute
& ClassInfo::ParamCoerceModeFalse
) {
587 attrs_
|= AttrParamCoerceModeFalse
;
590 if (info
->attribute
& ClassInfo::IsStatic
) {
591 attrs_
|= AttrStatic
;
593 if (info
->attribute
& ClassInfo::IsFinal
) {
596 if (info
->attribute
& ClassInfo::IsAbstract
) {
597 attrs_
|= AttrAbstract
;
599 if (info
->attribute
& ClassInfo::IsPrivate
) {
600 attrs_
|= AttrPrivate
;
601 } else if (info
->attribute
& ClassInfo::IsProtected
) {
602 attrs_
|= AttrProtected
;
604 attrs_
|= AttrPublic
;
606 } else if (info
->attribute
& ClassInfo::AllowOverride
) {
607 attrs_
|= AttrAllowOverride
;
610 returnType
= info
->returnType
;
611 docComment
= makeStaticString(info
->docComment
);
613 setBuiltinFunc(bif
, nif
, attrs_
, base_
);
615 for (unsigned i
= 0; i
< info
->parameters
.size(); ++i
) {
616 // For builtin only, we use a dummy ParamInfo
617 FuncEmitter::ParamInfo pi
;
618 const auto& parameter
= info
->parameters
[i
];
619 pi
.byRef
= parameter
->attribute
& ClassInfo::IsReference
;
620 pi
.builtinType
= parameter
->argType
;
621 appendParam(makeStaticString(parameter
->name
), pi
);
625 void FuncEmitter::setBuiltinFunc(BuiltinFunction bif
, BuiltinFunction nif
,
626 Attr attrs_
, Offset base_
) {
628 m_builtinFuncPtr
= bif
;
629 m_nativeFuncPtr
= nif
;
632 // TODO: Task #1137917: See if we can avoid marking most builtins with
633 // "MayUseVV" and still make things work
634 attrs
= attrs_
| AttrBuiltin
| AttrSkipFrame
| AttrMayUseVV
;
638 ///////////////////////////////////////////////////////////////////////////////
641 FuncRepoProxy::FuncRepoProxy(Repo
& repo
)
643 insertFunc
{InsertFuncStmt(repo
, 0), InsertFuncStmt(repo
, 1)},
644 getFuncs
{GetFuncsStmt(repo
, 0), GetFuncsStmt(repo
, 1)}
647 FuncRepoProxy::~FuncRepoProxy() {
650 void FuncRepoProxy::createSchema(int repoId
, RepoTxn
& txn
) {
651 std::stringstream ssCreate
;
652 ssCreate
<< "CREATE TABLE " << m_repo
.table(repoId
, "Func")
653 << "(unitSn INTEGER, funcSn INTEGER, preClassId INTEGER,"
654 " name TEXT, top INTEGER,"
656 " PRIMARY KEY (unitSn, funcSn));";
657 txn
.exec(ssCreate
.str());
660 void FuncRepoProxy::InsertFuncStmt
661 ::insert(const FuncEmitter
& fe
,
662 RepoTxn
& txn
, int64_t unitSn
, int funcSn
,
663 Id preClassId
, const StringData
* name
,
666 std::stringstream ssInsert
;
667 ssInsert
<< "INSERT INTO " << m_repo
.table(m_repoId
, "Func")
668 << " VALUES(@unitSn, @funcSn, @preClassId, @name, "
669 " @top, @extraData);";
670 txn
.prepare(*this, ssInsert
.str());
673 BlobEncoder extraBlob
;
674 RepoTxnQuery
query(txn
, *this);
675 query
.bindInt64("@unitSn", unitSn
);
676 query
.bindInt("@funcSn", funcSn
);
677 query
.bindId("@preClassId", preClassId
);
678 query
.bindStaticString("@name", name
);
679 query
.bindBool("@top", top
);
680 const_cast<FuncEmitter
&>(fe
).serdeMetaData(extraBlob
);
681 query
.bindBlob("@extraData", extraBlob
, /* static */ true);
685 void FuncRepoProxy::GetFuncsStmt
686 ::get(UnitEmitter
& ue
) {
689 std::stringstream ssSelect
;
690 ssSelect
<< "SELECT funcSn,preClassId,name,top,extraData "
692 << m_repo
.table(m_repoId
, "Func")
693 << " WHERE unitSn == @unitSn ORDER BY funcSn ASC;";
694 txn
.prepare(*this, ssSelect
.str());
696 RepoTxnQuery
query(txn
, *this);
697 query
.bindInt64("@unitSn", ue
.m_sn
);
701 int funcSn
; /**/ query
.getInt(0, funcSn
);
702 Id preClassId
; /**/ query
.getId(1, preClassId
);
703 StringData
* name
; /**/ query
.getStaticString(2, name
);
704 bool top
; /**/ query
.getBool(3, top
);
705 BlobDecoder extraBlob
= /**/ query
.getBlob(4);
708 if (preClassId
< 0) {
709 fe
= ue
.newFuncEmitter(name
);
711 PreClassEmitter
* pce
= ue
.pce(preClassId
);
712 fe
= ue
.newMethodEmitter(name
, pce
);
713 bool added UNUSED
= pce
->addMethod(fe
);
716 assert(fe
->sn() == funcSn
);
718 fe
->serdeMetaData(extraBlob
);
719 if (!SystemLib::s_inited
&& !fe
->isPseudoMain()) {
720 assert(fe
->attrs
& AttrBuiltin
);
721 if (preClassId
< 0) {
722 assert(fe
->attrs
& AttrPersistent
);
723 assert(fe
->attrs
& AttrUnique
);
724 assert(fe
->attrs
& AttrSkipFrame
);
727 fe
->setEHTabIsSorted();
728 fe
->finish(fe
->past
, true);
729 ue
.recordFunction(fe
);
731 } while (!query
.done());
735 ///////////////////////////////////////////////////////////////////////////////