2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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/runtime/base/array-iterator.h"
20 #include "hphp/runtime/base/unit-cache.h"
21 #include "hphp/runtime/base/rds.h"
23 #include "hphp/runtime/ext/extension.h"
25 #include "hphp/runtime/vm/blob-helper.h"
26 #include "hphp/runtime/vm/bytecode.h"
27 #include "hphp/runtime/vm/native.h"
28 #include "hphp/runtime/vm/reified-generics.h"
29 #include "hphp/runtime/vm/repo.h"
30 #include "hphp/runtime/vm/repo-autoload-map-builder.h"
31 #include "hphp/runtime/vm/runtime.h"
33 #include "hphp/runtime/vm/jit/types.h"
35 #include "hphp/runtime/vm/verifier/cfg.h"
37 #include "hphp/system/systemlib.h"
39 #include "hphp/util/atomic-vector.h"
40 #include "hphp/util/file.h"
41 #include "hphp/util/trace.h"
44 ///////////////////////////////////////////////////////////////////////////////
47 FuncEmitter::FuncEmitter(UnitEmitter
& ue
, int sn
, Id id
, const StringData
* n
)
54 , hniReturnType(folly::none
)
55 , retUserType(nullptr)
57 , originalFilename(nullptr)
58 , memoizePropName(nullptr)
59 , memoizeGuardPropName(nullptr)
60 , memoizeSharedPropIndex(0)
62 , m_numUnnamedLocals(0)
64 , m_nextFreeIterator(0)
65 , m_ehTabSorted(false)
68 FuncEmitter::FuncEmitter(UnitEmitter
& ue
, int sn
, const StringData
* n
,
76 , hniReturnType(folly::none
)
77 , retUserType(nullptr)
79 , originalFilename(nullptr)
80 , memoizePropName(nullptr)
81 , memoizeGuardPropName(nullptr)
82 , memoizeSharedPropIndex(0)
84 , m_numUnnamedLocals(0)
86 , m_nextFreeIterator(0)
87 , m_ehTabSorted(false)
90 FuncEmitter::~FuncEmitter() {
94 ///////////////////////////////////////////////////////////////////////////////
95 // Initialization and execution.
97 void FuncEmitter::init(int l1
, int l2
, Offset base_
, Attr attrs_
,
98 const StringData
* docComment_
) {
102 attrs
= fix_attrs(attrs_
);
103 docComment
= docComment_
;
105 if (!SystemLib::s_inited
) assertx(attrs
& AttrBuiltin
);
108 void FuncEmitter::finish(Offset past_
) {
113 void FuncEmitter::commit(RepoTxn
& txn
) const {
114 Repo
& repo
= Repo::get();
115 FuncRepoProxy
& frp
= repo
.frp();
116 int repoId
= m_ue
.m_repoId
;
117 int64_t usn
= m_ue
.m_sn
;
119 frp
.insertFunc
[repoId
]
120 .insert(*this, txn
, usn
, m_sn
, m_pce
? m_pce
->id() : -1, name
);
123 const StaticString
s_DynamicallyCallable("__DynamicallyCallable");
125 Func
* FuncEmitter::create(Unit
& unit
, PreClass
* preClass
/* = NULL */) const {
126 bool isGenerated
= isdigit(name
->data()[0]);
128 auto attrs
= fix_attrs(this->attrs
);
129 if (preClass
&& preClass
->attrs() & AttrInterface
) {
130 attrs
|= AttrAbstract
;
132 if (attrs
& AttrIsMethCaller
&& RuntimeOption::RepoAuthoritative
) {
133 attrs
|= AttrPersistent
| AttrUnique
;
135 if (attrs
& AttrPersistent
&& !preClass
) {
136 if ((RuntimeOption::EvalJitEnableRenameFunction
||
137 attrs
& AttrInterceptable
||
138 (!RuntimeOption::RepoAuthoritative
&& SystemLib::s_inited
))) {
139 if (attrs
& AttrBuiltin
) {
140 SystemLib::s_anyNonPersistentBuiltins
= true;
142 attrs
= Attr(attrs
& ~AttrPersistent
);
145 assertx(preClass
|| !(attrs
& AttrBuiltin
) || (attrs
& AttrIsMethCaller
));
148 attrs
|= AttrVariadicParam
;
150 if (isAsync
&& !isGenerator
) {
151 // Async functions can return results directly.
152 attrs
|= AttrSupportsAsyncEagerReturn
;
155 auto const dynCallSampleRate
= [&] () -> folly::Optional
<int64_t> {
156 if (!(attrs
& AttrDynamicallyCallable
)) return {};
158 auto const uattr
= userAttributes
.find(s_DynamicallyCallable
.get());
159 if (uattr
== userAttributes
.end()) return {};
161 auto const tv
= uattr
->second
;
162 assertx(isArrayLikeType(type(tv
)));
163 auto const rate
= val(tv
).parr
->get(int64_t(0));
164 if (!isIntType(type(rate
)) || val(rate
).num
< 0) return {};
166 attrs
= Attr(attrs
& ~AttrDynamicallyCallable
);
167 return val(rate
).num
;
170 assertx(!m_pce
== !preClass
);
171 auto f
= m_ue
.newFunc(this, unit
, name
, attrs
, params
.size());
173 f
->m_isPreFunc
= !!preClass
;
175 auto const uait
= userAttributes
.find(s___Reified
.get());
176 auto const hasReifiedGenerics
= uait
!= userAttributes
.end();
178 bool const needsExtendedSharedData
=
180 line2
- line1
>= Func::kSmallDeltaLimit
||
181 past
- base
>= Func::kSmallDeltaLimit
||
182 hasReifiedGenerics
||
183 hasParamsWithMultiUBs
||
184 hasReturnWithMultiUBs
||
188 needsExtendedSharedData
189 ? new Func::ExtendedSharedData(preClass
, base
, past
, line1
, line2
,
190 !containsCalls
, docComment
)
191 : new Func::SharedData(preClass
, base
, past
,
192 line1
, line2
, !containsCalls
, docComment
)
195 f
->init(params
.size());
197 if (auto const ex
= f
->extShared()) {
198 ex
->m_allFlags
.m_hasExtendedSharedData
= true;
199 ex
->m_arFuncPtr
= nullptr;
200 ex
->m_nativeFuncPtr
= nullptr;
203 ex
->m_dynCallSampleRate
= dynCallSampleRate
.value_or(-1);
204 ex
->m_allFlags
.m_returnByValue
= false;
205 ex
->m_allFlags
.m_isMemoizeWrapper
= false;
206 ex
->m_allFlags
.m_isMemoizeWrapperLSB
= false;
209 std::vector
<Func::ParamInfo
> fParams
;
210 for (unsigned i
= 0; i
< params
.size(); ++i
) {
211 Func::ParamInfo pi
= params
[i
];
212 if (pi
.isVariadic()) {
213 pi
.builtinType
= RuntimeOption::EvalHackArrDVArrs
214 ? KindOfVec
: KindOfVArray
;
216 f
->appendParam(params
[i
].isInOut(), pi
, fParams
);
217 auto const& fromUBs
= params
[i
].upperBounds
;
218 if (!fromUBs
.empty()) {
219 auto& ub
= f
->extShared()->m_paramUBs
[i
];
220 ub
.resize(fromUBs
.size());
221 std::copy(fromUBs
.begin(), fromUBs
.end(), ub
.begin());
222 f
->shared()->m_allFlags
.m_hasParamsWithMultiUBs
= true;
226 auto const originalFullName
=
227 (!originalFilename
||
228 !RuntimeOption::RepoAuthoritative
||
229 FileUtil::isAbsolutePath(originalFilename
->slice())) ?
231 makeStaticString(RuntimeOption::SourceRoot
+
232 originalFilename
->toCppString());
234 f
->shared()->m_localNames
.create(m_localNames
);
235 f
->shared()->m_numLocals
= m_numLocals
;
236 f
->shared()->m_numIterators
= m_numIterators
;
237 f
->m_maxStackCells
= maxStackCells
;
238 f
->shared()->m_ehtab
= ehtab
;
239 f
->shared()->m_allFlags
.m_isClosureBody
= isClosureBody
;
240 f
->shared()->m_allFlags
.m_isAsync
= isAsync
;
241 f
->shared()->m_allFlags
.m_isGenerator
= isGenerator
;
242 f
->shared()->m_allFlags
.m_isPairGenerator
= isPairGenerator
;
243 f
->shared()->m_userAttributes
= userAttributes
;
244 f
->shared()->m_retTypeConstraint
= retTypeConstraint
;
245 f
->shared()->m_retUserType
= retUserType
;
246 if (!retUpperBounds
.empty()) {
247 f
->extShared()->m_returnUBs
.resize(retUpperBounds
.size());
248 std::copy(retUpperBounds
.begin(), retUpperBounds
.end(),
249 f
->extShared()->m_returnUBs
.begin());
250 f
->shared()->m_allFlags
.m_hasReturnWithMultiUBs
= true;
252 f
->shared()->m_originalFilename
= originalFullName
;
253 f
->shared()->m_allFlags
.m_isGenerated
= isGenerated
;
254 f
->shared()->m_repoReturnType
= repoReturnType
;
255 f
->shared()->m_repoAwaitedReturnType
= repoAwaitedReturnType
;
256 f
->shared()->m_allFlags
.m_isMemoizeWrapper
= isMemoizeWrapper
;
257 f
->shared()->m_allFlags
.m_isMemoizeWrapperLSB
= isMemoizeWrapperLSB
;
258 f
->shared()->m_allFlags
.m_hasReifiedGenerics
= hasReifiedGenerics
;
259 f
->shared()->m_allFlags
.m_isRxDisabled
= isRxDisabled
;
261 if (hasReifiedGenerics
) {
262 auto tv
= uait
->second
;
263 assertx(tvIsHAMSafeVArray(tv
));
264 f
->extShared()->m_reifiedGenericsInfo
=
265 extractSizeAndPosFromReifiedAttribute(tv
.m_data
.parr
);
269 auto const ex
= f
->extShared();
271 ex
->m_hniReturnType
= hniReturnType
;
273 auto const info
= getNativeInfo();
275 Attr dummy
= AttrNone
;
276 auto nativeAttributes
= parseNativeAttributes(dummy
);
277 Native::getFunctionPointers(
284 if (ex
->m_nativeFuncPtr
) {
285 if (info
.sig
.ret
== Native::NativeSig::Type::MixedTV
) {
286 ex
->m_allFlags
.m_returnByValue
= true;
288 int extra
= isMethod() ? 1 : 0;
289 assertx(info
.sig
.args
.size() == params
.size() + extra
);
290 for (auto i
= params
.size(); i
--; ) {
291 switch (info
.sig
.args
[extra
+ i
]) {
292 case Native::NativeSig::Type::ObjectArg
:
293 case Native::NativeSig::Type::StringArg
:
294 case Native::NativeSig::Type::ArrayArg
:
295 case Native::NativeSig::Type::ResourceArg
:
296 fParams
[i
].setFlag(Func::ParamInfo::Flags::NativeArg
);
298 case Native::NativeSig::Type::MixedTV
:
299 fParams
[i
].setFlag(Func::ParamInfo::Flags::NativeArg
);
300 fParams
[i
].setFlag(Func::ParamInfo::Flags::AsTypedValue
);
302 case Native::NativeSig::Type::Mixed
:
303 fParams
[i
].setFlag(Func::ParamInfo::Flags::AsVariant
);
312 f
->finishedEmittingParams(fParams
);
316 String
FuncEmitter::nativeFullname() const {
317 return Native::fullName(name
, m_pce
? m_pce
->name() : nullptr,
318 (attrs
& AttrStatic
));
321 Native::NativeFunctionInfo
FuncEmitter::getNativeInfo() const {
322 return Native::getNativeFunction(
325 m_pce
? m_pce
->name() : nullptr,
330 ///////////////////////////////////////////////////////////////////////////////
331 // Locals, iterators, and parameters.
333 void FuncEmitter::allocVarId(const StringData
* name
, bool slotless
) {
334 assertx(name
!= nullptr);
336 if (m_localNames
.find(name
) == m_localNames
.end()) {
337 // Slotless locals must come after all locals with slots.
339 id
= (m_numLocals
++);
340 assertx(id
== (int)m_localNames
.size());
342 m_localNames
.add(name
, name
);
346 Id
FuncEmitter::allocIterator() {
347 assertx(m_numIterators
>= m_nextFreeIterator
);
348 Id id
= m_nextFreeIterator
++;
349 if (m_numIterators
< m_nextFreeIterator
) {
350 m_numIterators
= m_nextFreeIterator
;
355 Id
FuncEmitter::allocUnnamedLocal() {
356 m_localNames
.addUnnamed(nullptr);
357 ++m_numUnnamedLocals
;
358 return m_numLocals
++;
361 ///////////////////////////////////////////////////////////////////////////////
364 EHEnt
& FuncEmitter::addEHEnt() {
365 assertx(!m_ehTabSorted
366 || "should only mark the ehtab as sorted after adding all of them");
367 ehtab
.emplace_back();
368 ehtab
.back().m_parentIndex
= 7777;
375 * Ordering on EHEnts where e1 < e2 iff
377 * a) e1 and e2 do not overlap, and e1 comes first
381 bool operator()(const EHEnt
& e1
, const EHEnt
& e2
) const {
382 if (e1
.m_base
== e2
.m_base
) {
383 return e1
.m_past
> e2
.m_past
;
385 return e1
.m_base
< e2
.m_base
;
391 void FuncEmitter::sortEHTab() {
392 if (m_ehTabSorted
) return;
394 std::sort(ehtab
.begin(), ehtab
.end(), EHEntComp());
396 for (unsigned int i
= 0; i
< ehtab
.size(); i
++) {
397 ehtab
[i
].m_parentIndex
= -1;
398 for (int j
= i
- 1; j
>= 0; j
--) {
399 if (ehtab
[j
].m_past
>= ehtab
[i
].m_past
) {
400 // parent EHEnt better enclose this one.
401 assertx(ehtab
[j
].m_base
<= ehtab
[i
].m_base
);
402 ehtab
[i
].m_parentIndex
= j
;
411 void FuncEmitter::setEHTabIsSorted() {
412 m_ehTabSorted
= true;
416 for (size_t i
= 0; i
< ehtab
.size(); ++i
) {
419 // Base offsets must be monotonically increasing.
420 always_assert(curBase
<= eh
.m_base
);
423 // Parent should come before, and must enclose this guy.
424 always_assert(eh
.m_parentIndex
== -1 || eh
.m_parentIndex
< i
);
425 if (eh
.m_parentIndex
!= -1) {
426 auto& parent
= ehtab
[eh
.m_parentIndex
];
427 always_assert(parent
.m_base
<= eh
.m_base
&&
428 parent
.m_past
>= eh
.m_past
);
434 ///////////////////////////////////////////////////////////////////////////////
437 /* <<__Native>> user attribute causes systemlib declarations
438 * to hook internal (C++) implementation of funcs/methods
440 * The Native attribute may have the following sub-options
441 * "NoFCallBuiltin": Prevent FCallBuiltin optimization
442 * Effectively forces functions to generate an ActRec
443 * "NoInjection": Do not include this frame in backtraces
445 * e.g. <<__Native("NoFCallBuiltin")>> function foo():mixed;
447 static const StaticString
448 s_native("__Native"),
449 s_nofcallbuiltin("NoFCallBuiltin"),
450 s_noinjection("NoInjection"),
451 s_opcodeimpl("OpCodeImpl");
453 int FuncEmitter::parseNativeAttributes(Attr
& attrs_
) const {
454 int ret
= Native::AttrNone
;
456 auto it
= userAttributes
.find(s_native
.get());
457 assertx(it
!= userAttributes
.end());
458 const TypedValue userAttr
= it
->second
;
459 assertx(isArrayLikeType(userAttr
.m_type
));
460 for (ArrayIter
it(userAttr
.m_data
.parr
); it
; ++it
) {
461 Variant userAttrVal
= it
.second();
462 if (userAttrVal
.isString()) {
463 String userAttrStrVal
= userAttrVal
.toString();
464 if (userAttrStrVal
.get()->isame(s_nofcallbuiltin
.get())) {
465 attrs_
|= AttrNoFCallBuiltin
;
466 } else if (userAttrStrVal
.get()->isame(s_noinjection
.get())) {
467 attrs_
|= AttrNoInjection
;
468 } else if (userAttrStrVal
.get()->isame(s_opcodeimpl
.get())) {
469 ret
|= Native::AttrOpCodeImpl
;
476 Attr
FuncEmitter::fix_attrs(Attr a
) const {
477 if (RuntimeOption::RepoAuthoritative
) return a
;
479 a
= Attr(a
& ~AttrInterceptable
);
481 if (RuntimeOption::EvalJitEnableRenameFunction
) {
482 return a
| AttrInterceptable
;
487 ///////////////////////////////////////////////////////////////////////////////
488 // Serialization/Deserialization
490 template<class SerDe
>
491 void FuncEmitter::serdeMetaData(SerDe
& sd
) {
492 // NOTE: name and a few other fields currently handled outside of this.
496 if (!SerDe::deserializing
) {
497 past_delta
= past
- base
;
498 a
= fix_attrs(attrs
);
508 (repoAwaitedReturnType
)
516 (m_localNames
, [](auto s
) { return s
; })
518 [&](const EHEnt
& prev
, EHEnt cur
) -> EHEnt
{
519 if (!SerDe::deserializing
) {
520 cur
.m_handler
-= cur
.m_past
;
521 cur
.m_past
-= cur
.m_base
;
522 cur
.m_base
-= prev
.m_base
;
524 cur
.m_base
+= prev
.m_base
;
525 cur
.m_past
+= cur
.m_base
;
526 cur
.m_handler
+= cur
.m_past
;
538 if (SerDe::deserializing
) {
539 repoReturnType
.resolveArray(ue());
540 repoAwaitedReturnType
.resolveArray(ue());
541 past
= base
+ past_delta
;
542 attrs
= fix_attrs(a
);
546 ///////////////////////////////////////////////////////////////////////////////
549 FuncRepoProxy::FuncRepoProxy(Repo
& repo
)
551 insertFunc
{InsertFuncStmt(repo
, 0), InsertFuncStmt(repo
, 1)},
552 getFuncs
{GetFuncsStmt(repo
, 0), GetFuncsStmt(repo
, 1)}
555 FuncRepoProxy::~FuncRepoProxy() {
558 void FuncRepoProxy::createSchema(int repoId
, RepoTxn
& txn
) {
559 auto createQuery
= folly::sformat(
561 "(unitSn INTEGER, funcSn INTEGER, preClassId INTEGER, name TEXT, "
562 " extraData BLOB, PRIMARY KEY (unitSn, funcSn));",
563 m_repo
.table(repoId
, "Func"));
564 txn
.exec(createQuery
);
567 void FuncRepoProxy::InsertFuncStmt
568 ::insert(const FuncEmitter
& fe
,
569 RepoTxn
& txn
, int64_t unitSn
, int funcSn
,
570 Id preClassId
, const StringData
* name
) {
572 auto insertQuery
= folly::sformat(
574 "VALUES(@unitSn, @funcSn, @preClassId, @name, @extraData);",
575 m_repo
.table(m_repoId
, "Func"));
576 txn
.prepare(*this, insertQuery
);
579 BlobEncoder extraBlob
{fe
.useGlobalIds()};
580 RepoTxnQuery
query(txn
, *this);
581 query
.bindInt64("@unitSn", unitSn
);
582 query
.bindInt("@funcSn", funcSn
);
583 query
.bindId("@preClassId", preClassId
);
584 query
.bindStaticString("@name", name
);
585 const_cast<FuncEmitter
&>(fe
).serdeMetaData(extraBlob
);
586 query
.bindBlob("@extraData", extraBlob
, /* static */ true);
590 void FuncRepoProxy::GetFuncsStmt
591 ::get(UnitEmitter
& ue
) {
592 auto txn
= RepoTxn
{m_repo
.begin()};
594 auto selectQuery
= folly::sformat(
595 "SELECT funcSn, preClassId, name, extraData "
597 "WHERE unitSn == @unitSn ORDER BY funcSn ASC;",
598 m_repo
.table(m_repoId
, "Func"));
599 txn
.prepare(*this, selectQuery
);
601 RepoTxnQuery
query(txn
, *this);
602 query
.bindInt64("@unitSn", ue
.m_sn
);
606 int funcSn
; /**/ query
.getInt(0, funcSn
);
607 Id preClassId
; /**/ query
.getId(1, preClassId
);
608 StringData
* name
; /**/ query
.getStaticString(2, name
);
609 BlobDecoder extraBlob
= /**/ query
.getBlob(3, ue
.useGlobalIds());
612 if (preClassId
< 0) {
613 fe
= ue
.newFuncEmitter(name
);
615 PreClassEmitter
* pce
= ue
.pce(preClassId
);
616 fe
= ue
.newMethodEmitter(name
, pce
);
617 bool added UNUSED
= pce
->addMethod(fe
);
620 assertx(fe
->sn() == funcSn
);
621 fe
->serdeMetaData(extraBlob
);
622 if (!SystemLib::s_inited
) {
623 assertx(fe
->attrs
& AttrBuiltin
);
624 if (preClassId
< 0) {
625 assertx(fe
->attrs
& AttrPersistent
);
626 assertx(fe
->attrs
& AttrUnique
);
629 fe
->setEHTabIsSorted();
630 fe
->finish(fe
->past
);
632 } while (!query
.done());
636 ///////////////////////////////////////////////////////////////////////////////