2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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/unit.h"
19 #include "hphp/compiler/option.h"
21 #include "hphp/parser/parser.h"
23 #include "hphp/runtime/base/file-repository.h"
24 #include "hphp/runtime/base/rds.h"
25 #include "hphp/runtime/base/stats.h"
26 #include "hphp/runtime/base/strings.h"
28 #include "hphp/runtime/ext/std/ext_std_variable.h"
29 #include "hphp/runtime/vm/blob-helper.h"
30 #include "hphp/runtime/vm/bytecode.h"
31 #include "hphp/runtime/vm/disas.h"
32 #include "hphp/runtime/vm/func-inline.h"
33 #include "hphp/runtime/vm/repo.h"
34 #include "hphp/runtime/vm/treadmill.h"
35 #include "hphp/runtime/vm/unit-util.h"
37 #include "hphp/runtime/vm/jit/mc-generator.h"
38 #include "hphp/runtime/vm/jit/translator-inline.h"
40 #include "hphp/runtime/vm/verifier/check.h"
42 #include "hphp/util/atomic.h"
43 #include "hphp/util/file-util.h"
44 #include "hphp/util/lock.h"
45 #include "hphp/util/read-only-arena.h"
47 #include "folly/Memory.h"
48 #include "folly/ScopeGuard.h"
50 #include <boost/algorithm/string.hpp>
52 #include <tbb/concurrent_unordered_map.h>
57 ///////////////////////////////////////////////////////////////////////////////
61 const StaticString
s_stdin("STDIN");
62 const StaticString
s_stdout("STDOUT");
63 const StaticString
s_stderr("STDERR");
66 * Read typed data from an offset relative to a base address
69 T
& getDataRef(void* base
, unsigned offset
) {
70 return *(T
*)((char*)base
+ offset
);
73 ReadOnlyArena
& get_readonly_arena() {
74 static ReadOnlyArena
arena(RuntimeOption::EvalHHBCArenaChunkSize
);
78 // Exports for the admin server.
79 size_t hhbc_arena_capacity() {
80 if (!RuntimeOption::RepoAuthoritative
) return 0;
81 return get_readonly_arena().capacity();
84 static const unsigned char*
85 allocateBCRegion(const unsigned char* bc
, size_t bclen
) {
86 if (RuntimeOption::RepoAuthoritative
) {
87 // In RepoAuthoritative, we assume we won't ever deallocate units
88 // and that this is read-only, mostly cold data. So we throw it
89 // in a bump-allocator that's mprotect'd to prevent writes.
90 return static_cast<const unsigned char*>(
91 get_readonly_arena().allocate(bc
, bclen
)
94 auto mem
= static_cast<unsigned char*>(malloc(bclen
));
95 std::copy(bc
, bc
+ bclen
, mem
);
99 Mutex
Unit::s_classesMutex
;
101 * We hold onto references to elements of this map. If we use a different
102 * map, we must use one that doesnt invalidate references to its elements
103 * (unless they are deleted, which never happens here). Any standard
104 * associative container will meet this requirement.
106 static NamedEntityMap
*s_namedDataMap
;
109 NamedEntity
* getNamedEntityHelper(const StringData
* str
) {
110 if (!str
->isStatic()) {
111 str
= makeStaticString(str
);
114 auto res
= s_namedDataMap
->insert(str
, NamedEntity());
115 return &res
.first
->second
;
118 size_t Unit::GetNamedEntityTableSize() {
119 return s_namedDataMap
? s_namedDataMap
->size() : 0;
123 static void initializeNamedDataMap() {
124 NamedEntityMap::Config config
;
125 config
.growthFactor
= 1;
127 new NamedEntityMap(RuntimeOption::EvalInitialNamedEntityTableSize
,
131 NamedEntity
* Unit::GetNamedEntity(const StringData
* str
,
132 bool allowCreate
/*= true*/,
133 String
* normalizedStr
/*= nullptr*/) {
134 if (UNLIKELY(!s_namedDataMap
)) {
135 initializeNamedDataMap();
137 NamedEntityMap::iterator it
= s_namedDataMap
->find(str
);
138 if (LIKELY(it
!= s_namedDataMap
->end())) return &it
->second
;
139 if (needsNSNormalization(str
)) {
140 auto normStr
= normalizeNS(StrNR(str
).asString());
142 *normalizedStr
= normStr
;
144 return GetNamedEntity(normStr
.get(), allowCreate
, normalizedStr
);
146 if (LIKELY(allowCreate
)) { return getNamedEntityHelper(str
); }
150 void NamedEntity::setCachedFunc(Func
* f
) {
154 Func
* NamedEntity::getCachedFunc() const {
155 return LIKELY(m_cachedFunc
.bound()) ? *m_cachedFunc
: nullptr;
158 void NamedEntity::setCachedClass(Class
* f
) {
162 Class
* NamedEntity::getCachedClass() const {
163 return LIKELY(m_cachedClass
.bound()) ? *m_cachedClass
: nullptr;
166 void NamedEntity::setCachedTypeAlias(const TypeAliasReq
& td
) {
167 *m_cachedTypeAlias
= td
;
170 const TypeAliasReq
* NamedEntity::getCachedTypeAlias() const {
171 // TODO(#2103214): support persistent typeAliases
172 return m_cachedTypeAlias
.bound() && m_cachedTypeAlias
->name
?
173 m_cachedTypeAlias
.get() : nullptr;
176 void NamedEntity::pushClass(Class
* cls
) {
177 assert(!cls
->m_nextClass
);
178 cls
->m_nextClass
= m_clsList
.load(std::memory_order_acquire
);
179 m_clsList
.store(cls
, std::memory_order_release
);
182 void NamedEntity::removeClass(Class
* goner
) {
183 Class
* head
= m_clsList
.load(std::memory_order_acquire
);
186 return m_clsList
.store(head
->m_nextClass
, std::memory_order_release
);
188 Class
** cls
= &head
->m_nextClass
;
189 while (*cls
!= goner
) {
191 cls
= &(*cls
)->m_nextClass
;
193 *cls
= goner
->m_nextClass
;
196 UnitMergeInfo
* UnitMergeInfo::alloc(size_t size
) {
197 UnitMergeInfo
* mi
= (UnitMergeInfo
*)malloc(
198 sizeof(UnitMergeInfo
) + size
* sizeof(void*));
199 mi
->m_firstHoistableFunc
= 0;
200 mi
->m_firstHoistablePreClass
= 0;
201 mi
->m_firstMergeablePreClass
= 0;
202 mi
->m_mergeablesSize
= size
;
206 Array
Unit::getFunctions(bool system
) {
207 // Return an array of all defined functions. This method is used
208 // to support get_defined_functions().
209 Array a
= Array::Create();
210 if (s_namedDataMap
) {
211 for (NamedEntityMap::const_iterator it
= s_namedDataMap
->begin();
212 it
!= s_namedDataMap
->end(); ++it
) {
213 Func
* func_
= it
->second
.getCachedFunc();
214 if (!func_
|| (system
^ func_
->isBuiltin()) || func_
->isGenerated()) {
217 a
.append(func_
->nameRef());
223 AllClasses::AllClasses()
224 : m_next(s_namedDataMap
->begin())
225 , m_end(s_namedDataMap
->end())
226 , m_current(m_next
!= m_end
? m_next
->second
.clsList() : nullptr) {
227 if (!empty()) skip();
230 void AllClasses::skip() {
235 m_current
= m_next
->second
.clsList();
236 if (m_current
) break;
240 assert(empty() || front());
243 void AllClasses::next() {
244 m_current
= m_current
->m_nextClass
;
248 bool AllClasses::empty() const {
249 return m_next
== m_end
;
252 Class
* AllClasses::front() const {
258 Class
* AllClasses::popFront() {
259 Class
* cls
= front();
264 class AllCachedClasses
{
265 NamedEntityMap::iterator m_next
, m_end
;
270 cls
= m_next
->second
.clsList();
271 if (cls
&& cls
->getCached() &&
272 (cls
->parent() != SystemLib::s_ClosureClass
)) break;
279 : m_next(s_namedDataMap
->begin())
280 , m_end(s_namedDataMap
->end())
285 return m_next
== m_end
;
289 Class
* c
= m_next
->second
.clsList();
303 Array
Unit::getClassesWithAttrInfo(Attr attrs
, bool inverse
) {
304 Array a
= Array::Create();
305 if (s_namedDataMap
) {
306 for (AllCachedClasses ac
; !ac
.empty();) {
307 Class
* c
= ac
.popFront();
308 if ((c
->attrs() & attrs
) ? !inverse
: inverse
) {
309 if (c
->isBuiltin()) {
310 a
.prepend(c
->nameRef());
312 a
.append(c
->nameRef());
320 Array
Unit::getClassesInfo() {
321 // Return an array of all defined class names. This method is used to
322 // support get_declared_classes().
323 return getClassesWithAttrInfo(AttrInterface
| AttrTrait
,
324 /* inverse = */ true);
327 Array
Unit::getInterfacesInfo() {
328 // Return an array of all defined interface names. This method is used to
329 // support get_declared_interfaces().
330 return getClassesWithAttrInfo(AttrInterface
);
333 Array
Unit::getTraitsInfo() {
334 // Returns an array with all defined trait names. This method is used to
335 // support get_declared_traits().
336 return getClassesWithAttrInfo(AttrTrait
);
339 bool Unit::checkStringId(Id id
) const {
340 if (isGlobalLitstrId(id
)) {
341 return decodeGlobalLitstrId(id
) < LitstrTable::get().numLitstrs();
343 return id
>= 0 && unsigned(id
) < numLitstrs();
346 bool Unit::MetaHandle::findMeta(const Unit
* unit
, Offset offset
) {
347 if (!unit
->m_bc_meta_len
) return false;
348 assert(unit
->m_bc_meta
);
349 Offset
* index1
= (Offset
*)unit
->m_bc_meta
;
350 Offset
* index2
= index1
+ *index1
+ 1;
352 assert(index1
[*index1
+ 1] == INT_MAX
); // sentinel
353 assert(offset
>= 0 && (unsigned)offset
< unit
->m_bclen
);
354 assert(cur
== 0 || index
== index1
);
355 if (cur
&& offset
>= index
[cur
]) {
356 while (offset
>= index
[cur
+1]) cur
++;
358 int hi
= *index1
+ 2;
360 while (hi
- lo
> 1) {
361 int mid
= (hi
+ lo
) >> 1;
362 if (offset
>= index1
[mid
]) {
371 assert(cur
<= (unsigned)*index1
);
372 assert((unsigned)index2
[cur
] <= unit
->m_bc_meta_len
);
373 ptr
= unit
->m_bc_meta
+ index2
[cur
];
374 return index
[cur
] == offset
;
377 bool Unit::MetaHandle::nextArg(MetaInfo
& info
) {
378 assert(index
&& cur
&& ptr
);
379 uint8_t* end
= (uint8_t*)index
+ index
[*index
+ cur
+ 2];
381 if (ptr
== end
) return false;
382 info
.m_kind
= (Unit::MetaInfo::Kind
)*ptr
++;
384 info
.m_data
= decodeVariableSizeImm(&ptr
);
388 //=============================================================================
391 LitstrTable
* LitstrTable::s_litstrTable
= nullptr;
393 Id
LitstrTable::mergeLitstr(const StringData
* litstr
) {
395 assert(!m_safeToRead
);
396 auto it
= m_litstr2id
.find(litstr
);
397 if (it
== m_litstr2id
.end()) {
398 const StringData
* str
= makeStaticString(litstr
);
399 Id id
= m_litstrs
.size();
400 NamedEntityPair np
= { str
, nullptr };
401 m_litstr2id
[str
] = id
;
402 m_litstrs
.push_back(str
);
403 m_namedInfo
.push_back(np
);
412 void LitstrTable::insert(RepoTxn
& txn
, UnitOrigin uo
) {
413 Repo
& repo
= Repo::get();
414 LitstrRepoProxy
& lsrp
= repo
.lsrp();
415 int repoId
= Repo::get().repoIdForNewUnit(uo
);
416 for (int i
= 0; i
< m_litstrs
.size(); ++i
) {
417 lsrp
.insertLitstr(repoId
).insert(txn
, i
, m_litstrs
[i
]);
421 //=============================================================================
425 tvWriteUninit(&m_mainReturn
);
429 if (!RuntimeOption::RepoAuthoritative
) {
431 // poison released bytecode
432 memset(const_cast<unsigned char*>(m_bc
), 0xff, m_bclen
);
434 free(const_cast<unsigned char*>(m_bc
));
435 free(const_cast<unsigned char*>(m_bc_meta
));
439 for (auto* func
: mutableFuncs()) Func::destroy(func
);
442 // ExecutionContext and the TC may retain references to Class'es, so
443 // it is possible for Class'es to outlive their Unit.
444 for (int i
= m_preClasses
.size(); i
--; ) {
445 PreClass
* pcls
= m_preClasses
[i
].get();
446 Class
* cls
= pcls
->namedEntity()->clsList();
449 cls
= cls
->m_nextClass
;
450 if (cur
->preClass() == pcls
) {
458 if (m_pseudoMainCache
) {
459 for (auto it
= m_pseudoMainCache
->begin();
460 it
!= m_pseudoMainCache
->end(); ++it
) {
463 delete m_pseudoMainCache
;
467 void* Unit::operator new(size_t sz
) {
468 return low_malloc(sz
);
471 void Unit::operator delete(void* p
, size_t sz
) {
475 bool Unit::compileTimeFatal(const StringData
*& msg
, int& line
) const {
476 // A compile-time fatal is encoded as a pseudomain that contains precisely:
478 // String <id>; Fatal;
480 // Decode enough of pseudomain to determine whether it contains a
481 // compile-time fatal, and if so, extract the error message and line number.
482 auto entry
= reinterpret_cast<const Op
*>(getMain()->getEntry());
484 // String <id>; Fatal;
486 if (*pc
!= Op::String
) {
490 // String <id>; Fatal;
494 // String <id>; Fatal;
496 if (*pc
!= Op::Fatal
) {
499 msg
= lookupLitstrId(id
);
500 line
= getLineNumber(Offset(pc
- entry
));
504 bool Unit::parseFatal(const StringData
*& msg
, int& line
) const {
505 if (!compileTimeFatal(msg
, line
)) {
509 auto pc
= getMain()->getEntry();
511 // two opcodes + String's ID
512 pc
+= sizeof(Id
) + 2;
514 auto kind_char
= *pc
;
515 return kind_char
== static_cast<uint8_t>(FatalOp::Parse
);
520 explicit FrameRestore(const PreClass
* preClass
) {
521 auto const ec
= g_context
.getNoCheck();
522 ActRec
* fp
= ec
->getFP();
525 if (ec
->m_stack
.top() &&
526 (!fp
|| fp
->m_func
->unit() != preClass
->unit())) {
527 m_top
= ec
->m_stack
.top();
532 we can be called from Unit::merge, which hasnt yet setup
533 the frame (because often it doesnt need to).
534 Set up a fake frame here, in case of errors.
535 But note that mergeUnit is called for systemlib etc before the
536 stack has been setup. So dont do anything if m_stack.top()
539 ActRec
&tmp
= *ec
->m_stack
.allocA();
540 tmp
.m_savedRbp
= (uint64_t)fp
;
542 tmp
.m_func
= preClass
->unit()->getMain();
545 : fp
->m_func
->unit()->offsetOf(pc
) - fp
->m_func
->base();
546 tmp
.setThis(nullptr);
550 ec
->m_pc
= preClass
->unit()->at(preClass
->getOffset());
551 ec
->pushLocalsAndIterators(tmp
.m_func
);
559 auto const ec
= g_context
.getNoCheck();
561 ec
->m_stack
.top() = m_top
;
572 Class
* Unit::defClass(const PreClass
* preClass
,
573 bool failIsFatal
/* = true */) {
574 NamedEntity
* const nameList
= preClass
->namedEntity();
575 Class
* top
= nameList
->clsList();
578 * Check if there is already a name defined in this request for this
581 * Raise a fatal unless the existing class definition is identical to the
582 * one this invocation would create.
584 if (auto current
= nameList
->getCachedTypeAlias()) {
585 FrameRestore
fr(preClass
);
586 raise_error("Cannot declare class with the same name (%s) as an "
587 "existing type", current
->name
->data());
591 // If there was already a class declared with DefClass, check if
593 if (Class
* cls
= nameList
->getCachedClass()) {
594 if (cls
->preClass() != preClass
) {
596 FrameRestore
fr(preClass
);
597 raise_error("Class already declared: %s", preClass
->name()->data());
604 // Get a compatible Class, and add it to the list of defined classes.
605 Class
* parent
= nullptr;
607 // Search for a compatible extant class. Searching from most to least
608 // recently created may have better locality than alternative search orders.
609 // In addition, its the only simple way to make this work lock free...
610 for (Class
* class_
= top
; class_
!= nullptr; ) {
612 class_
= class_
->m_nextClass
;
613 if (cur
->preClass() != preClass
) continue;
614 Class::Avail avail
= cur
->avail(parent
, failIsFatal
/*tryAutoload*/);
615 if (LIKELY(avail
== Class::Avail::True
)) {
617 DEBUGGER_ATTACHED_ONLY(phpDebuggerDefClassHook(cur
));
620 if (avail
== Class::Avail::Fail
) {
622 FrameRestore
fr(preClass
);
623 raise_error("unknown class %s", parent
->name()->data());
627 assert(avail
== Class::Avail::False
);
630 // Create a new class.
631 if (!parent
&& preClass
->parent()->size() != 0) {
632 parent
= Unit::getClass(preClass
->parent(), failIsFatal
);
633 if (parent
== nullptr) {
635 FrameRestore
fr(preClass
);
636 raise_error("unknown class %s", preClass
->parent()->data());
644 FrameRestore
fr(preClass
);
645 newClass
= Class::newClass(const_cast<PreClass
*>(preClass
), parent
);
647 Lock
l(Unit::s_classesMutex
);
649 if (UNLIKELY(top
!= nameList
->clsList())) {
650 top
= nameList
->clsList();
654 bool const isPersistent
=
655 (!SystemLib::s_inited
|| RuntimeOption::RepoAuthoritative
) &&
656 newClass
->verifyPersistent();
657 nameList
->m_cachedClass
.bind(
658 isPersistent
? RDS::Mode::Persistent
661 newClass
->setClassHandle(nameList
->m_cachedClass
);
662 newClass
.get()->incAtomicCount();
664 if (InstanceBits::initFlag
.load(std::memory_order_acquire
)) {
665 // If the instance bitmap has already been set up, we can just
666 // initialize our new class's bits and add ourselves to the class
668 newClass
->setInstanceBits();
669 nameList
->pushClass(newClass
.get());
671 // Otherwise, we have to grab the read lock. If the map has been
672 // initialized since we checked, initialize the bits normally. If not,
673 // we must add the new class to the class list before dropping the lock
674 // to ensure its bits are initialized when the time comes.
675 ReadLock
l(InstanceBits::lock
);
676 if (InstanceBits::initFlag
.load(std::memory_order_acquire
)) {
677 newClass
->setInstanceBits();
679 nameList
->pushClass(newClass
.get());
681 if (RuntimeOption::EvalPerfDataMap
) {
682 Debug::DebugInfo::recordDataMap(
683 newClass
.get(), newClass
.get() + 1,
684 folly::format("Class-{}", preClass
->name()->data()).str());
685 Debug::DebugInfo::recordDataMap(
686 (char*)(intptr_t)nameList
->m_cachedClass
.handle(),
687 (char*)(intptr_t)nameList
->m_cachedClass
.handle() + sizeof(void*),
688 folly::format("rds+Class-{}", preClass
->name()->data()).str());
691 * call setCached after adding to the class list, otherwise the
692 * target-cache short circuit at the top could return a class
693 * which is not yet on the clsList().
695 newClass
.get()->setCached();
696 DEBUGGER_ATTACHED_ONLY(phpDebuggerDefClassHook(newClass
.get()));
697 return newClass
.get();
701 bool Unit::aliasClass(Class
* original
, const StringData
* alias
) {
702 auto const aliasNe
= Unit::GetNamedEntity(alias
);
703 aliasNe
->m_cachedClass
.bind();
705 auto const aliasClass
= aliasNe
->getCachedClass();
707 raise_warning("Cannot redeclare class %s", alias
->data());
710 aliasNe
->setCachedClass(original
);
714 void Unit::defTypeAlias(Id id
) {
715 assert(id
< m_typeAliases
.size());
716 auto thisType
= &m_typeAliases
[id
];
717 auto nameList
= GetNamedEntity(thisType
->name
);
718 const StringData
* typeName
= thisType
->value
;
721 * Check if this name already was defined as a type alias, and if so
722 * make sure it is compatible.
724 if (auto current
= nameList
->getCachedTypeAlias()) {
725 if (thisType
->kind
!= current
->kind
||
726 thisType
->nullable
!= current
->nullable
||
727 Unit::lookupClass(typeName
) != current
->klass
) {
728 raise_error("The type %s is already defined to an incompatible type",
729 thisType
->name
->data());
734 // There might also be a class with this name already.
735 if (nameList
->getCachedClass()) {
736 raise_error("The name %s is already defined as a class",
737 thisType
->name
->data());
741 // TODO(#2103214): persistent type alias support
742 if (!nameList
->m_cachedTypeAlias
.bound()) {
743 nameList
->m_cachedTypeAlias
.bind();
744 RDS::recordRds(nameList
->m_cachedTypeAlias
.handle(),
745 sizeof(TypeAliasReq
),
746 "TypeAlias", typeName
->data());
749 * If this type alias is a KindOfObject and the name on the right
750 * hand side was another type alias, we will bind the name to the
751 * other side for this request (i.e. resolve that type alias now).
753 * We need to inspect the right hand side and figure out what it was
756 * If the right hand side was a class, we need to autoload and
757 * ensure it exists at this point.
760 if (thisType
->kind
!= KindOfObject
) {
761 nameList
->setCachedTypeAlias(
762 TypeAliasReq
{ thisType
->kind
,
770 auto targetNameList
= GetNamedEntity(typeName
);
771 if (auto targetTd
= getTypeAliasWithAutoload(targetNameList
, typeName
)) {
772 nameList
->setCachedTypeAlias(
773 TypeAliasReq
{ targetTd
->kind
,
774 thisType
->nullable
|| targetTd
->nullable
,
780 if (auto klass
= Unit::loadClass(typeName
)) {
781 nameList
->setCachedTypeAlias(
782 TypeAliasReq
{ KindOfObject
,
790 raise_error("Unknown type or class %s", typeName
->data());
793 void Unit::renameFunc(const StringData
* oldName
, const StringData
* newName
) {
794 // renameFunc() should only be used by ExecutionContext::createFunction.
795 // We do a linear scan over all the functions in the unit searching for the
796 // func with a given name; in practice this is okay because the units created
797 // by create_function() will always have the function being renamed at the
799 assert(oldName
&& oldName
->isStatic());
800 assert(newName
&& newName
->isStatic());
802 for (MutableFuncRange
fr(hoistableFuncs()); !fr
.empty(); ) {
803 Func
* func
= fr
.popFront();
804 const StringData
* name
= func
->name();
806 if (name
->same(oldName
)) {
807 func
->rename(newName
);
813 Class
* Unit::loadClass(const NamedEntity
* ne
,
814 const StringData
* name
) {
816 if (LIKELY((cls
= ne
->getCachedClass()) != nullptr)) {
819 return loadMissingClass(ne
, name
);
822 Class
* Unit::loadMissingClass(const NamedEntity
* ne
,
823 const StringData
* name
) {
825 AutoloadHandler::s_instance
->invokeHandler(
826 StrNR(const_cast<StringData
*>(name
)));
827 return Unit::lookupClass(ne
);
830 Class
* Unit::getClass(const NamedEntity
* ne
,
831 const StringData
*name
, bool tryAutoload
) {
832 Class
*cls
= lookupClass(ne
);
833 if (UNLIKELY(!cls
)) {
835 return loadMissingClass(ne
, name
);
841 bool Unit::classExists(const StringData
* name
, bool autoload
, Attr typeAttrs
) {
842 Class
* cls
= Unit::getClass(name
, autoload
);
843 return cls
&& (cls
->attrs() & (AttrInterface
| AttrTrait
)) == typeAttrs
;
846 void Unit::loadFunc(const Func
*func
) {
847 assert(!func
->isMethod());
848 auto const ne
= func
->getNamedEntity();
849 auto const isPersistent
=
850 (RuntimeOption::RepoAuthoritative
|| !SystemLib::s_inited
) &&
851 (func
->attrs() & AttrPersistent
);
852 ne
->m_cachedFunc
.bind(
853 isPersistent
? RDS::Mode::Persistent
856 const_cast<Func
*>(func
)->setFuncHandle(ne
->m_cachedFunc
);
857 if (RuntimeOption::EvalPerfDataMap
) {
858 Debug::DebugInfo::recordDataMap(
859 (char*)(intptr_t)ne
->m_cachedFunc
.handle(),
860 (char*)(intptr_t)ne
->m_cachedFunc
.handle() + sizeof(void*),
861 folly::format("rds+Func-{}", func
->name()->data()).str());
865 static void mergeCns(TypedValue
& tv
, TypedValue
*value
,
867 if (LIKELY(tv
.m_type
== KindOfUninit
)) {
872 raise_notice(Strings::CONSTANT_ALREADY_DEFINED
, name
->data());
875 static SimpleMutex
unitInitLock(false /* reentrant */, RankUnitInit
);
877 void Unit::initialMerge() {
878 unitInitLock
.assertOwnedBySelf();
879 if (LIKELY(m_mergeState
== UnitMergeStateUnmerged
)) {
880 if (RuntimeOption::EvalPerfDataMap
) {
881 Debug::DebugInfo::recordDataMap(
882 this, this + 1, folly::format("Unit-{}", m_filepath
->data()).str());
885 bool needsCompact
= false;
886 m_mergeState
= UnitMergeStateMerging
;
888 bool allFuncsUnique
= RuntimeOption::RepoAuthoritative
;
889 for (MutableFuncRange
fr(nonMainFuncs()); !fr
.empty();) {
890 Func
* f
= fr
.popFront();
891 if (allFuncsUnique
) {
892 allFuncsUnique
= (f
->attrs() & AttrUnique
);
895 if (RDS::isPersistentHandle(f
->funcHandle())) {
899 if (allFuncsUnique
) state
|= UnitMergeStateUniqueFuncs
;
900 if (RuntimeOption::RepoAuthoritative
|| !SystemLib::s_inited
) {
902 * The mergeables array begins with the hoistable Func*s,
903 * followed by the (potentially) hoistable Class*s.
905 * If the Unit is merge only, it then contains enough information
906 * to simulate executing the pseudomain. Normally, this is just
907 * the Class*s that might not be hoistable. In RepoAuthoritative
908 * mode it also includes assignments of the form:
909 * $GLOBALS[string-literal] = scalar;
910 * defines of the form:
911 * define(string-literal, scalar);
914 * These cases are differentiated using the bottom 3 bits
915 * of the pointer. In the case of a define or a global,
916 * the pointer will be followed by a TypedValue representing
917 * the value being defined/assigned.
919 int ix
= m_mergeInfo
->m_firstHoistablePreClass
;
920 int end
= m_mergeInfo
->m_firstMergeablePreClass
;
922 PreClass
* pre
= (PreClass
*)m_mergeInfo
->mergeableObj(ix
++);
923 if (pre
->attrs() & AttrUnique
) {
928 ix
= m_mergeInfo
->m_firstMergeablePreClass
;
929 end
= m_mergeInfo
->m_mergeablesSize
;
931 void *obj
= m_mergeInfo
->mergeableObj(ix
);
932 UnitMergeKind k
= UnitMergeKind(uintptr_t(obj
) & 7);
934 case UnitMergeKindUniqueDefinedClass
:
935 case UnitMergeKindDone
:
937 case UnitMergeKindClass
:
938 if (((PreClass
*)obj
)->attrs() & AttrUnique
) {
942 case UnitMergeKindReqDoc
: {
943 StringData
* s
= (StringData
*)((char*)obj
- (int)k
);
944 auto const efile
= g_context
->lookupIncludeRoot(s
,
945 InclOpFlags::DocRoot
, nullptr, this);
947 Unit
* unit
= efile
->unit();
948 unit
->initialMerge();
949 m_mergeInfo
->mergeableObj(ix
) = (void*)((char*)unit
+ (int)k
);
952 case UnitMergeKindPersistentDefine
:
954 case UnitMergeKindDefine
: {
955 StringData
* s
= (StringData
*)((char*)obj
- (int)k
);
956 auto* v
= (TypedValueAux
*) m_mergeInfo
->mergeableData(ix
+ 1);
957 ix
+= sizeof(*v
) / sizeof(void*);
958 v
->rdsHandle() = makeCnsHandle(
959 s
, k
== UnitMergeKindPersistentDefine
);
960 if (k
== UnitMergeKindPersistentDefine
) {
961 mergeCns(RDS::handleToRef
<TypedValue
>(v
->rdsHandle()),
966 case UnitMergeKindGlobal
:
967 // Skip over the value of the global, embedded in mergeableData
968 ix
+= sizeof(TypedValueAux
) / sizeof(void*);
974 if (needsCompact
) state
|= UnitMergeStateNeedsCompact
;
976 m_mergeState
= UnitMergeStateMerged
| state
;
980 const Cell
* Unit::lookupCns(const StringData
* cnsName
) {
981 auto const handle
= lookupCnsHandle(cnsName
);
982 if (LIKELY(handle
!= 0)) {
983 TypedValue
& tv
= RDS::handleToRef
<TypedValue
>(handle
);
984 if (LIKELY(tv
.m_type
!= KindOfUninit
)) {
985 assert(cellIsPlausible(tv
));
988 if (UNLIKELY(tv
.m_data
.pref
!= nullptr)) {
989 ClassInfo::ConstantInfo
* ci
=
990 (ClassInfo::ConstantInfo
*)(void*)tv
.m_data
.pref
;
991 auto const tvRet
= const_cast<Variant
&>(
992 ci
->getDeferredValue()).asTypedValue();
993 assert(cellIsPlausible(*tvRet
));
994 if (LIKELY(tvRet
->m_type
!= KindOfUninit
)) {
999 if (UNLIKELY(RDS::s_constants().get() != nullptr)) {
1000 return RDS::s_constants()->nvGet(cnsName
);
1005 const Cell
* Unit::lookupPersistentCns(const StringData
* cnsName
) {
1006 auto const handle
= lookupCnsHandle(cnsName
);
1007 if (!RDS::isPersistentHandle(handle
)) return nullptr;
1008 auto const ret
= &RDS::handleToRef
<TypedValue
>(handle
);
1009 assert(cellIsPlausible(*ret
));
1013 const TypedValue
* Unit::loadCns(const StringData
* cnsName
) {
1014 auto const tv
= lookupCns(cnsName
);
1015 if (LIKELY(tv
!= nullptr)) return tv
;
1017 if (needsNSNormalization(cnsName
)) {
1018 return loadCns(normalizeNS(cnsName
));
1021 if (!AutoloadHandler::s_instance
->autoloadConstant(
1022 const_cast<StringData
*>(cnsName
))) {
1025 return lookupCns(cnsName
);
1028 bool Unit::defCns(const StringData
* cnsName
, const TypedValue
* value
,
1029 bool persistent
/* = false */) {
1030 auto const handle
= makeCnsHandle(cnsName
, persistent
);
1032 if (UNLIKELY(handle
== 0)) {
1033 if (UNLIKELY(!RDS::s_constants().get())) {
1035 * This only happens when we call define on a non
1036 * static string. Not worth presizing or otherwise
1039 RDS::s_constants() =
1040 Array::attach(MixedArray::MakeReserve(1));
1042 auto const existed
= !!RDS::s_constants()->nvGet(cnsName
);
1044 RDS::s_constants().set(StrNR(cnsName
),
1045 tvAsCVarRef(value
), true /* isKey */);
1048 raise_notice(Strings::CONSTANT_ALREADY_DEFINED
, cnsName
->data());
1051 return defCnsHelper(handle
, value
, cnsName
);
1054 uint64_t Unit::defCnsHelper(uint64_t ch
,
1055 const TypedValue
*value
,
1056 const StringData
*cnsName
) {
1057 TypedValue
* cns
= &RDS::handleToRef
<TypedValue
>(ch
);
1058 if (UNLIKELY(cns
->m_type
!= KindOfUninit
) ||
1059 UNLIKELY(cns
->m_data
.pref
!= nullptr)) {
1060 raise_notice(Strings::CONSTANT_ALREADY_DEFINED
, cnsName
->data());
1061 } else if (UNLIKELY(!tvAsCVarRef(value
).isAllowedAsConstantValue())) {
1062 raise_warning(Strings::CONSTANTS_MUST_BE_SCALAR
);
1064 Variant v
= tvAsCVarRef(value
);
1066 cns
->m_data
= v
.asTypedValue()->m_data
;
1067 cns
->m_type
= v
.asTypedValue()->m_type
;
1073 void Unit::defDynamicSystemConstant(const StringData
* cnsName
,
1075 static const bool kServer
= RuntimeOption::ServerExecutionMode();
1076 // Zend doesn't define the STD* streams in server mode so we don't either
1077 if (UNLIKELY(kServer
&&
1078 (s_stdin
.equal(cnsName
) ||
1079 s_stdout
.equal(cnsName
) ||
1080 s_stderr
.equal(cnsName
)))) {
1083 auto const handle
= makeCnsHandle(cnsName
, true);
1085 TypedValue
* cns
= &RDS::handleToRef
<TypedValue
>(handle
);
1086 assert(cns
->m_type
== KindOfUninit
);
1087 cns
->m_data
.pref
= (RefData
*)data
;
1090 static void setGlobal(StringData
* name
, TypedValue
*value
) {
1091 g_context
->m_globalVarEnv
->set(name
, value
);
1094 void Unit::merge() {
1095 if (UNLIKELY(!(m_mergeState
& UnitMergeStateMerged
))) {
1096 SimpleLock
lock(unitInitLock
);
1100 if (UNLIKELY(isDebuggerAttached())) {
1101 mergeImpl
<true>(RDS::tl_base
, m_mergeInfo
);
1103 mergeImpl
<false>(RDS::tl_base
, m_mergeInfo
);
1107 void* Unit::replaceUnit() const {
1108 if (m_mergeState
& UnitMergeStateEmpty
) return nullptr;
1109 if (isMergeOnly() &&
1110 m_mergeInfo
->m_mergeablesSize
== m_mergeInfo
->m_firstHoistableFunc
+ 1) {
1112 m_mergeInfo
->mergeableObj(m_mergeInfo
->m_firstHoistableFunc
);
1113 if (m_mergeInfo
->m_firstMergeablePreClass
==
1114 m_mergeInfo
->m_firstHoistableFunc
) {
1115 int k
= uintptr_t(obj
) & 7;
1116 if (k
!= UnitMergeKindClass
) return obj
;
1117 } else if (m_mergeInfo
->m_firstHoistablePreClass
==
1118 m_mergeInfo
->m_firstHoistableFunc
) {
1119 if (uintptr_t(obj
) & 1) {
1120 return (char*)obj
- 1 + (int)UnitMergeKindUniqueDefinedClass
;
1124 return const_cast<Unit
*>(this);
1127 size_t compactUnitMergeInfo(UnitMergeInfo
* in
, UnitMergeInfo
* out
) {
1128 Func
** it
= in
->funcHoistableBegin();
1129 Func
** fend
= in
->funcEnd();
1131 unsigned ix
, end
, oix
= 0;
1134 if (in
!= out
) memcpy(out
, in
, uintptr_t(it
) - uintptr_t(in
));
1135 iout
= out
->funcHoistableBegin();
1139 while (it
!= fend
) {
1141 if (RDS::isPersistentHandle(func
->funcHandle())) {
1149 oix
= out
->m_firstHoistablePreClass
-= delta
;
1152 ix
= in
->m_firstHoistablePreClass
;
1153 end
= in
->m_firstMergeablePreClass
;
1154 for (; ix
< end
; ++ix
) {
1155 void* obj
= in
->mergeableObj(ix
);
1156 assert((uintptr_t(obj
) & 1) == 0);
1157 PreClass
* pre
= (PreClass
*)obj
;
1158 if (pre
->attrs() & AttrUnique
) {
1159 Class
* cls
= pre
->namedEntity()->clsList();
1160 assert(cls
&& !cls
->m_nextClass
);
1161 assert(cls
->preClass() == pre
);
1162 if (RDS::isPersistentHandle(cls
->classHandle())) {
1165 out
->mergeableObj(oix
++) = (void*)(uintptr_t(cls
) | 1);
1168 out
->mergeableObj(oix
++) = obj
;
1173 out
->m_firstMergeablePreClass
= oix
;
1176 end
= in
->m_mergeablesSize
;
1178 void* obj
= in
->mergeableObj(ix
++);
1179 UnitMergeKind k
= UnitMergeKind(uintptr_t(obj
) & 7);
1181 case UnitMergeKindClass
: {
1182 PreClass
* pre
= (PreClass
*)obj
;
1183 if (pre
->attrs() & AttrUnique
) {
1184 Class
* cls
= pre
->namedEntity()->clsList();
1185 assert(cls
&& !cls
->m_nextClass
);
1186 assert(cls
->preClass() == pre
);
1187 if (RDS::isPersistentHandle(cls
->classHandle())) {
1190 out
->mergeableObj(oix
++) =
1191 (void*)(uintptr_t(cls
) | UnitMergeKindUniqueDefinedClass
);
1194 out
->mergeableObj(oix
++) = obj
;
1198 case UnitMergeKindUniqueDefinedClass
:
1201 case UnitMergeKindPersistentDefine
:
1202 delta
+= 1 + sizeof(TypedValueAux
) / sizeof(void*);
1203 ix
+= sizeof(TypedValueAux
) / sizeof(void*);
1206 case UnitMergeKindDefine
:
1207 case UnitMergeKindGlobal
:
1209 out
->mergeableObj(oix
++) = obj
;
1210 *(TypedValueAux
*)out
->mergeableData(oix
) =
1211 *(TypedValueAux
*)in
->mergeableData(ix
);
1212 oix
+= sizeof(TypedValueAux
) / sizeof(void*);
1214 ix
+= sizeof(TypedValueAux
) / sizeof(void*);
1217 case UnitMergeKindReqDoc
: {
1218 Unit
*unit
= (Unit
*)((char*)obj
- (int)k
);
1219 void *rep
= unit
->replaceUnit();
1224 out
->mergeableObj(oix
++) = obj
;
1226 out
->mergeableObj(oix
++) = rep
;
1231 case UnitMergeKindDone
:
1236 // copy the UnitMergeKindDone marker
1237 out
->mergeableObj(oix
) = in
->mergeableObj(ix
);
1238 out
->m_mergeablesSize
= oix
;
1243 template <bool debugger
>
1244 void Unit::mergeImpl(void* tcbase
, UnitMergeInfo
* mi
) {
1245 assert(m_mergeState
& UnitMergeStateMerged
);
1247 Func
** it
= mi
->funcHoistableBegin();
1248 Func
** fend
= mi
->funcEnd();
1250 if (LIKELY((m_mergeState
& UnitMergeStateUniqueFuncs
) != 0)) {
1253 assert(func
->top());
1254 getDataRef
<Func
*>(tcbase
, func
->funcHandle()) = func
;
1255 if (debugger
) phpDebuggerDefFuncHook(func
);
1256 } while (++it
!= fend
);
1260 assert(func
->top());
1261 setCachedFunc(func
, debugger
);
1262 } while (++it
!= fend
);
1266 bool redoHoistable
= false;
1267 int ix
= mi
->m_firstHoistablePreClass
;
1268 int end
= mi
->m_firstMergeablePreClass
;
1269 // iterate over all the potentially hoistable classes
1270 // with no fatals on failure
1273 // The first time this unit is merged, if the classes turn out to be all
1274 // unique and defined, we replace the PreClass*'s with the corresponding
1275 // Class*'s, with the low-order bit marked.
1276 PreClass
* pre
= (PreClass
*)mi
->mergeableObj(ix
);
1277 if (LIKELY(uintptr_t(pre
) & 1)) {
1278 Stats::inc(Stats::UnitMerge_hoistable
);
1279 Class
* cls
= (Class
*)(uintptr_t(pre
) & ~1);
1280 if (cls
->isPersistent()) {
1281 Stats::inc(Stats::UnitMerge_hoistable_persistent
);
1283 if (Stats::enabled() &&
1284 RDS::isPersistentHandle(cls
->classHandle())) {
1285 Stats::inc(Stats::UnitMerge_hoistable_persistent_cache
);
1287 if (Class
* parent
= cls
->parent()) {
1288 if (parent
->isPersistent()) {
1289 Stats::inc(Stats::UnitMerge_hoistable_persistent_parent
);
1291 if (Stats::enabled() &&
1292 RDS::isPersistentHandle(parent
->classHandle())) {
1293 Stats::inc(Stats::UnitMerge_hoistable_persistent_parent_cache
);
1295 if (UNLIKELY(!getDataRef
<Class
*>(tcbase
, parent
->classHandle()))) {
1296 redoHoistable
= true;
1300 getDataRef
<Class
*>(tcbase
, cls
->classHandle()) = cls
;
1301 if (debugger
) phpDebuggerDefClassHook(cls
);
1303 if (UNLIKELY(!defClass(pre
, false))) {
1304 redoHoistable
= true;
1307 } while (++ix
< end
);
1308 if (UNLIKELY(redoHoistable
)) {
1309 // if this unit isnt mergeOnly, we're done
1310 if (!isMergeOnly()) return;
1311 // as a special case, if all the classes are potentially
1312 // hoistable, we dont list them twice, but instead
1313 // iterate over them again
1314 // At first glance, it may seem like we could leave
1315 // the maybe-hoistable classes out of the second list
1316 // and then always reset ix to 0; but that gets this
1317 // case wrong if there's an autoloader for C, and C
1321 // class B implements I {}
1322 // class D extends C {}
1324 // because now A and D go on the maybe-hoistable list
1325 // B goes on the never hoistable list, and we
1326 // fatal trying to instantiate D before B
1327 Stats::inc(Stats::UnitMerge_redo_hoistable
);
1328 if (end
== (int)mi
->m_mergeablesSize
) {
1329 ix
= mi
->m_firstHoistablePreClass
;
1331 void* obj
= mi
->mergeableObj(ix
);
1332 if (UNLIKELY(uintptr_t(obj
) & 1)) {
1333 Class
* cls
= (Class
*)(uintptr_t(obj
) & ~1);
1334 defClass(cls
->preClass(), true);
1336 defClass((PreClass
*)obj
, true);
1338 } while (++ix
< end
);
1344 // iterate over all but the guaranteed hoistable classes
1345 // fataling if we fail.
1346 void* obj
= mi
->mergeableObj(ix
);
1347 UnitMergeKind k
= UnitMergeKind(uintptr_t(obj
) & 7);
1350 case UnitMergeKindClass
:
1352 Stats::inc(Stats::UnitMerge_mergeable
);
1353 Stats::inc(Stats::UnitMerge_mergeable_class
);
1354 defClass((PreClass
*)obj
, true);
1355 obj
= mi
->mergeableObj(++ix
);
1356 k
= UnitMergeKind(uintptr_t(obj
) & 7);
1360 case UnitMergeKindUniqueDefinedClass
:
1362 Stats::inc(Stats::UnitMerge_mergeable
);
1363 Stats::inc(Stats::UnitMerge_mergeable_unique
);
1364 Class
* other
= nullptr;
1365 Class
* cls
= (Class
*)((char*)obj
- (int)k
);
1366 if (cls
->isPersistent()) {
1367 Stats::inc(Stats::UnitMerge_mergeable_unique_persistent
);
1369 if (Stats::enabled() &&
1370 RDS::isPersistentHandle(cls
->classHandle())) {
1371 Stats::inc(Stats::UnitMerge_mergeable_unique_persistent_cache
);
1373 Class::Avail avail
= cls
->avail(other
, true);
1374 if (UNLIKELY(avail
== Class::Avail::Fail
)) {
1375 raise_error("unknown class %s", other
->name()->data());
1377 assert(avail
== Class::Avail::True
);
1378 getDataRef
<Class
*>(tcbase
, cls
->classHandle()) = cls
;
1379 if (debugger
) phpDebuggerDefClassHook(cls
);
1380 obj
= mi
->mergeableObj(++ix
);
1381 k
= UnitMergeKind(uintptr_t(obj
) & 7);
1382 } while (k
== UnitMergeKindUniqueDefinedClass
);
1385 case UnitMergeKindPersistentDefine
:
1386 // will be removed by compactUnitMergeInfo
1387 // but could be hit by other threads before
1390 ix
+= 1 + sizeof(TypedValueAux
) / sizeof(void*);
1391 obj
= mi
->mergeableObj(ix
);
1392 k
= UnitMergeKind(uintptr_t(obj
) & 7);
1393 } while (k
== UnitMergeKindDefine
);
1396 case UnitMergeKindDefine
:
1398 Stats::inc(Stats::UnitMerge_mergeable
);
1399 Stats::inc(Stats::UnitMerge_mergeable_define
);
1400 StringData
* name
= (StringData
*)((char*)obj
- (int)k
);
1401 auto* v
= (TypedValueAux
*)mi
->mergeableData(ix
+ 1);
1402 assert(v
->m_type
!= KindOfUninit
);
1403 mergeCns(getDataRef
<TypedValue
>(tcbase
, v
->rdsHandle()), v
, name
);
1404 ix
+= 1 + sizeof(*v
) / sizeof(void*);
1405 obj
= mi
->mergeableObj(ix
);
1406 k
= UnitMergeKind(uintptr_t(obj
) & 7);
1407 } while (k
== UnitMergeKindDefine
);
1410 case UnitMergeKindGlobal
:
1412 Stats::inc(Stats::UnitMerge_mergeable
);
1413 Stats::inc(Stats::UnitMerge_mergeable_global
);
1414 StringData
* name
= (StringData
*)((char*)obj
- (int)k
);
1415 auto* v
= (TypedValueAux
*)mi
->mergeableData(ix
+ 1);
1417 ix
+= 1 + sizeof(*v
) / sizeof(void*);
1418 obj
= mi
->mergeableObj(ix
);
1419 k
= UnitMergeKind(uintptr_t(obj
) & 7);
1420 } while (k
== UnitMergeKindGlobal
);
1423 case UnitMergeKindReqDoc
:
1425 Stats::inc(Stats::UnitMerge_mergeable
);
1426 Stats::inc(Stats::UnitMerge_mergeable_require
);
1427 Unit
*unit
= (Unit
*)((char*)obj
- (int)k
);
1428 unsigned char& unitLoadedFlags
=
1429 getDataRef
<unsigned char>(tcbase
, unit
->m_cacheOffset
);
1430 if (!(unitLoadedFlags
& unit
->m_cacheMask
)) {
1431 unitLoadedFlags
|= unit
->m_cacheMask
;
1432 unit
->mergeImpl
<debugger
>(tcbase
, unit
->m_mergeInfo
);
1433 if (UNLIKELY(!unit
->isMergeOnly())) {
1434 Stats::inc(Stats::PseudoMain_Reentered
);
1436 VarEnv
* ve
= nullptr;
1437 ActRec
* fp
= g_context
->m_fp
;
1439 ve
= g_context
->m_globalVarEnv
;
1441 if (fp
->hasVarEnv()) {
1444 // Nothing to do. If there is no varEnv, the enclosing
1445 // file was called by fb_autoload_map, which wants a
1449 g_context
->invokeFunc(&ret
, unit
->getMain(), init_null_variant
,
1450 nullptr, nullptr, ve
);
1451 tvRefcountedDecRef(&ret
);
1453 Stats::inc(Stats::PseudoMain_SkipDeep
);
1456 Stats::inc(Stats::PseudoMain_Guarded
);
1458 obj
= mi
->mergeableObj(++ix
);
1459 k
= UnitMergeKind(uintptr_t(obj
) & 7);
1460 } while (isMergeKindReq(k
));
1462 case UnitMergeKindDone
:
1463 Stats::inc(Stats::UnitMerge_mergeable
, -1);
1464 assert((unsigned)ix
== mi
->m_mergeablesSize
);
1465 if (UNLIKELY(m_mergeState
& UnitMergeStateNeedsCompact
)) {
1466 SimpleLock
lock(unitInitLock
);
1467 if (!(m_mergeState
& UnitMergeStateNeedsCompact
)) return;
1468 if (!redoHoistable
) {
1470 * All the classes are known to be unique, and we just got
1471 * here, so all were successfully defined. We can now go
1472 * back and convert all UnitMergeKindClass entries to
1473 * UnitMergeKindUniqueDefinedClass, and all hoistable
1474 * classes to their Class*'s instead of PreClass*'s.
1476 * We can also remove any Persistent Class/Func*'s,
1477 * and any requires of modules that are (now) empty
1479 size_t delta
= compactUnitMergeInfo(mi
, nullptr);
1480 UnitMergeInfo
* newMi
= mi
;
1482 newMi
= UnitMergeInfo::alloc(mi
->m_mergeablesSize
- delta
);
1485 * In the case where mi == newMi, there's an apparent
1486 * race here. Although we have a lock, so we're the only
1487 * ones modifying this, there could be any number of
1488 * readers. But thats ok, because it doesnt matter
1489 * whether they see the old contents or the new.
1491 compactUnitMergeInfo(mi
, newMi
);
1493 this->m_mergeInfo
= newMi
;
1494 Treadmill::deferredFree(mi
);
1495 if (isMergeOnly() &&
1496 newMi
->m_firstHoistableFunc
== newMi
->m_mergeablesSize
) {
1497 m_mergeState
|= UnitMergeStateEmpty
;
1500 assert(newMi
->m_firstMergeablePreClass
== newMi
->m_mergeablesSize
||
1503 m_mergeState
&= ~UnitMergeStateNeedsCompact
;
1507 // Normal cases should continue, KindDone returns
1512 Func
* Unit::getMain(Class
* cls
/*= NULL*/) const {
1513 if (!cls
) return *m_mergeInfo
->funcBegin();
1514 Lock
lock(s_classesMutex
);
1515 if (!m_pseudoMainCache
) {
1516 m_pseudoMainCache
= new PseudoMainCacheMap
;
1518 auto it
= m_pseudoMainCache
->find(cls
);
1519 if (it
!= m_pseudoMainCache
->end()) {
1522 Func
* f
= (*m_mergeInfo
->funcBegin())->clone(cls
);
1525 (*m_pseudoMainCache
)[cls
] = f
;
1529 SourceLocTable
Unit::getSourceLocTable() const {
1530 if (m_sourceLocTable
.size() > 0 || m_repoId
== RepoIdInvalid
) {
1531 return m_sourceLocTable
;
1533 Lock
lock(s_classesMutex
);
1534 UnitRepoProxy
& urp
= Repo::get().urp();
1535 urp
.getSourceLocTab(m_repoId
).get(m_sn
, ((Unit
*)this)->m_sourceLocTable
);
1536 return m_sourceLocTable
;
1539 LineToOffsetRangeVecMap
Unit::getLineToOffsetRangeVecMap() const {
1540 if (m_lineToOffsetRangeVecMap
.size() > 0) {
1541 return m_lineToOffsetRangeVecMap
;
1543 auto srcLoc
= this->getSourceLocTable();
1544 LineToOffsetRangeVecMap map
;
1546 for (size_t i
= 0; i
< srcLoc
.size(); ++i
) {
1547 Offset pastOff
= srcLoc
[i
].pastOffset();
1548 OffsetRange
range(baseOff
, pastOff
);
1549 auto line0
= srcLoc
[i
].val().line0
;
1550 auto line1
= srcLoc
[i
].val().line1
;
1551 for (int line
= line0
; line
<= line1
; line
++) {
1552 auto it
= map
.find(line
);
1553 if (it
!= map
.end()) {
1554 it
->second
.push_back(range
);
1556 OffsetRangeVec
v(1);
1563 const_cast<Unit
*>(this)->m_lineToOffsetRangeVecMap
= map
;
1564 return m_lineToOffsetRangeVecMap
;
1567 // This uses range lookups so offsets in the middle of instructions are
1569 int getLineNumber(const LineTable
& table
, Offset pc
) {
1570 auto const key
= LineEntry(pc
, -1);
1571 auto it
= std::upper_bound(begin(table
), end(table
), key
);
1572 if (it
!= end(table
)) {
1573 assert(pc
< it
->pastOffset());
1579 int Unit::getLineNumber(Offset pc
) const {
1580 return HPHP::getLineNumber(m_lineTable
, pc
);
1583 bool getSourceLoc(const SourceLocTable
& table
, Offset pc
, SourceLoc
& sLoc
) {
1584 SourceLocEntry
key(pc
, sLoc
);
1585 auto it
= std::upper_bound(table
.begin(), table
.end(), key
);
1586 if (it
!= table
.end()) {
1587 assert(pc
< it
->pastOffset());
1594 // Sets sLoc to the source location of the first source location
1595 // entry that contains pc in its range of source locations.
1597 bool Unit::getSourceLoc(Offset pc
, SourceLoc
& sLoc
) const {
1598 auto sourceLocTable
= getSourceLocTable();
1599 return HPHP::getSourceLoc(sourceLocTable
, pc
, sLoc
);
1602 bool Unit::getOffsetRanges(int line
, OffsetRangeVec
& offsets
) const {
1603 assert(offsets
.size() == 0);
1604 auto map
= this->getLineToOffsetRangeVecMap();
1605 auto it
= map
.find(line
);
1606 if (it
== map
.end()) return false;
1607 offsets
= it
->second
;
1611 bool Unit::getOffsetRange(Offset pc
, OffsetRange
& range
) const {
1612 LineEntry key
= LineEntry(pc
, -1);
1613 std::vector
<LineEntry
>::const_iterator it
=
1614 upper_bound(m_lineTable
.begin(), m_lineTable
.end(), key
);
1615 if (it
!= m_lineTable
.end()) {
1616 assert(pc
< it
->pastOffset());
1617 Offset base
= it
== m_lineTable
.begin() ? 0 : (it
-1)->pastOffset();
1618 range
.m_base
= base
;
1619 range
.m_past
= it
->pastOffset();
1625 const Func
* Unit::getFunc(Offset pc
) const {
1626 FuncEntry key
= FuncEntry(pc
, nullptr);
1627 auto it
= std::upper_bound(m_funcTable
.begin(), m_funcTable
.end(), key
);
1628 if (it
!= m_funcTable
.end()) {
1629 assert(pc
< it
->pastOffset());
1635 void Unit::prettyPrint(std::ostream
& out
, PrintOpts opts
) const {
1636 auto startOffset
= opts
.startOffset
!= kInvalidOffset
1637 ? opts
.startOffset
: 0;
1638 auto stopOffset
= opts
.stopOffset
!= kInvalidOffset
1639 ? opts
.stopOffset
: m_bclen
;
1641 std::map
<Offset
,const Func
*> funcMap
;
1642 for (FuncRange
fr(funcs()); !fr
.empty();) {
1643 const Func
* f
= fr
.popFront();
1644 funcMap
[f
->base()] = f
;
1646 for (PreClassPtrVec::const_iterator it
= m_preClasses
.begin();
1647 it
!= m_preClasses
.end(); ++it
) {
1648 Func
* const* methods
= (*it
)->methods();
1649 size_t const numMethods
= (*it
)->numMethods();
1650 for (size_t i
= 0; i
< numMethods
; ++i
) {
1651 funcMap
[methods
[i
]->base()] = methods
[i
];
1655 auto funcIt
= funcMap
.lower_bound(startOffset
);
1657 const auto* it
= &m_bc
[startOffset
];
1658 int prevLineNum
= -1;
1659 MetaHandle metaHand
;
1660 while (it
< &m_bc
[stopOffset
]) {
1661 assert(funcIt
== funcMap
.end() ||
1662 funcIt
->first
>= offsetOf(it
));
1663 if (opts
.showFuncs
) {
1664 if (funcIt
!= funcMap
.end() &&
1665 funcIt
->first
== offsetOf(it
)) {
1667 funcIt
->second
->prettyPrint(out
);
1672 if (opts
.showLines
) {
1673 int lineNum
= getLineNumber(offsetOf(it
));
1674 if (lineNum
!= prevLineNum
) {
1675 out
<< " // line " << lineNum
<< std::endl
;
1676 prevLineNum
= lineNum
;
1680 out
<< std::string(opts
.indentSize
, ' ')
1681 << std::setw(4) << (it
- m_bc
) << ": ";
1682 out
<< instrToString((Op
*)it
, this);
1683 if (metaHand
.findMeta(this, offsetOf(it
))) {
1685 Unit::MetaInfo info
;
1686 while (metaHand
.nextArg(info
)) {
1687 int arg
= info
.m_arg
& ~MetaInfo::VectorArg
;
1688 const char *argKind
= info
.m_arg
& MetaInfo::VectorArg
? "M" : "";
1689 switch (info
.m_kind
) {
1690 case Unit::MetaInfo::Kind::DataTypeInferred
:
1691 case Unit::MetaInfo::Kind::DataTypePredicted
:
1692 out
<< " i" << argKind
<< arg
1693 << ":t=" << tname(DataType(info
.m_data
));
1694 if (info
.m_kind
== Unit::MetaInfo::Kind::DataTypePredicted
) {
1698 case Unit::MetaInfo::Kind::Class
: {
1699 const StringData
* sd
= lookupLitstrId(info
.m_data
);
1700 out
<< " i" << argKind
<< arg
<< ":c=" << sd
->data();
1703 case Unit::MetaInfo::Kind::MVecPropClass
: {
1704 const StringData
* sd
= lookupLitstrId(info
.m_data
);
1705 out
<< " i" << argKind
<< arg
<< ":pc=" << sd
->data();
1708 case Unit::MetaInfo::Kind::GuardedThis
:
1709 out
<< " GuardedThis";
1711 case Unit::MetaInfo::Kind::GuardedCls
:
1712 out
<< " GuardedCls";
1714 case Unit::MetaInfo::Kind::None
:
1721 it
+= instrLen((Op
*)it
);
1725 std::string
Unit::toString() const {
1726 std::ostringstream ss
;
1728 for (auto& pc
: m_preClasses
) {
1729 pc
->prettyPrint(ss
);
1731 for (FuncRange
fr(funcs()); !fr
.empty();) {
1732 fr
.popFront()->prettyPrint(ss
);
1737 Func
* Unit::lookupFunc(const NamedEntity
* ne
) {
1738 return ne
->getCachedFunc();
1741 Func
* Unit::lookupFunc(const StringData
* funcName
) {
1742 const NamedEntity
* ne
= GetNamedEntity(funcName
);
1743 return ne
->getCachedFunc();
1746 Func
* Unit::loadFunc(const NamedEntity
* ne
, const StringData
* funcName
) {
1747 Func
* func
= ne
->getCachedFunc();
1748 if (LIKELY(func
!= nullptr)) return func
;
1749 if (AutoloadHandler::s_instance
->autoloadFunc(
1750 const_cast<StringData
*>(funcName
))) {
1751 func
= ne
->getCachedFunc();
1756 Func
* Unit::loadFunc(const StringData
* name
) {
1758 auto ne
= GetNamedEntity(name
, true, &normStr
);
1760 name
= normStr
.get();
1762 return loadFunc(ne
, name
);
1765 //=============================================================================
1768 UnitRepoProxy::UnitRepoProxy(Repo
& repo
)
1770 #define URP_OP(c, o) \
1771 , m_##o##Local(repo, RepoIdLocal), m_##o##Central(repo, RepoIdCentral)
1775 #define URP_OP(c, o) \
1776 m_##o[RepoIdLocal] = &m_##o##Local; \
1777 m_##o[RepoIdCentral] = &m_##o##Central;
1782 UnitRepoProxy::~UnitRepoProxy() {
1785 void UnitRepoProxy::createSchema(int repoId
, RepoTxn
& txn
) {
1787 std::stringstream ssCreate
;
1788 ssCreate
<< "CREATE TABLE " << m_repo
.table(repoId
, "Unit")
1789 << "(unitSn INTEGER PRIMARY KEY, md5 BLOB, bc BLOB,"
1790 " bc_meta BLOB, mainReturn BLOB, mergeable INTEGER,"
1791 "lines BLOB, typeAliases BLOB, UNIQUE (md5));";
1792 txn
.exec(ssCreate
.str());
1795 std::stringstream ssCreate
;
1796 ssCreate
<< "CREATE TABLE " << m_repo
.table(repoId
, "UnitLitstr")
1797 << "(unitSn INTEGER, litstrId INTEGER, litstr TEXT,"
1798 " PRIMARY KEY (unitSn, litstrId));";
1799 txn
.exec(ssCreate
.str());
1802 std::stringstream ssCreate
;
1803 ssCreate
<< "CREATE TABLE " << m_repo
.table(repoId
, "UnitArray")
1804 << "(unitSn INTEGER, arrayId INTEGER, array BLOB,"
1805 " PRIMARY KEY (unitSn, arrayId));";
1806 txn
.exec(ssCreate
.str());
1809 std::stringstream ssCreate
;
1810 ssCreate
<< "CREATE TABLE " << m_repo
.table(repoId
, "UnitMergeables")
1811 << "(unitSn INTEGER, mergeableIx INTEGER,"
1812 " mergeableKind INTEGER, mergeableId INTEGER,"
1813 " mergeableValue BLOB,"
1814 " PRIMARY KEY (unitSn, mergeableIx));";
1815 txn
.exec(ssCreate
.str());
1818 std::stringstream ssCreate
;
1819 ssCreate
<< "CREATE TABLE " << m_repo
.table(repoId
, "UnitSourceLoc")
1820 << "(unitSn INTEGER, pastOffset INTEGER, line0 INTEGER,"
1821 " char0 INTEGER, line1 INTEGER, char1 INTEGER,"
1822 " PRIMARY KEY (unitSn, pastOffset));";
1823 txn
.exec(ssCreate
.str());
1827 bool UnitRepoProxy::loadHelper(UnitEmitter
& ue
,
1828 const std::string
& name
,
1830 ue
.setFilepath(makeStaticString(name
));
1831 // Look for a repo that contains a unit with matching MD5.
1833 for (repoId
= RepoIdCount
- 1; repoId
>= 0; --repoId
) {
1834 if (!getUnit(repoId
).get(ue
, md5
)) {
1839 TRACE(3, "No repo contains '%s' (0x%016" PRIx64
"%016" PRIx64
")\n",
1840 name
.c_str(), md5
.q
[0], md5
.q
[1]);
1844 getUnitLitstrs(repoId
).get(ue
);
1845 getUnitArrays(repoId
).get(ue
);
1846 m_repo
.pcrp().getPreClasses(repoId
).get(ue
);
1847 getUnitMergeables(repoId
).get(ue
);
1848 m_repo
.frp().getFuncs(repoId
).get(ue
);
1849 } catch (RepoExc
& re
) {
1851 "Repo error loading '%s' (0x%016" PRIx64
"%016"
1852 PRIx64
") from '%s': %s\n",
1853 name
.c_str(), md5
.q
[0], md5
.q
[1], m_repo
.repoName(repoId
).c_str(),
1857 TRACE(3, "Repo loaded '%s' (0x%016" PRIx64
"%016" PRIx64
") from '%s'\n",
1858 name
.c_str(), md5
.q
[0], md5
.q
[1], m_repo
.repoName(repoId
).c_str());
1862 std::unique_ptr
<UnitEmitter
>
1863 UnitRepoProxy::loadEmitter(const std::string
& name
, const MD5
& md5
) {
1864 auto ue
= folly::make_unique
<UnitEmitter
>(md5
);
1865 if (!loadHelper(*ue
, name
, md5
)) ue
.reset();
1869 Unit
* UnitRepoProxy::load(const std::string
& name
, const MD5
& md5
) {
1870 UnitEmitter
ue(md5
);
1871 if (!loadHelper(ue
, name
, md5
)) return nullptr;
1875 void UnitRepoProxy::InsertUnitStmt
1876 ::insert(RepoTxn
& txn
, int64_t& unitSn
, const MD5
& md5
,
1877 const unsigned char* bc
, size_t bclen
,
1878 const unsigned char* bc_meta
, size_t bc_meta_len
,
1879 const TypedValue
* mainReturn
, bool mergeOnly
,
1880 const LineTable
& lines
,
1881 const std::vector
<TypeAlias
>& typeAliases
) {
1882 BlobEncoder linesBlob
;
1883 BlobEncoder typeAliasesBlob
;
1886 std::stringstream ssInsert
;
1887 ssInsert
<< "INSERT INTO " << m_repo
.table(m_repoId
, "Unit")
1888 << " VALUES(NULL, @md5, @bc, @bc_meta,"
1889 " @mainReturn, @mergeable, @lines, @typeAliases);";
1890 txn
.prepare(*this, ssInsert
.str());
1892 RepoTxnQuery
query(txn
, *this);
1893 query
.bindMd5("@md5", md5
);
1894 query
.bindBlob("@bc", (const void*)bc
, bclen
);
1895 query
.bindBlob("@bc_meta",
1896 bc_meta_len
? (const void*)bc_meta
: (const void*)"",
1898 query
.bindTypedValue("@mainReturn", *mainReturn
);
1899 query
.bindBool("@mergeable", mergeOnly
);
1900 query
.bindBlob("@lines", linesBlob(lines
), /* static */ true);
1901 query
.bindBlob("@typeAliases",
1902 typeAliasesBlob(typeAliases
), /* static */ true);
1904 unitSn
= query
.getInsertedRowid();
1907 bool UnitRepoProxy::GetUnitStmt
1908 ::get(UnitEmitter
& ue
, const MD5
& md5
) {
1910 RepoTxn
txn(m_repo
);
1912 std::stringstream ssSelect
;
1913 ssSelect
<< "SELECT unitSn,bc,bc_meta,mainReturn,mergeable,"
1914 "lines,typeAliases FROM "
1915 << m_repo
.table(m_repoId
, "Unit")
1916 << " WHERE md5 == @md5;";
1917 txn
.prepare(*this, ssSelect
.str());
1919 RepoTxnQuery
query(txn
, *this);
1920 query
.bindMd5("@md5", md5
);
1925 int64_t unitSn
; /**/ query
.getInt64(0, unitSn
);
1926 const void* bc
; size_t bclen
; /**/ query
.getBlob(1, bc
, bclen
);
1927 const void* bc_meta
; size_t bc_meta_len
; /**/ query
.getBlob(2, bc_meta
,
1929 TypedValue value
; /**/ query
.getTypedValue(3, value
);
1930 bool mergeable
; /**/ query
.getBool(4, mergeable
);
1931 BlobDecoder linesBlob
= /**/ query
.getBlob(5);
1932 BlobDecoder typeAliasesBlob
= /**/ query
.getBlob(6);
1933 ue
.setRepoId(m_repoId
);
1935 ue
.setBc((const unsigned char*)bc
, bclen
);
1936 ue
.setBcMeta((const unsigned char*)bc_meta
, bc_meta_len
);
1937 ue
.setMainReturn(&value
);
1938 ue
.setMergeOnly(mergeable
);
1944 typeAliasesBlob(ue
.m_typeAliases
);
1947 } catch (RepoExc
& re
) {
1953 void UnitRepoProxy::InsertUnitLitstrStmt
1954 ::insert(RepoTxn
& txn
, int64_t unitSn
, Id litstrId
,
1955 const StringData
* litstr
) {
1957 std::stringstream ssInsert
;
1958 ssInsert
<< "INSERT INTO " << m_repo
.table(m_repoId
, "UnitLitstr")
1959 << " VALUES(@unitSn, @litstrId, @litstr);";
1960 txn
.prepare(*this, ssInsert
.str());
1962 RepoTxnQuery
query(txn
, *this);
1963 query
.bindInt64("@unitSn", unitSn
);
1964 query
.bindId("@litstrId", litstrId
);
1965 query
.bindStaticString("@litstr", litstr
);
1969 void UnitRepoProxy::GetUnitLitstrsStmt
1970 ::get(UnitEmitter
& ue
) {
1971 RepoTxn
txn(m_repo
);
1973 std::stringstream ssSelect
;
1974 ssSelect
<< "SELECT litstrId,litstr FROM "
1975 << m_repo
.table(m_repoId
, "UnitLitstr")
1976 << " WHERE unitSn == @unitSn ORDER BY litstrId ASC;";
1977 txn
.prepare(*this, ssSelect
.str());
1979 RepoTxnQuery
query(txn
, *this);
1980 query
.bindInt64("@unitSn", ue
.sn());
1984 Id litstrId
; /**/ query
.getId(0, litstrId
);
1985 StringData
* litstr
; /**/ query
.getStaticString(1, litstr
);
1986 Id id UNUSED
= ue
.mergeUnitLitstr(litstr
);
1987 assert(id
== litstrId
);
1989 } while (!query
.done());
1993 void UnitRepoProxy::InsertUnitArrayStmt
1994 ::insert(RepoTxn
& txn
, int64_t unitSn
, Id arrayId
,
1995 const std::string
& array
) {
1997 std::stringstream ssInsert
;
1998 ssInsert
<< "INSERT INTO " << m_repo
.table(m_repoId
, "UnitArray")
1999 << " VALUES(@unitSn, @arrayId, @array);";
2000 txn
.prepare(*this, ssInsert
.str());
2002 RepoTxnQuery
query(txn
, *this);
2003 query
.bindInt64("@unitSn", unitSn
);
2004 query
.bindId("@arrayId", arrayId
);
2005 query
.bindStdString("@array", array
);
2009 void UnitRepoProxy::GetUnitArraysStmt
2010 ::get(UnitEmitter
& ue
) {
2011 RepoTxn
txn(m_repo
);
2013 std::stringstream ssSelect
;
2014 ssSelect
<< "SELECT arrayId,array FROM "
2015 << m_repo
.table(m_repoId
, "UnitArray")
2016 << " WHERE unitSn == @unitSn ORDER BY arrayId ASC;";
2017 txn
.prepare(*this, ssSelect
.str());
2019 RepoTxnQuery
query(txn
, *this);
2020 query
.bindInt64("@unitSn", ue
.sn());
2024 Id arrayId
; /**/ query
.getId(0, arrayId
);
2025 std::string key
; /**/ query
.getStdString(1, key
);
2026 Variant v
= unserialize_from_buffer(key
.data(), key
.size());
2027 Id id UNUSED
= ue
.mergeArray(v
.asArrRef().get(), key
);
2028 assert(id
== arrayId
);
2030 } while (!query
.done());
2034 void UnitRepoProxy::InsertUnitMergeableStmt
2035 ::insert(RepoTxn
& txn
, int64_t unitSn
,
2036 int ix
, UnitMergeKind kind
, Id id
,
2037 TypedValue
* value
) {
2039 std::stringstream ssInsert
;
2040 ssInsert
<< "INSERT INTO " << m_repo
.table(m_repoId
, "UnitMergeables")
2041 << " VALUES(@unitSn, @mergeableIx, @mergeableKind,"
2042 " @mergeableId, @mergeableValue);";
2043 txn
.prepare(*this, ssInsert
.str());
2046 RepoTxnQuery
query(txn
, *this);
2047 query
.bindInt64("@unitSn", unitSn
);
2048 query
.bindInt("@mergeableIx", ix
);
2049 query
.bindInt("@mergeableKind", (int)kind
);
2050 query
.bindId("@mergeableId", id
);
2052 assert(kind
== UnitMergeKindDefine
||
2053 kind
== UnitMergeKindPersistentDefine
||
2054 kind
== UnitMergeKindGlobal
);
2055 query
.bindTypedValue("@mergeableValue", *value
);
2057 assert(kind
== UnitMergeKindReqDoc
);
2058 query
.bindNull("@mergeableValue");
2063 void UnitRepoProxy::GetUnitMergeablesStmt
2064 ::get(UnitEmitter
& ue
) {
2065 RepoTxn
txn(m_repo
);
2067 std::stringstream ssSelect
;
2068 ssSelect
<< "SELECT mergeableIx,mergeableKind,mergeableId,mergeableValue"
2070 << m_repo
.table(m_repoId
, "UnitMergeables")
2071 << " WHERE unitSn == @unitSn ORDER BY mergeableIx ASC;";
2072 txn
.prepare(*this, ssSelect
.str());
2074 RepoTxnQuery
query(txn
, *this);
2075 query
.bindInt64("@unitSn", ue
.sn());
2079 int mergeableIx
; /**/ query
.getInt(0, mergeableIx
);
2080 int mergeableKind
; /**/ query
.getInt(1, mergeableKind
);
2081 Id mergeableId
; /**/ query
.getInt(2, mergeableId
);
2083 if (UNLIKELY(!RuntimeOption::RepoAuthoritative
)) {
2085 * We're using a repo generated in WholeProgram mode,
2086 * but we're not using it in RepoAuthoritative mode
2087 * (this is dodgy to start with). We're not going to
2088 * deal with requires at merge time, so drop them
2089 * here, and clear the mergeOnly flag for the unit.
2090 * The one exception is persistent constants are allowed in systemlib.
2092 if (mergeableKind
!= UnitMergeKindPersistentDefine
||
2093 SystemLib::s_inited
) {
2094 ue
.setMergeOnly(false);
2097 switch (mergeableKind
) {
2098 case UnitMergeKindReqDoc
:
2099 ue
.insertMergeableInclude(mergeableIx
,
2100 (UnitMergeKind
)mergeableKind
, mergeableId
);
2102 case UnitMergeKindPersistentDefine
:
2103 case UnitMergeKindDefine
:
2104 case UnitMergeKindGlobal
: {
2105 TypedValue mergeableValue
; /**/ query
.getTypedValue(3,
2107 ue
.insertMergeableDef(mergeableIx
, (UnitMergeKind
)mergeableKind
,
2108 mergeableId
, mergeableValue
);
2113 } while (!query
.done());
2117 void UnitRepoProxy::InsertUnitSourceLocStmt
2118 ::insert(RepoTxn
& txn
, int64_t unitSn
, Offset pastOffset
,
2119 int line0
, int char0
, int line1
, int char1
) {
2121 std::stringstream ssInsert
;
2122 ssInsert
<< "INSERT INTO " << m_repo
.table(m_repoId
, "UnitSourceLoc")
2123 << " VALUES(@unitSn, @pastOffset, @line0, @char0, @line1,"
2125 txn
.prepare(*this, ssInsert
.str());
2127 RepoTxnQuery
query(txn
, *this);
2128 query
.bindInt64("@unitSn", unitSn
);
2129 query
.bindOffset("@pastOffset", pastOffset
);
2130 query
.bindInt("@line0", line0
);
2131 query
.bindInt("@char0", char0
);
2132 query
.bindInt("@line1", line1
);
2133 query
.bindInt("@char1", char1
);
2137 bool UnitRepoProxy::GetSourceLocTabStmt
2138 ::get(int64_t unitSn
, SourceLocTable
& sourceLocTab
) {
2140 RepoTxn
txn(m_repo
);
2142 std::stringstream ssSelect
;
2143 ssSelect
<< "SELECT pastOffset,line0,char0,line1,char1 FROM "
2144 << m_repo
.table(m_repoId
, "UnitSourceLoc")
2145 << " WHERE unitSn == @unitSn"
2146 " ORDER BY pastOffset ASC;";
2147 txn
.prepare(*this, ssSelect
.str());
2149 RepoTxnQuery
query(txn
, *this);
2150 query
.bindInt64("@unitSn", unitSn
);
2157 query
.getOffset(0, pastOffset
);
2159 query
.getInt(1, sLoc
.line0
);
2160 query
.getInt(2, sLoc
.char0
);
2161 query
.getInt(3, sLoc
.line1
);
2162 query
.getInt(4, sLoc
.char1
);
2163 SourceLocEntry
entry(pastOffset
, sLoc
);
2164 sourceLocTab
.push_back(entry
);
2165 } while (!query
.done());
2167 } catch (RepoExc
& re
) {
2173 //=============================================================================
2176 UnitEmitter::UnitEmitter(const MD5
& md5
)
2177 : m_repoId(-1), m_sn(-1), m_bcmax(BCMaxInit
), m_bc((unsigned char*)malloc(BCMaxInit
)),
2178 m_bclen(0), m_bc_meta(nullptr), m_bc_meta_len(0), m_filepath(nullptr),
2179 m_md5(md5
), m_nextFuncSn(0), m_mergeOnly(false),
2180 m_allClassesHoistable(true), m_returnSeen(false) {
2181 tvWriteUninit(&m_mainReturn
);
2184 UnitEmitter::~UnitEmitter() {
2191 for (FeVec::const_iterator it
= m_fes
.begin(); it
!= m_fes
.end(); ++it
) {
2194 for (PceVec::const_iterator it
= m_pceVec
.begin(); it
!= m_pceVec
.end();
2200 void UnitEmitter::addTrivialPseudoMain() {
2202 FuncEmitter
* mfe
= getMain();
2206 mfe
->setMaxStackCells(1);
2207 mfe
->finish(bcPos(), false);
2208 recordFunction(mfe
);
2210 TypedValue mainReturn
;
2211 mainReturn
.m_data
.num
= 1;
2212 mainReturn
.m_type
= KindOfInt64
;
2213 setMainReturn(&mainReturn
);
2217 void UnitEmitter::setBc(const unsigned char* bc
, size_t bclen
) {
2221 m_bc
= (unsigned char*)malloc(bclen
);
2223 memcpy(m_bc
, bc
, bclen
);
2227 void UnitEmitter::setBcMeta(const unsigned char* bc_meta
, size_t bc_meta_len
) {
2228 assert(m_bc_meta
== nullptr);
2230 m_bc_meta
= (unsigned char*)malloc(bc_meta_len
);
2231 memcpy(m_bc_meta
, bc_meta
, bc_meta_len
);
2233 m_bc_meta_len
= bc_meta_len
;
2236 void UnitEmitter::setLines(const LineTable
& lines
) {
2237 this->m_lineTable
= lines
;
2240 Id
UnitEmitter::mergeUnitLitstr(const StringData
* litstr
) {
2241 auto it
= m_litstr2id
.find(litstr
);
2242 if (it
== m_litstr2id
.end()) {
2243 const StringData
* str
= makeStaticString(litstr
);
2244 Id id
= m_litstrs
.size();
2245 m_litstrs
.push_back(str
);
2246 m_litstr2id
[str
] = id
;
2253 Id
UnitEmitter::mergeLitstr(const StringData
* litstr
) {
2254 if (Option::WholeProgram
) {
2255 return encodeGlobalLitstrId(LitstrTable::get().mergeLitstr(litstr
));
2257 return mergeUnitLitstr(litstr
);
2260 Id
UnitEmitter::mergeArray(const ArrayData
* a
) {
2261 Variant
v(const_cast<ArrayData
*>(a
));
2262 auto key
= HHVM_FN(serialize
)(v
).toCppString();
2263 return mergeArray(a
, key
);
2266 Id
UnitEmitter::mergeArray(const ArrayData
* a
, const std::string
& key
) {
2267 ArrayIdMap::const_iterator it
= m_array2id
.find(key
);
2268 if (it
!= m_array2id
.end()) {
2271 a
= ArrayData::GetScalarArray(const_cast<ArrayData
*>(a
), key
);
2272 Id id
= m_arrays
.size();
2273 ArrayVecElm ave
= {key
, a
};
2274 m_arrays
.push_back(ave
);
2275 m_array2id
[key
] = id
;
2279 const StringData
* UnitEmitter::lookupLitstr(Id id
) const {
2280 if (isGlobalLitstrId(id
)) {
2281 return LitstrTable::get().lookupLitstrId(decodeGlobalLitstrId(id
));
2283 assert(id
< m_litstrs
.size());
2284 return m_litstrs
[id
];
2287 const ArrayData
* UnitEmitter::lookupArray(Id id
) const {
2288 assert(id
< m_arrays
.size());
2289 return m_arrays
[id
].array
;
2292 FuncEmitter
* UnitEmitter::getMain() {
2296 void UnitEmitter::initMain(int line1
, int line2
) {
2297 assert(m_fes
.size() == 0);
2298 StringData
* name
= makeStaticString("");
2299 FuncEmitter
* pseudomain
= newFuncEmitter(name
);
2300 Attr attrs
= AttrMayUseVV
;
2301 pseudomain
->init(line1
, line2
, 0, attrs
, false, name
);
2304 FuncEmitter
* UnitEmitter::newFuncEmitter(const StringData
* n
) {
2305 assert(m_fes
.size() > 0 || !strcmp(n
->data(), "")); // Pseudomain comes first.
2306 FuncEmitter
* fe
= new FuncEmitter(*this, m_nextFuncSn
++, m_fes
.size(), n
);
2307 m_fes
.push_back(fe
);
2311 void UnitEmitter::appendTopEmitter(FuncEmitter
* fe
) {
2312 fe
->setIds(m_nextFuncSn
++, m_fes
.size());
2313 m_fes
.push_back(fe
);
2316 void UnitEmitter::pushMergeableClass(PreClassEmitter
* e
) {
2317 m_mergeableStmts
.push_back(std::make_pair(UnitMergeKindClass
, e
->id()));
2320 void UnitEmitter::pushMergeableInclude(UnitMergeKind kind
,
2321 const StringData
* unitName
) {
2322 m_mergeableStmts
.push_back(
2323 std::make_pair(kind
, mergeLitstr(unitName
)));
2324 m_allClassesHoistable
= false;
2327 void UnitEmitter::insertMergeableInclude(int ix
, UnitMergeKind kind
, int id
) {
2328 assert(size_t(ix
) <= m_mergeableStmts
.size());
2329 m_mergeableStmts
.insert(m_mergeableStmts
.begin() + ix
,
2330 std::make_pair(kind
, id
));
2331 m_allClassesHoistable
= false;
2334 void UnitEmitter::pushMergeableDef(UnitMergeKind kind
,
2335 const StringData
* name
,
2336 const TypedValue
& tv
) {
2337 m_mergeableStmts
.push_back(std::make_pair(kind
, m_mergeableValues
.size()));
2338 m_mergeableValues
.push_back(std::make_pair(mergeLitstr(name
), tv
));
2339 m_allClassesHoistable
= false;
2342 void UnitEmitter::insertMergeableDef(int ix
, UnitMergeKind kind
,
2343 Id id
, const TypedValue
& tv
) {
2344 assert(size_t(ix
) <= m_mergeableStmts
.size());
2345 m_mergeableStmts
.insert(m_mergeableStmts
.begin() + ix
,
2346 std::make_pair(kind
, m_mergeableValues
.size()));
2347 m_mergeableValues
.push_back(std::make_pair(id
, tv
));
2348 m_allClassesHoistable
= false;
2351 FuncEmitter
* UnitEmitter::newMethodEmitter(const StringData
* n
,
2352 PreClassEmitter
* pce
) {
2353 return new FuncEmitter(*this, m_nextFuncSn
++, n
, pce
);
2356 PreClassEmitter
* UnitEmitter::newPreClassEmitter(const StringData
* n
,
2359 // See class.h for information about hoistability.
2360 if (hoistable
&& m_hoistablePreClassSet
.count(n
)) {
2361 hoistable
= PreClass::Mergeable
;
2364 PreClassEmitter
* pce
= new PreClassEmitter(*this, m_pceVec
.size(), n
,
2367 if (hoistable
>= PreClass::MaybeHoistable
) {
2368 m_hoistablePreClassSet
.insert(n
);
2369 if (hoistable
== PreClass::ClosureHoistable
) {
2370 // Closures should appear at the VERY top of the file, so if any class in
2371 // the same file tries to use them, they are already defined. We had a
2372 // fun race where one thread was autoloading a file, finished parsing the
2373 // class, then another thread came along and saw the class was already
2374 // loaded and ran it before the first thread had time to parse the
2376 m_hoistablePceIdList
.push_front(pce
->id());
2378 m_hoistablePceIdList
.push_back(pce
->id());
2381 m_allClassesHoistable
= false;
2383 if (hoistable
>= PreClass::Mergeable
&&
2384 hoistable
< PreClass::AlwaysHoistable
) {
2386 m_allClassesHoistable
= false;
2388 pushMergeableClass(pce
);
2391 m_pceVec
.push_back(pce
);
2395 Id
UnitEmitter::addTypeAlias(const TypeAlias
& td
) {
2396 Id id
= m_typeAliases
.size();
2397 m_typeAliases
.push_back(td
);
2401 void UnitEmitter::recordSourceLocation(const Location
* sLoc
, Offset start
) {
2402 // Some byte codes, such as for the implicit "return 0" at the end of a
2403 // a source file do not have valid source locations. This check makes
2404 // sure we don't record a (dummy) source location in this case.
2405 if (start
> 0 && sLoc
->line0
== 1 && sLoc
->char0
== 1 &&
2406 sLoc
->line1
== 1 && sLoc
->char1
== 1 && strlen(sLoc
->file
) == 0) return;
2407 SourceLoc
newLoc(*sLoc
);
2408 if (!m_sourceLocTab
.empty()) {
2409 if (m_sourceLocTab
.back().second
== newLoc
) {
2410 // Combine into the interval already at the back of the vector.
2411 assert(start
>= m_sourceLocTab
.back().first
);
2414 assert(m_sourceLocTab
.back().first
< start
&&
2415 "source location offsets must be added to UnitEmitter in "
2416 "increasing order");
2418 // First record added should be for bytecode offset zero.
2421 m_sourceLocTab
.push_back(std::make_pair(start
, newLoc
));
2424 void UnitEmitter::recordFunction(FuncEmitter
* fe
) {
2425 m_feTab
.push_back(std::make_pair(fe
->past(), fe
));
2428 Func
* UnitEmitter::newFunc(const FuncEmitter
* fe
, Unit
& unit
,
2429 Id id
, PreClass
* preClass
, int line1
, int line2
,
2430 Offset base
, Offset past
,
2431 const StringData
* name
, Attr attrs
, bool top
,
2432 const StringData
* docComment
, int numParams
,
2433 bool needsNextClonedClosure
) {
2434 Func
* f
= new (Func::allocFuncMem(name
, numParams
,
2435 needsNextClonedClosure
,
2437 Func(unit
, id
, preClass
, line1
, line2
, base
, past
, name
,
2438 attrs
, top
, docComment
, numParams
);
2443 static LineTable
createLineTable(
2444 std::vector
<std::pair
<Offset
,SourceLoc
> >& srcLoc
,
2447 for (size_t i
= 0; i
< srcLoc
.size(); ++i
) {
2448 Offset endOff
= i
< srcLoc
.size() - 1 ? srcLoc
[i
+ 1].first
: bclen
;
2449 lines
.push_back(LineEntry(endOff
, srcLoc
[i
].second
.line1
));
2454 SourceLocTable
UnitEmitter::createSourceLocTable() const {
2455 SourceLocTable locations
;
2456 for (size_t i
= 0; i
< m_sourceLocTab
.size(); ++i
) {
2457 Offset endOff
= i
< m_sourceLocTab
.size() - 1
2458 ? m_sourceLocTab
[i
+ 1].first
2460 locations
.push_back(SourceLocEntry(endOff
, m_sourceLocTab
[i
].second
));
2465 static LineToOffsetRangeVecMap
createLineToOffsetMap(
2466 std::vector
<std::pair
<Offset
,SourceLoc
> >& srcLoc
,
2468 LineToOffsetRangeVecMap map
;
2469 for (size_t i
= 0; i
< srcLoc
.size(); ++i
) {
2470 Offset baseOff
= srcLoc
[i
].first
;
2471 Offset endOff
= i
< srcLoc
.size() - 1 ? srcLoc
[i
+ 1].first
: bclen
;
2472 OffsetRange
range(baseOff
, endOff
);
2473 auto line0
= srcLoc
[i
].second
.line0
;
2474 auto line1
= srcLoc
[i
].second
.line1
;
2475 for (int line
= line0
; line
<= line1
; line
++) {
2476 auto it
= map
.find(line
);
2477 if (it
!= map
.end()) {
2478 it
->second
.push_back(range
);
2480 OffsetRangeVec
v(1);
2489 bool UnitEmitter::insert(UnitOrigin unitOrigin
, RepoTxn
& txn
) {
2490 Repo
& repo
= Repo::get();
2491 UnitRepoProxy
& urp
= repo
.urp();
2492 int repoId
= Repo::get().repoIdForNewUnit(unitOrigin
);
2493 if (repoId
== RepoIdInvalid
) {
2500 auto lines
= createLineTable(m_sourceLocTab
, m_bclen
);
2501 urp
.insertUnit(repoId
).insert(txn
, m_sn
, m_md5
, m_bc
, m_bclen
,
2502 m_bc_meta
, m_bc_meta_len
,
2503 &m_mainReturn
, m_mergeOnly
, lines
,
2507 for (unsigned i
= 0; i
< m_litstrs
.size(); ++i
) {
2508 urp
.insertUnitLitstr(repoId
).insert(txn
, usn
, i
, m_litstrs
[i
]);
2510 for (unsigned i
= 0; i
< m_arrays
.size(); ++i
) {
2511 urp
.insertUnitArray(repoId
).insert(txn
, usn
, i
, m_arrays
[i
].serialized
);
2513 for (auto it
= m_fes
.begin(); it
!= m_fes
.end(); ++it
) {
2516 for (auto it
= m_pceVec
.begin(); it
!= m_pceVec
.end(); ++it
) {
2520 for (int i
= 0, n
= m_mergeableStmts
.size(); i
< n
; i
++) {
2521 switch (m_mergeableStmts
[i
].first
) {
2522 case UnitMergeKindDone
:
2523 case UnitMergeKindUniqueDefinedClass
:
2525 case UnitMergeKindClass
: break;
2526 case UnitMergeKindReqDoc
: {
2527 urp
.insertUnitMergeable(repoId
).insert(
2529 m_mergeableStmts
[i
].first
, m_mergeableStmts
[i
].second
, nullptr);
2532 case UnitMergeKindDefine
:
2533 case UnitMergeKindPersistentDefine
:
2534 case UnitMergeKindGlobal
: {
2535 int ix
= m_mergeableStmts
[i
].second
;
2536 urp
.insertUnitMergeable(repoId
).insert(
2538 m_mergeableStmts
[i
].first
,
2539 m_mergeableValues
[ix
].first
, &m_mergeableValues
[ix
].second
);
2544 if (RuntimeOption::RepoDebugInfo
) {
2545 for (size_t i
= 0; i
< m_sourceLocTab
.size(); ++i
) {
2546 SourceLoc
& e
= m_sourceLocTab
[i
].second
;
2547 Offset endOff
= i
< m_sourceLocTab
.size() - 1
2548 ? m_sourceLocTab
[i
+ 1].first
2551 urp
.insertUnitSourceLoc(repoId
)
2552 .insert(txn
, usn
, endOff
, e
.line0
, e
.char0
, e
.line1
, e
.char1
);
2556 } catch (RepoExc
& re
) {
2557 TRACE(3, "Failed to commit '%s' (0x%016" PRIx64
"%016" PRIx64
") to '%s': %s\n",
2558 m_filepath
->data(), m_md5
.q
[0], m_md5
.q
[1],
2559 repo
.repoName(repoId
).c_str(), re
.msg().c_str());
2564 void UnitEmitter::commit(UnitOrigin unitOrigin
) {
2565 Repo
& repo
= Repo::get();
2568 bool err
= insert(unitOrigin
, txn
);
2572 } catch (RepoExc
& re
) {
2573 int repoId
= repo
.repoIdForNewUnit(unitOrigin
);
2574 if (repoId
!= RepoIdInvalid
) {
2575 TRACE(3, "Failed to commit '%s' (0x%016" PRIx64
"%016" PRIx64
") to '%s': %s\n",
2576 m_filepath
->data(), m_md5
.q
[0], m_md5
.q
[1],
2577 repo
.repoName(repoId
).c_str(), re
.msg().c_str());
2582 Unit
* UnitEmitter::create() {
2583 Unit
* u
= new Unit();
2584 u
->m_repoId
= m_repoId
;
2586 u
->m_bc
= allocateBCRegion(m_bc
, m_bclen
);
2587 u
->m_bclen
= m_bclen
;
2588 if (m_bc_meta_len
) {
2589 u
->m_bc_meta
= allocateBCRegion(m_bc_meta
, m_bc_meta_len
);
2590 u
->m_bc_meta_len
= m_bc_meta_len
;
2592 u
->m_filepath
= m_filepath
;
2593 u
->m_mainReturn
= m_mainReturn
;
2594 u
->m_mergeOnly
= m_mergeOnly
;
2596 const std::string
& dirname
= FileUtil::safe_dirname(m_filepath
->data(),
2597 m_filepath
->size());
2598 u
->m_dirpath
= makeStaticString(dirname
);
2601 for (unsigned i
= 0; i
< m_litstrs
.size(); ++i
) {
2603 np
.first
= m_litstrs
[i
];
2604 np
.second
= nullptr;
2605 u
->m_namedInfo
.push_back(np
);
2607 for (unsigned i
= 0; i
< m_arrays
.size(); ++i
) {
2608 u
->m_arrays
.push_back(m_arrays
[i
].array
);
2610 for (PceVec::const_iterator it
= m_pceVec
.begin(); it
!= m_pceVec
.end();
2612 u
->m_preClasses
.push_back(PreClassPtr((*it
)->create(*u
)));
2614 u
->m_typeAliases
= m_typeAliases
;
2616 size_t ix
= m_fes
.size() + m_hoistablePceIdList
.size();
2617 if (m_mergeOnly
&& !m_allClassesHoistable
) {
2619 for (auto it
= m_mergeableStmts
.begin();
2620 it
!= m_mergeableStmts
.end(); ++it
) {
2622 if (!RuntimeOption::RepoAuthoritative
&& SystemLib::s_inited
) {
2623 if (it
->first
!= UnitMergeKindClass
) {
2625 u
->m_mergeOnly
= false;
2628 } else switch (it
->first
) {
2629 case UnitMergeKindPersistentDefine
:
2630 case UnitMergeKindDefine
:
2631 case UnitMergeKindGlobal
:
2632 extra
+= sizeof(TypedValueAux
) / sizeof(void*);
2640 UnitMergeInfo
*mi
= UnitMergeInfo::alloc(ix
);
2641 u
->m_mergeInfo
= mi
;
2643 for (auto it
= m_fes
.begin(); it
!= m_fes
.end(); ++it
) {
2644 Func
* func
= (*it
)->create(*u
);
2646 if (!mi
->m_firstHoistableFunc
) {
2647 mi
->m_firstHoistableFunc
= ix
;
2650 assert(!mi
->m_firstHoistableFunc
);
2652 mi
->mergeableObj(ix
++) = func
;
2654 assert(u
->getMain()->isPseudoMain());
2655 if (!mi
->m_firstHoistableFunc
) {
2656 mi
->m_firstHoistableFunc
= ix
;
2658 mi
->m_firstHoistablePreClass
= ix
;
2659 assert(m_fes
.size());
2660 for (auto& id
: m_hoistablePceIdList
) {
2661 mi
->mergeableObj(ix
++) = u
->m_preClasses
[id
].get();
2663 mi
->m_firstMergeablePreClass
= ix
;
2664 if (u
->m_mergeOnly
&& !m_allClassesHoistable
) {
2665 for (auto it
= m_mergeableStmts
.begin();
2666 it
!= m_mergeableStmts
.end(); ++it
) {
2667 switch (it
->first
) {
2668 case UnitMergeKindClass
:
2669 mi
->mergeableObj(ix
++) = u
->m_preClasses
[it
->second
].get();
2671 case UnitMergeKindReqDoc
: {
2672 assert(RuntimeOption::RepoAuthoritative
);
2673 void* name
= u
->lookupLitstrId(it
->second
);
2674 mi
->mergeableObj(ix
++) = (char*)name
+ (int)it
->first
;
2677 case UnitMergeKindDefine
:
2678 case UnitMergeKindGlobal
:
2679 assert(RuntimeOption::RepoAuthoritative
);
2680 case UnitMergeKindPersistentDefine
: {
2681 void* name
= u
->lookupLitstrId(m_mergeableValues
[it
->second
].first
);
2682 mi
->mergeableObj(ix
++) = (char*)name
+ (int)it
->first
;
2683 auto& tv
= m_mergeableValues
[it
->second
].second
;
2684 auto* tva
= (TypedValueAux
*)mi
->mergeableData(ix
);
2685 tva
->m_data
= tv
.m_data
;
2686 tva
->m_type
= tv
.m_type
;
2687 // leave tva->m_aux uninitialized
2688 ix
+= sizeof(*tva
) / sizeof(void*);
2689 assert(sizeof(*tva
) % sizeof(void*) == 0);
2692 case UnitMergeKindDone
:
2693 case UnitMergeKindUniqueDefinedClass
:
2698 assert(ix
== mi
->m_mergeablesSize
);
2699 mi
->mergeableObj(ix
) = (void*)UnitMergeKindDone
;
2700 u
->m_sourceLocTable
= createSourceLocTable();
2701 if (m_lineTable
.size() == 0) {
2702 u
->m_lineTable
= createLineTable(m_sourceLocTab
, m_bclen
);
2704 u
->m_lineTable
= m_lineTable
;
2706 u
->m_lineToOffsetRangeVecMap
= createLineToOffsetMap(m_sourceLocTab
, m_bclen
);
2707 for (size_t i
= 0; i
< m_feTab
.size(); ++i
) {
2708 assert(m_feTab
[i
].second
->past() == m_feTab
[i
].first
);
2709 assert(m_fMap
.find(m_feTab
[i
].second
) != m_fMap
.end());
2710 u
->m_funcTable
.push_back(
2711 FuncEntry(m_feTab
[i
].first
, m_fMap
.find(m_feTab
[i
].second
)->second
));
2714 // Funcs can be recorded out of order when loading them from the
2715 // repo currently. So sort 'em here.
2716 std::sort(u
->m_funcTable
.begin(), u
->m_funcTable
.end());
2720 if (RuntimeOption::EvalDumpBytecode
) {
2721 // Dump human-readable bytecode.
2722 Trace::traceRelease("%s", u
->toString().c_str());
2724 if (RuntimeOption::EvalDumpHhas
&& SystemLib::s_inited
) {
2725 std::printf("%s", disassemble(u
).c_str());
2726 std::fflush(stdout
);
2730 static const bool kVerify
= debug
|| getenv("HHVM_VERIFY");
2731 static const bool kVerifyVerboseSystem
=
2732 getenv("HHVM_VERIFY_VERBOSE_SYSTEM");
2733 static const bool kVerifyVerbose
=
2734 kVerifyVerboseSystem
|| getenv("HHVM_VERIFY_VERBOSE");
2736 const bool isSystemLib
= u
->filepath()->empty() ||
2737 boost::ends_with(u
->filepath()->data(), "systemlib.php");
2738 const bool doVerify
=
2739 kVerify
|| boost::ends_with(u
->filepath()->data(), "hhas");
2741 Verifier::checkUnit(
2743 isSystemLib
? kVerifyVerboseSystem
: kVerifyVerbose
2750 ///////////////////////////////////////////////////////////////////////////////