Deshim VirtualExecutor in folly
[hiphop-php.git] / hphp / hhbbc / index.cpp
blob76e5b8aa0d6ea65d4f5e913ed6e0117c3fa4be62
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/index.h"
18 #include <algorithm>
19 #include <cstdio>
20 #include <cstdlib>
21 #include <iterator>
22 #include <map>
23 #include <memory>
24 #include <mutex>
25 #include <unordered_map>
26 #include <utility>
27 #include <vector>
29 #include <boost/dynamic_bitset.hpp>
30 #include <boost/filesystem.hpp>
32 #include <tbb/concurrent_hash_map.h>
33 #include <tbb/concurrent_unordered_map.h>
35 #include <folly/Format.h>
36 #include <folly/Hash.h>
37 #include <folly/Lazy.h>
38 #include <folly/MapUtil.h>
39 #include <folly/Memory.h>
40 #include <folly/Range.h>
41 #include <folly/SharedMutex.h>
42 #include <folly/String.h>
43 #include <folly/concurrency/ConcurrentHashMap.h>
45 #include "hphp/runtime/base/array-iterator.h"
46 #include "hphp/runtime/base/runtime-option.h"
47 #include "hphp/runtime/base/tv-comparisons.h"
48 #include "hphp/runtime/base/type-structure-helpers-defs.h"
50 #include "hphp/runtime/vm/native.h"
51 #include "hphp/runtime/vm/preclass-emitter.h"
52 #include "hphp/runtime/vm/runtime.h"
53 #include "hphp/runtime/vm/trait-method-import-data.h"
54 #include "hphp/runtime/vm/unit-util.h"
56 #include "hphp/hhbbc/analyze.h"
57 #include "hphp/hhbbc/class-util.h"
58 #include "hphp/hhbbc/context.h"
59 #include "hphp/hhbbc/func-util.h"
60 #include "hphp/hhbbc/optimize.h"
61 #include "hphp/hhbbc/options.h"
62 #include "hphp/hhbbc/options-util.h"
63 #include "hphp/hhbbc/parallel.h"
64 #include "hphp/hhbbc/representation.h"
65 #include "hphp/hhbbc/type-builtins.h"
66 #include "hphp/hhbbc/type-structure.h"
67 #include "hphp/hhbbc/type-system.h"
68 #include "hphp/hhbbc/unit-util.h"
69 #include "hphp/hhbbc/wide-func.h"
71 #include "hphp/util/assertions.h"
72 #include "hphp/util/bitset-utils.h"
73 #include "hphp/util/check-size.h"
74 #include "hphp/util/configs/eval.h"
75 #include "hphp/util/hash-set.h"
76 #include "hphp/util/lock-free-lazy.h"
77 #include "hphp/util/match.h"
79 #include "hphp/zend/zend-string.h"
81 namespace HPHP {
82 namespace HHBBC {
84 TRACE_SET_MOD(hhbbc_index);
86 //////////////////////////////////////////////////////////////////////
88 using namespace extern_worker;
89 namespace coro = folly::coro;
91 //////////////////////////////////////////////////////////////////////
93 struct ClassInfo;
94 struct ClassInfo2;
96 struct ClassGraphHasher;
98 struct AuxClassGraphs;
100 //////////////////////////////////////////////////////////////////////
102 namespace {
104 //////////////////////////////////////////////////////////////////////
106 const StaticString s_construct("__construct");
107 const StaticString s_toBoolean("__toBoolean");
108 const StaticString s_invoke("__invoke");
109 const StaticString s_Closure("Closure");
110 const StaticString s_AsyncGenerator("HH\\AsyncGenerator");
111 const StaticString s_Generator("Generator");
112 const StaticString s_Awaitable("HH\\Awaitable");
113 const StaticString s_TrivialHHVMBuiltinWrapper("TrivialHHVMBuiltinWrapper");
114 const StaticString s_Traversable("HH\\Traversable");
116 //////////////////////////////////////////////////////////////////////
118 // HHBBC consumes a LOT of memory, so we keep representation types small.
119 static_assert(CheckSize<php::Block, 24>(), "");
120 static_assert(CheckSize<php::Local, use_lowptr ? 12 : 16>(), "");
121 static_assert(CheckSize<php::Param, use_lowptr ? 64 : 96>(), "");
122 static_assert(CheckSize<php::Func, use_lowptr ? 184 : 232>(), "");
124 // Likewise, we also keep the bytecode and immediate types small.
125 static_assert(CheckSize<Bytecode, use_lowptr ? 32 : 40>(), "");
126 static_assert(CheckSize<MKey, 16>(), "");
127 static_assert(CheckSize<IterArgs, 16>(), "");
128 static_assert(CheckSize<FCallArgs, 8>(), "");
129 static_assert(CheckSize<RepoAuthType, 8>(), "");
131 //////////////////////////////////////////////////////////////////////
133 Dep operator|(Dep a, Dep b) {
134 return static_cast<Dep>(
135 static_cast<uintptr_t>(a) | static_cast<uintptr_t>(b)
139 bool has_dep(Dep m, Dep t) {
140 return static_cast<uintptr_t>(m) & static_cast<uintptr_t>(t);
144 * Maps functions to contexts that depend on information about that
145 * function, with information about the type of dependency.
147 using DepMap =
148 tbb::concurrent_hash_map<
149 DependencyContext,
150 hphp_fast_map<
151 DependencyContext,
152 Dep,
153 DependencyContextHash,
154 DependencyContextEquals
156 DependencyContextHashCompare
159 //////////////////////////////////////////////////////////////////////
162 * Each ClassInfo has a table of public static properties with these entries.
164 struct PublicSPropEntry {
165 Type inferredType{TInitCell};
166 uint32_t refinements{0};
167 bool everModified{true};
170 //////////////////////////////////////////////////////////////////////
172 * Entries in the ClassInfo method table need to track some additional
173 * information.
175 * The reason for this is that we need to record attributes of the
176 * class hierarchy.
178 * We store a lot of these, so we go to some effort to keep it as
179 * small as possible.
181 struct MethTabEntry {
182 MethTabEntry()
183 : MethTabEntry{MethRef{}, Attr{}} {}
184 explicit MethTabEntry(const php::Func& f)
185 : MethTabEntry{MethRef{f}, f.attrs} {}
186 MethTabEntry(const php::Func& f, Attr a)
187 : MethTabEntry{MethRef{f}, a} {}
188 MethTabEntry(MethRef meth, Attr a)
189 : cls{TopLevel, meth.cls}
190 , clsIdx{meth.idx}
191 , attrs{a} {}
193 MethRef meth() const { return MethRef{cls.ptr(), clsIdx}; }
194 void setMeth(MethRef m) { cls.set(cls.tag(), m.cls); clsIdx = m.idx; }
196 // There's a private method further up the class hierarchy with the
197 // same name.
198 bool hasPrivateAncestor() const { return cls.tag() & HasPrivateAncestor; }
199 // This method came from the ClassInfo that owns the MethTabEntry,
200 // or one of its used traits.
201 bool topLevel() const { return cls.tag() & TopLevel; }
202 // This method isn't overridden by methods in any regular classes.
203 bool noOverrideRegular() const { return cls.tag() & NoOverrideRegular; }
204 // First appearance of a method with this name in the hierarchy.
205 bool firstName() const { return cls.tag() & FirstName; }
207 void setHasPrivateAncestor() {
208 cls.set(Bits(cls.tag() | HasPrivateAncestor), cls.ptr());
210 void setTopLevel() {
211 cls.set(Bits(cls.tag() | TopLevel), cls.ptr());
213 void setNoOverrideRegular() {
214 cls.set(Bits(cls.tag() | NoOverrideRegular), cls.ptr());
216 void setFirstName() {
217 cls.set(Bits(cls.tag() | FirstName), cls.ptr());
220 void clearHasPrivateAncestor() {
221 cls.set(Bits(cls.tag() & ~HasPrivateAncestor), cls.ptr());
223 void clearTopLevel() {
224 cls.set(Bits(cls.tag() & ~TopLevel), cls.ptr());
226 void clearNoOverrideRegular() {
227 cls.set(Bits(cls.tag() & ~NoOverrideRegular), cls.ptr());
229 void clearFirstName() {
230 cls.set(Bits(cls.tag() & ~FirstName), cls.ptr());
233 private:
234 // Logically, a MethTabEntry stores a MethRef. However doing so
235 // makes the MethTabEntry larger, due to alignment. So instead we
236 // store the MethRef fields (which lets the clsIdx and attrs share
237 // the same 64-bits). Moreover, we can store the special bits in the
238 // cls name pointer.
239 enum Bits : uint8_t {
240 HasPrivateAncestor = (1u << 0),
241 TopLevel = (1u << 1),
242 NoOverrideRegular = (1u << 2),
243 FirstName = (1u << 3)
245 CompactTaggedPtr<const StringData, Bits> cls;
246 uint32_t clsIdx;
248 public:
249 // A method could be imported from a trait, and its attributes
250 // changed.
251 Attr attrs;
253 template <typename SerDe> void serde(SerDe& sd) {
254 if constexpr (SerDe::deserializing) {
255 SString clsname;
256 Bits bits;
257 sd(clsname)(clsIdx)(attrs)(bits);
258 cls.set(bits, clsname);
259 } else {
260 sd(cls.ptr())(clsIdx)(attrs)(cls.tag());
265 // Don't indeliberably make this larger
266 static_assert(CheckSize<MethTabEntry, 16>(), "");
268 //////////////////////////////////////////////////////////////////////
270 using ContextRetTyMap = tbb::concurrent_hash_map<
271 CallContext,
272 Index::ReturnType,
273 CallContextHashCompare
276 //////////////////////////////////////////////////////////////////////
278 template<typename Filter>
279 PropState make_unknown_propstate(const IIndex& index,
280 const php::Class& cls,
281 Filter filter) {
282 auto ret = PropState{};
283 for (auto& prop : cls.properties) {
284 if (filter(prop)) {
285 auto& elem = ret[prop.name];
286 elem.ty = adjust_type_for_prop(
287 index,
288 cls,
289 &prop.typeConstraint,
290 TCell
292 if (prop.attrs & AttrSystemInitialValue) {
293 auto initial = loosen_all(from_cell(prop.val));
294 if (!initial.subtypeOf(BUninit)) elem.ty |= initial;
296 elem.tc = &prop.typeConstraint;
297 elem.attrs = prop.attrs;
298 elem.everModified = true;
302 return ret;
305 //////////////////////////////////////////////////////////////////////
307 bool func_supports_AER(const php::Func* func) {
308 // Async functions always support async eager return, and no other
309 // functions support it yet.
310 return func->isAsync && !func->isGenerator;
313 uint32_t func_num_inout(const php::Func* func) {
314 if (!func->hasInOutArgs) return 0;
315 uint32_t count = 0;
316 for (auto& p : func->params) count += p.inout;
317 return count;
320 PrepKind func_param_prep(const php::Func* f, uint32_t paramId) {
321 auto const sz = f->params.size();
322 if (paramId >= sz) return PrepKind{TriBool::No, TriBool::No};
323 PrepKind kind;
324 kind.inOut = yesOrNo(f->params[paramId].inout);
325 kind.readonly = yesOrNo(f->params[paramId].readonly);
326 return kind;
329 uint32_t numNVArgs(const php::Func& f) {
330 uint32_t cnt = f.params.size();
331 return cnt && f.params[cnt - 1].isVariadic ? cnt - 1 : cnt;
334 //////////////////////////////////////////////////////////////////////
338 //////////////////////////////////////////////////////////////////////
340 std::string show(const ConstIndex& idx, const IIndex& index) {
341 if (auto const cls = index.lookup_class(idx.cls)) {
342 assertx(idx.idx < cls->constants.size());
343 return folly::sformat("{}::{}", idx.cls, cls->constants[idx.idx].name);
345 return show(idx);
348 //////////////////////////////////////////////////////////////////////
351 * Currently inferred information about a PHP function.
353 * Nothing in this structure can ever be untrue. The way the
354 * algorithm works, whatever is in here must be factual (even if it is
355 * not complete information), because we may deduce other facts based
356 * on it.
358 struct res::Func::FuncInfo {
359 const php::Func* func = nullptr;
361 * The best-known return type of the function, if we have any
362 * information. May be TBottom if the function is known to never
363 * return (e.g. always throws).
365 Type returnTy = TInitCell;
368 * If the function always returns the same parameter, this will be
369 * set to its id; otherwise it will be NoLocalId.
371 LocalId retParam{NoLocalId};
374 * The number of times we've refined returnTy.
376 uint32_t returnRefinements{0};
379 * Whether the function is effectFree.
381 bool effectFree{false};
384 * Bitset representing which parameters definitely don't affect the
385 * result of the function, assuming it produces one. Note that
386 * the parameter type verification does not count as a use in this context.
388 std::bitset<64> unusedParams;
391 * List of all func families this function belongs to.
393 CompactVector<FuncFamily*> families;
397 * Currently inferred information about a Hack function.
399 * Nothing in this structure can ever be untrue. The way the algorithm
400 * works, whatever is in here must be factual (even if it is not
401 * complete information), because we may deduce other facts based on
402 * it.
404 * This class mirrors the FuncInfo struct, but is produced and used by
405 * remote workers. As needed, this struct will gain more and more of
406 * FuncInfo's fields (but stored in a more remote worker friendly
407 * way). Local calculations will continue to use FuncInfo. Once
408 * everything is converted to use remote workers, this struct will
409 * subsume FuncInfo entirely (and be renamed).
411 struct FuncInfo2 {
413 * Name of this function. If this is a top level function, this will
414 * be an unique identifier. For methods, it will just be the method
415 * name.
417 SString name;
420 * The php::Func representing this function. This field is not
421 * serialized. If you wish to make use of it, it must be fixed up
422 * manually after deserialization.
424 const php::Func* func = nullptr;
427 * The best-known return type of the function, if we have any
428 * information. May be TBottom if the function is known to never
429 * return (e.g. always throws).
431 Type returnTy = TInitCell;
434 * If the function always returns the same parameter, this will be
435 * set to its id; otherwise it will be NoLocalId.
437 LocalId retParam{NoLocalId};
440 * The number of times we've refined returnTy.
442 uint32_t returnRefinements{0};
445 * Whether this function is effect-free.
447 bool effectFree{false};
450 * Bitset representing which parameters definitely don't affect the
451 * result of the function, assuming it produces one. Note that
452 * the parameter type verification does not count as a use in this context.
454 std::bitset<64> unusedParams;
457 * If we utilize a ClassGraph while resolving types, we store it
458 * here. This ensures that that ClassGraph will always be available
459 * again. This is only used for top-level functions. If this
460 * function is a method, it will instead be stored in the
461 * ClassInfo2.
463 * This is wrapped in a std::unique_ptr because needing this for a
464 * function is very rare and we want to avoid making FuncInfo2 any
465 * larger than necessary. It also helps solve a circular dependency
466 * between the types (since std::unique_ptr does not require a
467 * complete type at declaration).
469 std::unique_ptr<AuxClassGraphs> auxClassGraphs;
471 template <typename SerDe> void serde(SerDe&);
474 //////////////////////////////////////////////////////////////////////
477 * FuncInfos2 for the methods of a class which does not have a
478 * ClassInfo2 (because it's uninstantiable). Even if a class doesn't
479 * have a ClassInfo2, we might still need FuncInfo2 for it's
480 * methods. These are stored in here.
482 struct MethodsWithoutCInfo {
483 SString cls;
484 // Same order as the methods vector on the associated php::Class.
485 CompactVector<std::unique_ptr<FuncInfo2>> finfos;
486 // __invoke methods on any closures declared in this class. Same
487 // order as closures vector on the associated php::Class.
488 CompactVector<std::unique_ptr<FuncInfo2>> closureInvokes;
489 template <typename SerDe> void serde(SerDe& sd) {
490 sd(cls)
491 (finfos)
492 (closureInvokes)
497 //////////////////////////////////////////////////////////////////////
499 using FuncFamily = res::Func::FuncFamily;
500 using FuncInfo = res::Func::FuncInfo;
502 //////////////////////////////////////////////////////////////////////
505 * Sometimes function resolution can't determine which function
506 * something will call, but can restrict it to a family of functions.
508 * For example, if you want to call an abstract function on a base
509 * class with all unique derived classes, we will resolve the function
510 * to a FuncFamily that contains references to all the possible
511 * overriding-functions.
513 * In general, a FuncFamily can contain functions which are used by a
514 * regular class or not. In some contexts, we only care about the
515 * subset which are used by a regular class, and in some contexts we
516 * care about them all. To save memory, we use a single FuncFamily for
517 * both cases. The users of the FuncFamily must skip over which funcs
518 * it does not care about.
520 * Since we cache information related to the func list, if the "all"
521 * case and the "regular-only" case are potentially different, we
522 * allocated space for both possibilities. If we determine they'll
523 * always be the same, we do not. For example, if the possible func
524 * list only contains methods on regular classes, the distinction is
525 * irrelevant.
527 struct res::Func::FuncFamily {
528 // A PossibleFunc is a php::Func* with an additional bit that
529 // indicates whether that func is present on a regular class or
530 // not. This lets us skip over that func if we only care about the
531 // regular subset of the list.
532 struct PossibleFunc {
533 PossibleFunc(const php::Func* f, bool r) : m_func{r, f} {}
534 const php::Func* ptr() const { return m_func.ptr(); }
535 bool inRegular() const { return (bool)m_func.tag(); }
536 bool operator==(const PossibleFunc& o) const { return m_func == o.m_func; }
537 private:
538 CompactTaggedPtr<const php::Func, uint8_t> m_func;
540 using PFuncVec = CompactVector<PossibleFunc>;
542 // We have a lot of FuncFamilies, and most of them have the same
543 // "static" information (doesn't change as a result of
544 // analysis). So, we store unique groups of static info separately
545 // and FuncFamilies point to the same ones.
546 struct StaticInfo {
547 Optional<uint32_t> m_numInOut;
548 Optional<RuntimeCoeffects> m_requiredCoeffects;
549 Optional<CompactVector<CoeffectRule>> m_coeffectRules;
550 PrepKindVec m_paramPreps;
551 uint32_t m_minNonVariadicParams;
552 uint32_t m_maxNonVariadicParams;
553 TriBool m_isReadonlyReturn;
554 TriBool m_isReadonlyThis;
555 TriBool m_supportsAER;
556 bool m_maybeReified : 1;
557 bool m_maybeCaresAboutDynCalls : 1;
558 bool m_maybeBuiltin : 1;
560 bool operator==(const StaticInfo& o) const;
561 size_t hash() const;
564 // State in the FuncFamily which might vary depending on whether
565 // we're considering the regular subset or not.
566 struct Info {
567 LockFreeLazy<Index::ReturnType> m_returnTy;
568 const StaticInfo* m_static{nullptr};
571 FuncFamily(PFuncVec&& v, bool add) : m_v{std::move(v)}
572 { if (add) m_regular = std::make_unique<Info>(); }
574 FuncFamily(FuncFamily&&) = delete;
575 FuncFamily(const FuncFamily&) = delete;
576 FuncFamily& operator=(FuncFamily&&) = delete;
577 FuncFamily& operator=(const FuncFamily&) = delete;
579 const PFuncVec& possibleFuncs() const {
580 return m_v;
583 Info& infoFor(bool regularOnly) {
584 if (regularOnly && m_regular) return *m_regular;
585 return m_all;
587 const Info& infoFor(bool regularOnly) const {
588 if (regularOnly && m_regular) return *m_regular;
589 return m_all;
592 Info m_all;
593 // Only allocated if we determined the distinction is relevant. If
594 // this is nullptr, m_all can be used for both cases.
595 std::unique_ptr<Info> m_regular;
596 PFuncVec m_v;
599 bool FuncFamily::StaticInfo::operator==(const FuncFamily::StaticInfo& o) const {
600 return
601 std::tie(m_numInOut, m_requiredCoeffects, m_coeffectRules,
602 m_paramPreps, m_minNonVariadicParams,
603 m_maxNonVariadicParams,
604 m_isReadonlyReturn, m_isReadonlyThis, m_supportsAER,
605 m_maybeReified, m_maybeCaresAboutDynCalls,
606 m_maybeBuiltin) ==
607 std::tie(o.m_numInOut, o.m_requiredCoeffects, o.m_coeffectRules,
608 o.m_paramPreps, o.m_minNonVariadicParams,
609 o.m_maxNonVariadicParams,
610 o.m_isReadonlyReturn, o.m_isReadonlyThis, o.m_supportsAER,
611 o.m_maybeReified, o.m_maybeCaresAboutDynCalls,
612 o.m_maybeBuiltin);
615 size_t FuncFamily::StaticInfo::hash() const {
616 auto hash = folly::hash::hash_combine(
617 m_numInOut,
618 m_requiredCoeffects,
619 m_minNonVariadicParams,
620 m_maxNonVariadicParams,
621 m_isReadonlyReturn,
622 m_isReadonlyThis,
623 m_supportsAER,
624 m_maybeReified,
625 m_maybeCaresAboutDynCalls,
626 m_maybeBuiltin
628 hash = folly::hash::hash_range(
629 m_paramPreps.begin(),
630 m_paramPreps.end(),
631 hash
633 if (m_coeffectRules) {
634 hash = folly::hash::hash_range(
635 m_coeffectRules->begin(),
636 m_coeffectRules->end(),
637 hash
640 return hash;
643 //////////////////////////////////////////////////////////////////////
645 namespace {
647 struct PFuncVecHasher {
648 size_t operator()(const FuncFamily::PFuncVec& v) const {
649 return folly::hash::hash_range(
650 v.begin(),
651 v.end(),
653 [] (FuncFamily::PossibleFunc pf) {
654 return hash_int64_pair(
655 pointer_hash<const php::Func>{}(pf.ptr()),
656 pf.inRegular()
662 struct FuncFamilyPtrHasher {
663 using is_transparent = void;
664 size_t operator()(const std::unique_ptr<FuncFamily>& ff) const {
665 return PFuncVecHasher{}(ff->possibleFuncs());
667 size_t operator()(const FuncFamily::PFuncVec& pf) const {
668 return PFuncVecHasher{}(pf);
671 struct FuncFamilyPtrEquals {
672 using is_transparent = void;
673 bool operator()(const std::unique_ptr<FuncFamily>& a,
674 const std::unique_ptr<FuncFamily>& b) const {
675 return a->possibleFuncs() == b->possibleFuncs();
677 bool operator()(const FuncFamily::PFuncVec& pf,
678 const std::unique_ptr<FuncFamily>& ff) const {
679 return pf == ff->possibleFuncs();
683 struct FFStaticInfoPtrHasher {
684 using is_transparent = void;
685 size_t operator()(const std::unique_ptr<FuncFamily::StaticInfo>& i) const {
686 return i->hash();
688 size_t operator()(const FuncFamily::StaticInfo& i) const {
689 return i.hash();
692 struct FFStaticInfoPtrEquals {
693 using is_transparent = void;
694 bool operator()(const std::unique_ptr<FuncFamily::StaticInfo>& a,
695 const std::unique_ptr<FuncFamily::StaticInfo>& b) const {
696 return *a == *b;
698 bool operator()(const FuncFamily::StaticInfo& a,
699 const std::unique_ptr<FuncFamily::StaticInfo>& b) const {
700 return a == *b;
704 //////////////////////////////////////////////////////////////////////
709 * Sometimes function resolution can't determine which exact function
710 * something will call, but can restrict it to a family of functions.
712 * For example, if you want to call a function on a base class, we
713 * will resolve the function to a func family that contains references
714 * to all the possible overriding-functions.
716 * In general, a func family can contain functions which are used by a
717 * regular class or not. In some contexts, we only care about the
718 * subset which are used by a regular class, and in some contexts we
719 * care about them all. To save memory, we use a single func family
720 * for both cases. The users of the func family should only consult
721 * the subset they care about.
723 * Besides the possible functions themselves, information in common
724 * about the functions is cached. For example, return type. This
725 * avoids having to iterate over potentially very large sets of
726 * functions.
728 * This class mirrors the FuncFamily struct but is produced and used
729 * by remote workers. Once everything is converted to use remote
730 * workers, this struct will replace FuncFamily entirely (and be
731 * renamed).
733 struct FuncFamily2 {
734 // Func families don't have any inherent name, but it's convenient
735 // to have an unique id to refer to each one. We produce a SHA1 hash
736 // of all of the methods in the func family.
737 using Id = SHA1;
739 Id m_id;
740 // All methods in a func family should have the same name. However,
741 // multiple func families may have the same name (so this is not an
742 // unique identifier).
743 SString m_name;
744 // Methods used by a regular classes
745 std::vector<MethRef> m_regular;
746 // Methods used exclusively by non-regular classes, but as a private
747 // method. In some situations, these are treated as if it was on
748 // m_regular.
749 std::vector<MethRef> m_nonRegularPrivate;
750 // Methods used exclusively by non-regular classes
751 std::vector<MethRef> m_nonRegular;
753 // Information about the group of methods relevant to analysis which
754 // doesn't change (hence "static").
755 struct StaticInfo {
756 Optional<uint32_t> m_numInOut;
757 Optional<RuntimeCoeffects> m_requiredCoeffects;
758 Optional<CompactVector<CoeffectRule>> m_coeffectRules;
759 PrepKindVec m_paramPreps;
760 uint32_t m_minNonVariadicParams;
761 uint32_t m_maxNonVariadicParams;
762 TriBool m_isReadonlyReturn;
763 TriBool m_isReadonlyThis;
764 TriBool m_supportsAER;
765 bool m_maybeReified;
766 bool m_maybeCaresAboutDynCalls;
767 bool m_maybeBuiltin;
769 StaticInfo& operator|=(const StaticInfo& o) {
770 if (m_numInOut != o.m_numInOut) {
771 m_numInOut.reset();
773 if (m_requiredCoeffects != o.m_requiredCoeffects) {
774 m_requiredCoeffects.reset();
776 if (m_coeffectRules != o.m_coeffectRules) {
777 m_coeffectRules.reset();
780 if (o.m_paramPreps.size() > m_paramPreps.size()) {
781 m_paramPreps.resize(
782 o.m_paramPreps.size(),
783 PrepKind{TriBool::No, TriBool::No}
786 for (size_t i = 0; i < o.m_paramPreps.size(); ++i) {
787 m_paramPreps[i].inOut |= o.m_paramPreps[i].inOut;
788 m_paramPreps[i].readonly |= o.m_paramPreps[i].readonly;
790 for (size_t i = o.m_paramPreps.size(); i < m_paramPreps.size(); ++i) {
791 m_paramPreps[i].inOut |= TriBool::No;
792 m_paramPreps[i].readonly |= TriBool::No;
795 m_minNonVariadicParams =
796 std::min(m_minNonVariadicParams, o.m_minNonVariadicParams);
797 m_maxNonVariadicParams =
798 std::max(m_maxNonVariadicParams, o.m_maxNonVariadicParams);
799 m_isReadonlyReturn |= o.m_isReadonlyReturn;
800 m_isReadonlyThis |= o.m_isReadonlyThis;
801 m_supportsAER |= o.m_supportsAER;
802 m_maybeReified |= o.m_maybeReified;
803 m_maybeCaresAboutDynCalls |= o.m_maybeCaresAboutDynCalls;
804 m_maybeBuiltin |= o.m_maybeBuiltin;
806 return *this;
809 template <typename SerDe> void serde(SerDe& sd) {
810 sd(m_numInOut)
811 (m_requiredCoeffects)
812 (m_coeffectRules)
813 (m_paramPreps)
814 (m_minNonVariadicParams)
815 (m_maxNonVariadicParams)
816 (m_isReadonlyReturn)
817 (m_isReadonlyThis)
818 (m_supportsAER)
819 (m_maybeReified)
820 (m_maybeCaresAboutDynCalls)
821 (m_maybeBuiltin)
825 Optional<StaticInfo> m_allStatic;
826 Optional<StaticInfo> m_regularStatic;
828 const StaticInfo& infoFor(bool regular) const {
829 if (regular) {
830 assertx(m_regularStatic.has_value());
831 return *m_regularStatic;
833 return *m_allStatic;
836 template <typename SerDe> void serde(SerDe& sd) {
837 sd(m_id)
838 (m_name)
839 (m_regular)
840 (m_nonRegularPrivate)
841 (m_nonRegular)
842 (m_allStatic)
843 (m_regularStatic)
848 namespace {
850 // Func families are (usually) very small, but we have a lot of
851 // them. To reduce remote worker overhead, we bundle func families
852 // together into one blob.
853 struct FuncFamilyGroup {
854 std::vector<std::unique_ptr<FuncFamily2>> m_ffs;
855 template <typename SerDe> void serde(SerDe& sd) {
856 // Multiple func families may reuse the same class name, so we
857 // want to de-dup strings.
858 ScopedStringDataIndexer _;
859 sd(m_ffs);
863 //////////////////////////////////////////////////////////////////////
866 * A method family table entry in a compact format. Can represent a
867 * FuncFamily, a single php::Func, or emptiness. This represents the
868 * possible resolutions of a call to a method with same name. It also
869 * stores whether the entry is "complete" or "incomplete". An
870 * incomplete entry means the possible resolutions includes the
871 * possibility of the method not existing. A complete entry guarantees
872 * it has to be one of the methods. This is (right now) irrelevant for
873 * FuncFamily, but matters for php::Func, as it determines whether you
874 * can fold away the call (if it's incomplete, the call might fatal).
876 * We create a lot of these, so we use some trickery to keep it
877 * pointer sized.
879 struct FuncFamilyOrSingle {
880 FuncFamilyOrSingle() : m_ptr{Type::Empty, nullptr} {}
881 explicit FuncFamilyOrSingle(FuncFamily* ff, bool incomplete)
882 : m_ptr{incomplete ? Type::FuncFamilyIncomplete : Type::FuncFamily, ff} {}
883 FuncFamilyOrSingle(const php::Func* f, bool incomplete)
884 : m_ptr{incomplete ? Type::SingleIncomplete : Type::Single, (void*)f} {}
886 // If this represents a FuncFamily, return it (or nullptr
887 // otherwise).
888 FuncFamily* funcFamily() const {
889 return
890 (m_ptr.tag() == Type::FuncFamily ||
891 m_ptr.tag() == Type::FuncFamilyIncomplete)
892 ? (FuncFamily*)m_ptr.ptr()
893 : nullptr;
896 // If this represents a single php::Func, return it (or nullptr
897 // otherwise).
898 const php::Func* func() const {
899 return
900 (m_ptr.tag() == Type::Single || m_ptr.tag() == Type::SingleIncomplete)
901 ? (const php::Func*)m_ptr.ptr()
902 : nullptr;
905 // Return true if this entry represents nothing at all (for example,
906 // if the method is guaranteed to not exist).
907 bool isEmpty() const { return m_ptr.tag() == Type::Empty; }
909 // NB: empty entries are neither incomplete nor complete. Check
910 // isEmpty() first if that matters.
912 // Return true if this resolution includes the possibility of no
913 // method.
914 bool isIncomplete() const {
915 return
916 m_ptr.tag() == Type::FuncFamilyIncomplete ||
917 m_ptr.tag() == Type::SingleIncomplete;
919 // Return true if the method would resolve to exactly one of the
920 // possibilities.
921 bool isComplete() const {
922 return
923 m_ptr.tag() == Type::FuncFamily ||
924 m_ptr.tag() == Type::Single;
927 private:
928 enum class Type : uint8_t {
929 Empty,
930 FuncFamily,
931 FuncFamilyIncomplete,
932 Single,
933 SingleIncomplete
935 CompactTaggedPtr<void, Type> m_ptr;
938 std::string show(const FuncFamilyOrSingle& fam) {
939 if (auto const ff = fam.funcFamily()) {
940 auto const f = ff->possibleFuncs().front().ptr();
941 return folly::sformat(
942 "func-family {}::{}{}",
943 f->cls->name, f->name,
944 fam.isIncomplete() ? " (incomplete)" : ""
947 if (auto const f = fam.func()) {
948 return folly::sformat(
949 "func {}::{}{}",
950 f->cls->name, f->name,
951 fam.isIncomplete() ? " (incomplete)" : ""
954 return "empty";
958 * A method family table entry. Each entry encodes the possible
959 * resolutions of a method call on a particular class. The reason why
960 * this isn't just a func family is because we don't want to create a
961 * func family when there's only one possible method involved (this is
962 * common and if we did we'd create way more func
963 * families). Furthermore, we really want information for two
964 * different resolutions. One resolution is when we're only
965 * considering regular classes, and the other is when considering all
966 * classes. One of these resolutions can correspond to a func family
967 * and the other may not. This struct encodes all the possible cases
968 * that can occur.
970 struct FuncFamilyEntry {
971 // The equivalent of FuncFamily::StaticInfo, but only relevant for a
972 // single method (so doesn't have a FuncFamily where the StaticInfo
973 // can live). This can be derived from the method directly, but by
974 // storing it here, we don't need to send the actual methods to the
975 // workers.
976 struct MethMetadata {
977 MethMetadata() : m_requiredCoeffects{RuntimeCoeffects::none()} {}
979 PrepKindVec m_prepKinds;
980 CompactVector<CoeffectRule> m_coeffectRules;
981 uint32_t m_numInOut;
982 uint32_t m_nonVariadicParams;
983 RuntimeCoeffects m_requiredCoeffects;
984 bool m_isReadonlyReturn : 1;
985 bool m_isReadonlyThis : 1;
986 bool m_supportsAER : 1;
987 bool m_isReified : 1;
988 bool m_caresAboutDyncalls : 1;
989 bool m_builtin : 1;
991 template <typename SerDe> void serde(SerDe& sd) {
992 sd(m_prepKinds)
993 (m_coeffectRules)
994 (m_numInOut)
995 (m_nonVariadicParams)
996 (m_requiredCoeffects)
998 SERDE_BITFIELD(m_isReadonlyReturn, sd);
999 SERDE_BITFIELD(m_isReadonlyThis, sd);
1000 SERDE_BITFIELD(m_supportsAER, sd);
1001 SERDE_BITFIELD(m_isReified, sd);
1002 SERDE_BITFIELD(m_caresAboutDyncalls, sd);
1003 SERDE_BITFIELD(m_builtin, sd);
1007 // Both "regular" and "all" resolutions map to a func family. This
1008 // must always be the same func family because the func family
1009 // stores the information necessary for both cases.
1010 struct BothFF {
1011 FuncFamily2::Id m_ff;
1012 template <typename SerDe> void serde(SerDe& sd) {
1013 sd(m_ff);
1016 // The "all" resolution maps to a func family but the "regular"
1017 // resolution maps to a single method.
1018 struct FFAndSingle {
1019 FuncFamily2::Id m_ff;
1020 MethRef m_regular;
1021 // If true, m_regular is actually non-regular, but a private
1022 // method (which is sometimes treated as regular).
1023 bool m_nonRegularPrivate;
1024 template <typename SerDe> void serde(SerDe& sd) {
1025 sd(m_ff)(m_regular)(m_nonRegularPrivate);
1028 // The "all" resolution maps to a func family but the "regular"
1029 // resolution maps to nothing (for example, there's no regular
1030 // classes with that method).
1031 struct FFAndNone {
1032 FuncFamily2::Id m_ff;
1033 template <typename SerDe> void serde(SerDe& sd) {
1034 sd(m_ff);
1037 // Both the "all" and "regular" resolutions map to (the same) single
1038 // method.
1039 struct BothSingle {
1040 MethRef m_all;
1041 MethMetadata m_meta;
1042 // If true, m_all is actually non-regular, but a private method
1043 // (which is sometimes treated as regular).
1044 bool m_nonRegularPrivate;
1045 template <typename SerDe> void serde(SerDe& sd) {
1046 sd(m_all)(m_meta)(m_nonRegularPrivate);
1049 // The "all" resolution maps to a single method but the "regular"
1050 // resolution maps to nothing.
1051 struct SingleAndNone {
1052 MethRef m_all;
1053 MethMetadata m_meta;
1054 template <typename SerDe> void serde(SerDe& sd) {
1055 sd(m_all)(m_meta);
1058 // No resolutions at all.
1059 struct None {
1060 template <typename SerDe> void serde(SerDe&) {}
1063 boost::variant<
1064 BothFF, FFAndSingle, FFAndNone, BothSingle, SingleAndNone, None
1065 > m_meths{None{}};
1066 // A resolution is "incomplete" if there's a subclass which does not
1067 // contain any method with that name (not even inheriting it). If a
1068 // resolution is incomplete, it means besides the set of resolved
1069 // methods, the call might also error due to missing method. This
1070 // distinction is only important in a few limited circumstances.
1071 bool m_allIncomplete{true};
1072 bool m_regularIncomplete{true};
1073 // Whether any method in the resolution overrides a private
1074 // method. This is only of interest when building func families.
1075 bool m_privateAncestor{false};
1077 template <typename SerDe> void serde(SerDe& sd) {
1078 if constexpr (SerDe::deserializing) {
1079 m_meths = [&] () -> decltype(m_meths) {
1080 uint8_t tag;
1081 sd(tag);
1082 switch (tag) {
1083 case 0: return sd.template make<BothFF>();
1084 case 1: return sd.template make<FFAndSingle>();
1085 case 2: return sd.template make<FFAndNone>();
1086 case 3: return sd.template make<BothSingle>();
1087 case 4: return sd.template make<SingleAndNone>();
1088 case 5: return sd.template make<None>();
1089 default: always_assert(false);
1091 }();
1092 } else {
1093 match<void>(
1094 m_meths,
1095 [&] (const BothFF& e) { sd(uint8_t(0))(e); },
1096 [&] (const FFAndSingle& e) { sd(uint8_t(1))(e); },
1097 [&] (const FFAndNone& e) { sd(uint8_t(2))(e); },
1098 [&] (const BothSingle& e) { sd(uint8_t(3))(e); },
1099 [&] (const SingleAndNone& e) { sd(uint8_t(4))(e); },
1100 [&] (const None& e) { sd(uint8_t(5))(e); }
1104 sd(m_allIncomplete)
1105 (m_regularIncomplete)
1106 (m_privateAncestor)
1111 //////////////////////////////////////////////////////////////////////
1115 //////////////////////////////////////////////////////////////////////
1118 * ClassGraph is an abstraction for representing a subset of the
1119 * complete class hierarchy in a program. Every instance of ClassGraph
1120 * "points" into the hierarchy at a specific class. From an instance,
1121 * all (transitive) parents are known, and all (transitive) children
1122 * may be known.
1124 * Using a ClassGraph, one can obtain relevant information about the
1125 * class hierarchy, or perform operations between two classes (union
1126 * or intersection, for example), without having the ClassInfo
1127 * available.
1129 * One advantage of ClassGraph is that it is self
1130 * contained. Serializing an instance of ClassGraph will serialize all
1131 * information needed about the hierarchy. This means that an
1132 * extern-worker job does not need to retrieve additional metadata
1133 * before using it.
1135 struct ClassGraph {
1136 // Default constructed instances are not usable for anything (needed
1137 // for serialization). Use the below factory functions to actually
1138 // create them.
1139 ClassGraph() = default;
1141 // A ClassGraph is falsey if it has been default-constructed.
1142 explicit operator bool() const { return this_; }
1144 // Retrieve the name of this class.
1145 SString name() const;
1147 // Retrieve an optional ClassInfo/ClassInfo2 associated with this
1148 // class. A ClassGraph is not guaranteed to have a
1149 // ClassInfo/ClassInfo2, but if it does, this can be used to save a
1150 // name -> info lookup.
1151 ClassInfo* cinfo() const;
1152 ClassInfo2* cinfo2() const;
1154 // Return a class equivalent to this one without considering
1155 // non-regular classes. This might be this class, or a subclass.
1156 ClassGraph withoutNonRegular() const;
1158 // Whether the class might be a regular or non-regular class. This
1159 // check is precise if isMissing() is false.
1160 bool mightBeRegular() const;
1161 bool mightBeNonRegular() const;
1163 // Whether this class might have any regular or non-regular
1164 // subclasses (not including this class itself). This check is
1165 // precise if hasCompleteChildren() or isConservative() is true.
1166 bool mightHaveRegularSubclass() const;
1167 bool mightHaveNonRegularSubclass() const;
1169 // A "missing" ClassGraph is a special variant which represents a
1170 // class about which nothing is known. The class might not even
1171 // exist. The only valid thing to do with such a class is query its
1172 // name. The "raw" variant ignores any permission checks.
1173 bool isMissing() const;
1174 bool isMissingRaw() const;
1176 // A class might or might not have complete knowledge about its
1177 // children. If this returns true, you can perform any of the below
1178 // queries on it. If not, you can only perform queries related to
1179 // the parents (non-missing classes always have complete parent
1180 // information). The "raw" variant ignores any permission checks.
1181 bool hasCompleteChildren() const;
1182 bool hasCompleteChildrenRaw() const;
1184 // A conservative class is one which will never have complete
1185 // children. This generally means that the class has too many
1186 // subclasses to efficiently represent. We treat such classes
1187 // conservatively, except in a few cases. The "raw" variant ignores
1188 // any permission checks.
1189 bool isConservative() const;
1190 bool isConservativeRaw() const;
1192 // Whether this class is an interface, a trait, an enum, or an
1193 // abstract class. It is invalid to check these if isMissing() is
1194 // true.
1195 bool isInterface() const;
1196 bool isTrait() const;
1197 bool isEnum() const;
1198 bool isAbstract() const;
1200 // Retrieve the immediate base class of this class. If this class
1201 // doesn't have an immediate base, or if isMissing() is true, a
1202 // falsey ClassGraph is returned.
1203 ClassGraph base() const;
1205 // Retrieve the "topmost" base class of this class. This is the base
1206 // class which does not have a base class.
1207 ClassGraph topBase() const;
1209 // Retrieve all base classes of this class, including this
1210 // class. The ordering is from top-most class to this one (so this
1211 // one will be the last element). The returned classes may not have
1212 // complete child info, even if this class does.
1213 std::vector<ClassGraph> bases() const;
1215 // Retrieve all interfaces implemented by this class, either
1216 // directly, or by transitive parents. This includes this class if
1217 // it is an interface. The ordering is alphabetical. Like bases(),
1218 // the returned classes may not have complete child info, even if
1219 // this class does.
1220 std::vector<ClassGraph> interfaces() const;
1222 // Walk over all parents of this class, calling the supplied
1223 // callable with each parent. If the callable returns true, that
1224 // parent's parents will then be visited. If false, then they will
1225 // be skipped.
1226 template <typename F> void walkParents(const F&) const;
1228 // Retrieve all children of this class (including this class
1229 // itself). This is only valid to call if hasCompleteChildren() is
1230 // true. NB: Being on a class' children list does not necessarily
1231 // mean that it has a "is-a" relationship. Namely, classes on a
1232 // trait's children list are not instances of the trait itself.
1233 std::vector<ClassGraph> children() const;
1235 // Retrieve the interfaces implemented by this class directly.
1236 std::vector<ClassGraph> declInterfaces() const {
1237 return directParents(FlagInterface);
1239 // Retrieve the set of non-flattened traits used by this class
1240 // directly. Any traits flattened into this class will not appear.
1241 std::vector<ClassGraph> usedTraits() const {
1242 return directParents(FlagTrait);
1245 // Retrieve the direct parents of this class (all of the classes for
1246 // which this class is a direct child).
1247 std::vector<ClassGraph> directParents() const;
1249 // Returns true if this class is a child of the other class.
1250 bool isChildOf(ClassGraph) const;
1252 // Retrieve the set of classes which *might* be equivalent to this
1253 // class when ignoring non-regular classes. This does not include
1254 // subclasses of this class.
1255 std::vector<ClassGraph> candidateRegOnlyEquivs() const;
1257 // Subtype checks
1258 bool exactSubtypeOfExact(ClassGraph, bool nonRegL, bool nonRegR) const;
1259 bool exactSubtypeOf(ClassGraph, bool nonRegL, bool nonRegR) const;
1260 bool subSubtypeOf(ClassGraph, bool nonRegL, bool nonRegR) const;
1262 // Could-be checks
1263 bool exactCouldBeExact(ClassGraph, bool nonRegL, bool nonRegR) const;
1264 bool exactCouldBe(ClassGraph, bool nonRegL, bool nonRegR) const;
1265 bool subCouldBe(ClassGraph, bool nonRegL, bool nonRegR) const;
1267 // "Ensures" that this ClassGraph will be present (with the
1268 // requested information) for subsequent analysis rounds. If these
1269 // functions return false, you must treat this ClassGraph as if it's
1270 // missing!
1271 [[nodiscard]] bool ensure() const;
1272 [[nodiscard]] bool ensureWithChildren() const;
1273 [[nodiscard]] bool ensureCInfo() const;
1275 // Check if you're allowed to use this ClassGraph's knowledge (and
1276 // child info if specified). If this returns false, you must treat
1277 // the ClassGraph as if it was missing.
1278 bool allowed(bool children) const;
1280 // Used when building ClassGraphs initially.
1281 void setClosureBase();
1282 void setComplete();
1283 void setBase(ClassGraph);
1284 void addParent(ClassGraph);
1285 void flattenTraitInto(ClassGraph);
1286 void setCInfo(ClassInfo&);
1287 void setRegOnlyEquivs() const;
1288 void finalizeParents();
1289 void reset();
1291 // ClassGraphs are ordered by their name alphabetically.
1292 bool operator==(ClassGraph h) const { return this_ == h.this_; }
1293 bool operator!=(ClassGraph h) const { return this_ != h.this_; }
1294 bool operator<(ClassGraph h) const;
1296 // Create a new ClassGraph corresponding to the given php::Class.
1297 // This function will assert if a ClassGraph with the php::Class'
1298 // name already exists.
1299 static ClassGraph create(const php::Class&);
1300 // Retrieve a ClassGraph with the given name, asserting if one
1301 // doesn't exist.
1302 static ClassGraph get(SString);
1303 // Retrieve a ClassGraph with the given name. If one doesn't already
1304 // existing, one will be created as if by calling getMissing().
1305 static ClassGraph getOrCreate(SString);
1306 // Retrieve a "missing" ClassGraph with the given name, asserting if
1307 // a non-missing one already exists. This is mainly used for tests.
1308 static ClassGraph getMissing(SString);
1310 // Before using ClassGraph, it must be initialized (particularly
1311 // before deserializing any). initConcurrent() must be used if any
1312 // deserialization with be performed in multiple threads
1313 // concurrently.
1314 static void init();
1315 static void initConcurrent();
1316 // If initConcurrent() was used, this must be used when
1317 // deserialization is done and perform querying any ClassGraphs.
1318 static void stopConcurrent();
1319 // Called to clean up memory used by ClassGraph framework.
1320 static void destroy();
1322 // Set/clear an AnalysisIndex to use for calls to ensure() (and
1323 // family).
1324 static void setAnalysisIndex(AnalysisIndex::IndexData&);
1325 static void clearAnalysisIndex();
1327 template <typename SerDe, typename T> void serde(SerDe&, T, bool = false);
1329 // When serializing multiple ClassGraphs, this can be declared
1330 // before serializing any of them, to allow for the serialization to
1331 // share common state and take up less space.
1332 struct ScopedSerdeState;
1334 friend struct ClassGraphHasher;
1336 private:
1337 struct Node;
1338 struct SerdeState;
1339 struct Table;
1341 using NodeSet = hphp_fast_set<Node*>;
1342 template <typename T> using NodeMap = hphp_fast_map<Node*, T>;
1343 using NodeVec = TinyVector<Node*, 4>;
1345 struct NodeIdxSet;
1346 struct TLNodeIdxSet;
1348 struct SmallBitset;
1349 struct LargeBitset;
1350 template <typename> struct ParentTracker;
1352 enum Flags : std::uint16_t {
1353 FlagNone = 0,
1354 FlagInterface = (1 << 0),
1355 FlagTrait = (1 << 1),
1356 FlagAbstract = (1 << 2),
1357 FlagEnum = (1 << 3),
1358 FlagCInfo2 = (1 << 4),
1359 FlagRegSub = (1 << 5),
1360 FlagNonRegSub = (1 << 6),
1361 FlagWait = (1 << 7),
1362 FlagChildren = (1 << 8),
1363 FlagConservative = (1 << 9),
1364 FlagMissing = (1 << 10)
1367 // These are only set at runtime, so shouldn't be serialized in a
1368 // Node.
1369 static constexpr Flags kSerializable =
1370 (Flags)~(FlagCInfo2 | FlagRegSub | FlagNonRegSub |
1371 FlagWait | FlagChildren | FlagConservative);
1373 // Iterating through parents or children can result in one of three
1374 // different outcomes:
1375 enum class Action {
1376 Continue, // Keep iterating into any children/parents
1377 Stop, // Stop iteration entirely
1378 Skip // Continue iteration, but skip over any children/parents of
1379 // this class
1382 std::vector<ClassGraph> directParents(Flags) const;
1384 bool storeAuxs(AnalysisIndex::IndexData&, bool) const;
1385 bool onAuxs(AnalysisIndex::IndexData&, bool) const;
1387 static Table& table();
1389 static NodeVec combine(const NodeVec&, const NodeVec&,
1390 bool, bool, bool, bool);
1391 static NodeVec intersect(const NodeVec&, const NodeVec&,
1392 bool, bool, bool&);
1393 static NodeVec removeNonReg(const NodeVec&);
1394 static bool couldBeIsect(const NodeVec&, const NodeVec&, bool, bool);
1395 static NodeVec canonicalize(const NodeSet&, bool);
1397 template <typename F>
1398 static void enumerateIsectMembers(const NodeVec&, bool,
1399 const F&, bool = false);
1401 static std::pair<NodeSet, NodeSet> calcSubclassOfSplit(Node&);
1402 static NodeSet calcSubclassOf(Node&);
1403 static Node* calcRegOnlyEquiv(Node&, const NodeSet&);
1405 // Rank nodes, optionally taking into account permission
1406 // information.
1407 template <bool> static bool betterNode(Node*, Node*);
1409 template <typename F>
1410 static Action forEachParent(Node&, const F&, NodeIdxSet&);
1411 template <typename F>
1412 static Action forEachParent(Node&, const F&);
1413 template <typename F>
1414 static Action forEachParentImpl(Node&, const F&, NodeIdxSet*, bool);
1416 template <typename F>
1417 static Action forEachChild(Node&, const F&, NodeIdxSet&);
1418 template <typename F>
1419 static Action forEachChild(Node&, const F&);
1420 template <typename F>
1421 static Action forEachChildImpl(Node&, const F&, NodeIdxSet*, bool);
1423 template <typename F, typename F2, typename T>
1424 static T foldParents(Node& n, const F& f, const F2& f2, NodeMap<T>& m) {
1425 return foldParentsImpl(n, f, f2, m, true);
1427 template <typename F, typename F2, typename T>
1428 static T foldParentsImpl(Node&, const F&, const F2&, NodeMap<T>&, bool);
1430 static bool findParent(Node&, Node&, NodeIdxSet&);
1431 static bool findParent(Node&, Node&);
1433 static NodeSet allParents(Node&);
1435 struct LockedSerdeImpl;
1436 struct UnlockedSerdeImpl;
1438 template <typename SerDe> static void encodeName(SerDe&, SString);
1439 template <typename SerDe> static SString decodeName(SerDe&);
1441 template <typename SerDe, typename Impl, typename T>
1442 void serdeImpl(SerDe&, const Impl&, T, bool);
1444 template <typename SerDe, typename Impl>
1445 static void deserBlock(SerDe&, const Impl&);
1446 template <typename SerDe> static size_t serDownward(SerDe&, Node&);
1447 template <typename SerDe> static bool serUpward(SerDe&, Node&);
1449 template <typename Impl>
1450 static std::pair<Flags, Optional<size_t>> setCompleteImpl(const Impl&, Node&);
1452 template <typename Impl>
1453 static void setConservative(const Impl&, Node&, bool, bool);
1455 static std::unique_ptr<Table> g_table;
1456 static __thread SerdeState* tl_serde_state;
1458 friend struct res::Class;
1460 explicit ClassGraph(Node* n) : this_{n} {}
1462 Node* this_{nullptr};
1465 std::unique_ptr<ClassGraph::Table> ClassGraph::g_table{nullptr};
1466 __thread ClassGraph::SerdeState* ClassGraph::tl_serde_state{nullptr};
1468 struct ClassGraphHasher {
1469 size_t operator()(ClassGraph g) const {
1470 return pointer_hash<ClassGraph::Node>{}(g.this_);
1474 // Node on the graph:
1475 struct ClassGraph::Node {
1476 // The flags are stored along with any ClassInfo to save memory.
1477 using CIAndFlags = CompactTaggedPtr<void, Flags>;
1479 SString name{nullptr};
1480 // Atomic because they might be manipulated from multiple threads
1481 // during deserialization.
1482 std::atomic<CIAndFlags::Opaque> ci{CIAndFlags{}.getOpaque()};
1483 // Direct (not transitive) parents and children of this node.
1484 CompactVector<Node*> parents;
1485 CompactVector<Node*> children;
1487 // This information is lazily cached (and is not serialized).
1488 struct NonRegularInfo {
1489 NodeSet subclassOf;
1490 Node* regOnlyEquiv{nullptr};
1492 LockFreeLazyPtr<NonRegularInfo> nonRegInfo;
1493 LockFreeLazyPtr<NonRegularInfo> nonRegInfoDisallow;
1495 // Unique sequential id assigned to every node. Used for NodeIdxSet.
1496 using Idx = uint32_t;
1497 Idx idx{0};
1499 Flags flags() const { return CIAndFlags{ci.load()}.tag(); }
1500 ClassInfo* cinfo() const {
1501 CIAndFlags cif{ci.load()};
1502 if (cif.tag() & FlagCInfo2) return nullptr;
1503 return (ClassInfo*)cif.ptr();
1505 ClassInfo2* cinfo2() const {
1506 CIAndFlags cif{ci.load()};
1507 if (!(cif.tag() & FlagCInfo2)) return nullptr;
1508 return (ClassInfo2*)cif.ptr();
1510 void* rawPtr() const {
1511 CIAndFlags cif{ci.load()};
1512 return cif.ptr();
1515 bool isBase() const { return !(flags() & (FlagInterface | FlagTrait)); }
1516 bool isRegular() const {
1517 return !(flags() & (FlagInterface | FlagTrait | FlagAbstract | FlagEnum));
1519 bool isTrait() const { return flags() & FlagTrait; }
1520 bool isInterface() const { return flags() & FlagInterface; }
1521 bool isEnum() const { return flags() & FlagEnum; }
1522 bool isAbstract() const { return flags() & FlagAbstract; }
1523 bool isMissing() const { return flags() & FlagMissing; }
1524 bool hasCompleteChildren() const { return flags() & FlagChildren; }
1525 bool isConservative() const { return flags() & FlagConservative; }
1527 bool hasRegularSubclass() const { return flags() & FlagRegSub; }
1528 bool hasNonRegularSubclass() const { return flags() & FlagNonRegSub; }
1530 const NonRegularInfo& nonRegularInfo();
1532 // NB: These aren't thread-safe, so don't use them during concurrent
1533 // deserialization.
1534 void setFlags(Flags f, Flags r = FlagNone) {
1535 CIAndFlags old{ci.load()};
1536 old.set((Flags)((old.tag() | f) & ~r), old.ptr());
1537 ci.store(old.getOpaque());
1539 void setCInfo(ClassInfo& c) {
1540 CIAndFlags old{ci.load()};
1541 old.set((Flags)(old.tag() & ~FlagCInfo2), &c);
1542 ci.store(old.getOpaque());
1544 void setCInfo(ClassInfo2& c) {
1545 CIAndFlags old{ci.load()};
1546 old.set((Flags)(old.tag() | FlagCInfo2), &c);
1547 ci.store(old.getOpaque());
1550 struct Compare {
1551 bool operator()(const Node* a, const Node* b) const {
1552 assertx(a->name);
1553 assertx(b->name);
1554 return string_data_lt_type{}(a->name, b->name);
1559 // Efficient set of ClassGraph::Nodes.
1560 struct ClassGraph::NodeIdxSet {
1561 NodeIdxSet();
1563 bool add(Node& n) {
1564 if (n.idx >= set.universe_size()) {
1565 set.resize(folly::nextPowTwo(n.idx+1));
1567 return set.insert(&n);
1569 void erase(Node& n) {
1570 if (n.idx < set.universe_size()) {
1571 set.erase(&n);
1574 void clear() { set.clear(); }
1575 bool empty() { return set.empty(); }
1576 private:
1577 struct Extract {
1578 Node::Idx operator()(const Node* n) const {
1579 assertx(n->idx > 0);
1580 return n->idx;
1583 sparse_id_set<Node::Idx, const Node*, Extract> set;
1586 // Thread-local NodeIdxSet which automatically clears itself
1587 // afterwards (thus avoids memory allocation).
1588 struct ClassGraph::TLNodeIdxSet {
1589 TLNodeIdxSet() : set{get()} { assertx(set.empty()); }
1591 ~TLNodeIdxSet() {
1592 set.clear();
1593 sets().emplace_back(std::move(set));
1596 NodeIdxSet& operator*() { return set; }
1597 NodeIdxSet* operator->() { return &set; }
1598 private:
1599 NodeIdxSet set;
1601 static NodeIdxSet get() {
1602 auto& s = sets();
1603 if (s.empty()) return {};
1604 auto set = std::move(s.back());
1605 s.pop_back();
1606 return set;
1609 static std::vector<NodeIdxSet>& sets() {
1610 static thread_local std::vector<NodeIdxSet> s;
1611 return s;
1615 struct ClassGraph::Table {
1616 // Node map to ensure pointer stability.
1617 TSStringToOneNodeT<Node> nodes;
1618 // Mapping of one node equivalent to another when only considering
1619 // regular subclasses. No entry if the mapping is an identity or to
1620 // a subclass (which is common). Stored separately to save memory
1621 // since it's rare.
1622 hphp_fast_map<Node*, Node*> regOnlyEquivs;
1623 hphp_fast_map<Node*, size_t> completeSizeCache;
1624 AnalysisIndex::IndexData* index{nullptr};
1625 struct Locking {
1626 mutable folly::SharedMutex table;
1627 std::array<std::mutex, 2048> nodes;
1628 mutable folly::SharedMutex equivs;
1629 mutable folly::SharedMutex sizes;
1631 // If present, we're doing concurrent deserialization.
1632 Optional<Locking> locking;
1635 ClassGraph::NodeIdxSet::NodeIdxSet()
1636 : set{folly::nextPowTwo((Node::Idx)(table().nodes.size() + 1))}
1638 assertx(!table().locking);
1641 struct ClassGraph::SerdeState {
1642 Optional<NodeIdxSet> upward;
1643 Optional<NodeIdxSet> downward;
1645 std::vector<SString> strings;
1646 std::vector<SString> newStrings;
1647 SStringToOneT<size_t> strToIdx;
1650 struct ClassGraph::ScopedSerdeState {
1651 ScopedSerdeState() {
1652 // If there's no SerdeState active, make one active, otherwise do
1653 // nothing.
1654 if (tl_serde_state) return;
1655 s.emplace();
1656 tl_serde_state = s.get_pointer();
1659 ~ScopedSerdeState() {
1660 if (!s.has_value()) return;
1661 assertx(tl_serde_state == s.get_pointer());
1662 tl_serde_state = nullptr;
1665 ScopedSerdeState(const ScopedSerdeState&) = delete;
1666 ScopedSerdeState(ScopedSerdeState&&) = delete;
1667 ScopedSerdeState& operator=(const ScopedSerdeState&) = delete;
1668 ScopedSerdeState& operator=(ScopedSerdeState&&) = delete;
1669 private:
1670 Optional<SerdeState> s;
1674 * When operating over ClassGraph nodes, we often need compact sets of
1675 * them. This is easy to do with bitsets, but we don't have a fixed
1676 * upper-size of the sets. We could always use dynamic_bitset, but
1677 * this can be inefficient. Instead we templatize the algorithm over
1678 * the bitset, and use a fixed size std::bitset for the common case
1679 * and dynamic_bitset for the (rare) exceptions.
1681 struct ClassGraph::SmallBitset {
1682 explicit SmallBitset(size_t limit) {
1683 assertx(limit <= bits.size());
1685 SmallBitset(size_t limit, size_t idx) {
1686 assertx(limit <= bits.size());
1687 assertx(idx < limit);
1688 bits[idx] = true;
1690 SmallBitset& operator|=(const SmallBitset& o) {
1691 bits |= o.bits;
1692 return *this;
1694 SmallBitset& operator&=(const SmallBitset& o) {
1695 bits &= o.bits;
1696 return *this;
1698 SmallBitset& operator-=(size_t idx) {
1699 assertx(idx < bits.size());
1700 bits[idx] = false;
1701 return *this;
1703 SmallBitset& flip(size_t limit) {
1704 assertx(limit <= bits.size());
1705 bits.flip();
1706 auto const offset = bits.size() - limit;
1707 bits <<= offset;
1708 bits >>= offset;
1709 return *this;
1711 bool test(size_t idx) const {
1712 assertx(idx < bits.size());
1713 return bits[idx];
1715 bool any() const { return bits.any(); }
1716 bool none() const { return bits.none(); }
1717 bool all(size_t limit) const {
1718 auto s = *this;
1719 s.flip(limit);
1720 return s.none();
1722 size_t first() const { return bitset_find_first(bits); }
1723 size_t next(size_t prev) const { return bitset_find_next(bits, prev); }
1724 bool operator==(const SmallBitset& o) const { return bits == o.bits; }
1726 static constexpr size_t kMaxSize = 64;
1727 using B = std::bitset<kMaxSize>;
1728 B bits;
1730 struct Hasher {
1731 size_t operator()(const SmallBitset& b) const {
1732 return std::hash<B>{}(b.bits);
1737 struct ClassGraph::LargeBitset {
1738 explicit LargeBitset(size_t limit): bits{limit} {}
1739 LargeBitset(size_t limit, size_t idx): bits{limit} {
1740 assertx(idx < limit);
1741 bits[idx] = true;
1743 LargeBitset& operator|=(const LargeBitset& o) {
1744 bits |= o.bits;
1745 return *this;
1747 LargeBitset& operator&=(const LargeBitset& o) {
1748 bits &= o.bits;
1749 return *this;
1751 LargeBitset& operator-=(size_t idx) {
1752 assertx(idx < bits.size());
1753 bits[idx] = false;
1754 return *this;
1756 LargeBitset& flip(size_t limit) {
1757 assertx(limit == bits.size());
1758 bits.flip();
1759 return *this;
1761 bool test(size_t idx) const {
1762 assertx(idx < bits.size());
1763 return bits[idx];
1765 bool any() const { return bits.any(); }
1766 bool none() const { return bits.none(); }
1767 bool all(size_t) const { return bits.all(); }
1768 size_t first() const { return bits.find_first(); }
1769 size_t next(size_t prev) const { return bits.find_next(prev); }
1770 bool operator==(const LargeBitset& o) const { return bits == o.bits; }
1772 boost::dynamic_bitset<> bits;
1774 struct Hasher {
1775 size_t operator()(const LargeBitset& b) const {
1776 return std::hash<boost::dynamic_bitset<>>{}(b.bits);
1781 // Helper class (parameterized over bitset type) to track Nodes and
1782 // their parents.
1783 template <typename Set>
1784 struct ClassGraph::ParentTracker {
1785 // Heads is the universe of nodes which are tracked.
1786 template <typename T>
1787 explicit ParentTracker(const T& heads, const NodeSet* ignore = nullptr)
1788 : count{heads.size()}
1789 , valid{count}
1790 , ignore{ignore}
1792 toNode.insert(heads.begin(), heads.end());
1793 indices.reserve(count);
1794 for (size_t i = 0; i < count; ++i) indices[toNode[i]] = i;
1795 valid.flip(count);
1798 // Intersect this node and it's parents with the current valid ones
1799 // and return true if any remain.
1800 bool operator()(Node& n) {
1801 valid &= set(n);
1802 return valid.any();
1804 // Return true if this node and it's parents contain all nodes being
1805 // tracked.
1806 bool all(Node& n) { return set(n).all(count); }
1808 // Return all nodes left in the valid set.
1809 NodeSet nodes() const {
1810 NodeSet s;
1811 for (auto i = valid.first(); i < count; i = valid.next(i)) {
1812 s.emplace(toNode[i]);
1814 return s;
1817 private:
1818 struct Wrapper {
1819 explicit Wrapper(size_t limit): s{limit} {}
1820 Wrapper(size_t limit, size_t b): s{limit, b} {}
1821 explicit operator bool() const { return stop; }
1822 Wrapper& operator|=(const Wrapper& o) {
1823 s |= o.s;
1824 stop &= o.stop;
1825 return *this;
1827 Set s;
1828 bool stop{false};
1831 Set set(Node& n) {
1832 if (n.isMissing()) {
1833 auto const idx = folly::get_ptr(indices, &n);
1834 if (!idx) return Set{count};
1835 assertx(*idx < count);
1836 return Set{count, *idx};
1839 auto wrapper = foldParents(
1841 [&] (Node& p) {
1842 if (ignore && ignore->count(&p)) {
1843 Wrapper w{count};
1844 w.stop = true;
1845 return w;
1847 auto const idx = folly::get_ptr(indices, &p);
1848 if (!idx) return Wrapper{count};
1849 assertx(*idx < count);
1850 return Wrapper{count, *idx};
1852 [&] { return Wrapper{count}; },
1853 nodeToParents
1855 return wrapper.s;
1858 size_t count;
1859 Set valid;
1860 NodeMap<size_t> indices;
1861 NodeMap<Wrapper> nodeToParents;
1862 NodeVec toNode;
1863 const NodeSet* ignore;
1866 // Abstractions for whether we're deserializing concurrently or
1867 // not. This separates out the locking logic and let's us avoid any
1868 // runtime checks (except one) during deserializing to see if we need
1869 // to lock.
1871 // Non-concurrent implementation. This just modifies the fields
1872 // without any locking.
1873 struct ClassGraph::UnlockedSerdeImpl {
1874 std::pair<Node*, bool> create(SString name) const {
1875 auto& t = table();
1876 auto& n = t.nodes[name];
1877 if (n.name) {
1878 assertx(n.name->tsame(name));
1879 return std::make_pair(&n, false);
1881 n.name = name;
1882 assertx(t.nodes.size() < std::numeric_limits<Node::Idx>::max());
1883 n.idx = t.nodes.size();
1884 assertx(n.idx > 0);
1885 return std::make_pair(&n, true);
1887 Node& get(SString name) const {
1888 auto n = folly::get_ptr(table().nodes, name);
1889 always_assert_flog(
1891 "Attempting to retrieve missing ClassGraph node '{}'",
1892 name
1894 assertx(n->name->tsame(name));
1895 return *n;
1897 void setEquiv(Node& n, Node& e) const {
1898 auto const [it, s] = table().regOnlyEquivs.emplace(&n, &e);
1899 always_assert(s || it->second == &e);
1901 size_t getCompleteSize(Node& n) const {
1902 auto const size = folly::get_default(table().completeSizeCache, &n);
1903 always_assert(size > 1);
1904 return size;
1906 void setCompleteSize(Node& n, size_t size) const {
1907 assertx(size > 1);
1908 auto const [it, s] = table().completeSizeCache.emplace(&n, size);
1909 always_assert(s || it->second == size);
1911 template <typename F> void lock(Node&, const F& f) const { f(); }
1912 template <typename F> void forEachChild(Node& n, const F& f) const {
1913 for (auto const c : n.children) f(*c);
1915 void signal(Node& n, Flags f) const {
1916 assertx(n.flags() == FlagNone);
1917 assertx(!(f & FlagWait));
1918 n.setFlags(f);
1920 void setCInfo(Node& n, ClassInfo& cinfo) const { n.setCInfo(cinfo); }
1921 void setCInfo(Node& n, ClassInfo2& cinfo) const { n.setCInfo(cinfo); }
1922 void updateFlags(Node& n, Flags add, Flags remove = FlagNone) const {
1923 if (add == FlagNone && remove == FlagNone) return;
1924 auto const oldFlags = n.flags();
1925 auto const newFlags = (Flags)((oldFlags | add) & ~remove);
1926 if (newFlags == oldFlags) return;
1927 n.ci.store(Node::CIAndFlags{newFlags, n.rawPtr()}.getOpaque());
1931 // Concurrent implementation. The node table is guarded by a RWLock
1932 // and the individual nodes are guarded by an array of locks. The hash
1933 // of the node's address determines the lock to use. In addition,
1934 // FlagWait is used to tell threads that the node is being created and
1935 // one must wait for the flag to be reset.
1936 struct ClassGraph::LockedSerdeImpl {
1937 std::pair<Node*, bool> create(SString name) const {
1938 // Called when we find an existing node. We cannot safely return
1939 // this node until FlagWait is cleared.
1940 auto const wait = [&] (Node& n) {
1941 assertx(n.name->tsame(name));
1942 while (true) {
1943 Node::CIAndFlags f{n.ci.load()};
1944 if (!(f.tag() & FlagWait)) return std::make_pair(&n, false);
1945 // Still set, wait for it to change and then check again.
1946 n.ci.wait(f.getOpaque());
1950 auto& t = table();
1952 // First access the table with a read lock and see if the node
1953 // already exists.
1955 auto const n = [&] {
1956 std::shared_lock _{t.locking->table};
1957 return folly::get_ptr(t.nodes, name);
1958 }();
1959 // It already exists, wait on FlagWait and then return.
1960 if (n) return wait(*n);
1963 // It didn't, we need to create it:
1964 std::unique_lock _{t.locking->table};
1965 // We now have exlusive access to the table. Check for the node
1966 // again, as someone else might have created it in the meantime.
1967 if (auto const n = folly::get_ptr(t.nodes, name)) {
1968 // If someone did, wait on FlagWait and then return. Drop the
1969 // write lock before waiting to avoid deadlock.
1970 _.unlock();
1971 return wait(*n);
1974 // Node still isn't present. Create one now.
1975 auto [it, emplaced] = t.nodes.try_emplace(name);
1976 always_assert(emplaced);
1977 auto& n = it->second;
1978 assertx(!n.name);
1979 assertx(!(n.flags() & FlagWait));
1980 n.name = name;
1981 assertx(t.nodes.size() < std::numeric_limits<Node::Idx>::max());
1982 n.idx = t.nodes.size();
1983 assertx(n.idx > 0);
1984 // Set FlagWait, this will ensure that any other thread who
1985 // retrieves this node (after we drop the write lock) will block
1986 // until we're done deserializing it and it's children.
1987 n.setFlags(FlagWait);
1988 return std::make_pair(&n, true);
1990 Node& get(SString name) const {
1991 auto& t = table();
1992 std::shared_lock _{t.locking->table};
1993 auto n = folly::get_ptr(t.nodes, name);
1994 always_assert_flog(
1996 "Attempting to retrieve missing ClassGraph node '{}'",
1997 name
1999 assertx(n->name->tsame(name));
2000 // FlagWait shouldn't be set here because we shouldn't call get()
2001 // until a node and all of it's dependents are created.
2002 assertx(!(n->flags() & FlagWait));
2003 return *n;
2005 void setEquiv(Node& n, Node& e) const {
2006 auto& t = table();
2008 std::shared_lock _{t.locking->equivs};
2009 if (auto const old = folly::get_default(t.regOnlyEquivs, &n)) {
2010 assertx(old == &e);
2011 return;
2014 std::unique_lock _{t.locking->equivs};
2015 auto const [it, s] = t.regOnlyEquivs.emplace(&n, &e);
2016 always_assert(s || it->second == &e);
2018 size_t getCompleteSize(Node& n) const {
2019 auto& t = table();
2020 std::shared_lock _{t.locking->sizes};
2021 auto const size = folly::get_default(t.completeSizeCache, &n);
2022 always_assert(size > 1);
2023 return size;
2025 void setCompleteSize(Node& n, size_t size) const {
2026 assertx(size > 1);
2027 auto& t = table();
2028 std::unique_lock _{t.locking->sizes};
2029 auto const [it, s] = t.completeSizeCache.emplace(&n, size);
2030 always_assert(s || it->second == size);
2032 // Lock a node by using the lock it hashes to and execute f() while
2033 // holding the lock.
2034 template <typename F> void lock(Node& n, const F& f) const {
2035 auto& t = table();
2036 auto& lock = t.locking->nodes[
2037 pointer_hash<Node>{}(&n) % t.locking->nodes.size()
2039 lock.lock();
2040 SCOPE_EXIT { lock.unlock(); };
2041 f();
2043 template <typename F> void forEachChild(Node& n, const F& f) const {
2044 CompactVector<Node*> children;
2045 lock(n, [&] { children = n.children; });
2046 for (auto const c : children) f(*c);
2048 // Signal that a node (and all of it's dependents) is done being
2049 // deserialized. This clears FlagWait and wakes up any threads
2050 // waiting on the flag. In addition, it sets the node's flags to the
2051 // provided flags.
2052 void signal(Node& n, Flags other) const {
2053 assertx(!(other & FlagWait));
2054 while (true) {
2055 auto old = n.ci.load();
2056 Node::CIAndFlags f{old};
2057 assertx(f.tag() == FlagWait);
2058 // Use CAS to set the flag
2059 f.set(other, f.ptr());
2060 if (n.ci.compare_exchange_strong(old, f.getOpaque())) break;
2062 // Wake up any other threads.
2063 n.ci.notify_all();
2065 // Set a ClassInfo2 on this node, using CAS to detect concurrent
2066 // modifications.
2067 void setCInfo(Node& n, ClassInfo& c) const {
2068 while (true) {
2069 auto old = n.ci.load();
2070 Node::CIAndFlags f{old};
2071 f.set((Flags)(f.tag() & ~FlagCInfo2), &c);
2072 if (n.ci.compare_exchange_strong(old, f.getOpaque())) break;
2075 void setCInfo(Node& n, ClassInfo2& c) const {
2076 while (true) {
2077 auto old = n.ci.load();
2078 Node::CIAndFlags f{old};
2079 f.set((Flags)(f.tag() | FlagCInfo2), &c);
2080 if (n.ci.compare_exchange_strong(old, f.getOpaque())) break;
2083 void updateFlags(Node& n, Flags add, Flags remove = FlagNone) const {
2084 if (add == FlagNone && remove == FlagNone) return;
2085 while (true) {
2086 auto old = n.ci.load();
2087 Node::CIAndFlags f{old};
2088 auto const oldFlags = (Flags)f.tag();
2089 auto const newFlags = (Flags)((oldFlags | add) & ~remove);
2090 if (newFlags == oldFlags) break;
2091 f.set(newFlags, f.ptr());
2092 if (n.ci.compare_exchange_strong(old, f.getOpaque())) break;
2097 SString ClassGraph::name() const {
2098 assertx(this_);
2099 return this_->name;
2102 ClassInfo* ClassGraph::cinfo() const {
2103 assertx(this_);
2104 return this_->cinfo();
2107 ClassInfo2* ClassGraph::cinfo2() const {
2108 assertx(this_);
2109 return this_->cinfo2();
2112 bool ClassGraph::mightBeRegular() const {
2113 assertx(this_);
2114 return isMissing() || this_->isRegular();
2117 bool ClassGraph::mightBeNonRegular() const {
2118 assertx(this_);
2119 return isMissing() || !this_->isRegular();
2122 bool ClassGraph::mightHaveRegularSubclass() const {
2123 assertx(this_);
2124 if (this_->hasCompleteChildren() || this_->isConservative()) {
2125 return this_->hasRegularSubclass() || !allowed(true);
2127 return true;
2130 bool ClassGraph::mightHaveNonRegularSubclass() const {
2131 assertx(this_);
2132 if (this_->hasCompleteChildren() || this_->isConservative()) {
2133 return this_->hasNonRegularSubclass() || !allowed(true);
2135 return true;
2138 bool ClassGraph::isMissing() const {
2139 assertx(this_);
2140 return this_->isMissing() || !allowed(false);
2143 bool ClassGraph::isMissingRaw() const {
2144 assertx(this_);
2145 return this_->isMissing();
2148 bool ClassGraph::hasCompleteChildren() const {
2149 assertx(this_);
2150 return !this_->isMissing() && this_->hasCompleteChildren() && allowed(true);
2153 bool ClassGraph::hasCompleteChildrenRaw() const {
2154 assertx(this_);
2155 return !this_->isMissing() && this_->hasCompleteChildren();
2158 bool ClassGraph::isConservative() const {
2159 assertx(this_);
2160 return !this_->isMissing() && this_->isConservative() && allowed(true);
2163 bool ClassGraph::isConservativeRaw() const {
2164 assertx(this_);
2165 return !this_->isMissing() && this_->isConservative();
2168 bool ClassGraph::isInterface() const {
2169 assertx(this_);
2170 assertx(!isMissing());
2171 return this_->isInterface();
2174 bool ClassGraph::isTrait() const {
2175 assertx(this_);
2176 assertx(!isMissing());
2177 return this_->isTrait();
2180 bool ClassGraph::isEnum() const {
2181 assertx(this_);
2182 assertx(!isMissing());
2183 return this_->isEnum();
2186 bool ClassGraph::isAbstract() const {
2187 assertx(this_);
2188 assertx(!isMissing());
2189 return this_->isAbstract();
2192 void ClassGraph::setComplete() {
2193 assertx(!table().locking);
2194 assertx(this_);
2195 assertx(!this_->isMissing());
2196 assertx(!this_->hasCompleteChildren());
2197 assertx(!this_->isConservative());
2198 setCompleteImpl(UnlockedSerdeImpl{}, *this_);
2201 void ClassGraph::setClosureBase() {
2202 assertx(!table().locking);
2203 assertx(this_);
2204 assertx(!this_->isMissing());
2205 assertx(!this_->hasCompleteChildren());
2206 assertx(!this_->isConservative());
2207 assertx(is_closure_base(this_->name));
2208 this_->children.clear();
2209 this_->setFlags((Flags)(FlagConservative | FlagRegSub));
2212 void ClassGraph::setBase(ClassGraph b) {
2213 assertx(!table().locking);
2214 assertx(this_);
2215 assertx(b.this_);
2216 assertx(!isMissing());
2217 assertx(!b.isMissing());
2218 assertx(b.this_->isBase());
2219 this_->parents.emplace_back(b.this_);
2220 if (!b.this_->isConservative()) {
2221 b.this_->children.emplace_back(this_);
2225 void ClassGraph::addParent(ClassGraph p) {
2226 assertx(!table().locking);
2227 assertx(this_);
2228 assertx(p.this_);
2229 assertx(!isMissing());
2230 assertx(!p.isMissing());
2231 assertx(!p.this_->isBase());
2232 this_->parents.emplace_back(p.this_);
2233 if (!p.this_->isConservative()) {
2234 p.this_->children.emplace_back(this_);
2238 void ClassGraph::flattenTraitInto(ClassGraph t) {
2239 assertx(!table().locking);
2240 assertx(this_);
2241 assertx(t.this_);
2242 assertx(t.this_->isTrait());
2243 assertx(!isMissing());
2244 assertx(!t.isMissing());
2245 assertx(!t.isConservative());
2246 assertx(!t.hasCompleteChildren());
2248 // Remove this trait as a parent, and move all of it's parents into
2249 // this class.
2250 always_assert(this_->parents.erase(t.this_));
2251 always_assert(t.this_->children.erase(this_));
2252 for (auto const p : t.this_->parents) {
2253 this_->parents.emplace_back(p);
2254 p->children.emplace_back(this_);
2258 // Indicates that all possible parents has been added to this
2259 // node. Puts the parents list in canonical order.
2260 void ClassGraph::finalizeParents() {
2261 assertx(!table().locking);
2262 assertx(this_);
2263 assertx(!this_->isMissing());
2264 assertx(!this_->isConservative());
2265 assertx(!this_->hasCompleteChildren());
2266 std::sort(begin(this_->parents), end(this_->parents), Node::Compare{});
2269 void ClassGraph::reset() {
2270 assertx(!table().locking);
2271 assertx(this_);
2272 assertx(this_->children.empty());
2273 assertx(!this_->isMissing());
2274 assertx(!this_->isConservative());
2275 assertx(!this_->hasCompleteChildren());
2276 for (auto const parent : this_->parents) {
2277 if (parent->isConservative()) continue;
2278 always_assert(parent->children.erase(this_));
2280 this_->parents.clear();
2281 this_->ci.store(Node::CIAndFlags::Opaque{});
2282 this_ = nullptr;
2285 void ClassGraph::setCInfo(ClassInfo& ci) {
2286 assertx(!table().locking);
2287 assertx(this_);
2288 assertx(!this_->isMissing());
2289 assertx(this_->hasCompleteChildren() || this_->isConservative());
2290 this_->setCInfo(ci);
2293 bool ClassGraph::operator<(ClassGraph h) const {
2294 return string_data_lt_type{}(this_->name, h.this_->name);
2297 ClassGraph ClassGraph::withoutNonRegular() const {
2298 assertx(!table().locking);
2299 assertx(this_);
2300 if (!ensure() || this_->isMissing() || this_->isRegular()) {
2301 return *this;
2303 return ClassGraph { this_->nonRegularInfo().regOnlyEquiv };
2306 ClassGraph ClassGraph::base() const {
2307 assertx(!table().locking);
2308 assertx(this_);
2309 if (ensure() && !this_->isMissing()) {
2310 for (auto const p : this_->parents) {
2311 if (p->isBase()) return ClassGraph { p };
2314 return ClassGraph { nullptr };
2317 ClassGraph ClassGraph::topBase() const {
2318 assertx(!table().locking);
2319 assertx(this_);
2320 assertx(!isMissing());
2322 always_assert(ensure());
2324 auto current = this_;
2325 auto last = this_;
2326 do {
2327 last = this_;
2328 auto const& parents = current->parents;
2329 current = nullptr;
2330 // There should be at most one base on the parent list. Verify
2331 // this in debug builds.
2332 for (auto const p : parents) {
2333 if (p->isBase()) {
2334 assertx(!current);
2335 current = p;
2336 if (!debug) break;
2339 } while (current);
2341 return ClassGraph { last };
2344 std::vector<ClassGraph> ClassGraph::bases() const {
2345 assertx(!table().locking);
2346 assertx(this_);
2347 assertx(!isMissing());
2349 always_assert(ensure());
2351 std::vector<ClassGraph> out;
2352 auto current = this_;
2353 do {
2354 out.emplace_back(ClassGraph{ current });
2355 auto const& parents = current->parents;
2356 current = nullptr;
2357 // There should be at most one base on the parent list. Verify
2358 // this in debug builds.
2359 for (auto const p : parents) {
2360 if (p->isBase()) {
2361 assertx(!current);
2362 current = p;
2363 if (!debug) break;
2366 } while (current);
2368 std::reverse(begin(out), end(out));
2369 return out;
2372 std::vector<ClassGraph> ClassGraph::interfaces() const {
2373 assertx(!table().locking);
2374 assertx(this_);
2375 assertx(!isMissing());
2377 always_assert(ensure());
2379 std::vector<ClassGraph> out;
2380 forEachParent(
2381 *this_,
2382 [&] (Node& p) {
2383 if (p.isInterface()) out.emplace_back(ClassGraph{ &p });
2384 return Action::Continue;
2387 std::sort(begin(out), end(out));
2388 return out;
2391 template <typename F>
2392 void ClassGraph::walkParents(const F& f) const {
2393 assertx(!table().locking);
2394 assertx(this_);
2395 assertx(!isMissing());
2397 always_assert(ensure());
2399 forEachParent(
2400 *this_,
2401 [&] (Node& p) {
2402 return !f(ClassGraph{ &p })
2403 ? Action::Skip
2404 : Action::Continue;
2409 std::vector<ClassGraph> ClassGraph::children() const {
2410 assertx(!table().locking);
2411 assertx(this_);
2412 assertx(!isMissing());
2413 assertx(hasCompleteChildren());
2415 always_assert(ensureWithChildren());
2417 std::vector<ClassGraph> out;
2418 // If this_ is a trait, then forEachChild won't walk the list. Use
2419 // forEachChildImpl with the right params to prevent this.
2420 TLNodeIdxSet visited;
2421 forEachChildImpl(
2422 *this_,
2423 [&] (Node& c) {
2424 out.emplace_back(ClassGraph{ &c });
2425 return Action::Continue;
2427 &*visited,
2428 false
2430 std::sort(begin(out), end(out));
2431 return out;
2434 std::vector<ClassGraph> ClassGraph::directParents(Flags flags) const {
2435 assertx(!table().locking);
2436 assertx(this_);
2437 assertx(!isMissing());
2439 always_assert(ensure());
2441 std::vector<ClassGraph> out;
2442 out.reserve(this_->parents.size());
2443 for (auto const p : this_->parents) {
2444 if (!(p->flags() & flags)) continue;
2445 out.emplace_back(ClassGraph { p });
2447 return out;
2450 std::vector<ClassGraph> ClassGraph::directParents() const {
2451 assertx(!table().locking);
2452 assertx(this_);
2453 assertx(!isMissing());
2455 always_assert(ensure());
2457 std::vector<ClassGraph> out;
2458 out.reserve(this_->parents.size());
2459 for (auto const p : this_->parents) out.emplace_back(ClassGraph { p });
2460 return out;
2463 bool ClassGraph::isChildOf(ClassGraph o) const {
2464 assertx(!table().locking);
2465 assertx(this_);
2466 assertx(o.this_);
2467 assertx(!isMissing());
2468 assertx(!o.isMissing());
2469 always_assert(ensure());
2470 if (this_ == o.this_) return true;
2471 // Nothing is a child of a trait except itself and we know they're
2472 // not equal.
2473 if (o.this_->isTrait()) return false;
2474 return findParent(*this_, *o.this_);
2477 std::vector<ClassGraph> ClassGraph::candidateRegOnlyEquivs() const {
2478 assertx(!table().locking);
2479 assertx(this_);
2480 assertx(!this_->isMissing());
2482 if (this_->isRegular() || this_->isConservative()) return {};
2483 assertx(this_->hasCompleteChildren());
2484 auto const nonParents = calcSubclassOfSplit(*this_).first;
2485 if (nonParents.empty()) return {};
2486 if (nonParents.size() == 1) return { ClassGraph { *nonParents.begin() } };
2488 auto heads = nonParents;
2490 // Remove any nodes which are reachable from another node. Such
2491 // nodes are redundant.
2493 TLNodeIdxSet visited;
2494 for (auto const n : nonParents) {
2495 if (!heads.count(n)) continue;
2496 forEachParent(
2498 [&] (Node& p) {
2499 if (&p == n) return Action::Continue;
2500 if (!nonParents.count(&p)) return Action::Continue;
2501 if (!heads.count(&p)) return Action::Skip;
2502 heads.erase(&p);
2503 return Action::Continue;
2505 *visited
2507 visited->erase(*n);
2511 // Remove any nodes which have a (regular) child which does not have
2512 // this_ a parent. Such a node can't be an equivalent because it
2513 // contains more regular nodes than this_. Note that we might not
2514 // have complete children information for all nodes, so this check
2515 // is conservative. We only remove nodes which are definitely not
2516 // candidates.
2517 folly::erase_if(
2518 heads,
2519 [&] (Node* n) {
2520 auto const action = forEachChild(
2522 [&] (Node& c) {
2523 if (!c.isRegular()) return Action::Continue;
2524 if (!findParent(c, *this_)) return Action::Stop;
2525 return Action::Skip;
2528 return action == Action::Stop;
2532 std::vector<ClassGraph> out;
2533 out.reserve(heads.size());
2534 for (auto const n : heads) out.emplace_back(ClassGraph { n });
2535 std::sort(begin(out), end(out));
2536 return out;
2539 void ClassGraph::setRegOnlyEquivs() const {
2540 assertx(!table().locking);
2541 assertx(this_);
2542 assertx(!this_->isMissing());
2543 if (this_->isRegular() || this_->isConservative()) return;
2544 assertx(this_->hasCompleteChildren());
2545 auto const equiv = calcRegOnlyEquiv(*this_, calcSubclassOf(*this_));
2546 if (!equiv || equiv == this_ || findParent(*equiv, *this_)) return;
2547 auto const [it, s] = table().regOnlyEquivs.emplace(this_, equiv);
2548 always_assert(s || it->second == equiv);
2551 const ClassGraph::Node::NonRegularInfo&
2552 ClassGraph::Node::nonRegularInfo() {
2553 assertx(!table().locking);
2554 assertx(!isMissing());
2555 assertx(!isRegular());
2556 auto const allowed = ClassGraph{this}.ensureWithChildren();
2557 auto const& i = nonRegInfo.get(
2558 [this] {
2559 auto info = std::make_unique<NonRegularInfo>();
2560 info->subclassOf = calcSubclassOf(*this);
2561 info->regOnlyEquiv = calcRegOnlyEquiv(*this, info->subclassOf);
2562 return info.release();
2565 if (allowed || !hasCompleteChildren() || isTrait()) return i;
2566 // We're not allowed to use this Node's information, so instead use
2567 // more pessimistic info.
2568 return nonRegInfoDisallow.get(
2569 [this] {
2570 auto info = std::make_unique<NonRegularInfo>();
2571 info->subclassOf = allParents(*this);
2572 info->regOnlyEquiv = calcRegOnlyEquiv(*this, info->subclassOf);
2573 return info.release();
2578 bool ClassGraph::exactSubtypeOfExact(ClassGraph o,
2579 bool nonRegL,
2580 bool nonRegR) const {
2581 assertx(!table().locking);
2582 assertx(this_);
2583 assertx(o.this_);
2585 auto const missing1 = !ensure() || this_->isMissing();
2586 auto const missing2 = !o.ensure() || o.this_->isMissing();
2588 // Two exact classes are only subtypes of another if they're the
2589 // same. One additional complication is if the class isn't regular
2590 // and we're not considering non-regular classes. In that case, the
2591 // class is actually Bottom, and we need to apply the rules of
2592 // subtyping to Bottom (Bottom is a subtype of everything, but
2593 // nothing is a subtype of it).
2594 if (missing1) {
2595 // Missing classes are only definitely a subtype if it's the same
2596 // node and the lhs can become bottom or the rhs cannot.
2597 return (this_ == o.this_) && (!nonRegL || nonRegR);
2598 } else if (!nonRegL && !this_->isRegular()) {
2599 // Lhs is a bottom, so a subtype of everything.
2600 return true;
2601 } else if (missing2 || (!nonRegR && !o.this_->isRegular())) {
2602 // Lhs is not a bottom, check if the rhs is.
2603 return false;
2604 } else {
2605 // Neither is bottom, check for equality.
2606 return this_ == o.this_;
2610 bool ClassGraph::exactSubtypeOf(ClassGraph o,
2611 bool nonRegL,
2612 bool nonRegR) const {
2613 assertx(!table().locking);
2614 assertx(this_);
2615 assertx(o.this_);
2617 auto const missing1 = !ensure() || this_->isMissing();
2618 auto const missing2 = !o.ensure() || o.this_->isMissing();
2620 // If we want to exclude non-regular classes on either side, and the
2621 // lhs is not regular, there's no subtype relation. If nonRegL is
2622 // false, then lhs is just a bottom (and bottom is a subtype of
2623 // everything), and if nonRegularR is false, then the rhs does not
2624 // contain any non-regular classes, so lhs is guaranteed to not be
2625 // part of it.
2626 if (missing1) {
2627 // If the lhs side is missing, it's identical to
2628 // exactSubtypeOfExact.
2629 return (this_ == o.this_) && (!nonRegL || nonRegR);
2630 } else if ((!nonRegL || !nonRegR) && !this_->isRegular()) {
2631 return !nonRegL;
2632 } else if (this_ == o.this_) {
2633 return true;
2634 } else if (missing2 || o.this_->isTrait()) {
2635 // The lhs is not missing, so cannot be a subtype of a missing
2636 // class. Nothing is a subtype of a trait.
2637 return false;
2638 } else {
2639 // Otherwise try to find the rhs node among the parents of the lhs
2640 // side.
2641 return findParent(*this_, *o.this_);
2645 bool ClassGraph::subSubtypeOf(ClassGraph o, bool nonRegL, bool nonRegR) const {
2646 assertx(!table().locking);
2647 assertx(this_);
2648 assertx(o.this_);
2650 auto const missing1 = !ensure() || this_->isMissing();
2651 auto const missing2 = !o.ensure() || o.this_->isMissing();
2653 if (nonRegL && !nonRegR) {
2654 if (missing1 || !this_->isRegular()) return false;
2655 if (this_->hasNonRegularSubclass()) return false;
2658 // If this_ must be part of the lhs, it's equivalent to
2659 // exactSubtypeOf. Otherwise if exactSubtypeOf returns true for the
2660 // conservative case, then it must always be true, so we don't need
2661 // to look at children.
2662 if (nonRegL || missing1 || this_->isRegular()) {
2663 return exactSubtypeOf(o, nonRegL, nonRegR);
2665 if (exactSubtypeOf(o, true, true)) return true;
2667 // this_ is not regular and will not be part of the lhs. We need to
2668 // look at the regular children of this_ and check whether they're
2669 // all subtypes of the rhs.
2671 // Traits have no children for the purposes of this test.
2672 if (this_->isTrait() || missing2 || o.this_->isTrait()) {
2673 return false;
2675 return this_->nonRegularInfo().subclassOf.count(o.this_);
2678 bool ClassGraph::exactCouldBeExact(ClassGraph o,
2679 bool nonRegL,
2680 bool nonRegR) const {
2681 assertx(!table().locking);
2682 assertx(this_);
2683 assertx(o.this_);
2685 auto const missing = !ensure() || this_->isMissing();
2687 // Two exact classes can only be each other if they're the same
2688 // class. The only complication is if the class isn't regular and
2689 // we're not considering non-regular classes. In that case, the
2690 // class is actually Bottom, a Bottom can never could-be anything
2691 // (not even itself).
2692 if (this_ != o.this_) return false;
2693 if (missing || this_->isRegular()) return true;
2694 return nonRegL && nonRegR;
2697 bool ClassGraph::exactCouldBe(ClassGraph o, bool nonRegL, bool nonRegR) const {
2698 assertx(!table().locking);
2699 assertx(this_);
2700 assertx(o.this_);
2702 auto const missing1 = !ensure() || this_->isMissing();
2703 auto const missing2 = !o.ensure() || o.this_->isMissing();
2705 // exactCouldBe is almost identical to exactSubtypeOf, except the
2706 // case of the lhs being bottom is treated differently (bottom in
2707 // exactSubtypeOf implies true, but here it implies false).
2708 if (missing1) {
2709 if (missing2) return true;
2710 if ((!nonRegL || !nonRegR) &&
2711 !o.this_->isRegular() &&
2712 !o.this_->hasRegularSubclass()) {
2713 return false;
2715 return !o.this_->hasCompleteChildren();
2716 } else if ((!nonRegL || !nonRegR) && !this_->isRegular()) {
2717 return false;
2718 } else if (this_ == o.this_) {
2719 return true;
2720 } else if (missing2 || o.this_->isTrait()) {
2721 return false;
2722 } else {
2723 return findParent(*this_, *o.this_);
2727 bool ClassGraph::subCouldBe(ClassGraph o, bool nonRegL, bool nonRegR) const {
2728 assertx(!table().locking);
2729 assertx(this_);
2730 assertx(o.this_);
2732 auto const missing1 = !ensure() || this_->isMissing();
2733 auto const missing2 = !o.ensure() || o.this_->isMissing();
2735 auto const regOnly = !nonRegL || !nonRegR;
2737 if (exactCouldBe(o, nonRegL, nonRegR) ||
2738 o.exactCouldBe(*this, nonRegR, nonRegL)) {
2739 return true;
2740 } else if (missing1 && missing2) {
2741 return true;
2742 } else if ((!missing1 && this_->isTrait()) ||
2743 (!missing2 && o.this_->isTrait())) {
2744 return false;
2745 } else if (regOnly) {
2746 if (!missing1 && !this_->isRegular() && !this_->hasRegularSubclass()) {
2747 return false;
2749 if (!missing2 && !o.this_->isRegular() && !o.this_->hasRegularSubclass()) {
2750 return false;
2754 auto left = this_;
2755 auto right = o.this_;
2756 if (betterNode<true>(right, left)) std::swap(left, right);
2758 if (!ClassGraph{left}.ensureWithChildren() ||
2759 !left->hasCompleteChildren()) {
2760 return true;
2762 if (right->isMissing()) return false;
2764 TLNodeIdxSet visited;
2765 auto const action = forEachChild(
2766 *left,
2767 [&] (Node& c) {
2768 if (regOnly && !c.isRegular()) return Action::Continue;
2769 return findParent(c, *right, *visited)
2770 ? Action::Stop : Action::Continue;
2773 return action == Action::Stop;
2776 // Calculate all the classes which this class could be a subclass
2777 // of. This is only necessary if the class is non-regular (for regular
2778 // classes the subtype check is trivial). This not only speeds up
2779 // subtypeOf checks, but it is used when calculating reg-only
2780 // equivalent classes. The results are split into the nodes which
2781 // aren't parents of this class, and the ones which are not.
2782 std::pair<ClassGraph::NodeSet, ClassGraph::NodeSet>
2783 ClassGraph::calcSubclassOfSplit(Node& n) {
2784 assertx(!table().locking);
2785 assertx(!n.isMissing());
2786 assertx(!n.isRegular());
2788 // Traits cannot be a subclass of anything but itself, and if we
2789 // don't know all of the children, we have to be pessimistic and
2790 // report only the parents.
2791 if (n.isTrait()) return std::make_pair(NodeSet{}, NodeSet{});
2792 if (!n.hasCompleteChildren()) {
2793 return std::make_pair(NodeSet{}, allParents(n));
2796 // Find the first regular child of this non-regular class.
2797 Node* first = nullptr;
2798 forEachChild(
2800 [&] (Node& c) {
2801 if (!c.isRegular()) return Action::Continue;
2802 assertx(&c != &n);
2803 // n has complete children, so all children must as well.
2804 assertx(c.hasCompleteChildren());
2805 first = &c;
2806 return Action::Stop;
2809 // No regular child
2810 if (!first) return std::make_pair(NodeSet{}, NodeSet{});
2812 auto parents = allParents(n);
2814 // Given the regular class we found, gather all of it's
2815 // children. This is the initial set of candidates.
2816 NodeVec candidates;
2817 forEachParent(
2818 *first,
2819 [&] (Node& p) {
2820 // Ignore parents since we already know about this.
2821 if (parents.count(&p)) return Action::Skip;
2822 candidates.emplace_back(&p);
2823 return Action::Continue;
2826 if (candidates.empty()) return std::make_pair(NodeSet{}, std::move(parents));
2828 // Then use ParentTracker to remove any nodes which are not parents
2829 // of the other children.
2830 auto const run = [&] (auto tracker) {
2831 forEachChild(
2833 [&] (Node& c) {
2834 if (!c.isRegular()) return Action::Continue;
2835 return tracker(c) ? Action::Skip : Action::Stop;
2838 auto nodes = tracker.nodes();
2839 return std::make_pair(std::move(nodes), std::move(parents));
2842 if (candidates.size() <= SmallBitset::kMaxSize) {
2843 return run(ParentTracker<SmallBitset>{candidates, &parents});
2844 } else {
2845 return run(ParentTracker<LargeBitset>{candidates, &parents});
2849 ClassGraph::NodeSet ClassGraph::calcSubclassOf(Node& n) {
2850 assertx(!table().locking);
2851 assertx(!n.isMissing());
2852 assertx(!n.isRegular());
2853 auto [nonParents, parents] = calcSubclassOfSplit(n);
2854 nonParents.insert(begin(parents), end(parents));
2855 return nonParents;
2858 // Calculate the "best" Node which is equivalent to this Node when
2859 // considering only regular children. We canonicalize this Node to the
2860 // equivalent Node where appropriate. Nullptr is returned if the
2861 // "equivalent" Node is actually Bottom (because there are no regular
2862 // children). subclassOf gives the set of "candidate" Nodes.
2863 ClassGraph::Node* ClassGraph::calcRegOnlyEquiv(Node& base,
2864 const NodeSet& subclassOf) {
2865 assertx(!table().locking);
2866 assertx(!base.isMissing());
2867 assertx(!base.isRegular());
2869 if (subclassOf.empty()) return nullptr;
2870 if (base.isTrait()) return nullptr;
2871 if (base.hasCompleteChildren() || base.isConservative()) {
2872 if (!base.hasRegularSubclass()) return nullptr;
2874 if (subclassOf.size() == 1) {
2875 assertx(subclassOf.count(&base));
2876 return &base;
2879 // If we recorded an equivalent when deserializing, just use that.
2880 if (auto const e = folly::get_default(table().regOnlyEquivs, &base)) {
2881 assertx(e->hasCompleteChildren());
2882 return e;
2884 // Otherwise calculate it.
2886 auto heads = subclassOf;
2888 // Remove any nodes which are reachable from another node. Such
2889 // nodes are redundant.
2891 TLNodeIdxSet visited;
2892 for (auto const n : subclassOf) {
2893 if (!heads.count(n)) continue;
2894 forEachParent(
2896 [&] (Node& p) {
2897 if (&p == n) return Action::Continue;
2898 if (!subclassOf.count(&p)) return Action::Continue;
2899 if (!heads.count(&p)) return Action::Skip;
2900 heads.erase(&p);
2901 return Action::Continue;
2903 *visited
2905 visited->erase(*n);
2908 if (heads.size() == 1) return *heads.begin();
2910 assertx(base.hasCompleteChildren());
2912 // Find the best node among the remaining candidates.
2913 auto const run = [&] (auto tracker) {
2914 // A node is only a valid candidate (and thus sufficient) if all
2915 // of it's children are subclasses of the "base" Node.
2916 auto const isSufficient = [&] (Node* n) {
2917 if (n == &base) return true;
2918 if (findParent(*n, base)) return true;
2919 if (!n->hasCompleteChildren()) return false;
2920 auto const action = forEachChild(
2922 [&] (Node& c) {
2923 if (!c.isRegular()) return Action::Continue;
2924 return tracker.all(c) ? Action::Skip : Action::Stop;
2927 return action != Action::Stop;
2930 Node* best = nullptr;
2931 for (auto const n : heads) {
2932 if (!isSufficient(n)) continue;
2933 if (!best || betterNode<false>(n, best)) best = n;
2935 assertx(best);
2936 return best;
2939 if (heads.size() <= SmallBitset::kMaxSize) {
2940 return run(ParentTracker<SmallBitset>{heads});
2941 } else {
2942 return run(ParentTracker<LargeBitset>{heads});
2946 // Somewhat arbitrarily ranks two Nodes in a consistent manner.
2947 template <bool Allow>
2948 bool ClassGraph::betterNode(Node* n1, Node* n2) {
2949 if (n1 == n2) return false;
2951 // Non-missing nodes are always better. Two missing nodes are ranked
2952 // according to name. If Allow is true and we're not allowed to use
2953 // the Node, treat it as equivalent to isMissing() being true.
2954 auto const missing1 =
2955 n1->isMissing() || (Allow && !ClassGraph{n1}.allowed(false));
2956 auto const missing2 =
2957 n2->isMissing() || (Allow && !ClassGraph{n2}.allowed(false));
2958 if (missing1) {
2959 if (!missing2) return false;
2960 return string_data_lt_type{}(n1->name, n2->name);
2961 } else if (missing2) {
2962 return true;
2965 auto const complete1 =
2966 n1->hasCompleteChildren() && (!Allow || ClassGraph{n1}.allowed(true));
2967 auto const complete2 =
2968 n2->hasCompleteChildren() && (!Allow || ClassGraph{n2}.allowed(true));
2970 // Nodes with complete children are better than those that don't.
2971 if (!complete1) {
2972 if (complete2) return false;
2973 return string_data_lt_type{}(n1->name, n2->name);
2974 } else if (!complete2) {
2975 return true;
2978 // Choose the one with the least (immediate) children. Calculating
2979 // the full subclass list would be better, but more
2980 // expensive. Traits are always considered to have no children.
2981 auto const s1 = n1->isTrait() ? 0 : n1->children.size();
2982 auto const s2 = n2->isTrait() ? 0 : n2->children.size();
2983 if (s1 != s2) return s1 < s2;
2985 // Otherwise rank them acccording to what flags they have and then
2986 // finally by name.
2987 auto const weight = [] (const Node* n) {
2988 auto const f = n->flags();
2989 if (f & FlagAbstract) return 1;
2990 if (f & FlagInterface) return 2;
2991 if (f & FlagTrait) return 3;
2992 if (f & FlagEnum) return 4;
2993 return 0;
2995 auto const w1 = weight(n1);
2996 auto const w2 = weight(n2);
2997 if (w1 != w2) return w1 < w2;
2998 return string_data_lt_type{}(n1->name, n2->name);
3001 // Union together two sets of intersected classes.
3002 ClassGraph::NodeVec ClassGraph::combine(const NodeVec& lhs,
3003 const NodeVec& rhs,
3004 bool isSubL,
3005 bool isSubR,
3006 bool nonRegL,
3007 bool nonRegR) {
3008 assertx(!table().locking);
3009 assertx(!lhs.empty());
3010 assertx(!rhs.empty());
3011 assertx(IMPLIES(!isSubL, lhs.size() == 1));
3012 assertx(IMPLIES(!isSubR, rhs.size() == 1));
3014 // Combine two sets, keeping only the nodes that are in both
3015 // sets. An empty set is equivalent to Top (not Bottom), thus
3016 // everything in the other set goes in.
3017 NodeSet combined;
3018 auto const combine = [&] (const NodeSet& s1, const NodeSet& s2) {
3019 if (s1.empty()) {
3020 always_assert(!s2.empty());
3021 combined.insert(begin(s2), end(s2));
3022 } else if (s2.empty()) {
3023 combined.insert(begin(s1), end(s1));
3024 } else if (s1.size() < s2.size()) {
3025 for (auto const e : s1) {
3026 if (s2.count(e)) combined.emplace(e);
3028 } else {
3029 for (auto const e : s2) {
3030 if (s1.count(e)) combined.emplace(e);
3036 * (A&B) | (C&D) = (A|C) & (A|D) & (B|C) & (B|D)
3038 * (this generalizes to arbitrary size lists). So to union together
3039 * two intersection sets, we need to calculate the union of
3040 * individual classes pair-wise, then intersect them together.
3042 auto const process = [&] (Node* l, Node* r) {
3043 auto allowedL = ClassGraph{l}.ensure();
3044 auto allowedR = ClassGraph{r}.ensure();
3046 // Order the l and r to cut down on the number of cases to deal
3047 // with below.
3048 auto const flip = [&] {
3049 if (!allowedL || l->isMissing()) return false;
3050 if (!allowedR || r->isMissing()) return true;
3051 if (nonRegL || l->isRegular()) return false;
3052 if (nonRegR || r->isRegular()) return true;
3053 if (isSubL) return false;
3054 return isSubR;
3055 }();
3056 if (flip) {
3057 std::swap(l, r);
3058 std::swap(allowedL, allowedR);
3060 auto const flipSubL = flip ? isSubR : isSubL;
3061 auto const flipSubR = flip ? isSubL : isSubR;
3062 auto const flipNonRegL = flip ? nonRegR : nonRegL;
3063 auto const flipNonRegR = flip ? nonRegL : nonRegR;
3065 // This logic handles the unioning of two classes. If two classes
3066 // don't have a common parent, their union if Top, which is
3067 // dropped from the intersection list. For regular classes, we can
3068 // just get the parent list. For non-regular classes, we need to
3069 // use subclassOf.
3070 if (!allowedL || l->isMissing()) {
3071 if (l == r) combined.emplace(l);
3072 } else if (flipNonRegL || l->isRegular()) {
3073 if (flipNonRegR || r->isRegular()) {
3074 combine(allParents(*l), allParents(*r));
3075 } else if (flipSubR) {
3076 combine(allParents(*l), r->nonRegularInfo().subclassOf);
3077 } else {
3078 combine(allParents(*l), {});
3080 } else if (flipSubL) {
3081 if (flipSubR) {
3082 combine(l->nonRegularInfo().subclassOf,
3083 r->nonRegularInfo().subclassOf);
3084 } else {
3085 combine(l->nonRegularInfo().subclassOf, {});
3087 } else {
3088 always_assert(false);
3092 for (auto const l : lhs) {
3093 for (auto const r : rhs) process(l, r);
3096 // The resultant list is not in canonical form, so canonicalize it.
3097 return canonicalize(combined, nonRegL || nonRegR);
3100 // Intersect two sets of intersected classes together. This seems like
3101 // you would just combine the lists, but the intersection of the two
3102 // might have more specific classes in common.
3103 ClassGraph::NodeVec ClassGraph::intersect(const NodeVec& lhs,
3104 const NodeVec& rhs,
3105 bool nonRegL,
3106 bool nonRegR,
3107 bool& nonRegOut) {
3108 assertx(!table().locking);
3109 assertx(!lhs.empty());
3110 assertx(!rhs.empty());
3112 // Combine the two lists together.
3113 NodeVec combined;
3114 combined.reserve(lhs.size() + rhs.size());
3115 std::set_union(
3116 lhs.begin(), lhs.end(),
3117 rhs.begin(), rhs.end(),
3118 std::back_inserter(combined),
3119 betterNode<false>
3122 // Build the "heads". These are the nodes which could potentially be
3123 // part of the new intersection list, but don't need to be
3124 // calculated below. We leave them out to reduce the amount of work.
3125 NodeSet heads;
3126 for (auto const n : combined) {
3127 auto const allowed = ClassGraph{n}.ensure();
3128 if (!allowed || n->isMissing()) {
3129 heads.emplace(n);
3130 } else if ((nonRegL && nonRegR) || n->isRegular()) {
3131 auto const p = allParents(*n);
3132 heads.insert(p.begin(), p.end());
3133 } else if (!n->hasRegularSubclass()) {
3134 nonRegOut = false;
3135 return {};
3136 } else {
3137 auto const& s = n->nonRegularInfo().subclassOf;
3138 heads.insert(s.begin(), s.end());
3142 // Then enumerate over all of the classes in the combined set and
3143 // track which parents they *all* have in common.
3145 Optional<ParentTracker<SmallBitset>> small;
3146 Optional<ParentTracker<LargeBitset>> large;
3147 nonRegOut = false;
3149 enumerateIsectMembers(
3150 combined,
3151 nonRegL && nonRegR,
3152 [&] (Node& n) {
3153 if (n.isMissing() || !n.hasCompleteChildren()) {
3154 if (nonRegL && nonRegR) nonRegOut = true;
3155 } else if (!n.isRegular()) {
3156 if (!nonRegL || !nonRegR) {
3157 assertx(!ClassGraph{&n}.allowed(true));
3158 } else {
3159 nonRegOut = true;
3161 } else if (!nonRegOut && nonRegL && nonRegR) {
3162 if (n.hasNonRegularSubclass()) nonRegOut = true;
3165 // If we already created a tracker, use it.
3166 if (small) {
3167 return (*small)(n);
3168 } else if (large) {
3169 return (*large)(n);
3170 } else {
3171 // Otherwise this is the first time. Find the initial set of
3172 // candidates (all of the parents of this node minus the
3173 // heads) and set up the tracker.
3174 auto common = n.isMissing() ? NodeSet{&n} : allParents(n);
3175 folly::erase_if(common, [&] (Node* c) { return heads.count(c); });
3176 if (common.size() <= SmallBitset::kMaxSize) {
3177 small.emplace(common, &heads);
3178 } else {
3179 large.emplace(common, &heads);
3181 return !common.empty();
3185 assertx(IMPLIES(!nonRegL || !nonRegR, !nonRegOut));
3187 // At this point the tracker only contains all of the parents which
3188 // are in common. These nodes, plus the heads, only need to be
3189 // canonicalized.
3190 if (small) {
3191 auto s = small->nodes();
3192 s.insert(heads.begin(), heads.end());
3193 return canonicalize(s, nonRegOut);
3194 } else if (large) {
3195 auto s = large->nodes();
3196 s.insert(heads.begin(), heads.end());
3197 return canonicalize(s, nonRegOut);
3198 } else {
3199 return NodeVec{};
3203 // Given a set of intersected nodes, return an equivalent set with all
3204 // of the non-regular classes removed.
3205 ClassGraph::NodeVec ClassGraph::removeNonReg(const NodeVec& v) {
3206 assertx(!table().locking);
3207 assertx(!v.empty());
3209 // We can just treat this as an intersection:
3210 NodeVec lhs;
3211 NodeVec rhs;
3212 for (auto const n : v) {
3213 auto const allowed = ClassGraph{n}.ensure();
3214 if (!allowed || n->isMissing() || n->isRegular()) {
3215 lhs.emplace_back(n);
3216 } else if (auto const e = n->nonRegularInfo().regOnlyEquiv) {
3217 lhs.emplace_back(e);
3218 } else {
3219 return NodeVec{};
3222 if (lhs.size() <= 1) return lhs;
3224 std::sort(lhs.begin(), lhs.end(), betterNode<false>);
3225 rhs.emplace_back(lhs.back());
3226 lhs.pop_back();
3228 auto nonRegOut = false;
3229 SCOPE_EXIT { assertx(!nonRegOut); };
3230 return intersect(lhs, rhs, false, false, nonRegOut);
3233 // Given two lists of intersected classes, return true if the
3234 // intersection of the two lists might be non-empty.
3235 bool ClassGraph::couldBeIsect(const NodeVec& lhs,
3236 const NodeVec& rhs,
3237 bool nonRegL,
3238 bool nonRegR) {
3239 assertx(!table().locking);
3240 assertx(!lhs.empty());
3241 assertx(!rhs.empty());
3243 NodeVec combined;
3244 combined.reserve(lhs.size() + rhs.size());
3245 std::set_union(
3246 lhs.begin(), lhs.end(),
3247 rhs.begin(), rhs.end(),
3248 std::back_inserter(combined),
3249 betterNode<false>
3252 auto couldBe = false;
3253 enumerateIsectMembers(
3254 combined,
3255 nonRegL && nonRegR,
3256 [&] (Node&) {
3257 couldBe = true;
3258 return false;
3261 return couldBe;
3264 // Call the provided callback for each Node in the given intersection
3265 // list. If "all" is true, all Nodes are provided. Otherwise, only the
3266 // "top most" children are provided (the children which meet the
3267 // criteria and don't have any parents which do).
3268 template <typename F>
3269 void ClassGraph::enumerateIsectMembers(const NodeVec& nodes,
3270 bool nonReg,
3271 const F& f,
3272 bool all) {
3273 assertx(!table().locking);
3275 if (nodes.empty()) return;
3277 if (table().index) {
3278 for (auto const n : nodes) (void)ClassGraph{n}.ensure();
3281 // Find the "best" node to start with.
3282 auto head = *std::min_element(nodes.begin(), nodes.end(), betterNode<true>);
3283 if (!ClassGraph{head}.ensureWithChildren() ||
3284 head->isMissing() ||
3285 !head->hasCompleteChildren()) {
3286 // If the best node is missing or doesn't have complete children,
3287 // they all don't, so just supply the input list.
3288 for (auto const n : nodes) {
3289 assertx(
3290 n->isMissing() ||
3291 !n->hasCompleteChildren() ||
3292 !ClassGraph{n}.allowed(true)
3294 if (!f(*n)) break;
3296 return;
3299 // Otherwise report Nodes which are children of all of the Nodes.
3300 auto const run = [&] (auto tracker) {
3301 forEachChild(
3302 *head,
3303 [&] (Node& c) {
3304 if (nonReg || c.isRegular()) {
3305 if (tracker.all(c)) {
3306 if (!f(c)) return Action::Stop;
3307 return all ? Action::Continue : Action::Skip;
3310 return Action::Continue;
3315 if (nodes.size() <= SmallBitset::kMaxSize) {
3316 return run(ParentTracker<SmallBitset>{nodes});
3317 } else {
3318 return run(ParentTracker<LargeBitset>{nodes});
3322 // "Canonicalize" a set of intersected classes by removing redundant
3323 // Nodes and putting them in a deterministic order.
3324 ClassGraph::NodeVec ClassGraph::canonicalize(const NodeSet& nodes,
3325 bool nonReg) {
3326 NodeVec out;
3327 if (nodes.size() <= 1) {
3328 // Trivial cases
3329 out.insert(begin(nodes), end(nodes));
3330 return out;
3333 // Remove redundant nodes. A node is redundant if it is reachable
3334 // via another node. In that case, this node is implied by the other
3335 // and can be removed.
3336 auto heads = nodes;
3338 TLNodeIdxSet visited;
3339 for (auto const n : nodes) {
3340 if (!heads.count(n)) continue;
3341 auto const allowed = ClassGraph{n}.ensure();
3342 if (!allowed || n->isMissing()) continue;
3343 forEachParent(
3345 [&] (Node& p) {
3346 if (&p == n) return Action::Continue;
3347 if (!nodes.count(&p)) return Action::Continue;
3348 if (!heads.count(&p)) return Action::Skip;
3349 heads.erase(&p);
3350 return Action::Continue;
3352 *visited
3354 visited->erase(*n);
3358 // A node can be redundant with another even they aren't reachable
3359 // via each other. This can only happen if we're considering only
3360 // regular nodes. If a class is a super type of another, it's
3361 // redundant, as the other class implies this class. If the classes
3362 // are both super types of each other, they're equivalent, and we
3363 // keep the "best" one.
3364 if (!nonReg) {
3365 for (auto const n1 : heads) {
3366 auto const isSuperType = [&] {
3367 for (auto const n2 : heads) {
3368 if (n1 == n2) continue;
3369 if (!ClassGraph{n2}.subSubtypeOf(ClassGraph{n1}, false, false)) {
3370 continue;
3372 if (!ClassGraph{n1}.subSubtypeOf(ClassGraph{n2}, false, false)) {
3373 return true;
3375 if (betterNode<true>(n2, n1)) return true;
3377 return false;
3378 }();
3379 if (!isSuperType) out.emplace_back(n1);
3381 } else {
3382 out.insert(begin(heads), end(heads));
3385 // Finally sort them according to how good they are.
3386 std::sort(out.begin(), out.end(), betterNode<false>);
3387 return out;
3390 template <typename F>
3391 ClassGraph::Action ClassGraph::forEachParent(Node& n, const F& f, NodeIdxSet& v) {
3392 return forEachParentImpl(n, f, &v, true);
3395 template <typename F>
3396 ClassGraph::Action ClassGraph::forEachParent(Node& n, const F& f) {
3397 TLNodeIdxSet v;
3398 return forEachParentImpl(n, f, &*v, true);
3401 template <typename F>
3402 ClassGraph::Action ClassGraph::forEachParentImpl(Node& n,
3403 const F& f,
3404 NodeIdxSet* v,
3405 bool start) {
3406 assertx(!n.isMissing());
3407 if (v && !v->add(n)) return Action::Skip;
3408 if (start || !n.isTrait()) {
3409 auto const action = f(n);
3410 if (action != Action::Continue) return action;
3412 for (auto const parent : n.parents) {
3413 if (forEachParentImpl(*parent, f, v, false) == Action::Stop) {
3414 return Action::Stop;
3417 return Action::Continue;
3420 template <typename F, typename F2, typename T>
3421 T ClassGraph::foldParentsImpl(Node& n,
3422 const F& f,
3423 const F2& f2,
3424 NodeMap<T>& m,
3425 bool start) {
3426 assertx(!n.isMissing());
3427 if (auto const t = folly::get_ptr(m, &n)) return *t;
3428 T t = (start || !n.isTrait()) ? f(n) : f2();
3429 for (auto const parent : n.parents) {
3430 if (t) break;
3431 t |= foldParentsImpl(*parent, f, f2, m, false);
3433 m.insert_or_assign(&n, t);
3434 return t;
3437 bool ClassGraph::findParent(Node& n1, Node& n2) {
3438 TLNodeIdxSet visited;
3439 return findParent(n1, n2, *visited);
3442 bool ClassGraph::findParent(Node& start, Node& target, NodeIdxSet& visited) {
3443 assertx(!start.isMissing());
3445 static thread_local hphp_fast_map<std::pair<Node*, Node*>, bool> cache;
3446 if (auto const r = folly::get_ptr(cache, std::make_pair(&start, &target))) {
3447 return *r;
3450 auto const action = forEachParent(
3451 start,
3452 [&] (Node& p) { return (&p == &target) ? Action::Stop : Action::Continue; },
3453 visited
3456 cache.try_emplace(
3457 std::make_pair(&start, &target),
3458 action == Action::Stop
3460 return action == Action::Stop;
3463 ClassGraph::NodeSet ClassGraph::allParents(Node& n) {
3464 assertx(!n.isMissing());
3465 NodeSet s;
3466 forEachParent(
3468 [&] (Node& p) {
3469 s.emplace(&p);
3470 return Action::Continue;
3473 return s;
3476 template <typename F>
3477 ClassGraph::Action ClassGraph::forEachChild(Node& n, const F& f, NodeIdxSet& v) {
3478 return forEachChildImpl(n, f, &v, true);
3481 template <typename F>
3482 ClassGraph::Action ClassGraph::forEachChild(Node& n, const F& f) {
3483 TLNodeIdxSet v;
3484 return forEachChildImpl(n, f, &*v, true);
3487 template <typename F>
3488 ClassGraph::Action
3489 ClassGraph::forEachChildImpl(Node& n, const F& f, NodeIdxSet* v, bool start) {
3490 assertx(!n.isMissing());
3491 if (v && !v->add(n)) return Action::Skip;
3492 auto const action = f(n);
3493 if (action != Action::Continue) return action;
3494 if (start && n.isTrait()) return action;
3495 for (auto const child : n.children) {
3496 if (forEachChildImpl(*child, f, v, false) == Action::Stop) {
3497 return Action::Stop;
3500 return Action::Continue;
3503 ClassGraph ClassGraph::create(const php::Class& cls) {
3504 assertx(!table().locking);
3506 auto const& [n, emplaced] = table().nodes.try_emplace(cls.name);
3507 always_assert_flog(
3508 emplaced,
3509 "Attempting to create already existing ClassGraph node '{}'",
3510 cls.name
3512 assertx(!n->second.name);
3513 assertx(n->second.flags() == FlagNone);
3514 assertx(!n->second.cinfo());
3515 assertx(!n->second.cinfo2());
3516 n->second.name = cls.name;
3517 n->second.idx = table().nodes.size();
3519 auto f = FlagNone;
3520 if (cls.attrs & AttrInterface) f = Flags(f | FlagInterface);
3521 if (cls.attrs & AttrTrait) f = Flags(f | FlagTrait);
3522 if (cls.attrs & AttrAbstract) f = Flags(f | FlagAbstract);
3523 if (cls.attrs & (AttrEnum | AttrEnumClass)) f = Flags(f | FlagEnum);
3524 n->second.setFlags(f);
3526 return ClassGraph{ &n->second };
3529 ClassGraph ClassGraph::get(SString name) {
3530 assertx(!table().locking);
3531 auto n = folly::get_ptr(table().nodes, name);
3532 always_assert_flog(
3534 "Attempting to retrieve missing ClassGraph node '{}'",
3535 name
3537 assertx(n->name->tsame(name));
3538 return ClassGraph{ n };
3541 ClassGraph ClassGraph::getOrCreate(SString name) {
3542 assertx(!table().locking);
3544 auto const& [n, emplaced] = table().nodes.try_emplace(name);
3545 if (emplaced) {
3546 assertx(!n->second.name);
3547 assertx(n->second.flags() == FlagNone);
3548 assertx(!n->second.cinfo());
3549 assertx(!n->second.cinfo2());
3550 n->second.name = name;
3551 n->second.idx = table().nodes.size();
3552 n->second.setFlags(FlagMissing);
3553 } else {
3554 assertx(n->second.name->tsame(name));
3556 return ClassGraph{ &n->second };
3559 ClassGraph ClassGraph::getMissing(SString name) {
3560 assertx(!table().locking);
3562 auto const& [n, emplaced] = table().nodes.try_emplace(name);
3563 if (emplaced) {
3564 assertx(!n->second.name);
3565 assertx(n->second.flags() == FlagNone);
3566 assertx(!n->second.cinfo());
3567 assertx(!n->second.cinfo2());
3568 n->second.name = name;
3569 n->second.idx = table().nodes.size();
3570 n->second.setFlags(FlagMissing);
3571 } else {
3572 assertx(n->second.name->tsame(name));
3573 assertx(n->second.flags() == FlagMissing);
3575 return ClassGraph{ &n->second };
3578 void ClassGraph::init() {
3579 always_assert(!g_table);
3580 g_table = std::make_unique<Table>();
3583 void ClassGraph::initConcurrent() {
3584 always_assert(!g_table);
3585 g_table = std::make_unique<Table>();
3586 g_table->locking.emplace();
3589 void ClassGraph::stopConcurrent() {
3590 always_assert(g_table);
3591 g_table->locking.reset();
3594 void ClassGraph::destroy() {
3595 assertx(IMPLIES(g_table, !g_table->index));
3596 g_table.reset();
3599 ClassGraph::Table& ClassGraph::table() {
3600 always_assert_flog(
3601 g_table,
3602 "Attempting to access ClassGraph node table when one isn't active!"
3604 return *g_table;
3607 void ClassGraph::setAnalysisIndex(AnalysisIndex::IndexData& index) {
3608 assertx(!table().locking);
3609 assertx(!table().index);
3610 table().index = &index;
3613 void ClassGraph::clearAnalysisIndex() {
3614 assertx(!table().locking);
3615 assertx(table().index);
3616 table().index = nullptr;
3619 // Set the FlagComplete/FlagConservative and FlagRegSub/FlagNonRegSub
3620 // flags in this class and it's children. Return the
3621 // FlagRegSub/FlagNonRegSub flags from the children, along with the
3622 // count of the subclass list from that child. If std::nullopt is
3623 // given as the count, we've exceeded the limit we want to track.
3624 template <typename Impl>
3625 std::pair<ClassGraph::Flags, Optional<size_t>>
3626 ClassGraph::setCompleteImpl(const Impl& impl, Node& n) {
3627 assertx(!n.isMissing());
3629 // Conservative nodes don't have children. However, they're
3630 // guaranteed to have their FlagRegSub and FlagNonRegSub flags set
3631 // properly, so we don't need to.
3632 if (n.hasCompleteChildren() || n.isConservative()) {
3633 auto f = FlagNone;
3634 if (n.isRegular() || n.hasRegularSubclass()) {
3635 f = (Flags)(f | FlagRegSub);
3637 if (!n.isRegular() || n.hasNonRegularSubclass()) {
3638 f = (Flags)(f | FlagNonRegSub);
3640 if (n.hasCompleteChildren()) {
3641 // If we know all the children, the list should be in canonical
3642 // order.
3643 if (debug) {
3644 impl.lock(
3646 [&] {
3647 always_assert(
3648 std::is_sorted(
3649 begin(n.children),
3650 end(n.children),
3651 Node::Compare{}
3657 return std::make_pair(
3659 n.children.empty() ? 1 : impl.getCompleteSize(n)
3662 return std::make_pair(f, std::nullopt);
3665 // Otherwise aggregate the flags and counts from the children.
3666 auto flags = FlagNone;
3667 Optional<size_t> count;
3668 count.emplace(1);
3670 if (!n.children.empty()) {
3671 impl.forEachChild(
3673 [&] (Node& child) {
3674 auto const [f, c] = setCompleteImpl(impl, child);
3675 flags = (Flags)(flags | f);
3676 if (count) {
3677 if (c) {
3678 *count += *c;
3679 } else {
3680 count.reset();
3687 if (!count || *count > options.preciseSubclassLimit) {
3688 // The child is conservative, or we've exceeded the subclass list
3689 // limit. Mark this node as being conservative.
3690 setConservative(impl, n, flags & FlagRegSub, flags & FlagNonRegSub);
3691 count.reset();
3692 } else {
3693 // Didn't have complete children, but now does. Update the flags.
3694 if (!n.children.empty()) impl.setCompleteSize(n, *count);
3695 impl.lock(
3697 [&] { std::sort(begin(n.children), end(n.children), Node::Compare{}); }
3699 impl.updateFlags(n, (Flags)(flags | FlagChildren));
3702 if (n.isRegular()) flags = (Flags)(flags | FlagRegSub);
3703 if (!n.isRegular()) flags = (Flags)(flags | FlagNonRegSub);
3704 return std::make_pair(flags, count);
3707 // Make a node conservative (does not have any child information).
3708 template <typename Impl>
3709 void ClassGraph::setConservative(const Impl& impl,
3710 Node& n,
3711 bool regSub,
3712 bool nonRegSub) {
3713 assertx(!n.isMissing());
3714 assertx(!n.hasCompleteChildren());
3716 if (n.isConservative()) {
3717 assertx(n.hasRegularSubclass() == regSub);
3718 assertx(n.hasNonRegularSubclass() == nonRegSub);
3719 return;
3722 assertx(!n.hasRegularSubclass());
3723 assertx(!n.hasNonRegularSubclass());
3725 auto f = FlagConservative;
3726 if (regSub) f = (Flags)(f | FlagRegSub);
3727 if (nonRegSub) f = (Flags)(f | FlagNonRegSub);
3729 impl.updateFlags(n, f);
3730 impl.lock(n, [&] { n.children.clear(); });
3733 template <typename SerDe, typename T>
3734 void ClassGraph::serde(SerDe& sd, T cinfo, bool ignoreChildren) {
3735 // Serialization/deserialization entry point. If we're operating
3736 // concurrently, use one Impl, otherwise, use the other.
3737 if (SerDe::deserializing && table().locking) {
3738 serdeImpl(sd, LockedSerdeImpl{}, cinfo, ignoreChildren);
3739 } else {
3740 serdeImpl(sd, UnlockedSerdeImpl{}, cinfo, ignoreChildren);
3744 template <typename SerDe, typename Impl, typename T>
3745 void ClassGraph::serdeImpl(SerDe& sd,
3746 const Impl& impl,
3747 T cinfo,
3748 bool ignoreChildren) {
3749 // Allocate SerdeState if someone else hasn't already.
3750 ScopedSerdeState _;
3752 sd.alternate(
3753 [&] {
3754 if constexpr (SerDe::deserializing) {
3755 // Deserializing:
3757 // First ensure that all nodes reachable by this node are
3758 // deserialized.
3759 sd.readWithLazyCount([&] { deserBlock(sd, impl); });
3761 // Then obtain a pointer to the node that ClassGraph points
3762 // to.
3763 if (auto const name = decodeName(sd)) {
3764 this_ = &impl.get(name);
3766 // If this node was marked as having complete children (and
3767 // we're not ignoring that), mark this node and all of it's
3768 // transitive children as also having complete children.
3769 Flags flags;
3770 sd(flags);
3771 if (flags & FlagChildren) {
3772 assertx(flags == FlagChildren);
3773 assertx(!this_->isConservative());
3774 if (!this_->hasCompleteChildren()) {
3775 setCompleteImpl(impl, *this_);
3776 assertx(this_->hasCompleteChildren());
3778 } else if (flags & FlagConservative) {
3779 assertx(flags == (flags & (FlagConservative |
3780 FlagRegSub | FlagNonRegSub)));
3781 setConservative(
3782 impl,
3783 *this_,
3784 flags & FlagRegSub,
3785 flags & FlagNonRegSub
3787 } else {
3788 assertx(flags == FlagNone);
3791 // If this node isn't regular, and we've recorded an equivalent
3792 // node for it, make sure that the equivalent is
3793 // hasCompleteChildren(), as that's an invariant.
3794 if (auto const equiv = decodeName(sd)) {
3795 auto const equivNode = &impl.get(equiv);
3796 assertx(!equivNode->isRegular());
3797 if (!equivNode->hasCompleteChildren()) {
3798 assertx(!equivNode->isConservative());
3799 setCompleteImpl(impl, *equivNode);
3800 assertx(equivNode->hasCompleteChildren());
3802 impl.setEquiv(*this_, *equivNode);
3805 if constexpr (!std::is_null_pointer_v<T>) {
3806 if (cinfo) {
3807 assertx(!this_->isMissing());
3808 impl.setCInfo(*this_, *cinfo);
3811 } else {
3812 this_ = nullptr;
3814 } else {
3815 // Serializing:
3817 if (!tl_serde_state->upward) tl_serde_state->upward.emplace();
3818 if (!tl_serde_state->downward) tl_serde_state->downward.emplace();
3820 // Serialize all of the nodes reachable by this node (parents,
3821 // children, and parents of children) and encode how many.
3822 sd.lazyCount(
3823 [&] () -> size_t {
3824 if (!this_) return 0;
3825 // Only encode children if requested.
3826 auto count = ignoreChildren
3827 ? serUpward(sd, *this_)
3828 : serDownward(sd, *this_);
3829 if (ignoreChildren) return count;
3830 if (auto const e =
3831 folly::get_default(table().regOnlyEquivs, this_)) {
3832 assertx(!this_->isRegular());
3833 assertx(e->hasCompleteChildren());
3834 count += serDownward(sd, *e);
3836 return count;
3839 // Encode the "entry-point" into the graph represented by this
3840 // ClassGraph.
3841 if (this_) {
3842 encodeName(sd, this_->name);
3843 // Record whether this node has complete children, so we can
3844 // reconstruct that when deserializing.
3845 assertx(IMPLIES(hasCompleteChildren(), !isConservative()));
3846 assertx(IMPLIES(isConservative(), this_->children.empty()));
3847 assertx(IMPLIES(!hasCompleteChildren() && !isConservative(),
3848 !this_->hasRegularSubclass() &&
3849 !this_->hasNonRegularSubclass()));
3851 auto mask = (Flags)(FlagChildren | FlagConservative);
3852 if (this_->isConservative()) {
3853 mask = (Flags)(mask | FlagRegSub | FlagNonRegSub);
3855 if (ignoreChildren) mask = (Flags)(mask & ~FlagChildren);
3856 sd((Flags)(this_->flags() & mask));
3858 // If this Node isn't regular and has an equivalent node, record
3859 // that here.
3860 if (!ignoreChildren && !this_->isMissing() && !this_->isRegular()) {
3861 if (auto const e =
3862 folly::get_default(table().regOnlyEquivs, this_)) {
3863 assertx(e->hasCompleteChildren());
3864 encodeName(sd, e->name);
3865 } else {
3866 encodeName(sd, nullptr);
3868 } else {
3869 encodeName(sd, nullptr);
3871 } else {
3872 encodeName(sd, nullptr);
3876 [&] {
3877 // When serializing, we write this out last. When deserializing,
3878 // we read it first.
3879 assertx(tl_serde_state);
3880 sd(tl_serde_state->newStrings);
3881 tl_serde_state->strings.insert(
3882 end(tl_serde_state->strings),
3883 begin(tl_serde_state->newStrings),
3884 end(tl_serde_state->newStrings)
3886 tl_serde_state->newStrings.clear();
3891 // Serialize a string using the string table.
3892 template <typename SerDe>
3893 void ClassGraph::encodeName(SerDe& sd, SString s) {
3894 assertx(tl_serde_state);
3895 if (auto const idx = folly::get_ptr(tl_serde_state->strToIdx, s)) {
3896 sd(*idx);
3897 return;
3899 auto const idx =
3900 tl_serde_state->strings.size() + tl_serde_state->newStrings.size();
3901 tl_serde_state->newStrings.emplace_back(s);
3902 tl_serde_state->strToIdx.emplace(s, idx);
3903 sd(idx);
3906 // Deserialize a string using the string table.
3907 template <typename SerDe>
3908 SString ClassGraph::decodeName(SerDe& sd) {
3909 assertx(tl_serde_state);
3910 size_t idx;
3911 sd(idx);
3912 always_assert(idx < tl_serde_state->strings.size());
3913 return tl_serde_state->strings[idx];
3916 // Deserialize a node, along with any other nodes it depends on.
3917 template <typename SerDe, typename Impl>
3918 void ClassGraph::deserBlock(SerDe& sd, const Impl& impl) {
3919 // First get the name for this node.
3920 auto const name = decodeName(sd);
3921 assertx(name);
3923 Flags flags;
3924 sd(flags);
3925 // These flags are never encoded and only exist at runtime.
3926 assertx((flags & kSerializable) == flags);
3927 assertx(IMPLIES(flags & FlagMissing, flags == FlagMissing));
3929 // Try to create it:
3930 auto const [node, created] = impl.create(name);
3931 if (created || node->isMissing()) {
3932 // If this is the first time we've seen this node, deserialize any
3933 // dependent nodes.
3934 sd.withSize(
3935 [&, node=node] {
3936 assertx(!node->hasCompleteChildren());
3937 assertx(!node->isConservative());
3939 // Either it already existed and we got an existing Node, or we
3940 // created it. Even if it already existed, we still need to process
3941 // it below as if it was new, because this might have additional
3942 // flags to add to the Node.
3944 // Deserialize dependent nodes.
3945 sd.readWithLazyCount([&] { deserBlock(sd, impl); });
3947 // At this point all dependent nodes are guaranteed to exist.
3949 // Read the parent links. The children links are not encoded as
3950 // they can be inferred from the parent links.
3951 CompactVector<Node*> parents;
3953 size_t size;
3954 sd(size);
3955 parents.reserve(size);
3956 for (size_t i = 0; i < size; ++i) {
3957 // This should always succeed because all dependents
3958 // should exist.
3959 parents.emplace_back(&impl.get(decodeName(sd)));
3961 parents.shrink_to_fit();
3964 // If this is a "missing" node, it shouldn't have any links
3965 // (because we shouldn't know anything about it).
3966 assertx(IMPLIES(flags & FlagMissing, parents.empty()));
3967 assertx(std::is_sorted(begin(parents), end(parents), Node::Compare{}));
3969 // For each parent, register this node as a child. Lock the
3970 // appropriate node if we're concurrent deserializing.
3971 for (auto const parent : parents) {
3972 impl.lock(
3973 *parent,
3974 [&, node=node] {
3975 if (parent->hasCompleteChildren() ||
3976 parent->isConservative()) {
3977 return;
3979 parent->children.emplace_back(node);
3984 impl.lock(
3985 *node,
3986 [&, node=node] { node->parents = std::move(parents); }
3991 if (created) {
3992 // If we created this node, we need to clear FlagWait and
3993 // simultaneously set the node's flags to what we decoded.
3994 impl.signal(*node, flags);
3995 } else if (!(flags & FlagMissing)) {
3996 impl.updateFlags(*node, flags, FlagMissing);
3998 } else {
3999 // Otherwise skip over the dependent nodes.
4000 always_assert(flags == FlagMissing ||
4001 flags == (node->flags() & kSerializable));
4002 sd.skipWithSize();
4006 // Walk downward through a node's children until we hit a leaf. At
4007 // that point, we call serUpward on the leaf, which will serialize it
4008 // and all of it's parents (which should include all nodes traversed
4009 // here). Return the number of nodes serialized.
4010 template <typename SerDe>
4011 size_t ClassGraph::serDownward(SerDe& sd, Node& n) {
4012 assertx(!table().locking);
4013 assertx(tl_serde_state);
4014 if (!tl_serde_state->downward->add(n)) return 0;
4016 if (n.children.empty() || !n.hasCompleteChildren()) {
4017 return serUpward(sd, n);
4019 assertx(!n.isConservative());
4020 assertx(std::is_sorted(begin(n.children), end(n.children), Node::Compare{}));
4022 size_t count = 0;
4023 for (auto const child : n.children) {
4024 count += serDownward(sd, *child);
4026 return count;
4029 // Serialize the given node, along with all of it's parents. Return
4030 // true if anything was serialized.
4031 template <typename SerDe>
4032 bool ClassGraph::serUpward(SerDe& sd, Node& n) {
4033 assertx(!table().locking);
4034 assertx(tl_serde_state);
4035 // If we've already serialized this node, no need to serialize it
4036 // again.
4037 if (!tl_serde_state->upward->add(n)) return false;
4039 assertx(n.name);
4040 assertx(IMPLIES(n.isMissing(), n.parents.empty()));
4041 assertx(IMPLIES(n.isMissing(), n.children.empty()));
4042 assertx(IMPLIES(n.isMissing(), n.flags() == FlagMissing));
4043 assertx(IMPLIES(n.isConservative(), n.children.empty()));
4044 assertx(IMPLIES(!n.hasCompleteChildren() && !n.isConservative(),
4045 !n.hasRegularSubclass()));
4046 assertx(IMPLIES(!n.hasCompleteChildren() && !n.isConservative(),
4047 !n.hasNonRegularSubclass()));
4048 assertx(std::is_sorted(begin(n.parents), end(n.parents), Node::Compare{}));
4050 encodeName(sd, n.name);
4051 // Shouldn't have any FlagWait when serializing.
4052 assertx(!(n.flags() & FlagWait));
4053 sd((Flags)(n.flags() & kSerializable));
4055 sd.withSize(
4056 [&] {
4057 // Recursively serialize all parents of this node. This ensures
4058 // that when deserializing, the parents will be available before
4059 // deserializing this node.
4060 sd.lazyCount(
4061 [&] {
4062 size_t count = 0;
4063 for (auto const parent : n.parents) {
4064 count += serUpward(sd, *parent);
4066 return count;
4070 // Record the names of the parents, to restore the links when
4071 // deserializing.
4072 sd(n.parents.size());
4073 for (auto const p : n.parents) {
4074 assertx(p->name);
4075 encodeName(sd, p->name);
4080 return true;
4083 //////////////////////////////////////////////////////////////////////
4085 // Storage for the auxiliary ClassGraphs a class or func may need.
4086 struct AuxClassGraphs {
4087 // Nodes for which we don't need children.
4088 hphp_fast_set<ClassGraph, ClassGraphHasher> noChildren;
4089 // Nodes for which we have and need children.
4090 hphp_fast_set<ClassGraph, ClassGraphHasher> withChildren;
4092 // Equivalent to the above, but Nodes added within the current
4093 // worker. These are not serialized, but will replace noChildren and
4094 // withChildren when the worker is finishing.
4095 hphp_fast_set<ClassGraph, ClassGraphHasher> newNoChildren;
4096 hphp_fast_set<ClassGraph, ClassGraphHasher> newWithChildren;
4098 template <typename SerDe> void serde(SerDe& sd) {
4099 sd(noChildren, std::less<>{}, nullptr, true)
4100 (withChildren, std::less<>{}, nullptr, false)
4102 // newNoChildren and newWithChildren deliberately not serialized.
4106 //////////////////////////////////////////////////////////////////////
4108 template <typename SerDe> void FuncInfo2::serde(SerDe& sd) {
4109 ScopedStringDataIndexer _;
4110 ClassGraph::ScopedSerdeState _2;
4111 sd(name)
4112 (returnTy)
4113 (retParam)
4114 (returnRefinements)
4115 (effectFree)
4116 (unusedParams)
4119 if constexpr (SerDe::deserializing) {
4120 bool present;
4121 sd(present);
4122 if (present) {
4123 auxClassGraphs = std::make_unique<AuxClassGraphs>(
4124 sd.template make<AuxClassGraphs>()
4127 } else {
4128 sd((bool)auxClassGraphs);
4129 if (auxClassGraphs) sd(*auxClassGraphs);
4133 //////////////////////////////////////////////////////////////////////
4136 * Known information about an instantiatiable class.
4138 struct ClassInfo {
4140 * A pointer to the underlying php::Class that we're storing
4141 * information about.
4143 const php::Class* cls = nullptr;
4146 * The info for the parent of this Class.
4148 ClassInfo* parent = nullptr;
4150 struct ConstIndex {
4151 const php::Const& operator*() const {
4152 return cls->constants[idx];
4154 const php::Const* operator->() const {
4155 return get();
4157 const php::Const* get() const {
4158 return &cls->constants[idx];
4160 const php::Class* cls;
4161 uint32_t idx;
4165 * A (case-sensitive) map from class constant name to the php::Class* and
4166 * index into the constants vector that it came from. This map is flattened
4167 * across the inheritance hierarchy. Use a vector_map for stable iteration.
4169 hphp_vector_map<SString, ConstIndex> clsConstants;
4172 * Inferred class constant types for the constants declared on this
4173 * class. The order mirrors the order in the php::Class constants
4174 * vector. If the vector is smaller than a constant's index, that
4175 * constant's type is implicitly TInitCell.
4177 CompactVector<ClsConstInfo> clsConstTypes;
4180 * The traits used by this class, which *haven't* been flattened
4181 * into it. If the class is AttrNoExpandTrait, this will always be
4182 * empty.
4184 CompactVector<const ClassInfo*> usedTraits;
4187 * A list of extra properties supplied by this class's used traits.
4189 CompactVector<php::Prop> traitProps;
4192 * A (case-sensitive) map from class method names to the php::Func
4193 * associated with it. This map is flattened across the inheritance
4194 * hierarchy. There's a lot of these, so we use a sorted_vector_map
4195 * to minimize wasted space.
4197 folly::sorted_vector_map<SString, MethTabEntry> methods;
4200 * A (case-sensitive) map from class method names to associated
4201 * FuncFamilyOrSingle objects that represent the set of
4202 * possibly-overriding methods.
4204 * In addition to the set of methods, a bit is also set indicating
4205 * whether the set of "complete" or not. A complete set means the
4206 * ultimate method will definitely be one in the set. An incomplete
4207 * set means that the ultimate method will either be one in the set,
4208 * or won't resolve to anything (a missing function).
4210 * We do not encode "special" methods in these, as their semantics
4211 * are special and it's not useful.
4213 * For every method present in this ClassInfo's method table, there
4214 * will be an entry in methodFamilies. For regular classes, this
4215 * suffices for looking up information for both all subclasses and
4216 * the regular subset. For non-regular classes, the results for
4217 * "all" and the regular subset may differ. In that case, there is a
4218 * separate "aux" table containing the results for the regular
4219 * subset. If there is no associated entry in the aux table, the
4220 * result is the same as the entry in the normal table (this is a
4221 * common case and saves on memory). For regular classes the aux
4222 * table is always empty.
4224 * If a method is marked as AttrNoOverride, it will not have an
4225 * entry in these maps. If a method is marked as noOverrideRegular,
4226 * it will not have an entry in the aux map (if it would have
4227 * otherwise). In either case, the resolved method is assumed to be
4228 * the same method in this ClassInfo's method table.
4230 * The above is true for all class types. For abstract classes and
4231 * interfaces, however, there may be more entries here than present
4232 * in the methods table. These correspond to methods implemented by
4233 * *all* regular subclasses of the abstract class/interface. For
4234 * that reason, they will only be present in the regular variant of
4235 * the map. This is needed to preserve monotonicity (see comment in
4236 * BuildSubclassListJob::process_roots).
4238 folly::sorted_vector_map<SString, FuncFamilyOrSingle> methodFamilies;
4239 folly::sorted_vector_map<SString, FuncFamilyOrSingle> methodFamiliesAux;
4241 ClassGraph classGraph;
4244 * Property types for public static properties, declared on this exact class
4245 * (i.e. not flattened in the hierarchy).
4247 SStringToOneT<PublicSPropEntry> publicStaticProps;
4250 * Flags to track if this class is mocked, or if any of its derived classes
4251 * are mocked.
4253 bool isMocked{false};
4254 bool isSubMocked{false};
4257 * Track if this class has a property which might redeclare a property in a
4258 * parent class with an inequivalent type-hint.
4260 bool hasBadRedeclareProp{true};
4263 * Track if this class has any properties with initial values that might
4264 * violate their type-hints.
4266 bool hasBadInitialPropValues{true};
4269 * Track if this class has any const props (including inherited ones).
4271 bool hasConstProp{false};
4274 * Track if any derived classes (including this one) have any const props.
4276 bool subHasConstProp{false};
4279 * Track if this class has a reified parent.
4281 bool hasReifiedParent{false};
4285 * Known information about an instantiable class.
4287 * This class mirrors the ClassInfo struct, but is produced and used
4288 * by remote workers. As needed, this struct will gain more and more
4289 * of ClassInfo's fields (but stored in a more remote worker friendly
4290 * way). Local calculations will continue to use ClassInfo. Once
4291 * everything is converted to use remote workers, this struct will
4292 * subsume ClassInfo entirely (and be renamed).
4294 struct ClassInfo2 {
4296 * The name of the underlying php::Class that this ClassInfo
4297 * represents.
4299 LSString name{nullptr};
4302 * The name of the parent of this class (or nullptr if none).
4304 LSString parent{nullptr};
4307 * php::Class associated with this ClassInfo. Must be set manually
4308 * when needed.
4310 const php::Class* cls{nullptr};
4313 * A (case-sensitive) map from class constant name to the ConstIndex
4314 * representing the constant. This map is flattened across the
4315 * inheritance hierarchy.
4317 struct ConstIndexAndKind {
4318 ConstIndex idx;
4319 // Store the kind here as well, so we don't need to potentially
4320 // find another class to determine it.
4321 ConstModifiers::Kind kind;
4322 template <typename SerDe> void serde(SerDe& sd) {
4323 sd(idx)(kind);
4326 SStringToOneT<ConstIndexAndKind> clsConstants;
4329 * Inferred information about a class constant declared on this
4330 * class (not flattened).
4332 SStringToOneT<ClsConstInfo> clsConstantInfo;
4335 * A list of extra properties supplied by this class's used traits.
4337 CompactVector<php::Prop> traitProps;
4340 * A (case-sensitive) map from class method names to the
4341 * MethTabEntry associated with it. This map is flattened across the
4342 * inheritance hierarchy. MethTabEntry represents the php::Func,
4343 * along with some metadata specific to the method on this specific
4344 * class.
4346 SStringToOneT<MethTabEntry> methods;
4349 * In most situations, methods are inherited from a parent class, so
4350 * if a class declares a method, all of its subclasses are
4351 * guaranteed to possess it. An exception to this is with
4352 * interfaces, whose methods are not inherited.
4354 * However, when building func-families and other method data, its
4355 * useful to know all of the methods declared by all parents of a
4356 * class. Most of those methods will be on the method table for this
4357 * ClassInfo, but if any aren't, they'll be added to this set.
4359 SStringSet missingMethods;
4362 * ClassGraph representing this class. This ClassGraph is guaranteed
4363 * to have hasCompleteChildren() be true (except if this is the
4364 * Closure base class).
4366 ClassGraph classGraph;
4369 * Set of "extra" methods for this class. These are methods which
4370 * aren't formally part of this class, but must be analyzed along
4371 * with the class' methods. Examples are unflattened trait methods,
4372 * and the invoke methods of their associated closures.
4374 MethRefSet extraMethods;
4377 * A vector of the closures class-infos declared in this class. Such
4378 * closures are stored in their declaring class, not as a top-level
4379 * class.
4381 CompactVector<std::unique_ptr<ClassInfo2>> closures;
4384 * A (case-sensitive) map from method names to associated
4385 * FuncFamilyEntry objects that represent the set of
4386 * possibly-overriding methods.
4388 * We do not encode "special" methods in these, as their semantics
4389 * are special and it's not useful.
4391 * For every method present in this ClassInfo's method table, there
4392 * will be an entry in methodFamilies (unless if AttrNoOverride, see
4393 * below). The FuncFamilyEntry will provide the set of
4394 * possibly-overriding methods, for both the regular class subset
4395 * and all classes.
4397 * If a method is marked as AttrNoOverride, it will not have an
4398 * entry in this map. The resolved method is assumed to be the same
4399 * method in this ClassInfo's method table. If a method is marked as
4400 * noOverrideRegular, it will have an entry in this map, but can be
4401 * treated as AttrNoOverride if you're only considering regular
4402 * classes.
4404 * There may be more entries in methodFamilies than in the
4405 * ClassInfo's method table. For classes which aren't abstract and
4406 * aren't interfaces, this is an artifact of how the table is
4407 * created and can be ignored. For abstract classes and interfaces,
4408 * however, these extra entries correspond to methods implemented by
4409 * *all* regular subclasses of the abstract class/interface. In this
4410 * situation, only the data in the FuncFamilyEntry corresponding to
4411 * the regular subset is meaningful. This is needed to preserve
4412 * monotonicity (see comment in
4413 * BuildSubclassListJob::process_roots).
4415 SStringToOneT<FuncFamilyEntry> methodFamilies;
4418 * FuncInfo2s for the methods declared on this class (not
4419 * flattened). This is in the same order as the methods vector on
4420 * the associated php::Class.
4422 CompactVector<std::unique_ptr<FuncInfo2>> funcInfos;
4425 * If we utilize a ClassGraph while resolving types, we store it
4426 * here. This ensures that that ClassGraph will always be available
4427 * again.
4429 AuxClassGraphs auxClassGraphs;
4432 * Track if this class has a property which might redeclare a property in a
4433 * parent class with an inequivalent type-hint.
4435 bool hasBadRedeclareProp{true};
4438 * Track if this class has any properties with initial values that might
4439 * violate their type-hints.
4441 bool hasBadInitialPropValues{true};
4444 * Track if this class has any const props (including inherited ones).
4446 bool hasConstProp{false};
4449 * Track if any derived classes (including this one) have any const props.
4451 bool subHasConstProp{false};
4454 * Track if this class has a reified parent.
4456 bool hasReifiedParent{false};
4459 * Whether this class (or any derived classes) has a __Reified
4460 * attribute.
4462 bool hasReifiedGeneric{false};
4463 bool subHasReifiedGeneric{false};
4466 * Initial AttrNoReifiedInit setting of attrs (which might be
4467 * modified).
4469 bool initialNoReifiedInit{false};
4472 * Whether is_mock_class() is true for this class.
4474 bool isMockClass{false};
4477 * Whether this class is mocked (has a direct subclass for which
4478 * is_mock_class() is true).
4480 bool isMocked{false};
4483 * Whether isMocked is true for this class, or any of its
4484 * subclasses.
4486 bool isSubMocked{false};
4489 * Whether is_regular_class() is true for this class.
4491 bool isRegularClass{false};
4493 template <typename SerDe> void serde(SerDe& sd) {
4494 ScopedStringDataIndexer _;
4495 ClassGraph::ScopedSerdeState _2;
4496 sd(name)
4497 (parent)
4498 (clsConstants, string_data_lt{})
4499 (clsConstantInfo, string_data_lt{})
4500 (traitProps)
4501 (methods, string_data_lt{})
4502 (missingMethods, string_data_lt{})
4503 (classGraph, this)
4504 (extraMethods, std::less<MethRef>{})
4505 (closures)
4506 (methodFamilies, string_data_lt{})
4507 (funcInfos)
4508 (auxClassGraphs)
4509 (hasBadRedeclareProp)
4510 (hasBadInitialPropValues)
4511 (hasConstProp)
4512 (subHasConstProp)
4513 (hasReifiedParent)
4514 (hasReifiedGeneric)
4515 (subHasReifiedGeneric)
4516 (initialNoReifiedInit)
4517 (isMockClass)
4518 (isMocked)
4519 (isSubMocked)
4520 (isRegularClass)
4525 //////////////////////////////////////////////////////////////////////
4527 namespace res {
4529 Class::Class(ClassGraph g): opaque{g.this_} {
4530 assertx(g.this_);
4533 ClassGraph Class::graph() const {
4534 assertx(opaque.left());
4535 return ClassGraph{ (ClassGraph::Node*)opaque.left() };
4538 bool Class::isSerialized() const {
4539 return opaque.right();
4542 ClassInfo* Class::cinfo() const {
4543 return graph().cinfo();
4546 ClassInfo2* Class::cinfo2() const {
4547 return graph().cinfo2();
4550 bool Class::same(const Class& o) const {
4551 return graph() == o.graph();
4554 bool Class::exactSubtypeOfExact(const Class& o,
4555 bool nonRegL,
4556 bool nonRegR) const {
4557 return graph().exactSubtypeOfExact(o.graph(), nonRegL, nonRegR);
4560 bool Class::exactSubtypeOf(const Class& o, bool nonRegL, bool nonRegR) const {
4561 return graph().exactSubtypeOf(o.graph(), nonRegL, nonRegR);
4564 bool Class::subSubtypeOf(const Class& o, bool nonRegL, bool nonRegR) const {
4565 return graph().subSubtypeOf(o.graph(), nonRegL, nonRegR);
4568 bool Class::exactCouldBeExact(const Class& o,
4569 bool nonRegL,
4570 bool nonRegR) const {
4571 return graph().exactCouldBeExact(o.graph(), nonRegL, nonRegR);
4574 bool Class::exactCouldBe(const Class& o, bool nonRegL, bool nonRegR) const {
4575 return graph().exactCouldBe(o.graph(), nonRegL, nonRegR);
4578 bool Class::subCouldBe(const Class& o, bool nonRegL, bool nonRegR) const {
4579 return graph().subCouldBe(o.graph(), nonRegL, nonRegR);
4582 SString Class::name() const {
4583 if (opaque.left()) {
4584 return graph().name();
4585 } else {
4586 assertx(opaque.right());
4587 return opaque.right();
4591 Optional<res::Class> Class::withoutNonRegular() const {
4592 if (auto const g = graph().withoutNonRegular()) {
4593 return Class { g };
4594 } else {
4595 return std::nullopt;
4599 bool Class::mightBeRegular() const { return graph().mightBeRegular(); }
4600 bool Class::mightBeNonRegular() const { return graph().mightBeNonRegular(); }
4602 bool Class::couldBeOverridden() const {
4603 if (!graph().isMissing() && graph().isTrait()) return false;
4604 return
4605 graph().mightHaveRegularSubclass() ||
4606 graph().mightHaveNonRegularSubclass();
4609 bool Class::couldBeOverriddenByRegular() const {
4610 if (!graph().isMissing() && graph().isTrait()) return false;
4611 return graph().mightHaveRegularSubclass();
4614 bool Class::mightContainNonRegular() const {
4615 return graph().mightBeNonRegular() || graph().mightHaveNonRegularSubclass();
4618 bool Class::couldHaveMagicBool() const {
4619 auto const g = graph();
4620 if (!g.ensure()) return true;
4621 if (g.isMissing()) return true;
4622 if (!g.isInterface()) return has_magic_bool_conversion(g.topBase().name());
4623 if (!g.ensureWithChildren()) return true;
4624 if (!g.hasCompleteChildren()) return true;
4625 for (auto const c : g.children()) {
4626 if (has_magic_bool_conversion(c.topBase().name())) return true;
4628 return false;
4631 bool Class::couldHaveMockedSubClass() const {
4632 if (!graph().ensureCInfo()) {
4633 return true;
4634 } else if (auto const ci = cinfo()) {
4635 return ci->isSubMocked;
4636 } else if (auto const ci = cinfo2()) {
4637 return ci->isSubMocked;
4638 } else {
4639 return true;
4643 bool Class::couldBeMocked() const {
4644 if (!graph().ensureCInfo()) {
4645 return true;
4646 } else if (auto const ci = cinfo()) {
4647 return ci->isMocked;
4648 } else if (auto const ci = cinfo2()) {
4649 return ci->isMocked;
4650 } else {
4651 return true;
4655 bool Class::couldHaveReifiedGenerics() const {
4656 if (!graph().ensureCInfo()) {
4657 return true;
4658 } else if (auto const ci = cinfo()) {
4659 return ci->cls->hasReifiedGenerics;
4660 } else if (auto const ci = cinfo2()) {
4661 return ci->hasReifiedGeneric;
4662 } else {
4663 return true;
4667 bool Class::mustHaveReifiedGenerics() const {
4668 if (!graph().ensureCInfo()) {
4669 return false;
4670 } else if (auto const ci = cinfo()) {
4671 return ci->cls->hasReifiedGenerics;
4672 } else if (auto const ci = cinfo2()) {
4673 return ci->hasReifiedGeneric;
4674 } else {
4675 return false;
4679 bool Class::couldHaveReifiedParent() const {
4680 if (!graph().ensureCInfo()) {
4681 return true;
4682 } else if (auto const ci = cinfo()) {
4683 return ci->hasReifiedParent;
4684 } else if (auto const ci = cinfo2()) {
4685 return ci->hasReifiedParent;
4686 } else {
4687 return true;
4691 bool Class::mustHaveReifiedParent() const {
4692 if (!graph().ensureCInfo()) {
4693 return false;
4694 } else if (auto const ci = cinfo()) {
4695 return ci->hasReifiedParent;
4696 } else if (auto const ci = cinfo2()) {
4697 return ci->hasReifiedParent;
4698 } else {
4699 return false;
4703 bool Class::mightCareAboutDynConstructs() const {
4704 if (!Cfg::Eval::ForbidDynamicConstructs) return false;
4705 if (!graph().ensureCInfo()) {
4706 return true;
4707 } else if (auto const ci = cinfo()) {
4708 return !(ci->cls->attrs & AttrDynamicallyConstructible);
4709 } else if (auto const ci = cinfo2()) {
4710 return !ci->cls || !(ci->cls->attrs & AttrDynamicallyConstructible);
4711 } else {
4712 return true;
4716 bool Class::mightCareAboutDynamicallyReferenced() const {
4717 if (Cfg::Eval::DynamicallyReferencedNoticeSampleRate == 0) return false;
4718 if (!graph().ensureCInfo()) {
4719 return true;
4720 } else if (auto const ci = cinfo()) {
4721 return !(ci->cls->attrs & AttrDynamicallyReferenced);
4722 } else if (auto const ci = cinfo2()) {
4723 return !ci->cls || !(ci->cls->attrs & AttrDynamicallyReferenced);
4724 } else {
4725 return true;
4729 bool Class::couldHaveConstProp() const {
4730 if (!graph().ensureCInfo()) {
4731 return true;
4732 } else if (auto const ci = cinfo()) {
4733 return ci->hasConstProp;
4734 } else if (auto const ci = cinfo2()) {
4735 return ci->hasConstProp;
4736 } else {
4737 return true;
4741 bool Class::subCouldHaveConstProp() const {
4742 if (!graph().ensureCInfo()) {
4743 return true;
4744 } else if (auto const ci = cinfo()) {
4745 return ci->subHasConstProp;
4746 } else if (auto const ci = cinfo2()) {
4747 return ci->subHasConstProp;
4748 } else {
4749 return true;
4753 Optional<res::Class> Class::parent() const {
4754 if (auto const p = graph().base()) {
4755 return Class { p };
4756 } else {
4757 return std::nullopt;
4761 const php::Class* Class::cls() const {
4762 if (!graph().ensureCInfo()) {
4763 return nullptr;
4764 } else if (auto const ci = cinfo()) {
4765 return ci->cls;
4766 } else if (auto const ci = cinfo2()) {
4767 return ci->cls;
4768 } else {
4769 return nullptr;
4773 bool Class::hasCompleteChildren() const {
4774 return graph().hasCompleteChildren();
4777 bool Class::isComplete() const {
4778 return
4779 !graph().isMissing() &&
4780 (graph().hasCompleteChildren() || graph().isConservative());
4783 bool
4784 Class::forEachSubclass(const std::function<void(SString, Attr)>& f) const {
4785 auto const g = graph();
4786 if (!g.ensureWithChildren()) return false;
4787 if (g.isMissing() || !g.hasCompleteChildren()) return false;
4788 for (auto const c : g.children()) {
4789 auto const attrs = [&] {
4790 if (c.isInterface()) return AttrInterface;
4791 if (c.isTrait()) return AttrTrait;
4792 if (c.isEnum()) return AttrEnum;
4793 if (c.isAbstract()) return AttrAbstract;
4794 return AttrNone;
4795 }();
4796 f(c.name(), attrs);
4798 return true;
4801 std::string show(const Class& c) {
4802 if (auto const n = c.opaque.right()) {
4803 return folly::sformat("?\"{}\"", n);
4806 auto const g = c.graph();
4807 if (g.isMissingRaw()) return folly::sformat("\"{}\"", g.name());
4808 if (!g.hasCompleteChildrenRaw()) {
4809 if (g.isConservativeRaw()) {
4810 return folly::sformat(
4811 "{}*{}",
4812 (c.cinfo() || c.cinfo2()) ? "" : "-",
4813 g.name()
4816 return folly::sformat("!{}", g.name());
4818 if (!c.cinfo() && !c.cinfo2()) return folly::sformat("-{}", g.name());
4819 return g.name()->toCppString();
4822 // Call the given callable for every class which is a subclass of
4823 // *all* the classes in the range. If the range includes nothing but
4824 // unresolved classes, they will be passed, as-is, to the callable. If
4825 // the range includes a mix of resolved and unresolved classes, the
4826 // unresolved classes will be used to narrow the classes passed to the
4827 // callable, but the unresolved classes themself will not be passed to
4828 // the callable. If the callable returns false, iteration is
4829 // stopped. If includeNonRegular is true, non-regular subclasses are
4830 // visited (normally they are skipped).
4831 template <typename F>
4832 void Class::visitEverySub(folly::Range<const Class*> classes,
4833 bool includeNonRegular,
4834 const F& f) {
4835 if (classes.size() == 1) {
4836 auto const n = classes[0].graph().this_;
4837 if (!ClassGraph{n}.ensureWithChildren() ||
4838 n->isMissing() ||
4839 !n->hasCompleteChildren()) {
4840 f(Class{ClassGraph{ n }});
4841 return;
4843 ClassGraph::forEachChild(
4845 [&] (ClassGraph::Node& c) {
4846 assertx(!c.isMissing());
4847 if (includeNonRegular || c.isRegular()) {
4848 if (!f(Class{ClassGraph{ &c }})) return ClassGraph::Action::Stop;
4850 return ClassGraph::Action::Continue;
4853 return;
4856 ClassGraph::NodeVec v;
4857 v.reserve(classes.size());
4858 for (auto const c : classes) v.emplace_back(c.graph().this_);
4860 ClassGraph::enumerateIsectMembers(
4862 includeNonRegular,
4863 [&] (ClassGraph::Node& c) { return f(Class{ClassGraph{ &c }}); },
4864 true
4868 Class::ClassVec Class::combine(folly::Range<const Class*> classes1,
4869 folly::Range<const Class*> classes2,
4870 bool isSub1,
4871 bool isSub2,
4872 bool nonRegular1,
4873 bool nonRegular2) {
4874 ClassGraph::NodeVec v1;
4875 ClassGraph::NodeVec v2;
4876 v1.reserve(classes1.size());
4877 v2.reserve(classes2.size());
4878 for (auto const c : classes1) v1.emplace_back(c.graph().this_);
4879 for (auto const c : classes2) v2.emplace_back(c.graph().this_);
4880 auto const i =
4881 ClassGraph::combine(v1, v2, isSub1, isSub2, nonRegular1, nonRegular2);
4882 ClassVec out;
4883 out.reserve(i.size());
4884 for (auto const c : i) out.emplace_back(Class{ClassGraph { c }});
4885 return out;
4888 Class::ClassVec Class::removeNonRegular(folly::Range<const Class*> classes) {
4889 ClassGraph::NodeVec v;
4890 v.reserve(classes.size());
4891 for (auto const c : classes) v.emplace_back(c.graph().this_);
4892 auto const i = ClassGraph::removeNonReg(v);
4893 ClassVec out;
4894 out.reserve(i.size());
4895 for (auto const c : i) out.emplace_back(Class{ClassGraph { c }});
4896 return out;
4899 Class::ClassVec Class::intersect(folly::Range<const Class*> classes1,
4900 folly::Range<const Class*> classes2,
4901 bool nonRegular1,
4902 bool nonRegular2,
4903 bool& nonRegularOut) {
4904 ClassGraph::NodeVec v1;
4905 ClassGraph::NodeVec v2;
4906 v1.reserve(classes1.size());
4907 v2.reserve(classes2.size());
4908 for (auto const c : classes1) v1.emplace_back(c.graph().this_);
4909 for (auto const c : classes2) v2.emplace_back(c.graph().this_);
4910 auto const i =
4911 ClassGraph::intersect(v1, v2, nonRegular1, nonRegular2, nonRegularOut);
4912 ClassVec out;
4913 out.reserve(i.size());
4914 for (auto const c : i) out.emplace_back(Class{ClassGraph { c }});
4915 return out;
4918 bool Class::couldBeIsect(folly::Range<const Class*> classes1,
4919 folly::Range<const Class*> classes2,
4920 bool nonRegular1,
4921 bool nonRegular2) {
4922 ClassGraph::NodeVec v1;
4923 ClassGraph::NodeVec v2;
4924 v1.reserve(classes1.size());
4925 v2.reserve(classes2.size());
4926 for (auto const c : classes1) v1.emplace_back(c.graph().this_);
4927 for (auto const c : classes2) v2.emplace_back(c.graph().this_);
4928 return ClassGraph::couldBeIsect(v1, v2, nonRegular1, nonRegular2);
4931 Optional<Class> Class::unserialize(const IIndex& index) const {
4932 if (opaque.left()) return *this;
4933 return index.resolve_class(opaque.right());
4936 Class Class::get(SString name) {
4937 return Class{ ClassGraph::get(name) };
4940 Class Class::get(const ClassInfo& cinfo) {
4941 assertx(cinfo.classGraph);
4942 return Class{ cinfo.classGraph };
4945 Class Class::get(const ClassInfo2& cinfo) {
4946 assertx(cinfo.classGraph);
4947 return Class{ cinfo.classGraph };
4950 Class Class::getOrCreate(SString name) {
4951 return Class{ ClassGraph::getOrCreate(name) };
4954 Class Class::getUnresolved(SString name) {
4955 return Class{ ClassGraph::getMissing(name) };
4958 void Class::makeConservativeForTest() {
4959 auto n = graph().this_;
4960 n->setFlags(ClassGraph::FlagConservative, ClassGraph::FlagChildren);
4961 n->children.clear();
4964 #ifndef NDEBUG
4965 bool Class::isMissingDebug() const {
4966 return graph().isMissingRaw();
4968 #endif
4970 void Class::serde(BlobEncoder& sd) const {
4972 opaque.left()
4973 ? graph()
4974 : ClassGraph::get(opaque.right()),
4975 nullptr
4979 Class Class::makeForSerde(BlobDecoder& sd) {
4980 ClassGraph g;
4981 sd(g, nullptr);
4982 assertx(g.this_);
4983 // Make the class start out as serialized.
4984 return Class{ g.name() };
4987 //////////////////////////////////////////////////////////////////////
4989 Func::Func(Rep val)
4990 : val(val)
4993 std::string Func::name() const {
4994 return match<std::string>(
4995 val,
4996 [] (FuncName s) { return s.name->toCppString(); },
4997 [] (MethodName s) {
4998 if (s.cls) return folly::sformat("{}::{}", s.cls, s.name);
4999 return folly::sformat("???::{}", s.name);
5001 [] (Fun f) { return f.finfo->func->name->toCppString(); },
5002 [] (Fun2 f) { return f.finfo->name->toCppString(); },
5003 [] (Method m) { return func_fullname(*m.finfo->func); },
5004 [] (Method2 m) { return func_fullname(*m.finfo->func); },
5005 [] (MethodFamily fam) {
5006 return folly::sformat(
5007 "*::{}",
5008 fam.family->possibleFuncs().front().ptr()->name
5011 [] (MethodFamily2 fam) {
5012 return folly::sformat(
5013 "{}::{}",
5014 fam.family->m_id,
5015 fam.family->m_name
5018 [] (MethodOrMissing m) { return func_fullname(*m.finfo->func); },
5019 [] (MethodOrMissing2 m) { return func_fullname(*m.finfo->func); },
5020 [] (MissingFunc m) { return m.name->toCppString(); },
5021 [] (MissingMethod m) {
5022 if (m.cls) return folly::sformat("{}::{}", m.cls, m.name);
5023 return folly::sformat("???::{}", m.name);
5025 [] (const Isect& i) {
5026 assertx(i.families.size() > 1);
5027 return func_fullname(*i.families[0]->possibleFuncs().front().ptr());
5029 [] (const Isect2& i) {
5030 assertx(i.families.size() > 1);
5031 using namespace folly::gen;
5032 return folly::sformat(
5033 "{}::{}",
5034 from(i.families)
5035 | map([] (const FuncFamily2* ff) { return ff->m_id.toString(); })
5036 | unsplit<std::string>("&"),
5037 i.families[0]->m_name
5043 const php::Func* Func::exactFunc() const {
5044 using Ret = const php::Func*;
5045 return match<Ret>(
5046 val,
5047 [] (FuncName) { return Ret{}; },
5048 [] (MethodName) { return Ret{}; },
5049 [] (Fun f) { return f.finfo->func; },
5050 [] (Fun2 f) { return f.finfo->func; },
5051 [] (Method m) { return m.finfo->func; },
5052 [] (Method2 m) { return m.finfo->func; },
5053 [] (MethodFamily) { return Ret{}; },
5054 [] (MethodFamily2) { return Ret{}; },
5055 [] (MethodOrMissing) { return Ret{}; },
5056 [] (MethodOrMissing2) { return Ret{}; },
5057 [] (MissingFunc) { return Ret{}; },
5058 [] (MissingMethod) { return Ret{}; },
5059 [] (const Isect&) { return Ret{}; },
5060 [] (const Isect2&) { return Ret{}; }
5064 TriBool Func::exists() const {
5065 return match<TriBool>(
5066 val,
5067 [] (FuncName) { return TriBool::Maybe; },
5068 [] (MethodName) { return TriBool::Maybe; },
5069 [] (Fun) { return TriBool::Yes; },
5070 [] (Fun2) { return TriBool::Yes; },
5071 [] (Method) { return TriBool::Yes; },
5072 [] (Method2) { return TriBool::Yes; },
5073 [] (MethodFamily) { return TriBool::Maybe; },
5074 [] (MethodFamily2) { return TriBool::Maybe; },
5075 [] (MethodOrMissing) { return TriBool::Maybe; },
5076 [] (MethodOrMissing2) { return TriBool::Maybe; },
5077 [] (MissingFunc) { return TriBool::No; },
5078 [] (MissingMethod) { return TriBool::No; },
5079 [] (const Isect&) { return TriBool::Maybe; },
5080 [] (const Isect2&) { return TriBool::Maybe; }
5084 bool Func::isFoldable() const {
5085 return match<bool>(
5086 val,
5087 [] (FuncName) { return false; },
5088 [] (MethodName) { return false; },
5089 [] (Fun f) {
5090 return f.finfo->func->attrs & AttrIsFoldable;
5092 [] (Fun2 f) {
5093 return f.finfo->func->attrs & AttrIsFoldable;
5095 [] (Method m) { return m.finfo->func->attrs & AttrIsFoldable; },
5096 [] (Method2 m) { return m.finfo->func->attrs & AttrIsFoldable; },
5097 [] (MethodFamily) { return false; },
5098 [] (MethodFamily2) { return false; },
5099 [] (MethodOrMissing) { return false; },
5100 [] (MethodOrMissing2){ return false; },
5101 [] (MissingFunc) { return false; },
5102 [] (MissingMethod) { return false; },
5103 [] (const Isect&) { return false; },
5104 [] (const Isect2&) { return false; }
5108 bool Func::couldHaveReifiedGenerics() const {
5109 return match<bool>(
5110 val,
5111 [] (FuncName s) { return true; },
5112 [] (MethodName) { return true; },
5113 [] (Fun f) { return f.finfo->func->isReified; },
5114 [] (Fun2 f) { return f.finfo->func->isReified; },
5115 [] (Method m) { return m.finfo->func->isReified; },
5116 [] (Method2 m) { return m.finfo->func->isReified; },
5117 [] (MethodFamily fa) {
5118 return fa.family->infoFor(fa.regularOnly).m_static->m_maybeReified;
5120 [] (MethodFamily2 fa) {
5121 return fa.family->infoFor(fa.regularOnly).m_maybeReified;
5123 [] (MethodOrMissing m) { return m.finfo->func->isReified; },
5124 [] (MethodOrMissing2 m) { return m.finfo->func->isReified; },
5125 [] (MissingFunc) { return false; },
5126 [] (MissingMethod) { return false; },
5127 [] (const Isect& i) {
5128 for (auto const ff : i.families) {
5129 if (!ff->infoFor(i.regularOnly).m_static->m_maybeReified) return false;
5131 return true;
5133 [] (const Isect2& i) {
5134 for (auto const ff : i.families) {
5135 if (!ff->infoFor(i.regularOnly).m_maybeReified) return false;
5137 return true;
5142 bool Func::mightCareAboutDynCalls() const {
5143 if (Cfg::Eval::NoticeOnBuiltinDynamicCalls && mightBeBuiltin()) {
5144 return true;
5146 auto const mightCareAboutFuncs =
5147 Cfg::Eval::ForbidDynamicCallsToFunc > 0;
5148 auto const mightCareAboutInstMeth =
5149 Cfg::Eval::ForbidDynamicCallsToInstMeth > 0;
5150 auto const mightCareAboutClsMeth =
5151 Cfg::Eval::ForbidDynamicCallsToClsMeth > 0;
5153 return match<bool>(
5154 val,
5155 [&] (FuncName) { return mightCareAboutFuncs; },
5156 [&] (MethodName) {
5157 return mightCareAboutClsMeth || mightCareAboutInstMeth;
5159 [&] (Fun f) {
5160 return dyn_call_error_level(f.finfo->func) > 0;
5162 [&] (Fun2 f) {
5163 return dyn_call_error_level(f.finfo->func) > 0;
5165 [&] (Method m) { return dyn_call_error_level(m.finfo->func) > 0; },
5166 [&] (Method2 m) { return dyn_call_error_level(m.finfo->func) > 0; },
5167 [&] (MethodFamily fa) {
5168 return
5169 fa.family->infoFor(fa.regularOnly).m_static->m_maybeCaresAboutDynCalls;
5171 [&] (MethodFamily2 fa) {
5172 return
5173 fa.family->infoFor(fa.regularOnly).m_maybeCaresAboutDynCalls;
5175 [&] (MethodOrMissing m) { return dyn_call_error_level(m.finfo->func) > 0; },
5176 [&] (MethodOrMissing2 m) { return dyn_call_error_level(m.finfo->func) > 0; },
5177 [&] (MissingFunc m) { return false; },
5178 [&] (MissingMethod m) { return false; },
5179 [&] (const Isect& i) {
5180 for (auto const ff : i.families) {
5181 if (!ff->infoFor(i.regularOnly).m_static->m_maybeCaresAboutDynCalls) {
5182 return false;
5185 return true;
5187 [&] (const Isect2& i) {
5188 for (auto const ff : i.families) {
5189 if (!ff->infoFor(i.regularOnly).m_maybeCaresAboutDynCalls) {
5190 return false;
5193 return true;
5198 bool Func::mightBeBuiltin() const {
5199 return match<bool>(
5200 val,
5201 [] (FuncName s) { return true; },
5202 [] (MethodName) { return true; },
5203 [] (Fun f) { return f.finfo->func->attrs & AttrBuiltin; },
5204 [] (Fun2 f) { return f.finfo->func->attrs & AttrBuiltin; },
5205 [] (Method m) { return m.finfo->func->attrs & AttrBuiltin; },
5206 [] (Method2 m) { return m.finfo->func->attrs & AttrBuiltin; },
5207 [] (MethodFamily fa) {
5208 return fa.family->infoFor(fa.regularOnly).m_static->m_maybeBuiltin;
5210 [] (MethodFamily2 fa) {
5211 return fa.family->infoFor(fa.regularOnly).m_maybeBuiltin;
5213 [] (MethodOrMissing m) { return m.finfo->func->attrs & AttrBuiltin; },
5214 [] (MethodOrMissing2 m) { return m.finfo->func->attrs & AttrBuiltin; },
5215 [] (MissingFunc m) { return false; },
5216 [] (MissingMethod m) { return false; },
5217 [] (const Isect& i) {
5218 for (auto const ff : i.families) {
5219 if (!ff->infoFor(i.regularOnly).m_static->m_maybeBuiltin) return false;
5221 return true;
5223 [] (const Isect2& i) {
5224 for (auto const ff : i.families) {
5225 if (!ff->infoFor(i.regularOnly).m_maybeBuiltin) return false;
5227 return true;
5232 uint32_t Func::minNonVariadicParams() const {
5233 return match<uint32_t>(
5234 val,
5235 [] (FuncName) { return 0; },
5236 [] (MethodName) { return 0; },
5237 [] (Fun f) { return numNVArgs(*f.finfo->func); },
5238 [] (Fun2 f) { return numNVArgs(*f.finfo->func); },
5239 [] (Method m) { return numNVArgs(*m.finfo->func); },
5240 [] (Method2 m) { return numNVArgs(*m.finfo->func); },
5241 [] (MethodFamily fa) {
5242 return
5243 fa.family->infoFor(fa.regularOnly).m_static->m_minNonVariadicParams;
5245 [&] (MethodFamily2 fa) {
5246 return fa.family->infoFor(fa.regularOnly).m_minNonVariadicParams;
5248 [] (MethodOrMissing m) { return numNVArgs(*m.finfo->func); },
5249 [] (MethodOrMissing2 m) { return numNVArgs(*m.finfo->func); },
5250 [] (MissingFunc) { return 0; },
5251 [] (MissingMethod) { return 0; },
5252 [] (const Isect& i) {
5253 uint32_t nv = 0;
5254 for (auto const ff : i.families) {
5255 nv = std::max(
5257 ff->infoFor(i.regularOnly).m_static->m_minNonVariadicParams
5260 return nv;
5262 [] (const Isect2& i) {
5263 uint32_t nv = 0;
5264 for (auto const ff : i.families) {
5265 nv = std::max(
5267 ff->infoFor(i.regularOnly).m_minNonVariadicParams
5270 return nv;
5275 uint32_t Func::maxNonVariadicParams() const {
5276 return match<uint32_t>(
5277 val,
5278 [] (FuncName) { return std::numeric_limits<uint32_t>::max(); },
5279 [] (MethodName) { return std::numeric_limits<uint32_t>::max(); },
5280 [] (Fun f) { return numNVArgs(*f.finfo->func); },
5281 [] (Fun2 f) { return numNVArgs(*f.finfo->func); },
5282 [] (Method m) { return numNVArgs(*m.finfo->func); },
5283 [] (Method2 m) { return numNVArgs(*m.finfo->func); },
5284 [] (MethodFamily fa) {
5285 return
5286 fa.family->infoFor(fa.regularOnly).m_static->m_maxNonVariadicParams;
5288 [] (MethodFamily2 fa) {
5289 return fa.family->infoFor(fa.regularOnly).m_maxNonVariadicParams;
5291 [] (MethodOrMissing m) { return numNVArgs(*m.finfo->func); },
5292 [] (MethodOrMissing2 m) { return numNVArgs(*m.finfo->func); },
5293 [] (MissingFunc) { return 0; },
5294 [] (MissingMethod) { return 0; },
5295 [] (const Isect& i) {
5296 auto nv = std::numeric_limits<uint32_t>::max();
5297 for (auto const ff : i.families) {
5298 nv = std::min(
5300 ff->infoFor(i.regularOnly).m_static->m_maxNonVariadicParams
5303 return nv;
5305 [] (const Isect2& i) {
5306 auto nv = std::numeric_limits<uint32_t>::max();
5307 for (auto const ff : i.families) {
5308 nv = std::min(
5310 ff->infoFor(i.regularOnly).m_maxNonVariadicParams
5313 return nv;
5318 const RuntimeCoeffects* Func::requiredCoeffects() const {
5319 return match<const RuntimeCoeffects*>(
5320 val,
5321 [] (FuncName) { return nullptr; },
5322 [] (MethodName) { return nullptr; },
5323 [] (Fun f) { return &f.finfo->func->requiredCoeffects; },
5324 [] (Fun2 f) { return &f.finfo->func->requiredCoeffects; },
5325 [] (Method m) { return &m.finfo->func->requiredCoeffects; },
5326 [] (Method2 m) { return &m.finfo->func->requiredCoeffects; },
5327 [] (MethodFamily fa) {
5328 return fa.family->infoFor(fa.regularOnly)
5329 .m_static->m_requiredCoeffects.get_pointer();
5331 [] (MethodFamily2 fa) {
5332 return
5333 fa.family->infoFor(fa.regularOnly).m_requiredCoeffects.get_pointer();
5335 [] (MethodOrMissing m) { return &m.finfo->func->requiredCoeffects; },
5336 [] (MethodOrMissing2 m) { return &m.finfo->func->requiredCoeffects; },
5337 [] (MissingFunc) { return nullptr; },
5338 [] (MissingMethod) { return nullptr; },
5339 [] (const Isect& i) {
5340 const RuntimeCoeffects* coeffects = nullptr;
5341 for (auto const ff : i.families) {
5342 auto const& info = *ff->infoFor(i.regularOnly).m_static;
5343 if (!info.m_requiredCoeffects) continue;
5344 assertx(IMPLIES(coeffects, *coeffects == *info.m_requiredCoeffects));
5345 if (!coeffects) coeffects = info.m_requiredCoeffects.get_pointer();
5347 return coeffects;
5349 [] (const Isect2& i) {
5350 const RuntimeCoeffects* coeffects = nullptr;
5351 for (auto const ff : i.families) {
5352 auto const& info = ff->infoFor(i.regularOnly);
5353 if (!info.m_requiredCoeffects) continue;
5354 assertx(IMPLIES(coeffects, *coeffects == *info.m_requiredCoeffects));
5355 if (!coeffects) coeffects = info.m_requiredCoeffects.get_pointer();
5357 return coeffects;
5362 const CompactVector<CoeffectRule>* Func::coeffectRules() const {
5363 return match<const CompactVector<CoeffectRule>*>(
5364 val,
5365 [] (FuncName) { return nullptr; },
5366 [] (MethodName) { return nullptr; },
5367 [] (Fun f) { return &f.finfo->func->coeffectRules; },
5368 [] (Fun2 f) { return &f.finfo->func->coeffectRules; },
5369 [] (Method m) { return &m.finfo->func->coeffectRules; },
5370 [] (Method2 m) { return &m.finfo->func->coeffectRules; },
5371 [] (MethodFamily fa) {
5372 return fa.family->infoFor(fa.regularOnly)
5373 .m_static->m_coeffectRules.get_pointer();
5375 [] (MethodFamily2 fa) {
5376 return fa.family->infoFor(fa.regularOnly).m_coeffectRules.get_pointer();
5378 [] (MethodOrMissing m) { return &m.finfo->func->coeffectRules; },
5379 [] (MethodOrMissing2 m) { return &m.finfo->func->coeffectRules; },
5380 [] (MissingFunc) { return nullptr; },
5381 [] (MissingMethod) { return nullptr; },
5382 [] (const Isect& i) {
5383 const CompactVector<CoeffectRule>* coeffects = nullptr;
5384 for (auto const ff : i.families) {
5385 auto const& info = *ff->infoFor(i.regularOnly).m_static;
5386 if (!info.m_coeffectRules) continue;
5387 assertx(
5388 IMPLIES(
5389 coeffects,
5390 std::is_permutation(
5391 begin(*coeffects),
5392 end(*coeffects),
5393 begin(*info.m_coeffectRules),
5394 end(*info.m_coeffectRules)
5398 if (!coeffects) coeffects = info.m_coeffectRules.get_pointer();
5400 return coeffects;
5402 [] (const Isect2& i) {
5403 const CompactVector<CoeffectRule>* coeffects = nullptr;
5404 for (auto const ff : i.families) {
5405 auto const& info = ff->infoFor(i.regularOnly);
5406 if (!info.m_coeffectRules) continue;
5407 assertx(
5408 IMPLIES(
5409 coeffects,
5410 std::is_permutation(
5411 begin(*coeffects),
5412 end(*coeffects),
5413 begin(*info.m_coeffectRules),
5414 end(*info.m_coeffectRules)
5418 if (!coeffects) coeffects = info.m_coeffectRules.get_pointer();
5420 return coeffects;
5425 TriBool Func::supportsAsyncEagerReturn() const {
5426 return match<TriBool>(
5427 val,
5428 [] (FuncName) { return TriBool::Maybe; },
5429 [] (MethodName) { return TriBool::Maybe; },
5430 [] (Fun f) { return yesOrNo(func_supports_AER(f.finfo->func)); },
5431 [] (Fun2 f) { return yesOrNo(func_supports_AER(f.finfo->func)); },
5432 [] (Method m) { return yesOrNo(func_supports_AER(m.finfo->func)); },
5433 [] (Method2 m) { return yesOrNo(func_supports_AER(m.finfo->func)); },
5434 [] (MethodFamily fam) {
5435 return fam.family->infoFor(fam.regularOnly).m_static->m_supportsAER;
5437 [] (MethodFamily2 fam) {
5438 return fam.family->infoFor(fam.regularOnly).m_supportsAER;
5440 [] (MethodOrMissing m) {
5441 return yesOrNo(func_supports_AER(m.finfo->func));
5443 [] (MethodOrMissing2 m) {
5444 return yesOrNo(func_supports_AER(m.finfo->func));
5446 [] (MissingFunc) { return TriBool::No; },
5447 [] (MissingMethod) { return TriBool::No; },
5448 [] (const Isect& i) {
5449 auto aer = TriBool::Maybe;
5450 for (auto const ff : i.families) {
5451 auto const& info = *ff->infoFor(i.regularOnly).m_static;
5452 if (info.m_supportsAER == TriBool::Maybe) continue;
5453 assertx(IMPLIES(aer != TriBool::Maybe, aer == info.m_supportsAER));
5454 if (aer == TriBool::Maybe) aer = info.m_supportsAER;
5456 return aer;
5458 [] (const Isect2& i) {
5459 auto aer = TriBool::Maybe;
5460 for (auto const ff : i.families) {
5461 auto const& info = ff->infoFor(i.regularOnly);
5462 if (info.m_supportsAER == TriBool::Maybe) continue;
5463 assertx(IMPLIES(aer != TriBool::Maybe, aer == info.m_supportsAER));
5464 if (aer == TriBool::Maybe) aer = info.m_supportsAER;
5466 return aer;
5471 Optional<uint32_t> Func::lookupNumInoutParams() const {
5472 return match<Optional<uint32_t>>(
5473 val,
5474 [] (FuncName s) -> Optional<uint32_t> { return std::nullopt; },
5475 [] (MethodName s) -> Optional<uint32_t> { return std::nullopt; },
5476 [] (Fun f) { return func_num_inout(f.finfo->func); },
5477 [] (Fun2 f) { return func_num_inout(f.finfo->func); },
5478 [] (Method m) { return func_num_inout(m.finfo->func); },
5479 [] (Method2 m) { return func_num_inout(m.finfo->func); },
5480 [] (MethodFamily fam) {
5481 return fam.family->infoFor(fam.regularOnly).m_static->m_numInOut;
5483 [] (MethodFamily2 fam) {
5484 return fam.family->infoFor(fam.regularOnly).m_numInOut;
5486 [] (MethodOrMissing m) { return func_num_inout(m.finfo->func); },
5487 [] (MethodOrMissing2 m) { return func_num_inout(m.finfo->func); },
5488 [] (MissingFunc) { return 0; },
5489 [] (MissingMethod) { return 0; },
5490 [] (const Isect& i) {
5491 Optional<uint32_t> numInOut;
5492 for (auto const ff : i.families) {
5493 auto const& info = *ff->infoFor(i.regularOnly).m_static;
5494 if (!info.m_numInOut) continue;
5495 assertx(IMPLIES(numInOut, *numInOut == *info.m_numInOut));
5496 if (!numInOut) numInOut = info.m_numInOut;
5498 return numInOut;
5500 [] (const Isect2& i) {
5501 Optional<uint32_t> numInOut;
5502 for (auto const ff : i.families) {
5503 auto const& info = ff->infoFor(i.regularOnly);
5504 if (!info.m_numInOut) continue;
5505 assertx(IMPLIES(numInOut, *numInOut == *info.m_numInOut));
5506 if (!numInOut) numInOut = info.m_numInOut;
5508 return numInOut;
5513 PrepKind Func::lookupParamPrep(uint32_t paramId) const {
5514 auto const fromFuncFamily = [&] (FuncFamily* ff, bool regularOnly) {
5515 auto const& info = *ff->infoFor(regularOnly).m_static;
5516 if (paramId >= info.m_paramPreps.size()) {
5517 return PrepKind{TriBool::No, TriBool::No};
5519 return info.m_paramPreps[paramId];
5521 auto const fromFuncFamily2 = [&] (const FuncFamily2* ff, bool regularOnly) {
5522 auto const& info = ff->infoFor(regularOnly);
5523 if (paramId >= info.m_paramPreps.size()) {
5524 return PrepKind{TriBool::No, TriBool::No};
5526 return info.m_paramPreps[paramId];
5529 return match<PrepKind>(
5530 val,
5531 [&] (FuncName s) { return PrepKind{TriBool::Maybe, TriBool::Maybe}; },
5532 [&] (MethodName s) { return PrepKind{TriBool::Maybe, TriBool::Maybe}; },
5533 [&] (Fun f) { return func_param_prep(f.finfo->func, paramId); },
5534 [&] (Fun2 f) { return func_param_prep(f.finfo->func, paramId); },
5535 [&] (Method m) { return func_param_prep(m.finfo->func, paramId); },
5536 [&] (Method2 m) { return func_param_prep(m.finfo->func, paramId); },
5537 [&] (MethodFamily f) { return fromFuncFamily(f.family, f.regularOnly); },
5538 [&] (MethodFamily2 f) { return fromFuncFamily2(f.family, f.regularOnly); },
5539 [&] (MethodOrMissing m) { return func_param_prep(m.finfo->func, paramId); },
5540 [&] (MethodOrMissing2 m) { return func_param_prep(m.finfo->func, paramId); },
5541 [&] (MissingFunc) { return PrepKind{TriBool::No, TriBool::Yes}; },
5542 [&] (MissingMethod) { return PrepKind{TriBool::No, TriBool::Yes}; },
5543 [&] (const Isect& i) {
5544 auto inOut = TriBool::Maybe;
5545 auto readonly = TriBool::Maybe;
5547 for (auto const ff : i.families) {
5548 auto const prepKind = fromFuncFamily(ff, i.regularOnly);
5549 if (prepKind.inOut != TriBool::Maybe) {
5550 assertx(IMPLIES(inOut != TriBool::Maybe, inOut == prepKind.inOut));
5551 if (inOut == TriBool::Maybe) inOut = prepKind.inOut;
5554 if (prepKind.readonly != TriBool::Maybe) {
5555 assertx(
5556 IMPLIES(readonly != TriBool::Maybe, readonly == prepKind.readonly)
5558 if (readonly == TriBool::Maybe) readonly = prepKind.readonly;
5562 return PrepKind{inOut, readonly};
5564 [&] (const Isect2& i) {
5565 auto inOut = TriBool::Maybe;
5566 auto readonly = TriBool::Maybe;
5568 for (auto const ff : i.families) {
5569 auto const prepKind = fromFuncFamily2(ff, i.regularOnly);
5570 if (prepKind.inOut != TriBool::Maybe) {
5571 assertx(IMPLIES(inOut != TriBool::Maybe, inOut == prepKind.inOut));
5572 if (inOut == TriBool::Maybe) inOut = prepKind.inOut;
5575 if (prepKind.readonly != TriBool::Maybe) {
5576 assertx(
5577 IMPLIES(readonly != TriBool::Maybe, readonly == prepKind.readonly)
5579 if (readonly == TriBool::Maybe) readonly = prepKind.readonly;
5583 return PrepKind{inOut, readonly};
5588 TriBool Func::lookupReturnReadonly() const {
5589 return match<TriBool>(
5590 val,
5591 [] (FuncName) { return TriBool::Maybe; },
5592 [] (MethodName) { return TriBool::Maybe; },
5593 [] (Fun f) { return yesOrNo(f.finfo->func->isReadonlyReturn); },
5594 [] (Fun2 f) { return yesOrNo(f.finfo->func->isReadonlyReturn); },
5595 [] (Method m) { return yesOrNo(m.finfo->func->isReadonlyReturn); },
5596 [] (Method2 m) { return yesOrNo(m.finfo->func->isReadonlyReturn); },
5597 [] (MethodFamily fam) {
5598 return fam.family->infoFor(fam.regularOnly).m_static->m_isReadonlyReturn;
5600 [] (MethodFamily2 fam) {
5601 return fam.family->infoFor(fam.regularOnly).m_isReadonlyReturn;
5603 [] (MethodOrMissing m) { return yesOrNo(m.finfo->func->isReadonlyReturn); },
5604 [] (MethodOrMissing2 m) { return yesOrNo(m.finfo->func->isReadonlyReturn); },
5605 [] (MissingFunc) { return TriBool::No; },
5606 [] (MissingMethod) { return TriBool::No; },
5607 [] (const Isect& i) {
5608 auto readOnly = TriBool::Maybe;
5609 for (auto const ff : i.families) {
5610 auto const& info = *ff->infoFor(i.regularOnly).m_static;
5611 if (info.m_isReadonlyReturn == TriBool::Maybe) continue;
5612 assertx(IMPLIES(readOnly != TriBool::Maybe,
5613 readOnly == info.m_isReadonlyReturn));
5614 if (readOnly == TriBool::Maybe) readOnly = info.m_isReadonlyReturn;
5616 return readOnly;
5618 [] (const Isect2& i) {
5619 auto readOnly = TriBool::Maybe;
5620 for (auto const ff : i.families) {
5621 auto const& info = ff->infoFor(i.regularOnly);
5622 if (info.m_isReadonlyReturn == TriBool::Maybe) continue;
5623 assertx(IMPLIES(readOnly != TriBool::Maybe,
5624 readOnly == info.m_isReadonlyReturn));
5625 if (readOnly == TriBool::Maybe) readOnly = info.m_isReadonlyReturn;
5627 return readOnly;
5632 TriBool Func::lookupReadonlyThis() const {
5633 return match<TriBool>(
5634 val,
5635 [] (FuncName s) { return TriBool::Maybe; },
5636 [] (MethodName s) { return TriBool::Maybe; },
5637 [] (Fun f) { return yesOrNo(f.finfo->func->isReadonlyThis); },
5638 [] (Fun2 f) { return yesOrNo(f.finfo->func->isReadonlyThis); },
5639 [] (Method m) { return yesOrNo(m.finfo->func->isReadonlyThis); },
5640 [] (Method2 m) { return yesOrNo(m.finfo->func->isReadonlyThis); },
5641 [] (MethodFamily fam) {
5642 return fam.family->infoFor(fam.regularOnly).m_static->m_isReadonlyThis;
5644 [] (MethodFamily2 fam) {
5645 return fam.family->infoFor(fam.regularOnly).m_isReadonlyThis;
5647 [] (MethodOrMissing m) { return yesOrNo(m.finfo->func->isReadonlyThis); },
5648 [] (MethodOrMissing2 m) { return yesOrNo(m.finfo->func->isReadonlyThis); },
5649 [] (MissingFunc) { return TriBool::No; },
5650 [] (MissingMethod) { return TriBool::No; },
5651 [] (const Isect& i) {
5652 auto readOnly = TriBool::Maybe;
5653 for (auto const ff : i.families) {
5654 auto const& info = *ff->infoFor(i.regularOnly).m_static;
5655 if (info.m_isReadonlyThis == TriBool::Maybe) continue;
5656 assertx(IMPLIES(readOnly != TriBool::Maybe,
5657 readOnly == info.m_isReadonlyThis));
5658 if (readOnly == TriBool::Maybe) readOnly = info.m_isReadonlyThis;
5660 return readOnly;
5662 [] (const Isect2& i) {
5663 auto readOnly = TriBool::Maybe;
5664 for (auto const ff : i.families) {
5665 auto const& info = ff->infoFor(i.regularOnly);
5666 if (info.m_isReadonlyThis == TriBool::Maybe) continue;
5667 assertx(IMPLIES(readOnly != TriBool::Maybe,
5668 readOnly == info.m_isReadonlyThis));
5669 if (readOnly == TriBool::Maybe) readOnly = info.m_isReadonlyThis;
5671 return readOnly;
5676 Optional<SString> Func::triviallyWrappedFunc() const {
5677 auto const check = [](const php::Func* func) -> Optional<SString> {
5678 auto const it = func->userAttributes.find(s_TrivialHHVMBuiltinWrapper.get());
5679 if (it == func->userAttributes.end()) return std::nullopt;
5680 assertx(tvIsVec(it->second));
5681 auto const args = it->second.m_data.parr;
5682 if (args->size() != 1) return std::nullopt;
5683 auto const wrappedFunc = args->at(int64_t{0});
5684 if (!tvIsString(wrappedFunc)) return std::nullopt;
5685 assertx(wrappedFunc.m_data.pstr->isStatic());
5686 return wrappedFunc.m_data.pstr;
5688 return match<Optional<SString>>(
5689 val,
5690 [] (Func::FuncName) { return std::nullopt; },
5691 [] (Func::MethodName) { return std::nullopt; },
5692 [&] (Func::Fun f) { return check(f.finfo->func); },
5693 [&] (Func::Fun2 f) { return check(f.finfo->func); },
5694 [] (Func::Method) { return std::nullopt; },
5695 [] (Func::Method2) { return std::nullopt; },
5696 [] (Func::MethodFamily) { return std::nullopt; },
5697 [] (Func::MethodFamily2) { return std::nullopt; },
5698 [] (Func::MethodOrMissing) { return std::nullopt; },
5699 [] (Func::MethodOrMissing2) { return std::nullopt; },
5700 [] (Func::MissingFunc) { return std::nullopt; },
5701 [] (Func::MissingMethod) { return std::nullopt; },
5702 [] (const Func::Isect&) { return std::nullopt; },
5703 [] (const Func::Isect2&) { return std::nullopt; }
5707 std::string show(const Func& f) {
5708 auto ret = f.name();
5709 match<void>(
5710 f.val,
5711 [&] (Func::FuncName) {},
5712 [&] (Func::MethodName) {},
5713 [&] (Func::Fun) { ret += "*"; },
5714 [&] (Func::Fun2) { ret += "*"; },
5715 [&] (Func::Method) { ret += "*"; },
5716 [&] (Func::Method2) { ret += "*"; },
5717 [&] (Func::MethodFamily) { ret += "+"; },
5718 [&] (Func::MethodFamily2) { ret += "+"; },
5719 [&] (Func::MethodOrMissing) { ret += "-"; },
5720 [&] (Func::MethodOrMissing2) { ret += "-"; },
5721 [&] (Func::MissingFunc) { ret += "!"; },
5722 [&] (Func::MissingMethod) { ret += "!"; },
5723 [&] (const Func::Isect&) { ret += "&"; },
5724 [&] (const Func::Isect2&) { ret += "&"; }
5726 return ret;
5731 //////////////////////////////////////////////////////////////////////
5733 struct Index::IndexData {
5734 explicit IndexData(Index* index) : m_index{index} {}
5735 IndexData(const IndexData&) = delete;
5736 IndexData& operator=(const IndexData&) = delete;
5738 Index* m_index;
5740 bool frozen{false};
5741 bool ever_frozen{false};
5743 // If non-nullptr, log information about each pass into it.
5744 StructuredLogEntry* sample;
5746 // Async state:
5747 std::unique_ptr<TicketExecutor> executor;
5748 std::unique_ptr<Client> client;
5749 DisposeCallback disposeClient;
5751 // Global configeration, stored in extern-worker.
5752 std::unique_ptr<CoroAsyncValue<Ref<Config>>> configRef;
5754 // Maps unit/class/func name to the extern-worker ref representing
5755 // php::Program data for that. Any associated bytecode is stored
5756 // separately.
5757 SStringToOneT<UniquePtrRef<php::Unit>> unitRefs;
5758 TSStringToOneT<UniquePtrRef<php::Class>> classRefs;
5759 FSStringToOneT<UniquePtrRef<php::Func>> funcRefs;
5761 // Maps class name to the extern-worker ref representing the class's
5762 // associated ClassInfo2. Only has entries for instantiable classes.
5763 TSStringToOneT<UniquePtrRef<ClassInfo2>> classInfoRefs;
5765 // Maps func name (global functions, not methods) to the
5766 // extern-worked ref representing the func's associated
5767 // FuncInfo2. The FuncInfo2 for methods are stored in their parent
5768 // ClassInfo2.
5769 FSStringToOneT<UniquePtrRef<FuncInfo2>> funcInfoRefs;
5771 // Maps class/func names to the extern-worker ref representing the
5772 // bytecode for that class or (global) function. The bytecode of all
5773 // of a class' methods are stored together.
5774 TSStringToOneT<UniquePtrRef<php::ClassBytecode>> classBytecodeRefs;
5775 FSStringToOneT<UniquePtrRef<php::FuncBytecode>> funcBytecodeRefs;
5777 // Uninstantiable classes do not have ClassInfo2s, but their methods
5778 // still have FuncInfo2s. Since we don't have a ClassInfo2 to store
5779 // them on, we do it separately.
5780 TSStringToOneT<UniquePtrRef<MethodsWithoutCInfo>> uninstantiableClsMethRefs;
5782 // Func family entries representing all methods with a particular
5783 // name.
5784 SStringToOneT<FuncFamilyEntry> nameOnlyMethodFamilies;
5786 // Maps func-family ids to the func family group which contains the
5787 // func family with that id.
5788 hphp_fast_map<FuncFamily2::Id, Ref<FuncFamilyGroup>> funcFamilyRefs;
5790 // Maps of functions and classes to the names of closures defined
5791 // within.
5792 TSStringToOneT<TSStringSet> classToClosures;
5793 FSStringToOneT<TSStringSet> funcToClosures;
5795 // Maps entities to the unit they were declared in.
5796 TSStringToOneT<SString> classToUnit;
5797 FSStringToOneT<SString> funcToUnit;
5798 TSStringToOneT<SString> typeAliasToUnit;
5799 // If bool is true, then the constant is "dynamic" and has an
5800 // associated 86cinit function.
5801 SStringToOneT<std::pair<SString, bool>> constantToUnit;
5803 // Maps a closure to it's declaring class or function.
5804 TSStringToOneT<SString> closureToClass;
5805 TSStringToOneT<SString> closureToFunc;
5807 // Maps a class to the classes which it has inherited class
5808 // constants from.
5809 TSStringToOneT<TSStringSet> classToCnsBases;
5811 // All the classes that have a 86*init function.
5812 TSStringSet classesWith86Inits;
5813 // All the 86cinit functions for "dynamic" top-level constants.
5814 FSStringSet constantInitFuncs;
5815 // All the units that have type-aliases within them.
5816 SStringSet unitsWithTypeAliases;
5818 std::unique_ptr<php::Program> program;
5820 TSStringToOneT<php::Class*> classes;
5821 FSStringToOneT<php::Func*> funcs;
5822 TSStringToOneT<php::TypeAlias*> typeAliases;
5823 TSStringToOneT<php::Class*> enums;
5824 SStringToOneT<php::Constant*> constants;
5825 SStringToOneT<php::Module*> modules;
5826 SStringToOneT<php::Unit*> units;
5829 * Func families representing methods with a particular name (across
5830 * all classes).
5832 struct MethodFamilyEntry {
5833 FuncFamilyOrSingle m_all;
5834 FuncFamilyOrSingle m_regular;
5836 SStringToOneT<MethodFamilyEntry> methodFamilies;
5838 // Map from each class to all the closures that are allocated in
5839 // functions of that class.
5840 hphp_fast_map<
5841 const php::Class*,
5842 CompactVector<const php::Class*>
5843 > classClosureMap;
5845 hphp_fast_map<
5846 const php::Class*,
5847 hphp_fast_set<const php::Func*>
5848 > classExtraMethodMap;
5851 * Map from each class name to ClassInfo objects if one exists.
5853 * It may not exists if we would fatal when defining the class. That could
5854 * happen for if the inheritance is bad or __Sealed or other things.
5856 TSStringToOneT<ClassInfo*> classInfo;
5859 * All the ClassInfos, stored in no particular order.
5861 std::vector<std::unique_ptr<ClassInfo>> allClassInfos;
5863 std::vector<FuncInfo> funcInfo;
5864 std::atomic<uint32_t> nextFuncId{};
5866 // Private instance and static property types are stored separately
5867 // from ClassInfo, because you don't need to resolve a class to get
5868 // at them.
5869 hphp_hash_map<
5870 const php::Class*,
5871 PropState
5872 > privatePropInfo;
5873 hphp_hash_map<
5874 const php::Class*,
5875 PropState
5876 > privateStaticPropInfo;
5879 * Public static property information:
5882 // If this is true, we've seen mutations to public static
5883 // properties. Once this is true, it's no longer legal to report a
5884 // pessimistic static property set (unknown class and
5885 // property). Doing so is a monotonicity violation.
5886 bool seenPublicSPropMutations{false};
5888 // The set of gathered public static property mutations for each function. The
5889 // inferred types for the public static properties is the union of all these
5890 // mutations. If a function is not analyzed in a particular analysis round,
5891 // its mutations are left unchanged from the previous round.
5892 folly_concurrent_hash_map_simd<
5893 const php::Func*,
5894 PublicSPropMutations,
5895 pointer_hash<const php::Func>> publicSPropMutations;
5897 // All FuncFamilies. These are stored globally so we can avoid
5898 // generating duplicates.
5899 folly_concurrent_hash_map_simd<
5900 std::unique_ptr<FuncFamily>,
5901 bool,
5902 FuncFamilyPtrHasher,
5903 FuncFamilyPtrEquals
5904 > funcFamilies;
5906 folly_concurrent_hash_map_simd<
5907 std::unique_ptr<FuncFamily::StaticInfo>,
5908 bool,
5909 FFStaticInfoPtrHasher,
5910 FFStaticInfoPtrEquals
5911 > funcFamilyStaticInfos;
5914 * Map from interfaces to their assigned vtable slots, computed in
5915 * compute_iface_vtables().
5917 TSStringToOneT<Slot> ifaceSlotMap;
5919 hphp_hash_map<
5920 const php::Class*,
5921 CompactVector<Type>
5922 > closureUseVars;
5924 struct ClsConstTypesHasher {
5925 bool operator()(const std::pair<const php::Class*, SString>& k) const {
5926 return folly::hash::hash_combine(
5927 pointer_hash<php::Class>{}(k.first),
5928 pointer_hash<StringData>{}(k.second)
5932 struct ClsConstTypesEquals {
5933 bool operator()(const std::pair<const php::Class*, SString>& a,
5934 const std::pair<const php::Class*, SString>& b) const {
5935 return a.first == b.first && a.second == b.second;
5939 // Cache for lookup_class_constant
5940 folly_concurrent_hash_map_simd<
5941 std::pair<const php::Class*, SString>,
5942 ClsConstLookupResult,
5943 ClsConstTypesHasher,
5944 ClsConstTypesEquals
5945 > clsConstLookupCache;
5947 bool useClassDependencies{};
5948 DepMap dependencyMap;
5951 * If a function is effect-free when called with a particular set of
5952 * literal arguments, and produces a literal result, there will be
5953 * an entry here representing the type.
5955 * The map isn't just an optimization; we can't call
5956 * analyze_func_inline during the optimization phase, because the
5957 * bytecode could be modified while we do so.
5959 ContextRetTyMap foldableReturnTypeMap;
5962 * Call-context sensitive return types are cached here. This is not
5963 * an optimization.
5965 * The reason we need to retain this information about the
5966 * calling-context-sensitive return types is that once the Index is
5967 * frozen (during the final optimization pass), calls to
5968 * lookup_return_type with a CallContext can't look at the bytecode
5969 * bodies of functions other than the calling function. So we need
5970 * to know what we determined the last time we were allowed to do
5971 * that so we can return it again.
5973 ContextRetTyMap contextualReturnTypes{};
5976 //////////////////////////////////////////////////////////////////////
5978 namespace { struct DepTracker; };
5980 struct AnalysisIndex::IndexData {
5981 IndexData(AnalysisIndex& index,
5982 AnalysisWorklist& worklist,
5983 Mode mode)
5984 : index{index}
5985 , worklist{worklist}
5986 , deps{std::make_unique<DepTracker>(*this)}
5987 , mode{mode} {}
5989 IndexData(const IndexData&) = delete;
5990 IndexData& operator=(const IndexData&) = delete;
5992 AnalysisIndex& index;
5993 AnalysisWorklist& worklist;
5994 std::unique_ptr<DepTracker> deps;
5996 // The names of classes/funcs/units which will be in the output of
5997 // the job.
5998 std::vector<SString> outClassNames;
5999 std::vector<SString> outFuncNames;
6000 std::vector<SString> outUnitNames;
6002 // Maps names to various data-structures. This works for both normal
6003 // classes and closures.
6004 TSStringToOneT<php::Class*> classes;
6005 TSStringToOneT<ClassInfo2*> cinfos;
6006 TSStringToOneT<MethodsWithoutCInfo*> minfos;
6008 FSStringToOneT<php::Func*> funcs;
6009 FSStringToOneT<FuncInfo2*> finfos;
6011 SStringToOneT<php::Unit*> units;
6013 SStringToOneT<std::pair<php::Constant*, php::Unit*>> constants;
6014 TSStringToOneT<std::pair<php::TypeAlias*, php::Unit*>> typeAliases;
6016 std::vector<FuncInfo2*> finfosByIdx;
6018 // "Owns" the actual data structures. Closures will not be on these
6019 // maps, as they are owned by another Class. Generally look ups
6020 // should use the other maps above.
6021 TSStringToOneT<std::unique_ptr<php::Class>> allClasses;
6022 TSStringToOneT<std::unique_ptr<ClassInfo2>> allCInfos;
6023 TSStringToOneT<std::unique_ptr<MethodsWithoutCInfo>> allMInfos;
6024 FSStringToOneT<std::unique_ptr<php::Func>> allFuncs;
6025 FSStringToOneT<std::unique_ptr<FuncInfo2>> allFInfos;
6026 SStringToOneT<std::unique_ptr<php::Unit>> allUnits;
6028 // Anything on these lists is known to definitely not exist.
6029 TSStringSet badClasses;
6030 FSStringSet badFuncs;
6031 SStringSet badConstants;
6033 SStringSet dynamicConstants;
6035 // AnalysisIndex maintains a stack of the contexts being analyzed
6036 // (we can have multiple because of inline interp).
6037 std::vector<Context> contexts;
6039 // If we're currently resolving class type constants. This changes
6040 // how some of the dependencies are treated.
6041 bool inTypeCns{false};
6043 size_t foldableInterpNestingLevel{0};
6044 size_t contextualInterpNestingLevel{0};
6046 // The bucket id which AnalysisScheduler assigned to this worker.
6047 size_t bucketIdx;
6049 // Once the index is frozen, no further updates to it are allowed
6050 // (will assert). We only gather dependencies when the index is
6051 // frozen.
6052 bool frozen{false};
6054 Mode mode;
6057 //////////////////////////////////////////////////////////////////////
6059 namespace {
6061 //////////////////////////////////////////////////////////////////////
6063 // Obtain the current (most recently pushed) context. This corresponds
6064 // to the context currently being analyzed.
6065 const Context& current_context(const AnalysisIndex::IndexData& index) {
6066 always_assert_flog(
6067 !index.contexts.empty(),
6068 "Accessing current context without any contexts active"
6070 return index.contexts.back();
6073 // Obtain the context to use for the purposes of dependency
6074 // tracking. This is the first context pushed. This differs from
6075 // current_context() because of inline interp. If we're inline
6076 // interp-ing a function, we still want to attribute the dependencies
6077 // to the context which started the inline interp.
6078 const Context& context_for_deps(const AnalysisIndex::IndexData& index) {
6079 always_assert_flog(
6080 !index.contexts.empty(),
6081 "Accessing dependency context without any contexts active"
6083 return index.contexts.front();
6086 //////////////////////////////////////////////////////////////////////
6088 FuncClsUnit fc_from_context(const Context& ctx,
6089 const AnalysisIndex::IndexData& index) {
6090 if (ctx.cls) return ctx.cls;
6091 if (ctx.func) return ctx.func;
6092 assertx(ctx.unit);
6093 return &index.index.lookup_unit(ctx.unit);
6096 //////////////////////////////////////////////////////////////////////
6098 // Record the dependencies of all classes and functions being
6099 // processed with an AnalysisIndex. These dependencies will ultimately
6100 // be reported back to the master AnalysisScheduler, but will also
6101 // drive the worklist on the local analysis job.
6102 struct DepTracker {
6103 explicit DepTracker(const AnalysisIndex::IndexData& index)
6104 : index{index} {}
6106 using Type = AnalysisDeps::Type;
6107 using Func = AnalysisDeps::Func;
6108 using Class = AnalysisDeps::Class;
6109 using Constant = AnalysisDeps::Constant;
6110 using AnyClassConstant = AnalysisDeps::AnyClassConstant;
6112 // Register dependencies on various entities to the current
6113 // dependency context.
6115 [[nodiscard]] bool add(Class c) {
6116 auto const fc = context();
6117 auto const a = allowed(fc, c, false);
6118 if (!index.frozen) return a;
6119 if (auto const c2 = fc.cls()) {
6120 if (c2->name->tsame(c.name)) return a;
6122 auto& d = deps[fc];
6123 if (d.add(c, index.inTypeCns)) {
6124 FTRACE(2, "{} now depends on class {}{}\n",
6125 HHBBC::show(fc), c.name,
6126 index.inTypeCns ? " (in type-cns)" : "");
6128 // Class either exists or not and won't change within the job, so
6129 // nothing to record for worklist.
6130 return a;
6133 [[nodiscard]] bool add(const php::Func& f, Type t = Type::None) {
6134 auto const fc = context();
6135 assertx(!fc.unit());
6136 auto const a = f.cls
6137 ? allowed(fc, Class { f.cls->name }, t & Type::Bytecode)
6138 : allowed(fc, Func { f.name }, t & Type::Bytecode);
6139 if (index.frozen) {
6140 if (auto const c = fc.cls()) {
6141 if (f.cls && c->name->tsame(f.cls->name)) return a;
6142 } else if (auto const f2 = fc.func()) {
6143 if (!f.cls && f2->name->fsame(f.name)) return a;
6146 if (auto const added = deps[fc].add(f, t)) {
6147 FTRACE(
6148 2, "{} now depends on {}{} {}\n",
6149 HHBBC::show(fc), displayAdded(added),
6150 f.cls ? "method" : "func",
6151 func_fullname(f)
6154 } else if (!a) {
6155 return false;
6156 } else {
6157 // Record dependency for worklist if anything can change within
6158 // the job.
6159 t &= AnalysisDeps::kValidForChanges;
6160 if (t == Type::None) return true;
6161 funcs[&f][fc] |= t;
6163 return a;
6166 [[nodiscard]] bool add(MethRef m, Type t = Type::None) {
6167 auto const fc = context();
6168 assertx(!fc.unit());
6169 auto const a = allowed(fc, Class { m.cls }, t & Type::Bytecode);
6170 if (index.frozen) {
6171 if (auto const c = fc.cls()) {
6172 if (c->name->tsame(m.cls)) return a;
6174 if (auto const added = deps[fc].add(m, t)) {
6175 FTRACE(2, "{} now depends on {}method {}\n",
6176 HHBBC::show(fc), displayAdded(added), display(m));
6178 } else if (!a) {
6179 return false;
6180 } else {
6181 // Record dependency for worklist if anything can change within
6182 // the job.
6183 t &= AnalysisDeps::kValidForChanges;
6184 if (t == Type::None) return true;
6185 if (auto const p = from(m)) funcs[p][fc] |= t;
6187 return a;
6190 [[nodiscard]] bool add(Func f, Type t = Type::None) {
6191 auto const fc = context();
6192 assertx(!fc.unit());
6193 auto const a = allowed(fc, f, t & Type::Bytecode);
6194 if (index.frozen) {
6195 if (auto const f2 = fc.func()) {
6196 if (f2->name->fsame(f.name)) return a;
6198 if (auto const added = deps[fc].add(f, t)) {
6199 FTRACE(2, "{} now depends on {}func {}\n",
6200 HHBBC::show(fc), displayAdded(added), f.name);
6202 } else if (!a) {
6203 return false;
6204 } else {
6205 // Record dependency for worklist if anything can change within
6206 // the job.
6207 t &= AnalysisDeps::kValidForChanges;
6208 if (t == Type::None) return true;
6209 if (auto const p = folly::get_default(index.funcs, f.name)) {
6210 funcs[p][fc] |= t;
6213 return a;
6216 [[nodiscard]] bool add(ConstIndex cns) {
6217 auto const fc = context();
6218 auto const a = allowed(fc, Class { cns.cls }, false);
6219 if (index.frozen) {
6220 if (auto const c = fc.cls()) {
6221 if (c->name->tsame(cns.cls)) return a;
6223 if (deps[fc].add(cns, index.inTypeCns)) {
6224 FTRACE(2, "{} now depends on class constant {}{}\n",
6225 HHBBC::show(fc), display(cns),
6226 index.inTypeCns ? " (in type-cns)" : "");
6228 } else if (!a) {
6229 return false;
6230 } else if (auto const p = from(cns)) {
6231 clsConstants[p].emplace(fc);
6233 return a;
6236 [[nodiscard]] bool add(AnyClassConstant cns) {
6237 auto const fc = context();
6238 auto const a = allowed(fc, Class { cns.name }, false);
6239 if (index.frozen) {
6240 if (auto const c = fc.cls()) {
6241 if (c->name->tsame(cns.name)) return a;
6243 if (deps[fc].add(cns, index.inTypeCns)) {
6244 FTRACE(2, "{} now depends on any class constant from {}{}\n",
6245 HHBBC::show(fc), cns.name,
6246 index.inTypeCns ? " (in type-cns)" : "");
6248 } else if (!a) {
6249 return false;
6250 } else if (auto const cls = folly::get_default(index.classes, cns.name)) {
6251 anyClsConstants[cls].emplace(fc);
6253 return a;
6256 [[nodiscard]] bool add(Constant cns) {
6257 auto const fc = context();
6258 assertx(!fc.unit());
6259 auto const a = allowed(fc, cns);
6260 if (index.frozen) {
6261 if (deps[fc].add(cns)) {
6262 FTRACE(2, "{} now depends on constant {}\n", HHBBC::show(fc), cns.name);
6264 } else if (!a) {
6265 return false;
6266 } else if (auto const p = folly::get_ptr(index.constants, cns.name)) {
6267 constants[p->first].emplace(fc);
6269 return a;
6272 bool allowed(Class c) const { return allowed(context(), c, false); }
6274 // Mark that the given entity has changed in some way. This not only
6275 // results in the change being reported back to the
6276 // AnalysisScheduler, but will reschedule any work locally which has
6277 // a dependency.
6279 void update(const php::Func& f, Type t) {
6280 if (t == Type::None) return;
6281 assertx(AnalysisDeps::isValidForChanges(t));
6282 assertx(!index.frozen);
6283 FTRACE(
6284 2, "{} {} {} changed, scheduling\n",
6285 f.cls ? "method" : "func",
6286 func_fullname(f),
6287 show(t)
6289 changes.changed(f, t);
6290 schedule(folly::get_ptr(funcs, &f), t);
6293 void update(const php::Const& cns, ConstIndex idx) {
6294 assertx(!index.frozen);
6295 FTRACE(2, "constant {}::{} changed, scheduling\n", idx.cls, cns.name);
6296 changes.changed(idx);
6297 schedule(folly::get_ptr(clsConstants, &cns));
6298 if (auto const p = folly::get_default(index.classes, idx.cls)) {
6299 schedule(folly::get_ptr(anyClsConstants, p));
6303 void update(const php::Constant& cns) {
6304 assertx(!index.frozen);
6305 FTRACE(2, "constant {} changed, scheduling\n", cns.name);
6306 changes.changed(cns);
6307 schedule(folly::get_ptr(constants, &cns));
6310 // Add pre-known dependencies directly.
6311 void preadd(FuncClsUnit fc, Func f, Type t) {
6312 assertx(!index.frozen);
6313 assertx(!fc.unit());
6314 if (t == Type::None) return;
6315 auto const p = folly::get_default(index.funcs, f.name);
6316 if (!p) return;
6317 funcs[p][fc] |= t;
6320 void preadd(FuncClsUnit fc, MethRef m, Type t) {
6321 assertx(!index.frozen);
6322 assertx(!fc.unit());
6323 if (t == Type::None) return;
6324 if (auto const p = from(m)) funcs[p][fc] |= t;
6327 void preadd(FuncClsUnit fc, ConstIndex cns) {
6328 assertx(!index.frozen);
6329 if (auto const p = from(cns)) clsConstants[p].emplace(fc);
6332 void preadd(FuncClsUnit fc, AnyClassConstant cns) {
6333 assertx(!index.frozen);
6334 auto const p = folly::get_default(index.classes, cns.name);
6335 if (p) anyClsConstants[p].emplace(fc);
6338 void preadd(FuncClsUnit fc, Constant cns) {
6339 assertx(!index.frozen);
6340 assertx(!fc.unit());
6341 auto const p = folly::get_ptr(index.constants, cns.name);
6342 if (!p) return;
6343 constants[p->first].emplace(fc);
6346 // Add bucket presence information for the given entities, which
6347 // will be used to perform permission checks.
6348 using BucketPresence = AnalysisInput::BucketPresence;
6350 void restrict(FuncClsUnit fc, BucketPresence b) {
6351 assertx(b.present->contains(index.bucketIdx));
6352 always_assert(allows.emplace(fc, std::move(b)).second);
6355 void restrict(Class c, BucketPresence b) {
6356 assertx(b.present->contains(index.bucketIdx));
6357 assertx(index.badClasses.count(c.name));
6358 always_assert(badClassAllows.emplace(c.name, std::move(b)).second);
6361 void restrict(Func f, BucketPresence b) {
6362 assertx(b.present->contains(index.bucketIdx));
6363 assertx(index.badFuncs.count(f.name));
6364 always_assert(badFuncAllows.emplace(f.name, std::move(b)).second);
6367 void restrict(Constant cns, BucketPresence b) {
6368 assertx(b.present->contains(index.bucketIdx));
6369 assertx(index.badConstants.count(cns.name));
6370 always_assert(badConstantAllows.emplace(cns.name, std::move(b)).second);
6373 const BucketPresence& bucketFor(FuncClsUnit fc) const {
6374 auto const b = folly::get_ptr(allows, fc);
6375 always_assert_flog(b, "Unable to get bucket for {}", show(fc));
6376 return *b;
6379 void reset(FuncClsUnit fc) { deps.erase(fc); }
6381 AnalysisDeps take(FuncClsUnit fc) {
6382 auto it = deps.find(fc);
6383 if (it == end(deps)) return AnalysisDeps{};
6384 return std::move(it->second);
6387 AnalysisChangeSet& getChanges() { return changes; }
6388 const AnalysisChangeSet& getChanges() const { return changes; }
6390 private:
6391 // NB: One entity is allowed to use another entity's information if
6392 // the user's bucket presence is a subset of the usee's bucket
6393 // presence. This means the the usee is present in *all* the buckets
6394 // that the user is in, and therefore all workers will come to the
6395 // same analysis.
6397 bool allowed(FuncClsUnit fc, Class c, bool bytecode) const {
6398 auto const a = folly::get_ptr(allows, fc);
6399 assertx(a);
6400 assertx(a->process->contains(index.bucketIdx));
6401 if (auto const cls = folly::get_default(index.classes, c.name)) {
6402 auto const canon = canonicalize(*cls);
6403 if (auto const c2 = canon.cls()) {
6404 auto const ca = folly::get_ptr(allows, c2);
6405 assertx(ca);
6406 assertx(ca->present->contains(index.bucketIdx));
6407 return a->process->isSubset(
6408 bytecode ? *ca->withBC : *ca->present
6411 if (auto const f = canon.func()) {
6412 auto const fa = folly::get_ptr(allows, f);
6413 assertx(fa);
6414 assertx(fa->present->contains(index.bucketIdx));
6415 return a->process->isSubset(
6416 bytecode ? *fa->withBC : *fa->present
6419 always_assert(false);
6421 if (auto const ta = folly::get_ptr(index.typeAliases, c.name)) {
6422 auto const ua = folly::get_ptr(allows, ta->second);
6423 assertx(ua);
6424 assertx(ua->present->contains(index.bucketIdx));
6425 return a->process->isSubset(*ua->present);
6427 if (index.badClasses.count(c.name)) {
6428 auto const ca = folly::get_ptr(badClassAllows, c.name);
6429 assertx(ca);
6430 assertx(ca->present->contains(index.bucketIdx));
6431 return a->process->isSubset(*ca->present);
6433 return false;
6436 bool allowed(FuncClsUnit fc, Func f, bool bytecode) const {
6437 assertx(!fc.unit());
6438 auto const a = folly::get_ptr(allows, fc);
6439 assertx(a);
6440 assertx(a->process->contains(index.bucketIdx));
6441 if (auto const func = folly::get_default(index.funcs, f.name)) {
6442 auto const fa = folly::get_ptr(allows, func);
6443 assertx(fa);
6444 assertx(fa->present->contains(index.bucketIdx));
6445 return a->process->isSubset(
6446 bytecode ? *fa->withBC : *fa->present
6449 if (index.badFuncs.count(f.name)) {
6450 auto const fa = folly::get_ptr(badFuncAllows, f.name);
6451 assertx(fa);
6452 assertx(fa->present->contains(index.bucketIdx));
6453 return a->process->isSubset(*fa->present);
6455 return false;
6458 bool allowed(FuncClsUnit fc, Constant cns) const {
6459 assertx(!fc.unit());
6460 auto const a = folly::get_ptr(allows, fc);
6461 assertx(a);
6462 assertx(a->process->contains(index.bucketIdx));
6463 if (auto const c = folly::get_ptr(index.constants, cns.name)) {
6464 auto const unit = folly::get_default(index.units, c->second->filename);
6465 assertx(unit);
6466 auto const ua = folly::get_ptr(allows, unit);
6467 assertx(ua);
6468 assertx(ua->present->contains(index.bucketIdx));
6469 return a->process->isSubset(*ua->present);
6471 if (index.badConstants.count(cns.name)) {
6472 auto const ca = folly::get_ptr(badConstantAllows, cns.name);
6473 assertx(ca);
6474 assertx(ca->present->contains(index.bucketIdx));
6475 return a->process->isSubset(*ca->present);
6477 return false;
6480 // Return appropriate entity to attribute the dependency to. If
6481 // we're analyzing a function within a class, use the class. If it's
6482 // a top-level function, use that.
6483 FuncClsUnit context() const {
6484 auto const fc = fc_from_context(context_for_deps(index), index);
6485 if (auto const c = fc.cls()) return canonicalize(*c);
6486 return fc;
6489 // If a class is a closure, the correct context might actually be a
6490 // function.
6491 FuncClsUnit canonicalize(const php::Class& cls) const {
6492 // If this is a closure, the context is the closure's declaring
6493 // class or function.
6494 if (cls.closureContextCls) {
6495 auto const c =
6496 folly::get_default(index.classes, cls.closureContextCls);
6497 always_assert(c);
6498 return c;
6500 if (cls.closureDeclFunc) {
6501 auto const f = folly::get_default(index.funcs, cls.closureDeclFunc);
6502 always_assert(f);
6503 return f;
6505 return &cls;
6508 const php::Func* from(MethRef m) const {
6509 if (auto const cls = folly::get_default(index.classes, m.cls)) {
6510 assertx(m.idx < cls->methods.size());
6511 return cls->methods[m.idx].get();
6513 return nullptr;
6516 const php::Const* from(ConstIndex cns) const {
6517 if (auto const cls = folly::get_default(index.classes, cns.cls)) {
6518 assertx(cns.idx < cls->constants.size());
6519 return &cls->constants[cns.idx];
6521 return nullptr;
6524 std::string display(MethRef m) const {
6525 if (auto const p = from(m)) return func_fullname(*p);
6526 return show(m);
6529 std::string display(ConstIndex cns) const {
6530 if (auto const p = from(cns)) {
6531 return folly::sformat("{}::{}", p->cls, p->name);
6533 return show(cns, AnalysisIndexAdaptor { index.index });
6536 static std::string displayAdded(Type t) {
6537 auto out = show(t - Type::Meta);
6538 if (!out.empty()) folly::format(&out, " of ");
6539 return out;
6542 using FuncClsUnitSet =
6543 hphp_fast_set<FuncClsUnit, FuncClsUnitHasher>;
6544 using FuncClsUnitToType =
6545 hphp_fast_map<FuncClsUnit, Type, FuncClsUnitHasher>;
6547 void schedule(const FuncClsUnitSet* fcs) {
6548 if (!fcs || fcs->empty()) return;
6549 TinyVector<FuncClsUnit, 4> v;
6550 v.insert(begin(*fcs), end(*fcs));
6551 addToWorklist(v);
6554 void schedule(const FuncClsUnitToType* fcs, Type t) {
6555 assertx(!(t & Type::Meta));
6556 if (!fcs || fcs->empty()) return;
6557 TinyVector<FuncClsUnit, 4> v;
6558 for (auto const [fc, t2] : *fcs) {
6559 if (t & t2) v.emplace_back(fc);
6561 addToWorklist(v);
6564 void addToWorklist(TinyVector<FuncClsUnit, 4>& fcs) {
6565 if (fcs.empty()) return;
6566 std::sort(
6567 fcs.begin(), fcs.end(),
6568 [] (FuncClsUnit fc1, FuncClsUnit fc2) {
6569 if (auto const c1 = fc1.cls()) {
6570 if (auto const c2 = fc2.cls()) {
6571 return string_data_lt_type{}(c1->name, c2->name);
6573 return true;
6575 if (auto const f1 = fc1.func()) {
6576 if (auto const f2 = fc2.func()) {
6577 return string_data_lt_func{}(f1->name, f2->name);
6579 return !fc2.cls();
6581 if (auto const u1 = fc1.unit()) {
6582 if (auto const u2 = fc2.unit()) {
6583 return string_data_lt{}(u1->filename, u2->filename);
6585 return !fc2.cls() && !fc2.func();
6587 return false;
6590 Trace::Indent _;
6591 for (auto const fc : fcs) index.worklist.schedule(fc);
6594 const AnalysisIndex::IndexData& index;
6595 AnalysisChangeSet changes;
6596 hphp_fast_map<FuncClsUnit, AnalysisDeps, FuncClsUnitHasher> deps;
6598 hphp_fast_map<FuncClsUnit, BucketPresence, FuncClsUnitHasher> allows;
6599 TSStringToOneT<BucketPresence> badClassAllows;
6600 FSStringToOneT<BucketPresence> badFuncAllows;
6601 SStringToOneT<BucketPresence> badConstantAllows;
6603 hphp_fast_map<const php::Func*, FuncClsUnitToType> funcs;
6604 hphp_fast_map<const php::Const*, FuncClsUnitSet> clsConstants;
6605 hphp_fast_map<const php::Constant*, FuncClsUnitSet> constants;
6606 hphp_fast_map<const php::Class*, FuncClsUnitSet> anyClsConstants;
6609 //////////////////////////////////////////////////////////////////////
6611 using IndexData = Index::IndexData;
6613 std::mutex closure_use_vars_mutex;
6614 std::mutex private_propstate_mutex;
6616 DependencyContext make_dep(const php::Func* func) {
6617 return DependencyContext{DependencyContextType::Func, func};
6619 DependencyContext make_dep(const php::Class* cls) {
6620 return DependencyContext{DependencyContextType::Class, cls};
6622 DependencyContext make_dep(const php::Prop* prop) {
6623 return DependencyContext{DependencyContextType::Prop, prop};
6625 DependencyContext make_dep(const FuncFamily* family) {
6626 return DependencyContext{DependencyContextType::FuncFamily, family};
6629 DependencyContext dep_context(IndexData& data, const Context& baseCtx) {
6630 auto const& ctx = baseCtx.forDep();
6631 if (!ctx.cls || !data.useClassDependencies) return make_dep(ctx.func);
6632 auto const cls = ctx.cls->closureContextCls
6633 ? data.classes.at(ctx.cls->closureContextCls)
6634 : ctx.cls;
6635 if (is_used_trait(*cls)) return make_dep(ctx.func);
6636 return make_dep(cls);
6639 template <typename T>
6640 void add_dependency(IndexData& data,
6641 T src,
6642 const Context& dst,
6643 Dep newMask) {
6644 if (data.frozen) return;
6646 auto d = dep_context(data, dst);
6647 DepMap::accessor acc;
6648 data.dependencyMap.insert(acc, make_dep(src));
6649 auto& current = acc->second[d];
6650 current = current | newMask;
6652 // We should only have a return type dependency on func families.
6653 assertx(
6654 IMPLIES(
6655 acc->first.tag() == DependencyContextType::FuncFamily,
6656 newMask == Dep::ReturnTy
6661 template <typename T>
6662 void find_deps(IndexData& data,
6663 T src,
6664 Dep mask,
6665 DependencyContextSet& deps) {
6666 auto const srcDep = make_dep(src);
6668 // We should only ever have return type dependencies on func family.
6669 assertx(
6670 IMPLIES(
6671 srcDep.tag() == DependencyContextType::FuncFamily,
6672 mask == Dep::ReturnTy
6676 DepMap::const_accessor acc;
6677 if (data.dependencyMap.find(acc, srcDep)) {
6678 for (auto const& kv : acc->second) {
6679 if (has_dep(kv.second, mask)) deps.insert(kv.first);
6684 //////////////////////////////////////////////////////////////////////
6686 FuncInfo* func_info(IndexData& data, const php::Func* f) {
6687 assertx(f->idx < data.funcInfo.size());
6688 auto const fi = &data.funcInfo[f->idx];
6689 assertx(fi->func == f);
6690 return fi;
6693 FuncInfo2& func_info(AnalysisIndex::IndexData& data, const php::Func& f) {
6694 assertx(f.idx < data.finfosByIdx.size());
6695 auto const fi = data.finfosByIdx[f.idx];
6696 assertx(fi->func == &f);
6697 return *fi;
6700 //////////////////////////////////////////////////////////////////////
6702 // Obtain the php::Func* represented by a MethRef.
6703 const php::Func* func_from_meth_ref(const IndexData& index,
6704 const MethRef& meth) {
6705 auto const cls = index.classes.at(meth.cls);
6706 assertx(meth.idx < cls->methods.size());
6707 return cls->methods[meth.idx].get();
6710 const php::Func* func_from_meth_ref(const AnalysisIndex::IndexData& index,
6711 const MethRef& meth) {
6712 if (!index.deps->add(AnalysisDeps::Class { meth.cls })) return nullptr;
6713 auto const cls = folly::get_default(index.classes, meth.cls);
6714 if (!cls) {
6715 always_assert_flog(
6716 !index.badClasses.count(meth.cls),
6717 "MethRef references non-existent class {}\n",
6718 meth.cls
6720 return nullptr;
6722 assertx(meth.idx < cls->methods.size());
6723 return cls->methods[meth.idx].get();
6726 //////////////////////////////////////////////////////////////////////
6730 //////////////////////////////////////////////////////////////////////
6732 // Defined here so that AnalysisIndex::IndexData is a complete type.
6734 bool ClassGraph::storeAuxs(AnalysisIndex::IndexData& i, bool children) const {
6735 assertx(i.frozen);
6737 auto const add = [&] (AuxClassGraphs& auxs) {
6738 if (children) {
6739 if (!auxs.newWithChildren.emplace(*this).second) return false;
6740 auxs.newNoChildren.erase(*this);
6741 return true;
6742 } else {
6743 if (auxs.newWithChildren.count(*this)) return false;
6744 return auxs.newNoChildren.emplace(*this).second;
6748 // Get the current context and store this ClassGraph on it's aux
6749 // list.
6750 auto const fc = fc_from_context(context_for_deps(i), i);
6751 if (auto const c = fc.cls()) {
6752 if (!c->cinfo) return false;
6753 if (c->cinfo == cinfo2()) return true;
6754 if (add(c->cinfo->auxClassGraphs)) {
6755 FTRACE(
6756 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6757 c->name, name(),
6758 children ? " (with children)" : ""
6761 return true;
6762 } else if (auto const f = fc.func()) {
6763 auto& fi = func_info(i, *f);
6764 if (!fi.auxClassGraphs) {
6765 fi.auxClassGraphs = std::make_unique<AuxClassGraphs>();
6767 if (add(*fi.auxClassGraphs)) {
6768 FTRACE(
6769 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6770 f->name, name(),
6771 children ? " (with children)" : ""
6774 return true;
6777 return false;
6780 bool ClassGraph::onAuxs(AnalysisIndex::IndexData& i, bool children) const {
6781 // Check if this ClassGraph is on the current context's aux set *or*
6782 // if it is implied by another ClassGraph on the aux set (for
6783 // example, if this ClassGraph is a parent of a ClassGraph already
6784 // present).
6785 auto const check = [&] (const AuxClassGraphs& auxs, Node* target) {
6786 assertx(!target || !target->isMissing());
6788 if (target == this_) return true;
6789 // Check for direct membership first
6790 if (auxs.withChildren.count(*this)) return true;
6791 if (auxs.noChildren.count(*this)) return !children;
6792 if (this_->isMissing()) return false;
6794 // Check if any parents of this Node are on the set.
6795 if (!auxs.withChildren.empty()) {
6796 auto const a = forEachParent(
6797 *this_,
6798 [&] (Node& p) {
6799 if (target == &p) return Action::Stop;
6800 return auxs.withChildren.count(ClassGraph { &p })
6801 ? Action::Stop
6802 : Action::Continue;
6805 if (a == Action::Stop) return true;
6808 if (children) return false;
6810 TLNodeIdxSet visited;
6811 for (auto const n : auxs.noChildren) {
6812 if (n.this_->isMissing()) continue;
6813 if (findParent(*n.this_, *this_, *visited)) return true;
6815 for (auto const n : auxs.withChildren) {
6816 if (n.this_->isMissing()) continue;
6817 if (findParent(*n.this_, *this_, *visited)) return true;
6819 if (target && findParent(*target, *this_, *visited)) return true;
6821 return false;
6824 auto const fc = fc_from_context(context_for_deps(i), i);
6825 if (auto const c = fc.cls()) {
6826 if (!c->cinfo) return false;
6827 if (c->cinfo == cinfo2()) return true;
6828 return check(c->cinfo->auxClassGraphs, c->cinfo->classGraph.this_);
6831 if (auto const f = fc.func()) {
6832 auto const& fi = func_info(i, *f);
6833 if (!fi.auxClassGraphs) return false;
6834 return check(*fi.auxClassGraphs, nullptr);
6837 return false;
6840 // Ensure ClassGraph is not missing
6841 bool ClassGraph::ensure() const {
6842 assertx(this_);
6843 auto const i = table().index;
6844 if (!i) return true;
6845 if (onAuxs(*i, false)) {
6846 if (i->frozen) always_assert(storeAuxs(*i, false));
6847 return true;
6848 } else if (i->deps->allowed(AnalysisDeps::Class { name() })) {
6849 if (this_->isMissing()) {
6850 always_assert(i->deps->add(AnalysisDeps::Class { name() }));
6852 if (!i->frozen) return true;
6853 if (!storeAuxs(*i, false)) {
6854 (void)i->deps->add(AnalysisDeps::Class { name() });
6856 return true;
6857 } else {
6858 (void)i->deps->add(AnalysisDeps::Class { name() });
6859 return false;
6863 // Ensure ClassGraph is not missing and has complete child
6864 // information.
6865 bool ClassGraph::ensureWithChildren() const {
6866 assertx(this_);
6867 auto const i = table().index;
6868 if (!i) return true;
6869 if (onAuxs(*i, true)) {
6870 if (i->frozen) always_assert(storeAuxs(*i, true));
6871 return true;
6872 } else if (i->deps->allowed(AnalysisDeps::Class { name() })) {
6873 if (this_->isMissing() ||
6874 (!this_->hasCompleteChildren() && !this_->isConservative())) {
6875 always_assert(i->deps->add(AnalysisDeps::Class { name() }));
6877 if (!i->frozen) return true;
6878 if (!storeAuxs(*i, true)) {
6879 (void)i->deps->add(AnalysisDeps::Class { name() });
6881 return true;
6882 } else {
6883 (void)i->deps->add(AnalysisDeps::Class { name() });
6884 return false;
6888 // Ensure ClassGraph is not missing and has an associated ClassInfo2
6889 // (strongest condition).
6890 bool ClassGraph::ensureCInfo() const {
6891 auto const i = table().index;
6892 return !i || i->deps->add(AnalysisDeps::Class { name() });
6895 bool ClassGraph::allowed(bool children) const {
6896 auto const i = table().index;
6897 return !i || onAuxs(*i, children) ||
6898 i->deps->allowed(AnalysisDeps::Class { name() });
6901 //////////////////////////////////////////////////////////////////////
6903 namespace {
6905 //////////////////////////////////////////////////////////////////////
6907 struct TraitMethod {
6908 using class_type = std::pair<const ClassInfo2*, const php::Class*>;
6909 using method_type = const php::Func*;
6910 using origin_type = SString;
6912 TraitMethod(class_type trait_, method_type method_, Attr modifiers_)
6913 : trait(trait_)
6914 , method(method_)
6915 , modifiers(modifiers_)
6918 class_type trait;
6919 method_type method;
6920 Attr modifiers;
6923 struct TMIOps {
6924 using string_type = LSString;
6925 using class_type = TraitMethod::class_type;
6926 using method_type = TraitMethod::method_type;
6927 using origin_type = TraitMethod::origin_type;
6929 struct TMIException : std::exception {
6930 explicit TMIException(std::string msg) : msg(msg) {}
6931 const char* what() const noexcept override { return msg.c_str(); }
6932 private:
6933 std::string msg;
6936 // Return the name for the trait class.
6937 static const string_type clsName(class_type traitCls) {
6938 return traitCls.first->name;
6941 // Return the name of the trait where the method was originally defined
6942 static origin_type originalClass(method_type meth) {
6943 return meth->originalClass ? meth->originalClass : meth->cls->name;
6946 // Is-a methods.
6947 static bool isAbstract(Attr modifiers) {
6948 return modifiers & AttrAbstract;
6951 // Whether to exclude methods with name `methName' when adding.
6952 static bool exclude(string_type methName) {
6953 return Func::isSpecial(methName);
6956 // Errors.
6957 static void errorDuplicateMethod(class_type cls,
6958 string_type methName,
6959 const std::vector<const StringData*>&) {
6960 auto const& m = cls.second->methods;
6961 if (std::find_if(m.begin(), m.end(),
6962 [&] (auto const& f) {
6963 return f->name->same(methName);
6964 }) != m.end()) {
6965 // the duplicate methods will be overridden by the class method.
6966 return;
6968 throw TMIException(folly::sformat("DuplicateMethod: {}", methName));
6972 using TMIData = TraitMethodImportData<TraitMethod, TMIOps>;
6974 //////////////////////////////////////////////////////////////////////
6976 template <typename T, typename R>
6977 void add_symbol_to_index(R& map, T t, const char* type) {
6978 auto const name = t->name;
6979 auto const ret = map.emplace(name, std::move(t));
6980 always_assert_flog(
6981 ret.second,
6982 "More than one {} with the name {} "
6983 "(should have been caught by parser)",
6984 type,
6985 name
6989 template <typename T, typename R, typename E>
6990 void add_symbol_to_index(R& map, T t, const char* type, const E& other) {
6991 auto const it = other.find(t->name);
6992 always_assert_flog(
6993 it == other.end(),
6994 "More than one symbol with the name {} "
6995 "(should have been caught by parser)",
6996 t->name
6998 add_symbol_to_index(map, std::move(t), type);
7001 // We want const qualifiers on various index data structures for php
7002 // object pointers, but during index creation time we need to
7003 // manipulate some of their attributes (changing the representation).
7004 // This little wrapper keeps the const_casting out of the main line of
7005 // code below.
7006 void attribute_setter(const Attr& attrs, bool set, Attr attr) {
7007 attrSetter(const_cast<Attr&>(attrs), set, attr);
7010 void add_system_constants_to_index(IndexData& index) {
7011 for (auto cnsPair : Native::getConstants()) {
7012 assertx(cnsPair.second.m_type != KindOfUninit);
7013 auto pc = new php::Constant {
7014 cnsPair.first,
7015 cnsPair.second,
7016 AttrPersistent
7018 add_symbol_to_index(index.constants, pc, "constant");
7022 void add_unit_to_index(IndexData& index, php::Unit& unit) {
7023 always_assert_flog(
7024 index.units.emplace(unit.filename, &unit).second,
7025 "More than one unit with the same name {} "
7026 "(should have been caught by parser)",
7027 unit.filename
7030 for (auto& ta : unit.typeAliases) {
7031 add_symbol_to_index(
7032 index.typeAliases,
7033 ta.get(),
7034 "type alias",
7035 index.classes
7039 for (auto& c : unit.constants) {
7040 add_symbol_to_index(index.constants, c.get(), "constant");
7043 for (auto& m : unit.modules) {
7044 add_symbol_to_index(index.modules, m.get(), "module");
7048 void add_class_to_index(IndexData& index, php::Class& c) {
7049 if (c.attrs & AttrEnum) {
7050 add_symbol_to_index(index.enums, &c, "enum");
7053 add_symbol_to_index(index.classes, &c, "class", index.typeAliases);
7055 for (auto& m : c.methods) {
7056 attribute_setter(m->attrs, false, AttrNoOverride);
7057 m->idx = index.nextFuncId++;
7061 void add_func_to_index(IndexData& index, php::Func& func) {
7062 add_symbol_to_index(index.funcs, &func, "function");
7063 func.idx = index.nextFuncId++;
7066 void add_program_to_index(IndexData& index) {
7067 trace_time timer{"add program to index", index.sample};
7068 timer.ignore_client_stats();
7070 auto& program = *index.program;
7071 for (auto const& u : program.units) {
7072 add_unit_to_index(index, *u);
7074 for (auto const& c : program.classes) {
7075 add_class_to_index(index, *c);
7076 for (auto const& clo : c->closures) {
7077 add_class_to_index(index, *clo);
7080 for (auto const& f : program.funcs) {
7081 add_func_to_index(index, *f);
7084 for (auto const& c : program.classes) {
7085 assertx(!c->closureContextCls);
7086 for (auto const& clo : c->closures) {
7087 assertx(clo->closureContextCls);
7088 auto& s = index.classClosureMap[index.classes.at(clo->closureContextCls)];
7089 s.emplace_back(clo.get());
7093 // All funcs have been assigned indices above. Now for each func we
7094 // initialize a default FuncInfo in the funcInfo vec at the
7095 // appropriate index.
7097 trace_time timer2{"create func-infos"};
7098 timer2.ignore_client_stats();
7100 index.funcInfo.resize(index.nextFuncId);
7102 auto const create = [&] (const php::Func& f) {
7103 assertx(f.idx < index.funcInfo.size());
7104 auto& fi = index.funcInfo[f.idx];
7105 assertx(!fi.func);
7106 fi.func = &f;
7109 parallel::for_each(
7110 program.classes,
7111 [&] (const std::unique_ptr<php::Class>& cls) {
7112 for (auto const& m : cls->methods) create(*m);
7113 for (auto const& clo : cls->closures) {
7114 assertx(clo->methods.size() == 1);
7115 create(*clo->methods[0]);
7120 parallel::for_each(
7121 program.funcs,
7122 [&] (const std::unique_ptr<php::Func>& func) { create(*func); }
7126 //////////////////////////////////////////////////////////////////////
7129 * Lists of interfaces that conflict with each other due to being
7130 * implemented by the same class.
7133 struct InterfaceConflicts {
7134 SString name{nullptr};
7135 // The number of classes which implements this interface (used to
7136 // prioritize lower slots for more heavily used interfaces).
7137 size_t usage{0};
7138 TSStringSet conflicts;
7139 template <typename SerDe> void serde(SerDe& sd) {
7140 sd(name)(usage)(conflicts, string_data_lt_type{});
7144 void compute_iface_vtables(IndexData& index,
7145 std::vector<InterfaceConflicts> conflicts) {
7146 trace_time tracer{"compute interface vtables"};
7147 tracer.ignore_client_stats();
7149 if (conflicts.empty()) return;
7151 // Sort interfaces by usage frequencies. We assign slots greedily,
7152 // so sort the interface list so the most frequently implemented
7153 // ones come first.
7154 std::sort(
7155 begin(conflicts),
7156 end(conflicts),
7157 [&] (const InterfaceConflicts& a, const InterfaceConflicts& b) {
7158 if (a.usage != b.usage) return a.usage > b.usage;
7159 return string_data_lt_type{}(a.name, b.name);
7163 // Assign slots, keeping track of the largest assigned slot and the
7164 // total number of uses for each slot.
7166 Slot maxSlot = 0;
7167 hphp_fast_map<Slot, int> slotUses;
7168 boost::dynamic_bitset<> used;
7170 for (auto const& iface : conflicts) {
7171 used.reset();
7173 // Find the lowest Slot that doesn't conflict with anything in the
7174 // conflict set for iface.
7175 auto const slot = [&] () -> Slot {
7176 // No conflicts. This is the only interface implemented by the
7177 // classes that implement it.
7178 if (iface.conflicts.empty()) return 0;
7180 for (auto const conflict : iface.conflicts) {
7181 auto const it = index.ifaceSlotMap.find(conflict);
7182 if (it == end(index.ifaceSlotMap)) continue;
7183 auto const s = it->second;
7184 if (used.size() <= s) used.resize(s + 1);
7185 used.set(s);
7188 used.flip();
7189 return used.any() ? used.find_first() : used.size();
7190 }();
7192 always_assert(
7193 index.ifaceSlotMap.emplace(iface.name, slot).second
7195 maxSlot = std::max(maxSlot, slot);
7196 slotUses[slot] += iface.usage;
7199 if (debug) {
7200 // Make sure we have an initialized entry for each slot for the sort below.
7201 for (Slot slot = 0; slot < maxSlot; ++slot) {
7202 always_assert(slotUses.count(slot));
7206 // Finally, sort and reassign slots so the most frequently used
7207 // slots come first. This slightly reduces the number of wasted
7208 // vtable vector entries at runtime.
7210 auto const slots = [&] {
7211 std::vector<std::pair<Slot, int>> flattened{
7212 begin(slotUses), end(slotUses)
7214 std::sort(
7215 begin(flattened),
7216 end(flattened),
7217 [&] (auto const& a, auto const& b) {
7218 if (a.second != b.second) return a.second > b.second;
7219 return a.first < b.first;
7222 std::vector<Slot> out;
7223 out.reserve(flattened.size());
7224 for (auto const& [slot, _] : flattened) out.emplace_back(slot);
7225 return out;
7226 }();
7228 std::vector<Slot> slotsPermute(maxSlot + 1, 0);
7229 for (size_t i = 0; i <= maxSlot; ++i) slotsPermute[slots[i]] = i;
7231 // re-map interfaces to permuted slots
7232 for (auto& [cls, slot] : index.ifaceSlotMap) {
7233 slot = slotsPermute[slot];
7237 //////////////////////////////////////////////////////////////////////
7239 struct CheckClassInfoInvariantsJob {
7240 static std::string name() { return "hhbbc-check-cinfo-invariants"; }
7241 static void init(const Config& config) {
7242 process_init(config.o, config.gd, false);
7243 ClassGraph::init();
7245 static void fini() { ClassGraph::destroy(); }
7247 static bool run(std::unique_ptr<ClassInfo2> cinfo,
7248 std::unique_ptr<php::Class> cls) {
7249 SCOPE_ASSERT_DETAIL("class") { return cls->name->toCppString(); };
7251 always_assert(check(*cls, false));
7253 auto const check = [] (const ClassInfo2* cinfo,
7254 const php::Class* cls) {
7255 // ClassGraph stored in a ClassInfo should not be missing, always
7256 // have the ClassInfo stored, and have complete children
7257 // information.
7258 always_assert(cinfo->classGraph);
7259 always_assert(!cinfo->classGraph.isMissing());
7260 always_assert(cinfo->classGraph.name()->tsame(cinfo->name));
7261 always_assert(cinfo->classGraph.cinfo2() == cinfo);
7262 if (is_closure_base(cinfo->name)) {
7263 // The closure base class is special. We don't store it's
7264 // children information because it's too large.
7265 always_assert(!cinfo->classGraph.hasCompleteChildren());
7266 always_assert(cinfo->classGraph.isConservative());
7267 } else {
7268 always_assert(cinfo->classGraph.hasCompleteChildren() ||
7269 cinfo->classGraph.isConservative());
7272 // This class and withoutNonRegular should be equivalent when
7273 // ignoring non-regular classes. The withoutNonRegular class
7274 // should be a fixed-point.
7275 if (auto const without = cinfo->classGraph.withoutNonRegular()) {
7276 always_assert(without.hasCompleteChildren() ||
7277 without.isConservative());
7278 always_assert(without.subSubtypeOf(cinfo->classGraph, false, false));
7279 always_assert(cinfo->classGraph.subSubtypeOf(without, false, false));
7280 always_assert(without.withoutNonRegular() == without);
7281 always_assert(cinfo->classGraph.mightBeRegular() ||
7282 cinfo->classGraph.mightHaveRegularSubclass());
7283 always_assert(IMPLIES(cinfo->classGraph.mightBeRegular(),
7284 without == cinfo->classGraph));
7285 } else if (!is_used_trait(*cls)) {
7286 always_assert(!cinfo->classGraph.mightBeRegular());
7287 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7290 // AttrNoOverride is a superset of AttrNoOverrideRegular
7291 always_assert(
7292 IMPLIES(!(cls->attrs & AttrNoOverrideRegular),
7293 !(cls->attrs & AttrNoOverride))
7296 // Override attrs and what we know about the subclasses should be in
7297 // agreement.
7298 if (cls->attrs & AttrNoOverride) {
7299 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7300 always_assert(!cinfo->classGraph.mightHaveNonRegularSubclass());
7301 } else if (cls->attrs & AttrNoOverrideRegular) {
7302 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7303 always_assert(cinfo->classGraph.mightHaveNonRegularSubclass());
7306 // Make sure the information stored on the ClassInfo matches that
7307 // which the ClassGraph reports.
7308 if (cls->attrs & AttrNoMock) {
7309 always_assert(!cinfo->isMocked);
7310 always_assert(!cinfo->isSubMocked);
7311 } else {
7312 always_assert(cinfo->isSubMocked);
7315 always_assert(
7316 bool(cls->attrs & AttrNoExpandTrait) ==
7317 cinfo->classGraph.usedTraits().empty()
7320 for (auto const& [name, mte] : cinfo->methods) {
7321 // Interface method tables should only contain its own methods.
7322 if (cls->attrs & AttrInterface) {
7323 always_assert(mte.meth().cls->tsame(cinfo->name));
7326 // AttrNoOverride implies noOverrideRegular
7327 always_assert(IMPLIES(mte.attrs & AttrNoOverride, mte.noOverrideRegular()));
7329 if (!is_special_method_name(name)) {
7330 // If the class isn't overridden, none of it's methods can be
7331 // either.
7332 always_assert(IMPLIES(cls->attrs & AttrNoOverride,
7333 mte.attrs & AttrNoOverride));
7334 } else {
7335 always_assert(!(mte.attrs & AttrNoOverride));
7336 always_assert(!mte.noOverrideRegular());
7339 if (cinfo->name->tsame(s_Closure.get()) || is_closure_name(cinfo->name)) {
7340 always_assert(mte.attrs & AttrNoOverride);
7343 // Don't store method families for special methods.
7344 auto const famIt = cinfo->methodFamilies.find(name);
7345 if (is_special_method_name(name)) {
7346 always_assert(famIt == end(cinfo->methodFamilies));
7347 continue;
7349 if (famIt == end(cinfo->methodFamilies)) {
7350 always_assert(is_closure_name(cinfo->name));
7351 continue;
7353 auto const& entry = famIt->second;
7355 // No override methods should always have a single entry.
7356 if (mte.attrs & AttrNoOverride) {
7357 always_assert(
7358 boost::get<FuncFamilyEntry::BothSingle>(&entry.m_meths) ||
7359 boost::get<FuncFamilyEntry::SingleAndNone>(&entry.m_meths)
7361 continue;
7364 if (cinfo->isRegularClass) {
7365 // "all" should only be a func family. It can't be empty,
7366 // because we know there's at least one method in it (the one in
7367 // cinfo->methods). It can't be a single func, because one of
7368 // the methods must be the cinfo->methods method, and we know it
7369 // isn't AttrNoOverride, so there *must* be another method. So,
7370 // it must be a func family.
7371 always_assert(
7372 boost::get<FuncFamilyEntry::BothFF>(&entry.m_meths) ||
7373 boost::get<FuncFamilyEntry::FFAndSingle>(&entry.m_meths)
7375 // This is a regular class, so we cannot have an incomplete
7376 // entry (can only happen with interfaces).
7377 always_assert(!entry.m_regularIncomplete);
7381 // If the class is marked as having not having bad initial prop
7382 // values, all of it's properties should have
7383 // AttrInitialSatisfiesTC set. Likewise, if it is, at least one
7384 // property should not have it set.
7385 if (!cinfo->hasBadInitialPropValues) {
7386 auto const all = std::all_of(
7387 begin(cls->properties),
7388 end(cls->properties),
7389 [] (const php::Prop& p) {
7390 return p.attrs & AttrInitialSatisfiesTC;
7393 always_assert(all);
7394 } else {
7395 auto const someBad = std::any_of(
7396 begin(cls->properties),
7397 end(cls->properties),
7398 [] (const php::Prop& p) {
7399 return !(p.attrs & AttrInitialSatisfiesTC);
7402 always_assert(someBad);
7405 if (is_closure_name(cinfo->name)) {
7406 assertx(cinfo->classGraph.hasCompleteChildren());
7407 // Closures have no children.
7408 auto const subclasses = cinfo->classGraph.children();
7409 always_assert(subclasses.size() == 1);
7410 always_assert(subclasses[0].name()->tsame(cinfo->name));
7411 } else if (cinfo->classGraph.hasCompleteChildren()) {
7412 // Otherwise the children list is non-empty, contains this
7413 // class, and contains only unique elements.
7414 auto const subclasses = cinfo->classGraph.children();
7415 always_assert(
7416 std::find_if(
7417 begin(subclasses),
7418 end(subclasses),
7419 [&] (ClassGraph g) { return g.name()->tsame(cinfo->name); }
7420 ) != end(subclasses)
7422 auto cpy = subclasses;
7423 std::sort(begin(cpy), end(cpy));
7424 cpy.erase(std::unique(begin(cpy), end(cpy)), end(cpy));
7425 always_assert(cpy.size() == subclasses.size());
7428 // The base list is non-empty, and the last element is this class.
7429 auto const bases = cinfo->classGraph.bases();
7430 always_assert(!bases.empty());
7431 always_assert(cinfo->classGraph == bases.back());
7432 if (is_closure_base(cinfo->name)) {
7433 always_assert(bases.size() == 1);
7434 } else if (is_closure_name(cinfo->name)) {
7435 always_assert(bases.size() == 2);
7436 always_assert(bases[0].name()->tsame(s_Closure.get()));
7439 always_assert(IMPLIES(is_closure(*cls), cls->closures.empty()));
7440 always_assert(cls->closures.size() == cinfo->closures.size());
7443 check(cinfo.get(), cls.get());
7444 for (size_t i = 0, size = cls->closures.size(); i < size; ++i) {
7445 always_assert(cls->closures[i]->name->tsame(cinfo->closures[i]->name));
7446 always_assert(is_closure(*cls->closures[i]));
7447 check(cinfo->closures[i].get(), cls->closures[i].get());
7450 return true;
7454 struct CheckFuncFamilyInvariantsJob {
7455 static std::string name() { return "hhbbc-check-ff-invariants"; }
7456 static void init(const Config& config) {
7457 process_init(config.o, config.gd, false);
7459 static void fini() {}
7461 static bool run(FuncFamilyGroup group) {
7462 for (auto const& ff : group.m_ffs) {
7463 // FuncFamily should always have more than one func on it.
7464 always_assert(
7465 ff->m_regular.size() +
7466 ff->m_nonRegularPrivate.size() +
7467 ff->m_nonRegular.size()
7471 // Every method should be sorted in its respective list. We
7472 // should never see a method for the same Class more than once.
7473 TSStringSet classes;
7474 Optional<MethRef> lastReg;
7475 Optional<MethRef> lastPrivate;
7476 Optional<MethRef> lastNonReg;
7477 for (auto const& meth : ff->m_regular) {
7478 if (lastReg) always_assert(*lastReg < meth);
7479 lastReg = meth;
7480 always_assert(classes.emplace(meth.cls).second);
7482 for (auto const& meth : ff->m_nonRegularPrivate) {
7483 if (lastPrivate) always_assert(*lastPrivate < meth);
7484 lastPrivate = meth;
7485 always_assert(classes.emplace(meth.cls).second);
7487 for (auto const& meth : ff->m_nonRegular) {
7488 if (lastNonReg) always_assert(*lastNonReg < meth);
7489 lastNonReg = meth;
7490 always_assert(classes.emplace(meth.cls).second);
7493 always_assert(ff->m_allStatic.has_value());
7494 always_assert(
7495 ff->m_regularStatic.has_value() ==
7496 (!ff->m_regular.empty() || !ff->m_nonRegularPrivate.empty())
7499 return true;
7503 Job<CheckClassInfoInvariantsJob> s_checkCInfoInvariantsJob;
7504 Job<CheckFuncFamilyInvariantsJob> s_checkFuncFamilyInvariantsJob;
7506 void check_invariants(const IndexData& index) {
7507 if (!debug) return;
7509 trace_time trace{"check-invariants", index.sample};
7511 constexpr size_t kCInfoBucketSize = 3000;
7512 constexpr size_t kFFBucketSize = 500;
7514 auto cinfoBuckets = consistently_bucketize(
7515 [&] {
7516 std::vector<SString> roots;
7517 roots.reserve(index.classInfoRefs.size());
7518 for (auto const& [name, _] : index.classInfoRefs) {
7519 roots.emplace_back(name);
7521 return roots;
7522 }(),
7523 kCInfoBucketSize
7526 SStringToOneT<Ref<FuncFamilyGroup>> nameToFuncFamilyGroup;
7528 auto ffBuckets = consistently_bucketize(
7529 [&] {
7530 std::vector<SString> roots;
7531 roots.reserve(index.funcFamilyRefs.size());
7532 for (auto const& [_, ref] : index.funcFamilyRefs) {
7533 auto const name = makeStaticString(ref.id().toString());
7534 if (nameToFuncFamilyGroup.emplace(name, ref).second) {
7535 roots.emplace_back(name);
7538 return roots;
7539 }(),
7540 kFFBucketSize
7543 using namespace folly::gen;
7545 auto const runCInfo = [&] (std::vector<SString> work) -> coro::Task<void> {
7546 co_await coro::co_reschedule_on_current_executor;
7548 if (work.empty()) co_return;
7550 auto inputs = from(work)
7551 | map([&] (SString name) {
7552 return std::make_tuple(
7553 index.classInfoRefs.at(name),
7554 index.classRefs.at(name)
7557 | as<std::vector>();
7559 Client::ExecMetadata metadata{
7560 .job_key = folly::sformat("check cinfo invariants {}", work[0])
7562 auto config = co_await index.configRef->getCopy();
7563 auto outputs = co_await index.client->exec(
7564 s_checkCInfoInvariantsJob,
7565 std::move(config),
7566 std::move(inputs),
7567 std::move(metadata)
7569 assertx(outputs.size() == work.size());
7571 co_return;
7574 auto const runFF = [&] (std::vector<SString> work) -> coro::Task<void> {
7575 co_await coro::co_reschedule_on_current_executor;
7577 if (work.empty()) co_return;
7579 auto inputs = from(work)
7580 | map([&] (SString name) {
7581 return std::make_tuple(nameToFuncFamilyGroup.at(name));
7583 | as<std::vector>();
7585 Client::ExecMetadata metadata{
7586 .job_key = folly::sformat("check func-family invariants {}", work[0])
7588 auto config = co_await index.configRef->getCopy();
7589 auto outputs = co_await index.client->exec(
7590 s_checkFuncFamilyInvariantsJob,
7591 std::move(config),
7592 std::move(inputs),
7593 std::move(metadata)
7595 assertx(outputs.size() == work.size());
7597 co_return;
7600 std::vector<coro::TaskWithExecutor<void>> tasks;
7601 for (auto& work : cinfoBuckets) {
7602 tasks.emplace_back(
7603 runCInfo(std::move(work)).scheduleOn(index.executor->sticky())
7606 for (auto& work : ffBuckets) {
7607 tasks.emplace_back(
7608 runFF(std::move(work)).scheduleOn(index.executor->sticky())
7611 coro::blockingWait(coro::collectAllRange(std::move(tasks)));
7614 void check_local_invariants(const IndexData& index, const ClassInfo* cinfo) {
7615 SCOPE_ASSERT_DETAIL("class") { return cinfo->cls->name->toCppString(); };
7617 always_assert(check(*cinfo->cls));
7619 // AttrNoOverride is a superset of AttrNoOverrideRegular
7620 always_assert(
7621 IMPLIES(!(cinfo->cls->attrs & AttrNoOverrideRegular),
7622 !(cinfo->cls->attrs & AttrNoOverride))
7625 always_assert(cinfo->classGraph);
7626 always_assert(!cinfo->classGraph.isMissing());
7627 always_assert(cinfo->classGraph.name()->tsame(cinfo->cls->name));
7628 always_assert(cinfo->classGraph.cinfo() == cinfo);
7629 if (is_closure_base(cinfo->cls->name)) {
7630 // The closure base class is special. We don't store it's children
7631 // information because it's too large.
7632 always_assert(!cinfo->classGraph.hasCompleteChildren());
7633 always_assert(cinfo->classGraph.isConservative());
7634 } else {
7635 always_assert(cinfo->classGraph.hasCompleteChildren() ||
7636 cinfo->classGraph.isConservative());
7639 // This class and withoutNonRegular should be equivalent when
7640 // ignoring non-regular classes. The withoutNonRegular class should
7641 // be a fixed-point.
7642 if (auto const without = cinfo->classGraph.withoutNonRegular()) {
7643 always_assert(without.hasCompleteChildren() ||
7644 without.isConservative());
7645 always_assert(without.subSubtypeOf(cinfo->classGraph, false, false));
7646 always_assert(cinfo->classGraph.subSubtypeOf(without, false, false));
7647 always_assert(without.withoutNonRegular() == without);
7648 always_assert(cinfo->classGraph.mightBeRegular() ||
7649 cinfo->classGraph.mightHaveRegularSubclass());
7650 always_assert(IMPLIES(cinfo->classGraph.mightBeRegular(),
7651 without == cinfo->classGraph));
7652 } else if (!is_used_trait(*cinfo->cls)) {
7653 always_assert(!cinfo->classGraph.mightBeRegular());
7654 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7657 // Override attrs and what we know about the subclasses should be in
7658 // agreement.
7659 if (cinfo->cls->attrs & AttrNoOverride) {
7660 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7661 always_assert(!cinfo->classGraph.mightHaveNonRegularSubclass());
7662 } else if (cinfo->cls->attrs & AttrNoOverrideRegular) {
7663 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7664 always_assert(cinfo->classGraph.mightHaveNonRegularSubclass());
7667 if (cinfo->cls->attrs & AttrNoMock) {
7668 always_assert(!cinfo->isMocked);
7669 always_assert(!cinfo->isSubMocked);
7672 // An AttrNoExpand class shouldn't have any used traits.
7673 always_assert(
7674 bool(cinfo->cls->attrs & AttrNoExpandTrait) ==
7675 cinfo->usedTraits.empty()
7678 for (size_t idx = 0; idx < cinfo->cls->methods.size(); ++idx) {
7679 // Each method in a class has an entry in its ClassInfo method
7680 // table.
7681 auto const& m = cinfo->cls->methods[idx];
7682 auto const it = cinfo->methods.find(m->name);
7683 always_assert(it != cinfo->methods.end());
7684 always_assert(it->second.meth().cls->tsame(cinfo->cls->name));
7685 always_assert(it->second.meth().idx == idx);
7687 // Every method (except for constructors and special methods
7688 // should be in the global name-only tables.
7689 auto const nameIt = index.methodFamilies.find(m->name);
7690 if (!has_name_only_func_family(m->name)) {
7691 always_assert(nameIt == end(index.methodFamilies));
7692 continue;
7694 always_assert(nameIt != end(index.methodFamilies));
7696 auto const& entry = nameIt->second;
7697 // The global name-only tables are never complete.
7698 always_assert(entry.m_all.isIncomplete());
7699 always_assert(entry.m_regular.isEmpty() || entry.m_regular.isIncomplete());
7701 // "all" should always be non-empty and contain this method.
7702 always_assert(!entry.m_all.isEmpty());
7703 if (auto const ff = entry.m_all.funcFamily()) {
7704 always_assert(ff->possibleFuncs().size() > 1);
7705 // The FuncFamily shouldn't have a section for regular results
7706 // if "regular" isn't using it.
7707 if (entry.m_regular.func() || entry.m_regular.isEmpty()) {
7708 always_assert(!ff->m_regular);
7709 } else {
7710 // "all" and "regular" always share the same func family.
7711 always_assert(entry.m_regular.funcFamily() == ff);
7713 } else {
7714 auto const func = entry.m_all.func();
7715 always_assert(func);
7716 always_assert(func == m.get());
7717 // "regular" is always a subset of "all", so it can either be a
7718 // single func (the same as "all"), or empty.
7719 always_assert(entry.m_regular.func() || entry.m_regular.isEmpty());
7720 if (auto const func2 = entry.m_regular.func()) {
7721 always_assert(func == func2);
7725 // If this is a regular class, "regular" should be non-empty and
7726 // contain this method.
7727 if (auto const ff = entry.m_regular.funcFamily()) {
7728 always_assert(ff->possibleFuncs().size() > 1);
7729 } else if (auto const func = entry.m_regular.func()) {
7730 if (is_regular_class(*cinfo->cls)) {
7731 always_assert(func == m.get());
7733 } else {
7734 always_assert(!is_regular_class(*cinfo->cls));
7738 // Interface ClassInfo method table should only contain methods from
7739 // the interface itself.
7740 if (cinfo->cls->attrs & AttrInterface) {
7741 always_assert(cinfo->cls->methods.size() == cinfo->methods.size());
7744 // If a class isn't overridden, it shouldn't have any func families
7745 // (because the method table is sufficient).
7746 if (cinfo->cls->attrs & AttrNoOverride) {
7747 always_assert(cinfo->methodFamilies.empty());
7748 always_assert(cinfo->methodFamiliesAux.empty());
7751 // The auxiliary method families map is only used by non-regular
7752 // classes.
7753 if (is_regular_class(*cinfo->cls)) {
7754 always_assert(cinfo->methodFamiliesAux.empty());
7757 for (auto const& [name, mte] : cinfo->methods) {
7758 // Interface method tables should only contain its own methods.
7759 if (cinfo->cls->attrs & AttrInterface) {
7760 always_assert(mte.meth().cls->tsame(cinfo->cls->name));
7761 } else {
7762 // Non-interface method tables should not contain any methods
7763 // defined by an interface.
7764 auto const func = func_from_meth_ref(index, mte.meth());
7765 always_assert(!(func->cls->attrs & AttrInterface));
7768 // AttrNoOverride implies noOverrideRegular
7769 always_assert(IMPLIES(mte.attrs & AttrNoOverride, mte.noOverrideRegular()));
7771 if (!is_special_method_name(name)) {
7772 // If the class isn't overridden, none of it's methods can be
7773 // either.
7774 always_assert(IMPLIES(cinfo->cls->attrs & AttrNoOverride,
7775 mte.attrs & AttrNoOverride));
7776 } else {
7777 always_assert(!(mte.attrs & AttrNoOverride));
7778 always_assert(!mte.noOverrideRegular());
7781 if (is_closure_base(*cinfo->cls) || is_closure(*cinfo->cls)) {
7782 always_assert(mte.attrs & AttrNoOverride);
7785 auto const famIt = cinfo->methodFamilies.find(name);
7786 // Don't store method families for special methods, or if there's
7787 // no override.
7788 if (is_special_method_name(name) || (mte.attrs & AttrNoOverride)) {
7789 always_assert(famIt == end(cinfo->methodFamilies));
7790 always_assert(!cinfo->methodFamiliesAux.count(name));
7791 continue;
7792 } else {
7793 always_assert(famIt != end(cinfo->methodFamilies));
7795 auto const& entry = famIt->second;
7797 if (is_regular_class(*cinfo->cls)) {
7798 // "all" should only be a func family. It can't be empty,
7799 // because we know there's at least one method in it (the one in
7800 // cinfo->methods). It can't be a single func, because one of
7801 // the methods must be the cinfo->methods method, and we know it
7802 // isn't AttrNoOverride, so there *must* be another method. So,
7803 // it must be a func family.
7804 always_assert(entry.funcFamily());
7805 // This is a regular class, so we cannot have an incomplete
7806 // entry (can only happen with interfaces).
7807 always_assert(entry.isComplete());
7808 } else {
7809 // This class isn't AttrNoOverride, and since the method is on
7810 // this class, it should at least contain that.
7811 always_assert(!entry.isEmpty());
7812 // Only interfaces can have incomplete entries.
7813 always_assert(
7814 IMPLIES(entry.isIncomplete(), cinfo->cls->attrs & AttrInterface)
7816 // If we got a single func, it should be the func on this
7817 // class. Since this isn't AttrNoOverride, it implies the entry
7818 // should be incomplete.
7819 always_assert(IMPLIES(entry.func(), entry.isIncomplete()));
7820 always_assert(
7821 IMPLIES(entry.func(),
7822 entry.func() == func_from_meth_ref(index, mte.meth()))
7825 // The "aux" entry is optional. If it isn't present, it's the
7826 // same as the normal table.
7827 auto const auxIt = cinfo->methodFamiliesAux.find(name);
7828 if (auxIt != end(cinfo->methodFamiliesAux)) {
7829 auto const& aux = auxIt->second;
7831 // We shouldn't store in the aux table if the entry is the
7832 // same or if there's no override.
7833 always_assert(!mte.noOverrideRegular());
7834 always_assert(
7835 aux.isIncomplete() ||
7836 aux.func() != entry.func() ||
7837 aux.funcFamily() != entry.funcFamily()
7840 // Normally the aux should be non-empty and complete. However
7841 // if this class is an interface, they could be.
7842 always_assert(
7843 IMPLIES(aux.isEmpty(), cinfo->cls->attrs & AttrInterface)
7845 always_assert(
7846 IMPLIES(aux.isIncomplete(), cinfo->cls->attrs & AttrInterface)
7849 // Since we know this was overridden (it wouldn't be in the
7850 // aux table otherwise), it must either be incomplete, or if
7851 // it has a single func, it cannot be the same func as this
7852 // class.
7853 always_assert(
7854 aux.isIncomplete() ||
7855 ((mte.attrs & AttrPrivate) && mte.topLevel()) ||
7856 aux.func() != func_from_meth_ref(index, mte.meth())
7859 // Aux entry is a subset of the normal entry. If they both
7860 // have a func family or func, they must be the same. If the
7861 // normal entry has a func family, but aux doesn't, that func
7862 // family shouldn't have extra space allocated.
7863 always_assert(IMPLIES(entry.func(), !aux.funcFamily()));
7864 always_assert(IMPLIES(entry.funcFamily() && aux.funcFamily(),
7865 entry.funcFamily() == aux.funcFamily()));
7866 always_assert(IMPLIES(entry.func() && aux.func(),
7867 entry.func() == aux.func()));
7868 always_assert(IMPLIES(entry.funcFamily() && !aux.funcFamily(),
7869 !entry.funcFamily()->m_regular));
7874 // "Aux" entries should only exist for methods on this class, and
7875 // with a corresponding methodFamilies entry.
7876 for (auto const& [name, _] : cinfo->methodFamiliesAux) {
7877 always_assert(cinfo->methods.count(name));
7878 always_assert(cinfo->methodFamilies.count(name));
7881 // We should only have func families for methods declared on this
7882 // class (except for interfaces and abstract classes).
7883 for (auto const& [name, entry] : cinfo->methodFamilies) {
7884 if (cinfo->methods.count(name)) continue;
7885 // Interfaces and abstract classes can have func families for
7886 // methods not defined on this class.
7887 always_assert(cinfo->cls->attrs & (AttrInterface|AttrAbstract));
7888 // We don't expand func families for these.
7889 always_assert(name != s_construct.get() && !is_special_method_name(name));
7891 // We only expand entries for interfaces and abstract classes if
7892 // it appears in every regular subclass. Therefore it cannot be
7893 // empty and is complete.
7894 always_assert(!entry.isEmpty());
7895 always_assert(entry.isComplete());
7896 if (auto const ff = entry.funcFamily()) {
7897 always_assert(!ff->m_regular);
7898 } else if (auto const func = entry.func()) {
7899 always_assert(func->cls != cinfo->cls);
7903 // If the class is marked as having not having bad initial prop
7904 // values, all of it's properties should have AttrInitialSatisfiesTC
7905 // set. Likewise, if it is, at least one property should not have it
7906 // set.
7907 if (!cinfo->hasBadInitialPropValues) {
7908 auto const all = std::all_of(
7909 begin(cinfo->cls->properties),
7910 end(cinfo->cls->properties),
7911 [] (const php::Prop& p) {
7912 return p.attrs & AttrInitialSatisfiesTC;
7915 always_assert(all);
7916 } else {
7917 auto const someBad = std::any_of(
7918 begin(cinfo->cls->properties),
7919 end(cinfo->cls->properties),
7920 [] (const php::Prop& p) {
7921 return !(p.attrs & AttrInitialSatisfiesTC);
7924 always_assert(someBad);
7927 if (is_closure_name(cinfo->cls->name)) {
7928 assertx(cinfo->classGraph.hasCompleteChildren());
7929 // Closures have no children.
7930 auto const subclasses = cinfo->classGraph.children();
7931 always_assert(subclasses.size() == 1);
7932 always_assert(subclasses[0].name()->tsame(cinfo->cls->name));
7933 } else if (cinfo->classGraph.hasCompleteChildren()) {
7934 // Otherwise the children list is non-empty, contains this
7935 // class, and contains only unique elements.
7936 auto const subclasses = cinfo->classGraph.children();
7937 always_assert(
7938 std::find_if(
7939 begin(subclasses),
7940 end(subclasses),
7941 [&] (ClassGraph g) { return g.name()->tsame(cinfo->cls->name); }
7942 ) != end(subclasses)
7944 auto cpy = subclasses;
7945 std::sort(begin(cpy), end(cpy));
7946 cpy.erase(std::unique(begin(cpy), end(cpy)), end(cpy));
7947 always_assert(cpy.size() == subclasses.size());
7950 // The base list is non-empty, and the last element is this class.
7951 auto const bases = cinfo->classGraph.bases();
7952 always_assert(!bases.empty());
7953 always_assert(cinfo->classGraph == bases.back());
7954 if (is_closure_base(cinfo->cls->name)) {
7955 always_assert(bases.size() == 1);
7956 } else if (is_closure_name(cinfo->cls->name)) {
7957 always_assert(bases.size() == 2);
7958 always_assert(bases[0].name()->tsame(s_Closure.get()));
7962 void check_local_invariants(const IndexData& data, const FuncFamily& ff) {
7963 // FuncFamily should always have more than one func on it.
7964 always_assert(ff.possibleFuncs().size() > 1);
7966 SString name{nullptr};
7967 FuncFamily::PossibleFunc last{nullptr, false};
7968 for (auto const pf : ff.possibleFuncs()) {
7969 // Should only contain methods
7970 always_assert(pf.ptr()->cls);
7972 // Every method on the list should have the same name.
7973 if (!name) {
7974 name = pf.ptr()->name;
7975 } else {
7976 always_assert(name == pf.ptr()->name);
7979 // Verify the list is sorted and doesn't contain any duplicates.
7980 hphp_fast_set<const php::Func*> seen;
7981 if (last.ptr()) {
7982 always_assert(
7983 [&] {
7984 if (last.inRegular() && !pf.inRegular()) return true;
7985 if (!last.inRegular() && pf.inRegular()) return false;
7986 return string_data_lt_type{}(last.ptr()->cls->name, pf.ptr()->cls->name);
7990 always_assert(seen.emplace(pf.ptr()).second);
7991 last = pf;
7994 if (!ff.possibleFuncs().front().inRegular() ||
7995 ff.possibleFuncs().back().inRegular()) {
7996 // If there's no funcs on a regular class, or if all functions are
7997 // on a regular class, we don't need to keep separate information
7998 // for the regular subset (it either doesn't exist, or it's equal to
7999 // the entire list).
8000 always_assert(!ff.m_regular);
8004 void check_local_invariants(const IndexData& data) {
8005 if (!debug) return;
8007 trace_time timer{"check-local-invariants"};
8009 parallel::for_each(
8010 data.allClassInfos,
8011 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
8012 check_local_invariants(data, cinfo.get());
8016 std::vector<const FuncFamily*> funcFamilies;
8017 funcFamilies.reserve(data.funcFamilies.size());
8018 for (auto const& [ff, _] : data.funcFamilies) {
8019 funcFamilies.emplace_back(ff.get());
8021 parallel::for_each(
8022 funcFamilies,
8023 [&] (const FuncFamily* ff) { check_local_invariants(data, *ff); }
8027 //////////////////////////////////////////////////////////////////////
8029 Type adjust_closure_context(const IIndex& index, const CallContext& ctx) {
8030 if (ctx.callee->cls && ctx.callee->cls->closureContextCls) {
8031 auto const withClosureContext = Context {
8032 ctx.callee->unit,
8033 ctx.callee,
8034 index.lookup_closure_context(*ctx.callee->cls)
8036 if (auto const s = selfCls(index, withClosureContext)) {
8037 return setctx(toobj(*s));
8039 return TObj;
8041 return ctx.context;
8044 Index::ReturnType context_sensitive_return_type(IndexData& data,
8045 const Context& ctx,
8046 CallContext callCtx,
8047 Index::ReturnType returnType) {
8048 constexpr auto max_interp_nexting_level = 2;
8049 static __thread uint32_t interp_nesting_level;
8050 auto const finfo = func_info(data, callCtx.callee);
8052 auto const adjustedCtx = adjust_closure_context(
8053 IndexAdaptor { *data.m_index },
8054 callCtx
8056 returnType.t = return_with_context(std::move(returnType.t), adjustedCtx);
8058 auto const checkParam = [&] (int i) {
8059 auto const& constraint = finfo->func->params[i].typeConstraint;
8060 if (constraint.hasConstraint() &&
8061 !constraint.isTypeVar() &&
8062 !constraint.isTypeConstant()) {
8063 auto const ctx = Context {
8064 finfo->func->unit,
8065 finfo->func,
8066 finfo->func->cls
8068 return callCtx.args[i].strictlyMoreRefined(
8069 lookup_constraint(IndexAdaptor { *data.m_index }, ctx, constraint).upper
8072 return callCtx.args[i].strictSubtypeOf(TInitCell);
8075 // TODO(#3788877): more heuristics here would be useful.
8076 auto const tryContextSensitive = [&] {
8077 if (finfo->func->noContextSensitiveAnalysis ||
8078 finfo->func->params.empty() ||
8079 interp_nesting_level + 1 >= max_interp_nexting_level ||
8080 returnType.t.is(BBottom)) {
8081 return false;
8084 if (finfo->retParam != NoLocalId &&
8085 callCtx.args.size() > finfo->retParam &&
8086 checkParam(finfo->retParam)) {
8087 return true;
8090 if (!options.ContextSensitiveInterp) return false;
8092 if (callCtx.args.size() < finfo->func->params.size()) return true;
8093 for (auto i = 0; i < finfo->func->params.size(); i++) {
8094 if (checkParam(i)) return true;
8096 return false;
8097 }();
8099 if (!tryContextSensitive) return returnType;
8102 ContextRetTyMap::const_accessor acc;
8103 if (data.contextualReturnTypes.find(acc, callCtx)) {
8104 if (data.frozen ||
8105 acc->second.t.is(BBottom) ||
8106 is_scalar(acc->second.t)) {
8107 return acc->second;
8112 if (data.frozen) return returnType;
8114 auto contextType = [&] {
8115 ++interp_nesting_level;
8116 SCOPE_EXIT { --interp_nesting_level; };
8118 auto const func = finfo->func;
8119 auto const wf = php::WideFunc::cns(func);
8120 auto const calleeCtx = AnalysisContext {
8121 func->unit,
8123 func->cls,
8124 &ctx.forDep()
8126 auto fa = analyze_func_inline(
8127 IndexAdaptor { *data.m_index },
8128 calleeCtx,
8129 adjustedCtx,
8130 callCtx.args
8132 return Index::ReturnType{
8133 return_with_context(std::move(fa.inferredReturn), adjustedCtx),
8134 fa.effectFree
8136 }();
8138 if (!interp_nesting_level) {
8139 FTRACE(3,
8140 "Context sensitive type: {}\n"
8141 "Context insensitive type: {}\n",
8142 show(contextType.t), show(returnType.t));
8145 if (!returnType.t.subtypeOf(BUnc)) {
8146 // If the context insensitive return type could be non-static, staticness
8147 // could be a result of temporary context sensitive bytecode optimizations.
8148 contextType.t = loosen_staticness(std::move(contextType.t));
8151 auto ret = Index::ReturnType{
8152 intersection_of(std::move(returnType.t), std::move(contextType.t)),
8153 returnType.effectFree && contextType.effectFree
8156 if (!interp_nesting_level) {
8157 FTRACE(3, "Context sensitive result: {}\n", show(ret.t));
8160 ContextRetTyMap::accessor acc;
8161 if (data.contextualReturnTypes.insert(acc, callCtx) ||
8162 ret.t.strictSubtypeOf(acc->second.t) ||
8163 (ret.effectFree && !acc->second.effectFree)) {
8164 acc->second = ret;
8167 return ret;
8170 //////////////////////////////////////////////////////////////////////
8172 Index::ReturnType context_sensitive_return_type(AnalysisIndex::IndexData& data,
8173 const CallContext& callCtx,
8174 Index::ReturnType returnType) {
8175 constexpr size_t maxNestingLevel = 2;
8177 using R = Index::ReturnType;
8179 auto const& func = *callCtx.callee;
8181 if (data.mode == AnalysisIndex::Mode::Constants) {
8182 ITRACE_MOD(
8183 Trace::hhbbc, 4,
8184 "Skipping inline interp of {} because analyzing constants\n",
8185 func_fullname(func)
8187 return returnType;
8190 auto const& finfo = func_info(data, func);
8191 auto const& caller = *context_for_deps(data).func;
8193 auto const adjustedCtx = adjust_closure_context(
8194 AnalysisIndexAdaptor { data.index },
8195 callCtx
8197 returnType.t = return_with_context(std::move(returnType.t), adjustedCtx);
8199 auto const checkParam = [&] (size_t i) {
8200 auto const& constraint = func.params[i].typeConstraint;
8201 if (constraint.hasConstraint() &&
8202 !constraint.isTypeVar() &&
8203 !constraint.isTypeConstant()) {
8204 return callCtx.args[i].strictlyMoreRefined(
8205 lookup_constraint(
8206 AnalysisIndexAdaptor { data.index },
8207 Context { func.unit, &func, func.cls },
8208 constraint
8209 ).upper
8212 return callCtx.args[i].strictSubtypeOf(TInitCell);
8215 // TODO(#3788877): more heuristics here would be useful.
8216 auto const tryContextSensitive = [&] {
8217 if (func.noContextSensitiveAnalysis ||
8218 func.params.empty() ||
8219 data.contextualInterpNestingLevel + 1 >= maxNestingLevel ||
8220 returnType.t.is(BBottom)) {
8221 return false;
8224 if (data.deps->add(func, AnalysisDeps::RetParam)) {
8225 if (finfo.retParam != NoLocalId &&
8226 callCtx.args.size() > finfo.retParam &&
8227 checkParam(finfo.retParam)) {
8228 return true;
8232 if (!options.ContextSensitiveInterp) return false;
8234 auto const numParams = func.params.size();
8235 if (callCtx.args.size() < numParams) return true;
8236 for (size_t i = 0; i < numParams; ++i) {
8237 if (checkParam(i)) return true;
8239 return false;
8240 }();
8242 if (!tryContextSensitive) {
8243 ITRACE_MOD(Trace::hhbbc, 4, "not trying context sensitive\n");
8244 return returnType;
8247 if (!data.deps->add(func, AnalysisDeps::Bytecode)) {
8248 ITRACE_MOD(
8249 Trace::hhbbc, 4,
8250 "Skipping inline interp of {} because inspecting "
8251 "bytecode is not allowed\n",
8252 func_fullname(func)
8254 return R{ TInitCell, false };
8256 if (!func.rawBlocks) {
8257 ITRACE_MOD(
8258 Trace::hhbbc, 4,
8259 "Skipping inline interp of {} because bytecode not present\n",
8260 func_fullname(func)
8262 return R{ TInitCell, false };
8265 auto const contextType = [&] {
8266 ++data.contextualInterpNestingLevel;
8267 SCOPE_EXIT { --data.contextualInterpNestingLevel; };
8269 auto const wf = php::WideFunc::cns(&func);
8270 auto fa = analyze_func_inline(
8271 AnalysisIndexAdaptor { data.index },
8272 AnalysisContext {
8273 func.unit,
8275 func.cls,
8276 &context_for_deps(data)
8278 adjustedCtx,
8279 callCtx.args
8281 return R{
8282 return_with_context(std::move(fa.inferredReturn), std::move(adjustedCtx)),
8283 fa.effectFree
8285 }();
8287 ITRACE_MOD(
8288 Trace::hhbbc, 4,
8289 "Context sensitive type: {}, context insensitive type: {}\n",
8290 show(contextType.t), show(returnType.t)
8293 auto const error_context = [&] {
8294 using namespace folly::gen;
8295 return folly::sformat(
8296 "{} calling {} (context: {}, args: {})",
8297 func_fullname(caller),
8298 func_fullname(func),
8299 show(callCtx.context),
8300 from(callCtx.args)
8301 | map([] (const Type& t) { return show(t); })
8302 | unsplit<std::string>(",")
8306 always_assert_flog(
8307 contextType.t.subtypeOf(returnType.t),
8308 "Context sensitive return type for {} is {} ",
8309 "which is not at least as refined as context insensitive "
8310 "return type {}\n",
8311 error_context(),
8312 show(contextType.t),
8313 show(returnType.t)
8315 always_assert_flog(
8316 contextType.effectFree || !returnType.effectFree,
8317 "Context sensitive effect-free for {} is {} ",
8318 "which is not at least as refined as context insensitive "
8319 "effect-free {}\n",
8320 error_context(),
8321 contextType.effectFree,
8322 returnType.effectFree
8325 return contextType;
8328 //////////////////////////////////////////////////////////////////////
8330 template<typename F> auto
8331 visit_parent_cinfo(const ClassInfo* cinfo, F fun) -> decltype(fun(cinfo)) {
8332 for (auto ci = cinfo; ci != nullptr; ci = ci->parent) {
8333 if (auto const ret = fun(ci)) return ret;
8334 for (auto const trait : ci->usedTraits) {
8335 if (auto const ret = visit_parent_cinfo(trait, fun)) {
8336 return ret;
8340 return {};
8343 //////////////////////////////////////////////////////////////////////
8345 // The type of a public static property, considering only it's initial
8346 // value.
8347 Type initial_type_for_public_sprop(const Index& index,
8348 const php::Class& cls,
8349 const php::Prop& prop) {
8351 * If the initializer type is TUninit, it means an 86sinit provides
8352 * the actual initialization type or it is AttrLateInit. So we don't
8353 * want to include the Uninit (which isn't really a user-visible
8354 * type for the property) or by the time we union things in we'll
8355 * have inferred nothing much.
8357 auto const ty = from_cell(prop.val);
8358 if (ty.subtypeOf(BUninit)) return TBottom;
8359 if (prop.attrs & AttrSystemInitialValue) return ty;
8360 return adjust_type_for_prop(
8361 IndexAdaptor { index },
8362 cls,
8363 &prop.typeConstraint,
8368 Type lookup_public_prop_impl(
8369 const IndexData& data,
8370 const ClassInfo* cinfo,
8371 SString propName
8373 // Find a property declared in this class (or a parent) with the same name.
8374 const php::Class* knownCls = nullptr;
8375 auto const prop = visit_parent_cinfo(
8376 cinfo,
8377 [&] (const ClassInfo* ci) -> const php::Prop* {
8378 for (auto const& prop : ci->cls->properties) {
8379 if (prop.name == propName) {
8380 knownCls = ci->cls;
8381 return &prop;
8384 return nullptr;
8388 if (!prop) return TCell;
8389 // Make sure its non-static and public. Otherwise its another function's
8390 // problem.
8391 if (prop->attrs & (AttrStatic | AttrPrivate)) return TCell;
8393 // Get a type corresponding to its declared type-hint (if any).
8394 auto ty = adjust_type_for_prop(
8395 IndexAdaptor { *data.m_index }, *knownCls, &prop->typeConstraint, TCell
8397 // We might have to include the initial value which might be outside of the
8398 // type-hint.
8399 auto initialTy = loosen_all(from_cell(prop->val));
8400 if (!initialTy.subtypeOf(TUninit) && (prop->attrs & AttrSystemInitialValue)) {
8401 ty |= initialTy;
8403 return ty;
8406 // Test if the given property (declared in `cls') is accessible in the
8407 // given context (null if we're not in a class).
8408 bool static_is_accessible(const ClassInfo* clsCtx,
8409 const ClassInfo* cls,
8410 const php::Prop& prop) {
8411 assertx(prop.attrs & AttrStatic);
8412 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
8413 case AttrPublic:
8414 // Public is accessible everywhere
8415 return true;
8416 case AttrProtected:
8417 // Protected is accessible from both derived classes and parent
8418 // classes
8419 return clsCtx &&
8420 (clsCtx->classGraph.exactSubtypeOf(cls->classGraph, true, true) ||
8421 cls->classGraph.exactSubtypeOf(clsCtx->classGraph, true, true));
8422 case AttrPrivate:
8423 // Private is only accessible from within the declared class
8424 return clsCtx == cls;
8426 always_assert(false);
8429 // Return true if the given class can possibly throw when its
8430 // initialized. Initialization can happen when an object of that class
8431 // is instantiated, or (more importantly) when static properties are
8432 // accessed.
8433 bool class_init_might_raise(IndexData& data,
8434 Context ctx,
8435 const ClassInfo* cinfo) {
8436 // Check this class and all of its parents for possible inequivalent
8437 // redeclarations or bad initial values.
8438 do {
8439 // Be conservative for now if we have unflattened traits.
8440 if (!cinfo->traitProps.empty()) return true;
8441 if (cinfo->hasBadRedeclareProp) return true;
8442 if (cinfo->hasBadInitialPropValues) {
8443 add_dependency(data, cinfo->cls, ctx, Dep::PropBadInitialValues);
8444 return true;
8446 cinfo = cinfo->parent;
8447 } while (cinfo);
8448 return false;
8452 * Calculate the effects of applying the given type against the
8453 * type-constraints for the given prop. This includes the subtype
8454 * which will succeed (if any), and if the type-constraint check might
8455 * throw.
8457 PropMergeResult prop_tc_effects(const Index& index,
8458 const ClassInfo* ci,
8459 const php::Prop& prop,
8460 const Type& val,
8461 bool checkUB) {
8462 assertx(prop.typeConstraint.validForProp());
8464 using R = PropMergeResult;
8466 // If we're not actually checking property type-hints, everything
8467 // goes
8468 if (Cfg::Eval::CheckPropTypeHints <= 0) return R{ val, TriBool::No };
8470 auto const ctx = Context { nullptr, nullptr, ci->cls };
8472 auto const check = [&] (const TypeConstraint& tc, const Type& t) {
8473 // If the type as is satisfies the constraint, we won't throw and
8474 // the type is unchanged.
8475 if (t.moreRefined(
8476 lookup_constraint(IndexAdaptor { index }, ctx, tc, t).lower)
8478 return R{ t, TriBool:: No };
8480 // Otherwise adjust the type. If we get a Bottom we'll definitely
8481 // throw. We already know the type doesn't completely satisfy the
8482 // constraint, so we'll at least maybe throw.
8483 auto adjusted =
8484 adjust_type_for_prop(IndexAdaptor { index }, *ctx.cls, &tc, t);
8485 auto const throws = yesOrMaybe(adjusted.subtypeOf(BBottom));
8486 return R{ std::move(adjusted), throws };
8489 // First check the main type-constraint.
8490 auto result = check(prop.typeConstraint, val);
8491 // If we're not checking generics upper-bounds, or if we already
8492 // know we'll fail, we're done.
8493 if (!checkUB || result.throws == TriBool::Yes) {
8494 return result;
8497 // Otherwise check every generic upper-bound. We'll feed the
8498 // narrowed type into each successive round. If we reach the point
8499 // where we'll know we'll definitely fail, just stop.
8500 for (auto const& ub : prop.ubs.m_constraints) {
8501 auto r = check(ub, result.adjusted);
8502 result.throws &= r.throws;
8503 result.adjusted = std::move(r.adjusted);
8504 if (result.throws == TriBool::Yes) break;
8507 return result;
8511 * Lookup data for the static property named `propName', starting from
8512 * the specified class `start'. If `propName' is nullptr, then any
8513 * accessible static property in the class hierarchy is considered. If
8514 * `startOnly' is specified, if the property isn't found in `start',
8515 * it is treated as a lookup failure. Otherwise the lookup continues
8516 * in all parent classes of `start', until a property is found, or
8517 * until all parent classes have been exhausted (`startOnly' is used
8518 * to avoid redundant class hierarchy walks). `clsCtx' is the current
8519 * context, converted to a ClassInfo* (or nullptr if not in a class).
8521 PropLookupResult lookup_static_impl(IndexData& data,
8522 Context ctx,
8523 const ClassInfo* clsCtx,
8524 const PropertiesInfo& privateProps,
8525 const ClassInfo* start,
8526 SString propName,
8527 bool startOnly) {
8528 ITRACE(
8529 6, "lookup_static_impl: {} {} {}\n",
8530 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
8531 start->cls->name,
8532 propName ? propName->toCppString() : std::string{"*"}
8534 Trace::Indent _;
8536 auto const type = [&] (const php::Prop& prop,
8537 const ClassInfo* ci) {
8538 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
8539 case AttrPublic:
8540 case AttrProtected: {
8541 if (ctx.unit) add_dependency(data, &prop, ctx, Dep::PublicSProp);
8542 if (!data.seenPublicSPropMutations) {
8543 // If we haven't recorded any mutations yet, we need to be
8544 // conservative and consider only the type-hint and initial
8545 // value.
8546 return union_of(
8547 adjust_type_for_prop(
8548 IndexAdaptor { *data.m_index },
8549 *ci->cls,
8550 &prop.typeConstraint,
8551 TInitCell
8553 initial_type_for_public_sprop(*data.m_index, *ci->cls, prop)
8556 auto const it = ci->publicStaticProps.find(propName);
8557 if (it == end(ci->publicStaticProps)) {
8558 // We've recorded mutations, but have information for this
8559 // property. That means there's no mutations so only
8560 // consider the initial value.
8561 return initial_type_for_public_sprop(*data.m_index, *ci->cls, prop);
8563 return it->second.inferredType;
8565 case AttrPrivate: {
8566 assertx(clsCtx == ci);
8567 auto const elem = privateProps.readPrivateStatic(prop.name);
8568 if (!elem) return TInitCell;
8569 return remove_uninit(elem->ty);
8572 always_assert(false);
8575 auto const initMightRaise = class_init_might_raise(data, ctx, start);
8577 auto const fromProp = [&] (const php::Prop& prop,
8578 const ClassInfo* ci) {
8579 // The property was definitely found. Compute its attributes
8580 // from the prop metadata.
8581 return PropLookupResult{
8582 type(prop, ci),
8583 propName,
8584 TriBool::Yes,
8585 yesOrNo(prop.attrs & AttrIsConst),
8586 yesOrNo(prop.attrs & AttrIsReadonly),
8587 yesOrNo(prop.attrs & AttrLateInit),
8588 yesOrNo(prop.attrs & AttrInternal),
8589 initMightRaise
8593 auto const notFound = [&] {
8594 // The property definitely wasn't found.
8595 return PropLookupResult{
8596 TBottom,
8597 propName,
8598 TriBool::No,
8599 TriBool::No,
8600 TriBool::No,
8601 TriBool::No,
8602 TriBool::No,
8603 false
8607 if (!propName) {
8608 // We don't statically know the prop name. Walk up the hierarchy
8609 // and union the data for any accessible static property.
8610 ITRACE(4, "no prop name, considering all accessible\n");
8611 auto result = notFound();
8612 visit_parent_cinfo(
8613 start,
8614 [&] (const ClassInfo* ci) {
8615 for (auto const& prop : ci->cls->properties) {
8616 if (!(prop.attrs & AttrStatic) ||
8617 !static_is_accessible(clsCtx, ci, prop)) {
8618 ITRACE(
8619 6, "skipping inaccessible {}::${}\n",
8620 ci->cls->name, prop.name
8622 continue;
8624 auto const r = fromProp(prop, ci);
8625 ITRACE(6, "including {}:${} {}\n", ci->cls->name, prop.name, show(r));
8626 result |= r;
8628 // If we're only interested in the starting class, don't walk
8629 // up to the parents.
8630 return startOnly;
8633 return result;
8636 // We statically know the prop name. Walk up the hierarchy and stop
8637 // at the first matching property and use that data.
8638 assertx(!startOnly);
8639 auto const result = visit_parent_cinfo(
8640 start,
8641 [&] (const ClassInfo* ci) -> Optional<PropLookupResult> {
8642 for (auto const& prop : ci->cls->properties) {
8643 if (prop.name != propName) continue;
8644 // We have a matching prop. If its not static or not
8645 // accessible, the access will not succeed.
8646 if (!(prop.attrs & AttrStatic) ||
8647 !static_is_accessible(clsCtx, ci, prop)) {
8648 ITRACE(
8649 6, "{}::${} found but inaccessible, stopping\n",
8650 ci->cls->name, propName
8652 return notFound();
8654 // Otherwise its a match
8655 auto const r = fromProp(prop, ci);
8656 ITRACE(6, "found {}:${} {}\n", ci->cls->name, propName, show(r));
8657 return r;
8659 return std::nullopt;
8662 if (!result) {
8663 // We walked up to all of the base classes and didn't find a
8664 // property with a matching name. The access will fail.
8665 ITRACE(6, "nothing found\n");
8666 return notFound();
8668 return *result;
8672 * Lookup the static property named `propName', starting from the
8673 * specified class `start'. If an accessible property is found, then
8674 * merge the given type `val' into the already known type for that
8675 * property. If `propName' is nullptr, then any accessible static
8676 * property in the class hierarchy is considered. If `startOnly' is
8677 * specified, if the property isn't found in `start', then the nothing
8678 * is done. Otherwise the lookup continues in all parent classes of
8679 * `start', until a property is found, or until all parent classes
8680 * have been exhausted (`startOnly' is to avoid redundant class
8681 * hierarchy walks). `clsCtx' is the current context, converted to a
8682 * ClassInfo* (or nullptr if not in a class). If `ignoreConst' is
8683 * false, then AttrConst properties will not have their type
8684 * modified. `mergePublic' is a lambda with the logic to merge a type
8685 * for a public property (this is needed to avoid cyclic
8686 * dependencies).
8688 template <typename F>
8689 PropMergeResult merge_static_type_impl(IndexData& data,
8690 Context ctx,
8691 F mergePublic,
8692 PropertiesInfo& privateProps,
8693 const ClassInfo* clsCtx,
8694 const ClassInfo* start,
8695 SString propName,
8696 const Type& val,
8697 bool checkUB,
8698 bool ignoreConst,
8699 bool mustBeReadOnly,
8700 bool startOnly) {
8701 ITRACE(
8702 6, "merge_static_type_impl: {} {} {} {}\n",
8703 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
8704 start->cls->name,
8705 propName ? propName->toCppString() : std::string{"*"},
8706 show(val)
8708 Trace::Indent _;
8710 assertx(!val.subtypeOf(BBottom));
8712 // Perform the actual merge for a given property, returning the
8713 // effects of that merge.
8714 auto const merge = [&] (const php::Prop& prop, const ClassInfo* ci) {
8715 // First calculate the effects of the type-constraint.
8716 auto const effects = prop_tc_effects(*data.m_index, ci, prop, val, checkUB);
8717 // No point in merging if the type-constraint will always fail.
8718 if (effects.throws == TriBool::Yes) {
8719 ITRACE(
8720 6, "tc would throw on {}::${} with {}, skipping\n",
8721 ci->cls->name, prop.name, show(val)
8723 return effects;
8725 assertx(!effects.adjusted.subtypeOf(BBottom));
8727 ITRACE(
8728 6, "merging {} into {}::${}\n",
8729 show(effects), ci->cls->name, prop.name
8732 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
8733 case AttrPublic:
8734 case AttrProtected:
8735 mergePublic(ci, prop, unctx(effects.adjusted));
8736 // If the property is internal, accessing it may throw
8737 // TODO(T131951529): we can do better by checking modules here
8738 if ((prop.attrs & AttrInternal) && effects.throws == TriBool::No) {
8739 ITRACE(6, "{}::${} is internal, "
8740 "being pessimistic with regards to throwing\n",
8741 ci->cls->name, prop.name);
8742 return PropMergeResult{
8743 effects.adjusted,
8744 TriBool::Maybe
8747 return effects;
8748 case AttrPrivate: {
8749 assertx(clsCtx == ci);
8750 privateProps.mergeInPrivateStaticPreAdjusted(
8751 prop.name,
8752 unctx(effects.adjusted)
8754 return effects;
8757 always_assert(false);
8760 // If we don't find a property, then the mutation will definitely
8761 // fail.
8762 auto const notFound = [&] {
8763 return PropMergeResult{
8764 TBottom,
8765 TriBool::Yes
8769 if (!propName) {
8770 // We don't statically know the prop name. Walk up the hierarchy
8771 // and merge the type for any accessible static property.
8772 ITRACE(6, "no prop name, considering all accessible\n");
8773 auto result = notFound();
8774 visit_parent_cinfo(
8775 start,
8776 [&] (const ClassInfo* ci) {
8777 for (auto const& prop : ci->cls->properties) {
8778 if (!(prop.attrs & AttrStatic) ||
8779 !static_is_accessible(clsCtx, ci, prop)) {
8780 ITRACE(
8781 6, "skipping inaccessible {}::${}\n",
8782 ci->cls->name, prop.name
8784 continue;
8786 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
8787 ITRACE(6, "skipping const {}::${}\n", ci->cls->name, prop.name);
8788 continue;
8790 if (mustBeReadOnly && !(prop.attrs & AttrIsReadonly)) {
8791 ITRACE(6, "skipping mutable property that must be readonly {}::${}\n",
8792 ci->cls->name, prop.name);
8793 continue;
8795 result |= merge(prop, ci);
8797 return startOnly;
8800 return result;
8803 // We statically know the prop name. Walk up the hierarchy and stop
8804 // at the first matching property and merge the type there.
8805 assertx(!startOnly);
8806 auto result = visit_parent_cinfo(
8807 start,
8808 [&] (const ClassInfo* ci) -> Optional<PropMergeResult> {
8809 for (auto const& prop : ci->cls->properties) {
8810 if (prop.name != propName) continue;
8811 // We found a property with the right name, but its
8812 // inaccessible from this context (or not even static). This
8813 // mutation will fail, so we don't need to modify the type.
8814 if (!(prop.attrs & AttrStatic) ||
8815 !static_is_accessible(clsCtx, ci, prop)) {
8816 ITRACE(
8817 6, "{}::${} found but inaccessible, stopping\n",
8818 ci->cls->name, propName
8820 return notFound();
8822 // Mutations to AttrConst properties will fail as well, unless
8823 // it we want to override that behavior.
8824 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
8825 ITRACE(
8826 6, "{}:${} found but const, stopping\n",
8827 ci->cls->name, propName
8829 return notFound();
8831 if (mustBeReadOnly && !(prop.attrs & AttrIsReadonly)) {
8832 ITRACE(
8833 6, "{}:${} found but is mutable and must be readonly, stopping\n",
8834 ci->cls->name, propName
8836 return notFound();
8838 return merge(prop, ci);
8840 return std::nullopt;
8843 if (!result) {
8844 ITRACE(6, "nothing found\n");
8845 return notFound();
8848 // If the mutation won't throw, we still need to check if the class
8849 // initialization can throw. If we might already throw (or
8850 // definitely will throw), this doesn't matter.
8851 if (result->throws == TriBool::No) {
8852 return PropMergeResult{
8853 std::move(result->adjusted),
8854 maybeOrNo(class_init_might_raise(data, ctx, start))
8857 return *result;
8860 //////////////////////////////////////////////////////////////////////
8863 * Split a group of buckets so that no bucket is larger (including its
8864 * dependencies) than the given max size. The given callable is used
8865 * to obtain the dependencies of bucket item.
8867 * Note: if a single item has dependencies larger than maxSize, you'll
8868 * get a bucket with just that and its dependencies (which will be
8869 * larger than maxSize). This is the only situation where a returned
8870 * bucket will be larger than maxSize.
8872 template <typename GetDeps>
8873 std::vector<std::vector<SString>>
8874 split_buckets(const std::vector<std::vector<SString>>& items,
8875 size_t maxSize,
8876 const GetDeps& getDeps) {
8877 // Split all of the buckets in parallel
8878 auto rebuckets = parallel::map(
8879 items,
8880 [&] (const std::vector<SString>& bucket) {
8881 // If there's only one thing in a bucket, there's no point in
8882 // splitting it.
8883 if (bucket.size() <= 1) return singleton_vec(bucket);
8885 // The splitting algorithm is simple. Iterate over each element
8886 // in the bucket. As long as all the dependencies are less than
8887 // the maximum size, we put it into a new bucket. If we exceed
8888 // the max size, create a new bucket.
8889 std::vector<std::vector<SString>> out;
8890 out.emplace_back();
8891 out.back().emplace_back(bucket[0]);
8893 auto allDeps = getDeps(bucket[0]);
8894 for (size_t i = 1, size = bucket.size(); i < size; ++i) {
8895 auto const& d = getDeps(bucket[i]);
8896 allDeps.insert(begin(d), end(d));
8897 auto const newSize = allDeps.size() + out.back().size() + 1;
8898 if (newSize > maxSize) {
8899 allDeps = d;
8900 out.emplace_back();
8902 out.back().emplace_back(bucket[i]);
8904 return out;
8908 // Flatten all of the new buckets into a single list of buckets.
8909 std::vector<std::vector<SString>> flattened;
8910 flattened.reserve(items.size());
8911 for (auto& r : rebuckets) {
8912 for (auto& b : r) flattened.emplace_back(std::move(b));
8914 return flattened;
8917 //////////////////////////////////////////////////////////////////////
8920 * For efficiency reasons, we often want to process classes in as few
8921 * passes as possible. However, this is tricky because the algorithms
8922 * are usually naturally iterative. You start at the root classes
8923 * (which may be the top classes in the hierarchy, or leaf classes),
8924 * and flow data down to each of their parents or children. This
8925 * requires N passes, where N is the maximum depth of the class
8926 * hierarchy. N can get large.
8928 * Instead when we process a class, we ensure that all of it's
8929 * dependencies (all the way up to the roots) are also present in the
8930 * job. Since we're doing this in one pass, none of the dependencies
8931 * will have any calculated information, and the job will have to do
8932 * this first.
8934 * It is not, in general, possible to ensure that each dependency is
8935 * present in exactly one job (because the dependency may be shared by
8936 * lots of classes which are not bucketed together). So, any given
8937 * dependency may end up on multiple jobs and have the same
8938 * information calculated for it. This is fine, as it just results in
8939 * some wasted work.
8941 * We perform flattening using the following approach:
8943 * - First we Bucketize the root classes (using the standard
8944 * consistent hashing algorithm) into N buckets.
8946 * - We split any buckets which are larger than the specified maximum
8947 * size. This prevents buckets from becoming pathologically large if
8948 * there's many dependencies.
8950 * - For each bucket, find all of the (transitive) dependencies of the
8951 * leaves and add them to that bucket (as dependencies). As stated
8952 * above, the same class may end up in multiple buckets as
8953 * dependencies.
8955 * - So far for each bucket (each bucket will map to one job), we have
8956 * a set of input classes (the roots), and all of the dependencies
8957 * for each roots.
8959 * - We want results for every class, not just the roots, so the
8960 * dependencies need to become inputs of the first kind in at least
8961 * one bucket. So, for each dependency, in one of the buckets
8962 * they're already present in, we "promote" it to a full input (and
8963 * will receive output for it). This is done by hashing the bucket
8964 * index and class name and picking the bucket that results in the
8965 * lowest hash. In some situations we don't want a dependency to
8966 * ever be promoted, so those will be skipped.
8969 // Single output bucket for assign_hierarchical_work. Each bucket
8970 // contains classes which will be processed and returned as output,
8971 // and a set of dependency classes which will just be used as inputs.
8972 struct HierarchicalWorkBucket {
8973 std::vector<SString> classes;
8974 std::vector<SString> deps;
8975 std::vector<SString> uninstantiable;
8979 * Assign work for a set of root classes (using the above
8980 * algorithm). The function is named because it's meant for situations
8981 * where we're processing classes in a "hierarchical" manner (either
8982 * from parent class to children, or from leaf class to parents).
8984 * The dependencies for each class is provided by the getDeps
8985 * callable. For the purposes of promoting a class to a full output
8986 * (see above algorithm description), each class must be assigned an
8987 * index. The (optional) index for a class is provided by the getIdx
8988 * callable. If getIdx returns std::nullopt, then that class won't be
8989 * considered for promotion. The given "numClasses" parameter is an
8990 * upper bound on the possible returned indices.
8992 template <typename GetDeps, typename GetIdx>
8993 std::vector<HierarchicalWorkBucket>
8994 build_hierarchical_work(std::vector<std::vector<SString>>& buckets,
8995 size_t numClasses,
8996 const GetDeps& getDeps,
8997 const GetIdx& getIdx) {
8998 struct DepHashState {
8999 std::mutex lock;
9000 size_t lowestHash{std::numeric_limits<size_t>::max()};
9001 size_t lowestBucket{std::numeric_limits<size_t>::max()};
9003 std::vector<DepHashState> depHashState{numClasses};
9005 // For each bucket (which right now just contains the root classes),
9006 // find all the transitive dependencies those root classes need. A
9007 // dependency might end up in multiple buckets (because multiple
9008 // roots in different buckets depend on it). We only want to
9009 // actually perform the flattening for those dependencies in one of
9010 // the buckets. So, we need a tie-breaker. We hash the name of the
9011 // dependency along with the bucket number. The bucket that the
9012 // dependency is present in with the lowest hash is what "wins".
9013 auto const bucketDeps = parallel::gen(
9014 buckets.size(),
9015 [&] (size_t bucketIdx) {
9016 assertx(bucketIdx < buckets.size());
9017 auto& bucket = buckets[bucketIdx];
9018 const TSStringSet roots{begin(bucket), end(bucket)};
9020 // Gather up all dependencies for this bucket
9021 TSStringSet deps;
9022 for (auto const cls : bucket) {
9023 auto const d = getDeps(cls).first;
9024 deps.insert(begin(*d), end(*d));
9027 // Make sure dependencies and roots are disjoint.
9028 for (auto const c : bucket) deps.erase(c);
9030 // For each dependency, store the bucket with the lowest hash.
9031 for (auto const d : deps) {
9032 auto const idx = getIdx(roots, bucketIdx, d);
9033 if (!idx.has_value()) continue;
9034 assertx(*idx < depHashState.size());
9035 auto& s = depHashState[*idx];
9036 auto const hash = hash_int64_pair(
9037 d->hashStatic(),
9038 bucketIdx
9040 std::lock_guard<std::mutex> _{s.lock};
9041 if (hash < s.lowestHash) {
9042 s.lowestHash = hash;
9043 s.lowestBucket = bucketIdx;
9044 } else if (hash == s.lowestHash) {
9045 s.lowestBucket = std::min(s.lowestBucket, bucketIdx);
9049 return deps;
9053 // Now for each bucket, "promote" dependencies into a full input
9054 // class. The dependency is promoted in the bucket with the lowest
9055 // hash, which we've already calculated.
9056 assertx(buckets.size() == bucketDeps.size());
9057 return parallel::gen(
9058 buckets.size(),
9059 [&] (size_t bucketIdx) {
9060 auto& bucket = buckets[bucketIdx];
9061 auto const& deps = bucketDeps[bucketIdx];
9062 const TSStringSet roots{begin(bucket), end(bucket)};
9064 std::vector<SString> depOut;
9065 depOut.reserve(deps.size());
9067 for (auto const d : deps) {
9068 // Calculate the hash for the dependency for this bucket. If
9069 // the hash equals the already calculated lowest hash, promote
9070 // this dependency.
9071 auto const idx = getIdx(roots, bucketIdx, d);
9072 if (!idx.has_value()) {
9073 depOut.emplace_back(d);
9074 continue;
9076 assertx(*idx < depHashState.size());
9077 auto const& s = depHashState[*idx];
9078 auto const hash = hash_int64_pair(
9079 d->hashStatic(),
9080 bucketIdx
9082 if (hash == s.lowestHash && bucketIdx == s.lowestBucket) {
9083 bucket.emplace_back(d);
9084 } else if (getDeps(d).second) {
9085 // Otherwise keep it as a dependency, but only if it's
9086 // actually instantiable.
9087 depOut.emplace_back(d);
9091 // Split off any uninstantiable classes in the bucket.
9092 auto const bucketEnd = std::partition(
9093 begin(bucket),
9094 end(bucket),
9095 [&] (SString cls) { return getDeps(cls).second; }
9097 std::vector<SString> uninstantiable{bucketEnd, end(bucket)};
9098 bucket.erase(bucketEnd, end(bucket));
9100 // Keep deterministic ordering. Make sure there's no duplicates.
9101 std::sort(bucket.begin(), bucket.end(), string_data_lt_type{});
9102 std::sort(depOut.begin(), depOut.end(), string_data_lt_type{});
9103 std::sort(uninstantiable.begin(), uninstantiable.end(),
9104 string_data_lt_type{});
9105 assertx(std::adjacent_find(bucket.begin(), bucket.end()) == bucket.end());
9106 assertx(std::adjacent_find(depOut.begin(), depOut.end()) == depOut.end());
9107 assertx(
9108 std::adjacent_find(uninstantiable.begin(), uninstantiable.end()) ==
9109 uninstantiable.end()
9111 return HierarchicalWorkBucket{
9112 std::move(bucket),
9113 std::move(depOut),
9114 std::move(uninstantiable)
9120 template <typename GetDeps, typename GetIdx>
9121 std::vector<HierarchicalWorkBucket>
9122 assign_hierarchical_work(std::vector<SString> roots,
9123 size_t numClasses,
9124 size_t bucketSize,
9125 size_t maxSize,
9126 const GetDeps& getDeps,
9127 const GetIdx& getIdx) {
9128 // First turn roots into buckets, and split if any exceed the
9129 // maximum size.
9130 auto buckets = split_buckets(
9131 consistently_bucketize(roots, bucketSize),
9132 maxSize,
9133 [&] (SString cls) -> const TSStringSet& {
9134 auto const [d, _] = getDeps(cls);
9135 return *d;
9138 return build_hierarchical_work(buckets, numClasses, getDeps, getIdx);
9141 //////////////////////////////////////////////////////////////////////
9142 // Class flattening:
9144 const StaticString
9145 s___Sealed("__Sealed"),
9146 s___EnableMethodTraitDiamond("__EnableMethodTraitDiamond"),
9147 s___ModuleLevelTrait("__ModuleLevelTrait");
9150 * Extern-worker job to build ClassInfo2s (which involves flattening
9151 * data across the hierarchy) and flattening traits.
9153 struct FlattenJob {
9154 static std::string name() { return "hhbbc-flatten"; }
9155 static void init(const Config& config) {
9156 process_init(config.o, config.gd, false);
9157 ClassGraph::init();
9159 static void fini() { ClassGraph::destroy(); }
9162 * Metadata representing results of flattening. This is information
9163 * that the local coordinator (as opposed to later remote jobs) will
9164 * need to consume.
9166 struct OutputMeta {
9167 // Classes which have been determined to be uninstantiable
9168 // (therefore have no result output data).
9169 TSStringSet uninstantiable;
9170 // New closures produced from trait flattening. Such new closures
9171 // will require "fixups" in the php::Program data.
9172 struct NewClosure {
9173 SString unit;
9174 SString name;
9175 SString context;
9176 template <typename SerDe> void serde(SerDe& sd) {
9177 sd(unit)(name)(context);
9180 std::vector<NewClosure> newClosures;
9181 // Report parents of each class. A class is a parent of another if
9182 // it would appear on a subclass list. The parents of a closure
9183 // are not reported because that's implicit.
9184 struct Parents {
9185 std::vector<SString> names;
9186 template <typename SerDe> void serde(SerDe& sd) { sd(names); }
9188 std::vector<Parents> parents;
9189 // Classes which are interfaces.
9190 TSStringSet interfaces;
9191 // Classes which have 86init functions. A class can gain a 86init
9192 // from flattening even if it didn't have it before.
9193 TSStringSet with86init;
9194 // The types used by the type-constraints of input classes and
9195 // functions.
9196 std::vector<TSStringSet> classTypeUses;
9197 std::vector<TSStringSet> funcTypeUses;
9198 std::vector<InterfaceConflicts> interfaceConflicts;
9199 template <typename SerDe> void serde(SerDe& sd) {
9200 ScopedStringDataIndexer _;
9201 sd(uninstantiable, string_data_lt_type{})
9202 (newClosures)
9203 (parents)
9204 (interfaces, string_data_lt_type{})
9205 (with86init, string_data_lt_type{})
9206 (classTypeUses, string_data_lt_type{})
9207 (funcTypeUses, string_data_lt_type{})
9208 (interfaceConflicts)
9214 * Job returns a list of (potentially modified) php::Class, a list
9215 * of new ClassInfo2, a list of (potentially modified) php::Func,
9216 * and metadata for the entire job. The order of the lists reflects
9217 * the order of the input classes and functions (skipping over
9218 * classes marked as uninstantiable in the metadata).
9220 using Output = Multi<
9221 Variadic<std::unique_ptr<php::Class>>,
9222 Variadic<std::unique_ptr<php::ClassBytecode>>,
9223 Variadic<std::unique_ptr<ClassInfo2>>,
9224 Variadic<std::unique_ptr<php::Func>>,
9225 Variadic<std::unique_ptr<FuncInfo2>>,
9226 Variadic<std::unique_ptr<MethodsWithoutCInfo>>,
9227 OutputMeta
9231 * Job takes a list of classes which are to be flattened. In
9232 * addition to this, it also takes a list of classes which are
9233 * dependencies of the classes to be flattened. (A class might be
9234 * one of the inputs *and* a dependency, in which case it should
9235 * just be on the input list). It is expected that *all*
9236 * dependencies are provided. All instantiable classes will have
9237 * their type-constraints resolved to their ultimate type, or left
9238 * as unresolved if it refers to a missing/invalid type. The
9239 * provided functions only have their type-constraints updated. The
9240 * provided type-mappings and list of missing types is used for
9241 * type-constraint resolution (if a type isn't in a type mapping and
9242 * isn't a missing type, it is assumed to be a object type).
9244 * Bytecode needs to be provided for every provided class. The
9245 * bytecode must be provided in the same order as the bytecode's
9246 * associated class.
9248 static Output run(Variadic<std::unique_ptr<php::Class>> classes,
9249 Variadic<std::unique_ptr<php::Class>> deps,
9250 Variadic<std::unique_ptr<php::ClassBytecode>> classBytecode,
9251 Variadic<std::unique_ptr<php::Func>> funcs,
9252 Variadic<std::unique_ptr<php::Class>> uninstantiable,
9253 std::vector<TypeMapping> typeMappings,
9254 std::vector<SString> missingTypes) {
9255 LocalIndex index;
9257 for (auto& tc : typeMappings) {
9258 auto const name = tc.name;
9259 always_assert(index.m_typeMappings.emplace(name, std::move(tc)).second);
9261 for (auto const m : missingTypes) {
9262 always_assert(index.m_missingTypes.emplace(m).second);
9264 typeMappings.clear();
9265 missingTypes.clear();
9267 // Bytecode should have been provided for every class provided.
9268 always_assert(
9269 classBytecode.vals.size() == (classes.vals.size() + deps.vals.size())
9271 // Store the provided bytecode in the matching php::Class.
9272 for (size_t i = 0,
9273 size = classBytecode.vals.size(),
9274 classesSize = classes.vals.size();
9275 i < size; ++i) {
9276 auto& cls = i < classesSize
9277 ? classes.vals[i]
9278 : deps.vals[i - classesSize];
9279 auto& bytecode = classBytecode.vals[i];
9281 // We shouldn't have closures here. They're children of the
9282 // declaring class.
9283 assertx(!cls->closureContextCls);
9284 auto const numMethods = cls->methods.size();
9285 auto const numClosures = cls->closures.size();
9286 always_assert(bytecode->methodBCs.size() == numMethods + numClosures);
9288 for (size_t j = 0, bcSize = bytecode->methodBCs.size(); j < bcSize; ++j) {
9289 if (j < numMethods) {
9290 cls->methods[j]->rawBlocks = std::move(bytecode->methodBCs[j].bc);
9291 } else {
9292 assertx(cls->closures[j-numMethods]->methods.size() == 1);
9293 cls->closures[j-numMethods]->methods[0]->rawBlocks =
9294 std::move(bytecode->methodBCs[j].bc);
9298 classBytecode.vals.clear();
9300 // Some classes might be dependencies of another. Moreover, some
9301 // classes might share dependencies. Topologically sort all of the
9302 // classes and process them in that order. Information will flow
9303 // from parent classes to their children.
9304 auto const worklist = prepare(
9305 index,
9306 [&] {
9307 TSStringToOneT<php::Class*> out;
9308 out.reserve(classes.vals.size() + deps.vals.size());
9309 for (auto const& c : classes.vals) {
9310 always_assert(out.emplace(c->name, c.get()).second);
9311 for (auto const& clo : c->closures) {
9312 always_assert(out.emplace(clo->name, clo.get()).second);
9315 for (auto const& c : deps.vals) {
9316 always_assert(out.emplace(c->name, c.get()).second);
9317 for (auto const& clo : c->closures) {
9318 always_assert(out.emplace(clo->name, clo.get()).second);
9321 return out;
9325 for (auto const& cls : uninstantiable.vals) {
9326 always_assert(index.m_uninstantiable.emplace(cls->name).second);
9327 for (auto const& clo : cls->closures) {
9328 always_assert(index.m_uninstantiable.emplace(clo->name).second);
9332 std::vector<const php::Class*> newClosures;
9334 for (auto const cls : worklist) {
9335 Trace::Bump bumper{
9336 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(cls->unit)
9339 ITRACE(2, "flatten class: {}\n", cls->name);
9340 Trace::Indent indent;
9342 index.m_ctx = cls;
9343 SCOPE_EXIT { index.m_ctx = nullptr; };
9345 auto state = std::make_unique<State>();
9346 // Attempt to make the ClassInfo2 for this class. If we can't,
9347 // it means the class is not instantiable.
9348 auto newInfo = make_info(index, *cls, *state);
9349 if (!newInfo) {
9350 ITRACE(4, "{} is not instantiable\n", cls->name);
9351 always_assert(index.m_uninstantiable.emplace(cls->name).second);
9352 continue;
9354 auto const cinfo = newInfo.get();
9356 ITRACE(5, "adding state for class '{}' to local index\n", cls->name);
9357 assertx(cinfo->name->tsame(cls->name));
9359 // We might look up this class when flattening itself, so add it
9360 // to the local index before we start.
9361 always_assert(index.m_classes.emplace(cls->name, cls).second);
9362 always_assert(
9363 index.m_classInfos.emplace(cls->name, std::move(newInfo)).second
9366 auto const [stateIt, stateSuccess] =
9367 index.m_states.emplace(cls->name, std::move(state));
9368 always_assert(stateSuccess);
9370 auto closureIdx = cls->closures.size();
9371 auto closures = flatten_traits(index, *cls, *cinfo, *stateIt->second);
9373 // Trait flattening may produce new closures, so those need to
9374 // be added to the local index as well.
9375 for (auto& i : closures) {
9376 assertx(closureIdx < cls->closures.size());
9377 auto& c = cls->closures[closureIdx++];
9378 ITRACE(5, "adding state for closure '{}' to local index\n", c->name);
9379 assertx(!is_closure(*cls));
9380 assertx(c->name->tsame(i->name));
9381 assertx(is_closure(*c));
9382 assertx(c->closureContextCls);
9383 assertx(c->closures.empty());
9384 assertx(i->closures.empty());
9385 always_assert(index.m_classes.emplace(c->name, c.get()).second);
9386 always_assert(index.m_classInfos.emplace(c->name, std::move(i)).second);
9387 newClosures.emplace_back(c.get());
9390 std::sort(
9391 begin(cls->closures), end(cls->closures),
9392 [] (const std::unique_ptr<php::Class>& c1,
9393 const std::unique_ptr<php::Class>& c2) {
9394 return string_data_lt_type{}(c1->name, c2->name);
9398 // We're done with this class. All of it's parents are now
9399 // added.
9400 cinfo->classGraph.finalizeParents();
9403 // Format the output data and put it in a deterministic order.
9404 Variadic<std::unique_ptr<php::Class>> outClasses;
9405 Variadic<std::unique_ptr<ClassInfo2>> outInfos;
9406 Variadic<std::unique_ptr<MethodsWithoutCInfo>> outMethods;
9407 OutputMeta outMeta;
9408 TSStringSet outNames;
9410 outClasses.vals.reserve(classes.vals.size());
9411 outInfos.vals.reserve(classes.vals.size());
9412 outNames.reserve(classes.vals.size());
9413 outMeta.parents.reserve(classes.vals.size());
9414 outMeta.newClosures.reserve(newClosures.size());
9416 auto const makeMethodsWithoutCInfo = [&] (const php::Class& cls) {
9417 always_assert(outMeta.uninstantiable.emplace(cls.name).second);
9418 // Even though the class is uninstantiable, we still need to
9419 // create FuncInfos for it's methods. These are stored
9420 // separately (there's no ClassInfo to store it in!)
9421 auto methods = std::make_unique<MethodsWithoutCInfo>();
9422 methods->cls = cls.name;
9423 for (auto const& func : cls.methods) {
9424 methods->finfos.emplace_back(make_func_info(index, *func));
9426 for (auto const& clo : cls.closures) {
9427 assertx(clo->methods.size() == 1);
9428 methods->closureInvokes.emplace_back(
9429 make_func_info(index, *clo->methods[0])
9432 outMethods.vals.emplace_back(std::move(methods));
9435 // Do the processing which relies on a fully accessible
9436 // LocalIndex
9438 TSStringToOneT<InterfaceConflicts> ifaceConflicts;
9439 for (auto& cls : classes.vals) {
9440 assertx(!cls->closureContextCls);
9441 auto const cinfoIt = index.m_classInfos.find(cls->name);
9442 if (cinfoIt == end(index.m_classInfos)) {
9443 ITRACE(
9444 4, "{} discovered to be not instantiable, instead "
9445 "creating MethodsWithoutCInfo for it\n",
9446 cls->name
9448 always_assert(index.uninstantiable(cls->name));
9449 makeMethodsWithoutCInfo(*cls);
9450 continue;
9452 auto& cinfo = cinfoIt->second;
9454 index.m_ctx = cls.get();
9455 SCOPE_EXIT { index.m_ctx = nullptr; };
9457 outMeta.classTypeUses.emplace_back();
9458 update_type_constraints(index, *cls, &outMeta.classTypeUses.back());
9459 optimize_properties(index, *cls, *cinfo);
9460 for (auto const& func : cls->methods) {
9461 cinfo->funcInfos.emplace_back(make_func_info(index, *func));
9464 assertx(cinfo->closures.empty());
9465 for (auto& clo : cls->closures) {
9466 auto const it = index.m_classInfos.find(clo->name);
9467 always_assert(it != end(index.m_classInfos));
9468 auto& cloinfo = it->second;
9469 update_type_constraints(index, *clo, &outMeta.classTypeUses.back());
9470 optimize_properties(index, *clo, *cloinfo);
9471 assertx(clo->methods.size() == 1);
9472 cloinfo->funcInfos.emplace_back(
9473 make_func_info(index, *clo->methods[0])
9477 outNames.emplace(cls->name);
9479 // Record interface conflicts
9481 // Only consider normal or abstract classes
9482 if (cls->attrs &
9483 (AttrInterface | AttrTrait | AttrEnum | AttrEnumClass)) {
9484 continue;
9487 auto const interfaces = cinfo->classGraph.interfaces();
9489 if (debug) {
9490 always_assert(IMPLIES(is_closure(*cls), interfaces.empty()));
9491 for (auto const& cloinfo : cinfo->closures) {
9492 always_assert(cloinfo->classGraph.interfaces().empty());
9496 for (auto const i1 : interfaces) {
9497 auto& conflicts = ifaceConflicts[i1.name()];
9498 conflicts.name = i1.name();
9499 ++conflicts.usage;
9500 for (auto const i2 : interfaces) {
9501 if (i1 == i2) continue;
9502 conflicts.conflicts.emplace(i2.name());
9507 outMeta.interfaceConflicts.reserve(ifaceConflicts.size());
9508 for (auto& [_, c] : ifaceConflicts) {
9509 outMeta.interfaceConflicts.emplace_back(std::move(c));
9511 std::sort(
9512 begin(outMeta.interfaceConflicts),
9513 end(outMeta.interfaceConflicts),
9514 [] (auto const& c1, auto const& c2) {
9515 return string_data_lt_type{}(c1.name, c2.name);
9519 // We don't process classes marked as uninstantiable beforehand,
9520 // except for creating method FuncInfos for them.
9521 for (auto const& cls : uninstantiable.vals) {
9522 ITRACE(
9523 4, "{} already known to be not instantiable, creating "
9524 "MethodsWithoutCInfo for it\n",
9525 cls->name
9527 makeMethodsWithoutCInfo(*cls);
9530 // Now move the classes out of LocalIndex and into the output. At
9531 // this point, it's not safe to access the LocalIndex unless
9532 // you're sure something hasn't been moved yet.
9533 for (auto& cls : classes.vals) {
9534 auto const name = cls->name;
9536 auto const cinfoIt = index.m_classInfos.find(name);
9537 if (cinfoIt == end(index.m_classInfos)) {
9538 assertx(outMeta.uninstantiable.count(name));
9539 continue;
9541 auto& cinfo = cinfoIt->second;
9543 // Check if this class has a 86*init function (it might have
9544 // already or might have gained one from trait flattening).
9545 auto const has86init =
9546 std::any_of(
9547 begin(cls->methods), end(cls->methods),
9548 [] (auto const& m) { return is_86init_func(*m); }
9549 ) ||
9550 std::any_of(
9551 begin(cinfo->clsConstants), end(cinfo->clsConstants),
9552 [] (auto const& cns) {
9553 return cns.second.kind == ConstModifiers::Kind::Type;
9556 if (has86init) {
9557 assertx(!is_closure(*cls));
9558 outMeta.with86init.emplace(name);
9561 index.m_ctx = cls.get();
9562 SCOPE_EXIT { index.m_ctx = nullptr; };
9564 // For building FuncFamily::StaticInfo, we need to ensure that
9565 // every method has an entry in methodFamilies. Make all of the
9566 // initial entries here (they'll be created assuming this method
9567 // is AttrNoOverride).
9568 for (auto const& [methname, mte] : cinfo->methods) {
9569 if (is_special_method_name(methname)) continue;
9570 auto entry = make_initial_func_family_entry(*cls, index.meth(mte), mte);
9571 always_assert(
9572 cinfo->methodFamilies.emplace(methname, std::move(entry)).second
9576 if (!is_closure(*cls)) {
9577 auto const& state = index.m_states.at(name);
9578 outMeta.parents.emplace_back();
9579 auto& parents = outMeta.parents.back().names;
9580 parents.reserve(state->m_parents.size());
9581 for (auto const p : state->m_parents) {
9582 parents.emplace_back(p->name);
9586 if (cls->attrs & AttrInterface) {
9587 outMeta.interfaces.emplace(name);
9590 // We always know the subclass status of closures and the
9591 // closure base class.
9592 if (is_closure_base(*cls)) {
9593 cinfo->classGraph.setClosureBase();
9594 } else if (is_closure(*cls)) {
9595 cinfo->classGraph.setComplete();
9598 assertx(cinfo->closures.empty());
9599 for (auto& clo : cls->closures) {
9600 auto const it = index.m_classInfos.find(clo->name);
9601 always_assert(it != end(index.m_classInfos));
9602 auto& cloinfo = it->second;
9604 // Closures are always leafs.
9605 cloinfo->classGraph.setComplete();
9606 assertx(!cloinfo->classGraph.mightHaveRegularSubclass());
9607 assertx(!cloinfo->classGraph.mightHaveNonRegularSubclass());
9609 for (auto const& [methname, mte] : cloinfo->methods) {
9610 if (is_special_method_name(methname)) continue;
9611 auto entry =
9612 make_initial_func_family_entry(*clo, index.meth(mte), mte);
9613 always_assert(
9614 cloinfo->methodFamilies.emplace(methname, std::move(entry)).second
9618 cinfo->closures.emplace_back(std::move(cloinfo));
9621 outClasses.vals.emplace_back(std::move(cls));
9622 outInfos.vals.emplace_back(std::move(cinfo));
9625 std::sort(
9626 begin(newClosures), end(newClosures),
9627 [] (const php::Class* c1, const php::Class* c2) {
9628 return string_data_lt_type{}(c1->name, c2->name);
9631 for (auto clo : newClosures) {
9632 assertx(clo->closureContextCls);
9633 if (!outNames.count(clo->closureContextCls)) continue;
9634 outMeta.newClosures.emplace_back(
9635 OutputMeta::NewClosure{clo->unit, clo->name, clo->closureContextCls}
9639 Variadic<std::unique_ptr<FuncInfo2>> funcInfos;
9640 funcInfos.vals.reserve(funcs.vals.size());
9641 for (auto& func : funcs.vals) {
9642 outMeta.funcTypeUses.emplace_back();
9643 update_type_constraints(index, *func, &outMeta.funcTypeUses.back());
9644 funcInfos.vals.emplace_back(make_func_info(index, *func));
9647 // Provide any updated bytecode back to the caller.
9648 Variadic<std::unique_ptr<php::ClassBytecode>> outBytecode;
9649 outBytecode.vals.reserve(outClasses.vals.size());
9650 for (auto& cls : outClasses.vals) {
9651 auto bytecode = std::make_unique<php::ClassBytecode>();
9652 bytecode->cls = cls->name;
9653 bytecode->methodBCs.reserve(cls->methods.size());
9654 for (auto& method : cls->methods) {
9655 bytecode->methodBCs.emplace_back(
9656 method->name,
9657 std::move(method->rawBlocks)
9660 for (auto& clo : cls->closures) {
9661 assertx(clo->methods.size() == 1);
9662 auto& method = clo->methods[0];
9663 bytecode->methodBCs.emplace_back(
9664 method->name,
9665 std::move(method->rawBlocks)
9668 outBytecode.vals.emplace_back(std::move(bytecode));
9671 return std::make_tuple(
9672 std::move(outClasses),
9673 std::move(outBytecode),
9674 std::move(outInfos),
9675 std::move(funcs),
9676 std::move(funcInfos),
9677 std::move(outMethods),
9678 std::move(outMeta)
9682 private:
9684 * State which needs to be propagated from a dependency to a child
9685 * class during flattening, but not required after flattening (so
9686 * doesn't belong in ClassInfo2).
9688 struct State {
9689 struct PropTuple {
9690 SString name;
9691 SString src;
9692 php::Prop prop;
9694 // Maintain order of properties as we inherit them.
9695 CompactVector<PropTuple> m_props;
9696 SStringToOneT<size_t> m_propIndices;
9697 CompactVector<php::Const> m_traitCns;
9698 SStringSet m_cnsFromTrait;
9699 SStringToOneT<size_t> m_methodIndices;
9700 CompactVector<const php::Class*> m_parents;
9702 size_t& methodIdx(SString context, SString cls, SString name) {
9703 auto const it = m_methodIndices.find(name);
9704 always_assert_flog(
9705 it != m_methodIndices.end(),
9706 "While processing '{}', "
9707 "tried to access missing method index for '{}::{}'",
9708 context, cls, name
9710 return it->second;
9713 size_t methodIdx(SString context, SString cls, SString name) const {
9714 return const_cast<State*>(this)->methodIdx(context, cls, name);
9719 * LocalIndex is similar to Index, but for this job. It maps names
9720 * to class information needed during flattening. It also verifies
9721 * we don't try to access information about a class until it's
9722 * actually available (which shouldn't happen if our dataflow is
9723 * correct).
9725 struct LocalIndex {
9726 const php::Class* m_ctx{nullptr};
9728 TSStringToOneT<const php::Class*> m_classes;
9729 TSStringToOneT<std::unique_ptr<ClassInfo2>> m_classInfos;
9730 TSStringToOneT<std::unique_ptr<State>> m_states;
9732 TSStringSet m_uninstantiable;
9734 TSStringToOneT<TypeMapping> m_typeMappings;
9735 TSStringSet m_missingTypes;
9737 const php::Class& cls(SString name) const {
9738 if (m_ctx->name->tsame(name)) return *m_ctx;
9739 auto const it = m_classes.find(name);
9740 always_assert_flog(
9741 it != m_classes.end(),
9742 "While processing '{}', tried to access missing class '{}' from index",
9743 m_ctx->name,
9744 name
9746 assertx(it->second);
9747 return *it->second;
9750 const ClassInfo2& classInfo(SString name) const {
9751 auto const it = m_classInfos.find(name);
9752 always_assert_flog(
9753 it != m_classInfos.end(),
9754 "While processing '{}', tried to access missing class-info for '{}' "
9755 "from index",
9756 m_ctx->name,
9757 name
9759 assertx(it->second.get());
9760 return *it->second;
9763 const State& state(SString name) const {
9764 auto const it = m_states.find(name);
9765 always_assert_flog(
9766 it != m_states.end(),
9767 "While processing '{}', tried to access missing flatten state for '{}' "
9768 "from index",
9769 m_ctx->name,
9770 name
9772 assertx(it->second.get());
9773 return *it->second;
9776 bool uninstantiable(SString name) const {
9777 return m_uninstantiable.count(name);
9780 const TypeMapping* typeMapping(SString name) const {
9781 return folly::get_ptr(m_typeMappings, name);
9784 bool missingType(SString name) const {
9785 return m_missingTypes.count(name);
9788 const php::Func& meth(const MethRef& r) const {
9789 auto const& mcls = cls(r.cls);
9790 assertx(r.idx < mcls.methods.size());
9791 return *mcls.methods[r.idx];
9793 const php::Func& meth(const MethTabEntry& mte) const {
9794 return meth(mte.meth());
9797 const php::Const& cns(const ConstIndex& idx) const {
9798 auto const& c = cls(idx.cls);
9799 assertx(idx.idx < c.constants.size());
9800 return c.constants[idx.idx];
9803 size_t methodIdx(SString cls, SString name) const {
9804 return state(cls).methodIdx(m_ctx->name, cls, name);
9809 * Calculate the order in which the classes should be flattened,
9810 * taking into account dependencies.
9812 static std::vector<php::Class*> prepare(
9813 LocalIndex& index,
9814 const TSStringToOneT<php::Class*>& classes
9816 // We might not have any classes if we're just processing funcs.
9817 if (classes.empty()) return {};
9819 auto const get = [&] (SString name) -> php::Class& {
9820 auto const it = classes.find(name);
9821 always_assert_flog(
9822 it != classes.end(),
9823 "Tried to access missing class '{}' while calculating flattening order",
9824 name
9826 return *it->second;
9829 auto const forEachDep = [&] (php::Class& c, auto const& f) {
9830 if (c.parentName) f(get(c.parentName));
9831 for (auto const i : c.interfaceNames) f(get(i));
9832 for (auto const e : c.includedEnumNames) f(get(e));
9833 for (auto const t : c.usedTraitNames) f(get(t));
9834 for (auto const& clo : c.closures) {
9835 f(const_cast<php::Class&>(*clo));
9840 * Perform a standard topological sort:
9842 * - For each class, calculate the number of classes which depend on it.
9844 * - Any class which has a use count of zero is not depended on by
9845 * anyone and goes onto the intitial worklist.
9847 * - For every class on the worklist, push it onto the output
9848 * list, and decrement the use count of all of it's
9849 * dependencies.
9851 * - For any class which now has a use count of zero, push it onto
9852 * the worklist and repeat above step until all classes are
9853 * pushed onto the output list.
9855 * - Reverse the list.
9857 * - This does not handle cycles, but we should not encounter any
9858 * here, as such cycles should be detected earlier and not be
9859 * scheduled in a job.
9861 hphp_fast_map<const php::Class*, size_t> uses;
9862 uses.reserve(classes.size());
9863 for (auto const& [_, cls] : classes) {
9864 forEachDep(*cls, [&] (const php::Class& d) { ++uses[&d]; });
9867 std::vector<php::Class*> worklist;
9868 for (auto const [_, cls] : classes) {
9869 if (!uses[cls]) worklist.emplace_back(cls);
9871 always_assert(!worklist.empty());
9872 std::sort(
9873 worklist.begin(),
9874 worklist.end(),
9875 [] (const php::Class* c1, const php::Class* c2) {
9876 return string_data_lt_type{}(c1->name, c2->name);
9880 std::vector<php::Class*> ordered;
9881 ordered.reserve(classes.size());
9882 do {
9883 auto const cls = worklist.back();
9884 assertx(!uses[cls]);
9885 worklist.pop_back();
9886 forEachDep(
9887 *cls,
9888 [&] (php::Class& d) {
9889 if (!--uses.at(&d)) worklist.emplace_back(&d);
9892 ordered.emplace_back(cls);
9893 } while (!worklist.empty());
9895 for (auto const& [_, cls] : classes) always_assert(!uses.at(cls));
9896 std::reverse(ordered.begin(), ordered.end());
9897 return ordered;
9901 * Create a FuncFamilyEntry for the give method. This
9902 * FuncFamilyEntry assumes that the method is AttrNoOverride, and
9903 * hence reflects just this method. The method isn't necessarily
9904 * actually AttrNoOverride, but if not, it will be updated in
9905 * BuildSubclassListJob (that job needs the initial entries).
9907 static FuncFamilyEntry make_initial_func_family_entry(
9908 const php::Class& cls,
9909 const php::Func& meth,
9910 const MethTabEntry& mte
9912 FuncFamilyEntry entry;
9913 entry.m_allIncomplete = false;
9914 entry.m_regularIncomplete = false;
9915 entry.m_privateAncestor = is_regular_class(cls) && mte.hasPrivateAncestor();
9917 FuncFamilyEntry::MethMetadata meta;
9919 for (size_t i = 0; i < meth.params.size(); ++i) {
9920 meta.m_prepKinds.emplace_back(func_param_prep(&meth, i));
9922 // Any param beyond the size of m_paramPreps is implicitly
9923 // TriBool::No, so we can drop trailing entries which are
9924 // TriBool::No.
9925 while (!meta.m_prepKinds.empty()) {
9926 auto& back = meta.m_prepKinds.back();
9927 if (back.inOut != TriBool::No || back.readonly != TriBool::No) break;
9928 meta.m_prepKinds.pop_back();
9930 meta.m_numInOut = func_num_inout(&meth);
9931 meta.m_nonVariadicParams = numNVArgs(meth);
9932 meta.m_coeffectRules = meth.coeffectRules;
9933 meta.m_requiredCoeffects = meth.requiredCoeffects;
9934 meta.m_isReadonlyReturn = meth.isReadonlyReturn;
9935 meta.m_isReadonlyThis = meth.isReadonlyThis;
9936 meta.m_supportsAER = func_supports_AER(&meth);
9937 meta.m_isReified = meth.isReified;
9938 meta.m_caresAboutDyncalls = (dyn_call_error_level(&meth) > 0);
9939 meta.m_builtin = meth.attrs & AttrBuiltin;
9941 if (is_regular_class(cls)) {
9942 entry.m_meths =
9943 FuncFamilyEntry::BothSingle{mte.meth(), std::move(meta), false};
9944 } else if (bool(mte.attrs & AttrPrivate) && mte.topLevel()) {
9945 entry.m_meths =
9946 FuncFamilyEntry::BothSingle{mte.meth(), std::move(meta), true};
9947 } else {
9948 entry.m_meths =
9949 FuncFamilyEntry::SingleAndNone{mte.meth(), std::move(meta)};
9952 return entry;
9955 static std::unique_ptr<ClassInfo2> make_info(const LocalIndex& index,
9956 php::Class& cls,
9957 State& state) {
9958 if (debug && (is_closure(cls) || is_closure_base(cls))) {
9959 if (is_closure(cls)) {
9960 always_assert(cls.parentName->tsame(s_Closure.get()));
9961 } else {
9962 always_assert(!cls.parentName);
9964 always_assert(cls.interfaceNames.empty());
9965 always_assert(cls.includedEnumNames.empty());
9966 always_assert(cls.usedTraitNames.empty());
9967 always_assert(cls.requirements.empty());
9968 always_assert(cls.constants.empty());
9969 always_assert(cls.userAttributes.empty());
9970 always_assert(!(cls.attrs & (AttrTrait | AttrInterface | AttrAbstract)));
9973 // Set up some initial values for ClassInfo properties. If this
9974 // class is a leaf (we can't actually determine that yet), these
9975 // will be valid and remain as-is. If not, they'll be updated
9976 // properly when calculating subclass information in another pass.
9977 auto cinfo = std::make_unique<ClassInfo2>();
9978 cinfo->name = cls.name;
9979 cinfo->hasConstProp = cls.hasConstProp;
9980 cinfo->hasReifiedParent = cls.hasReifiedGenerics;
9981 cinfo->hasReifiedGeneric = cls.userAttributes.count(s___Reified.get());
9982 cinfo->subHasReifiedGeneric = cinfo->hasReifiedGeneric;
9983 cinfo->initialNoReifiedInit = cls.attrs & AttrNoReifiedInit;
9984 cinfo->isMockClass = is_mock_class(&cls);
9985 cinfo->isRegularClass = is_regular_class(cls);
9987 // Create a ClassGraph for this class. If we decide to not keep
9988 // the ClassInfo, reset the ClassGraph to keep it from ending up
9989 // in the graph.
9990 cinfo->classGraph = ClassGraph::create(cls);
9991 auto success = false;
9992 SCOPE_EXIT { if (!success) cinfo->classGraph.reset(); };
9994 // Assume the class isn't overridden. This is true for leafs and
9995 // non-leafs will get updated when we build subclass information.
9996 if (!is_closure_base(cls)) {
9997 attribute_setter(cls.attrs, true, AttrNoOverride);
9998 attribute_setter(cls.attrs, true, AttrNoOverrideRegular);
9999 } else {
10000 attribute_setter(cls.attrs, false, AttrNoOverride);
10001 attribute_setter(cls.attrs, false, AttrNoOverrideRegular);
10004 // Assume this. If not a leaf, will be updated in
10005 // BuildSubclassList job.
10006 attribute_setter(cls.attrs, true, AttrNoMock);
10008 for (auto const& clo : cls.closures) {
10009 if (index.uninstantiable(clo->name)) {
10010 ITRACE(2,
10011 "Making class-info failed for `{}' because "
10012 "its closure `{}' is uninstantiable\n",
10013 cls.name, clo->name);
10014 return nullptr;
10018 if (cls.parentName) {
10019 assertx(!is_closure_base(cls));
10020 assertx(is_closure(cls) == cls.parentName->tsame(s_Closure.get()));
10022 if (index.uninstantiable(cls.parentName)) {
10023 ITRACE(2,
10024 "Making class-info failed for `{}' because "
10025 "its parent `{}' is uninstantiable\n",
10026 cls.name, cls.parentName);
10027 return nullptr;
10029 auto const& parent = index.cls(cls.parentName);
10030 auto const& parentInfo = index.classInfo(cls.parentName);
10032 assertx(!is_closure(parent));
10033 if (parent.attrs & (AttrInterface | AttrTrait)) {
10034 ITRACE(2,
10035 "Making class-info failed for `{}' because "
10036 "its parent `{}' is not a class\n",
10037 cls.name, cls.parentName);
10038 return nullptr;
10040 if (!enforce_sealing(*cinfo, cls, parent)) return nullptr;
10042 cinfo->parent = cls.parentName;
10043 cinfo->hasConstProp |= parentInfo.hasConstProp;
10044 cinfo->hasReifiedParent |= parentInfo.hasReifiedParent;
10046 state.m_parents.emplace_back(&parent);
10047 cinfo->classGraph.setBase(parentInfo.classGraph);
10048 } else if (!cinfo->hasReifiedGeneric) {
10049 attribute_setter(cls.attrs, true, AttrNoReifiedInit);
10052 for (auto const iname : cls.interfaceNames) {
10053 assertx(!is_closure(cls));
10054 assertx(!is_closure_base(cls));
10055 if (index.uninstantiable(iname)) {
10056 ITRACE(2,
10057 "Making class-info failed for `{}' because "
10058 "{} is uninstantiable\n",
10059 cls.name, iname);
10060 return nullptr;
10062 auto const& iface = index.cls(iname);
10063 auto const& ifaceInfo = index.classInfo(iname);
10065 assertx(!is_closure(iface));
10066 if (!(iface.attrs & AttrInterface)) {
10067 ITRACE(2,
10068 "Making class-info failed for `{}' because `{}' "
10069 "is not an interface\n",
10070 cls.name, iname);
10071 return nullptr;
10073 if (!enforce_sealing(*cinfo, cls, iface)) return nullptr;
10075 cinfo->hasReifiedParent |= ifaceInfo.hasReifiedParent;
10077 state.m_parents.emplace_back(&iface);
10078 cinfo->classGraph.addParent(ifaceInfo.classGraph);
10081 for (auto const ename : cls.includedEnumNames) {
10082 assertx(!is_closure(cls));
10083 assertx(!is_closure_base(cls));
10084 if (index.uninstantiable(ename)) {
10085 ITRACE(2,
10086 "Making class-info failed for `{}' because "
10087 "{} is uninstantiable\n",
10088 cls.name, ename);
10089 return nullptr;
10091 auto const& e = index.cls(ename);
10092 auto const& einfo = index.classInfo(ename);
10094 assertx(!is_closure(e));
10095 auto const wantAttr = cls.attrs & (AttrEnum | AttrEnumClass);
10096 if (!(e.attrs & wantAttr)) {
10097 ITRACE(2,
10098 "Making class-info failed for `{}' because `{}' "
10099 "is not an enum{}\n",
10100 cls.name, ename,
10101 wantAttr & AttrEnumClass ? " class" : "");
10102 return nullptr;
10104 if (!enforce_sealing(*cinfo, cls, e)) return nullptr;
10106 for (auto const iface : einfo.classGraph.declInterfaces()) {
10107 cinfo->classGraph.addParent(iface);
10111 auto const clsHasModuleLevelTrait =
10112 cls.userAttributes.count(s___ModuleLevelTrait.get());
10113 if (clsHasModuleLevelTrait &&
10114 (!(cls.attrs & AttrTrait) || (cls.attrs & AttrInternal))) {
10115 ITRACE(2,
10116 "Making class-info failed for `{}' because "
10117 "attribute <<__ModuleLevelTrait>> can only be "
10118 "specified on public traits\n",
10119 cls.name);
10120 return nullptr;
10123 for (auto const tname : cls.usedTraitNames) {
10124 assertx(!is_closure(cls));
10125 assertx(!is_closure_base(cls));
10126 if (index.uninstantiable(tname)) {
10127 ITRACE(2,
10128 "Making class-info failed for `{}' because "
10129 "{} is uninstantiable\n",
10130 cls.name, tname);
10131 return nullptr;
10133 auto const& trait = index.cls(tname);
10134 auto const& traitInfo = index.classInfo(tname);
10136 assertx(!is_closure(trait));
10137 if (!(trait.attrs & AttrTrait)) {
10138 ITRACE(2,
10139 "Making class-info failed for `{}' because `{}' "
10140 "is not a trait\n",
10141 cls.name, tname);
10142 return nullptr;
10144 if (!enforce_sealing(*cinfo, cls, trait)) return nullptr;
10146 cinfo->hasConstProp |= traitInfo.hasConstProp;
10147 cinfo->hasReifiedParent |= traitInfo.hasReifiedParent;
10149 state.m_parents.emplace_back(&trait);
10150 cinfo->classGraph.addParent(traitInfo.classGraph);
10153 if (cls.attrs & AttrEnum) {
10154 auto const baseType = [&] {
10155 auto const& base = cls.enumBaseTy;
10156 if (!base.isUnresolved()) return base.type();
10157 auto const tm = index.typeMapping(base.typeName());
10158 if (!tm) return AnnotType::Unresolved;
10159 // enums cannot use case types
10160 assertx(!tm->value.isUnion());
10161 return tm->value.type();
10162 }();
10163 if (!enumSupportsAnnot(baseType)) {
10164 ITRACE(2,
10165 "Making class-info failed for `{}' because {} "
10166 "is not a valid enum base type\n",
10167 cls.name, annotName(baseType));
10168 return nullptr;
10172 if (!build_methods(index, cls, *cinfo, state)) return nullptr;
10173 if (!build_properties(index, cls, *cinfo, state)) return nullptr;
10174 if (!build_constants(index, cls, *cinfo, state)) return nullptr;
10176 std::sort(
10177 begin(state.m_parents),
10178 end(state.m_parents),
10179 [] (const php::Class* a, const php::Class* b) {
10180 return string_data_lt_type{}(a->name, b->name);
10183 state.m_parents.erase(
10184 std::unique(begin(state.m_parents), end(state.m_parents)),
10185 end(state.m_parents)
10187 assertx(
10188 std::none_of(
10189 begin(state.m_parents), end(state.m_parents),
10190 [] (const php::Class* c) { return is_closure(*c); }
10194 cinfo->subHasConstProp = cinfo->hasConstProp;
10196 // All methods are originally not overridden (we'll update this as
10197 // necessary later), except for special methods, which are always
10198 // considered to be overridden.
10199 for (auto& [name, mte] : cinfo->methods) {
10200 assertx(!cinfo->missingMethods.count(name));
10201 if (is_special_method_name(name)) {
10202 attribute_setter(mte.attrs, false, AttrNoOverride);
10203 mte.clearNoOverrideRegular();
10204 } else {
10205 attribute_setter(mte.attrs, true, AttrNoOverride);
10206 mte.setNoOverrideRegular();
10210 // We don't calculate subclass information for closures, so make
10211 // sure their initial values are all what they should be.
10212 if (debug && (is_closure(cls) || is_closure_base(cls))) {
10213 if (is_closure(cls)) {
10214 always_assert(is_closure_name(cls.name));
10215 always_assert(state.m_parents.size() == 1);
10216 always_assert(state.m_parents[0]->name->tsame(s_Closure.get()));
10217 always_assert(!(cls.attrs & AttrNoReifiedInit));
10218 } else {
10219 always_assert(state.m_parents.empty());
10220 always_assert(cls.attrs & AttrNoReifiedInit);
10222 always_assert(cinfo->missingMethods.empty());
10223 always_assert(!cinfo->hasConstProp);
10224 always_assert(!cinfo->subHasConstProp);
10225 always_assert(!cinfo->hasReifiedParent);
10226 always_assert(!cinfo->hasReifiedGeneric);
10227 always_assert(!cinfo->subHasReifiedGeneric);
10228 always_assert(!cinfo->initialNoReifiedInit);
10229 always_assert(!cinfo->isMockClass);
10230 always_assert(cinfo->isRegularClass);
10231 always_assert(!is_mock_class(&cls));
10234 ITRACE(2, "new class-info: {}\n", cls.name);
10235 if (Trace::moduleEnabled(Trace::hhbbc_index, 3)) {
10236 if (cinfo->parent) {
10237 ITRACE(3, " parent: {}\n", cinfo->parent);
10239 auto const cg = cinfo->classGraph;
10240 for (auto const DEBUG_ONLY base : cg.bases()) {
10241 ITRACE(3, " base: {}\n", base.name());
10243 for (auto const DEBUG_ONLY iface : cls.interfaceNames) {
10244 ITRACE(3, " decl implements: {}\n", iface);
10246 for (auto const DEBUG_ONLY iface : cg.interfaces()) {
10247 ITRACE(3, " implements: {}\n", iface.name());
10249 for (auto const DEBUG_ONLY e : cls.includedEnumNames) {
10250 ITRACE(3, " enum: {}\n", e);
10252 for (auto const DEBUG_ONLY trait : cls.usedTraitNames) {
10253 ITRACE(3, " uses: {}\n", trait);
10255 for (auto const& DEBUG_ONLY closure : cls.closures) {
10256 ITRACE(3, " closure: {}\n", closure->name);
10260 // We're going to use this ClassInfo.
10261 success = true;
10262 return cinfo;
10265 static bool enforce_sealing(const ClassInfo2& cinfo,
10266 const php::Class& cls,
10267 const php::Class& parent) {
10268 if (is_mock_class(&cls)) return true;
10269 if (!(parent.attrs & AttrSealed)) return true;
10270 auto const it = parent.userAttributes.find(s___Sealed.get());
10271 assertx(it != parent.userAttributes.end());
10272 assertx(tvIsArrayLike(it->second));
10273 auto allowed = false;
10274 IterateV(
10275 it->second.m_data.parr,
10276 [&] (TypedValue v) {
10277 assertx(tvIsStringLike(v));
10278 if (tvAssertStringLike(v)->tsame(cinfo.name)) {
10279 allowed = true;
10280 return true;
10282 return false;
10285 if (!allowed) {
10286 ITRACE(
10288 "Making class-info failed for `{}' because "
10289 "`{}' is sealed\n",
10290 cinfo.name, parent.name
10293 return allowed;
10296 static bool build_properties(const LocalIndex& index,
10297 const php::Class& cls,
10298 ClassInfo2& cinfo,
10299 State& state) {
10300 if (cls.parentName) {
10301 auto const& parentState = index.state(cls.parentName);
10302 state.m_props = parentState.m_props;
10303 state.m_propIndices = parentState.m_propIndices;
10306 for (auto const iface : cls.interfaceNames) {
10307 if (!merge_properties(cinfo, state, index.state(iface))) {
10308 return false;
10311 for (auto const trait : cls.usedTraitNames) {
10312 if (!merge_properties(cinfo, state, index.state(trait))) {
10313 return false;
10316 for (auto const e : cls.includedEnumNames) {
10317 if (!merge_properties(cinfo, state, index.state(e))) {
10318 return false;
10322 if (cls.attrs & AttrInterface) return true;
10324 auto const cannotDefineInternalProperties =
10325 // public traits cannot define internal properties unless they
10326 // have the __ModuleLevelTrait attribute
10327 ((cls.attrs & AttrTrait) && (cls.attrs & AttrPublic)) &&
10328 !(cls.userAttributes.count(s___ModuleLevelTrait.get()));
10330 for (auto const& p : cls.properties) {
10331 if (cannotDefineInternalProperties && (p.attrs & AttrInternal)) {
10332 ITRACE(2,
10333 "Adding property failed for `{}' because property `{}' "
10334 "is internal and public traits cannot define internal properties\n",
10335 cinfo.name, p.name);
10336 return false;
10338 if (!add_property(cinfo, state, p.name, p, cinfo.name, false)) {
10339 return false;
10343 // There's no need to do this work if traits have been flattened
10344 // already, or if the top level class has no traits. In those
10345 // cases, we might be able to rule out some instantiations, but it
10346 // doesn't seem worth it.
10347 if (cls.attrs & AttrNoExpandTrait) return true;
10349 for (auto const traitName : cls.usedTraitNames) {
10350 auto const& trait = index.cls(traitName);
10351 auto const& traitInfo = index.classInfo(traitName);
10352 for (auto const& p : trait.properties) {
10353 if (!add_property(cinfo, state, p.name, p, cinfo.name, true)) {
10354 return false;
10357 for (auto const& p : traitInfo.traitProps) {
10358 if (!add_property(cinfo, state, p.name, p, cinfo.name, true)) {
10359 return false;
10364 return true;
10367 static bool add_property(ClassInfo2& cinfo,
10368 State& state,
10369 SString name,
10370 const php::Prop& prop,
10371 SString src,
10372 bool trait) {
10373 auto const [it, emplaced] =
10374 state.m_propIndices.emplace(name, state.m_props.size());
10375 if (emplaced) {
10376 state.m_props.emplace_back(State::PropTuple{name, src, prop});
10377 if (trait) cinfo.traitProps.emplace_back(prop);
10378 return true;
10380 assertx(it->second < state.m_props.size());
10381 auto& prevTuple = state.m_props[it->second];
10382 auto const& prev = prevTuple.prop;
10383 auto const prevSrc = prevTuple.src;
10385 if (cinfo.name->tsame(prevSrc)) {
10386 if ((prev.attrs ^ prop.attrs) &
10387 (AttrStatic | AttrPublic | AttrProtected | AttrPrivate) ||
10388 (!(prop.attrs & AttrSystemInitialValue) &&
10389 !(prev.attrs & AttrSystemInitialValue) &&
10390 !Class::compatibleTraitPropInit(prev.val, prop.val))) {
10391 ITRACE(2,
10392 "Adding property failed for `{}' because "
10393 "two declarations of `{}' at the same level had "
10394 "different attributes\n",
10395 cinfo.name, prop.name);
10396 return false;
10398 return true;
10401 if (!(prev.attrs & AttrPrivate)) {
10402 if ((prev.attrs ^ prop.attrs) & AttrStatic) {
10403 ITRACE(2,
10404 "Adding property failed for `{}' because "
10405 "`{}' was defined both static and non-static\n",
10406 cinfo.name, prop.name);
10407 return false;
10409 if (prop.attrs & AttrPrivate) {
10410 ITRACE(2,
10411 "Adding property failed for `{}' because "
10412 "`{}' was re-declared private\n",
10413 cinfo.name, prop.name);
10414 return false;
10416 if (prop.attrs & AttrProtected && !(prev.attrs & AttrProtected)) {
10417 ITRACE(2,
10418 "Adding property failed for `{}' because "
10419 "`{}' was redeclared protected from public\n",
10420 cinfo.name, prop.name);
10421 return false;
10425 if (trait) cinfo.traitProps.emplace_back(prop);
10426 prevTuple = State::PropTuple{name, src, prop};
10427 return true;
10430 static bool merge_properties(ClassInfo2& cinfo,
10431 State& dst,
10432 const State& src) {
10433 for (auto const& [name, src, prop] : src.m_props) {
10434 if (!add_property(cinfo, dst, name, prop, src, false)) {
10435 return false;
10438 return true;
10441 static bool build_constants(const LocalIndex& index,
10442 php::Class& cls,
10443 ClassInfo2& cinfo,
10444 State& state) {
10445 if (cls.parentName) {
10446 cinfo.clsConstants = index.classInfo(cls.parentName).clsConstants;
10447 state.m_cnsFromTrait = index.state(cls.parentName).m_cnsFromTrait;
10450 for (auto const iname : cls.interfaceNames) {
10451 auto const& iface = index.classInfo(iname);
10452 auto const& ifaceState = index.state(iname);
10453 for (auto const& [cnsName, cnsIdx] : iface.clsConstants) {
10454 auto const added = add_constant(
10455 index, cinfo, state, cnsName,
10456 cnsIdx, ifaceState.m_cnsFromTrait.count(cnsName)
10458 if (!added) return false;
10462 auto const addShallowConstants = [&] {
10463 auto const numConstants = cls.constants.size();
10464 for (uint32_t idx = 0; idx < numConstants; ++idx) {
10465 auto const& cns = cls.constants[idx];
10466 auto const added = add_constant(
10467 index, cinfo, state,
10468 cns.name,
10469 ClassInfo2::ConstIndexAndKind {
10470 ConstIndex { cls.name, idx },
10471 cns.kind
10473 false
10475 if (!added) return false;
10477 return true;
10480 auto const addTraitConstants = [&] {
10481 for (auto const tname : cls.usedTraitNames) {
10482 auto const& trait = index.classInfo(tname);
10483 for (auto const& [cnsName, cnsIdx] : trait.clsConstants) {
10484 auto const added = add_constant(
10485 index, cinfo, state, cnsName,
10486 cnsIdx, true
10488 if (!added) return false;
10491 return true;
10494 if (Cfg::Eval::TraitConstantInterfaceBehavior) {
10495 // trait constants must be inserted before constants shallowly
10496 // declared on the class to match the interface semantics
10497 if (!addTraitConstants()) return false;
10498 if (!addShallowConstants()) return false;
10499 } else {
10500 if (!addShallowConstants()) return false;
10501 if (!addTraitConstants()) return false;
10504 for (auto const ename : cls.includedEnumNames) {
10505 auto const& e = index.classInfo(ename);
10506 for (auto const& [cnsName, cnsIdx] : e.clsConstants) {
10507 auto const added = add_constant(
10508 index, cinfo, state, cnsName,
10509 cnsIdx, false
10511 if (!added) return false;
10515 auto const addTraitConst = [&] (const php::Const& c) {
10517 * Only copy in constants that win. Otherwise, in the runtime, if
10518 * we have a constant from an interface implemented by a trait
10519 * that wins over this fromTrait constant, we won't know which
10520 * trait it came from, and therefore won't know which constant
10521 * should win. Dropping losing constants here works because if
10522 * they fatal with constants in declared interfaces, we catch that
10523 * above.
10525 auto const& existing = cinfo.clsConstants.find(c.name);
10526 if (existing->second.idx.cls->tsame(c.cls)) {
10527 state.m_traitCns.emplace_back(c);
10528 state.m_traitCns.back().isFromTrait = true;
10531 for (auto const tname : cls.usedTraitNames) {
10532 auto const& trait = index.cls(tname);
10533 auto const& traitState = index.state(tname);
10534 for (auto const& c : trait.constants) addTraitConst(c);
10535 for (auto const& c : traitState.m_traitCns) addTraitConst(c);
10538 if (cls.attrs & (AttrAbstract | AttrInterface | AttrTrait)) return true;
10540 std::vector<SString> sortedClsConstants;
10541 sortedClsConstants.reserve(cinfo.clsConstants.size());
10542 for (auto const& [name, _] : cinfo.clsConstants) {
10543 sortedClsConstants.emplace_back(name);
10545 std::sort(
10546 sortedClsConstants.begin(),
10547 sortedClsConstants.end(),
10548 string_data_lt{}
10551 for (auto const name : sortedClsConstants) {
10552 auto& cnsIdx = cinfo.clsConstants.find(name)->second;
10553 if (cnsIdx.idx.cls->tsame(cls.name)) continue;
10555 auto const& cns = index.cns(cnsIdx.idx);
10556 if (!cns.isAbstract || !cns.val) continue;
10558 if (cns.val->m_type == KindOfUninit) {
10559 auto const& cnsCls = index.cls(cnsIdx.idx.cls);
10560 assertx(!cnsCls.methods.empty());
10561 assertx(cnsCls.methods.back()->name == s_86cinit.get());
10562 auto const& cnsCInit = *cnsCls.methods.back();
10564 if (cls.methods.empty() ||
10565 cls.methods.back()->name != s_86cinit.get()) {
10566 ClonedClosures clonedClosures;
10567 auto cloned = clone(
10568 index,
10569 cnsCInit,
10570 cnsCInit.name,
10571 cnsCInit.attrs,
10572 cls,
10573 clonedClosures,
10574 true
10576 assertx(cloned);
10577 assertx(clonedClosures.empty());
10578 assertx(cloned->cls == &cls);
10579 cloned->clsIdx = cls.methods.size();
10580 auto const DEBUG_ONLY emplaced =
10581 cinfo.methods.emplace(cloned->name, MethTabEntry { *cloned });
10582 assertx(emplaced.second);
10583 cls.methods.emplace_back(std::move(cloned));
10584 } else {
10585 auto const DEBUG_ONLY succeeded =
10586 append_86cinit(cls.methods.back().get(), cnsCInit);
10587 assertx(succeeded);
10591 // This is similar to trait constant flattening
10592 auto copy = cns;
10593 copy.cls = cls.name;
10594 copy.isAbstract = false;
10595 state.m_cnsFromTrait.erase(copy.name);
10597 cnsIdx.idx.cls = cls.name;
10598 cnsIdx.idx.idx = cls.constants.size();
10599 cnsIdx.kind = copy.kind;
10600 cls.constants.emplace_back(std::move(copy));
10603 return true;
10606 static bool add_constant(const LocalIndex& index,
10607 ClassInfo2& cinfo,
10608 State& state,
10609 SString name,
10610 const ClassInfo2::ConstIndexAndKind& cnsIdx,
10611 bool fromTrait) {
10612 auto [it, emplaced] = cinfo.clsConstants.emplace(name, cnsIdx);
10613 if (emplaced) {
10614 if (fromTrait) {
10615 always_assert(state.m_cnsFromTrait.emplace(name).second);
10616 } else {
10617 always_assert(!state.m_cnsFromTrait.count(name));
10619 return true;
10621 auto& existingIdx = it->second;
10623 // Same constant (from an interface via two different paths) is ok
10624 if (existingIdx.idx.cls->tsame(cnsIdx.idx.cls)) return true;
10626 auto const& existingCnsCls = index.cls(existingIdx.idx.cls);
10627 auto const& existing = index.cns(existingIdx.idx);
10628 auto const& cns = index.cns(cnsIdx.idx);
10630 if (existing.kind != cns.kind) {
10631 ITRACE(
10633 "Adding constant failed for `{}' because `{}' was defined by "
10634 "`{}' as a {} and by `{}' as a {}\n",
10635 cinfo.name,
10636 name,
10637 cnsIdx.idx.cls,
10638 ConstModifiers::show(cns.kind),
10639 existingIdx.idx.cls,
10640 ConstModifiers::show(existing.kind)
10642 return false;
10645 // Ignore abstract constants
10646 if (cns.isAbstract && !cns.val) return true;
10647 // If the existing constant in the map is concrete, then don't
10648 // overwrite it with an incoming abstract constant's default
10649 if (!existing.isAbstract && cns.isAbstract) return true;
10651 if (existing.val) {
10653 * A constant from a declared interface collides with a constant
10654 * (Excluding constants from interfaces a trait implements).
10656 * Need this check otherwise constants from traits that conflict
10657 * with declared interfaces will silently lose and not conflict
10658 * in the runtime.
10660 * Type and Context constants can be overridden.
10662 auto const& cnsCls = index.cls(cnsIdx.idx.cls);
10663 if (cns.kind == ConstModifiers::Kind::Value &&
10664 !existing.isAbstract &&
10665 (existingCnsCls.attrs & AttrInterface) &&
10666 !((cnsCls.attrs & AttrInterface) && fromTrait)) {
10667 auto const& cls = index.cls(cinfo.name);
10668 for (auto const iface : cls.interfaceNames) {
10669 if (existingIdx.idx.cls->tsame(iface)) {
10670 ITRACE(
10672 "Adding constant failed for `{}' because "
10673 "`{}' was defined by both `{}' and `{}'\n",
10674 cinfo.name,
10675 name,
10676 cnsIdx.idx.cls,
10677 existingIdx.idx.cls
10679 return false;
10684 // Constants from traits silently lose
10685 if (!Cfg::Eval::TraitConstantInterfaceBehavior && fromTrait) return true;
10687 if ((cnsCls.attrs & AttrInterface ||
10688 (Cfg::Eval::TraitConstantInterfaceBehavior &&
10689 (cnsCls.attrs & AttrTrait))) &&
10690 (existing.isAbstract ||
10691 cns.kind == ConstModifiers::Kind::Type)) {
10692 // Because existing has val, this covers the case where it is
10693 // abstract with default allow incoming to win. Also, type
10694 // constants from interfaces may be overridden even if they're
10695 // not abstract.
10696 } else {
10697 // A constant from an interface or from an included enum
10698 // collides with an existing constant.
10699 if (cnsCls.attrs & (AttrInterface | AttrEnum | AttrEnumClass) ||
10700 (Cfg::Eval::TraitConstantInterfaceBehavior &&
10701 (cnsCls.attrs & AttrTrait))) {
10702 ITRACE(
10704 "Adding constant failed for `{}' because "
10705 "`{}' was defined by both `{}' and `{}'\n",
10706 cinfo.name,
10707 name,
10708 cnsIdx.idx.cls,
10709 existingIdx.idx.cls
10711 return false;
10716 if (fromTrait) {
10717 state.m_cnsFromTrait.emplace(name);
10718 } else {
10719 state.m_cnsFromTrait.erase(name);
10721 existingIdx = cnsIdx;
10722 return true;
10726 * Make a flattened table of the methods on this class.
10728 * Duplicate method names override parent methods, unless the parent
10729 * method is final and the class is not a __MockClass, in which case
10730 * this class definitely would fatal if ever defined.
10732 * Note: we're leaving non-overridden privates in their subclass
10733 * method table, here. This isn't currently "wrong", because calling
10734 * it would be a fatal, but note that resolve_method needs to be
10735 * pretty careful about privates and overriding in general.
10737 static bool build_methods(const LocalIndex& index,
10738 const php::Class& cls,
10739 ClassInfo2& cinfo,
10740 State& state) {
10741 // Since interface methods are not inherited, any methods in
10742 // interfaces this class implements are automatically missing.
10743 assertx(cinfo.methods.empty());
10744 for (auto const iname : cls.interfaceNames) {
10745 auto const& iface = index.classInfo(iname);
10746 for (auto const& [name, _] : iface.methods) {
10747 if (is_special_method_name(name)) continue;
10748 cinfo.missingMethods.emplace(name);
10750 for (auto const name : iface.missingMethods) {
10751 assertx(!is_special_method_name(name));
10752 cinfo.missingMethods.emplace(name);
10756 // Interface methods are just stubs which return null. They don't
10757 // get inherited by their implementations.
10758 if (cls.attrs & AttrInterface) {
10759 assertx(!cls.parentName);
10760 assertx(cls.usedTraitNames.empty());
10761 uint32_t idx = cinfo.methods.size();
10762 assertx(!idx);
10763 for (auto const& m : cls.methods) {
10764 auto const res = cinfo.methods.emplace(m->name, MethTabEntry { *m });
10765 always_assert(res.second);
10766 always_assert(state.m_methodIndices.emplace(m->name, idx++).second);
10767 if (cinfo.missingMethods.count(m->name)) {
10768 assertx(!res.first->second.firstName());
10769 cinfo.missingMethods.erase(m->name);
10770 } else {
10771 res.first->second.setFirstName();
10773 ITRACE(4, " {}: adding method {}::{}\n",
10774 cls.name, cls.name, m->name);
10776 return true;
10779 auto const overridden = [&] (MethTabEntry& existing,
10780 MethRef meth,
10781 Attr attrs) {
10782 auto const& existingMeth = index.meth(existing);
10783 if (existingMeth.attrs & AttrFinal) {
10784 if (!is_mock_class(&cls)) {
10785 ITRACE(
10787 "Adding methods failed for `{}' because "
10788 "it tried to override final method `{}::{}'\n",
10789 cls.name,
10790 existing.meth().cls,
10791 existingMeth.name
10793 return false;
10796 ITRACE(
10798 "{}: overriding method {}::{} with {}::{}\n",
10799 cls.name,
10800 existing.meth().cls,
10801 existingMeth.name,
10802 meth.cls,
10803 existingMeth.name
10805 if (existingMeth.attrs & AttrPrivate) {
10806 existing.setHasPrivateAncestor();
10808 existing.setMeth(meth);
10809 existing.attrs = attrs;
10810 existing.setTopLevel();
10811 return true;
10814 // If there's a parent, start by copying its methods
10815 if (cls.parentName) {
10816 auto const& parentInfo = index.classInfo(cls.parentName);
10818 assertx(cinfo.methods.empty());
10819 cinfo.missingMethods.insert(
10820 begin(parentInfo.missingMethods),
10821 end(parentInfo.missingMethods)
10824 for (auto const& mte : parentInfo.methods) {
10825 // Don't inherit the 86* methods
10826 if (HPHP::Func::isSpecial(mte.first)) continue;
10828 auto const emplaced = cinfo.methods.emplace(mte);
10829 always_assert(emplaced.second);
10830 emplaced.first->second.clearTopLevel();
10831 emplaced.first->second.clearFirstName();
10833 always_assert(
10834 state.m_methodIndices.emplace(
10835 mte.first,
10836 index.methodIdx(cls.parentName, mte.first)
10837 ).second
10840 cinfo.missingMethods.erase(mte.first);
10842 ITRACE(
10844 "{}: inheriting method {}::{}\n",
10845 cls.name,
10846 cls.parentName,
10847 mte.first
10852 auto idx = cinfo.methods.size();
10853 auto const clsHasModuleLevelTrait =
10854 cls.userAttributes.count(s___ModuleLevelTrait.get());
10856 // Now add our methods.
10857 for (auto const& m : cls.methods) {
10858 if ((cls.attrs & AttrTrait) &&
10859 (!((cls.attrs & AttrInternal) || clsHasModuleLevelTrait)) &&
10860 (m->attrs & AttrInternal)) {
10861 ITRACE(2,
10862 "Adding methods failed for `{}' because "
10863 "method `{}' is internal and public traits "
10864 "cannot define internal methods unless they have "
10865 "the <<__ModuleLevelTrait>> attribute\n",
10866 cls.name, m->name);
10867 return false;
10869 auto const emplaced = cinfo.methods.emplace(m->name, MethTabEntry { *m });
10870 if (emplaced.second) {
10871 ITRACE(
10873 "{}: adding method {}::{}\n",
10874 cls.name,
10875 cls.name,
10876 m->name
10878 always_assert(state.m_methodIndices.emplace(m->name, idx++).second);
10879 if (cinfo.missingMethods.count(m->name)) {
10880 assertx(!emplaced.first->second.firstName());
10881 cinfo.missingMethods.erase(m->name);
10882 } else {
10883 emplaced.first->second.setFirstName();
10885 continue;
10888 // If the method is already in our table, it shouldn't be
10889 // missing.
10890 assertx(!cinfo.missingMethods.count(m->name));
10892 assertx(!emplaced.first->second.firstName());
10894 if ((m->attrs & AttrTrait) && (m->attrs & AttrAbstract)) {
10895 // Abstract methods from traits never override anything.
10896 continue;
10898 if (!overridden(emplaced.first->second, MethRef { *m }, m->attrs)) {
10899 return false;
10903 // If our traits were previously flattened, we're done.
10904 if (cls.attrs & AttrNoExpandTrait) return true;
10906 try {
10907 TMIData tmid;
10908 for (auto const tname : cls.usedTraitNames) {
10909 auto const& tcls = index.cls(tname);
10910 auto const& t = index.classInfo(tname);
10911 std::vector<std::pair<SString, const MethTabEntry*>>
10912 methods(t.methods.size());
10913 for (auto const& [name, mte] : t.methods) {
10914 if (HPHP::Func::isSpecial(name)) continue;
10915 auto const idx = index.methodIdx(tname, name);
10916 assertx(!methods[idx].first);
10917 methods[idx] = std::make_pair(name, &mte);
10918 if (auto it = cinfo.methods.find(name);
10919 it != end(cinfo.methods)) {
10920 it->second.clearFirstName();
10924 for (auto const name : t.missingMethods) {
10925 assertx(!is_special_method_name(name));
10926 if (cinfo.methods.count(name)) continue;
10927 cinfo.missingMethods.emplace(name);
10930 for (auto const& [name, mte] : methods) {
10931 if (!name) continue;
10932 auto const& meth = index.meth(*mte);
10933 tmid.add(
10934 TraitMethod { std::make_pair(&t, &tcls), &meth, mte->attrs },
10935 name
10938 for (auto const& clo : tcls.closures) {
10939 auto const invoke = find_method(clo.get(), s_invoke.get());
10940 assertx(invoke);
10941 cinfo.extraMethods.emplace(MethRef { *invoke });
10945 auto const traitMethods = tmid.finish(
10946 std::make_pair(&cinfo, &cls),
10947 cls.userAttributes.count(s___EnableMethodTraitDiamond.get())
10950 // Import the methods.
10951 for (auto const& mdata : traitMethods) {
10952 auto const method = mdata.tm.method;
10953 auto attrs = mdata.tm.modifiers;
10955 if (attrs == AttrNone) {
10956 attrs = method->attrs;
10957 } else {
10958 auto const attrMask =
10959 (Attr)(AttrPublic | AttrProtected | AttrPrivate |
10960 AttrAbstract | AttrFinal);
10961 attrs = (Attr)((attrs & attrMask) |
10962 (method->attrs & ~attrMask));
10965 auto const emplaced = cinfo.methods.emplace(
10966 mdata.name,
10967 MethTabEntry { *method, attrs }
10969 if (emplaced.second) {
10970 ITRACE(
10972 "{}: adding trait method {}::{} as {}\n",
10973 cls.name,
10974 method->cls->name, method->name, mdata.name
10976 always_assert(
10977 state.m_methodIndices.emplace(mdata.name, idx++).second
10979 cinfo.missingMethods.erase(mdata.name);
10980 } else {
10981 assertx(!cinfo.missingMethods.count(mdata.name));
10982 if (attrs & AttrAbstract) continue;
10983 if (emplaced.first->second.meth().cls->tsame(cls.name)) continue;
10984 if (!overridden(emplaced.first->second, MethRef { *method }, attrs)) {
10985 return false;
10987 state.methodIdx(index.m_ctx->name, cinfo.name, mdata.name) = idx++;
10989 cinfo.extraMethods.emplace(MethRef { *method });
10991 } catch (const TMIOps::TMIException& exn) {
10992 ITRACE(
10994 "Adding methods failed for `{}' importing traits: {}\n",
10995 cls.name, exn.what()
10997 return false;
11000 return true;
11003 using ClonedClosures =
11004 hphp_fast_map<const php::Class*, std::unique_ptr<php::Class>>;
11006 static SString rename_closure(const php::Class& closure,
11007 const php::Class& newContext) {
11008 auto n = closure.name->slice();
11009 auto const p = n.find(';');
11010 if (p != std::string::npos) n = n.subpiece(0, p);
11011 return makeStaticString(folly::sformat("{};{}", n, newContext.name));
11014 static std::unique_ptr<php::Class>
11015 clone_closure(const LocalIndex& index,
11016 const php::Class& closure,
11017 const php::Class& newContext,
11018 bool requiresFromOriginalModule,
11019 ClonedClosures& clonedClosures) {
11020 auto clone = std::make_unique<php::Class>(closure);
11021 assertx(clone->closureContextCls);
11023 clone->name = rename_closure(closure, newContext);
11024 clone->closureContextCls = newContext.name;
11025 clone->unit = newContext.unit;
11027 ITRACE(4, "- cloning closure {} as {} (with context {})\n",
11028 closure.name, clone->name, newContext.name);
11030 for (size_t i = 0, numMeths = clone->methods.size(); i < numMeths; ++i) {
11031 auto meth = std::move(clone->methods[i]);
11032 meth->cls = clone.get();
11033 assertx(meth->clsIdx == i);
11034 if (!meth->originalFilename) meth->originalFilename = meth->unit;
11035 if (!meth->originalUnit) meth->originalUnit = meth->unit;
11036 if (!meth->originalClass) meth->originalClass = closure.name;
11037 meth->requiresFromOriginalModule = requiresFromOriginalModule;
11038 meth->unit = newContext.unit;
11040 clone->methods[i] =
11041 clone_closures(index, std::move(meth), requiresFromOriginalModule, clonedClosures);
11042 if (!clone->methods[i]) return nullptr;
11045 return clone;
11048 static std::unique_ptr<php::Func>
11049 clone_closures(const LocalIndex& index,
11050 std::unique_ptr<php::Func> cloned,
11051 bool requiresFromOriginalModule,
11052 ClonedClosures& clonedClosures) {
11053 if (!cloned->hasCreateCl) return cloned;
11055 auto const onClosure = [&] (LSString& closureName) {
11056 auto const& cls = index.cls(closureName);
11057 assertx(is_closure(cls));
11059 // CreateCls are allowed to refer to the same closure within the
11060 // same func. If this is a duplicate, use the already cloned
11061 // closure name.
11062 if (auto const it = clonedClosures.find(&cls);
11063 it != clonedClosures.end()) {
11064 closureName = it->second->name;
11065 return true;
11068 // Otherwise clone the closure (which gives it a new name), and
11069 // update the name in the CreateCl to match.
11070 auto closure = clone_closure(
11071 index,
11072 cls,
11073 cloned->cls->closureContextCls
11074 ? index.cls(cloned->cls->closureContextCls)
11075 : *cloned->cls,
11076 requiresFromOriginalModule,
11077 clonedClosures
11079 if (!closure) return false;
11080 closureName = closure->name;
11081 always_assert(clonedClosures.emplace(&cls, std::move(closure)).second);
11082 return true;
11085 auto mf = php::WideFunc::mut(cloned.get());
11086 assertx(!mf.blocks().empty());
11087 for (size_t bid = 0; bid < mf.blocks().size(); bid++) {
11088 auto const b = mf.blocks()[bid].mutate();
11089 for (size_t ix = 0; ix < b->hhbcs.size(); ix++) {
11090 auto& bc = b->hhbcs[ix];
11091 switch (bc.op) {
11092 case Op::CreateCl: {
11093 if (!onClosure(bc.CreateCl.str2)) return nullptr;
11094 break;
11096 default:
11097 break;
11102 return cloned;
11105 static std::unique_ptr<php::Func> clone(const LocalIndex& index,
11106 const php::Func& orig,
11107 SString name,
11108 Attr attrs,
11109 const php::Class& dstCls,
11110 ClonedClosures& clonedClosures,
11111 bool internal = false) {
11112 auto cloned = std::make_unique<php::Func>(orig);
11113 cloned->name = name;
11114 cloned->attrs = attrs;
11115 if (!internal) cloned->attrs |= AttrTrait;
11116 cloned->cls = const_cast<php::Class*>(&dstCls);
11117 cloned->unit = dstCls.unit;
11119 if (!cloned->originalFilename) cloned->originalFilename = orig.unit;
11120 if (!cloned->originalUnit) cloned->originalUnit = orig.unit;
11121 cloned->originalClass = orig.originalClass
11122 ? orig.originalClass
11123 : orig.cls->name;
11124 cloned->originalModuleName = orig.originalModuleName;
11126 // If the "module level traits" semantics is enabled, whenever HHBBC
11127 // inlines a method from a trait defined in module A into a trait/class
11128 // defined in module B, it sets the requiresFromOriginalModule flag of the
11129 // method to true. This flag causes the originalModuleName field to be
11130 // copied in the HHVM extendedSharedData section of the method, so that
11131 // HHVM is able to resolve correctly the original module of the method.
11132 // Preserving the original module of a method is also needed when a
11133 // method is defined in an internal trait that is used by a module level
11134 // trait.
11135 const bool requiresFromOriginalModule = [&] () {
11136 bool copyFromModuleLevelTrait =
11137 orig.fromModuleLevelTrait && !orig.requiresFromOriginalModule &&
11138 orig.originalModuleName != dstCls.moduleName;
11139 bool copyFromInternal =
11140 (orig.cls->attrs & AttrInternal)
11141 && dstCls.userAttributes.count(s___ModuleLevelTrait.get());;
11143 if (Cfg::Eval::ModuleLevelTraits &&
11144 (copyFromModuleLevelTrait || copyFromInternal)) {
11145 return true;
11146 } else {
11147 return orig.requiresFromOriginalModule;
11149 }();
11150 cloned->requiresFromOriginalModule = requiresFromOriginalModule;
11152 // cloned method isn't in any method table yet, so trash its
11153 // index.
11154 cloned->clsIdx = std::numeric_limits<uint32_t>::max();
11155 return clone_closures(index, std::move(cloned), requiresFromOriginalModule, clonedClosures);
11158 static bool merge_inits(const LocalIndex& index,
11159 const php::Class& cls,
11160 const ClassInfo2& cinfo,
11161 SString name,
11162 std::vector<std::unique_ptr<php::Func>>& clones) {
11163 auto const existing = [&] () -> const php::Func* {
11164 for (auto const& m : cls.methods) {
11165 if (m->name == name) return m.get();
11167 return nullptr;
11168 }();
11170 std::unique_ptr<php::Func> cloned;
11172 auto const merge = [&] (const php::Func& f) {
11173 if (!cloned) {
11174 ClonedClosures clonedClosures;
11175 if (existing) {
11176 cloned = clone(
11177 index,
11178 *existing,
11179 existing->name,
11180 existing->attrs,
11181 cls,
11182 clonedClosures,
11183 true
11185 assertx(clonedClosures.empty());
11186 if (!cloned) return false;
11187 } else {
11188 ITRACE(4, "- cloning {}::{} as {}::{}\n",
11189 f.cls->name, f.name, cls.name, name);
11190 cloned = clone(index, f, f.name, f.attrs, cls, clonedClosures, true);
11191 assertx(clonedClosures.empty());
11192 return (bool)cloned;
11196 ITRACE(4, "- appending {}::{} into {}::{}\n",
11197 f.cls->name, f.name, cls.name, name);
11198 if (name == s_86cinit.get()) return append_86cinit(cloned.get(), f);
11199 return append_func(cloned.get(), f);
11202 for (auto const tname : cls.usedTraitNames) {
11203 auto const& trait = index.classInfo(tname);
11204 auto const it = trait.methods.find(name);
11205 if (it == trait.methods.end()) continue;
11206 auto const& meth = index.meth(it->second);
11207 if (!merge(meth)) {
11208 ITRACE(4, "merge_inits: failed to merge {}::{}\n",
11209 meth.cls->name, name);
11210 return false;
11214 if (cloned) {
11215 ITRACE(4, "merge_inits: adding {}::{} to method table\n",
11216 cloned->cls->name, cloned->name);
11217 clones.emplace_back(std::move(cloned));
11220 return true;
11223 static bool merge_xinits(const LocalIndex& index,
11224 const php::Class& cls,
11225 const ClassInfo2& cinfo,
11226 const State& state,
11227 std::vector<std::unique_ptr<php::Func>>& clones) {
11228 auto const merge_one = [&] (SString name, Attr attr) {
11229 auto const unnecessary = std::all_of(
11230 cinfo.traitProps.begin(),
11231 cinfo.traitProps.end(),
11232 [&] (const php::Prop& p) {
11233 if ((p.attrs & (AttrStatic | AttrLSB)) != attr) return true;
11234 if (p.val.m_type != KindOfUninit) return true;
11235 if (p.attrs & AttrLateInit) return true;
11236 return false;
11239 if (unnecessary) return true;
11240 return merge_inits(index, cls, cinfo, name, clones);
11243 if (!merge_one(s_86pinit.get(), AttrNone)) return false;
11244 if (!merge_one(s_86sinit.get(), AttrStatic)) return false;
11245 if (!merge_one(s_86linit.get(), AttrStatic | AttrLSB)) return false;
11247 auto const unnecessary = std::all_of(
11248 state.m_traitCns.begin(),
11249 state.m_traitCns.end(),
11250 [&] (const php::Const& c) {
11251 return !c.val || c.val->m_type != KindOfUninit;
11254 if (unnecessary) return true;
11255 return merge_inits(index, cls, cinfo, s_86cinit.get(), clones);
11258 static std::vector<std::unique_ptr<ClassInfo2>>
11259 flatten_traits(const LocalIndex& index,
11260 php::Class& cls,
11261 ClassInfo2& cinfo,
11262 State& state) {
11263 if (cls.attrs & AttrNoExpandTrait) return {};
11264 if (cls.usedTraitNames.empty()) {
11265 cls.attrs |= AttrNoExpandTrait;
11266 return {};
11269 ITRACE(4, "flatten traits: {}\n", cls.name);
11270 Trace::Indent indent;
11272 assertx(!is_closure(cls));
11274 auto traitHasConstProp = cls.hasConstProp;
11275 for (auto const tname : cls.usedTraitNames) {
11276 auto const& trait = index.cls(tname);
11277 auto const& tinfo = index.classInfo(tname);
11278 if (!(trait.attrs & AttrNoExpandTrait)) {
11279 ITRACE(4, "Not flattening {} because of {}\n", cls.name, trait.name);
11280 return {};
11282 if (is_noflatten_trait(&trait)) {
11283 ITRACE(
11284 4, "Not flattening {} because {} is annotated with __NoFlatten\n",
11285 cls.name, trait.name
11287 return {};
11289 if (tinfo.hasConstProp) traitHasConstProp = true;
11292 std::vector<std::pair<SString, MethTabEntry*>> toAdd;
11293 for (auto& [name, mte] : cinfo.methods) {
11294 if (!mte.topLevel()) continue;
11295 if (mte.meth().cls->tsame(cls.name)) continue;
11296 assertx(index.cls(mte.meth().cls).attrs & AttrTrait);
11297 toAdd.emplace_back(name, &mte);
11300 if (!toAdd.empty()) {
11301 assertx(!cinfo.extraMethods.empty());
11302 std::sort(
11303 toAdd.begin(), toAdd.end(),
11304 [&] (auto const& a, auto const& b) {
11305 return
11306 state.methodIdx(index.m_ctx->name, cinfo.name, a.first) <
11307 state.methodIdx(index.m_ctx->name, cinfo.name, b.first);
11310 } else if (debug) {
11311 // When building the ClassInfos, we proactively added all
11312 // closures from usedTraits to the extraMethods map; but now
11313 // we're going to start from the used methods, and deduce which
11314 // closures actually get pulled in. Its possible *none* of the
11315 // methods got used, in which case, we won't need their closures
11316 // either. To be safe, verify that the only things in the map
11317 // are closures.
11318 for (auto const& mte : cinfo.extraMethods) {
11319 auto const& meth = index.meth(mte);
11320 always_assert(meth.isClosureBody);
11324 std::vector<std::unique_ptr<php::Func>> clones;
11325 ClonedClosures clonedClosures;
11327 for (auto const& [name, mte] : toAdd) {
11328 auto const& meth = index.meth(*mte);
11329 auto cloned = clone(
11330 index,
11331 meth,
11332 name,
11333 mte->attrs,
11334 cls,
11335 clonedClosures
11337 if (!cloned) {
11338 ITRACE(4, "Not flattening {} because {}::{} could not be cloned\n",
11339 cls.name, mte->meth().cls, name);
11340 return {};
11342 assertx(cloned->attrs & AttrTrait);
11343 clones.emplace_back(std::move(cloned));
11346 if (!merge_xinits(index, cls, cinfo, state, clones)) {
11347 ITRACE(4, "Not flattening {} because we couldn't merge the 86xinits\n",
11348 cls.name);
11349 return {};
11352 // We're now committed to flattening.
11353 ITRACE(3, "Flattening {}\n", cls.name);
11355 if (traitHasConstProp) {
11356 assertx(cinfo.hasConstProp);
11357 cls.hasConstProp = true;
11359 cinfo.extraMethods.clear();
11361 for (auto [_, mte] : toAdd) mte->attrs |= AttrTrait;
11363 for (auto& p : cinfo.traitProps) {
11364 ITRACE(4, "- prop {}\n", p.name);
11365 cls.properties.emplace_back(std::move(p));
11366 cls.properties.back().attrs |= AttrTrait;
11368 cinfo.traitProps.clear();
11370 for (auto& c : state.m_traitCns) {
11371 ITRACE(4, "- const {}\n", c.name);
11373 auto it = cinfo.clsConstants.find(c.name);
11374 assertx(it != cinfo.clsConstants.end());
11375 auto& cnsIdx = it->second;
11377 c.cls = cls.name;
11378 state.m_cnsFromTrait.erase(c.name);
11379 cnsIdx.idx.cls = cls.name;
11380 cnsIdx.idx.idx = cls.constants.size();
11381 cls.constants.emplace_back(std::move(c));
11383 state.m_traitCns.clear();
11385 // A class should inherit any declared interfaces of any traits
11386 // that are flattened into it.
11387 for (auto const tname : cls.usedTraitNames) {
11388 auto const& tinfo = index.classInfo(tname);
11389 cinfo.classGraph.flattenTraitInto(tinfo.classGraph);
11392 // If we flatten the traits into us, they're no longer actual
11393 // parents.
11394 state.m_parents.erase(
11395 std::remove_if(
11396 begin(state.m_parents),
11397 end(state.m_parents),
11398 [] (const php::Class* c) { return bool(c->attrs & AttrTrait); }
11400 end(state.m_parents)
11403 for (auto const tname : cls.usedTraitNames) {
11404 auto const& traitState = index.state(tname);
11405 state.m_parents.insert(
11406 end(state.m_parents),
11407 begin(traitState.m_parents),
11408 end(traitState.m_parents)
11411 std::sort(
11412 begin(state.m_parents),
11413 end(state.m_parents),
11414 [] (const php::Class* a, const php::Class* b) {
11415 return string_data_lt_type{}(a->name, b->name);
11418 state.m_parents.erase(
11419 std::unique(begin(state.m_parents), end(state.m_parents)),
11420 end(state.m_parents)
11423 std::vector<std::unique_ptr<ClassInfo2>> newClosures;
11424 if (!clones.empty()) {
11425 auto const add = [&] (std::unique_ptr<php::Func> clone) {
11426 assertx(clone->cls == &cls);
11427 clone->clsIdx = cls.methods.size();
11429 if (!is_special_method_name(clone->name)) {
11430 auto it = cinfo.methods.find(clone->name);
11431 assertx(it != cinfo.methods.end());
11432 assertx(!it->second.meth().cls->tsame(cls.name));
11433 it->second.setMeth(MethRef { cls.name, clone->clsIdx });
11434 } else {
11435 auto const [existing, emplaced] =
11436 cinfo.methods.emplace(clone->name, MethTabEntry { *clone });
11437 if (!emplaced) {
11438 assertx(existing->second.meth().cls->tsame(cls.name));
11439 if (clone->name != s_86cinit.get()) {
11440 auto const idx = existing->second.meth().idx;
11441 clone->clsIdx = idx;
11442 cls.methods[idx] = std::move(clone);
11443 return;
11444 } else {
11445 existing->second.setMeth(MethRef { cls.name, clone->clsIdx });
11450 cls.methods.emplace_back(std::move(clone));
11453 auto cinit = [&] () -> std::unique_ptr<php::Func> {
11454 if (cls.methods.empty()) return nullptr;
11455 if (cls.methods.back()->name != s_86cinit.get()) return nullptr;
11456 auto init = std::move(cls.methods.back());
11457 cls.methods.pop_back();
11458 return init;
11459 }();
11461 for (auto& clone : clones) {
11462 ITRACE(4, "- meth {}\n", clone->name);
11463 if (clone->name == s_86cinit.get()) {
11464 cinit = std::move(clone);
11465 continue;
11467 add(std::move(clone));
11469 if (cinit) add(std::move(cinit));
11471 for (auto& [orig, clo] : clonedClosures) {
11472 ITRACE(4, "- closure {} as {}\n", orig->name, clo->name);
11473 assertx(is_closure(*orig));
11474 assertx(is_closure(*clo));
11475 assertx(clo->closureContextCls->tsame(cls.name));
11476 assertx(clo->unit == cls.unit);
11478 assertx(clo->usedTraitNames.empty());
11479 State cloState;
11480 auto cloinfo = make_info(index, *clo, cloState);
11481 assertx(cloinfo);
11482 assertx(cloState.m_traitCns.empty());
11483 assertx(cloState.m_cnsFromTrait.empty());
11484 assertx(cloState.m_parents.size() == 1);
11485 assertx(cloState.m_parents[0]->name->tsame(s_Closure.get()));
11487 cls.closures.emplace_back(std::move(clo));
11488 newClosures.emplace_back(std::move(cloinfo));
11492 // Flattening methods into traits can turn methods from not "first
11493 // name" to "first name", so recalculate that here.
11494 for (auto& [name, mte] : cinfo.methods) {
11495 if (mte.firstName()) continue;
11496 auto const firstName = [&, name=name] {
11497 if (cls.parentName) {
11498 auto const& parentInfo = index.classInfo(cls.parentName);
11499 if (parentInfo.methods.count(name)) return false;
11500 if (parentInfo.missingMethods.count(name)) return false;
11502 for (auto const iname : cinfo.classGraph.interfaces()) {
11503 auto const& iface = index.classInfo(iname.name());
11504 if (iface.methods.count(name)) return false;
11505 if (iface.missingMethods.count(name)) return false;
11507 return true;
11508 }();
11509 if (firstName) mte.setFirstName();
11512 struct EqHash {
11513 bool operator()(const PreClass::ClassRequirement& a,
11514 const PreClass::ClassRequirement& b) const {
11515 return a.is_same(&b);
11517 size_t operator()(const PreClass::ClassRequirement& a) const {
11518 return a.hash();
11521 hphp_fast_set<PreClass::ClassRequirement, EqHash, EqHash> reqs{
11522 cls.requirements.begin(),
11523 cls.requirements.end()
11526 for (auto const tname : cls.usedTraitNames) {
11527 auto const& trait = index.cls(tname);
11528 for (auto const& req : trait.requirements) {
11529 if (reqs.emplace(req).second) cls.requirements.emplace_back(req);
11533 cls.attrs |= AttrNoExpandTrait;
11534 return newClosures;
11537 static std::unique_ptr<FuncInfo2> make_func_info(const LocalIndex& index,
11538 const php::Func& f) {
11539 auto finfo = std::make_unique<FuncInfo2>();
11540 finfo->name = f.name;
11541 return finfo;
11544 static bool resolve_one(TypeConstraint& tc,
11545 const TypeConstraint& tv,
11546 SString firstEnum,
11547 TSStringSet* uses,
11548 bool isProp,
11549 bool isUnion) {
11550 assertx(!tv.isUnion());
11551 // Whatever it's an alias of isn't valid, so leave unresolved.
11552 if (tv.isUnresolved()) return false;
11553 if (isProp && !propSupportsAnnot(tv.type())) return false;
11554 auto const value = [&] () -> SString {
11555 // Store the first enum encountered during resolution. This
11556 // lets us fixup the type later if needed.
11557 if (firstEnum) return firstEnum;
11558 if (tv.isSubObject()) {
11559 auto clsName = tv.clsName();
11560 assertx(clsName);
11561 return clsName;
11563 return nullptr;
11564 }();
11565 if (isUnion) tc.unresolve();
11566 tc.resolveType(tv.type(), tv.isNullable(), value);
11567 assertx(IMPLIES(isProp, tc.validForProp()));
11568 if (uses && value) uses->emplace(value);
11569 return true;
11572 // Update a type constraint to it's ultimate type, or leave it as
11573 // unresolved if it resolves to nothing valid. Record the new type
11574 // in case it needs to be fixed up later.
11575 static void update_type_constraint(const LocalIndex& index,
11576 TypeConstraint& tc,
11577 bool isProp,
11578 TSStringSet* uses) {
11579 always_assert(IMPLIES(isProp, tc.validForProp()));
11581 if (!tc.isUnresolved()) {
11582 // Any TC already resolved is assumed to be correct.
11583 if (uses) {
11584 for (auto& part : eachTypeConstraintInUnion(tc)) {
11585 if (auto clsName = part.clsName()) {
11586 uses->emplace(clsName);
11590 return;
11592 auto const name = tc.typeName();
11594 if (tc.isUnion()) {
11595 // This is a union that contains unresolved names.
11596 not_implemented(); // TODO(T151885113)
11599 // This is an unresolved name that can resolve to either a single type or
11600 // a union.
11602 // Is this name a type-alias or enum?
11603 if (auto const tm = index.typeMapping(name)) {
11604 if (tm->value.isUnion()) {
11605 auto flags =
11606 tc.flags() & (TypeConstraintFlags::Nullable
11607 | TypeConstraintFlags::TypeVar
11608 | TypeConstraintFlags::Soft
11609 | TypeConstraintFlags::TypeConstant
11610 | TypeConstraintFlags::DisplayNullable
11611 | TypeConstraintFlags::UpperBound);
11612 std::vector<TypeConstraint> members;
11613 for (auto& tv : eachTypeConstraintInUnion(tm->value)) {
11614 TypeConstraint copy = tv;
11615 copy.addFlags(flags);
11616 if (!resolve_one(copy, tv, tm->firstEnum, uses, isProp, true)) {
11617 return;
11619 members.emplace_back(std::move(copy));
11621 tc = TypeConstraint::makeUnion(name, members);
11622 return;
11625 // This unresolved name resolves to a single type.
11626 assertx(!tm->value.isUnion());
11627 resolve_one(tc, tm->value, tm->firstEnum, uses, isProp, false);
11628 return;
11631 // Not a type-alias or enum. If it's explicitly marked as missing,
11632 // leave it unresolved. Otherwise assume it's an object with that
11633 // name.
11634 if (index.missingType(name)) return;
11635 tc.resolveType(AnnotType::SubObject, tc.isNullable(), name);
11636 if (uses) uses->emplace(name);
11639 static void update_type_constraints(const LocalIndex& index,
11640 php::Func& func,
11641 TSStringSet* uses) {
11642 for (auto& p : func.params) {
11643 update_type_constraint(index, p.typeConstraint, false, uses);
11644 for (auto& ub : p.upperBounds.m_constraints) {
11645 update_type_constraint(index, ub, false, uses);
11648 update_type_constraint(index, func.retTypeConstraint, false, uses);
11649 for (auto& ub : func.returnUBs.m_constraints) {
11650 update_type_constraint(index, ub, false, uses);
11654 static void update_type_constraints(const LocalIndex& index,
11655 php::Class& cls,
11656 TSStringSet* uses) {
11657 if (cls.attrs & AttrEnum) {
11658 update_type_constraint(index, cls.enumBaseTy, false, uses);
11660 for (auto& meth : cls.methods) update_type_constraints(index, *meth, uses);
11661 for (auto& prop : cls.properties) {
11662 update_type_constraint(index, prop.typeConstraint, true, uses);
11663 for (auto& ub : prop.ubs.m_constraints) {
11664 update_type_constraint(index, ub, true, uses);
11670 * Mark any properties in cls that definitely do not redeclare a
11671 * property in the parent with an inequivalent type-hint.
11673 * Rewrite the initial values for any AttrSystemInitialValue
11674 * properties. If the properties' type-hint does not admit null
11675 * values, change the initial value to one that is not null
11676 * (if possible). This is only safe to do so if the property is not
11677 * redeclared in a derived class or if the redeclaration does not
11678 * have a null system provided default value. Otherwise, a property
11679 * can have a null value (even if its type-hint doesn't allow it)
11680 * without the JIT realizing that its possible.
11682 * Note that this ignores any unflattened traits. This is okay
11683 * because properties pulled in from traits which match an already
11684 * existing property can't change the initial value. The runtime
11685 * will clear AttrNoImplicitNullable on any property pulled from the
11686 * trait if it doesn't match an existing property.
11688 static void optimize_properties(const LocalIndex& index,
11689 php::Class& cls,
11690 ClassInfo2& cinfo) {
11691 assertx(cinfo.hasBadRedeclareProp);
11693 auto const isClosure = is_closure(cls);
11695 cinfo.hasBadRedeclareProp = false;
11696 for (auto& prop : cls.properties) {
11697 assertx(!(prop.attrs & AttrNoBadRedeclare));
11698 assertx(!(prop.attrs & AttrNoImplicitNullable));
11700 auto const noBadRedeclare = [&] {
11701 // Closures should never have redeclared properties.
11702 if (isClosure) return true;
11703 // Static and private properties never redeclare anything so
11704 // need not be considered.
11705 if (prop.attrs & (AttrStatic | AttrPrivate)) return true;
11707 for (auto const base : cinfo.classGraph.bases()) {
11708 if (base.name()->tsame(cls.name)) continue;
11710 auto& baseCInfo = index.classInfo(base.name());
11711 auto& baseCls = index.cls(base.name());
11713 auto const parentProp = [&] () -> php::Prop* {
11714 for (auto& p : baseCls.properties) {
11715 if (p.name == prop.name) return const_cast<php::Prop*>(&p);
11717 for (auto& p : baseCInfo.traitProps) {
11718 if (p.name == prop.name) return const_cast<php::Prop*>(&p);
11720 return nullptr;
11721 }();
11722 if (!parentProp) continue;
11723 if (parentProp->attrs & (AttrStatic | AttrPrivate)) continue;
11725 // This property's type-constraint might not have been
11726 // resolved (if the parent is not on the output list for
11727 // this job), so do so here.
11728 update_type_constraint(
11729 index,
11730 parentProp->typeConstraint,
11731 true,
11732 nullptr
11735 // This check is safe, but conservative. It might miss a few
11736 // rare cases, but it's sufficient and doesn't require class
11737 // hierarchies.
11738 if (prop.typeConstraint.maybeInequivalentForProp(
11739 parentProp->typeConstraint
11740 )) {
11741 return false;
11744 for (auto const& ub : prop.ubs.m_constraints) {
11745 for (auto& pub : parentProp->ubs.m_constraints) {
11746 update_type_constraint(index, pub, true, nullptr);
11747 if (ub.maybeInequivalentForProp(pub)) return false;
11752 return true;
11753 }();
11755 if (noBadRedeclare) {
11756 attribute_setter(prop.attrs, true, AttrNoBadRedeclare);
11757 } else {
11758 cinfo.hasBadRedeclareProp = true;
11761 auto const nullable = [&] {
11762 if (isClosure) return true;
11763 if (!(prop.attrs & AttrSystemInitialValue)) return false;
11764 return prop.typeConstraint.defaultValue().m_type == KindOfNull;
11765 }();
11767 attribute_setter(prop.attrs, !nullable, AttrNoImplicitNullable);
11768 if (!(prop.attrs & AttrSystemInitialValue)) continue;
11769 if (prop.val.m_type == KindOfUninit) {
11770 assertx(isClosure || bool(prop.attrs & AttrLateInit));
11771 continue;
11774 prop.val = [&] {
11775 if (nullable) return make_tv<KindOfNull>();
11776 // Give the 86reified_prop a special default value to avoid
11777 // pessimizing the inferred type (we want it to always be a
11778 // vec of a specific size).
11779 if (prop.name == s_86reified_prop.get()) {
11780 return get_default_value_of_reified_list(cls.userAttributes);
11782 return prop.typeConstraint.defaultValue();
11783 }();
11789 Job<FlattenJob> s_flattenJob;
11792 * For efficiency reasons, we want to do class flattening all in one
11793 * pass. So, we use assign_hierarchical_work (described above) to
11794 * calculate work buckets to allow us to do this.
11796 * - The "root" classes are the leaf classes in the hierarchy. These are
11797 * the buckets which are not dependencies of anything.
11799 * - The dependencies of a class are all of the (transitive) parent
11800 * classes of that class (up to the top classes with no parents).
11802 * - Each job takes two kinds of input. The first is the set of
11803 * classes which are actually to be flattened. These will have the
11804 * flattening results returned as output from the job. The second is
11805 * the set of dependencies that are required to perform flattening
11806 * on the first set of inputs. These will have the same flattening
11807 * algorithm applied to them, but only to obtain intermediate state
11808 * to calculate the output for the first set of inputs. Their
11809 * results will be thrown away.
11811 * - When we run the jobs, we'll take the outputs and turn that into a set of
11812 * updates, which we then apply to the Index data structures. Some
11813 * of these updates require changes to the php::Unit, which we do a
11814 * in separate set of "fixup" jobs at the end.
11817 // Input class metadata to be turned into work buckets.
11818 struct IndexFlattenMetadata {
11819 struct ClassMeta {
11820 TSStringSet deps;
11821 // All types mentioned in type-constraints in this class.
11822 std::vector<SString> unresolvedTypes;
11823 size_t idx; // Index into allCls vector
11824 bool isClosure{false};
11825 bool uninstantiable{false};
11827 TSStringToOneT<ClassMeta> cls;
11828 // All classes to be flattened
11829 std::vector<SString> allCls;
11830 // Mapping of units to classes which should be deleted from that
11831 // unit. This is typically from duplicate meth callers. This is
11832 // performed as part of "fixing up" the unit after flattening
11833 // because it's convenient to do so there.
11834 SStringToOneT<std::vector<SString>> unitDeletions;
11835 struct FuncMeta {
11836 // All types mentioned in type-constraints in this func.
11837 std::vector<SString> unresolvedTypes;
11839 FSStringToOneT<FuncMeta> func;
11840 std::vector<SString> allFuncs;
11841 TSStringToOneT<TypeMapping> typeMappings;
11844 //////////////////////////////////////////////////////////////////////
11846 constexpr size_t kNumTypeMappingRounds = 20;
11849 * Update the type-mappings in the program so they all point to their
11850 * ultimate type. After this step, every type-mapping that still has
11851 * an unresolved type points to an invalid type.
11853 void flatten_type_mappings(IndexData& index,
11854 IndexFlattenMetadata& meta) {
11855 trace_time tracer{"flatten type mappings"};
11856 tracer.ignore_client_stats();
11858 std::vector<const TypeMapping*> work;
11859 work.reserve(meta.typeMappings.size());
11860 for (auto const& [_, tm] : meta.typeMappings) work.emplace_back(&tm);
11862 auto resolved = parallel::map(
11863 work,
11864 [&] (const TypeMapping* typeMapping) {
11865 Optional<TSStringSet> seen;
11866 TypeConstraintFlags flags =
11867 typeMapping->value.flags() & (TypeConstraintFlags::Nullable
11868 | TypeConstraintFlags::TypeVar
11869 | TypeConstraintFlags::Soft
11870 | TypeConstraintFlags::TypeConstant
11871 | TypeConstraintFlags::DisplayNullable
11872 | TypeConstraintFlags::UpperBound);
11873 auto firstEnum = typeMapping->firstEnum;
11874 auto const isUnion = typeMapping->value.isUnion();
11875 bool anyUnresolved = false;
11877 auto enumMeta = folly::get_ptr(meta.cls, typeMapping->name);
11879 std::vector<TypeConstraint> tvu;
11881 for (auto const& tc : eachTypeConstraintInUnion(typeMapping->value)) {
11882 const auto type = tc.type();
11883 const auto value = tc.typeName();
11884 auto name = value;
11885 LSString curEnum;
11887 if (type != AnnotType::Unresolved) {
11888 // If the type-mapping is already resolved, we mainly take it
11889 // as is. The exception is if it's an enum, in which case we
11890 // validate the underlying base type.
11891 assertx(type != AnnotType::SubObject);
11892 if (!enumMeta) {
11893 tvu.emplace_back(tc);
11894 continue;
11896 if (!enumSupportsAnnot(type)) {
11897 FTRACE(
11898 2, "Type-mapping '{}' is invalid because it resolves to "
11899 "invalid enum type {}\n",
11900 typeMapping->name,
11901 annotName(type)
11903 tvu.emplace_back(AnnotType::Unresolved, tc.flags(), value);
11904 continue;
11906 tvu.emplace_back(type, tc.flags() | flags, value);
11907 anyUnresolved = true;
11908 continue;
11911 std::queue<LSString> queue;
11912 queue.push(name);
11914 for (size_t rounds = 0;; ++rounds) {
11915 if (queue.empty()) break;
11916 name = normalizeNS(queue.back());
11917 queue.pop();
11919 if (auto const next = folly::get_ptr(meta.typeMappings, name)) {
11920 flags |= next->value.flags() & (TypeConstraintFlags::Nullable
11921 | TypeConstraintFlags::TypeVar
11922 | TypeConstraintFlags::Soft
11923 | TypeConstraintFlags::TypeConstant
11924 | TypeConstraintFlags::DisplayNullable
11925 | TypeConstraintFlags::UpperBound);
11926 auto const nextEnum = next->firstEnum;
11927 if (!curEnum) curEnum = nextEnum;
11928 if (!firstEnum && !isUnion) firstEnum = curEnum;
11930 if (enumMeta && nextEnum) {
11931 enumMeta->deps.emplace(nextEnum);
11934 for (auto const& next_tc : eachTypeConstraintInUnion(next->value)) {
11935 auto next_type = next_tc.type();
11936 auto next_value = next_tc.typeName();
11937 if (next_type == AnnotType::Unresolved) {
11938 queue.push(next_value);
11939 continue;
11941 assertx(next_type != AnnotType::SubObject);
11942 if (curEnum && !enumSupportsAnnot(next_type)) {
11943 FTRACE(
11944 2, "Type-mapping '{}' is invalid because it resolves to "
11945 "invalid enum type {}{}\n",
11946 typeMapping->name,
11947 annotName(next_type),
11948 curEnum->tsame(typeMapping->name)
11949 ? "" : folly::sformat(" (via {})", curEnum)
11951 tvu.emplace_back(AnnotType::Unresolved, tc.flags() | flags, name);
11952 anyUnresolved = true;
11953 continue;
11955 tvu.emplace_back(next_type, tc.flags() | flags, next_value);
11957 } else if (index.classRefs.count(name)) {
11958 if (curEnum) {
11959 FTRACE(
11960 2, "Type-mapping '{}' is invalid because it resolves to "
11961 "invalid object '{}' for enum type (via {})\n",
11962 typeMapping->name,
11963 name,
11964 curEnum
11968 tvu.emplace_back(
11969 curEnum ? AnnotType::Unresolved : AnnotType::SubObject,
11970 tc.flags() | flags,
11971 name
11973 if (curEnum) anyUnresolved = true;
11974 break;
11975 } else {
11976 FTRACE(
11977 2, "Type-mapping '{}' is invalid because it involves "
11978 "non-existent type '{}'{}\n",
11979 typeMapping->name,
11980 name,
11981 (curEnum && !curEnum->tsame(typeMapping->name))
11982 ? folly::sformat(" (via {})", curEnum) : ""
11984 tvu.emplace_back(AnnotType::Unresolved, tc.flags() | flags, name);
11985 anyUnresolved = true;
11986 break;
11989 // Deal with cycles. Since we don't expect to encounter them, just
11990 // use a counter until we hit a chain length of kNumTypeMappingRounds,
11991 // then start tracking the names we resolve.
11992 if (rounds == kNumTypeMappingRounds) {
11993 seen.emplace();
11994 seen->insert(name);
11995 } else if (rounds > kNumTypeMappingRounds) {
11996 if (!seen->insert(name).second) {
11997 FTRACE(
11998 2, "Type-mapping '{}' is invalid because it's definition "
11999 "is circular with '{}'\n",
12000 typeMapping->name,
12001 name
12003 return TypeMapping {
12004 typeMapping->name,
12005 firstEnum,
12006 TypeConstraint{AnnotType::Unresolved, flags, name},
12012 if (isUnion && anyUnresolved) {
12013 // Unions cannot contain a mix of resolved an unresolved class names so
12014 // if one of the names failed to resolve we must mark all of them as
12015 // unresolved.
12016 for (auto& tc : tvu) if (tc.isSubObject()) tc.unresolve();
12018 assertx(!tvu.empty());
12019 // If any of the subtypes end up unresolved then the final union will also
12020 // be unresolved. But it's important to try the `makeUnion` anyway because
12021 // it will deal with some of the canonicalizations like `bool`.
12022 auto value = TypeConstraint::makeUnion(typeMapping->name, std::move(tvu));
12023 return TypeMapping { typeMapping->name, firstEnum, value };
12027 for (auto& after : resolved) {
12028 auto const name = after.name;
12029 using namespace folly::gen;
12030 FTRACE(
12031 4, "Type-mapping '{}' flattened to {}{}\n",
12032 name,
12033 after.value.debugName(),
12034 (after.firstEnum && !after.firstEnum->tsame(name))
12035 ? folly::sformat(" (via {})", after.firstEnum) : ""
12037 if (after.value.isUnresolved() && meta.cls.count(name)) {
12038 FTRACE(4, " Marking enum '{}' as uninstantiable\n", name);
12039 meta.cls.at(name).uninstantiable = true;
12041 meta.typeMappings.at(name) = std::move(after);
12045 //////////////////////////////////////////////////////////////////////
12047 struct FlattenClassesWork {
12048 std::vector<SString> classes;
12049 std::vector<SString> deps;
12050 std::vector<SString> funcs;
12051 std::vector<SString> uninstantiable;
12054 std::vector<FlattenClassesWork>
12055 flatten_classes_assign(IndexFlattenMetadata& meta) {
12056 trace_time trace{"flatten classes assign"};
12057 trace.ignore_client_stats();
12059 // First calculate the classes which *aren't* leafs. A class is a
12060 // leaf if it is not depended on by another class. The sense is
12061 // inverted because we want to default construct the atomics.
12062 std::vector<std::atomic<bool>> isNotLeaf(meta.allCls.size());
12063 parallel::for_each(
12064 meta.allCls,
12065 [&] (SString cls) {
12066 auto const& clsMeta = meta.cls.at(cls);
12067 for (auto const d : clsMeta.deps) {
12068 auto const it = meta.cls.find(d);
12069 if (it == meta.cls.end()) continue;
12070 assertx(it->second.idx < isNotLeaf.size());
12071 isNotLeaf[it->second.idx] = true;
12076 // Store all of the (transitive) dependencies for every class,
12077 // calculated lazily. LockFreeLazy ensures that multiple classes can
12078 // access this concurrently and safely calculate it on demand.
12079 struct DepLookup {
12080 TSStringSet deps;
12081 // Whether this class is instantiable
12082 bool instantiable{false};
12084 std::vector<LockFreeLazy<DepLookup>> allDeps{meta.allCls.size()};
12086 // Look up all of the transitive dependencies for the given class.
12087 auto const findAllDeps = [&] (SString cls,
12088 TSStringSet& visited,
12089 auto const& self) -> const DepLookup& {
12090 static const DepLookup empty;
12092 auto const it = meta.cls.find(cls);
12093 if (it == meta.cls.end()) {
12094 FTRACE(
12095 4, "{} is not instantiable because it is missing\n",
12098 return empty;
12101 // The class exists, so look up it's dependency information.
12102 auto const idx = it->second.idx;
12103 auto const& deps = it->second.deps;
12105 // Check for cycles. A class involved in cyclic inheritance is not
12106 // instantiable (and has no dependencies). This needs to be done
12107 // before accessing the LockFreeLazy below, because if we are in a
12108 // cycle, we'll deadlock when we do so.
12109 auto const emplaced = visited.emplace(cls).second;
12110 if (!emplaced) {
12111 FTRACE(
12112 4, "{} is not instantiable because it forms a dependency "
12113 "cycle with itself\n", cls
12115 it->second.uninstantiable = true;
12116 return empty;
12118 SCOPE_EXIT { visited.erase(cls); };
12120 assertx(idx < allDeps.size());
12121 return allDeps[idx].get(
12122 [&] {
12123 // Otherwise get all of the transitive dependencies of it's
12124 // dependencies and combine them.
12125 DepLookup out;
12126 out.instantiable = !it->second.uninstantiable;
12128 for (auto const d : deps) {
12129 auto const& lookup = self(d, visited, self);
12130 if (lookup.instantiable || meta.cls.count(d)) {
12131 out.deps.emplace(d);
12133 out.deps.insert(begin(lookup.deps), end(lookup.deps));
12134 if (lookup.instantiable) continue;
12135 // If the dependency is not instantiable, this isn't
12136 // either. Note, however, we still need to preserve the
12137 // already gathered dependencies, since they'll have to be
12138 // placed in some bucket.
12139 if (out.instantiable) {
12140 FTRACE(
12141 4, "{} is not instantiable because it depends on {}, "
12142 "which is not instantiable\n",
12143 cls, d
12145 it->second.uninstantiable = true;
12147 out.instantiable = false;
12150 return out;
12155 constexpr size_t kBucketSize = 2000;
12156 constexpr size_t kMaxBucketSize = 30000;
12158 auto assignments = assign_hierarchical_work(
12159 [&] {
12160 std::vector<SString> l;
12161 auto const size = meta.allCls.size();
12162 assertx(size == isNotLeaf.size());
12163 l.reserve(size);
12164 for (size_t i = 0; i < size; ++i) {
12165 if (!isNotLeaf[i]) l.emplace_back(meta.allCls[i]);
12167 return l;
12168 }(),
12169 meta.allCls.size(),
12170 kBucketSize,
12171 kMaxBucketSize,
12172 [&] (SString c) {
12173 TSStringSet visited;
12174 auto const& lookup = findAllDeps(c, visited, findAllDeps);
12175 return std::make_pair(&lookup.deps, lookup.instantiable);
12177 [&] (const TSStringSet&, size_t, SString c) -> Optional<size_t> {
12178 return meta.cls.at(c).idx;
12182 // Bucketize functions separately
12184 constexpr size_t kFuncBucketSize = 5000;
12186 auto funcBuckets = consistently_bucketize(meta.allFuncs, kFuncBucketSize);
12188 std::vector<FlattenClassesWork> work;
12189 // If both the class and func assignments map to a single bucket,
12190 // combine them both together. This is an optimization for things
12191 // like unit tests, where the total amount of work is low and we
12192 // want to run it all in a single job if possible.
12193 if (assignments.size() == 1 && funcBuckets.size() == 1) {
12194 work.emplace_back(
12195 FlattenClassesWork{
12196 std::move(assignments[0].classes),
12197 std::move(assignments[0].deps),
12198 std::move(funcBuckets[0]),
12199 std::move(assignments[0].uninstantiable)
12202 } else {
12203 // Otherwise split the classes and func work.
12204 work.reserve(assignments.size() + funcBuckets.size());
12205 for (auto& assignment : assignments) {
12206 work.emplace_back(
12207 FlattenClassesWork{
12208 std::move(assignment.classes),
12209 std::move(assignment.deps),
12211 std::move(assignment.uninstantiable)
12215 for (auto& bucket : funcBuckets) {
12216 work.emplace_back(
12217 FlattenClassesWork{ {}, {}, std::move(bucket), {} }
12222 if (Trace::moduleEnabled(Trace::hhbbc_index, 5)) {
12223 for (size_t i = 0; i < work.size(); ++i) {
12224 auto const& [classes, deps, funcs, uninstantiable] = work[i];
12225 FTRACE(5, "flatten work item #{}:\n", i);
12226 FTRACE(5, " classes ({}):\n", classes.size());
12227 for (auto const DEBUG_ONLY c : classes) FTRACE(5, " {}\n", c);
12228 FTRACE(5, " deps ({}):\n", deps.size());
12229 for (auto const DEBUG_ONLY d : deps) FTRACE(5, " {}\n", d);
12230 FTRACE(5, " funcs ({}):\n", funcs.size());
12231 for (auto const DEBUG_ONLY f : funcs) FTRACE(5, " {}\n", f);
12232 FTRACE(5, " uninstantiable classes ({}):\n", uninstantiable.size());
12233 for (auto const DEBUG_ONLY c : uninstantiable) FTRACE(5, " {}\n", c);
12237 return work;
12240 // Metadata used to assign work buckets for building subclasses. This
12241 // is produced from flattening classes. We don't put closures (or
12242 // Closure base class) into here. There's a lot of them, but we can
12243 // predict their results without running build subclass pass on them.
12244 struct SubclassMetadata {
12245 // Immediate children and parents of class (not transitive!).
12246 struct Meta {
12247 std::vector<SString> children;
12248 std::vector<SString> parents;
12249 size_t idx; // Index into all classes vector.
12251 TSStringToOneT<Meta> meta;
12252 // All classes to be processed
12253 std::vector<SString> all;
12256 // Metadata used to drive the init-types pass. This is produced from
12257 // flattening classes and added to when building subclasses.
12258 struct InitTypesMetadata {
12259 struct ClsMeta {
12260 // Dependencies of the class. A dependency is a class in a
12261 // property/param/return type-hint.
12262 TSStringSet deps;
12263 TSStringSet candidateRegOnlyEquivs;
12265 struct FuncMeta {
12266 // Same as ClsMeta, but for the func
12267 TSStringSet deps;
12269 // Modifications to make to an unit
12270 struct Fixup {
12271 std::vector<SString> addClass;
12272 std::vector<SString> removeFunc;
12273 template <typename SerDe> void serde(SerDe& sd) {
12274 sd(addClass)(removeFunc);
12277 TSStringToOneT<ClsMeta> classes;
12278 FSStringToOneT<FuncMeta> funcs;
12279 SStringToOneT<Fixup> fixups;
12280 SStringToOneT<std::vector<FuncFamilyEntry>> nameOnlyFF;
12283 std::tuple<SubclassMetadata, InitTypesMetadata, std::vector<InterfaceConflicts>>
12284 flatten_classes(IndexData& index, IndexFlattenMetadata meta) {
12285 trace_time trace("flatten classes", index.sample);
12286 trace.ignore_client_stats();
12288 using namespace folly::gen;
12290 struct ClassUpdate {
12291 SString name;
12292 UniquePtrRef<php::Class> cls;
12293 UniquePtrRef<php::ClassBytecode> bytecode;
12294 UniquePtrRef<ClassInfo2> cinfo;
12295 SString unitToAddTo;
12296 TSStringSet typeUses;
12297 bool isInterface{false};
12298 bool has86init{false};
12299 CompactVector<SString> parents;
12301 struct FuncUpdate {
12302 SString name;
12303 UniquePtrRef<php::Func> func;
12304 UniquePtrRef<FuncInfo2> finfo;
12305 TSStringSet typeUses;
12307 struct ClosureUpdate {
12308 SString name;
12309 SString context;
12310 SString unit;
12312 struct MethodUpdate {
12313 SString name;
12314 UniquePtrRef<MethodsWithoutCInfo> methods;
12316 using Update =
12317 boost::variant<ClassUpdate, FuncUpdate, ClosureUpdate, MethodUpdate>;
12318 using UpdateVec = std::vector<Update>;
12320 tbb::concurrent_hash_map<
12321 SString,
12322 InterfaceConflicts,
12323 string_data_hash_tsame
12324 > ifaceConflicts;
12326 auto const run = [&] (FlattenClassesWork work) -> coro::Task<UpdateVec> {
12327 co_await coro::co_reschedule_on_current_executor;
12329 if (work.classes.empty() &&
12330 work.funcs.empty() &&
12331 work.uninstantiable.empty()) {
12332 assertx(work.deps.empty());
12333 co_return UpdateVec{};
12336 Client::ExecMetadata metadata{
12337 .job_key = folly::sformat(
12338 "flatten classes {}",
12339 work.classes.empty()
12340 ? (work.uninstantiable.empty()
12341 ? work.funcs[0]
12342 : work.uninstantiable[0])
12343 : work.classes[0]
12346 auto classes = from(work.classes)
12347 | map([&] (SString c) { return index.classRefs.at(c); })
12348 | as<std::vector>();
12349 auto deps = from(work.deps)
12350 | map([&] (SString c) { return index.classRefs.at(c); })
12351 | as<std::vector>();
12352 auto bytecode = (from(work.classes) + from(work.deps))
12353 | map([&] (SString c) { return index.classBytecodeRefs.at(c); })
12354 | as<std::vector>();
12355 auto funcs = from(work.funcs)
12356 | map([&] (SString f) { return index.funcRefs.at(f); })
12357 | as<std::vector>();
12358 auto uninstantiableRefs = from(work.uninstantiable)
12359 | map([&] (SString c) { return index.classRefs.at(c); })
12360 | as<std::vector>();
12362 // Gather any type-mappings or missing types referenced by these
12363 // classes or funcs.
12364 std::vector<TypeMapping> typeMappings;
12365 std::vector<SString> missingTypes;
12367 TSStringSet seen;
12369 auto const addUnresolved = [&] (SString u) {
12370 if (!seen.emplace(u).second) return;
12371 if (auto const m = folly::get_ptr(meta.typeMappings, u)) {
12372 // If the type-mapping maps an enum, and that enum is
12373 // uninstantiable, just treat it as a missing type.
12374 if (m->firstEnum && meta.cls.at(m->firstEnum).uninstantiable) {
12375 missingTypes.emplace_back(u);
12376 } else {
12377 typeMappings.emplace_back(*m);
12379 } else if (!index.classRefs.count(u) ||
12380 meta.cls.at(u).uninstantiable) {
12381 missingTypes.emplace_back(u);
12385 auto const addClass = [&] (SString c) {
12386 for (auto const u : meta.cls.at(c).unresolvedTypes) addUnresolved(u);
12388 auto const addFunc = [&] (SString f) {
12389 for (auto const u : meta.func.at(f).unresolvedTypes) addUnresolved(u);
12392 for (auto const c : work.classes) addClass(c);
12393 for (auto const d : work.deps) addClass(d);
12394 for (auto const f : work.funcs) addFunc(f);
12396 std::sort(
12397 begin(typeMappings), end(typeMappings),
12398 [] (const TypeMapping& a, const TypeMapping& b) {
12399 return string_data_lt_type{}(a.name, b.name);
12402 std::sort(begin(missingTypes), end(missingTypes), string_data_lt_type{});
12405 auto [typeMappingsRef, missingTypesRef, config] = co_await
12406 coro::collectAll(
12407 index.client->store(std::move(typeMappings)),
12408 index.client->store(std::move(missingTypes)),
12409 index.configRef->getCopy()
12412 auto results = co_await
12413 index.client->exec(
12414 s_flattenJob,
12415 std::move(config),
12416 singleton_vec(
12417 std::make_tuple(
12418 std::move(classes),
12419 std::move(deps),
12420 std::move(bytecode),
12421 std::move(funcs),
12422 std::move(uninstantiableRefs),
12423 std::move(typeMappingsRef),
12424 std::move(missingTypesRef)
12427 std::move(metadata)
12429 // Every flattening job is a single work-unit, so we should only
12430 // ever get one result for each one.
12431 assertx(results.size() == 1);
12432 auto& [clsRefs, bytecodeRefs, cinfoRefs, funcRefs,
12433 finfoRefs, methodRefs, classMetaRef] = results[0];
12434 assertx(clsRefs.size() == cinfoRefs.size());
12435 assertx(clsRefs.size() == bytecodeRefs.size());
12436 assertx(funcRefs.size() == work.funcs.size());
12437 assertx(funcRefs.size() == finfoRefs.size());
12439 // We need the output metadata, but everything else stays
12440 // uploaded.
12441 auto clsMeta = co_await index.client->load(std::move(classMetaRef));
12442 assertx(methodRefs.size() == clsMeta.uninstantiable.size());
12444 // Create the updates by combining the job output (but skipping
12445 // over uninstantiable classes).
12446 UpdateVec updates;
12447 updates.reserve(work.classes.size() * 3);
12449 size_t outputIdx = 0;
12450 size_t parentIdx = 0;
12451 size_t methodIdx = 0;
12452 for (auto const name : work.classes) {
12453 if (clsMeta.uninstantiable.count(name)) {
12454 assertx(methodIdx < methodRefs.size());
12455 updates.emplace_back(
12456 MethodUpdate{ name, std::move(methodRefs[methodIdx]) }
12458 ++methodIdx;
12459 continue;
12461 assertx(outputIdx < clsRefs.size());
12462 assertx(outputIdx < clsMeta.classTypeUses.size());
12464 auto const& flattenMeta = meta.cls.at(name);
12465 updates.emplace_back(
12466 ClassUpdate{
12467 name,
12468 std::move(clsRefs[outputIdx]),
12469 std::move(bytecodeRefs[outputIdx]),
12470 std::move(cinfoRefs[outputIdx]),
12471 nullptr,
12472 std::move(clsMeta.classTypeUses[outputIdx]),
12473 (bool)clsMeta.interfaces.count(name),
12474 (bool)clsMeta.with86init.count(name)
12478 // Ignore closures. We don't run the build subclass pass for
12479 // closures, so we don't need information for them.
12480 if (!flattenMeta.isClosure) {
12481 assertx(parentIdx < clsMeta.parents.size());
12482 auto const& parents = clsMeta.parents[parentIdx].names;
12483 auto& update = boost::get<ClassUpdate>(updates.back());
12484 update.parents.insert(
12485 end(update.parents), begin(parents), end(parents)
12487 ++parentIdx;
12490 ++outputIdx;
12492 assertx(outputIdx == clsRefs.size());
12493 assertx(outputIdx == clsMeta.classTypeUses.size());
12495 for (auto const& [unit, name, context] : clsMeta.newClosures) {
12496 updates.emplace_back(ClosureUpdate{ name, context, unit });
12499 for (auto const name : work.uninstantiable) {
12500 assertx(clsMeta.uninstantiable.count(name));
12501 assertx(methodIdx < methodRefs.size());
12502 updates.emplace_back(
12503 MethodUpdate{ name, std::move(methodRefs[methodIdx]) }
12505 ++methodIdx;
12507 assertx(methodIdx == methodRefs.size());
12509 assertx(work.funcs.size() == clsMeta.funcTypeUses.size());
12510 for (size_t i = 0, size = work.funcs.size(); i < size; ++i) {
12511 updates.emplace_back(
12512 FuncUpdate{
12513 work.funcs[i],
12514 std::move(funcRefs[i]),
12515 std::move(finfoRefs[i]),
12516 std::move(clsMeta.funcTypeUses[i])
12521 for (auto const& c : clsMeta.interfaceConflicts) {
12522 decltype(ifaceConflicts)::accessor acc;
12523 ifaceConflicts.insert(acc, c.name);
12524 acc->second.name = c.name;
12525 acc->second.usage += c.usage;
12526 acc->second.conflicts.insert(begin(c.conflicts), end(c.conflicts));
12529 co_return updates;
12532 // Calculate the grouping of classes into work units for flattening,
12533 // perform the flattening, and gather all updates from the jobs.
12534 auto allUpdates = [&] {
12535 auto assignments = flatten_classes_assign(meta);
12537 trace_time trace2("flatten classes work", index.sample);
12538 return coro::blockingWait(coro::collectAllRange(
12539 from(assignments)
12540 | move
12541 | map([&] (FlattenClassesWork w) {
12542 return run(std::move(w)).scheduleOn(index.executor->sticky());
12544 | as<std::vector>()
12546 }();
12548 // Now take the updates and apply them to the Index tables. This
12549 // needs to be done in a single threaded context (per data
12550 // structure). This also gathers up all the fixups needed.
12552 SubclassMetadata subclassMeta;
12553 InitTypesMetadata initTypesMeta;
12556 trace_time trace2("flatten classes update");
12557 trace2.ignore_client_stats();
12559 parallel::parallel(
12560 [&] {
12561 for (auto& updates : allUpdates) {
12562 for (auto& update : updates) {
12563 auto u = boost::get<ClassUpdate>(&update);
12564 if (!u) continue;
12565 index.classRefs.insert_or_assign(
12566 u->name,
12567 std::move(u->cls)
12572 [&] {
12573 for (auto& updates : allUpdates) {
12574 for (auto& update : updates) {
12575 auto u = boost::get<ClassUpdate>(&update);
12576 if (!u) continue;
12577 always_assert(
12578 index.classInfoRefs.emplace(
12579 u->name,
12580 std::move(u->cinfo)
12581 ).second
12586 [&] {
12587 for (auto& updates : allUpdates) {
12588 for (auto& update : updates) {
12589 auto u = boost::get<ClassUpdate>(&update);
12590 if (!u) continue;
12591 index.classBytecodeRefs.insert_or_assign(
12592 u->name,
12593 std::move(u->bytecode)
12598 [&] {
12599 for (auto& updates : allUpdates) {
12600 for (auto& update : updates) {
12601 auto u = boost::get<FuncUpdate>(&update);
12602 if (!u) continue;
12603 index.funcRefs.at(u->name) = std::move(u->func);
12607 [&] {
12608 for (auto& updates : allUpdates) {
12609 for (auto& update : updates) {
12610 auto u = boost::get<FuncUpdate>(&update);
12611 if (!u) continue;
12612 always_assert(
12613 index.funcInfoRefs.emplace(
12614 u->name,
12615 std::move(u->finfo)
12616 ).second
12621 [&] {
12622 for (auto& updates : allUpdates) {
12623 for (auto& update : updates) {
12624 // Keep closure mappings up to date.
12625 auto u = boost::get<ClosureUpdate>(&update);
12626 if (!u) continue;
12627 initTypesMeta.fixups[u->unit].addClass.emplace_back(u->name);
12628 assertx(u->context);
12629 always_assert(
12630 index.closureToClass.emplace(u->name, u->context).second
12632 index.classToClosures[u->context].emplace(u->name);
12635 for (auto& [unit, deletions] : meta.unitDeletions) {
12636 initTypesMeta.fixups[unit].removeFunc = std::move(deletions);
12639 [&] {
12640 // A class which didn't have an 86*init function previously
12641 // can gain one due to trait flattening. Update that here.
12642 for (auto const& updates : allUpdates) {
12643 for (auto const& update : updates) {
12644 auto u = boost::get<ClassUpdate>(&update);
12645 if (!u || !u->has86init) continue;
12646 index.classesWith86Inits.emplace(u->name);
12650 [&] {
12651 // Build metadata for the next build subclass pass.
12652 auto& all = subclassMeta.all;
12653 auto& meta = subclassMeta.meta;
12654 for (auto& updates : allUpdates) {
12655 for (auto& update : updates) {
12656 auto u = boost::get<ClassUpdate>(&update);
12657 if (!u) continue;
12659 // We shouldn't have parents for closures because we
12660 // special case those explicitly.
12661 if (is_closure_name(u->name) || is_closure_base(u->name)) {
12662 assertx(u->parents.empty());
12663 continue;
12665 // Otherwise build the children lists from the parents.
12666 all.emplace_back(u->name);
12667 for (auto const p : u->parents) {
12668 meta[p].children.emplace_back(u->name);
12670 auto& parents = meta[u->name].parents;
12671 assertx(parents.empty());
12672 parents.insert(
12673 end(parents),
12674 begin(u->parents), end(u->parents)
12679 std::sort(begin(all), end(all), string_data_lt_type{});
12680 // Make sure there's no duplicates:
12681 assertx(std::adjacent_find(begin(all), end(all)) == end(all));
12683 for (size_t i = 0; i < all.size(); ++i) meta[all[i]].idx = i;
12685 [&] {
12686 for (auto& updates : allUpdates) {
12687 for (auto& update : updates) {
12688 auto u = boost::get<MethodUpdate>(&update);
12689 if (!u) continue;
12690 always_assert(
12691 index.uninstantiableClsMethRefs.emplace(
12692 u->name,
12693 std::move(u->methods)
12694 ).second
12699 [&] {
12700 for (auto& updates : allUpdates) {
12701 for (auto& update : updates) {
12702 if (auto const u = boost::get<ClassUpdate>(&update)) {
12703 auto& meta = initTypesMeta.classes[u->name];
12704 assertx(meta.deps.empty());
12705 meta.deps.insert(begin(u->typeUses), end(u->typeUses));
12706 } else if (auto const u = boost::get<FuncUpdate>(&update)) {
12707 auto& meta = initTypesMeta.funcs[u->name];
12708 assertx(meta.deps.empty());
12709 meta.deps.insert(begin(u->typeUses), end(u->typeUses));
12717 return std::make_tuple(
12718 std::move(subclassMeta),
12719 std::move(initTypesMeta),
12720 [&] {
12721 std::vector<InterfaceConflicts> out;
12722 out.reserve(ifaceConflicts.size());
12723 for (auto& [_, c] : ifaceConflicts) out.emplace_back(std::move(c));
12724 return out;
12729 //////////////////////////////////////////////////////////////////////
12730 // Subclass list
12733 * Subclass lists are built in a similar manner as flattening classes,
12734 * except the order is reversed.
12736 * However, there is one complication: the transitive children of each
12737 * class can be huge. In fact, for large hierarchies, they can easily
12738 * be too large to (efficiently) handle in a single job.
12740 * Rather than (always) processing everything in a single pass, we
12741 * might need to use multiple passes to keep the fan-in down. When
12742 * calculating the work buckets, we keep the size of each bucket into
12743 * account and don't allow any bucket to grow too large. If it does,
12744 * we'll just process that bucket, and process any dependencies in the
12745 * next pass.
12747 * This isn't sufficient. A single class have (far) more direct
12748 * children than we want in a single bucket. Multiple passes don't
12749 * help here because there's no intermediate classes to use as an
12750 * output. To fix this, we insert "splits", which serve to "summarize"
12751 * some subset of a class' direct children.
12753 * For example, suppose a class has 10k direct children, and our
12754 * maximum bucket size is 1k. On the first pass we'll process all of
12755 * the children in ~10 different jobs, each one processing 1k of the
12756 * children, and producing a single split node. The second pass will
12757 * actually process the class and take all of the splits as inputs
12758 * (not the actual children). The inputs to the job has been reduced
12759 * from 10k to 10. This is a simplification. In reality a job can
12760 * produce multiple splits, and inputs can be a mix of splits and
12761 * actual classes. In extreme cases, you might need multiple rounds of
12762 * splits before processing the class.
12764 * There is one other difference between this and the flatten classes
12765 * pass. Unlike in flatten classes, every class (except leafs) are
12766 * "roots" here. We do not promote any dependencies. This causes more
12767 * work overall, but it lets us process more classes in parallel.
12771 * Extern-worker job to build ClassInfo2 subclass lists, and calculate
12772 * various properties on the ClassInfo2 from it.
12774 struct BuildSubclassListJob {
12775 static std::string name() { return "hhbbc-build-subclass"; }
12776 static void init(const Config& config) {
12777 process_init(config.o, config.gd, false);
12778 ClassGraph::init();
12780 static void fini() { ClassGraph::destroy(); }
12782 // Aggregated data for some group of classes. The data can either
12783 // come from a split node, or inferred from a group of classes.
12784 struct Data {
12785 // Information about all of the methods with a particular name
12786 // between all of the classes in this Data.
12787 struct MethInfo {
12788 // Methods which are present on at least one regular class.
12789 MethRefSet regularMeths;
12790 // Methods which are only present on non-regular classes, but is
12791 // private on at least one class. These are sometimes treated
12792 // like a regular class.
12793 MethRefSet nonRegularPrivateMeths;
12794 // Methods which are only present on non-regular classes (and
12795 // never private). These three sets are always disjoint.
12796 MethRefSet nonRegularMeths;
12798 Optional<FuncFamily2::StaticInfo> allStatic;
12799 Optional<FuncFamily2::StaticInfo> regularStatic;
12801 // Whether all classes in this Data have a method with this
12802 // name.
12803 bool complete{true};
12804 // Whether all regular classes in this Data have a method with
12805 // this name.
12806 bool regularComplete{true};
12807 // Whether any of the methods has a private ancestor.
12808 bool privateAncestor{false};
12810 template <typename SerDe> void serde(SerDe& sd) {
12811 sd(regularMeths, std::less<MethRef>{})
12812 (nonRegularPrivateMeths, std::less<MethRef>{})
12813 (nonRegularMeths, std::less<MethRef>{})
12814 (allStatic)
12815 (regularStatic)
12816 (complete)
12817 (regularComplete)
12818 (privateAncestor)
12822 SStringToOneT<MethInfo> methods;
12824 // The name of properties which might have null values even if the
12825 // type-constraint doesn't allow it (due to system provided
12826 // initial values).
12827 SStringSet propsWithImplicitNullable;
12829 // The classes for whom isMocked would be true due to one of the
12830 // classes making up this Data. The classes in this set may not
12831 // necessarily be also part of this Data.
12832 TSStringSet mockedClasses;
12834 bool hasConstProp{false};
12835 bool hasReifiedGeneric{false};
12837 bool isSubMocked{false};
12839 // The meaning of these differ depending on whether the ClassInfo
12840 // contains just it's info, or all of it's subclass info.
12841 bool hasRegularClass{false};
12842 bool hasRegularClassFull{false};
12844 template <typename SerDe> void serde(SerDe& sd) {
12845 sd(methods, string_data_lt{})
12846 (propsWithImplicitNullable, string_data_lt{})
12847 (mockedClasses, string_data_lt_type{})
12848 (hasConstProp)
12849 (hasReifiedGeneric)
12850 (isSubMocked)
12851 (hasRegularClass)
12852 (hasRegularClassFull)
12857 // Split node. Used to wrap a Data when summarizing some subset of a
12858 // class' children.
12859 struct Split {
12860 Split() = default;
12861 Split(SString name, SString cls) : name{name}, cls{cls} {}
12863 SString name;
12864 SString cls;
12865 CompactVector<SString> children;
12866 CompactVector<ClassGraph> classGraphs;
12867 Data data;
12869 template <typename SerDe> void serde(SerDe& sd) {
12870 ScopedStringDataIndexer _;
12871 ClassGraph::ScopedSerdeState _2;
12872 sd(name)
12873 (cls)
12874 (children)
12875 (classGraphs, nullptr)
12876 (data)
12881 // Mark a dependency on a class to a split node. Since the splits
12882 // are not actually part of the hierarchy, the relationship between
12883 // classes and splits cannot be inferred otherwise.
12884 struct EdgeToSplit {
12885 SString cls;
12886 SString split;
12887 template <typename SerDe> void serde(SerDe& sd) {
12888 sd(cls)
12889 (split)
12894 // Job output meant to be downloaded and drive the next round.
12895 struct OutputMeta {
12896 // For every input ClassInfo, the set of func families present in
12897 // that ClassInfo's method family table. If the ClassInfo is used
12898 // as a dep later, these func families need to be provided as
12899 // well.
12900 std::vector<hphp_fast_set<FuncFamily2::Id>> funcFamilyDeps;
12901 // The ids of new (not provided as an input) func families
12902 // produced. The ids are grouped together to become
12903 // FuncFamilyGroups.
12904 std::vector<std::vector<FuncFamily2::Id>> newFuncFamilyIds;
12905 // Func family entries corresponding to all methods with a
12906 // particular name encountered in this job. Multiple jobs will
12907 // generally produce func family entries for the same name, so
12908 // they must be aggregated together afterwards.
12909 std::vector<std::pair<SString, FuncFamilyEntry>> nameOnly;
12910 std::vector<std::vector<SString>> regOnlyEquivCandidates;
12912 // For every output class, the set of classes which that class has
12913 // inherited class constants from.
12914 TSStringToOneT<TSStringSet> cnsBases;
12916 template <typename SerDe> void serde(SerDe& sd) {
12917 ScopedStringDataIndexer _;
12918 sd(funcFamilyDeps, std::less<FuncFamily2::Id>{})
12919 (newFuncFamilyIds)
12920 (nameOnly)
12921 (regOnlyEquivCandidates)
12922 (cnsBases, string_data_lt_type{}, string_data_lt_type{})
12926 using Output = Multi<
12927 Variadic<std::unique_ptr<ClassInfo2>>,
12928 Variadic<std::unique_ptr<Split>>,
12929 Variadic<std::unique_ptr<php::Class>>,
12930 Variadic<FuncFamilyGroup>,
12931 Variadic<std::unique_ptr<ClassInfo2>>,
12932 OutputMeta
12935 // Each job takes the list of classes and splits which should be
12936 // produced, dependency classes and splits (which are not updated),
12937 // edges between classes and splits, and func families (needed by
12938 // dependency classes). Leafs are like deps, except they'll be
12939 // considered as part of calculating the name-only func families
12940 // because its possible for them to be disjoint from classes.
12941 // (normal deps are not considered as their data is guaranteed to
12942 // be included by a class).
12943 static Output
12944 run(Variadic<std::unique_ptr<ClassInfo2>> classes,
12945 Variadic<std::unique_ptr<ClassInfo2>> deps,
12946 Variadic<std::unique_ptr<ClassInfo2>> leafs,
12947 Variadic<std::unique_ptr<Split>> splits,
12948 Variadic<std::unique_ptr<Split>> splitDeps,
12949 Variadic<std::unique_ptr<php::Class>> phpClasses,
12950 Variadic<EdgeToSplit> edges,
12951 Variadic<FuncFamilyGroup> funcFamilies) {
12952 // Store mappings of names to classes and edges.
12953 LocalIndex index;
12955 if (debug) {
12956 for (auto const& cinfo : classes.vals) {
12957 always_assert(!cinfo->classGraph.isMissing());
12958 always_assert(!cinfo->classGraph.hasCompleteChildren());
12959 always_assert(!cinfo->classGraph.isConservative());
12961 for (auto const& cinfo : leafs.vals) {
12962 always_assert(!cinfo->classGraph.isMissing());
12963 always_assert(!cinfo->classGraph.hasCompleteChildren());
12964 always_assert(!cinfo->classGraph.isConservative());
12966 for (auto const& cinfo : deps.vals) {
12967 always_assert(!cinfo->classGraph.isMissing());
12969 for (auto const& split : splits.vals) {
12970 for (auto const child : split->classGraphs) {
12971 always_assert(!child.isMissing());
12972 always_assert(!child.hasCompleteChildren());
12973 always_assert(!child.isConservative());
12976 for (auto const& split : splitDeps.vals) {
12977 for (auto const child : split->classGraphs) {
12978 always_assert(!child.isMissing());
12979 always_assert(child.hasCompleteChildren() ||
12980 child.isConservative());
12985 for (auto& cinfo : classes.vals) {
12986 assertx(!is_closure_name(cinfo->name));
12987 always_assert(
12988 index.classInfos.emplace(cinfo->name, cinfo.get()).second
12990 index.top.emplace(cinfo->name);
12992 for (auto& cinfo : deps.vals) {
12993 assertx(!is_closure_name(cinfo->name));
12994 always_assert(
12995 index.classInfos.emplace(cinfo->name, cinfo.get()).second
12998 for (auto& cinfo : leafs.vals) {
12999 assertx(!is_closure_name(cinfo->name));
13000 always_assert(
13001 index.classInfos.emplace(cinfo->name, cinfo.get()).second
13004 for (auto& split : splits.vals) {
13005 always_assert(
13006 index.splits.emplace(split->name, split.get()).second
13008 index.top.emplace(split->name);
13010 for (auto& split : splitDeps.vals) {
13011 assertx(split->children.empty());
13012 always_assert(
13013 index.splits.emplace(split->name, split.get()).second
13016 for (auto& cls : phpClasses.vals) {
13017 assertx(!is_closure(*cls));
13018 always_assert(
13019 index.classes.emplace(cls->name, cls.get()).second
13022 for (auto& group : funcFamilies.vals) {
13023 for (auto& ff : group.m_ffs) {
13024 auto const id = ff->m_id;
13025 // We could have multiple groups which contain the same
13026 // FuncFamily, so don't assert uniqueness here. We'll just
13027 // take the first one we see (they should all be equivalent).
13028 index.funcFamilies.emplace(id, std::move(ff));
13032 index.aggregateData.reserve(index.top.size());
13034 OutputMeta meta;
13036 // Mark all of the classes (including leafs) as being complete
13037 // since their subclass lists are correct.
13038 for (auto& cinfo : leafs.vals) {
13039 if (cinfo->classGraph.hasCompleteChildren()) continue;
13040 cinfo->classGraph.setComplete();
13042 for (auto& cinfo : classes.vals) {
13043 if (cinfo->classGraph.hasCompleteChildren() ||
13044 cinfo->classGraph.isConservative()) {
13045 continue;
13047 cinfo->classGraph.setComplete();
13049 for (auto& cinfo : deps.vals) {
13050 if (cinfo->classGraph.hasCompleteChildren() ||
13051 cinfo->classGraph.isConservative()) {
13052 continue;
13054 cinfo->classGraph.setComplete();
13056 for (auto& split : splits.vals) {
13057 for (auto child : split->classGraphs) {
13058 if (child.hasCompleteChildren() ||
13059 child.isConservative()) {
13060 continue;
13062 child.setComplete();
13066 // Store the regular-only equivalent classes in the output
13067 // metadata. This will be used in the init-types pass.
13068 meta.regOnlyEquivCandidates.reserve(classes.vals.size());
13069 for (auto& cinfo : classes.vals) {
13070 meta.regOnlyEquivCandidates.emplace_back();
13071 auto& candidates = meta.regOnlyEquivCandidates.back();
13072 for (auto const g : cinfo->classGraph.candidateRegOnlyEquivs()) {
13073 candidates.emplace_back(g.name());
13077 // If there's no classes or splits, this job is doing nothing but
13078 // calculating name only func family entries (so should have at
13079 // least one leaf).
13080 if (!index.top.empty()) {
13081 build_children(index, edges.vals);
13082 process_roots(index, classes.vals, splits.vals);
13083 } else {
13084 assertx(classes.vals.empty());
13085 assertx(splits.vals.empty());
13086 assertx(index.splits.empty());
13087 assertx(index.funcFamilies.empty());
13088 assertx(!leafs.vals.empty());
13089 assertx(!index.classInfos.empty());
13092 meta.nameOnly =
13093 make_name_only_method_entries(index, classes.vals, leafs.vals);
13095 // Record dependencies for each input class. A func family is a
13096 // dependency of the class if it appears in the method families
13097 // table.
13098 meta.funcFamilyDeps.reserve(classes.vals.size());
13099 for (auto const& cinfo : classes.vals) {
13100 meta.funcFamilyDeps.emplace_back();
13101 auto& deps = meta.funcFamilyDeps.back();
13102 for (auto const& [_, entry] : cinfo->methodFamilies) {
13103 match<void>(
13104 entry.m_meths,
13105 [&] (const FuncFamilyEntry::BothFF& e) { deps.emplace(e.m_ff); },
13106 [&] (const FuncFamilyEntry::FFAndSingle& e) { deps.emplace(e.m_ff); },
13107 [&] (const FuncFamilyEntry::FFAndNone& e) { deps.emplace(e.m_ff); },
13108 [&] (const FuncFamilyEntry::BothSingle&) {},
13109 [&] (const FuncFamilyEntry::SingleAndNone&) {},
13110 [&] (const FuncFamilyEntry::None&) {}
13115 Variadic<FuncFamilyGroup> funcFamilyGroups;
13116 group_func_families(index, funcFamilyGroups.vals, meta.newFuncFamilyIds);
13118 auto const addCnsBase = [&] (const ClassInfo2& cinfo) {
13119 auto& bases = meta.cnsBases[cinfo.name];
13120 for (auto const& [_, idx] : cinfo.clsConstants) {
13121 if (!cinfo.name->tsame(idx.idx.cls)) bases.emplace(idx.idx.cls);
13124 for (auto const& cinfo : classes.vals) addCnsBase(*cinfo);
13125 for (auto const& cinfo : leafs.vals) addCnsBase(*cinfo);
13127 // We only need to provide php::Class which correspond to a class
13128 // which wasn't a dep.
13129 phpClasses.vals.erase(
13130 std::remove_if(
13131 begin(phpClasses.vals),
13132 end(phpClasses.vals),
13133 [&] (const std::unique_ptr<php::Class>& c) {
13134 return !index.top.count(c->name);
13137 end(phpClasses.vals)
13140 return std::make_tuple(
13141 std::move(classes),
13142 std::move(splits),
13143 std::move(phpClasses),
13144 std::move(funcFamilyGroups),
13145 std::move(leafs),
13146 std::move(meta)
13150 protected:
13153 * MethInfo caching
13155 * When building a func family, MethRefSets must be sorted, then
13156 * hashed in order to generate the unique id. Once we do so, we can
13157 * then check if that func family already exists. For large func
13158 * families, this can very expensive and we might have to do this
13159 * (wasted) work multiple times.
13161 * To avoid this, we add a cache before the sorting/hashing
13162 * step. Instead of using a func family id (which is the expensive
13163 * thing to generate), the cache is keyed by the set of methods
13164 * directly. A commutative hash is used so that we don't actually
13165 * have to sort the MethRefSets, and equality is just equality of
13166 * the MethRefSets. Moreover, we make use of hetereogenous lookup to
13167 * avoid having to copy any MethRefSets (again they can be large)
13168 * when doing the lookup.
13171 // What is actually stored in the cache. Keeps a copy of the
13172 // MethRefSets.
13173 struct MethInfoTuple {
13174 MethRefSet regular;
13175 MethRefSet nonRegularPrivate;
13176 MethRefSet nonRegular;
13178 // Used for lookups. Just has pointers to the MethRefSets, so we
13179 // don't have to do any copying for the lookup.
13180 struct MethInfoTupleProxy {
13181 const MethRefSet* regular;
13182 const MethRefSet* nonRegularPrivate;
13183 const MethRefSet* nonRegular;
13186 struct MethInfoTupleHasher {
13187 using is_transparent = void;
13189 size_t operator()(const MethInfoTuple& t) const {
13190 auto const h1 = folly::hash::commutative_hash_combine_range_generic(
13191 0, MethRef::Hash{}, begin(t.regular), end(t.regular)
13193 auto const h2 = folly::hash::commutative_hash_combine_range_generic(
13194 0, MethRef::Hash{}, begin(t.nonRegularPrivate), end(t.nonRegularPrivate)
13196 auto const h3 = folly::hash::commutative_hash_combine_range_generic(
13197 0, MethRef::Hash{}, begin(t.nonRegular), end(t.nonRegular)
13199 return folly::hash::hash_combine(h1, h2, h3);
13201 size_t operator()(const MethInfoTupleProxy& t) const {
13202 auto const h1 = folly::hash::commutative_hash_combine_range_generic(
13203 0, MethRef::Hash{}, begin(*t.regular), end(*t.regular)
13205 auto const h2 = folly::hash::commutative_hash_combine_range_generic(
13206 0, MethRef::Hash{}, begin(*t.nonRegularPrivate), end(*t.nonRegularPrivate)
13208 auto const h3 = folly::hash::commutative_hash_combine_range_generic(
13209 0, MethRef::Hash{}, begin(*t.nonRegular), end(*t.nonRegular)
13211 return folly::hash::hash_combine(h1, h2, h3);
13214 struct MethInfoTupleEquals {
13215 using is_transparent = void;
13217 bool operator()(const MethInfoTuple& t1, const MethInfoTuple& t2) const {
13218 return
13219 t1.regular == t2.regular &&
13220 t1.nonRegularPrivate == t2.nonRegularPrivate &&
13221 t1.nonRegular == t2.nonRegular;
13223 bool operator()(const MethInfoTupleProxy& t1,
13224 const MethInfoTuple& t2) const {
13225 return
13226 *t1.regular == t2.regular &&
13227 *t1.nonRegularPrivate == t2.nonRegularPrivate &&
13228 *t1.nonRegular == t2.nonRegular;
13232 struct LocalIndex {
13233 // All ClassInfos, whether inputs or dependencies.
13234 TSStringToOneT<ClassInfo2*> classInfos;
13235 // All splits, whether inputs or dependencies.
13236 TSStringToOneT<Split*> splits;
13237 // All php::Class, whether inputs or dependencies.
13238 TSStringToOneT<php::Class*> classes;
13240 // ClassInfos and splits which are inputs (IE, we want to
13241 // calculate data for).
13242 TSStringSet top;
13244 // Aggregated data for an input
13245 TSStringToOneT<Data> aggregateData;
13247 // Mapping of input ClassInfos/splits to all of their subclasses
13248 // present in this Job. Some of the children may be splits, which
13249 // means some subset of the children were processed in another
13250 // Job.
13251 TSStringToOneT<std::vector<SString>> children;
13253 // The leafs in this job. This isn't necessarily an actual leaf,
13254 // but one whose children haven't been provided in this job
13255 // (because they've already been processed). Classes which are
13256 // leafs have information in their ClassInfo2 which reflect all of
13257 // their subclasses, otherwise just their own information.
13258 TSStringSet leafs;
13260 // All func families available in this Job, either from inputs, or
13261 // created during processing.
13262 hphp_fast_map<FuncFamily2::Id, std::unique_ptr<FuncFamily2>> funcFamilies;
13264 // Above mentioned func family cache. If an entry is present here,
13265 // we know the func family already exists and don't need to do
13266 // expensive sorting/hashing.
13267 hphp_fast_map<
13268 MethInfoTuple,
13269 FuncFamily2::Id,
13270 MethInfoTupleHasher,
13271 MethInfoTupleEquals
13272 > funcFamilyCache;
13274 // funcFamilies contains all func families. If a func family is
13275 // created during processing, it will be inserted here (used to
13276 // determine outputs).
13277 std::vector<FuncFamily2::Id> newFuncFamilies;
13279 php::Class& cls(SString name) {
13280 auto const it = classes.find(name);
13281 always_assert(it != end(classes));
13282 return *it->second;
13286 // Take all of the func families produced by this job and group them
13287 // together into FuncFamilyGroups. We produce both the
13288 // FuncFamilyGroups themselves, but also the associated ids in each
13289 // group (which will be output as metadata).
13290 static void group_func_families(
13291 LocalIndex& index,
13292 std::vector<FuncFamilyGroup>& groups,
13293 std::vector<std::vector<FuncFamily2::Id>>& ids
13295 constexpr size_t kGroupSize = 5000;
13297 // The grouping algorithm is very simple. First we sort all of the
13298 // func families by size. We then just group adjacent families
13299 // until their total size exceeds some threshold. Once it does, we
13300 // start a new group.
13301 std::sort(
13302 begin(index.newFuncFamilies),
13303 end(index.newFuncFamilies),
13304 [&] (const FuncFamily2::Id& id1, const FuncFamily2::Id& id2) {
13305 auto const& ff1 = index.funcFamilies.at(id1);
13306 auto const& ff2 = index.funcFamilies.at(id2);
13307 auto const size1 =
13308 ff1->m_regular.size() +
13309 ff1->m_nonRegularPrivate.size() +
13310 ff1->m_nonRegular.size();
13311 auto const size2 =
13312 ff2->m_regular.size() +
13313 ff2->m_nonRegularPrivate.size() +
13314 ff2->m_nonRegular.size();
13315 if (size1 != size2) return size1 < size2;
13316 return id1 < id2;
13320 size_t current = 0;
13321 for (auto const& id : index.newFuncFamilies) {
13322 auto& ff = index.funcFamilies.at(id);
13323 auto const size =
13324 ff->m_regular.size() +
13325 ff->m_nonRegularPrivate.size() +
13326 ff->m_nonRegular.size();
13327 if (groups.empty() || current > kGroupSize) {
13328 groups.emplace_back();
13329 ids.emplace_back();
13330 current = 0;
13332 groups.back().m_ffs.emplace_back(std::move(ff));
13333 ids.back().emplace_back(id);
13334 current += size;
13338 // Produce a set of name-only func families from the given set of
13339 // roots and leafs. It is assumed that any roots have already been
13340 // processed by process_roots, so that they'll have any appropriate
13341 // method families on them. Only entries which are "first name" are
13342 // processed.
13343 static std::vector<std::pair<SString, FuncFamilyEntry>>
13344 make_name_only_method_entries(
13345 LocalIndex& index,
13346 const std::vector<std::unique_ptr<ClassInfo2>>& roots,
13347 const std::vector<std::unique_ptr<ClassInfo2>>& leafs
13349 SStringToOneT<Data::MethInfo> infos;
13351 // Use the already calculated method family and merge
13352 // it's contents into what we already have.
13353 auto const process = [&] (const ClassInfo2* cinfo,
13354 SString name) {
13355 auto const it = cinfo->methodFamilies.find(name);
13356 always_assert(it != end(cinfo->methodFamilies));
13357 auto entryInfo = meth_info_from_func_family_entry(index, it->second);
13359 auto& info = infos[name];
13360 info.complete = false;
13361 info.regularComplete = false;
13363 for (auto const& meth : entryInfo.regularMeths) {
13364 if (info.regularMeths.count(meth)) continue;
13365 info.regularMeths.emplace(meth);
13366 info.nonRegularPrivateMeths.erase(meth);
13367 info.nonRegularMeths.erase(meth);
13369 for (auto const& meth : entryInfo.nonRegularPrivateMeths) {
13370 if (info.regularMeths.count(meth) ||
13371 info.nonRegularPrivateMeths.count(meth)) {
13372 continue;
13374 info.nonRegularPrivateMeths.emplace(meth);
13375 info.nonRegularMeths.erase(meth);
13377 for (auto const& meth : entryInfo.nonRegularMeths) {
13378 if (info.regularMeths.count(meth) ||
13379 info.nonRegularPrivateMeths.count(meth) ||
13380 info.nonRegularMeths.count(meth)) {
13381 continue;
13383 info.nonRegularMeths.emplace(meth);
13386 // Merge any StaticInfo entries we have for this method.
13387 if (entryInfo.allStatic) {
13388 if (!info.allStatic) {
13389 info.allStatic = std::move(*entryInfo.allStatic);
13390 } else {
13391 *info.allStatic |= *entryInfo.allStatic;
13394 if (entryInfo.regularStatic) {
13395 if (!info.regularStatic) {
13396 info.regularStatic = std::move(*entryInfo.regularStatic);
13397 } else {
13398 *info.regularStatic |= *entryInfo.regularStatic;
13403 // First process the roots. These methods might be overridden or
13404 // not.
13405 for (auto const& cinfo : roots) {
13406 for (auto const& [name, mte] : cinfo->methods) {
13407 if (!mte.firstName()) continue;
13408 if (!has_name_only_func_family(name)) continue;
13409 process(cinfo.get(), name);
13413 // Leafs are by definition always AttrNoOverride.
13414 for (auto const& cinfo : leafs) {
13415 for (auto const& [name, mte] : cinfo->methods) {
13416 if (!mte.firstName()) continue;
13417 if (!has_name_only_func_family(name)) continue;
13418 process(cinfo.get(), name);
13422 // Make the MethInfo order deterministic
13423 std::vector<SString> sorted;
13424 sorted.reserve(infos.size());
13425 for (auto const& [name, _] : infos) sorted.emplace_back(name);
13426 std::sort(begin(sorted), end(sorted), string_data_lt{});
13428 std::vector<std::pair<SString, FuncFamilyEntry>> entries;
13429 entries.reserve(infos.size());
13431 // Turn the MethInfos into FuncFamilyEntries
13432 for (auto const name : sorted) {
13433 auto& info = infos.at(name);
13434 entries.emplace_back(
13435 name,
13436 make_method_family_entry(index, name, std::move(info))
13439 return entries;
13442 // From the information present in the inputs, calculate a mapping
13443 // of classes and splits to their children (which can be other
13444 // classes or split nodes). This is not just direct children, but
13445 // all transitive subclasses.
13446 static void build_children(LocalIndex& index,
13447 const std::vector<EdgeToSplit>& edges) {
13448 TSStringToOneT<TSStringSet> children;
13449 // First record direct children. This can be inferred from the
13450 // parents of all present ClassInfos:
13452 // Everything starts out as a leaf.
13453 index.leafs.reserve(index.classInfos.size());
13454 for (auto const [name, _] : index.classInfos) {
13455 index.leafs.emplace(name);
13458 auto const onParent = [&] (SString parent, const ClassInfo2* child) {
13459 // Due to how work is divided, a class might have parents not
13460 // present in this job. Ignore those.
13461 if (!index.classInfos.count(parent)) return;
13462 children[parent].emplace(child->name);
13463 // If you're a parent, you're not a leaf.
13464 index.leafs.erase(parent);
13467 for (auto const [name, cinfo] : index.classInfos) {
13468 if (cinfo->parent) onParent(cinfo->parent, cinfo);
13469 for (auto const iface : cinfo->classGraph.declInterfaces()) {
13470 onParent(iface.name(), cinfo);
13472 for (auto const trait : cinfo->classGraph.usedTraits()) {
13473 onParent(trait.name(), cinfo);
13477 // Use the edges provided to the Job to know the mapping from
13478 // ClassInfo to split (it cannot be inferred otherwise).
13479 for (auto const& edge : edges) {
13480 SCOPE_ASSERT_DETAIL("Edge not present in job") {
13481 return folly::sformat("{} -> {}", edge.cls, edge.split);
13483 assertx(index.classInfos.count(edge.cls));
13484 assertx(index.splits.count(edge.split));
13485 children[edge.cls].emplace(edge.split);
13488 // Every "top" ClassInfo also has itself as a subclass (this
13489 // matches the semantics of the subclass list and simplifies the
13490 // processing).
13491 for (auto const name : index.top) {
13492 if (auto const split = folly::get_default(index.splits, name)) {
13493 // Copy the children list out of the split and add it to the
13494 // map.
13495 auto& c = children[name];
13496 for (auto const child : split->children) {
13497 assertx(index.classInfos.count(child) ||
13498 index.splits.count(child));
13499 c.emplace(child);
13504 // Calculate the indegree for all children. The indegree for a given node
13505 // differs depending on the top used, so these are calculated separately.
13506 auto const getIndegree = [&](SString root) {
13507 TSStringSet visited;
13508 TSStringSet toExplore{root};
13509 TSStringSet toExploreNext;
13510 TSStringToOneT<uint32_t> indegree;
13512 while (!toExplore.empty()) {
13513 toExploreNext.clear();
13514 for (auto const child : toExplore) {
13515 if (visited.count(child)) continue;
13516 visited.emplace(child);
13517 auto const it = children.find(child);
13518 // May not exist in children if processed in earlier round.
13519 if (it == end(children)) continue;
13520 for (auto const c : it->second) {
13521 indegree[c]++;
13522 toExploreNext.emplace(c);
13525 std::swap(toExplore, toExploreNext);
13527 return indegree;
13530 // Topological sort the transitive children for each node.
13531 for (auto& [name, _] : children) {
13532 auto indegree = getIndegree(name);
13533 std::vector<SString> sorted{name};
13535 int sortedBegin = 0;
13536 int sortedEnd = sorted.size();
13538 while (sortedBegin != sortedEnd) {
13539 for (int i = sortedBegin; i < sortedEnd; i++) {
13540 auto const cls = sorted[i];
13541 auto const it = children.find(cls);
13542 if (it == end(children)) continue;
13543 for (auto const c : it->second) {
13544 indegree[c]--;
13545 if (indegree[c] == 0) sorted.emplace_back(c);
13548 sortedBegin = sortedEnd;
13549 sortedEnd = sorted.size();
13551 assertx(indegree.size() + 1 == sorted.size());
13552 index.children[name] = std::move(sorted);
13556 static FuncFamily2::StaticInfo static_info_from_meth_meta(
13557 const FuncFamilyEntry::MethMetadata& meta
13559 FuncFamily2::StaticInfo info;
13560 info.m_numInOut = meta.m_numInOut;
13561 info.m_requiredCoeffects = meta.m_requiredCoeffects;
13562 info.m_coeffectRules = meta.m_coeffectRules;
13563 info.m_paramPreps = meta.m_prepKinds;
13564 info.m_minNonVariadicParams = info.m_maxNonVariadicParams =
13565 meta.m_nonVariadicParams;
13566 info.m_isReadonlyReturn = yesOrNo(meta.m_isReadonlyReturn);
13567 info.m_isReadonlyThis = yesOrNo(meta.m_isReadonlyThis);
13568 info.m_supportsAER = yesOrNo(meta.m_supportsAER);
13569 info.m_maybeReified = meta.m_isReified;
13570 info.m_maybeCaresAboutDynCalls = meta.m_caresAboutDyncalls;
13571 info.m_maybeBuiltin = meta.m_builtin;
13572 return info;
13575 // Turn a FuncFamilyEntry into an equivalent Data::MethInfo.
13576 static Data::MethInfo
13577 meth_info_from_func_family_entry(LocalIndex& index,
13578 const FuncFamilyEntry& entry) {
13579 Data::MethInfo info;
13580 info.complete = !entry.m_allIncomplete;
13581 info.regularComplete = !entry.m_regularIncomplete;
13582 info.privateAncestor = entry.m_privateAncestor;
13584 auto const getFF = [&] (const FuncFamily2::Id& id)
13585 -> const FuncFamily2& {
13586 auto const it = index.funcFamilies.find(id);
13587 always_assert_flog(
13588 it != end(index.funcFamilies),
13589 "Tried to access non-existent func-family '{}'",
13590 id.toString()
13592 return *it->second;
13595 match<void>(
13596 entry.m_meths,
13597 [&] (const FuncFamilyEntry::BothFF& e) {
13598 auto const& ff = getFF(e.m_ff);
13599 info.regularMeths.insert(
13600 begin(ff.m_regular),
13601 end(ff.m_regular)
13603 info.nonRegularPrivateMeths.insert(
13604 begin(ff.m_nonRegularPrivate),
13605 end(ff.m_nonRegularPrivate)
13607 info.nonRegularMeths.insert(
13608 begin(ff.m_nonRegular),
13609 end(ff.m_nonRegular)
13611 assertx(ff.m_allStatic);
13612 assertx(ff.m_regularStatic);
13613 info.allStatic = ff.m_allStatic;
13614 info.regularStatic = ff.m_regularStatic;
13616 [&] (const FuncFamilyEntry::FFAndSingle& e) {
13617 auto const& ff = getFF(e.m_ff);
13618 info.nonRegularMeths.insert(
13619 begin(ff.m_nonRegular),
13620 end(ff.m_nonRegular)
13622 if (e.m_nonRegularPrivate) {
13623 assertx(ff.m_nonRegularPrivate.size() == 1);
13624 assertx(ff.m_nonRegularPrivate[0] == e.m_regular);
13625 info.nonRegularPrivateMeths.emplace(e.m_regular);
13626 } else {
13627 assertx(ff.m_regular.size() == 1);
13628 assertx(ff.m_regular[0] == e.m_regular);
13629 info.regularMeths.emplace(e.m_regular);
13631 assertx(ff.m_allStatic);
13632 assertx(ff.m_regularStatic);
13633 info.allStatic = ff.m_allStatic;
13634 info.regularStatic = ff.m_regularStatic;
13636 [&] (const FuncFamilyEntry::FFAndNone& e) {
13637 auto const& ff = getFF(e.m_ff);
13638 assertx(ff.m_regular.empty());
13639 info.nonRegularMeths.insert(
13640 begin(ff.m_nonRegular),
13641 end(ff.m_nonRegular)
13643 assertx(ff.m_allStatic);
13644 assertx(!ff.m_regularStatic);
13645 info.allStatic = ff.m_allStatic;
13647 [&] (const FuncFamilyEntry::BothSingle& e) {
13648 if (e.m_nonRegularPrivate) {
13649 info.nonRegularPrivateMeths.emplace(e.m_all);
13650 } else {
13651 info.regularMeths.emplace(e.m_all);
13653 info.allStatic = info.regularStatic =
13654 static_info_from_meth_meta(e.m_meta);
13656 [&] (const FuncFamilyEntry::SingleAndNone& e) {
13657 info.nonRegularMeths.emplace(e.m_all);
13658 info.allStatic = static_info_from_meth_meta(e.m_meta);
13660 [&] (const FuncFamilyEntry::None&) {
13661 assertx(!info.complete);
13665 return info;
13668 // Create a Data representing the single ClassInfo or split with the
13669 // name "clsname".
13670 static Data build_data(LocalIndex& index, SString clsname) {
13671 // Does this name represent a class?
13672 if (auto const cinfo = folly::get_default(index.classInfos, clsname)) {
13673 // It's a class. We need to build a Data from what's in the
13674 // ClassInfo. If the ClassInfo hasn't been processed already
13675 // (it's a leaf or its the first round), the data will reflect
13676 // just that class. However if the ClassInfo has been processed
13677 // (it's a dependencies and it's past the first round), it will
13678 // reflect any subclasses of that ClassInfo as well.
13679 Data data;
13681 // Use the method family table to build initial MethInfos (if
13682 // the ClassInfo hasn't been processed this will be empty).
13683 for (auto const& [name, entry] : cinfo->methodFamilies) {
13684 data.methods.emplace(
13685 name,
13686 meth_info_from_func_family_entry(index, entry)
13690 auto const& cls = index.cls(cinfo->name);
13692 if (debug) {
13693 for (auto const& [name, mte] : cinfo->methods) {
13694 if (is_special_method_name(name)) continue;
13696 // Every method should have a methodFamilies entry. If this
13697 // method is AttrNoOverride, it shouldn't have a FuncFamily
13698 // associated with it.
13699 auto const it = cinfo->methodFamilies.find(name);
13700 always_assert(it != end(cinfo->methodFamilies));
13702 if (mte.attrs & AttrNoOverride) {
13703 always_assert(
13704 boost::get<FuncFamilyEntry::BothSingle>(&it->second.m_meths) ||
13705 boost::get<FuncFamilyEntry::SingleAndNone>(&it->second.m_meths)
13711 // Create a MethInfo for any missing methods as well.
13712 for (auto const name : cinfo->missingMethods) {
13713 assertx(!cinfo->methods.count(name));
13714 if (data.methods.count(name)) continue;
13715 // The MethInfo will be empty, and be marked as incomplete.
13716 auto& info = data.methods[name];
13717 info.complete = false;
13718 if (cinfo->isRegularClass) info.regularComplete = false;
13721 data.hasConstProp = cinfo->subHasConstProp;
13722 data.hasReifiedGeneric = cinfo->subHasReifiedGeneric;
13724 // If this is a mock class, any direct parent of this class
13725 // should be marked as mocked.
13726 if (cinfo->isMockClass) {
13727 for (auto const p : cinfo->classGraph.directParents()) {
13728 data.mockedClasses.emplace(p.name());
13731 data.isSubMocked = cinfo->isMocked || cinfo->isSubMocked;
13733 data.hasRegularClass = cinfo->isRegularClass;
13734 data.hasRegularClassFull =
13735 data.hasRegularClass || cinfo->classGraph.mightHaveRegularSubclass();
13736 if (!data.hasRegularClass && index.leafs.count(clsname)) {
13737 data.hasRegularClass = data.hasRegularClassFull;
13740 for (auto const& prop : cls.properties) {
13741 if (!(prop.attrs & (AttrStatic|AttrPrivate|AttrNoImplicitNullable))) {
13742 data.propsWithImplicitNullable.emplace(prop.name);
13746 return data;
13749 // It doesn't represent a class. It should represent a
13750 // split.
13752 // A split cannot be both a root and a dependency due to how we
13753 // set up the buckets.
13754 assertx(!index.top.count(clsname));
13755 auto const split = folly::get_default(index.splits, clsname);
13756 always_assert(split != nullptr);
13757 assertx(split->children.empty());
13758 // Split already contains the Data, so nothing to do but return
13759 // it.
13760 return split->data;
13763 static void update_data(Data& data, Data childData) {
13764 // Combine MethInfos for each method name:
13765 folly::erase_if(
13766 data.methods,
13767 [&] (std::pair<const SString, Data::MethInfo>& p) {
13768 auto const name = p.first;
13769 auto& info = p.second;
13771 if (auto const childInfo =
13772 folly::get_ptr(childData.methods, name)) {
13773 // There's a MethInfo with that name in the
13774 // child. "Promote" the MethRefs if they're in a superior
13775 // status in the child.
13776 for (auto const& meth : childInfo->regularMeths) {
13777 if (info.regularMeths.count(meth)) continue;
13778 info.regularMeths.emplace(meth);
13779 info.nonRegularPrivateMeths.erase(meth);
13780 info.nonRegularMeths.erase(meth);
13782 for (auto const& meth : childInfo->nonRegularPrivateMeths) {
13783 if (info.regularMeths.count(meth) ||
13784 info.nonRegularPrivateMeths.count(meth)) {
13785 continue;
13787 info.nonRegularPrivateMeths.emplace(meth);
13788 info.nonRegularMeths.erase(meth);
13790 for (auto const& meth : childInfo->nonRegularMeths) {
13791 if (info.regularMeths.count(meth) ||
13792 info.nonRegularPrivateMeths.count(meth) ||
13793 info.nonRegularMeths.count(meth)) {
13794 continue;
13796 info.nonRegularMeths.emplace(meth);
13798 info.complete &= childInfo->complete;
13799 if (childData.hasRegularClassFull) {
13800 info.regularComplete &= childInfo->regularComplete;
13801 info.privateAncestor |= childInfo->privateAncestor;
13802 } else {
13803 assertx(childInfo->regularComplete);
13804 assertx(!childInfo->privateAncestor);
13807 if (childInfo->allStatic) {
13808 if (!info.allStatic) {
13809 info.allStatic = std::move(*childInfo->allStatic);
13810 } else {
13811 *info.allStatic |= *childInfo->allStatic;
13814 if (childInfo->regularStatic) {
13815 if (!info.regularStatic) {
13816 info.regularStatic = std::move(*childInfo->regularStatic);
13817 } else {
13818 *info.regularStatic |= *childInfo->regularStatic;
13822 return false;
13825 // There's no MethInfo with that name in the child. We might
13826 // still want to keep the MethInfo because it will be needed
13827 // for expanding abstract class/interface method
13828 // families. If the child has a regular class, we can remove
13829 // it (it won't be part of the expansion).
13830 return
13831 childData.hasRegularClass ||
13832 !info.regularComplete ||
13833 info.privateAncestor ||
13834 is_special_method_name(name) ||
13835 name == s_construct.get();
13839 // Since we drop non-matching method names only if the class has
13840 // a regular class, it introduces an ordering dependency. If the
13841 // first class we encounter has a regular class, everything
13842 // works fine. However, if the first class we encounter does not
13843 // have a regular class, the Data will have its methods. If we
13844 // eventually process a class which does have a regular class,
13845 // we'll never process it's non-matching methods (because we
13846 // iterate over data.methods). They won't end up in data.methods
13847 // whereas they would if a class with regular class was
13848 // processed first. Detect this condition and manually add such
13849 // methods to data.methods.
13850 if (!data.hasRegularClass && childData.hasRegularClass) {
13851 for (auto& [name, info] : childData.methods) {
13852 if (!info.regularComplete || info.privateAncestor) continue;
13853 if (is_special_method_name(name)) continue;
13854 if (name == s_construct.get()) continue;
13855 if (data.methods.count(name)) continue;
13856 auto& newInfo = data.methods[name];
13857 newInfo.regularMeths = std::move(info.regularMeths);
13858 newInfo.nonRegularPrivateMeths =
13859 std::move(info.nonRegularPrivateMeths);
13860 newInfo.nonRegularMeths = std::move(info.nonRegularMeths);
13861 newInfo.allStatic = std::move(info.allStatic);
13862 newInfo.regularStatic = std::move(info.regularStatic);
13863 newInfo.complete = false;
13864 newInfo.regularComplete = true;
13865 newInfo.privateAncestor = false;
13869 data.propsWithImplicitNullable.insert(
13870 begin(childData.propsWithImplicitNullable),
13871 end(childData.propsWithImplicitNullable)
13874 data.mockedClasses.insert(
13875 begin(childData.mockedClasses),
13876 end(childData.mockedClasses)
13879 // The rest are booleans which can just be unioned together.
13880 data.hasConstProp |= childData.hasConstProp;
13881 data.hasReifiedGeneric |= childData.hasReifiedGeneric;
13882 data.isSubMocked |= childData.isSubMocked;
13883 data.hasRegularClass |= childData.hasRegularClass;
13884 data.hasRegularClassFull |= childData.hasRegularClassFull;
13887 // Obtain a Data for the given class/split named "top".
13888 // @param calculatedAcc: an accumulator passed in to track nodes we process
13889 // while processing children recursively
13890 static Data aggregate_data(LocalIndex& index,
13891 SString top,
13892 TSStringSet& calculatedAcc) {
13893 assertx(index.top.contains(top));
13895 auto const& children = [&]() -> const std::vector<SString>& {
13896 auto const it = index.children.find(top);
13897 always_assert(it != end(index.children));
13898 assertx(!it->second.empty());
13899 return it->second;
13900 }();
13902 auto const it = index.aggregateData.find(top);
13903 if (it != end(index.aggregateData)) {
13904 for (auto const child : children) calculatedAcc.emplace(child);
13905 return it->second;
13908 Data data;
13909 auto first = true;
13910 // Set of children calculated for current top to ensure we don't
13911 // duplicate work.
13912 TSStringSet calculatedForTop;
13914 // For each child of the class/split (for classes this includes
13915 // the top class itself), we create a Data, then union it together
13916 // with the rest.
13917 size_t childIdx = 0;
13918 while (calculatedForTop.size() < children.size()) {
13919 auto child = children[childIdx++];
13920 if (calculatedForTop.contains(child)) continue;
13921 // Top Splits have no associated data yet.
13922 if (index.top.count(child) && index.splits.count(child)) {
13923 calculatedForTop.emplace(child);
13924 continue;
13927 auto childData = [&]() {
13928 if (index.top.contains(child) && !child->tsame(top)) {
13929 return aggregate_data(index, child, calculatedForTop);
13930 } else {
13931 calculatedForTop.emplace(child);
13932 return build_data(index, child);
13934 }();
13936 // The first Data has nothing to union with, so just use it as is.
13937 if (first) {
13938 data = std::move(childData);
13939 first = false;
13940 continue;
13942 update_data(data, std::move(childData));
13945 for (auto const cls : calculatedForTop) calculatedAcc.emplace(cls);
13946 always_assert(index.aggregateData.emplace(top, data).second);
13947 return data;
13950 // Obtain a Data for the given class/split named "top".
13951 static Data aggregate_data(LocalIndex& index, SString top) {
13952 TSStringSet calculated;
13953 return aggregate_data(index, top, calculated);
13956 // Create (or re-use an existing) FuncFamily for the given MethInfo.
13957 static FuncFamily2::Id make_func_family(
13958 LocalIndex& index,
13959 SString name,
13960 Data::MethInfo info
13962 // We should have more than one method because otherwise we
13963 // shouldn't be trying to create a FuncFamily for it.
13964 assertx(
13965 info.regularMeths.size() +
13966 info.nonRegularPrivateMeths.size() +
13967 info.nonRegularMeths.size() > 1
13970 // Before doing the expensive sorting and hashing, see if this
13971 // FuncFamily already exists. If so, just return the id.
13972 if (auto const id = folly::get_ptr(
13973 index.funcFamilyCache,
13974 MethInfoTupleProxy{
13975 &info.regularMeths,
13976 &info.nonRegularPrivateMeths,
13977 &info.nonRegularMeths
13979 )) {
13980 return *id;
13983 // Nothing in the cache. We need to do the expensive step of
13984 // actually creating the FuncFamily.
13986 // First sort the methods so they're in deterministic order.
13987 std::vector<MethRef> regular{
13988 begin(info.regularMeths), end(info.regularMeths)
13990 std::vector<MethRef> nonRegularPrivate{
13991 begin(info.nonRegularPrivateMeths), end(info.nonRegularPrivateMeths)
13993 std::vector<MethRef> nonRegular{
13994 begin(info.nonRegularMeths), end(info.nonRegularMeths)
13996 std::sort(begin(regular), end(regular));
13997 std::sort(begin(nonRegularPrivate), end(nonRegularPrivate));
13998 std::sort(begin(nonRegular), end(nonRegular));
14000 // Create the id by hashing the methods:
14001 SHA1Hasher hasher;
14003 auto const size1 = regular.size();
14004 auto const size2 = nonRegularPrivate.size();
14005 auto const size3 = nonRegular.size();
14006 hasher.update((const char*)&size1, sizeof(size1));
14007 hasher.update((const char*)&size2, sizeof(size2));
14008 hasher.update((const char*)&size3, sizeof(size3));
14010 for (auto const& m : regular) {
14011 hasher.update(m.cls->data(), m.cls->size());
14012 hasher.update((const char*)&m.idx, sizeof(m.idx));
14014 for (auto const& m : nonRegularPrivate) {
14015 hasher.update(m.cls->data(), m.cls->size());
14016 hasher.update((const char*)&m.idx, sizeof(m.idx));
14018 for (auto const& m : nonRegular) {
14019 hasher.update(m.cls->data(), m.cls->size());
14020 hasher.update((const char*)&m.idx, sizeof(m.idx));
14022 auto const id = hasher.finish();
14024 // See if this id exists already. If so, record it in the cache
14025 // and we're done.
14026 if (index.funcFamilies.count(id)) {
14027 index.funcFamilyCache.emplace(
14028 MethInfoTuple{
14029 std::move(info.regularMeths),
14030 std::move(info.nonRegularPrivateMeths),
14031 std::move(info.nonRegularMeths)
14035 return id;
14038 // It's a new id. Create the actual FuncFamily:
14040 regular.shrink_to_fit();
14041 nonRegularPrivate.shrink_to_fit();
14042 nonRegular.shrink_to_fit();
14044 auto ff = std::make_unique<FuncFamily2>();
14045 ff->m_id = id;
14046 ff->m_name = name;
14047 ff->m_regular = std::move(regular);
14048 ff->m_nonRegularPrivate = std::move(nonRegularPrivate);
14049 ff->m_nonRegular = std::move(nonRegular);
14050 ff->m_allStatic = std::move(info.allStatic);
14051 ff->m_regularStatic = std::move(info.regularStatic);
14053 always_assert(
14054 index.funcFamilies.emplace(id, std::move(ff)).second
14056 index.newFuncFamilies.emplace_back(id);
14057 index.funcFamilyCache.emplace(
14058 MethInfoTuple{
14059 std::move(info.regularMeths),
14060 std::move(info.nonRegularPrivateMeths),
14061 std::move(info.nonRegularMeths)
14066 return id;
14069 // Turn a FuncFamily::StaticInfo into an equivalent
14070 // FuncFamilyEntry::MethMetadata. The StaticInfo must be valid for a
14071 // single method.
14072 static FuncFamilyEntry::MethMetadata single_meth_meta_from_static_info(
14073 const FuncFamily2::StaticInfo& info
14075 assertx(info.m_numInOut);
14076 assertx(info.m_requiredCoeffects);
14077 assertx(info.m_coeffectRules);
14078 assertx(info.m_minNonVariadicParams == info.m_maxNonVariadicParams);
14079 assertx(info.m_isReadonlyReturn != TriBool::Maybe);
14080 assertx(info.m_isReadonlyThis != TriBool::Maybe);
14081 assertx(info.m_supportsAER != TriBool::Maybe);
14083 FuncFamilyEntry::MethMetadata meta;
14084 meta.m_prepKinds = info.m_paramPreps;
14085 meta.m_coeffectRules = *info.m_coeffectRules;
14086 meta.m_numInOut = *info.m_numInOut;
14087 meta.m_requiredCoeffects = *info.m_requiredCoeffects;
14088 meta.m_nonVariadicParams = info.m_minNonVariadicParams;
14089 meta.m_isReadonlyReturn = info.m_isReadonlyReturn == TriBool::Yes;
14090 meta.m_isReadonlyThis = info.m_isReadonlyThis == TriBool::Yes;
14091 meta.m_supportsAER = info.m_supportsAER == TriBool::Yes;
14092 meta.m_isReified = info.m_maybeReified;
14093 meta.m_caresAboutDyncalls = info.m_maybeCaresAboutDynCalls;
14094 meta.m_builtin = info.m_maybeBuiltin;
14095 return meta;
14098 // Translate a MethInfo into the appropriate FuncFamilyEntry
14099 static FuncFamilyEntry make_method_family_entry(
14100 LocalIndex& index,
14101 SString name,
14102 Data::MethInfo info
14104 FuncFamilyEntry entry;
14105 entry.m_allIncomplete = !info.complete;
14106 entry.m_regularIncomplete = !info.regularComplete;
14107 entry.m_privateAncestor = info.privateAncestor;
14109 if (info.regularMeths.size() + info.nonRegularPrivateMeths.size() > 1) {
14110 // There's either multiple regularMeths, multiple
14111 // nonRegularPrivateMeths, or one of each (remember they are
14112 // disjoint). In either case, there's more than one method, so
14113 // we need a func family.
14114 assertx(info.allStatic);
14115 assertx(info.regularStatic);
14116 auto const ff = make_func_family(index, name, std::move(info));
14117 entry.m_meths = FuncFamilyEntry::BothFF{ff};
14118 } else if (!info.regularMeths.empty() ||
14119 !info.nonRegularPrivateMeths.empty()) {
14120 // We know their sum isn't greater than one, so only one of them
14121 // can be non-empty (and the one that is has only a single
14122 // method).
14123 assertx(info.allStatic);
14124 assertx(info.regularStatic);
14125 auto const r = !info.regularMeths.empty()
14126 ? *begin(info.regularMeths)
14127 : *begin(info.nonRegularPrivateMeths);
14128 if (info.nonRegularMeths.empty()) {
14129 // There's only one method and it covers both variants.
14130 entry.m_meths = FuncFamilyEntry::BothSingle{
14132 single_meth_meta_from_static_info(*info.allStatic),
14133 info.regularMeths.empty()
14135 } else {
14136 // nonRegularMeths is non-empty. Since the MethRefSets are
14137 // disjoint, overall there's more than one method so need a
14138 // func family.
14139 auto const nonRegularPrivate = info.regularMeths.empty();
14140 auto const ff = make_func_family(index, name, std::move(info));
14141 entry.m_meths = FuncFamilyEntry::FFAndSingle{ff, r, nonRegularPrivate};
14143 } else if (info.nonRegularMeths.size() > 1) {
14144 // Both regularMeths and nonRegularPrivateMeths is empty. If
14145 // there's multiple nonRegularMeths, we need a func family for
14146 // the non-regular variant, but the regular variant is empty.
14147 assertx(info.allStatic);
14148 assertx(!info.regularStatic);
14149 auto const ff = make_func_family(index, name, std::move(info));
14150 entry.m_meths = FuncFamilyEntry::FFAndNone{ff};
14151 } else if (!info.nonRegularMeths.empty()) {
14152 // There's exactly one nonRegularMeths method (and nothing for
14153 // the regular variant).
14154 assertx(info.allStatic);
14155 assertx(!info.regularStatic);
14156 entry.m_meths = FuncFamilyEntry::SingleAndNone{
14157 *begin(info.nonRegularMeths),
14158 single_meth_meta_from_static_info(*info.allStatic)
14160 } else {
14161 // No methods at all
14162 assertx(!info.complete);
14163 assertx(!info.allStatic);
14164 assertx(!info.regularStatic);
14165 entry.m_meths = FuncFamilyEntry::None{};
14168 return entry;
14171 // Calculate the data for each root (those which will we'll provide
14172 // outputs for) and update the ClassInfo or Split as appropriate.
14173 static void process_roots(
14174 LocalIndex& index,
14175 const std::vector<std::unique_ptr<ClassInfo2>>& roots,
14176 const std::vector<std::unique_ptr<Split>>& splits
14178 for (auto const& cinfo : roots) {
14179 assertx(index.top.count(cinfo->name));
14180 // Process the children of this class and build a unified Data
14181 // for it.
14182 auto data = aggregate_data(index, cinfo->name);
14184 // These are just copied directly from Data.
14185 cinfo->subHasConstProp = data.hasConstProp;
14186 cinfo->subHasReifiedGeneric = data.hasReifiedGeneric;
14188 auto& cls = index.cls(cinfo->name);
14190 // This class is mocked if its on the mocked classes list.
14191 cinfo->isMocked = (bool)data.mockedClasses.count(cinfo->name);
14192 cinfo->isSubMocked = data.isSubMocked || cinfo->isMocked;
14193 attribute_setter(cls.attrs, !cinfo->isSubMocked, AttrNoMock);
14195 // We can use whether we saw regular/non-regular subclasses to
14196 // infer if this class is overridden.
14197 if (cinfo->classGraph.mightHaveRegularSubclass()) {
14198 attribute_setter(cls.attrs, false, AttrNoOverrideRegular);
14199 attribute_setter(cls.attrs, false, AttrNoOverride);
14200 } else if (cinfo->classGraph.mightHaveNonRegularSubclass()) {
14201 attribute_setter(cls.attrs, true, AttrNoOverrideRegular);
14202 attribute_setter(cls.attrs, false, AttrNoOverride);
14203 } else {
14204 attribute_setter(cls.attrs, true, AttrNoOverrideRegular);
14205 attribute_setter(cls.attrs, true, AttrNoOverride);
14208 assertx(
14209 IMPLIES(
14210 cinfo->initialNoReifiedInit,
14211 cls.attrs & AttrNoReifiedInit
14215 attribute_setter(
14216 cls.attrs,
14217 [&] {
14218 if (cinfo->initialNoReifiedInit) return true;
14219 if (cinfo->parent) return false;
14220 if (cls.attrs & AttrInterface) return true;
14221 return !data.hasReifiedGeneric;
14222 }(),
14223 AttrNoReifiedInit
14226 for (auto& [name, mte] : cinfo->methods) {
14227 if (is_special_method_name(name)) continue;
14229 // Since this is the first time we're processing this class,
14230 // all of the methods should be marked as AttrNoOverride.
14231 assertx(mte.attrs & AttrNoOverride);
14232 assertx(mte.noOverrideRegular());
14234 auto& info = [&, name=name] () -> Data::MethInfo& {
14235 auto it = data.methods.find(name);
14236 always_assert(it != end(data.methods));
14237 return it->second;
14238 }();
14240 auto const meth = mte.meth();
14242 // Is this method overridden?
14243 auto const noOverride = [&] {
14244 // An incomplete method family is always overridden because
14245 // the call could fail.
14246 if (!info.complete) return false;
14247 // If more than one method then no.
14248 if (info.regularMeths.size() +
14249 info.nonRegularPrivateMeths.size() +
14250 info.nonRegularMeths.size() > 1) {
14251 return false;
14253 // NB: All of the below checks all return true. The
14254 // different conditions are just for checking the right
14255 // invariants.
14256 if (info.regularMeths.empty()) {
14257 // The (single) method isn't on a regular class. This
14258 // class shouldn't have any regular classes (the set is
14259 // complete so if we did, the method would have been on
14260 // it). The (single) method must be on nonRegularMeths or
14261 // nonRegularPrivateMeths.
14262 assertx(!cinfo->isRegularClass);
14263 if (info.nonRegularPrivateMeths.empty()) {
14264 assertx(info.nonRegularMeths.count(meth));
14265 return true;
14267 assertx(info.nonRegularMeths.empty());
14268 assertx(info.nonRegularPrivateMeths.count(meth));
14269 return true;
14271 assertx(info.nonRegularPrivateMeths.empty());
14272 assertx(info.nonRegularMeths.empty());
14273 assertx(info.regularMeths.count(meth));
14274 return true;
14277 // Is this method overridden in a regular class? (weaker
14278 // condition)
14279 auto const noOverrideRegular = [&] {
14280 // An incomplete method family is always overridden because
14281 // the call could fail.
14282 if (!info.regularComplete) return false;
14283 // If more than one method then no. For the purposes of this
14284 // check, non-regular but private methods are included.
14285 if (info.regularMeths.size() +
14286 info.nonRegularPrivateMeths.size() > 1) {
14287 return false;
14289 if (info.regularMeths.empty()) {
14290 // The method isn't on a regular class. Like in
14291 // noOverride(), the class shouldn't have any regular
14292 // classes. If nonRegularPrivateMethos is empty, this
14293 // means any possible override is non-regular, so we're
14294 // good.
14295 assertx(!cinfo->isRegularClass);
14296 if (info.nonRegularPrivateMeths.empty()) return true;
14297 return (bool)info.nonRegularPrivateMeths.count(meth);
14299 if (cinfo->isRegularClass) {
14300 // If this class is regular, the method on this class
14301 // should be marked as regular.
14302 assertx(info.regularMeths.count(meth));
14303 return true;
14305 // We know regularMeths is non-empty, and the size is at
14306 // most one. If this method is the (only) one in
14307 // regularMeths, it's not overridden by anything.
14308 return (bool)info.regularMeths.count(meth);
14311 if (!noOverrideRegular()) {
14312 mte.clearNoOverrideRegular();
14313 attribute_setter(mte.attrs, false, AttrNoOverride);
14314 } else if (!noOverride()) {
14315 attribute_setter(mte.attrs, false, AttrNoOverride);
14318 auto& entry = cinfo->methodFamilies.at(name);
14319 assertx(
14320 boost::get<FuncFamilyEntry::BothSingle>(&entry.m_meths) ||
14321 boost::get<FuncFamilyEntry::SingleAndNone>(&entry.m_meths)
14324 if (debug) {
14325 if (mte.attrs & AttrNoOverride) {
14326 always_assert(info.complete);
14327 always_assert(info.regularComplete);
14329 if (cinfo->isRegularClass ||
14330 cinfo->classGraph.mightHaveRegularSubclass()) {
14331 always_assert(info.regularMeths.size() == 1);
14332 always_assert(info.regularMeths.count(meth));
14333 always_assert(info.nonRegularPrivateMeths.empty());
14334 always_assert(info.nonRegularMeths.empty());
14335 } else {
14336 // If this class isn't regular, it could still have a
14337 // regular method which it inherited from a (regular)
14338 // parent. There should only be one method across all the
14339 // sets though.
14340 always_assert(
14341 info.regularMeths.size() +
14342 info.nonRegularPrivateMeths.size() +
14343 info.nonRegularMeths.size() == 1
14345 always_assert(
14346 info.regularMeths.count(meth) ||
14347 info.nonRegularPrivateMeths.count(meth) ||
14348 info.nonRegularMeths.count(meth)
14352 if (mte.hasPrivateAncestor() &&
14353 (cinfo->isRegularClass ||
14354 cinfo->classGraph.mightHaveRegularSubclass())) {
14355 always_assert(info.privateAncestor);
14356 } else {
14357 always_assert(!info.privateAncestor);
14359 } else {
14360 always_assert(!(cls.attrs & AttrNoOverride));
14364 // NB: Even if the method is AttrNoOverride, we might need to
14365 // change the FuncFamilyEntry. This class could be non-regular
14366 // and a child class could be regular. Even if the child class
14367 // doesn't override the method, it changes it from non-regular
14368 // to regular.
14369 entry = make_method_family_entry(index, name, std::move(info));
14371 if (mte.attrs & AttrNoOverride) {
14372 // However, even if the entry changes with AttrNoOverride,
14373 // it can only be these two cases.
14374 always_assert(
14375 boost::get<FuncFamilyEntry::BothSingle>(&entry.m_meths) ||
14376 boost::get<FuncFamilyEntry::SingleAndNone>(&entry.m_meths)
14382 * Interfaces can cause monotonicity violations. Suppose we have two
14383 * interfaces: I2 and I2. I1 declares a method named Foo. Every
14384 * class which implements I2 also implements I1 (therefore I2
14385 * implies I1). During analysis, a type is initially Obj<=I1 and we
14386 * resolve a call to Foo using I1's func families. After further
14387 * optimization, we narrow the type to Obj<=I2. Now when we go to
14388 * resolve a call to Foo using I2's func families, we find
14389 * nothing. Foo is declared in I1, not in I2, and interface methods
14390 * are not inherited. We use the fall back name-only tables, which
14391 * might give us a worse type than before. This is a monotonicity
14392 * violation because refining the object type gave us worse
14393 * analysis.
14395 * To avoid this, we expand an interface's (and abstract class'
14396 * which has similar issues) func families to include all methods
14397 * defined by *all* of it's (regular) implementations. So, in the
14398 * example above, we'd expand I2's func families to include Foo,
14399 * since all of I2's implements should define a Foo method (since
14400 * they also all implement I1).
14402 * Any MethInfos which are part of the abstract class/interface
14403 * method table has already been processed above. Any ones which
14404 * haven't are candidates for the above expansion and must also
14405 * be placed in the method families table. Note: we do not just
14406 * restrict this to just abstract classes or interfaces. This
14407 * class may be a child of an abstract class or interfaces and
14408 * we need to propagate these "expanded" methods so they're
14409 * available in the dependency when we actually process the
14410 * abstract class/interface in a later round.
14412 for (auto& [name, info] : data.methods) {
14413 if (cinfo->methods.count(name)) continue;
14414 assertx(!is_special_method_name(name));
14415 auto entry = make_method_family_entry(index, name, std::move(info));
14416 always_assert(
14417 cinfo->methodFamilies.emplace(name, std::move(entry)).second
14421 for (auto& prop : cls.properties) {
14422 if (bool(prop.attrs & AttrNoImplicitNullable) &&
14423 !(prop.attrs & (AttrStatic | AttrPrivate))) {
14424 attribute_setter(
14425 prop.attrs,
14426 !data.propsWithImplicitNullable.count(prop.name),
14427 AttrNoImplicitNullable
14431 if (!(prop.attrs & AttrSystemInitialValue)) continue;
14432 if (prop.val.m_type == KindOfUninit) {
14433 assertx(prop.attrs & AttrLateInit);
14434 continue;
14437 prop.val = [&] {
14438 if (!(prop.attrs & AttrNoImplicitNullable)) {
14439 return make_tv<KindOfNull>();
14441 // Give the 86reified_prop a special default value to
14442 // avoid pessimizing the inferred type (we want it to
14443 // always be a vec of a specific size).
14444 if (prop.name == s_86reified_prop.get()) {
14445 return get_default_value_of_reified_list(cls.userAttributes);
14447 return prop.typeConstraint.defaultValue();
14448 }();
14452 // Splits just store the data directly. Since this split hasn't
14453 // been processed yet (and no other job should process it), all of
14454 // the fields should be their default settings.
14455 for (auto& split : splits) {
14456 assertx(index.top.count(split->name));
14457 split->data = aggregate_data(index, split->name);
14458 // This split inherits all of the splits of their children.
14459 for (auto const child : split->children) {
14460 if (auto const c = folly::get_default(index.classInfos, child)) {
14461 split->classGraphs.emplace_back(c->classGraph);
14462 continue;
14464 auto const s = folly::get_default(index.splits, child);
14465 always_assert(s);
14466 split->classGraphs.insert(
14467 end(split->classGraphs),
14468 begin(s->classGraphs),
14469 end(s->classGraphs)
14472 std::sort(begin(split->classGraphs), end(split->classGraphs));
14473 split->classGraphs.erase(
14474 std::unique(begin(split->classGraphs), end(split->classGraphs)),
14475 end(split->classGraphs)
14477 split->children.clear();
14482 Job<BuildSubclassListJob> s_buildSubclassJob;
14484 struct SubclassWork {
14485 TSStringToOneT<std::unique_ptr<BuildSubclassListJob::Split>> allSplits;
14486 struct Bucket {
14487 std::vector<SString> classes;
14488 std::vector<SString> deps;
14489 std::vector<SString> splits;
14490 std::vector<SString> splitDeps;
14491 std::vector<SString> leafs;
14492 std::vector<BuildSubclassListJob::EdgeToSplit> edges;
14493 size_t cost{0};
14495 std::vector<std::vector<Bucket>> buckets;
14499 * Algorithm for assigning work for building subclass lists:
14501 * - Keep track of which classes have been processed and which ones
14502 * have not yet been.
14504 * - Keep looping until all classes have been processed. Each round of
14505 * the algorithm becomes a round of output.
14507 * - Iterate over all classes which haven't been
14508 * processed. Distinguish classes which are eligible for processing
14509 * or not. A class is eligible for processing if its transitive
14510 * dependencies are below the maximum size.
14512 * - Non-eligible classes are ignored and will be processed again next
14513 * round. However, if the class has more eligible direct children
14514 * than the bucket size, the class' children will be turned into
14515 * split nodes.
14517 * - Create split nodes. For each class (who we're splitting), use the
14518 * typical consistent hashing algorithm to assign each child to a
14519 * split node. Change the class' child list to contain the split
14520 * nodes instead of the children (this should shrink it
14521 * considerably). Each new split becomes a root.
14523 * - Assign each eligible class to a bucket. Use
14524 * assign_hierachial_work to map each eligible class to a bucket.
14526 * - Update the processed set. Any class which hasn't been processed
14527 * that round should have their dependency set shrunken. Processing
14528 * a class makes its dependency set be empty. So if a class wasn't
14529 * eligible, it should have a dependency which was. Therefore the
14530 * class' transitive dependencies should shrink. It should continue
14531 * to shrink until its eventually becomes eligible. The same happens
14532 * if the class' children are turned into split nodes. Each N
14533 * children is replaced with a single split (with no other
14534 * dependencies), so the class' dependencies should shrink. Thus,
14535 * the algorithm eventually terminates.
14538 // Dependency information for a class or split node.
14539 struct DepData {
14540 // Transitive dependencies (children) for this class.
14541 TSStringSet deps;
14542 // Any split nodes which are dependencies of this class.
14543 TSStringSet edges;
14544 // The number of direct children of this class which will be
14545 // processed this round.
14546 size_t processChildren{0};
14550 // Given a set of roots, greedily add roots and their children to buckets
14551 // via DFS traversal.
14552 template <typename GetDeps>
14553 std::vector<HierarchicalWorkBucket>
14554 dfs_bucketize(SubclassMetadata& subclassMeta,
14555 std::vector<SString> roots,
14556 const TSStringToOneT<std::vector<SString>>& splitImmDeps,
14557 size_t kMaxBucketSize,
14558 size_t maxClassIdx,
14559 bool alwaysCreateNew,
14560 const TSStringSet& leafs,
14561 const TSStringSet& processed, // already processed
14562 const GetDeps& getDeps) {
14563 TSStringSet visited;
14564 std::vector<std::vector<SString>> rootsToProcess;
14565 rootsToProcess.emplace_back();
14566 std::vector<size_t> rootsCost;
14568 auto const depsSize = [&] (SString cls) {
14569 return getDeps(cls, getDeps).deps.size();
14572 size_t cost = 0;
14574 auto const finishBucket = [&]() {
14575 if (!cost) return;
14576 rootsToProcess.emplace_back();
14577 rootsCost.emplace_back(cost);
14578 cost = 0;
14581 auto const addRoot = [&](SString c) {
14582 rootsToProcess.back().emplace_back(c);
14583 cost += depsSize(c);
14586 auto const processSubgraph = [&](SString cls) {
14587 assertx(!processed.count(cls));
14589 addRoot(cls);
14590 for (auto const& child : getDeps(cls, getDeps).deps) {
14591 if (processed.count(child)) continue;
14592 if (visited.count(child)) continue;
14593 visited.insert(child);
14594 // Leaves use special leaf-promotion logic in assign_hierarchial_work
14595 if (leafs.count(child)) continue;
14596 addRoot(child);
14598 if (cost < kMaxBucketSize) return;
14599 finishBucket();
14602 // Visit immediate children. Recurse until you find a node that has small
14603 // enough transitive deps.
14604 auto const visitSubgraph = [&](SString root, auto const& self) {
14605 if (processed.count(root) || visited.count(root)) return false;
14606 if (!depsSize(root)) return false;
14607 auto progress = false;
14608 visited.insert(root);
14610 assertx(IMPLIES(splitImmDeps.count(root), depsSize(root) <= kMaxBucketSize));
14611 if (depsSize(root) <= kMaxBucketSize) {
14612 processSubgraph(root);
14613 progress = true;
14614 } else {
14615 auto const immChildren = [&] {
14616 auto const it = subclassMeta.meta.find(root);
14617 assertx(it != end(subclassMeta.meta));
14618 return it->second.children;
14619 }();
14620 for (auto const& child : immChildren) progress |= self(child, self);
14622 return progress;
14625 // Sort the roots to keep it deterministic
14626 std::sort(
14627 begin(roots), end(roots),
14628 [&] (SString a, SString b) {
14629 auto const s1 = getDeps(a, getDeps).deps.size();
14630 auto const s2 = getDeps(b, getDeps).deps.size();
14631 if (s1 != s2) return s1 > s2;
14632 return string_data_lt_type{}(a, b);
14636 auto progress = false;
14637 for (auto const r : roots) {
14638 assertx(depsSize(r)); // Should never be processing one leaf
14639 progress |= visitSubgraph(r, visitSubgraph);
14641 assertx(progress);
14642 finishBucket();
14644 if (rootsToProcess.back().empty()) rootsToProcess.pop_back();
14646 auto const buckets = parallel::gen(
14647 rootsToProcess.size(),
14648 [&] (size_t bucketIdx) {
14649 auto numBuckets =
14650 (rootsCost[bucketIdx] + (kMaxBucketSize/2)) / kMaxBucketSize;
14651 if (!numBuckets) numBuckets = 1;
14652 return consistently_bucketize_by_num_buckets(rootsToProcess[bucketIdx],
14653 alwaysCreateNew ? rootsToProcess[bucketIdx].size() : numBuckets);
14657 std::vector<std::vector<SString>> flattened;
14658 for (auto const& b : buckets) {
14659 flattened.insert(flattened.end(), b.begin(), b.end());
14662 auto const work = build_hierarchical_work(
14663 flattened,
14664 maxClassIdx,
14665 [&] (SString c) {
14666 auto const& deps = getDeps(c, getDeps).deps;
14667 return std::make_pair(&deps, true);
14669 [&] (const TSStringSet&, size_t, SString c) -> Optional<size_t> {
14670 if (!leafs.count(c)) return std::nullopt;
14671 return subclassMeta.meta.at(c).idx;
14674 return work;
14677 // For each round:
14678 // While toProcess is not empty:
14679 // 1. Find transitive dep counts
14680 // 2. For each class, calculate splits, find roots, find rootLeafs
14681 // 3. For rootLeafs, consistently hash to make buckets
14682 // 4. For roots, assign subgraphs to buckets via greedy DFS. If buckets get too big,
14683 // split them via consistent hashing.
14684 SubclassWork build_subclass_lists_assign(SubclassMetadata subclassMeta) {
14685 trace_time trace{"build subclass lists assign"};
14686 trace.ignore_client_stats();
14688 constexpr size_t kBucketSize = 2000;
14689 constexpr size_t kMaxBucketSize = 25000;
14691 SubclassWork out;
14693 auto const maxClassIdx = subclassMeta.all.size();
14695 // A processed class/split is considered processed once it's
14696 // assigned to a bucket in a round. Once considered processed, it
14697 // will have no dependencies.
14698 TSStringSet processed;
14700 TSStringToOneT<std::unique_ptr<DepData>> splitDeps;
14701 TSStringToOneT<std::unique_ptr<BuildSubclassListJob::Split>> splitPtrs;
14702 TSStringSet leafs;
14703 TSStringToOneT<std::vector<SString>> splitImmDeps;
14705 // Keep creating rounds until all of the classes are assigned to a
14706 // bucket in a round.
14707 auto toProcess = std::move(subclassMeta.all);
14708 TSStringSet tp;
14709 if (debug) tp.insert(toProcess.begin(), toProcess.end());
14711 for (size_t round = 0; !toProcess.empty(); ++round) {
14712 // If we have this many rounds, something has gone wrong, because
14713 // it should require an astronomical amount of classes.
14714 always_assert_flog(
14715 round < 10,
14716 "Worklist still has {} items after {} rounds. "
14717 "This almost certainly means it's stuck in an infinite loop",
14718 toProcess.size(),
14719 round
14722 // The dependency information for every class, for just this
14723 // round. The information is calculated lazily and recursively by
14724 // findDeps.
14725 std::vector<LockFreeLazy<DepData>> deps{maxClassIdx};
14727 auto const findDeps = [&] (SString cls,
14728 auto const& self) -> const DepData& {
14729 // If it's processed, there's implicitly no dependencies
14730 static DepData empty;
14731 if (processed.count(cls)) return empty;
14733 // Look up the metadata for this class. If we don't find any,
14734 // assume that it's for a split.
14735 auto const it = subclassMeta.meta.find(cls);
14736 if (it == end(subclassMeta.meta)) {
14737 auto const it2 = splitDeps.find(cls);
14738 always_assert(it2 != end(splitDeps));
14739 return *it2->second;
14741 auto const& meta = it->second;
14742 auto const idx = meta.idx;
14743 assertx(idx < deps.size());
14745 // Now that we have the index into the dependency vector, look
14746 // it up, calculating it if it hasn't been already.
14747 return deps[idx].get(
14748 [&] {
14749 DepData out;
14750 for (auto const c : meta.children) {
14751 // At a minimum, we need the immediate deps in order to
14752 // construct the subclass lists for the parent.
14753 out.deps.emplace(c);
14754 if (splitDeps.count(c)) out.edges.emplace(c);
14755 auto const& childDeps = self(c, self);
14756 if (childDeps.deps.size() <= kMaxBucketSize) ++out.processChildren;
14757 out.deps.insert(begin(childDeps.deps), end(childDeps.deps));
14759 return out;
14764 auto const depsSize = [&] (SString cls) {
14765 return findDeps(cls, findDeps).deps.size();
14767 // If this class' children needs to be split into split nodes this
14768 // round. This happens if the number of direct children of this
14769 // class which are eligible for processing exceeds the bucket
14770 // size.
14771 auto const willSplitChildren = [&] (SString cls) {
14772 return findDeps(cls, findDeps).processChildren > kBucketSize;
14774 // If this class will be processed this round. A class will be
14775 // processed if it's dependencies are less than the maximum bucket
14776 // size.
14777 auto const willProcess = [&] (SString cls) {
14778 // NB: Not <=. When calculating splits, a class is included
14779 // among it's own dependencies so we need to leave space for one
14780 // more.
14781 return depsSize(cls) < kMaxBucketSize;
14784 // Process every remaining class in parallel and assign an action
14785 // to each:
14787 // This class will be processed this round and is a root.
14788 struct Root { SString cls; };
14789 struct RootLeaf { SString cls; };
14790 struct Child { SString cls; };
14791 // This class' children should be split. The class' child list
14792 // will be replaced with the new child list and splits created.
14793 struct Split {
14794 SString cls;
14795 std::vector<SString> children;
14796 struct Data {
14797 SString name;
14798 std::unique_ptr<DepData> deps;
14799 std::unique_ptr<BuildSubclassListJob::Split> ptr;
14800 std::vector<SString> children;
14802 std::vector<Data> splits;
14804 using Action = boost::variant<Root, Split, Child, RootLeaf>;
14806 auto const actions = parallel::map(
14807 toProcess,
14808 [&] (SString cls) {
14809 auto const& meta = subclassMeta.meta.at(cls);
14811 if (!willSplitChildren(cls)) {
14812 if (!meta.parents.empty()) return Action{ Child{cls} };
14813 if (meta.children.empty()) return Action{ RootLeaf{cls} };
14814 return Action{ Root{cls} };
14817 // Otherwise we're going to split some/all of this class'
14818 // children. Once we process those in this round, this class'
14819 // dependencies should be smaller and be able to be processed.
14820 Split split;
14821 split.cls = cls;
14822 split.splits = [&] {
14823 // Group all of the eligible children into buckets, and
14824 // split the buckets to ensure they remain below the maximum
14825 // size.
14826 auto const buckets = split_buckets(
14827 [&] {
14828 auto const numChildren = findDeps(cls, findDeps).processChildren;
14829 auto const numBuckets =
14830 (numChildren + kMaxBucketSize - 1) / kMaxBucketSize;
14831 assertx(numBuckets > 0);
14833 std::vector<std::vector<SString>> buckets;
14834 buckets.resize(numBuckets);
14835 for (auto const child : meta.children) {
14836 if (!willProcess(child)) continue;
14837 auto const idx =
14838 consistent_hash(child->hashStatic(), numBuckets);
14839 assertx(idx < numBuckets);
14840 buckets[idx].emplace_back(child);
14843 buckets.erase(
14844 std::remove_if(
14845 begin(buckets),
14846 end(buckets),
14847 [] (const std::vector<SString>& b) { return b.empty(); }
14849 end(buckets)
14852 assertx(!buckets.empty());
14853 return buckets;
14854 }(),
14855 kMaxBucketSize,
14856 [&] (SString child) -> const TSStringSet& {
14857 return findDeps(child, findDeps).deps;
14860 // Each bucket corresponds to a new split node, which will
14861 // contain the results for the children in that bucket.
14862 auto const numSplits = buckets.size();
14864 // Actually make the splits and fill their children list.
14865 std::vector<Split::Data> splits;
14866 splits.reserve(numSplits);
14867 for (size_t i = 0; i < numSplits; ++i) {
14868 // The names of a split node are arbitrary, but must be
14869 // unique and not collide with any actual classes.
14870 auto const name = makeStaticString(
14871 folly::sformat("{}_{}_split;{}", round, i, cls)
14874 auto deps = std::make_unique<DepData>();
14875 auto split =
14876 std::make_unique<BuildSubclassListJob::Split>(name, cls);
14877 std::vector<SString> children;
14879 for (auto const child : buckets[i]) {
14880 split->children.emplace_back(child);
14881 children.emplace_back(child);
14882 auto const& childDeps = findDeps(child, findDeps).deps;
14883 deps->deps.insert(begin(childDeps), end(childDeps));
14884 deps->deps.emplace(child);
14886 assertx(deps->deps.size() <= kMaxBucketSize);
14888 std::sort(
14889 begin(split->children),
14890 end(split->children),
14891 string_data_lt_type{}
14894 splits.emplace_back(
14895 Split::Data{
14896 name,
14897 std::move(deps),
14898 std::move(split),
14899 std::move(children)
14903 return splits;
14904 }();
14906 // Create the new children list for this class. The new
14907 // children list are any children which won't be processed,
14908 // and the new splits.
14909 for (auto const child : meta.children) {
14910 if (willProcess(child)) continue;
14911 split.children.emplace_back(child);
14913 for (auto const& [name, _, _2, _3] : split.splits) {
14914 split.children.emplace_back(name);
14917 return Action{ std::move(split) };
14921 assertx(actions.size() == toProcess.size());
14922 std::vector<SString> roots;
14923 roots.reserve(actions.size());
14924 std::vector<SString> rootLeafs;
14926 for (auto const& action : actions) {
14927 match<void>(
14928 action,
14929 [&] (Root r) {
14930 assertx(!subclassMeta.meta.at(r.cls).children.empty());
14931 roots.emplace_back(r.cls);
14933 [&] (RootLeaf r) {
14934 assertx(subclassMeta.meta.at(r.cls).children.empty());
14935 rootLeafs.emplace_back(r.cls);
14936 leafs.emplace(r.cls);
14938 [&] (Child n) {
14939 auto const& meta = subclassMeta.meta.at(n.cls);
14940 if (meta.children.empty()) leafs.emplace(n.cls);
14942 [&] (const Split& s) {
14943 auto& meta = subclassMeta.meta.at(s.cls);
14944 meta.children = s.children;
14945 if (meta.parents.empty()) {
14946 roots.emplace_back(s.cls);
14948 auto& splits = const_cast<std::vector<Split::Data>&>(s.splits);
14949 for (auto& [name, deps, ptr, children] : splits) {
14950 splitImmDeps.emplace(name, children);
14951 roots.emplace_back(name);
14952 splitDeps.emplace(name, std::move(deps));
14953 splitPtrs.emplace(name, std::move(ptr));
14959 auto work = dfs_bucketize(
14960 subclassMeta,
14961 std::move(roots),
14962 splitImmDeps,
14963 kMaxBucketSize,
14964 maxClassIdx,
14965 round > 0,
14966 leafs,
14967 processed,
14968 findDeps
14971 // Bucketize root leafs.
14972 // These are cheaper since we will only be calculating
14973 // name-only func family entries.
14974 for (auto& b : consistently_bucketize(rootLeafs, kMaxBucketSize)) {
14975 work.emplace_back(HierarchicalWorkBucket{ std::move(b) });
14978 std::vector<SString> markProcessed;
14979 markProcessed.reserve(actions.size());
14981 // The output of assign_hierarchical_work is just buckets with the
14982 // names. We need to map those to classes or edge nodes and put
14983 // them in the correct data structure in the output. If there's a
14984 // class dependency on a split node, we also need to record an
14985 // edge between them.
14986 auto const add = [&] (SString cls, auto& clsList,
14987 auto& splitList, auto& edgeList) {
14988 auto const it = splitPtrs.find(cls);
14989 if (it == end(splitPtrs)) {
14990 clsList.emplace_back(cls);
14991 for (auto const s : findDeps(cls, findDeps).edges) {
14992 edgeList.emplace_back(BuildSubclassListJob::EdgeToSplit{cls, s});
14994 } else {
14995 splitList.emplace_back(it->second->name);
14999 out.buckets.emplace_back();
15000 for (auto const& w : work) {
15001 assertx(w.uninstantiable.empty());
15002 out.buckets.back().emplace_back();
15003 auto& bucket = out.buckets.back().back();
15004 // Separate out any of the "roots" which are actually leafs.
15005 for (auto const cls : w.classes) {
15006 bucket.cost += depsSize(cls);
15007 markProcessed.emplace_back(cls);
15008 if (leafs.count(cls)) {
15009 leafs.erase(cls);
15010 bucket.leafs.emplace_back(cls);
15011 } else {
15012 add(cls, bucket.classes, bucket.splits, bucket.edges);
15015 for (auto const cls : w.deps) {
15016 add(cls, bucket.deps, bucket.splitDeps, bucket.edges);
15019 std::sort(
15020 begin(bucket.edges), end(bucket.edges),
15021 [] (const BuildSubclassListJob::EdgeToSplit& a,
15022 const BuildSubclassListJob::EdgeToSplit& b) {
15023 if (string_data_lt_type{}(a.cls, b.cls)) return true;
15024 if (string_data_lt_type{}(b.cls, a.cls)) return false;
15025 return string_data_lt_type{}(a.split, b.split);
15028 std::sort(begin(bucket.leafs), end(bucket.leafs), string_data_lt_type{});
15031 std::sort(
15032 begin(out.buckets.back()), end(out.buckets.back()),
15033 [] (const SubclassWork::Bucket& a,
15034 const SubclassWork::Bucket& b) {
15035 return a.cost > b.cost;
15039 // Update the processed set. We have to defer that until here
15040 // because we'd check it when building the buckets.
15041 processed.insert(begin(markProcessed), end(markProcessed));
15043 auto const before = toProcess.size();
15044 toProcess.erase(
15045 std::remove_if(
15046 begin(toProcess), end(toProcess),
15047 [&] (SString c) { return processed.count(c); }
15049 end(toProcess)
15051 always_assert(toProcess.size() < before);
15054 // Keep all split nodes created in the output
15055 for (auto& [name, p] : splitPtrs) out.allSplits.emplace(name, std::move(p));
15057 // Ensure we create an output for everything exactly once
15058 if (debug) {
15059 for (size_t round = 0; round < out.buckets.size(); ++round) {
15060 auto const& r = out.buckets[round];
15061 for (size_t i = 0; i < r.size(); ++i) {
15062 auto const& bucket = r[i];
15063 for (auto const c : bucket.classes) always_assert(tp.erase(c));
15064 for (auto const l : bucket.leafs) always_assert(tp.erase(l));
15067 assertx(tp.empty());
15070 if (Trace::moduleEnabled(Trace::hhbbc_index, 4)) {
15071 for (size_t round = 0; round < out.buckets.size(); ++round) {
15072 size_t nc = 0;
15073 size_t ns = 0;
15074 size_t nd = 0;
15075 size_t nsd = 0;
15076 size_t nl = 0;
15078 auto const& r = out.buckets[round];
15079 for (size_t i = 0; i < r.size(); ++i) {
15080 auto const& bucket = r[i];
15081 FTRACE(5, "build subclass lists round #{} work item #{}:\n", round, i);
15082 FTRACE(5, " classes ({}):\n", bucket.classes.size());
15083 for (auto const DEBUG_ONLY c : bucket.classes) FTRACE(6, " {}\n", c);
15084 FTRACE(5, " splits ({}):\n", bucket.splits.size());
15085 for (auto const DEBUG_ONLY s : bucket.splits) FTRACE(6, " {}\n", s);
15086 FTRACE(5, " deps ({}):\n", bucket.deps.size());
15087 for (auto const DEBUG_ONLY d : bucket.deps) FTRACE(6, " {}\n", d);
15088 FTRACE(5, " split deps ({}):\n", bucket.splitDeps.size());
15089 for (auto const DEBUG_ONLY s : bucket.splitDeps) {
15090 FTRACE(6, " {}\n", s);
15092 FTRACE(5, " leafs ({}):\n", bucket.leafs.size());
15093 for (auto const DEBUG_ONLY c : bucket.leafs) FTRACE(6, " {}\n", c);
15094 FTRACE(5, " edges ({}):\n", bucket.edges.size());
15095 for (DEBUG_ONLY auto const& e : bucket.edges) {
15096 FTRACE(6, " {} -> {}\n", e.cls, e.split);
15098 nc += bucket.classes.size();
15099 ns += bucket.splits.size();
15100 nd += bucket.deps.size();
15101 nsd += bucket.splitDeps.size();
15102 nl += bucket.leafs.size();
15104 FTRACE(4, "BSL round #{} stats\n"
15105 " {} buckets\n"
15106 " {} classes\n"
15107 " {} splits\n"
15108 " {} deps\n"
15109 " {} split deps\n"
15110 " {} leafs\n",
15111 round, r.size(), nc, ns, nd, nsd, nl
15116 return out;
15119 void build_subclass_lists(IndexData& index,
15120 SubclassMetadata meta,
15121 InitTypesMetadata& initTypesMeta) {
15122 trace_time tracer{"build subclass lists", index.sample};
15123 tracer.ignore_client_stats();
15125 using namespace folly::gen;
15127 // Mapping of splits to their Ref. We only upload a split when we're
15128 // going to run a job which it is part of the output.
15129 TSStringToOneT<UniquePtrRef<BuildSubclassListJob::Split>> splitsToRefs;
15131 FSStringToOneT<hphp_fast_set<FuncFamily2::Id>> funcFamilyDeps;
15133 // Use the metadata to assign to rounds and buckets.
15134 auto work = build_subclass_lists_assign(std::move(meta));
15136 // We need to defer updates to data structures until after all the
15137 // jobs in a round have completed. Otherwise we could update a ref
15138 // to a class at the same time another thread is reading it.
15139 struct Updates {
15140 std::vector<
15141 std::tuple<SString, UniquePtrRef<ClassInfo2>, UniquePtrRef<php::Class>>
15142 > classes;
15143 std::vector<
15144 std::pair<SString, UniquePtrRef<BuildSubclassListJob::Split>>
15145 > splits;
15146 std::vector<
15147 std::pair<FuncFamily2::Id, Ref<FuncFamilyGroup>>
15148 > funcFamilies;
15149 std::vector<
15150 std::pair<SString, hphp_fast_set<FuncFamily2::Id>>
15151 > funcFamilyDeps;
15152 std::vector<std::pair<SString, UniquePtrRef<ClassInfo2>>> leafs;
15153 std::vector<std::pair<SString, FuncFamilyEntry>> nameOnly;
15154 std::vector<std::pair<SString, SString>> candidateRegOnlyEquivs;
15155 TSStringToOneT<TSStringSet> cnsBases;
15158 auto const run = [&] (SubclassWork::Bucket bucket, size_t round)
15159 -> coro::Task<Updates> {
15160 co_await coro::co_reschedule_on_current_executor;
15162 if (bucket.classes.empty() &&
15163 bucket.splits.empty() &&
15164 bucket.leafs.empty()) {
15165 assertx(bucket.splitDeps.empty());
15166 co_return Updates{};
15169 // We shouldn't get closures or Closure in any of this.
15170 if (debug) {
15171 for (auto const c : bucket.classes) {
15172 always_assert(!c->tsame(s_Closure.get()));
15173 always_assert(!is_closure_name(c));
15175 for (auto const c : bucket.deps) {
15176 always_assert(!c->tsame(s_Closure.get()));
15177 always_assert(!is_closure_name(c));
15181 auto classes = from(bucket.classes)
15182 | map([&] (SString c) { return index.classInfoRefs.at(c); })
15183 | as<std::vector>();
15184 auto deps = from(bucket.deps)
15185 | map([&] (SString c) { return index.classInfoRefs.at(c); })
15186 | as<std::vector>();
15187 auto leafs = from(bucket.leafs)
15188 | map([&] (SString c) { return index.classInfoRefs.at(c); })
15189 | as<std::vector>();
15190 auto splits = from(bucket.splits)
15191 | map([&] (SString s) {
15192 std::unique_ptr<BuildSubclassListJob::Split> split =
15193 std::move(work.allSplits.at(s));
15194 assertx(split);
15195 return split;
15197 | as<std::vector>();
15198 auto splitDeps = from(bucket.splitDeps)
15199 | map([&] (SString s) { return splitsToRefs.at(s); })
15200 | as<std::vector>();
15201 auto phpClasses =
15202 (from(bucket.classes) + from(bucket.deps) + from(bucket.leafs))
15203 | map([&] (SString c) { return index.classRefs.at(c); })
15204 | as<std::vector>();
15206 std::vector<Ref<FuncFamilyGroup>> funcFamilies;
15207 if (round > 0) {
15208 // Provide the func families associated with any dependency
15209 // classes going into this job. We only need to do this after
15210 // the first round because in the first round all dependencies
15211 // are leafs and won't have any func families.
15212 for (auto const c : bucket.deps) {
15213 if (auto const deps = folly::get_ptr(funcFamilyDeps, c)) {
15214 for (auto const& d : *deps) {
15215 funcFamilies.emplace_back(index.funcFamilyRefs.at(d));
15219 // Keep the func families in deterministic order and avoid
15220 // duplicates.
15221 std::sort(begin(funcFamilies), end(funcFamilies));
15222 funcFamilies.erase(
15223 std::unique(begin(funcFamilies), end(funcFamilies)),
15224 end(funcFamilies)
15226 } else {
15227 assertx(funcFamilyDeps.empty());
15228 assertx(index.funcFamilyRefs.empty());
15231 // ClassInfos and any dependency splits should already be
15232 // stored. Any splits as output of the job, or edges need to be
15233 // uploaded, however.
15234 auto [splitRefs, edges, config] = co_await coro::collectAll(
15235 index.client->storeMulti(std::move(splits)),
15236 index.client->storeMulti(std::move(bucket.edges)),
15237 index.configRef->getCopy()
15240 Client::ExecMetadata metadata{
15241 .job_key = folly::sformat(
15242 "build subclass list {}",
15243 [&] {
15244 if (!bucket.classes.empty()) return bucket.classes[0];
15245 if (!bucket.splits.empty()) return bucket.splits[0];
15246 assertx(!bucket.leafs.empty());
15247 return bucket.leafs[0];
15252 auto results = co_await
15253 index.client->exec(
15254 s_buildSubclassJob,
15255 std::move(config),
15256 singleton_vec(
15257 std::make_tuple(
15258 std::move(classes),
15259 std::move(deps),
15260 std::move(leafs),
15261 std::move(splitRefs),
15262 std::move(splitDeps),
15263 std::move(phpClasses),
15264 std::move(edges),
15265 std::move(funcFamilies)
15268 std::move(metadata)
15270 // Every job is a single work-unit, so we should only ever get one
15271 // result for each one.
15272 assertx(results.size() == 1);
15273 auto& [cinfoRefs, outSplitRefs, clsRefs, ffRefs, leafRefs, outMetaRef]
15274 = results[0];
15275 assertx(cinfoRefs.size() == bucket.classes.size());
15276 assertx(outSplitRefs.size() == bucket.splits.size());
15277 assertx(clsRefs.size() == bucket.classes.size());
15278 assertx(leafRefs.size() == bucket.leafs.size());
15280 auto outMeta = co_await index.client->load(std::move(outMetaRef));
15281 assertx(outMeta.newFuncFamilyIds.size() == ffRefs.size());
15282 assertx(outMeta.funcFamilyDeps.size() == cinfoRefs.size());
15283 assertx(outMeta.regOnlyEquivCandidates.size() == cinfoRefs.size());
15285 Updates updates;
15286 updates.classes.reserve(bucket.classes.size());
15287 updates.splits.reserve(bucket.splits.size());
15288 updates.funcFamilies.reserve(outMeta.newFuncFamilyIds.size());
15289 updates.funcFamilyDeps.reserve(outMeta.funcFamilyDeps.size());
15290 updates.nameOnly.reserve(outMeta.nameOnly.size());
15291 updates.leafs.reserve(bucket.leafs.size());
15293 for (size_t i = 0, size = bucket.classes.size(); i < size; ++i) {
15294 updates.classes.emplace_back(bucket.classes[i], cinfoRefs[i], clsRefs[i]);
15296 for (size_t i = 0, size = bucket.splits.size(); i < size; ++i) {
15297 updates.splits.emplace_back(bucket.splits[i], outSplitRefs[i]);
15299 for (size_t i = 0, size = bucket.leafs.size(); i < size; ++i) {
15300 updates.leafs.emplace_back(bucket.leafs[i], leafRefs[i]);
15302 for (size_t i = 0, size = outMeta.newFuncFamilyIds.size(); i < size; ++i) {
15303 auto const ref = ffRefs[i];
15304 for (auto const& id : outMeta.newFuncFamilyIds[i]) {
15305 updates.funcFamilies.emplace_back(id, ref);
15308 for (size_t i = 0, size = outMeta.funcFamilyDeps.size(); i < size; ++i) {
15309 updates.funcFamilyDeps.emplace_back(
15310 bucket.classes[i],
15311 std::move(outMeta.funcFamilyDeps[i])
15314 updates.nameOnly = std::move(outMeta.nameOnly);
15315 for (size_t i = 0, size = outMeta.regOnlyEquivCandidates.size();
15316 i < size; ++i) {
15317 auto const name = bucket.classes[i];
15318 for (auto const c : outMeta.regOnlyEquivCandidates[i]) {
15319 updates.candidateRegOnlyEquivs.emplace_back(name, c);
15322 updates.cnsBases = std::move(outMeta.cnsBases);
15324 co_return updates;
15328 trace_time tracer2{"build subclass lists work", index.sample};
15330 for (size_t roundNum = 0; roundNum < work.buckets.size(); ++roundNum) {
15331 auto& round = work.buckets[roundNum];
15332 // In each round, run all of the work for each bucket
15333 // simultaneously, gathering up updates from each job.
15334 auto const updates = coro::blockingWait(coro::collectAllRange(
15335 from(round)
15336 | move
15337 | map([&] (SubclassWork::Bucket&& b) {
15338 return run(std::move(b), roundNum)
15339 .scheduleOn(index.executor->sticky());
15341 | as<std::vector>()
15344 // Apply the updates to ClassInfo refs. We can do this
15345 // concurrently because every ClassInfo is already in the map, so
15346 // we can update in place (without mutating the map).
15347 parallel::for_each(
15348 updates,
15349 [&] (const Updates& u) {
15350 for (auto const& [name, cinfo, cls] : u.classes) {
15351 index.classInfoRefs.at(name) = cinfo;
15352 index.classRefs.at(name) = cls;
15354 for (auto const& [name, cinfo] : u.leafs) {
15355 index.classInfoRefs.at(name) = cinfo;
15360 // However updating splitsToRefs cannot be, because we're mutating
15361 // the map by inserting into it. However there's a relatively
15362 // small number of splits, so this should be fine.
15363 parallel::parallel(
15364 [&] {
15365 for (auto const& u : updates) {
15366 for (auto const& [name, ref] : u.splits) {
15367 always_assert(splitsToRefs.emplace(name, ref).second);
15371 [&] {
15372 for (auto const& u : updates) {
15373 for (auto const& [id, ref] : u.funcFamilies) {
15374 // The same FuncFamily can be grouped into multiple
15375 // different groups. Prefer the group that's smaller and
15376 // if they're the same size, use the one with the lowest
15377 // id to keep determinism.
15378 auto const& [existing, inserted] =
15379 index.funcFamilyRefs.emplace(id, ref);
15380 if (inserted) continue;
15381 if (existing->second.id().m_size < ref.id().m_size) continue;
15382 if (ref.id().m_size < existing->second.id().m_size) {
15383 existing->second = ref;
15384 continue;
15386 if (existing->second.id() <= ref.id()) continue;
15387 existing->second = ref;
15391 [&] {
15392 for (auto& u : updates) {
15393 for (auto& [name, ids] : u.funcFamilyDeps) {
15394 always_assert(
15395 funcFamilyDeps.emplace(name, std::move(ids)).second
15400 [&] {
15401 for (auto& u : updates) {
15402 for (auto& [name, entry] : u.nameOnly) {
15403 initTypesMeta.nameOnlyFF[name].emplace_back(std::move(entry));
15405 for (auto [name, candidate] : u.candidateRegOnlyEquivs) {
15406 initTypesMeta.classes[name]
15407 .candidateRegOnlyEquivs.emplace(candidate);
15411 [&] {
15412 for (auto& u : updates) {
15413 for (auto& [n, o] : u.cnsBases) {
15414 always_assert(
15415 index.classToCnsBases.emplace(n, std::move(o)).second
15424 splitsToRefs.clear();
15425 funcFamilyDeps.clear();
15426 work.buckets.clear();
15427 work.allSplits.clear();
15430 //////////////////////////////////////////////////////////////////////
15433 * Initialize the return-types of functions and methods from their
15434 * type-hints. Also set AttrInitialSatisfiesTC on properties if
15435 * appropriate (which must be done after types are initialized).
15437 struct InitTypesJob {
15438 static std::string name() { return "hhbbc-init-types"; }
15439 static void init(const Config& config) {
15440 process_init(config.o, config.gd, false);
15441 ClassGraph::init();
15443 static void fini() { ClassGraph::destroy(); }
15445 using Output = Multi<
15446 Variadic<std::unique_ptr<php::Class>>,
15447 Variadic<std::unique_ptr<ClassInfo2>>,
15448 Variadic<std::unique_ptr<php::Func>>,
15449 Variadic<std::unique_ptr<FuncInfo2>>
15451 static Output run(Variadic<std::unique_ptr<php::Class>> classes,
15452 Variadic<std::unique_ptr<ClassInfo2>> cinfos,
15453 Variadic<std::unique_ptr<php::Func>> funcs,
15454 Variadic<std::unique_ptr<FuncInfo2>> finfos,
15455 Variadic<std::unique_ptr<ClassInfo2>> cinfoDeps) {
15456 LocalIndex index;
15458 for (auto const& cls : classes.vals) {
15459 always_assert(index.classes.emplace(cls->name, cls.get()).second);
15460 for (auto const& clo : cls->closures) {
15461 always_assert(index.classes.emplace(clo->name, clo.get()).second);
15465 // All of the classes which might be a regular only equivalent
15466 // have been provided to the job. So, we can now definitely set
15467 // the regular only equivalent (if necessary). We need to do this
15468 // before setting the initial types because we need that
15469 // information to canonicalize.
15470 for (auto const& cinfo : cinfos.vals) {
15471 always_assert(index.classInfos.emplace(cinfo->name, cinfo.get()).second);
15472 cinfo->classGraph.setRegOnlyEquivs();
15473 // If this is a regular class, we don't need the "expanded"
15474 // method family information anymore, so clear it here to save
15475 // memory.
15476 if (cinfo->isRegularClass) {
15477 folly::erase_if(
15478 cinfo->methodFamilies,
15479 [&] (auto const& e) { return !cinfo->methods.count(e.first); }
15483 for (auto const& clo : cinfo->closures) {
15484 always_assert(index.classInfos.emplace(clo->name, clo.get()).second);
15485 clo->classGraph.setRegOnlyEquivs();
15488 for (auto const& cinfo : cinfoDeps.vals) {
15489 always_assert(index.classInfos.emplace(cinfo->name, cinfo.get()).second);
15490 cinfo->classGraph.setRegOnlyEquivs();
15491 for (auto const& clo : cinfo->closures) {
15492 always_assert(index.classInfos.emplace(clo->name, clo.get()).second);
15493 clo->classGraph.setRegOnlyEquivs();
15497 auto const onCls = [&] (php::Class& cls, ClassInfo2& cinfo) {
15498 assertx(cls.name->tsame(cinfo.name));
15499 assertx(cinfo.funcInfos.size() == cls.methods.size());
15501 unresolve_missing(index, cls);
15502 set_bad_initial_prop_values(index, cls, cinfo);
15503 for (size_t i = 0, size = cls.methods.size(); i < size; ++i) {
15504 auto const& func = cls.methods[i];
15505 auto& finfo = cinfo.funcInfos[i];
15506 assertx(func->name == finfo->name);
15507 assertx(finfo->returnTy.is(BInitCell));
15508 finfo->returnTy = initial_return_type(index, *func);
15512 assertx(classes.vals.size() == cinfos.vals.size());
15513 for (size_t i = 0, size = classes.vals.size(); i < size; ++i) {
15514 auto& cls = classes.vals[i];
15515 auto& cinfo = cinfos.vals[i];
15516 onCls(*cls, *cinfo);
15518 assertx(cls->closures.size() == cinfo->closures.size());
15519 for (size_t j = 0, size2 = cls->closures.size(); j < size2; ++j) {
15520 auto& clo = cls->closures[j];
15521 auto& cloinfo = cinfo->closures[j];
15522 onCls(*clo, *cloinfo);
15526 assertx(funcs.vals.size() == finfos.vals.size());
15527 for (size_t i = 0, size = funcs.vals.size(); i < size; ++i) {
15528 auto const& func = funcs.vals[i];
15529 auto& finfo = finfos.vals[i];
15530 assertx(func->name == finfo->name);
15531 assertx(finfo->returnTy.is(BInitCell));
15532 unresolve_missing(index, *func);
15533 finfo->returnTy = initial_return_type(index, *func);
15536 return std::make_tuple(
15537 std::move(classes),
15538 std::move(cinfos),
15539 std::move(funcs),
15540 std::move(finfos)
15544 private:
15546 struct LocalIndex {
15547 TSStringToOneT<const ClassInfo2*> classInfos;
15548 TSStringToOneT<const php::Class*> classes;
15551 static void unresolve_missing(const LocalIndex& index, TypeConstraint& tc) {
15552 if (!tc.isSubObject()) return;
15553 auto const name = tc.clsName();
15554 if (index.classInfos.count(name)) return;
15555 FTRACE(
15556 4, "Unresolving type-constraint for '{}' because it does not exist\n",
15557 name
15559 tc.unresolve();
15562 static void unresolve_missing(const LocalIndex& index, php::Func& func) {
15563 for (auto& p : func.params) {
15564 unresolve_missing(index, p.typeConstraint);
15565 for (auto& ub : p.upperBounds.m_constraints) unresolve_missing(index, ub);
15567 unresolve_missing(index, func.retTypeConstraint);
15568 for (auto& ub : func.returnUBs.m_constraints) unresolve_missing(index, ub);
15571 static void unresolve_missing(const LocalIndex& index, php::Class& cls) {
15572 if (cls.attrs & AttrEnum) unresolve_missing(index, cls.enumBaseTy);
15573 for (auto& meth : cls.methods) unresolve_missing(index, *meth);
15574 for (auto& prop : cls.properties) {
15575 unresolve_missing(index, prop.typeConstraint);
15576 for (auto& ub : prop.ubs.m_constraints) unresolve_missing(index, ub);
15580 static Type initial_return_type(const LocalIndex& index, const php::Func& f) {
15581 Trace::Bump _{
15582 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(f.unit)
15585 auto const ty = [&] {
15586 // Return type of native functions is calculated differently.
15587 if (f.isNative) return native_function_return_type(&f);
15589 if ((f.attrs & AttrBuiltin) || f.isMemoizeWrapper) return TInitCell;
15591 if (f.isGenerator) {
15592 if (f.isAsync) {
15593 // Async generators always return AsyncGenerator object.
15594 return objExact(res::Class::get(s_AsyncGenerator.get()));
15596 // Non-async generators always return Generator object.
15597 return objExact(res::Class::get(s_Generator.get()));
15600 auto const make_type = [&] (const TypeConstraint& tc) {
15601 auto lookup = type_from_constraint(
15603 TInitCell,
15604 [&] (SString name) -> Optional<res::Class> {
15605 if (auto const ci = folly::get_default(index.classInfos, name)) {
15606 auto const c = res::Class::get(*ci);
15607 assertx(c.isComplete());
15608 return c;
15610 return std::nullopt;
15612 [&] () -> Optional<Type> {
15613 if (!f.cls) return std::nullopt;
15614 auto const& cls = [&] () -> const php::Class& {
15615 if (!f.cls->closureContextCls) return *f.cls;
15616 auto const c =
15617 folly::get_default(index.classes, f.cls->closureContextCls);
15618 always_assert_flog(
15620 "When processing return-type for {}, "
15621 "tried to access missing class {}",
15622 func_fullname(f),
15623 f.cls->closureContextCls
15625 return *c;
15626 }();
15627 if (cls.attrs & AttrTrait) return std::nullopt;
15628 auto const c = res::Class::get(cls.name);
15629 assertx(c.isComplete());
15630 return subCls(c, true);
15633 if (lookup.coerceClassToString == TriBool::Yes) {
15634 lookup.upper = promote_classish(std::move(lookup.upper));
15635 } else if (lookup.coerceClassToString == TriBool::Maybe) {
15636 lookup.upper |= TSStr;
15638 return unctx(std::move(lookup.upper));
15641 auto const process = [&] (const TypeConstraint& tc,
15642 const TypeIntersectionConstraint& ubs) {
15643 auto ret = TInitCell;
15644 ret = intersection_of(std::move(ret), make_type(tc));
15645 for (auto const& ub : ubs.m_constraints) {
15646 ret = intersection_of(std::move(ret), make_type(ub));
15648 return ret;
15651 auto ret = process(f.retTypeConstraint, f.returnUBs);
15652 if (f.hasInOutArgs && !ret.is(BBottom)) {
15653 std::vector<Type> types;
15654 types.reserve(f.params.size() + 1);
15655 types.emplace_back(std::move(ret));
15656 for (auto const& p : f.params) {
15657 if (!p.inout) continue;
15658 auto t = process(p.typeConstraint, p.upperBounds);
15659 if (t.is(BBottom)) return TBottom;
15660 types.emplace_back(std::move(t));
15662 std::reverse(begin(types)+1, end(types));
15663 ret = vec(std::move(types));
15666 if (f.isAsync) {
15667 // Async functions always return WaitH<T>, where T is the type
15668 // returned internally.
15669 return wait_handle(std::move(ret));
15671 return ret;
15672 }();
15674 FTRACE(3, "Initial return type for {}: {}\n",
15675 func_fullname(f), show(ty));
15676 return ty;
15679 static void set_bad_initial_prop_values(const LocalIndex& index,
15680 php::Class& cls,
15681 ClassInfo2& cinfo) {
15682 Trace::Bump _{
15683 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(cls.unit)
15686 assertx(cinfo.hasBadInitialPropValues);
15688 auto const isClosure = is_closure(cls);
15690 cinfo.hasBadInitialPropValues = false;
15691 for (auto& prop : cls.properties) {
15692 assertx(!(prop.attrs & AttrInitialSatisfiesTC));
15694 // Check whether the property's initial value satisfies it's
15695 // type-hint.
15696 auto const initialSatisfies = [&] {
15697 if (isClosure) return true;
15698 if (is_used_trait(cls)) return false;
15700 // Any property with an unresolved type-constraint here might
15701 // fatal when we initialize the class.
15702 if (prop.typeConstraint.isUnresolved()) return false;
15703 for (auto const& ub : prop.ubs.m_constraints) {
15704 if (ub.isUnresolved()) return false;
15707 if (prop.attrs & (AttrSystemInitialValue | AttrLateInit)) return true;
15709 auto const initial = from_cell(prop.val);
15710 if (initial.subtypeOf(BUninit)) return false;
15712 auto const make_type = [&] (const TypeConstraint& tc) {
15713 auto lookup = type_from_constraint(
15715 initial,
15716 [&] (SString name) -> Optional<res::Class> {
15717 if (auto const ci = folly::get_default(index.classInfos, name)) {
15718 auto const c = res::Class::get(*ci);
15719 assertx(c.isComplete());
15720 return c;
15722 return std::nullopt;
15724 [&] () -> Optional<Type> {
15725 auto const& ctx = [&] () -> const php::Class& {
15726 if (!cls.closureContextCls) return cls;
15727 auto const c =
15728 folly::get_default(index.classes, cls.closureContextCls);
15729 always_assert_flog(
15731 "When processing bad initial prop values for {}, "
15732 "tried to access missing class {}",
15733 cls.name,
15734 cls.closureContextCls
15736 return *c;
15737 }();
15738 if (ctx.attrs & AttrTrait) return std::nullopt;
15739 auto const c = res::Class::get(ctx.name);
15740 assertx(c.isComplete());
15741 return subCls(c, true);
15744 return unctx(std::move(lookup.lower));
15747 if (!initial.subtypeOf(make_type(prop.typeConstraint))) return false;
15748 for (auto const& ub : prop.ubs.m_constraints) {
15749 if (!initial.subtypeOf(make_type(ub))) return false;
15751 return true;
15752 }();
15754 if (initialSatisfies) {
15755 attribute_setter(prop.attrs, true, AttrInitialSatisfiesTC);
15756 } else {
15757 cinfo.hasBadInitialPropValues = true;
15764 * "Fixups" a php::Unit by removing specified funcs from it, and
15765 * adding specified classes. This is needed to add closures created
15766 * from trait flattening into their associated units. While we're
15767 * doing this, we also remove redundant meth caller funcs here
15768 * (because it's convenient).
15770 struct UnitFixupJob {
15771 static std::string name() { return "hhbbc-unit-fixup"; }
15772 static void init(const Config& config) {
15773 process_init(config.o, config.gd, false);
15775 static void fini() {}
15777 static std::unique_ptr<php::Unit> run(std::unique_ptr<php::Unit> unit,
15778 const InitTypesMetadata::Fixup& fixup) {
15779 SCOPE_ASSERT_DETAIL("unit") { return unit->filename->toCppString(); };
15781 if (!fixup.removeFunc.empty()) {
15782 // If we want to remove a func, it should be in this unit.
15783 auto DEBUG_ONLY erased = false;
15784 unit->funcs.erase(
15785 std::remove_if(
15786 begin(unit->funcs),
15787 end(unit->funcs),
15788 [&] (SString func) {
15789 // This is a kinda dumb O(N^2) algorithm, but these lists
15790 // are typicaly size 1.
15791 auto const erase = std::any_of(
15792 begin(fixup.removeFunc),
15793 end(fixup.removeFunc),
15794 [&] (SString remove) { return remove == func; }
15796 if (erase) erased = true;
15797 return erase;
15800 end(unit->funcs)
15802 assertx(erased);
15805 auto const before = unit->classes.size();
15806 unit->classes.insert(
15807 end(unit->classes),
15808 begin(fixup.addClass),
15809 end(fixup.addClass)
15811 // Only sort the newly added classes. The order of the existing
15812 // classes is visible to programs.
15813 std::sort(
15814 begin(unit->classes) + before,
15815 end(unit->classes),
15816 string_data_lt_type{}
15818 always_assert(
15819 std::adjacent_find(
15820 begin(unit->classes), end(unit->classes),
15821 string_data_tsame{}) == end(unit->classes)
15823 return unit;
15828 * BuildSubclassListJob produces name-only func family entries. This
15829 * job merges entries for the same name into one.
15831 struct AggregateNameOnlyJob: public BuildSubclassListJob {
15832 static std::string name() { return "hhbbc-aggregate-name-only"; }
15834 struct OutputMeta {
15835 std::vector<std::vector<FuncFamily2::Id>> newFuncFamilyIds;
15836 std::vector<FuncFamilyEntry> nameOnly;
15837 template <typename SerDe> void serde(SerDe& sd) {
15838 ScopedStringDataIndexer _;
15839 sd(newFuncFamilyIds)
15840 (nameOnly)
15844 using Output = Multi<
15845 Variadic<FuncFamilyGroup>,
15846 OutputMeta
15849 static Output
15850 run(std::vector<std::pair<SString, std::vector<FuncFamilyEntry>>> allEntries,
15851 Variadic<FuncFamilyGroup> funcFamilies) {
15852 LocalIndex index;
15854 for (auto& group : funcFamilies.vals) {
15855 for (auto& ff : group.m_ffs) {
15856 auto const id = ff->m_id;
15857 // We could have multiple groups which contain the same
15858 // FuncFamily, so don't assert uniqueness here. We'll just
15859 // take the first one we see (they should all be equivalent).
15860 index.funcFamilies.emplace(id, std::move(ff));
15864 OutputMeta meta;
15866 for (auto const& [name, entries] : allEntries) {
15867 Data::MethInfo info;
15868 info.complete = false;
15869 info.regularComplete = false;
15871 for (auto const& entry : entries) {
15872 auto entryInfo = meth_info_from_func_family_entry(index, entry);
15873 for (auto const& meth : entryInfo.regularMeths) {
15874 if (info.regularMeths.count(meth)) continue;
15875 info.regularMeths.emplace(meth);
15876 info.nonRegularPrivateMeths.erase(meth);
15877 info.nonRegularMeths.erase(meth);
15879 for (auto const& meth : entryInfo.nonRegularPrivateMeths) {
15880 if (info.regularMeths.count(meth) ||
15881 info.nonRegularPrivateMeths.count(meth)) {
15882 continue;
15884 info.nonRegularPrivateMeths.emplace(meth);
15885 info.nonRegularMeths.erase(meth);
15887 for (auto const& meth : entryInfo.nonRegularMeths) {
15888 if (info.regularMeths.count(meth) ||
15889 info.nonRegularPrivateMeths.count(meth) ||
15890 info.nonRegularMeths.count(meth)) {
15891 continue;
15893 info.nonRegularMeths.emplace(meth);
15896 if (entryInfo.allStatic) {
15897 if (!info.allStatic) {
15898 info.allStatic = std::move(*entryInfo.allStatic);
15899 } else {
15900 *info.allStatic |= *entryInfo.allStatic;
15903 if (entryInfo.regularStatic) {
15904 if (!info.regularStatic) {
15905 info.regularStatic = std::move(*entryInfo.regularStatic);
15906 } else {
15907 *info.regularStatic |= *entryInfo.regularStatic;
15912 meta.nameOnly.emplace_back(
15913 make_method_family_entry(index, name, std::move(info))
15917 Variadic<FuncFamilyGroup> funcFamilyGroups;
15918 group_func_families(index, funcFamilyGroups.vals, meta.newFuncFamilyIds);
15920 return std::make_tuple(
15921 std::move(funcFamilyGroups),
15922 std::move(meta)
15927 Job<InitTypesJob> s_initTypesJob;
15928 Job<UnitFixupJob> s_unitFixupJob;
15929 Job<AggregateNameOnlyJob> s_aggregateNameOnlyJob;
15931 // Initialize return-types, fixup units, and aggregate name-only
15932 // func-families all at once.
15933 void init_types(IndexData& index, InitTypesMetadata meta) {
15934 trace_time tracer{"init types", index.sample};
15936 constexpr size_t kTypesBucketSize = 2000;
15937 constexpr size_t kFixupsBucketSize = 3000;
15938 constexpr size_t kAggregateBucketSize = 3000;
15940 auto typeBuckets = consistently_bucketize(
15941 [&] {
15942 // Temporarily suppress case collision logging
15943 auto oldLogLevel = Cfg::Eval::LogTsameCollisions;
15944 Cfg::Eval::LogTsameCollisions = 0;
15945 SCOPE_EXIT { Cfg::Eval::LogTsameCollisions = oldLogLevel; };
15947 std::vector<SString> roots;
15948 roots.reserve(meta.classes.size() + meta.funcs.size());
15949 for (auto const& [name, _] : meta.classes) {
15950 roots.emplace_back(name);
15952 for (auto const& [name, _] : meta.funcs) {
15953 // A class and a func could have the same name. Avoid
15954 // duplicates. If we do have a name collision it just means
15955 // the func and class will be assigned to the same bucket.
15956 if (meta.classes.count(name)) continue;
15957 roots.emplace_back(name);
15959 return roots;
15960 }(),
15961 kTypesBucketSize
15964 auto fixupBuckets = consistently_bucketize(
15965 [&] {
15966 std::vector<SString> sorted;
15967 sorted.reserve(meta.fixups.size());
15968 for (auto& [unit, _] : meta.fixups) sorted.emplace_back(unit);
15969 std::sort(sorted.begin(), sorted.end(), string_data_lt{});
15970 return sorted;
15971 }(),
15972 kFixupsBucketSize
15975 auto aggregateBuckets = consistently_bucketize(
15976 [&] {
15977 std::vector<SString> sorted;
15978 sorted.reserve(meta.nameOnlyFF.size());
15979 for (auto const& [name, entries] : meta.nameOnlyFF) {
15980 if (entries.size() <= 1) {
15981 // If there's only one entry for a name, there's nothing to
15982 // aggregate, and can be inserted directly as the final
15983 // result.
15984 always_assert(
15985 index.nameOnlyMethodFamilies.emplace(name, entries[0]).second
15987 continue;
15989 // Otherwise insert a dummy entry. This will let us update the
15990 // entry later from multiple threads without having to mutate
15991 // the map.
15992 always_assert(
15993 index.nameOnlyMethodFamilies.emplace(name, FuncFamilyEntry{}).second
15995 sorted.emplace_back(name);
15997 std::sort(begin(sorted), end(sorted), string_data_lt{});
15998 return sorted;
15999 }(),
16000 kAggregateBucketSize
16003 // We want to avoid updating any Index data-structures until after
16004 // all jobs have read their inputs. We use the latch to block tasks
16005 // until all tasks have passed the point of reading their inputs.
16006 CoroLatch typesLatch{typeBuckets.size()};
16008 auto const runTypes = [&] (std::vector<SString> work) -> coro::Task<void> {
16009 co_await coro::co_reschedule_on_current_executor;
16011 if (work.empty()) {
16012 typesLatch.count_down();
16013 co_return;
16016 std::vector<UniquePtrRef<php::Class>> classes;
16017 std::vector<UniquePtrRef<ClassInfo2>> cinfos;
16018 std::vector<UniquePtrRef<php::Func>> funcs;
16019 std::vector<UniquePtrRef<FuncInfo2>> finfos;
16020 std::vector<UniquePtrRef<ClassInfo2>> cinfoDeps;
16022 TSStringSet roots;
16023 std::vector<SString> classNames;
16024 std::vector<SString> funcNames;
16026 roots.reserve(work.size());
16027 classNames.reserve(work.size());
16029 for (auto const w : work) {
16030 if (meta.classes.count(w)) {
16031 always_assert(roots.emplace(w).second);
16032 classNames.emplace_back(w);
16034 if (meta.funcs.count(w)) funcNames.emplace_back(w);
16037 // Add a dependency to the job. A class is a dependency if it
16038 // shows up in a class' type-hints, or if it's a potential
16039 // reg-only equivalent.
16040 auto const addDep = [&] (SString dep, bool addEquiv) {
16041 if (!meta.classes.count(dep) || roots.count(dep)) return;
16042 cinfoDeps.emplace_back(index.classInfoRefs.at(dep));
16043 if (!addEquiv) return;
16044 if (auto const cls = folly::get_ptr(meta.classes, dep)) {
16045 for (auto const d : cls->candidateRegOnlyEquivs) {
16046 if (!meta.classes.count(d) || roots.count(d)) continue;
16047 cinfoDeps.emplace_back(index.classInfoRefs.at(d));
16052 for (auto const w : work) {
16053 if (auto const cls = folly::get_ptr(meta.classes, w)) {
16054 classes.emplace_back(index.classRefs.at(w));
16055 cinfos.emplace_back(index.classInfoRefs.at(w));
16056 for (auto const d : cls->deps) addDep(d, true);
16057 for (auto const d : cls->candidateRegOnlyEquivs) addDep(d, false);
16059 // Not else if. A name can correspond to both a class and a
16060 // func.
16061 if (auto const func = folly::get_ptr(meta.funcs, w)) {
16062 funcs.emplace_back(index.funcRefs.at(w));
16063 finfos.emplace_back(index.funcInfoRefs.at(w));
16064 for (auto const d : func->deps) addDep(d, true);
16067 addDep(s_Awaitable.get(), true);
16068 addDep(s_AsyncGenerator.get(), true);
16069 addDep(s_Generator.get(), true);
16071 // Record that we've read our inputs
16072 typesLatch.count_down();
16074 std::sort(begin(cinfoDeps), end(cinfoDeps));
16075 cinfoDeps.erase(
16076 std::unique(begin(cinfoDeps), end(cinfoDeps)),
16077 end(cinfoDeps)
16080 auto config = co_await index.configRef->getCopy();
16082 Client::ExecMetadata metadata{
16083 .job_key = folly::sformat("init types {}", work[0])
16086 auto results = co_await
16087 index.client->exec(
16088 s_initTypesJob,
16089 std::move(config),
16090 singleton_vec(
16091 std::make_tuple(
16092 std::move(classes),
16093 std::move(cinfos),
16094 std::move(funcs),
16095 std::move(finfos),
16096 std::move(cinfoDeps)
16099 std::move(metadata)
16101 assertx(results.size() == 1);
16102 auto& [classRefs, cinfoRefs, funcRefs, finfoRefs] = results[0];
16103 assertx(classRefs.size() == classNames.size());
16104 assertx(cinfoRefs.size() == classNames.size());
16105 assertx(funcRefs.size() == funcNames.size());
16106 assertx(finfoRefs.size() == funcNames.size());
16108 // Wait for all tasks to finish reading from the Index ref tables
16109 // before starting to overwrite them.
16110 co_await typesLatch.wait();
16112 for (size_t i = 0, size = classNames.size(); i < size; ++i) {
16113 auto const name = classNames[i];
16114 index.classRefs.at(name) = std::move(classRefs[i]);
16115 index.classInfoRefs.at(name) = std::move(cinfoRefs[i]);
16117 for (size_t i = 0, size = funcNames.size(); i < size; ++i) {
16118 auto const name = funcNames[i];
16119 index.funcRefs.at(name) = std::move(funcRefs[i]);
16120 index.funcInfoRefs.at(name) = std::move(finfoRefs[i]);
16123 co_return;
16126 auto const runFixups = [&] (std::vector<SString> units) -> coro::Task<void> {
16127 co_await coro::co_reschedule_on_current_executor;
16129 if (units.empty()) co_return;
16131 std::vector<InitTypesMetadata::Fixup> fixups;
16133 // Gather up the fixups and ensure a deterministic ordering.
16134 fixups.reserve(units.size());
16135 for (auto const unit : units) {
16136 auto f = std::move(meta.fixups.at(unit));
16137 assertx(!f.addClass.empty() || !f.removeFunc.empty());
16138 std::sort(f.addClass.begin(), f.addClass.end(), string_data_lt_type{});
16139 std::sort(f.removeFunc.begin(), f.removeFunc.end(), string_data_lt{});
16140 fixups.emplace_back(std::move(f));
16142 auto fixupRefs = co_await index.client->storeMulti(std::move(fixups));
16143 assertx(fixupRefs.size() == units.size());
16145 std::vector<
16146 std::tuple<UniquePtrRef<php::Unit>, Ref<InitTypesMetadata::Fixup>>
16147 > inputs;
16148 inputs.reserve(units.size());
16150 for (size_t i = 0, size = units.size(); i < size; ++i) {
16151 inputs.emplace_back(
16152 index.unitRefs.at(units[i]),
16153 std::move(fixupRefs[i])
16157 Client::ExecMetadata metadata{
16158 .job_key = folly::sformat("fixup units {}", units[0])
16161 auto config = co_await index.configRef->getCopy();
16162 auto outputs = co_await index.client->exec(
16163 s_unitFixupJob,
16164 std::move(config),
16165 std::move(inputs),
16166 std::move(metadata)
16168 assertx(outputs.size() == units.size());
16170 // Every unit is already in the Index table, so we can overwrite
16171 // them without locking.
16172 for (size_t i = 0, size = units.size(); i < size; ++i) {
16173 index.unitRefs.at(units[i]) = std::move(outputs[i]);
16176 co_return;
16179 struct AggregateUpdates {
16180 std::vector<
16181 std::pair<FuncFamily2::Id, Ref<FuncFamilyGroup>>
16182 > funcFamilies;
16185 auto const runAggregate = [&] (std::vector<SString> names)
16186 -> coro::Task<AggregateUpdates> {
16187 co_await coro::co_reschedule_on_current_executor;
16189 if (names.empty()) co_return AggregateUpdates{};
16191 std::vector<std::pair<SString, std::vector<FuncFamilyEntry>>> entries;
16192 std::vector<Ref<FuncFamilyGroup>> funcFamilies;
16194 entries.reserve(names.size());
16195 // Extract out any func families the entries refer to, so they can
16196 // be provided to the job.
16197 for (auto const n : names) {
16198 auto& e = meta.nameOnlyFF.at(n);
16199 entries.emplace_back(n, std::move(e));
16200 for (auto const& entry : entries.back().second) {
16201 match<void>(
16202 entry.m_meths,
16203 [&] (const FuncFamilyEntry::BothFF& e) {
16204 funcFamilies.emplace_back(index.funcFamilyRefs.at(e.m_ff));
16206 [&] (const FuncFamilyEntry::FFAndSingle& e) {
16207 funcFamilies.emplace_back(index.funcFamilyRefs.at(e.m_ff));
16209 [&] (const FuncFamilyEntry::FFAndNone& e) {
16210 funcFamilies.emplace_back(index.funcFamilyRefs.at(e.m_ff));
16212 [&] (const FuncFamilyEntry::BothSingle&) {},
16213 [&] (const FuncFamilyEntry::SingleAndNone&) {},
16214 [&] (const FuncFamilyEntry::None&) {}
16219 std::sort(begin(funcFamilies), end(funcFamilies));
16220 funcFamilies.erase(
16221 std::unique(begin(funcFamilies), end(funcFamilies)),
16222 end(funcFamilies)
16225 auto [entriesRef, config] = co_await coro::collectAll(
16226 index.client->store(std::move(entries)),
16227 index.configRef->getCopy()
16230 Client::ExecMetadata metadata{
16231 .job_key = folly::sformat("aggregate name-only {}", names[0])
16234 auto results = co_await
16235 index.client->exec(
16236 s_aggregateNameOnlyJob,
16237 std::move(config),
16238 singleton_vec(
16239 std::make_tuple(std::move(entriesRef), std::move(funcFamilies))
16241 std::move(metadata)
16243 assertx(results.size() == 1);
16244 auto& [ffRefs, outMetaRef] = results[0];
16246 auto outMeta = co_await index.client->load(std::move(outMetaRef));
16247 assertx(outMeta.newFuncFamilyIds.size() == ffRefs.size());
16248 assertx(outMeta.nameOnly.size() == names.size());
16250 // Update the dummy entries with the actual result.
16251 for (size_t i = 0, size = names.size(); i < size; ++i) {
16252 auto& old = index.nameOnlyMethodFamilies.at(names[i]);
16253 assertx(boost::get<FuncFamilyEntry::None>(&old.m_meths));
16254 old = std::move(outMeta.nameOnly[i]);
16257 AggregateUpdates updates;
16258 updates.funcFamilies.reserve(outMeta.newFuncFamilyIds.size());
16259 for (size_t i = 0, size = outMeta.newFuncFamilyIds.size(); i < size; ++i) {
16260 auto const ref = ffRefs[i];
16261 for (auto const& id : outMeta.newFuncFamilyIds[i]) {
16262 updates.funcFamilies.emplace_back(id, ref);
16266 co_return updates;
16269 auto const runAggregateCombine = [&] (auto tasks) -> coro::Task<void> {
16270 co_await coro::co_reschedule_on_current_executor;
16272 auto const updates = co_await coro::collectAllRange(std::move(tasks));
16274 for (auto const& u : updates) {
16275 for (auto const& [id, ref] : u.funcFamilies) {
16276 // The same FuncFamily can be grouped into multiple
16277 // different groups. Prefer the group that's smaller and
16278 // if they're the same size, use the one with the lowest
16279 // id to keep determinism.
16280 auto const& [existing, inserted] =
16281 index.funcFamilyRefs.emplace(id, ref);
16282 if (inserted) continue;
16283 if (existing->second.id().m_size < ref.id().m_size) continue;
16284 if (ref.id().m_size < existing->second.id().m_size) {
16285 existing->second = ref;
16286 continue;
16288 if (existing->second.id() <= ref.id()) continue;
16289 existing->second = ref;
16293 co_return;
16296 using namespace folly::gen;
16298 std::vector<coro::TaskWithExecutor<void>> tasks;
16299 tasks.reserve(typeBuckets.size() + fixupBuckets.size() + 1);
16301 // Temporarily suppress case collision logging
16302 auto oldTypeLogLevel = Cfg::Eval::LogTsameCollisions;
16303 Cfg::Eval::LogTsameCollisions = 0;
16304 SCOPE_EXIT {
16305 Cfg::Eval::LogTsameCollisions = oldTypeLogLevel;
16308 for (auto& work : typeBuckets) {
16309 tasks.emplace_back(
16310 runTypes(std::move(work)).scheduleOn(index.executor->sticky())
16313 for (auto& work : fixupBuckets) {
16314 tasks.emplace_back(
16315 runFixups(std::move(work)).scheduleOn(index.executor->sticky())
16318 auto subTasks = from(aggregateBuckets)
16319 | move
16320 | map([&] (std::vector<SString>&& work) {
16321 return runAggregate(
16322 std::move(work)
16323 ).scheduleOn(index.executor->sticky());
16325 | as<std::vector>();
16326 tasks.emplace_back(
16327 runAggregateCombine(
16328 std::move(subTasks)
16329 ).scheduleOn(index.executor->sticky())
16332 coro::blockingWait(coro::collectAllRange(std::move(tasks)));
16335 //////////////////////////////////////////////////////////////////////
16337 Index::Input::UnitMeta make_native_unit_meta(IndexData& index) {
16338 auto unit = make_native_unit();
16339 auto const name = unit->filename;
16341 std::vector<std::pair<SString, bool>> constants;
16342 constants.reserve(unit->constants.size());
16343 for (auto const& cns : unit->constants) {
16344 constants.emplace_back(cns->name, type(cns->val) == KindOfUninit);
16347 auto unitRef = coro::blockingWait(index.client->store(std::move(unit)));
16348 Index::Input::UnitMeta meta{ std::move(unitRef), name };
16349 meta.constants = std::move(constants);
16350 return meta;
16353 // Set up the async state, populate the (initial) table of
16354 // extern-worker refs in the Index, and build some metadata needed for
16355 // class flattening.
16356 IndexFlattenMetadata make_remote(IndexData& index,
16357 Config config,
16358 Index::Input input,
16359 std::unique_ptr<TicketExecutor> executor,
16360 std::unique_ptr<Client> client,
16361 DisposeCallback dispose) {
16362 trace_time tracer("make remote");
16363 tracer.ignore_client_stats();
16365 assertx(input.classes.size() == input.classBC.size());
16366 assertx(input.funcs.size() == input.funcBC.size());
16368 index.executor = std::move(executor);
16369 index.client = std::move(client);
16370 index.disposeClient = std::move(dispose);
16372 // Kick off the storage of the global config. We'll start early so
16373 // it will (hopefully) be done before we need it.
16374 index.configRef = std::make_unique<CoroAsyncValue<Ref<Config>>>(
16375 [&index, config = std::move(config)] () mutable {
16376 return index.client->store(std::move(config));
16378 index.executor->sticky()
16381 // Create a fake unit to store native constants and add it as an
16382 // input.
16383 input.units.emplace_back(make_native_unit_meta(index));
16385 IndexFlattenMetadata flattenMeta;
16386 SStringToOneT<SString> methCallerUnits;
16388 flattenMeta.cls.reserve(input.classes.size());
16389 flattenMeta.allCls.reserve(input.classes.size());
16390 flattenMeta.allFuncs.reserve(input.funcs.size());
16392 // Add unit and class information to their appropriate tables. This
16393 // is also where we'll detect duplicate funcs and class names (which
16394 // should be caught earlier during parsing).
16395 for (auto& unit : input.units) {
16396 FTRACE(5, "unit {} -> {}\n", unit.name, unit.unit.id().toString());
16398 for (auto& typeMapping : unit.typeMappings) {
16399 auto const name = typeMapping.name;
16400 auto const isTypeAlias = typeMapping.isTypeAlias;
16401 always_assert_flog(
16402 flattenMeta.typeMappings.emplace(name, std::move(typeMapping)).second,
16403 "Duplicate type-mapping: {}",
16404 name
16406 if (isTypeAlias) {
16407 always_assert(index.typeAliasToUnit.emplace(name, unit.name).second);
16408 index.unitsWithTypeAliases.emplace(unit.name);
16412 always_assert_flog(
16413 index.unitRefs.emplace(unit.name, std::move(unit.unit)).second,
16414 "Duplicate unit: {}",
16415 unit.name
16418 for (auto const& [cnsName, hasInit] : unit.constants) {
16419 always_assert_flog(
16420 index.constantToUnit.emplace(
16421 cnsName,
16422 std::make_pair(unit.name, hasInit)
16423 ).second,
16424 "Duplicate constant: {}",
16425 cnsName
16430 for (auto& cls : input.classes) {
16431 FTRACE(5, "class {} -> {}\n", cls.name, cls.cls.id().toString());
16432 always_assert_flog(
16433 index.classRefs.emplace(cls.name, std::move(cls.cls)).second,
16434 "Duplicate class: {}",
16435 cls.name
16437 always_assert(index.classToUnit.emplace(cls.name, cls.unit).second);
16439 auto& meta = flattenMeta.cls[cls.name];
16440 if (cls.closureFunc) {
16441 assertx(cls.closures.empty());
16442 index.funcToClosures[cls.closureFunc].emplace(cls.name);
16443 index.closureToFunc.emplace(cls.name, cls.closureFunc);
16444 meta.isClosure = true;
16446 index.classToClosures[cls.name].insert(
16447 begin(cls.closures),
16448 end(cls.closures)
16450 for (auto const clo : cls.closures) {
16451 index.closureToClass.emplace(clo, cls.name);
16454 meta.deps.insert(begin(cls.dependencies), end(cls.dependencies));
16455 meta.unresolvedTypes = std::move(cls.unresolvedTypes);
16456 meta.idx = flattenMeta.allCls.size();
16457 flattenMeta.allCls.emplace_back(cls.name);
16459 if (cls.has86init) index.classesWith86Inits.emplace(cls.name);
16461 if (cls.typeMapping) {
16462 auto const name = cls.typeMapping->name;
16463 always_assert_flog(
16464 flattenMeta.typeMappings.emplace(
16465 name, std::move(*cls.typeMapping)
16466 ).second,
16467 "Duplicate type-mapping: {}",
16468 name
16473 // Funcs have an additional wrinkle, however. A func might be a meth
16474 // caller. Meth callers are special in that they might be present
16475 // (with the same name) in multiple units. However only one "wins"
16476 // and is actually emitted in the repo. We detect that here and
16477 // select a winner. The "losing" meth callers will be actually
16478 // removed from their unit after class flattening.
16479 for (auto& func : input.funcs) {
16480 FTRACE(5, "func {} -> {}\n", func.name, func.func.id().toString());
16482 if (func.methCaller) {
16483 // If this meth caller a duplicate of one we've already seen?
16484 auto const [existing, emplaced] =
16485 methCallerUnits.emplace(func.name, func.unit);
16486 if (!emplaced) {
16487 // It is. The duplicate shouldn't be in the same unit,
16488 // however.
16489 always_assert_flog(
16490 existing->second != func.unit,
16491 "Duplicate meth-caller {} in same unit {}",
16492 func.name,
16493 func.unit
16495 // The winner is the one with the unit with the "lesser"
16496 // name. This is completely arbitrary.
16497 if (string_data_lt{}(func.unit, existing->second)) {
16498 // This one wins. Schedule the older entry for deletion and
16499 // take over it's position in the map.
16500 FTRACE(
16501 4, " meth caller {} from unit {} taking priority over unit {}",
16502 func.name, func.unit, existing->second
16504 flattenMeta.unitDeletions[existing->second].emplace_back(func.name);
16505 existing->second = func.unit;
16506 index.funcRefs.at(func.name) = std::move(func.func);
16507 index.funcToUnit.at(func.name) = func.unit;
16508 } else {
16509 // This one loses. Schedule it for deletion.
16510 flattenMeta.unitDeletions[func.unit].emplace_back(func.name);
16512 continue;
16514 // It's not. Treat it like anything else.
16517 // If not a meth caller, treat it like anything else.
16518 always_assert_flog(
16519 index.funcRefs.emplace(func.name, std::move(func.func)).second,
16520 "Duplicate func: {}",
16521 func.name
16524 index.funcToUnit.emplace(func.name, func.unit);
16525 if (Constant::nameFromFuncName(func.name)) {
16526 index.constantInitFuncs.emplace(func.name);
16529 auto& meta = flattenMeta.func[func.name];
16530 meta.unresolvedTypes = std::move(func.unresolvedTypes);
16532 flattenMeta.allFuncs.emplace_back(func.name);
16535 for (auto& bc : input.classBC) {
16536 FTRACE(5, "class bytecode {} -> {}\n", bc.name, bc.bc.id().toString());
16538 always_assert_flog(
16539 index.classRefs.count(bc.name),
16540 "Class bytecode for non-existent class {}",
16541 bc.name
16543 always_assert_flog(
16544 index.classBytecodeRefs.emplace(bc.name, std::move(bc.bc)).second,
16545 "Duplicate class bytecode: {}",
16546 bc.name
16550 for (auto& bc : input.funcBC) {
16551 FTRACE(5, "func bytecode {} -> {}\n", bc.name, bc.bc.id().toString());
16553 always_assert_flog(
16554 index.funcRefs.count(bc.name),
16555 "Func bytecode for non-existent func {}",
16556 bc.name
16559 if (bc.methCaller) {
16560 // Only record this bytecode if it's associated meth-caller was
16561 // kept.
16562 auto const it = methCallerUnits.find(bc.name);
16563 always_assert_flog(
16564 it != end(methCallerUnits),
16565 "Bytecode for func {} is marked as meth-caller, "
16566 "but func is not a meth-caller",
16567 bc.name
16569 auto const unit = it->second;
16570 if (bc.unit != unit) {
16571 FTRACE(
16573 "Bytecode for meth-caller func {} in unit {} "
16574 "skipped because the meth-caller was dropped\n",
16575 bc.name, bc.unit
16577 continue;
16579 } else {
16580 always_assert_flog(
16581 !methCallerUnits.count(bc.name),
16582 "Bytecode for func {} is not marked as meth-caller, "
16583 "but func is a meth-caller",
16584 bc.name
16588 always_assert_flog(
16589 index.funcBytecodeRefs.emplace(bc.name, std::move(bc.bc)).second,
16590 "Duplicate func bytecode: {}",
16591 bc.name
16595 if (debug) {
16596 for (auto const& [cns, unitAndInit] : index.constantToUnit) {
16597 if (!unitAndInit.second) continue;
16598 if (is_native_unit(unitAndInit.first)) continue;
16599 auto const initName = Constant::funcNameFromName(cns);
16600 always_assert_flog(
16601 index.funcRefs.count(initName) > 0,
16602 "Constant {} is marked as having initialization func {}, "
16603 "but it does not exist",
16604 cns, initName
16609 return flattenMeta;
16612 //////////////////////////////////////////////////////////////////////
16615 * Combines multiple classes/class-infos/funcs/units into a single
16616 * blob. Makes make_local() more efficient, as you can download a
16617 * smaller number of large blobs rather than many small blobs.
16619 struct AggregateJob {
16620 static std::string name() { return "hhbbc-aggregate"; }
16621 static void init(const Config& config) {
16622 process_init(config.o, config.gd, false);
16623 ClassGraph::init();
16625 static void fini() { ClassGraph::destroy(); }
16627 struct Bundle {
16628 std::vector<std::unique_ptr<php::Class>> classes;
16629 std::vector<std::unique_ptr<ClassInfo2>> classInfos;
16630 std::vector<std::unique_ptr<php::ClassBytecode>> classBytecode;
16631 std::vector<std::unique_ptr<php::Func>> funcs;
16632 std::vector<std::unique_ptr<FuncInfo2>> funcInfos;
16633 std::vector<std::unique_ptr<php::FuncBytecode>> funcBytecode;
16634 std::vector<std::unique_ptr<php::Unit>> units;
16635 std::vector<FuncFamilyGroup> funcFamilies;
16636 std::vector<std::unique_ptr<MethodsWithoutCInfo>> methInfos;
16638 template <typename SerDe> void serde(SerDe& sd) {
16639 ScopedStringDataIndexer _;
16640 ClassGraph::ScopedSerdeState _2;
16641 sd(classes)
16642 (classInfos)
16643 (classBytecode)
16644 (funcs)
16645 (funcInfos)
16646 (funcBytecode)
16647 (units)
16648 (funcFamilies)
16649 (methInfos)
16654 static Bundle run(Variadic<std::unique_ptr<php::Class>> classes,
16655 Variadic<std::unique_ptr<ClassInfo2>> classInfos,
16656 Variadic<std::unique_ptr<php::ClassBytecode>> classBytecode,
16657 Variadic<std::unique_ptr<php::Func>> funcs,
16658 Variadic<std::unique_ptr<FuncInfo2>> funcInfos,
16659 Variadic<std::unique_ptr<php::FuncBytecode>> funcBytecode,
16660 Variadic<std::unique_ptr<php::Unit>> units,
16661 Variadic<FuncFamilyGroup> funcFamilies,
16662 Variadic<std::unique_ptr<MethodsWithoutCInfo>> methInfos) {
16663 Bundle bundle;
16664 bundle.classes.reserve(classes.vals.size());
16665 bundle.classInfos.reserve(classInfos.vals.size());
16666 bundle.classBytecode.reserve(classBytecode.vals.size());
16667 bundle.funcs.reserve(funcs.vals.size());
16668 bundle.funcInfos.reserve(funcInfos.vals.size());
16669 bundle.funcBytecode.reserve(funcBytecode.vals.size());
16670 bundle.units.reserve(units.vals.size());
16671 bundle.funcFamilies.reserve(funcFamilies.vals.size());
16672 bundle.methInfos.reserve(methInfos.vals.size());
16673 for (auto& c : classes.vals) {
16674 bundle.classes.emplace_back(std::move(c));
16676 for (auto& c : classInfos.vals) {
16677 bundle.classInfos.emplace_back(std::move(c));
16679 for (auto& b : classBytecode.vals) {
16680 bundle.classBytecode.emplace_back(std::move(b));
16682 for (auto& f : funcs.vals) {
16683 bundle.funcs.emplace_back(std::move(f));
16685 for (auto& f : funcInfos.vals) {
16686 bundle.funcInfos.emplace_back(std::move(f));
16688 for (auto& b : funcBytecode.vals) {
16689 bundle.funcBytecode.emplace_back(std::move(b));
16691 for (auto& u : units.vals) {
16692 bundle.units.emplace_back(std::move(u));
16694 for (auto& group : funcFamilies.vals) {
16695 bundle.funcFamilies.emplace_back(std::move(group));
16697 for (auto& m : methInfos.vals) {
16698 bundle.methInfos.emplace_back(std::move(m));
16700 return bundle;
16704 Job<AggregateJob> s_aggregateJob;
16706 void remote_func_info_to_local(IndexData& index,
16707 const php::Func& func,
16708 FuncInfo2& rfinfo) {
16709 assertx(func.name == rfinfo.name);
16710 auto finfo = func_info(index, &func);
16711 assertx(finfo->returnTy.is(BInitCell));
16712 finfo->returnTy = std::move(rfinfo.returnTy);
16713 finfo->returnRefinements = rfinfo.returnRefinements;
16714 finfo->retParam = rfinfo.retParam;
16715 finfo->effectFree = rfinfo.effectFree;
16716 finfo->unusedParams = rfinfo.unusedParams;
16719 // Convert the FuncInfo2s we loaded from extern-worker into their
16720 // equivalent FuncInfos.
16721 void make_func_infos_local(IndexData& index,
16722 std::vector<std::unique_ptr<FuncInfo2>> remote) {
16723 trace_time tracer{"make func-infos local"};
16724 tracer.ignore_client_stats();
16726 parallel::for_each(
16727 remote,
16728 [&] (const std::unique_ptr<FuncInfo2>& rfinfo) {
16729 auto const it = index.funcs.find(rfinfo->name);
16730 always_assert_flog(
16731 it != end(index.funcs),
16732 "Func-info for {} has no associated php::Func in index",
16733 rfinfo->name
16735 remote_func_info_to_local(index, *it->second, *rfinfo);
16740 // Convert the ClassInfo2s we loaded from extern-worker into their
16741 // equivalent ClassInfos (and store it in the Index).
16742 void make_class_infos_local(
16743 IndexData& index,
16744 std::vector<std::unique_ptr<ClassInfo2>> remote,
16745 std::vector<std::unique_ptr<FuncFamily2>> funcFamilies
16747 trace_time tracer{"make class-infos local"};
16748 tracer.ignore_client_stats();
16750 assertx(index.allClassInfos.empty());
16751 assertx(index.classInfo.empty());
16753 // First create a ClassInfo for each ClassInfo2. Since a ClassInfo
16754 // can refer to other ClassInfos, we can't do much more at this
16755 // stage.
16756 auto newCInfos = parallel::map(
16757 remote,
16758 [&] (const std::unique_ptr<ClassInfo2>& in) {
16759 std::vector<std::unique_ptr<ClassInfo>> out;
16761 auto const make = [&] (const ClassInfo2& cinfo) {
16762 auto c = std::make_unique<ClassInfo>();
16763 auto const it = index.classes.find(cinfo.name);
16764 always_assert_flog(
16765 it != end(index.classes),
16766 "Class-info for {} has no associated php::Class in index",
16767 cinfo.name
16769 c->cls = it->second;
16770 out.emplace_back(std::move(c));
16773 make(*in);
16774 for (auto const& clo : in->closures) make(*clo);
16775 return out;
16779 // Build table mapping name to ClassInfo.
16780 for (auto& cinfos : newCInfos) {
16781 for (auto& cinfo : cinfos) {
16782 always_assert(
16783 index.classInfo.emplace(cinfo->cls->name, cinfo.get()).second
16785 index.allClassInfos.emplace_back(std::move(cinfo));
16788 newCInfos.clear();
16789 newCInfos.shrink_to_fit();
16790 index.allClassInfos.shrink_to_fit();
16792 // Set AttrNoOverride to true for all methods. If we determine it's
16793 // actually overridden below, we'll clear it.
16794 parallel::for_each(
16795 index.program->classes,
16796 [&] (std::unique_ptr<php::Class>& cls) {
16797 for (auto& m : cls->methods) {
16798 assertx(!(m->attrs & AttrNoOverride));
16799 if (is_special_method_name(m->name)) continue;
16800 attribute_setter(m->attrs, true, AttrNoOverride);
16802 for (auto& clo : cls->closures) {
16803 assertx(clo->methods.size() == 1);
16804 auto& m = clo->methods[0];
16805 assertx(!(m->attrs & AttrNoOverride));
16806 assertx(!is_special_method_name(m->name));
16807 attribute_setter(m->attrs, true, AttrNoOverride);
16812 auto const get = [&] (SString name) {
16813 auto const it = index.classInfo.find(name);
16814 always_assert_flog(
16815 it != end(index.classInfo),
16816 "Class-info for {} not found in index",
16817 name
16819 return it->second;
16822 struct FFState {
16823 explicit FFState(std::unique_ptr<FuncFamily2> ff) : m_ff{std::move(ff)} {}
16824 std::unique_ptr<FuncFamily2> m_ff;
16825 LockFreeLazyPtrNoDelete<FuncFamily> m_notExpanded;
16826 LockFreeLazyPtrNoDelete<FuncFamily> m_expanded;
16828 FuncFamily* notExpanded(IndexData& index) {
16829 return const_cast<FuncFamily*>(
16830 &m_notExpanded.get([&] { return make(index, false); })
16833 FuncFamily* expanded(IndexData& index) {
16834 return const_cast<FuncFamily*>(
16835 &m_expanded.get([&] { return make(index, true); })
16839 FuncFamily* make(IndexData& index, bool expanded) const {
16840 FuncFamily::PFuncVec funcs;
16841 funcs.reserve(
16842 m_ff->m_regular.size() +
16843 (expanded
16845 : (m_ff->m_nonRegularPrivate.size() + m_ff->m_nonRegular.size())
16849 for (auto const& m : m_ff->m_regular) {
16850 funcs.emplace_back(func_from_meth_ref(index, m), true);
16852 for (auto const& m : m_ff->m_nonRegularPrivate) {
16853 funcs.emplace_back(func_from_meth_ref(index, m), true);
16855 if (!expanded) {
16856 for (auto const& m : m_ff->m_nonRegular) {
16857 funcs.emplace_back(func_from_meth_ref(index, m), false);
16861 auto const extra = !expanded && !m_ff->m_nonRegular.empty() &&
16862 (m_ff->m_regular.size() + m_ff->m_nonRegularPrivate.size()) > 1;
16864 std::sort(
16865 begin(funcs), end(funcs),
16866 [] (FuncFamily::PossibleFunc a, const FuncFamily::PossibleFunc b) {
16867 if (a.inRegular() && !b.inRegular()) return true;
16868 if (!a.inRegular() && b.inRegular()) return false;
16869 return string_data_lt_type{}(a.ptr()->cls->name, b.ptr()->cls->name);
16872 funcs.shrink_to_fit();
16874 assertx(funcs.size() > 1);
16876 auto const convert = [&] (const FuncFamily2::StaticInfo& in) {
16877 FuncFamily::StaticInfo out;
16878 out.m_numInOut = in.m_numInOut;
16879 out.m_requiredCoeffects = in.m_requiredCoeffects;
16880 out.m_coeffectRules = in.m_coeffectRules;
16881 out.m_paramPreps = in.m_paramPreps;
16882 out.m_minNonVariadicParams = in.m_minNonVariadicParams;
16883 out.m_maxNonVariadicParams = in.m_maxNonVariadicParams;
16884 out.m_isReadonlyReturn = in.m_isReadonlyReturn;
16885 out.m_isReadonlyThis = in.m_isReadonlyThis;
16886 out.m_supportsAER = in.m_supportsAER;
16887 out.m_maybeReified = in.m_maybeReified;
16888 out.m_maybeCaresAboutDynCalls = in.m_maybeCaresAboutDynCalls;
16889 out.m_maybeBuiltin = in.m_maybeBuiltin;
16891 auto const it = index.funcFamilyStaticInfos.find(out);
16892 if (it != end(index.funcFamilyStaticInfos)) return it->first.get();
16893 return index.funcFamilyStaticInfos.insert(
16894 std::make_unique<FuncFamily::StaticInfo>(std::move(out)),
16895 false
16896 ).first->first.get();
16899 auto newFuncFamily =
16900 std::make_unique<FuncFamily>(std::move(funcs), extra);
16902 always_assert(m_ff->m_allStatic);
16903 if (m_ff->m_regularStatic) {
16904 const FuncFamily::StaticInfo* reg = nullptr;
16905 if (expanded || extra) reg = convert(*m_ff->m_regularStatic);
16906 newFuncFamily->m_all.m_static =
16907 expanded ? reg : convert(*m_ff->m_allStatic);
16908 if (extra) {
16909 newFuncFamily->m_regular = std::make_unique<FuncFamily::Info>();
16910 newFuncFamily->m_regular->m_static = reg;
16912 } else {
16913 newFuncFamily->m_all.m_static = convert(*m_ff->m_allStatic);
16916 return index.funcFamilies.insert(
16917 std::move(newFuncFamily),
16918 false
16919 ).first->first.get();
16923 hphp_fast_map<FuncFamily2::Id, std::unique_ptr<FFState>> ffState;
16924 for (auto& ff : funcFamilies) {
16925 auto const id = ff->m_id;
16926 always_assert(
16927 ffState.emplace(
16929 std::make_unique<FFState>(std::move(ff))
16930 ).second
16933 funcFamilies.clear();
16935 std::mutex extraMethodLock;
16937 // Now that we can map name to ClassInfo, we can populate the rest
16938 // of the fields in each ClassInfo.
16939 parallel::for_each(
16940 remote,
16941 [&] (std::unique_ptr<ClassInfo2>& rcinfos) {
16942 auto const process = [&] (std::unique_ptr<ClassInfo2> rcinfo) {
16943 auto const cinfo = get(rcinfo->name);
16944 if (rcinfo->parent) cinfo->parent = get(rcinfo->parent);
16946 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
16947 auto const traits = rcinfo->classGraph.usedTraits();
16948 cinfo->usedTraits.reserve(traits.size());
16949 for (auto const trait : traits) {
16950 cinfo->usedTraits.emplace_back(get(trait.name()));
16952 cinfo->usedTraits.shrink_to_fit();
16954 cinfo->traitProps = std::move(rcinfo->traitProps);
16956 cinfo->clsConstants.reserve(rcinfo->clsConstants.size());
16957 for (auto const& [name, cns] : rcinfo->clsConstants) {
16958 auto const it = index.classes.find(cns.idx.cls);
16959 always_assert_flog(
16960 it != end(index.classes),
16961 "php::Class for {} not found in index",
16962 name
16964 cinfo->clsConstants.emplace(
16965 name,
16966 ClassInfo::ConstIndex { it->second, cns.idx.idx }
16970 for (size_t i = 0, size = cinfo->cls->constants.size(); i < size; ++i) {
16971 auto const& cns = cinfo->cls->constants[i];
16972 if (cns.kind != ConstModifiers::Kind::Value) continue;
16973 if (!cns.val.has_value()) continue;
16974 if (cns.val->m_type != KindOfUninit) continue;
16975 if (i >= cinfo->clsConstTypes.size()) {
16976 cinfo->clsConstTypes.resize(i+1, ClsConstInfo { TInitCell, 0 });
16978 cinfo->clsConstTypes[i] = folly::get_default(
16979 rcinfo->clsConstantInfo,
16980 cns.name,
16981 ClsConstInfo { TInitCell, 0 }
16984 cinfo->clsConstTypes.shrink_to_fit();
16987 std::vector<std::pair<SString, MethTabEntry>> methods;
16988 methods.reserve(cinfo->methods.size());
16989 for (auto const& [name, mte] : rcinfo->methods) {
16990 if (!(mte.attrs & AttrNoOverride)) {
16991 attribute_setter(
16992 func_from_meth_ref(index, mte.meth())->attrs,
16993 false,
16994 AttrNoOverride
16997 methods.emplace_back(name, mte);
16999 std::sort(
17000 begin(methods), end(methods),
17001 [] (auto const& p1, auto const& p2) { return p1.first < p2.first; }
17003 cinfo->methods.insert(
17004 folly::sorted_unique, begin(methods), end(methods)
17006 cinfo->methods.shrink_to_fit();
17009 cinfo->hasBadRedeclareProp = rcinfo->hasBadRedeclareProp;
17010 cinfo->hasBadInitialPropValues = rcinfo->hasBadInitialPropValues;
17011 cinfo->hasConstProp = rcinfo->hasConstProp;
17012 cinfo->hasReifiedParent = rcinfo->hasReifiedParent;
17013 cinfo->subHasConstProp = rcinfo->subHasConstProp;
17014 cinfo->isMocked = rcinfo->isMocked;
17015 cinfo->isSubMocked = rcinfo->isSubMocked;
17017 cinfo->classGraph = rcinfo->classGraph;
17018 cinfo->classGraph.setCInfo(*cinfo);
17020 auto const noOverride = [&] (SString name) {
17021 if (auto const mte = folly::get_ptr(cinfo->methods, name)) {
17022 return bool(mte->attrs & AttrNoOverride);
17024 return true;
17027 auto const noOverrideRegular = [&] (SString name) {
17028 if (auto const mte = folly::get_ptr(cinfo->methods, name)) {
17029 return mte->noOverrideRegular();
17031 return true;
17034 std::vector<std::pair<SString, FuncFamilyOrSingle>> entries;
17035 std::vector<std::pair<SString, FuncFamilyOrSingle>> aux;
17036 for (auto const& [name, entry] : rcinfo->methodFamilies) {
17037 assertx(!is_special_method_name(name));
17039 auto expanded = false;
17040 if (!cinfo->methods.count(name)) {
17041 if (!(cinfo->cls->attrs & (AttrAbstract|AttrInterface))) continue;
17042 if (!cinfo->classGraph.mightHaveRegularSubclass()) continue;
17043 if (entry.m_regularIncomplete || entry.m_privateAncestor) continue;
17044 if (name == s_construct.get()) continue;
17045 expanded = true;
17046 } else if (noOverride(name)) {
17047 continue;
17050 match<void>(
17051 entry.m_meths,
17052 [&, name=name, &entry=entry] (const FuncFamilyEntry::BothFF& e) {
17053 auto const it = ffState.find(e.m_ff);
17054 assertx(it != end(ffState));
17055 auto const& state = it->second;
17057 if (expanded) {
17058 if (state->m_ff->m_regular.empty()) return;
17059 if (state->m_ff->m_regular.size() == 1) {
17060 entries.emplace_back(
17061 name,
17062 FuncFamilyOrSingle{
17063 func_from_meth_ref(index, state->m_ff->m_regular[0]),
17064 false
17067 return;
17069 entries.emplace_back(
17070 name,
17071 FuncFamilyOrSingle{
17072 state->expanded(index),
17073 false
17076 return;
17079 entries.emplace_back(
17080 name,
17081 FuncFamilyOrSingle{
17082 state->notExpanded(index),
17083 entry.m_allIncomplete
17087 [&, name=name, &entry=entry] (const FuncFamilyEntry::FFAndSingle& e) {
17088 if (expanded) {
17089 if (e.m_nonRegularPrivate) return;
17090 entries.emplace_back(
17091 name,
17092 FuncFamilyOrSingle{
17093 func_from_meth_ref(index, e.m_regular),
17094 false
17097 return;
17100 auto const it = ffState.find(e.m_ff);
17101 assertx(it != end(ffState));
17103 entries.emplace_back(
17104 name,
17105 FuncFamilyOrSingle{
17106 it->second->notExpanded(index),
17107 entry.m_allIncomplete
17110 if (noOverrideRegular(name)) return;
17111 aux.emplace_back(
17112 name,
17113 FuncFamilyOrSingle{
17114 func_from_meth_ref(index, e.m_regular),
17115 false
17119 [&, name=name, &entry=entry] (const FuncFamilyEntry::FFAndNone& e) {
17120 if (expanded) return;
17121 auto const it = ffState.find(e.m_ff);
17122 assertx(it != end(ffState));
17124 entries.emplace_back(
17125 name,
17126 FuncFamilyOrSingle{
17127 it->second->notExpanded(index),
17128 entry.m_allIncomplete
17131 if (!noOverrideRegular(name)) {
17132 aux.emplace_back(name, FuncFamilyOrSingle{});
17135 [&, name=name, &entry=entry] (const FuncFamilyEntry::BothSingle& e) {
17136 if (expanded && e.m_nonRegularPrivate) return;
17137 entries.emplace_back(
17138 name,
17139 FuncFamilyOrSingle{
17140 func_from_meth_ref(index, e.m_all),
17141 !expanded && entry.m_allIncomplete
17145 [&, name=name, &entry=entry] (const FuncFamilyEntry::SingleAndNone& e) {
17146 if (expanded) return;
17147 entries.emplace_back(
17148 name,
17149 FuncFamilyOrSingle{
17150 func_from_meth_ref(index, e.m_all),
17151 entry.m_allIncomplete
17154 if (!noOverrideRegular(name)) {
17155 aux.emplace_back(name, FuncFamilyOrSingle{});
17158 [&, &entry=entry] (const FuncFamilyEntry::None&) {
17159 assertx(entry.m_allIncomplete);
17164 // Sort the lists of new entries, so we can insert them into the
17165 // method family maps (which are sorted_vector_maps) in bulk.
17166 std::sort(
17167 begin(entries), end(entries),
17168 [] (auto const& p1, auto const& p2) { return p1.first < p2.first; }
17170 std::sort(
17171 begin(aux), end(aux),
17172 [] (auto const& p1, auto const& p2) { return p1.first < p2.first; }
17174 if (!entries.empty()) {
17175 cinfo->methodFamilies.insert(
17176 folly::sorted_unique, begin(entries), end(entries)
17179 if (!aux.empty()) {
17180 cinfo->methodFamiliesAux.insert(
17181 folly::sorted_unique, begin(aux), end(aux)
17184 cinfo->methodFamilies.shrink_to_fit();
17185 cinfo->methodFamiliesAux.shrink_to_fit();
17187 if (!rcinfo->extraMethods.empty()) {
17188 // This is rare. Only happens with unflattened traits, so
17189 // taking a lock here is fine.
17190 std::lock_guard<std::mutex> _{extraMethodLock};
17191 auto& extra = index.classExtraMethodMap[cinfo->cls];
17192 for (auto const& meth : rcinfo->extraMethods) {
17193 extra.emplace(func_from_meth_ref(index, meth));
17197 // Build the FuncInfo for every method on this class. The
17198 // FuncInfos already have default types, so update them with the
17199 // type from the FuncInfo2. Any class types here will be
17200 // unresolved (will be resolved later).
17201 assertx(cinfo->cls->methods.size() == rcinfo->funcInfos.size());
17202 for (size_t i = 0, size = cinfo->cls->methods.size(); i < size; ++i) {
17203 auto& func = cinfo->cls->methods[i];
17204 auto& rfi = rcinfo->funcInfos[i];
17205 remote_func_info_to_local(index, *func, *rfi);
17209 for (auto& clo : rcinfos->closures) process(std::move(clo));
17210 process(std::move(rcinfos));
17214 remote.clear();
17215 remote.shrink_to_fit();
17217 for (auto const& [name, entry] : index.nameOnlyMethodFamilies) {
17218 match<void>(
17219 entry.m_meths,
17220 [&, name=name] (const FuncFamilyEntry::BothFF& e) {
17221 auto const it = ffState.find(e.m_ff);
17222 assertx(it != end(ffState));
17224 FuncFamilyOrSingle f{ it->second->notExpanded(index), true };
17225 index.methodFamilies.emplace(
17226 name,
17227 IndexData::MethodFamilyEntry { f, f }
17230 [&, name=name] (const FuncFamilyEntry::FFAndSingle& e) {
17231 auto const it = ffState.find(e.m_ff);
17232 assertx(it != end(ffState));
17234 index.methodFamilies.emplace(
17235 name,
17236 IndexData::MethodFamilyEntry {
17237 FuncFamilyOrSingle {
17238 it->second->notExpanded(index),
17239 true
17241 FuncFamilyOrSingle {
17242 func_from_meth_ref(index, e.m_regular),
17243 true
17248 [&, name=name] (const FuncFamilyEntry::FFAndNone& e) {
17249 auto const it = ffState.find(e.m_ff);
17250 assertx(it != end(ffState));
17252 index.methodFamilies.emplace(
17253 name,
17254 IndexData::MethodFamilyEntry {
17255 FuncFamilyOrSingle {
17256 it->second->notExpanded(index),
17257 true
17259 FuncFamilyOrSingle {}
17263 [&, name=name] (const FuncFamilyEntry::BothSingle& e) {
17264 FuncFamilyOrSingle f{ func_from_meth_ref(index, e.m_all), true };
17265 index.methodFamilies.emplace(
17266 name,
17267 IndexData::MethodFamilyEntry { f, f }
17270 [&, name=name] (const FuncFamilyEntry::SingleAndNone& e) {
17271 index.methodFamilies.emplace(
17272 name,
17273 IndexData::MethodFamilyEntry {
17274 FuncFamilyOrSingle {
17275 func_from_meth_ref(index, e.m_all),
17276 true
17278 FuncFamilyOrSingle {}
17282 [&] (const FuncFamilyEntry::None&) { always_assert(false); }
17286 ffState.clear();
17287 decltype(index.nameOnlyMethodFamilies){}.swap(index.nameOnlyMethodFamilies);
17289 // Now that all of the FuncFamilies have been created, generate the
17290 // back links from FuncInfo to their FuncFamilies.
17291 std::vector<FuncFamily*> work;
17292 work.reserve(index.funcFamilies.size());
17293 for (auto const& kv : index.funcFamilies) work.emplace_back(kv.first.get());
17295 // First calculate the needed capacity for each FuncInfo's family
17296 // list. We use this to presize the family list. This is superior
17297 // just pushing back and then shrinking the vectors, as that can
17298 // excessively fragment the heap.
17299 std::vector<std::atomic<size_t>> capacities(index.nextFuncId);
17301 parallel::for_each(
17302 work,
17303 [&] (FuncFamily* ff) {
17304 for (auto const pf : ff->possibleFuncs()) {
17305 ++capacities[pf.ptr()->idx];
17310 parallel::for_each(
17311 index.funcInfo,
17312 [&] (FuncInfo& fi) {
17313 if (!fi.func) return;
17314 fi.families.reserve(capacities[fi.func->idx]);
17317 capacities.clear();
17318 capacities.shrink_to_fit();
17320 // Different threads can touch the same FuncInfo when adding to the
17321 // func family list, so use sharded locking scheme.
17322 std::array<std::mutex, 256> locks;
17323 parallel::for_each(
17324 work,
17325 [&] (FuncFamily* ff) {
17326 for (auto const pf : ff->possibleFuncs()) {
17327 auto finfo = func_info(index, pf.ptr());
17328 auto& lock = locks[pointer_hash<FuncInfo>{}(finfo) % locks.size()];
17329 std::lock_guard<std::mutex> _{lock};
17330 finfo->families.emplace_back(ff);
17336 // Switch to "local" mode, in which all calculations are expected to
17337 // be done locally (not using extern-worker). This involves
17338 // downloading everything out of extern-worker and converting it. To
17339 // improve efficiency, we first aggregate many small(er) items into
17340 // larger aggregate blobs in external workers, then download the
17341 // larger blobs.
17342 void make_local(IndexData& index) {
17343 trace_time tracer("make local", index.sample);
17345 using namespace folly::gen;
17347 // These aren't needed below so we can free them immediately.
17348 decltype(index.funcToClosures){}.swap(index.funcToClosures);
17349 decltype(index.classToClosures){}.swap(index.classToClosures);
17350 decltype(index.classesWith86Inits){}.swap(index.classesWith86Inits);
17351 decltype(index.classToUnit){}.swap(index.classToUnit);
17352 decltype(index.funcToUnit){}.swap(index.funcToUnit);
17353 decltype(index.constantToUnit){}.swap(index.constantToUnit);
17354 decltype(index.constantInitFuncs){}.swap(index.constantInitFuncs);
17355 decltype(index.unitsWithTypeAliases){}.swap(index.unitsWithTypeAliases);
17356 decltype(index.closureToFunc){}.swap(index.closureToFunc);
17357 decltype(index.closureToClass){}.swap(index.closureToClass);
17358 decltype(index.classToCnsBases){}.swap(index.classToCnsBases);
17360 // Unlike other cases, we want to bound each bucket to roughly the
17361 // same total byte size (since ultimately we're going to download
17362 // everything).
17363 auto const usingSubprocess = index.client->usingSubprocess();
17364 // We can be more aggressive in subprocess mode because there's no
17365 // actual aggregation.
17366 auto const sizePerBucket = usingSubprocess
17367 ? 256*1024*1024
17368 : 128*1024*1024;
17371 * We'll use the names of the various items as the items to
17372 * bucketize. This is somewhat problematic because names between
17373 * units/funcs/classes/class-infos can collide (indeed classes and
17374 * class-infos will always collide). Adding to the complication is
17375 * that some of these are case sensitive and some aren't.
17377 * We'll store a case sensitive version of each name exactly *once*,
17378 * using a seen set. Since the hash for a static string (which
17379 * consistently_bucketize() uses) is case insensitive, all case
17380 * sensitive versions of the same name will always hash to the same
17381 * bucket.
17383 * When we obtain the Refs for a corresponding bucket, we'll load
17384 * all items with that given name, using a set to ensure each RefId
17385 * is only used once.
17388 SStringToOneT<Ref<FuncFamilyGroup>> nameToFuncFamilyGroup;
17389 auto const& [items, totalSize] = [&] {
17390 SStringSet seen;
17391 std::vector<SString> items;
17392 size_t totalSize = 0;
17393 items.reserve(
17394 index.unitRefs.size() + index.classRefs.size() +
17395 index.classInfoRefs.size() + index.funcRefs.size() +
17396 index.funcInfoRefs.size() + index.funcFamilyRefs.size() +
17397 index.uninstantiableClsMethRefs.size()
17399 for (auto const& [name, ref] : index.unitRefs) {
17400 totalSize += ref.id().m_size;
17401 if (!seen.emplace(name).second) continue;
17402 items.emplace_back(name);
17404 for (auto const& [name, ref] : index.classRefs) {
17405 totalSize += ref.id().m_size;
17406 if (!seen.emplace(name).second) continue;
17407 items.emplace_back(name);
17409 for (auto const& [name, ref] : index.classInfoRefs) {
17410 totalSize += ref.id().m_size;
17411 if (!seen.emplace(name).second) continue;
17412 items.emplace_back(name);
17414 for (auto const& [name, ref] : index.classBytecodeRefs) {
17415 totalSize += ref.id().m_size;
17416 if (!seen.emplace(name).second) continue;
17417 items.emplace_back(name);
17419 for (auto const& [name, ref] : index.funcRefs) {
17420 totalSize += ref.id().m_size;
17421 if (!seen.emplace(name).second) continue;
17422 items.emplace_back(name);
17424 for (auto const& [name, ref] : index.funcInfoRefs) {
17425 totalSize += ref.id().m_size;
17426 if (!seen.emplace(name).second) continue;
17427 items.emplace_back(name);
17429 for (auto const& [name, ref] : index.funcBytecodeRefs) {
17430 totalSize += ref.id().m_size;
17431 if (!seen.emplace(name).second) continue;
17432 items.emplace_back(name);
17434 for (auto const& [name, ref] : index.uninstantiableClsMethRefs) {
17435 totalSize += ref.id().m_size;
17436 if (!seen.emplace(name).second) continue;
17437 items.emplace_back(name);
17440 for (auto const& [_, ref] : index.funcFamilyRefs) {
17441 auto const name = makeStaticString(ref.id().toString());
17442 nameToFuncFamilyGroup.emplace(name, ref);
17444 for (auto const& [name, ref] : nameToFuncFamilyGroup) {
17445 totalSize += ref.id().m_size;
17446 if (!seen.emplace(name).second) continue;
17447 items.emplace_back(name);
17449 std::sort(begin(items), end(items), string_data_lt{});
17450 return std::make_pair(items, totalSize);
17451 }();
17453 // Back out the number of buckets we want from the total size of
17454 // everything and the target size of a bucket.
17455 auto const numBuckets = (totalSize + sizePerBucket - 1) / sizePerBucket;
17456 auto buckets = consistently_bucketize(
17457 items,
17458 (items.size() + numBuckets - 1) / numBuckets
17461 // We're going to be downloading all bytecode, so to avoid wasted
17462 // memory, try to re-use identical bytecode.
17463 Optional<php::FuncBytecode::Reuser> reuser;
17464 reuser.emplace();
17465 php::FuncBytecode::s_reuser = reuser.get_pointer();
17466 SCOPE_EXIT { php::FuncBytecode::s_reuser = nullptr; };
17468 std::mutex lock;
17469 auto program = std::make_unique<php::Program>();
17471 // Index stores ClassInfos, not ClassInfo2s, so we need a place to
17472 // store them until we convert it.
17473 std::vector<std::unique_ptr<ClassInfo2>> remoteClassInfos;
17474 remoteClassInfos.reserve(index.classInfoRefs.size());
17476 std::vector<std::unique_ptr<FuncInfo2>> remoteFuncInfos;
17477 remoteFuncInfos.reserve(index.funcInfoRefs.size());
17479 std::vector<std::unique_ptr<MethodsWithoutCInfo>> remoteMethInfos;
17480 remoteMethInfos.reserve(index.uninstantiableClsMethRefs.size());
17482 hphp_fast_set<FuncFamily2::Id> remoteFuncFamilyIds;
17483 std::vector<std::unique_ptr<FuncFamily2>> remoteFuncFamilies;
17484 remoteFuncFamilies.reserve(index.funcFamilyRefs.size());
17486 auto const run = [&] (std::vector<SString> chunks) -> coro::Task<void> {
17487 co_await coro::co_reschedule_on_current_executor;
17489 if (chunks.empty()) co_return;
17491 std::vector<UniquePtrRef<php::Class>> classes;
17492 std::vector<UniquePtrRef<ClassInfo2>> classInfos;
17493 std::vector<UniquePtrRef<php::ClassBytecode>> classBytecode;
17494 std::vector<UniquePtrRef<php::Func>> funcs;
17495 std::vector<UniquePtrRef<FuncInfo2>> funcInfos;
17496 std::vector<UniquePtrRef<php::FuncBytecode>> funcBytecode;
17497 std::vector<UniquePtrRef<php::Unit>> units;
17498 std::vector<Ref<FuncFamilyGroup>> funcFamilies;
17499 std::vector<UniquePtrRef<MethodsWithoutCInfo>> methInfos;
17501 // Since a name can map to multiple items, and different case
17502 // sensitive version of the same name can appear in the same
17503 // bucket, use a set to make sure any given RefId only included
17504 // once. A set of case insensitive identical names will always
17505 // appear in the same bucket, so there's no need to track
17506 // duplicates globally.
17507 hphp_fast_set<RefId, RefId::Hasher> ids;
17509 for (auto const name : chunks) {
17510 if (auto const r = folly::get_optional(index.unitRefs, name)) {
17511 if (ids.emplace(r->id()).second) {
17512 units.emplace_back(*r);
17515 if (auto const r = folly::get_optional(index.classRefs, name)) {
17516 auto const bytecode = index.classBytecodeRefs.at(name);
17517 if (ids.emplace(r->id()).second) {
17518 classes.emplace_back(*r);
17519 classBytecode.emplace_back(bytecode);
17522 if (auto const r = folly::get_optional(index.classInfoRefs, name)) {
17523 if (ids.emplace(r->id()).second) {
17524 classInfos.emplace_back(*r);
17527 if (auto const r = folly::get_optional(index.funcRefs, name)) {
17528 auto const bytecode = index.funcBytecodeRefs.at(name);
17529 if (ids.emplace(r->id()).second) {
17530 funcs.emplace_back(*r);
17531 funcBytecode.emplace_back(bytecode);
17534 if (auto const r = folly::get_optional(index.funcInfoRefs, name)) {
17535 if (ids.emplace(r->id()).second) {
17536 funcInfos.emplace_back(*r);
17539 if (auto const r = folly::get_optional(nameToFuncFamilyGroup, name)) {
17540 if (ids.emplace(r->id()).second) {
17541 funcFamilies.emplace_back(*r);
17544 if (auto const r = folly::get_optional(index.uninstantiableClsMethRefs,
17545 name)) {
17546 if (ids.emplace(r->id()).second) {
17547 methInfos.emplace_back(*r);
17552 AggregateJob::Bundle chunk;
17553 if (!usingSubprocess) {
17554 Client::ExecMetadata metadata{
17555 .job_key = folly::sformat("aggregate {}", chunks[0])
17558 // Aggregate the data, which makes downloading it more efficient.
17559 auto config = co_await index.configRef->getCopy();
17560 auto outputs = co_await index.client->exec(
17561 s_aggregateJob,
17562 std::move(config),
17563 singleton_vec(
17564 std::make_tuple(
17565 std::move(classes),
17566 std::move(classInfos),
17567 std::move(classBytecode),
17568 std::move(funcs),
17569 std::move(funcInfos),
17570 std::move(funcBytecode),
17571 std::move(units),
17572 std::move(funcFamilies),
17573 std::move(methInfos)
17576 std::move(metadata)
17578 assertx(outputs.size() == 1);
17580 // Download the aggregate chunks.
17581 chunk = co_await index.client->load(std::move(outputs[0]));
17582 } else {
17583 // If we're using subprocess mode, we don't need to aggregate
17584 // and we can just download the items directly.
17585 auto [c, cinfo, cbc, f, finfo, fbc, u, ff, minfo] =
17586 co_await coro::collectAll(
17587 index.client->load(std::move(classes)),
17588 index.client->load(std::move(classInfos)),
17589 index.client->load(std::move(classBytecode)),
17590 index.client->load(std::move(funcs)),
17591 index.client->load(std::move(funcInfos)),
17592 index.client->load(std::move(funcBytecode)),
17593 index.client->load(std::move(units)),
17594 index.client->load(std::move(funcFamilies)),
17595 index.client->load(std::move(methInfos))
17597 chunk.classes.insert(
17598 end(chunk.classes),
17599 std::make_move_iterator(begin(c)),
17600 std::make_move_iterator(end(c))
17602 chunk.classInfos.insert(
17603 end(chunk.classInfos),
17604 std::make_move_iterator(begin(cinfo)),
17605 std::make_move_iterator(end(cinfo))
17607 chunk.classBytecode.insert(
17608 end(chunk.classBytecode),
17609 std::make_move_iterator(begin(cbc)),
17610 std::make_move_iterator(end(cbc))
17612 chunk.funcs.insert(
17613 end(chunk.funcs),
17614 std::make_move_iterator(begin(f)),
17615 std::make_move_iterator(end(f))
17617 chunk.funcInfos.insert(
17618 end(chunk.funcInfos),
17619 std::make_move_iterator(begin(finfo)),
17620 std::make_move_iterator(end(finfo))
17622 chunk.funcBytecode.insert(
17623 end(chunk.funcBytecode),
17624 std::make_move_iterator(begin(fbc)),
17625 std::make_move_iterator(end(fbc))
17627 chunk.units.insert(
17628 end(chunk.units),
17629 std::make_move_iterator(begin(u)),
17630 std::make_move_iterator(end(u))
17632 chunk.funcFamilies.insert(
17633 end(chunk.funcFamilies),
17634 std::make_move_iterator(begin(ff)),
17635 std::make_move_iterator(end(ff))
17637 chunk.methInfos.insert(
17638 end(chunk.methInfos),
17639 std::make_move_iterator(begin(minfo)),
17640 std::make_move_iterator(end(minfo))
17644 always_assert(chunk.classBytecode.size() == chunk.classes.size());
17645 for (size_t i = 0, size = chunk.classes.size(); i < size; ++i) {
17646 auto& bc = chunk.classBytecode[i];
17647 auto& cls = chunk.classes[i];
17649 size_t bcIdx = 0;
17650 for (auto& meth : cls->methods) {
17651 assertx(bcIdx < bc->methodBCs.size());
17652 auto& methBC = bc->methodBCs[bcIdx++];
17653 always_assert(methBC.name == meth->name);
17654 always_assert(!meth->rawBlocks);
17655 meth->rawBlocks = std::move(methBC.bc);
17657 for (auto& clo : cls->closures) {
17658 assertx(bcIdx < bc->methodBCs.size());
17659 auto& methBC = bc->methodBCs[bcIdx++];
17660 assertx(clo->methods.size() == 1);
17661 always_assert(methBC.name == clo->methods[0]->name);
17662 always_assert(!clo->methods[0]->rawBlocks);
17663 clo->methods[0]->rawBlocks = std::move(methBC.bc);
17665 assertx(bcIdx == bc->methodBCs.size());
17667 chunk.classBytecode.clear();
17669 always_assert(chunk.funcBytecode.size() == chunk.funcs.size());
17670 for (size_t i = 0, size = chunk.funcs.size(); i < size; ++i) {
17671 auto& bytecode = chunk.funcBytecode[i];
17672 chunk.funcs[i]->rawBlocks = std::move(bytecode->bc);
17674 chunk.funcBytecode.clear();
17676 // And add it to our php::Program.
17678 std::scoped_lock<std::mutex> _{lock};
17679 for (auto& unit : chunk.units) {
17680 // Local execution doesn't need the native unit, so strip it
17681 // out.
17682 if (is_native_unit(*unit)) continue;
17683 program->units.emplace_back(std::move(unit));
17685 for (auto& cls : chunk.classes) {
17686 program->classes.emplace_back(std::move(cls));
17688 for (auto& func : chunk.funcs) {
17689 program->funcs.emplace_back(std::move(func));
17691 remoteClassInfos.insert(
17692 end(remoteClassInfos),
17693 std::make_move_iterator(begin(chunk.classInfos)),
17694 std::make_move_iterator(end(chunk.classInfos))
17696 remoteFuncInfos.insert(
17697 end(remoteFuncInfos),
17698 std::make_move_iterator(begin(chunk.funcInfos)),
17699 std::make_move_iterator(end(chunk.funcInfos))
17701 remoteMethInfos.insert(
17702 end(remoteMethInfos),
17703 std::make_move_iterator(begin(chunk.methInfos)),
17704 std::make_move_iterator(end(chunk.methInfos))
17706 for (auto& group : chunk.funcFamilies) {
17707 for (auto& ff : group.m_ffs) {
17708 if (remoteFuncFamilyIds.emplace(ff->m_id).second) {
17709 remoteFuncFamilies.emplace_back(std::move(ff));
17715 co_return;
17718 // We're going to load ClassGraphs concurrently.
17719 ClassGraph::initConcurrent();
17722 // Temporarily suppress case collision logging
17723 auto oldTypeLogLevel = Cfg::Eval::LogTsameCollisions;
17724 Cfg::Eval::LogTsameCollisions = 0;
17725 SCOPE_EXIT {
17726 Cfg::Eval::LogTsameCollisions = oldTypeLogLevel;
17729 coro::blockingWait(coro::collectAllRange(
17730 from(buckets)
17731 | move
17732 | map([&] (std::vector<SString> chunks) {
17733 return run(std::move(chunks)).scheduleOn(index.executor->sticky());
17735 | as<std::vector>()
17739 // Deserialization done.
17740 ClassGraph::stopConcurrent();
17742 // We've used any refs we need. Free them now to save memory.
17743 decltype(index.unitRefs){}.swap(index.unitRefs);
17744 decltype(index.classRefs){}.swap(index.classRefs);
17745 decltype(index.funcRefs){}.swap(index.funcRefs);
17746 decltype(index.classInfoRefs){}.swap(index.classInfoRefs);
17747 decltype(index.funcInfoRefs){}.swap(index.funcInfoRefs);
17748 decltype(index.funcFamilyRefs){}.swap(index.funcFamilyRefs);
17749 decltype(index.classBytecodeRefs){}.swap(index.classBytecodeRefs);
17750 decltype(index.funcBytecodeRefs){}.swap(index.funcBytecodeRefs);
17751 decltype(index.uninstantiableClsMethRefs){}.swap(
17752 index.uninstantiableClsMethRefs
17755 // Done with any extern-worker stuff at this point:
17756 index.configRef.reset();
17758 Logger::FInfo(
17759 "{}",
17760 index.client->getStats().toString(
17761 "hhbbc",
17762 folly::sformat(
17763 "{:,} units, {:,} classes, {:,} class-infos, {:,} funcs",
17764 program->units.size(),
17765 program->classes.size(),
17766 remoteClassInfos.size(),
17767 program->funcs.size()
17772 if (index.sample) {
17773 index.client->getStats().logSample("hhbbc", *index.sample);
17776 index.disposeClient(
17777 std::move(index.executor),
17778 std::move(index.client)
17780 index.disposeClient = decltype(index.disposeClient){};
17782 php::FuncBytecode::s_reuser = nullptr;
17783 reuser.reset();
17785 buckets.clear();
17786 nameToFuncFamilyGroup.clear();
17787 remoteFuncFamilyIds.clear();
17789 program->units.shrink_to_fit();
17790 program->classes.shrink_to_fit();
17791 program->funcs.shrink_to_fit();
17792 index.program = std::move(program);
17794 // For now we don't require system constants in any extern-worker
17795 // stuff we do. So we can just add it to the Index now.
17796 add_system_constants_to_index(index);
17798 // Buid Index data structures from the php::Program.
17799 add_program_to_index(index);
17801 // Convert the FuncInfo2s into FuncInfos.
17802 make_func_infos_local(
17803 index,
17804 std::move(remoteFuncInfos)
17807 // Convert the ClassInfo2s into ClassInfos.
17808 make_class_infos_local(
17809 index,
17810 std::move(remoteClassInfos),
17811 std::move(remoteFuncFamilies)
17814 // Convert any "orphan" FuncInfo2s (those representing methods for a
17815 // class without a ClassInfo).
17816 parallel::for_each(
17817 remoteMethInfos,
17818 [&] (std::unique_ptr<MethodsWithoutCInfo>& meths) {
17819 auto const cls = folly::get_default(index.classes, meths->cls);
17820 always_assert_flog(
17821 cls,
17822 "php::Class for {} not found in index",
17823 meths->cls
17825 assertx(cls->methods.size() == meths->finfos.size());
17826 for (size_t i = 0, size = cls->methods.size(); i < size; ++i) {
17827 remote_func_info_to_local(index, *cls->methods[i], *meths->finfos[i]);
17830 assertx(cls->closures.size() == meths->closureInvokes.size());
17831 for (size_t i = 0, size = cls->closures.size(); i < size; ++i) {
17832 auto const& clo = cls->closures[i];
17833 assertx(clo->methods.size() == 1);
17834 remote_func_info_to_local(
17835 index,
17836 *clo->methods[0],
17837 *meths->closureInvokes[i]
17843 // Ensure that all classes are unserialized since all local
17844 // processing requires that.
17846 trace_time tracer2{"unserialize classes"};
17847 tracer2.ignore_client_stats();
17849 parallel::for_each(
17850 index.allClassInfos,
17851 [&] (std::unique_ptr<ClassInfo>& cinfo) {
17852 for (auto& [ty, _] : cinfo->clsConstTypes) {
17853 ty = unserialize_classes(
17854 IndexAdaptor { *index.m_index },
17855 std::move(ty)
17861 parallel::for_each(
17862 index.funcInfo,
17863 [&] (FuncInfo& fi) {
17864 if (!fi.func) return;
17865 fi.returnTy = unserialize_classes(
17866 IndexAdaptor { *index.m_index },
17867 std::move(fi.returnTy)
17873 //////////////////////////////////////////////////////////////////////
17877 //////////////////////////////////////////////////////////////////////
17879 std::vector<SString> Index::Input::makeDeps(const php::Class& cls) {
17880 std::vector<SString> deps;
17881 if (cls.parentName) deps.emplace_back(cls.parentName);
17882 deps.insert(deps.end(), cls.interfaceNames.begin(), cls.interfaceNames.end());
17883 deps.insert(deps.end(), cls.usedTraitNames.begin(), cls.usedTraitNames.end());
17884 deps.insert(
17885 deps.end(),
17886 cls.includedEnumNames.begin(),
17887 cls.includedEnumNames.end()
17889 return deps;
17892 //////////////////////////////////////////////////////////////////////
17894 Index::Index(Input input,
17895 Config config,
17896 std::unique_ptr<TicketExecutor> executor,
17897 std::unique_ptr<Client> client,
17898 DisposeCallback dispose,
17899 StructuredLogEntry* sample)
17900 : m_data{std::make_unique<IndexData>(this)}
17902 trace_time tracer("create index", sample);
17903 m_data->sample = sample;
17905 auto flattenMeta = make_remote(
17906 *m_data,
17907 std::move(config),
17908 std::move(input),
17909 std::move(executor),
17910 std::move(client),
17911 std::move(dispose)
17914 flatten_type_mappings(*m_data, flattenMeta);
17915 auto [subclassMeta, initTypesMeta, ifaceConflicts] =
17916 flatten_classes(*m_data, std::move(flattenMeta));
17917 build_subclass_lists(*m_data, std::move(subclassMeta), initTypesMeta);
17918 init_types(*m_data, std::move(initTypesMeta));
17919 compute_iface_vtables(*m_data, std::move(ifaceConflicts));
17920 check_invariants(*m_data);
17923 // Defined here so IndexData is a complete type for the unique_ptr
17924 // destructor.
17925 Index::~Index() = default;
17927 Index::Index(Index&&) = default;
17928 Index& Index::operator=(Index&&) = default;
17930 //////////////////////////////////////////////////////////////////////
17932 const php::Program& Index::program() const {
17933 return *m_data->program;
17936 StructuredLogEntry* Index::sample() const {
17937 return m_data->sample;
17940 //////////////////////////////////////////////////////////////////////
17942 TicketExecutor& Index::executor() const {
17943 return *m_data->executor;
17946 Client& Index::client() const {
17947 return *m_data->client;
17950 const CoroAsyncValue<Ref<Config>>& Index::configRef() const {
17951 return *m_data->configRef;
17954 //////////////////////////////////////////////////////////////////////
17956 const TSStringSet& Index::classes_with_86inits() const {
17957 return m_data->classesWith86Inits;
17960 const FSStringSet& Index::constant_init_funcs() const {
17961 return m_data->constantInitFuncs;
17964 const SStringSet& Index::units_with_type_aliases() const {
17965 return m_data->unitsWithTypeAliases;
17968 //////////////////////////////////////////////////////////////////////
17970 void Index::make_local() {
17971 HHBBC::make_local(*m_data);
17972 check_local_invariants(*m_data);
17975 //////////////////////////////////////////////////////////////////////
17977 const php::Class* Index::lookup_closure_context(const php::Class& cls) const {
17978 if (!cls.closureContextCls) return &cls;
17979 return m_data->classes.at(cls.closureContextCls);
17982 const php::Unit* Index::lookup_func_unit(const php::Func& func) const {
17983 return m_data->units.at(func.unit);
17986 const php::Unit* Index::lookup_func_original_unit(const php::Func& func) const {
17987 auto const unit = func.originalUnit ? func.originalUnit : func.unit;
17988 return m_data->units.at(unit);
17991 const php::Unit* Index::lookup_class_unit(const php::Class& cls) const {
17992 return m_data->units.at(cls.unit);
17995 const php::Class* Index::lookup_const_class(const php::Const& cns) const {
17996 return m_data->classes.at(cns.cls);
17999 const php::Class* Index::lookup_class(SString name) const {
18000 return folly::get_default(m_data->classes, name);
18003 //////////////////////////////////////////////////////////////////////
18005 void Index::for_each_unit_func(const php::Unit& unit,
18006 std::function<void(const php::Func&)> f) const {
18007 for (auto const func : unit.funcs) {
18008 f(*m_data->funcs.at(func));
18012 void Index::for_each_unit_func_mutable(php::Unit& unit,
18013 std::function<void(php::Func&)> f) {
18014 for (auto const func : unit.funcs) {
18015 f(*m_data->funcs.at(func));
18019 void Index::for_each_unit_class(
18020 const php::Unit& unit,
18021 std::function<void(const php::Class&)> f) const {
18022 for (auto const cls : unit.classes) {
18023 f(*m_data->classes.at(cls));
18027 void Index::for_each_unit_class_mutable(php::Unit& unit,
18028 std::function<void(php::Class&)> f) {
18029 for (auto const cls : unit.classes) {
18030 f(*m_data->classes.at(cls));
18034 //////////////////////////////////////////////////////////////////////
18036 void Index::preresolve_type_structures() {
18037 trace_time tracer("pre-resolve type-structures", m_data->sample);
18039 // Now that everything has been updated, calculate the invariance
18040 // for each resolved type-structure. For each class constant,
18041 // examine all subclasses and see how the resolved type-structure
18042 // changes.
18043 parallel::for_each(
18044 m_data->allClassInfos,
18045 [&] (std::unique_ptr<ClassInfo>& cinfo) {
18046 if (!cinfo->classGraph.hasCompleteChildren()) return;
18048 for (auto& cns : const_cast<php::Class*>(cinfo->cls)->constants) {
18049 assertx(cns.invariance == php::Const::Invariance::None);
18050 if (cns.kind != ConstModifiers::Kind::Type) continue;
18051 if (!cns.val.has_value()) continue;
18052 if (!cns.resolvedTypeStructure) continue;
18054 auto const checkClassname =
18055 tvIsString(cns.resolvedTypeStructure->get(s_classname));
18057 // Assume it doesn't change
18058 auto invariance = php::Const::Invariance::Same;
18059 for (auto const g : cinfo->classGraph.children()) {
18060 assertx(!g.isMissing());
18061 assertx(g.hasCompleteChildren());
18062 auto const s = g.cinfo();
18063 assertx(s);
18064 assertx(invariance != php::Const::Invariance::None);
18065 assertx(
18066 IMPLIES(!checkClassname,
18067 invariance != php::Const::Invariance::ClassnamePresent)
18069 if (s == cinfo.get()) continue;
18071 auto const it = s->clsConstants.find(cns.name);
18072 assertx(it != s->clsConstants.end());
18073 if (it->second.cls != s->cls) continue;
18074 auto const& scns = *it->second;
18076 // Overridden in some strange way. Be pessimistic.
18077 if (!scns.val.has_value() ||
18078 scns.kind != ConstModifiers::Kind::Type) {
18079 invariance = php::Const::Invariance::None;
18080 break;
18083 // The resolved type structure in this subclass is not the
18084 // same.
18085 if (scns.resolvedTypeStructure != cns.resolvedTypeStructure) {
18086 if (!scns.resolvedTypeStructure) {
18087 // It's not even resolved here, so we can't assume
18088 // anything.
18089 invariance = php::Const::Invariance::None;
18090 break;
18092 // We might still be able to assert that a classname is
18093 // always present, or a resolved type structure at least
18094 // exists.
18095 if (invariance == php::Const::Invariance::Same ||
18096 invariance == php::Const::Invariance::ClassnamePresent) {
18097 invariance =
18098 (checkClassname &&
18099 tvIsString(scns.resolvedTypeStructure->get(s_classname)))
18100 ? php::Const::Invariance::ClassnamePresent
18101 : php::Const::Invariance::Present;
18106 if (invariance != php::Const::Invariance::None) {
18107 cns.invariance = invariance;
18114 //////////////////////////////////////////////////////////////////////
18116 const CompactVector<const php::Class*>*
18117 Index::lookup_closures(const php::Class* cls) const {
18118 auto const it = m_data->classClosureMap.find(cls);
18119 if (it != end(m_data->classClosureMap)) {
18120 return &it->second;
18122 return nullptr;
18125 const hphp_fast_set<const php::Func*>*
18126 Index::lookup_extra_methods(const php::Class* cls) const {
18127 if (cls->attrs & AttrNoExpandTrait) return nullptr;
18128 auto const it = m_data->classExtraMethodMap.find(cls);
18129 if (it != end(m_data->classExtraMethodMap)) {
18130 return &it->second;
18132 return nullptr;
18135 //////////////////////////////////////////////////////////////////////
18137 Optional<res::Class> Index::resolve_class(SString clsName) const {
18138 clsName = normalizeNS(clsName);
18139 auto const it = m_data->classInfo.find(clsName);
18140 if (it == end(m_data->classInfo)) return std::nullopt;
18141 return res::Class::get(*it->second);
18144 Optional<res::Class> Index::resolve_class(const php::Class& cls) const {
18145 return resolve_class(cls.name);
18148 const php::TypeAlias* Index::lookup_type_alias(SString name) const {
18149 auto const it = m_data->typeAliases.find(name);
18150 if (it == m_data->typeAliases.end()) return nullptr;
18151 return it->second;
18154 Index::ClassOrTypeAlias Index::lookup_class_or_type_alias(SString name) const {
18155 auto const rcls = resolve_class(name);
18156 if (rcls) {
18157 auto const cls = [&] () -> const php::Class* {
18158 if (auto const ci = rcls->cinfo()) return ci->cls;
18159 return m_data->classes.at(rcls->name());
18160 }();
18161 return ClassOrTypeAlias{cls, nullptr, true};
18163 if (auto const ta = lookup_type_alias(name)) {
18164 return ClassOrTypeAlias{nullptr, ta, true};
18166 return ClassOrTypeAlias{nullptr, nullptr, false};
18169 // Given a DCls, return the most specific res::Func for that DCls. For
18170 // intersections, this will call process/general on every component of
18171 // the intersection and combine the results. For non-intersections, it
18172 // will call process/general on the sole member of the DCls. process
18173 // is called to obtain a res::Func from a ClassInfo. If a ClassInfo
18174 // isn't available, general will be called instead.
18175 template <typename P, typename G>
18176 res::Func Index::rfunc_from_dcls(const DCls& dcls,
18177 SString name,
18178 const P& process,
18179 const G& general) const {
18180 if (dcls.isExact() || dcls.isSub()) {
18181 // If this isn't an intersection, there's only one cinfo to
18182 // process and we're done.
18183 auto const cinfo = dcls.cls().cinfo();
18184 if (!cinfo) return general(dcls.containsNonRegular());
18185 return process(cinfo, dcls.isExact(), dcls.containsNonRegular());
18188 if (dcls.isIsectAndExact()) {
18189 // Even though this has an intersection list, it always must be
18190 // the exact class, so it sufficies to provide that.
18191 auto const e = dcls.isectAndExact().first;
18192 auto const cinfo = e.cinfo();
18193 if (!cinfo) return general(dcls.containsNonRegular());
18194 return process(cinfo, true, dcls.containsNonRegular());
18198 * Otherwise get a res::Func for all members of the intersection and
18199 * combine them together. Since the DCls represents a class which is
18200 * a subtype of every ClassInfo in the list, every res::Func we get
18201 * is true.
18203 * The relevant res::Func types in order from most general to more
18204 * specific are:
18206 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
18208 * Since every res::Func in the intersection is true, we take the
18209 * res::Func which is most specific. Two different res::Funcs cannot
18210 * be contradict. For example, we shouldn't get a Method and a
18211 * Missing since one implies there's no func and the other implies
18212 * one specific func. Or two different res::Funcs shouldn't resolve
18213 * to two different methods.
18216 assertx(dcls.isIsect());
18217 using Func = res::Func;
18219 auto missing = TriBool::Maybe;
18220 Func::Isect isect;
18221 const php::Func* singleMethod = nullptr;
18223 auto const DEBUG_ONLY allIncomplete = !debug || std::all_of(
18224 begin(dcls.isect()), end(dcls.isect()),
18225 [] (res::Class c) { return !c.hasCompleteChildren(); }
18228 for (auto const i : dcls.isect()) {
18229 auto const cinfo = i.cinfo();
18230 if (!cinfo) continue;
18232 auto const func = process(cinfo, false, dcls.containsNonRegular());
18233 match<void>(
18234 func.val,
18235 [&] (Func::MethodName) {},
18236 [&] (Func::Method m) {
18237 if (singleMethod) {
18238 assertx(missing != TriBool::Yes);
18239 assertx(isect.families.empty());
18240 if (singleMethod != m.finfo->func) {
18241 assertx(allIncomplete);
18242 singleMethod = nullptr;
18243 missing = TriBool::Yes;
18244 } else {
18245 missing = TriBool::No;
18247 } else if (missing != TriBool::Yes) {
18248 singleMethod = m.finfo->func;
18249 isect.families.clear();
18250 missing = TriBool::No;
18253 [&] (Func::MethodFamily fam) {
18254 if (missing == TriBool::Yes) {
18255 assertx(!singleMethod);
18256 assertx(isect.families.empty());
18257 return;
18259 if (singleMethod) {
18260 assertx(missing != TriBool::Yes);
18261 assertx(isect.families.empty());
18262 return;
18264 assertx(missing == TriBool::Maybe);
18265 isect.families.emplace_back(fam.family);
18266 isect.regularOnly |= fam.regularOnly;
18268 [&] (Func::MethodOrMissing m) {
18269 if (singleMethod) {
18270 assertx(missing != TriBool::Yes);
18271 assertx(isect.families.empty());
18272 if (singleMethod != m.finfo->func) {
18273 assertx(allIncomplete);
18274 singleMethod = nullptr;
18275 missing = TriBool::Yes;
18277 } else if (missing != TriBool::Yes) {
18278 singleMethod = m.finfo->func;
18279 isect.families.clear();
18282 [&] (Func::MissingMethod) {
18283 assertx(IMPLIES(missing == TriBool::No, allIncomplete));
18284 singleMethod = nullptr;
18285 isect.families.clear();
18286 missing = TriBool::Yes;
18288 [&] (Func::FuncName) { always_assert(false); },
18289 [&] (Func::Fun) { always_assert(false); },
18290 [&] (Func::Fun2) { always_assert(false); },
18291 [&] (Func::Method2) { always_assert(false); },
18292 [&] (Func::MethodFamily2) { always_assert(false); },
18293 [&] (Func::MethodOrMissing2) { always_assert(false); },
18294 [&] (Func::MissingFunc) { always_assert(false); },
18295 [&] (const Func::Isect&) { always_assert(false); },
18296 [&] (const Func::Isect2&) { always_assert(false); }
18300 // If we got a method, that always wins. Again, every res::Func is
18301 // true, and method is more specific than a FuncFamily, so it is
18302 // preferred.
18303 if (singleMethod) {
18304 assertx(missing != TriBool::Yes);
18305 // If missing is Maybe, then *every* resolution was to a
18306 // MethodName or MethodOrMissing, so include that fact here by
18307 // using MethodOrMissing.
18308 if (missing == TriBool::Maybe) {
18309 return Func {
18310 Func::MethodOrMissing { func_info(*m_data, singleMethod) }
18313 return Func { Func::Method { func_info(*m_data, singleMethod) } };
18315 // We only got unresolved classes. If missing is TriBool::Yes, the
18316 // function doesn't exist. Otherwise be pessimistic.
18317 if (isect.families.empty()) {
18318 if (missing == TriBool::Yes) {
18319 return Func { Func::MissingMethod { dcls.smallestCls().name(), name } };
18321 assertx(missing == TriBool::Maybe);
18322 return general(dcls.containsNonRegular());
18324 // Isect case. Isects always might contain missing funcs.
18325 assertx(missing == TriBool::Maybe);
18327 // We could add a FuncFamily multiple times, so remove duplicates.
18328 std::sort(begin(isect.families), end(isect.families));
18329 isect.families.erase(
18330 std::unique(begin(isect.families), end(isect.families)),
18331 end(isect.families)
18333 // If everything simplifies down to a single FuncFamily, just use
18334 // that.
18335 if (isect.families.size() == 1) {
18336 return Func { Func::MethodFamily { isect.families[0], isect.regularOnly } };
18338 return Func { std::move(isect) };
18341 res::Func Index::resolve_method(Context ctx,
18342 const Type& thisType,
18343 SString name) const {
18344 assertx(thisType.subtypeOf(BCls) || thisType.subtypeOf(BObj));
18346 using Func = res::Func;
18349 * Without using the class type, try to infer a set of methods
18350 * using just the method name. This will, naturally, not produce
18351 * as precise a set as when using the class type, but it's better
18352 * than nothing. For all of these results, we need to include the
18353 * possibility of the method not existing (we cannot rule that out
18354 * for this situation).
18356 auto const general = [&] (bool includeNonRegular, SString maybeCls) {
18357 assertx(name != s_construct.get());
18359 // We don't produce name-only global func families for special
18360 // methods, so be conservative. We don't call special methods in a
18361 // context where we'd expect to not know the class, so it's not
18362 // worthwhile. The same goes for __invoke and __debuginfo, which
18363 // is corresponds to every closure, and gets too large without
18364 // much value.
18365 if (!has_name_only_func_family(name)) {
18366 return Func { Func::MethodName { maybeCls, name } };
18369 // Lookup up the name-only global func families for this name. If
18370 // we don't have one, the method cannot exist because it contains
18371 // every method with that name in the program.
18372 auto const famIt = m_data->methodFamilies.find(name);
18373 if (famIt == end(m_data->methodFamilies)) {
18374 return Func { Func::MissingMethod { maybeCls, name } };
18377 // The entry exists. Consult the correct data in it, depending on
18378 // whether we're including non-regular classes or not.
18379 auto const& entry = includeNonRegular
18380 ? famIt->second.m_all
18381 : famIt->second.m_regular;
18382 assertx(entry.isEmpty() || entry.isIncomplete());
18384 if (auto const ff = entry.funcFamily()) {
18385 return Func { Func::MethodFamily { ff, !includeNonRegular } };
18386 } else if (auto const f = entry.func()) {
18387 return Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18388 } else {
18389 return Func { Func::MissingMethod { maybeCls, name } };
18393 auto const process = [&] (ClassInfo* cinfo,
18394 bool isExact,
18395 bool includeNonRegular) {
18396 assertx(name != s_construct.get());
18398 auto const methIt = cinfo->methods.find(name);
18399 if (methIt == end(cinfo->methods)) {
18400 // We don't store metadata for special methods, so be
18401 // pessimistic (the lack of a method entry does not mean the
18402 // call might fail at runtme).
18403 if (is_special_method_name(name)) {
18404 return Func { Func::MethodName { cinfo->cls->name, name } };
18406 // We're only considering this class, not it's subclasses. Since
18407 // it doesn't exist here, the resolution will always fail.
18408 if (isExact) {
18409 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18411 // The method isn't present on this class, but it might be in
18412 // the subclasses. In most cases try a general lookup to get a
18413 // slightly better type than nothing.
18414 if (includeNonRegular ||
18415 !(cinfo->cls->attrs & (AttrInterface|AttrAbstract))) {
18416 return general(includeNonRegular, cinfo->cls->name);
18419 // A special case is if we're only considering regular classes,
18420 // and this is an interface or abstract class. For those, we
18421 // "expand" the method families table to include any methods
18422 // defined in *all* regular subclasses. This is needed to
18423 // preserve monotonicity. Check this now.
18424 auto const famIt = cinfo->methodFamilies.find(name);
18425 // If no entry, treat it pessimistically like the rest of the
18426 // cases.
18427 if (famIt == end(cinfo->methodFamilies)) {
18428 return general(false, cinfo->cls->name);
18431 // We found an entry. This cannot be empty (remember the method
18432 // is guaranteed to exist on *all* regular subclasses), and must
18433 // be complete (for the same reason). Use it.
18434 auto const& entry = famIt->second;
18435 assertx(!entry.isEmpty());
18436 assertx(entry.isComplete());
18437 if (auto const ff = entry.funcFamily()) {
18438 return Func { Func::MethodFamily { ff, true } };
18439 } else if (auto const func = entry.func()) {
18440 return Func { Func::Method { func_info(*m_data, func) } };
18441 } else {
18442 always_assert(false);
18445 // The method on this class.
18446 auto const& meth = methIt->second;
18447 auto const ftarget = func_from_meth_ref(*m_data, meth.meth());
18449 // We don't store method family information about special methods
18450 // and they have special inheritance semantics.
18451 if (is_special_method_name(name)) {
18452 // If we know the class exactly, we can use ftarget.
18453 if (isExact) return Func { Func::Method { func_info(*m_data, ftarget) } };
18454 // The method isn't overwritten, but they don't inherit, so it
18455 // could be missing.
18456 if (meth.attrs & AttrNoOverride) {
18457 return Func { Func::MethodOrMissing { func_info(*m_data, ftarget) } };
18459 // Otherwise be pessimistic.
18460 return Func { Func::MethodName { cinfo->cls->name, name } };
18463 // Private method handling: Private methods have special lookup
18464 // rules. If we're in the context of a particular class, and that
18465 // class defines a private method, an instance of the class will
18466 // always call that private method (even if overridden) in that
18467 // context.
18468 assertx(cinfo->cls);
18469 if (ctx.cls == cinfo->cls) {
18470 // The context matches the current class. If we've looked up a
18471 // private method (defined on this class), then that's what
18472 // we'll call.
18473 if ((meth.attrs & AttrPrivate) && meth.topLevel()) {
18474 return Func { Func::Method { func_info(*m_data, ftarget) } };
18476 } else if ((meth.attrs & AttrPrivate) || meth.hasPrivateAncestor()) {
18477 // Otherwise the context doesn't match the current class. If the
18478 // looked up method is private, or has a private ancestor,
18479 // there's a chance we'll call that method (or
18480 // ancestor). Otherwise there's no private method in the
18481 // inheritance tree we'll call.
18482 auto const ancestor = [&] () -> const php::Func* {
18483 if (!ctx.cls) return nullptr;
18484 // Look up the ClassInfo corresponding to the context:
18485 auto const it = m_data->classInfo.find(ctx.cls->name);
18486 if (it == end(m_data->classInfo)) return nullptr;
18487 auto const ctxCInfo = it->second;
18488 // Is this context a parent of our class?
18489 if (!cinfo->classGraph.exactSubtypeOf(ctxCInfo->classGraph,
18490 true,
18491 true)) {
18492 return nullptr;
18494 // It is. See if it defines a private method.
18495 auto const it2 = ctxCInfo->methods.find(name);
18496 if (it2 == end(ctxCInfo->methods)) return nullptr;
18497 auto const& mte = it2->second;
18498 // If it defines a private method, use it.
18499 if ((mte.attrs & AttrPrivate) && mte.topLevel()) {
18500 return func_from_meth_ref(*m_data, mte.meth());
18502 // Otherwise do normal lookup.
18503 return nullptr;
18504 }();
18505 if (ancestor) {
18506 return Func { Func::Method { func_info(*m_data, ancestor) } };
18509 // If none of the above cases trigger, we still might call a
18510 // private method (in a child class), but the func-family logic
18511 // below will handle that.
18513 // If we're only including regular subclasses, and this class
18514 // itself isn't regular, the result may not necessarily include
18515 // ftarget.
18516 if (!includeNonRegular && !is_regular_class(*cinfo->cls)) {
18517 // We're not including this base class. If we're exactly this
18518 // class, there's no method at all. It will always be missing.
18519 if (isExact) {
18520 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18522 if (meth.noOverrideRegular()) {
18523 // The method isn't overridden in a subclass, but we can't
18524 // use the base class either. This leaves two cases. Either
18525 // the method isn't overridden because there are no regular
18526 // subclasses (in which case there's no resolution at all), or
18527 // because there's regular subclasses, but they use the same
18528 // method (in which case the result is just ftarget).
18529 if (!cinfo->classGraph.mightHaveRegularSubclass()) {
18530 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18532 return Func { Func::Method { func_info(*m_data, ftarget) } };
18534 // We can't use the base class (because it's non-regular), but
18535 // the method is overridden by a regular subclass.
18537 // Since this is a non-regular class and we want the result for
18538 // the regular subset, we need to consult the aux table first.
18539 auto const auxIt = cinfo->methodFamiliesAux.find(name);
18540 if (auxIt != end(cinfo->methodFamiliesAux)) {
18541 // Found an entry in the aux table. Use whatever it provides.
18542 auto const& aux = auxIt->second;
18543 if (auto const ff = aux.funcFamily()) {
18544 return Func { Func::MethodFamily { ff, true } };
18545 } else if (auto const f = aux.func()) {
18546 return aux.isComplete()
18547 ? Func { Func::Method { func_info(*m_data, f) } }
18548 : Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18549 } else {
18550 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18553 // No entry in the aux table. The result is the same as the
18554 // normal table, so fall through and use that.
18555 } else if (isExact ||
18556 meth.attrs & AttrNoOverride ||
18557 (!includeNonRegular && meth.noOverrideRegular())) {
18558 // Either we want all classes, or the base class is regular. If
18559 // the method isn't overridden we know it must be just ftarget
18560 // (the override bits include it being missing in a subclass, so
18561 // we know it cannot be missing either).
18562 return Func { Func::Method { func_info(*m_data, ftarget) } };
18565 // Look up the entry in the normal method family table and use
18566 // whatever is there.
18567 auto const famIt = cinfo->methodFamilies.find(name);
18568 assertx(famIt != end(cinfo->methodFamilies));
18569 auto const& fam = famIt->second;
18570 assertx(!fam.isEmpty());
18572 if (auto const ff = fam.funcFamily()) {
18573 return Func { Func::MethodFamily { ff, !includeNonRegular } };
18574 } else if (auto const f = fam.func()) {
18575 return (!includeNonRegular || fam.isComplete())
18576 ? Func { Func::Method { func_info(*m_data, f) } }
18577 : Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18578 } else {
18579 always_assert(false);
18583 auto const isClass = thisType.subtypeOf(BCls);
18584 if (name == s_construct.get()) {
18585 if (isClass) {
18586 return Func { Func::MethodName { nullptr, s_construct.get() } };
18588 return resolve_ctor(thisType);
18591 if (isClass) {
18592 if (!is_specialized_cls(thisType)) return general(true, nullptr);
18593 } else if (!is_specialized_obj(thisType)) {
18594 return general(false, nullptr);
18597 auto const& dcls = isClass ? dcls_of(thisType) : dobj_of(thisType);
18598 return rfunc_from_dcls(
18599 dcls,
18600 name,
18601 process,
18602 [&] (bool i) { return general(i, dcls.smallestCls().name()); }
18606 res::Func Index::resolve_ctor(const Type& obj) const {
18607 assertx(obj.subtypeOf(BObj));
18609 using Func = res::Func;
18611 // Can't say anything useful if we don't know the object type.
18612 if (!is_specialized_obj(obj)) {
18613 return Func { Func::MethodName { nullptr, s_construct.get() } };
18616 auto const& dcls = dobj_of(obj);
18617 return rfunc_from_dcls(
18618 dcls,
18619 s_construct.get(),
18620 [&] (ClassInfo* cinfo, bool isExact, bool includeNonRegular) {
18621 // We're dealing with an object here, which never uses
18622 // non-regular classes.
18623 assertx(!includeNonRegular);
18625 // See if this class has a ctor.
18626 auto const methIt = cinfo->methods.find(s_construct.get());
18627 if (methIt == end(cinfo->methods)) {
18628 // There's no ctor on this class. This doesn't mean the ctor
18629 // won't exist at runtime, it might get the default ctor, so
18630 // we have to be conservative.
18631 return Func {
18632 Func::MethodName { cinfo->cls->name, s_construct.get() }
18636 // We have a ctor, but it might be overridden in a subclass.
18637 auto const& mte = methIt->second;
18638 assertx(!(mte.attrs & AttrStatic));
18639 auto const ftarget = func_from_meth_ref(*m_data, mte.meth());
18640 assertx(!(ftarget->attrs & AttrStatic));
18642 // If this class is known exactly, or we know nothing overrides
18643 // this ctor, we know this ctor is precisely it.
18644 if (isExact || mte.noOverrideRegular()) {
18645 // If this class isn't regular, and doesn't have any regular
18646 // subclasses (or if it's exact), this resolution will always
18647 // fail.
18648 if (!is_regular_class(*cinfo->cls) &&
18649 (isExact || !cinfo->classGraph.mightHaveRegularSubclass())) {
18650 return Func {
18651 Func::MissingMethod { cinfo->cls->name, s_construct.get() }
18654 return Func { Func::Method { func_info(*m_data, ftarget) } };
18657 // If this isn't a regular class, we need to check the "aux"
18658 // entry first (which always has priority when only looking at
18659 // the regular subset).
18660 if (!is_regular_class(*cinfo->cls)) {
18661 auto const auxIt = cinfo->methodFamiliesAux.find(s_construct.get());
18662 if (auxIt != end(cinfo->methodFamiliesAux)) {
18663 auto const& aux = auxIt->second;
18664 if (auto const ff = aux.funcFamily()) {
18665 return Func { Func::MethodFamily { ff, true } };
18666 } else if (auto const f = aux.func()) {
18667 return aux.isComplete()
18668 ? Func { Func::Method { func_info(*m_data, f) } }
18669 : Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18670 } else {
18671 // Ctor doesn't exist in any regular subclasses. This can
18672 // happen with interfaces. The ctor might get the default
18673 // ctor at runtime, so be conservative.
18674 return Func {
18675 Func::MethodName { cinfo->cls->name, s_construct.get() }
18680 // Otherwise this class is regular (in which case there's just
18681 // method families, or there's no entry in aux, which means the
18682 // regular subset entry is the same as the full entry.
18684 auto const famIt = cinfo->methodFamilies.find(s_construct.get());
18685 assertx(famIt != cinfo->methodFamilies.end());
18686 auto const& fam = famIt->second;
18687 assertx(!fam.isEmpty());
18689 if (auto const ff = fam.funcFamily()) {
18690 return Func { Func::MethodFamily { ff, true } };
18691 } else if (auto const f = fam.func()) {
18692 // Since we're looking at the regular subset, we can assume
18693 // the set is complete, regardless of the flag on fam.
18694 return Func { Func::Method { func_info(*m_data, f) } };
18695 } else {
18696 always_assert(false);
18699 [&] (bool includeNonRegular) {
18700 assertx(!includeNonRegular);
18701 return Func {
18702 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
18708 res::Func Index::resolve_func(SString name) const {
18709 name = normalizeNS(name);
18710 auto const it = m_data->funcs.find(name);
18711 if (it == end(m_data->funcs)) {
18712 return res::Func { res::Func::MissingFunc { name } };
18714 auto const func = it->second;
18715 assertx(func->attrs & AttrPersistent);
18716 return res::Func { res::Func::Fun { func_info(*m_data, func) } };
18719 res::Func Index::resolve_func_or_method(const php::Func& f) const {
18720 if (!f.cls) return res::Func { res::Func::Fun { func_info(*m_data, &f) } };
18721 return res::Func { res::Func::Method { func_info(*m_data, &f) } };
18724 bool Index::func_depends_on_arg(const php::Func* func, size_t arg) const {
18725 auto const& finfo = *func_info(*m_data, func);
18726 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
18729 // Helper function: Given a DCls, visit every subclass it represents,
18730 // passing it to the given callable. If the callable returns false,
18731 // stop iteration. Return false if any of the classes is unresolved,
18732 // true otherwise. This is used to simplify the below functions which
18733 // need to iterate over all possible subclasses and union the results.
18734 template <typename F>
18735 bool Index::visit_every_dcls_cls(const DCls& dcls, const F& f) const {
18736 if (dcls.isExact()) {
18737 auto const cinfo = dcls.cls().cinfo();
18738 if (!cinfo) return false;
18739 if (dcls.containsNonRegular() || is_regular_class(*cinfo->cls)) {
18740 f(cinfo);
18742 return true;
18743 } else if (dcls.isSub()) {
18744 auto unresolved = false;
18745 res::Class::visitEverySub(
18746 std::array<res::Class, 1>{dcls.cls()},
18747 dcls.containsNonRegular(),
18748 [&] (res::Class c) {
18749 if (c.hasCompleteChildren()) {
18750 if (auto const cinfo = c.cinfo()) return f(cinfo);
18752 unresolved = true;
18753 return false;
18756 return !unresolved;
18757 } else if (dcls.isIsect()) {
18758 auto const& isect = dcls.isect();
18759 assertx(isect.size() > 1);
18761 auto unresolved = false;
18762 res::Class::visitEverySub(
18763 isect,
18764 dcls.containsNonRegular(),
18765 [&] (res::Class c) {
18766 if (c.hasCompleteChildren()) {
18767 if (auto const cinfo = c.cinfo()) return f(cinfo);
18769 unresolved = true;
18770 return false;
18773 return !unresolved;
18774 } else {
18775 // Even though this has an intersection list, it must be the exact
18776 // class, so it's sufficient to provide that.
18777 assertx(dcls.isIsectAndExact());
18778 auto const e = dcls.isectAndExact().first;
18779 auto const cinfo = e.cinfo();
18780 if (!cinfo) return false;
18781 if (dcls.containsNonRegular() || is_regular_class(*cinfo->cls)) {
18782 f(cinfo);
18784 return true;
18788 ClsConstLookupResult Index::lookup_class_constant(Context ctx,
18789 const Type& cls,
18790 const Type& name) const {
18791 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
18792 show(ctx), show(cls), show(name));
18793 Trace::Indent _;
18795 using R = ClsConstLookupResult;
18797 auto const conservative = [] {
18798 ITRACE(4, "conservative\n");
18799 return R{ TInitCell, TriBool::Maybe, true };
18802 auto const notFound = [] {
18803 ITRACE(4, "not found\n");
18804 return R{ TBottom, TriBool::No, false };
18807 if (!is_specialized_cls(cls)) return conservative();
18809 // We could easily support the case where we don't know the constant
18810 // name, but know the class (like we do for properties), by unioning
18811 // together all possible constants. However it very rarely happens,
18812 // but when it does, the set of constants to union together can be
18813 // huge and it becomes very expensive.
18814 if (!is_specialized_string(name)) return conservative();
18815 auto const sname = sval_of(name);
18817 // If this lookup is safe to cache. Some classes can have a huge
18818 // number of subclasses and unioning together all possible constants
18819 // can become very expensive. We can aleviate some of this expense
18820 // by caching results. We cannot cache a result when we use 86cinit
18821 // analysis since that can change.
18822 auto cachable = true;
18824 auto const process = [&] (const ClassInfo* ci) {
18825 ITRACE(4, "{}:\n", ci->cls->name);
18826 Trace::Indent _;
18828 // Does the constant exist on this class?
18829 auto const it = ci->clsConstants.find(sname);
18830 if (it == ci->clsConstants.end()) return notFound();
18832 // Is it a value and is it non-abstract (we only deal with
18833 // concrete constants).
18834 auto const& cns = *it->second.get();
18835 if (cns.kind != ConstModifiers::Kind::Value) return notFound();
18836 if (!cns.val.has_value()) return notFound();
18838 auto const cnsIdx = it->second.idx;
18840 // Determine the constant's value and return it
18841 auto const r = [&] {
18842 if (cns.val->m_type == KindOfUninit) {
18843 // Constant is defined by a 86cinit. Use the result from
18844 // analysis and add a dependency. We cannot cache in this
18845 // case.
18846 cachable = false;
18847 auto const cnsCls = m_data->classes.at(cns.cls);
18848 if (ctx.func) {
18849 auto const cinit = cnsCls->methods.back().get();
18850 assertx(cinit->name == s_86cinit.get());
18851 add_dependency(*m_data, cinit, ctx, Dep::ClsConst);
18854 ITRACE(4, "(dynamic)\n");
18855 auto const type = [&] {
18856 auto const cnsClsCi = folly::get_default(m_data->classInfo, cnsCls->name);
18857 if (!cnsClsCi || cnsIdx >= cnsClsCi->clsConstTypes.size()) {
18858 return TInitCell;
18860 return cnsClsCi->clsConstTypes[cnsIdx].type;
18861 }();
18862 return R{ type, TriBool::Yes, true };
18865 // Fully resolved constant with a known value
18866 auto mightThrow = bool(ci->cls->attrs & AttrInternal);
18867 if (!mightThrow) {
18868 auto const unit = lookup_class_unit(*ci->cls);
18869 auto const moduleName = unit->moduleName;
18870 auto const packageInfo = unit->packageInfo;
18871 if (auto const activeDeployment = packageInfo.getActiveDeployment()) {
18872 if (!packageInfo.moduleInDeployment(
18873 moduleName, *activeDeployment, DeployKind::Hard)) {
18874 mightThrow = true;
18878 return R{ from_cell(*cns.val), TriBool::Yes, mightThrow };
18879 }();
18880 ITRACE(4, "-> {}\n", show(r));
18881 return r;
18884 auto const& dcls = dcls_of(cls);
18885 if (dcls.isSub()) {
18886 // Before anything, look up this entry in the cache. We don't
18887 // bother with the cache for the exact case because it's quick and
18888 // there's little point.
18889 auto const cinfo = dcls.cls().cinfo();
18890 if (!cinfo) return conservative();
18891 if (auto const it =
18892 m_data->clsConstLookupCache.find(std::make_pair(cinfo->cls, sname));
18893 it != m_data->clsConstLookupCache.end()) {
18894 ITRACE(4, "cache hit: {}\n", show(it->second));
18895 return it->second;
18899 Optional<R> result;
18900 auto const resolved = visit_every_dcls_cls(
18901 dcls,
18902 [&] (const ClassInfo* cinfo) {
18903 if (result) ITRACE(5, "-> {}\n", show(*result));
18904 auto r = process(cinfo);
18905 if (!result) {
18906 result.emplace(std::move(r));
18907 } else {
18908 *result |= r;
18910 return true;
18913 if (!resolved) return conservative();
18914 assertx(result.has_value());
18916 // Save this for future lookups if we can
18917 if (dcls.isSub() && cachable) {
18918 auto const cinfo = dcls.cls().cinfo();
18919 assertx(cinfo);
18920 m_data->clsConstLookupCache.emplace(
18921 std::make_pair(cinfo->cls, sname),
18922 *result
18926 ITRACE(4, "-> {}\n", show(*result));
18927 return *result;
18930 std::vector<std::pair<SString, ConstIndex>>
18931 Index::lookup_flattened_class_type_constants(const php::Class&) const {
18932 // Should never be used with an Index.
18933 always_assert(false);
18936 std::vector<std::pair<SString, ClsConstInfo>>
18937 Index::lookup_class_constants(const php::Class& cls) const {
18938 std::vector<std::pair<SString, ClsConstInfo>> out;
18939 out.reserve(cls.constants.size());
18941 auto const cinfo = folly::get_default(m_data->classInfo, cls.name);
18942 for (size_t i = 0, size = cls.constants.size(); i < size; ++i) {
18943 auto const& cns = cls.constants[i];
18944 if (cns.kind != ConstModifiers::Kind::Value) continue;
18945 if (!cns.val) continue;
18946 if (cns.val->m_type != KindOfUninit) {
18947 out.emplace_back(cns.name, ClsConstInfo{ from_cell(*cns.val), 0 });
18948 } else if (cinfo && i < cinfo->clsConstTypes.size()) {
18949 out.emplace_back(cns.name, cinfo->clsConstTypes[i]);
18950 } else {
18951 out.emplace_back(cns.name, ClsConstInfo{ TInitCell, 0 });
18954 return out;
18957 ClsTypeConstLookupResult
18958 Index::lookup_class_type_constant(
18959 const Type& cls,
18960 const Type& name,
18961 const ClsTypeConstLookupResolver& resolver) const {
18962 ITRACE(4, "lookup_class_type_constant: {}::{}\n", show(cls), show(name));
18963 Trace::Indent _;
18965 using R = ClsTypeConstLookupResult;
18967 auto const conservative = [] {
18968 ITRACE(4, "conservative\n");
18969 return R{
18970 TypeStructureResolution { TSDictN, true },
18971 TriBool::Maybe,
18972 TriBool::Maybe
18976 auto const notFound = [] {
18977 ITRACE(4, "not found\n");
18978 return R {
18979 TypeStructureResolution { TBottom, false },
18980 TriBool::No,
18981 TriBool::No
18985 // Unlike lookup_class_constant, we distinguish abstract from
18986 // not-found, as the runtime sometimes treats them differently.
18987 auto const abstract = [] {
18988 ITRACE(4, "abstract\n");
18989 return R {
18990 TypeStructureResolution { TBottom, false },
18991 TriBool::No,
18992 TriBool::Yes
18996 if (!is_specialized_cls(cls)) return conservative();
18998 // As in lookup_class_constant, we could handle this, but it's not
18999 // worth it.
19000 if (!is_specialized_string(name)) return conservative();
19001 auto const sname = sval_of(name);
19003 auto const process = [&] (const ClassInfo* ci) {
19004 ITRACE(4, "{}:\n", ci->cls->name);
19005 Trace::Indent _;
19007 // Does the type constant exist on this class?
19008 auto const it = ci->clsConstants.find(sname);
19009 if (it == ci->clsConstants.end()) return notFound();
19011 // Is it an actual non-abstract type-constant?
19012 auto const& cns = *it->second;
19013 if (cns.kind != ConstModifiers::Kind::Type) return notFound();
19014 if (!cns.val.has_value()) return abstract();
19016 assertx(tvIsDict(*cns.val));
19017 ITRACE(4, "({}) {}\n", cns.cls, show(dict_val(val(*cns.val).parr)));
19019 // If we've been given a resolver, use it. Otherwise resolve it in
19020 // the normal way.
19021 auto resolved = resolver
19022 ? resolver(cns, *ci->cls)
19023 : resolve_type_structure(IndexAdaptor { *this }, cns, *ci->cls);
19025 // The result of resolve_type_structure isn't, in general,
19026 // static. However a type-constant will always be, so force that
19027 // here.
19028 assertx(resolved.type.is(BBottom) || resolved.type.couldBe(BUnc));
19029 resolved.type &= TUnc;
19030 auto const r = R{
19031 std::move(resolved),
19032 TriBool::Yes,
19033 TriBool::No
19035 ITRACE(4, "-> {}\n", show(r));
19036 return r;
19039 auto const& dcls = dcls_of(cls);
19041 Optional<R> result;
19042 auto const resolved = visit_every_dcls_cls(
19043 dcls,
19044 [&] (const ClassInfo* cinfo) {
19045 if (result) {
19046 ITRACE(5, "-> {}\n", show(*result));
19048 auto r = process(cinfo);
19049 if (!result) {
19050 result.emplace(std::move(r));
19051 } else {
19052 *result |= r;
19054 return true;
19057 if (!resolved) return conservative();
19058 assertx(result.has_value());
19060 ITRACE(4, "-> {}\n", show(*result));
19061 return *result;
19064 ClsTypeConstLookupResult
19065 Index::lookup_class_type_constant(const php::Class&,
19066 SString,
19067 HHBBC::ConstIndex) const {
19068 // Should never be called with an Index.
19069 always_assert(false);
19072 Type Index::lookup_constant(Context ctx, SString cnsName) const {
19073 auto iter = m_data->constants.find(cnsName);
19074 if (iter == end(m_data->constants)) return TBottom;
19076 auto constant = iter->second;
19077 if (type(constant->val) != KindOfUninit) {
19078 return from_cell(constant->val);
19081 // Assume a runtime call to Constant::get(), which will invoke
19082 // 86cinit_<cnsName>(). Look up it's return type.
19084 auto const func_name = Constant::funcNameFromName(cnsName);
19085 assertx(func_name && "func_name will never be nullptr");
19087 auto rfunc = resolve_func(func_name);
19088 assertx(rfunc.exactFunc());
19090 return lookup_return_type(ctx, nullptr, rfunc, Dep::ConstVal).t;
19093 Index::ReturnType
19094 Index::lookup_foldable_return_type(Context ctx,
19095 const CallContext& calleeCtx) const {
19096 auto const func = calleeCtx.callee;
19097 constexpr auto max_interp_nexting_level = 2;
19098 static __thread uint32_t interp_nesting_level;
19100 using R = ReturnType;
19102 auto const ctxType = adjust_closure_context(
19103 IndexAdaptor { *this },
19104 calleeCtx
19107 // Don't fold functions when staticness mismatches
19108 if (!func->isClosureBody) {
19109 if ((func->attrs & AttrStatic) && ctxType.couldBe(TObj)) {
19110 return R{ TInitCell, false };
19112 if (!(func->attrs & AttrStatic) && ctxType.couldBe(TCls)) {
19113 return R{ TInitCell, false };
19117 auto const& finfo = *func_info(*m_data, func);
19118 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
19119 return R{ finfo.returnTy, true };
19122 auto showArgs DEBUG_ONLY = [] (const CompactVector<Type>& a) {
19123 std::string ret, sep;
19124 for (auto& arg : a) {
19125 folly::format(&ret, "{}{}", sep, show(arg));
19126 sep = ",";
19128 return ret;
19132 ContextRetTyMap::const_accessor acc;
19133 if (m_data->foldableReturnTypeMap.find(acc, calleeCtx)) {
19134 FTRACE_MOD(
19135 Trace::hhbbc, 4,
19136 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
19137 func->cls ? func->cls->name : staticEmptyString(),
19138 func->cls ? "::" : "",
19139 func->name,
19140 showArgs(calleeCtx.args),
19141 CallContextHashCompare{}.hash(calleeCtx));
19143 assertx(is_scalar(acc->second.t));
19144 assertx(acc->second.effectFree);
19145 return acc->second;
19149 if (frozen()) {
19150 FTRACE_MOD(
19151 Trace::hhbbc, 4,
19152 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
19153 func->cls ? func->cls->name : staticEmptyString(),
19154 func->cls ? "::" : "",
19155 func->name,
19156 showArgs(calleeCtx.args),
19157 CallContextHashCompare{}.hash(calleeCtx));
19158 return R{ TInitCell, false };
19161 if (interp_nesting_level > max_interp_nexting_level) {
19162 add_dependency(*m_data, func, ctx, Dep::InlineDepthLimit);
19163 return R{ TInitCell, false };
19166 auto const contextType = [&] {
19167 ++interp_nesting_level;
19168 SCOPE_EXIT { --interp_nesting_level; };
19170 auto const wf = php::WideFunc::cns(func);
19171 auto const fa = analyze_func_inline(
19172 IndexAdaptor { *this },
19173 AnalysisContext { func->unit, wf, func->cls, &ctx.forDep() },
19174 ctxType,
19175 calleeCtx.args,
19176 nullptr,
19177 CollectionOpts::EffectFreeOnly
19179 return R{
19180 fa.effectFree ? fa.inferredReturn : TInitCell,
19181 fa.effectFree
19183 }();
19185 if (!is_scalar(contextType.t)) return R{ TInitCell, false };
19187 ContextRetTyMap::accessor acc;
19188 if (m_data->foldableReturnTypeMap.insert(acc, calleeCtx)) {
19189 acc->second = contextType;
19190 } else {
19191 // someone beat us to it
19192 assertx(acc->second.t == contextType.t);
19194 return contextType;
19197 Index::ReturnType Index::lookup_return_type(Context ctx,
19198 MethodsInfo* methods,
19199 res::Func rfunc,
19200 Dep dep) const {
19201 using R = ReturnType;
19203 auto const funcFamily = [&] (FuncFamily* fam, bool regularOnly) {
19204 add_dependency(*m_data, fam, ctx, dep);
19205 return fam->infoFor(regularOnly).m_returnTy.get(
19206 [&] {
19207 auto ret = TBottom;
19208 auto effectFree = true;
19209 for (auto const pf : fam->possibleFuncs()) {
19210 if (regularOnly && !pf.inRegular()) continue;
19211 auto const finfo = func_info(*m_data, pf.ptr());
19212 if (!finfo->func) return R{ TInitCell, false };
19213 ret |= unctx(finfo->returnTy);
19214 effectFree &= finfo->effectFree;
19215 if (!ret.strictSubtypeOf(BInitCell) && !effectFree) break;
19217 return R{ std::move(ret), effectFree };
19221 auto const meth = [&] (const php::Func* func) {
19222 if (methods) {
19223 if (auto ret = methods->lookupReturnType(*func)) {
19224 return R{ unctx(std::move(ret->t)), ret->effectFree };
19227 add_dependency(*m_data, func, ctx, dep);
19228 auto const finfo = func_info(*m_data, func);
19229 if (!finfo->func) return R{ TInitCell, false };
19230 return R{ unctx(finfo->returnTy), finfo->effectFree };
19233 return match<R>(
19234 rfunc.val,
19235 [&] (res::Func::FuncName) { return R{ TInitCell, false }; },
19236 [&] (res::Func::MethodName) { return R{ TInitCell, false }; },
19237 [&] (res::Func::Fun f) {
19238 add_dependency(*m_data, f.finfo->func, ctx, dep);
19239 return R{ unctx(f.finfo->returnTy), f.finfo->effectFree };
19241 [&] (res::Func::Method m) { return meth(m.finfo->func); },
19242 [&] (res::Func::MethodFamily fam) {
19243 return funcFamily(fam.family, fam.regularOnly);
19245 [&] (res::Func::MethodOrMissing m) { return meth(m.finfo->func); },
19246 [&] (res::Func::MissingFunc) { return R{ TBottom, false }; },
19247 [&] (res::Func::MissingMethod) { return R{ TBottom, false }; },
19248 [&] (const res::Func::Isect& i) {
19249 auto ty = TInitCell;
19250 auto anyEffectFree = false;
19251 for (auto const ff : i.families) {
19252 auto const [t, e] = funcFamily(ff, i.regularOnly);
19253 ty &= t;
19254 if (e) anyEffectFree = true;
19256 return R{ std::move(ty), anyEffectFree };
19258 [&] (res::Func::Fun2) -> R { always_assert(false); },
19259 [&] (res::Func::Method2) -> R { always_assert(false); },
19260 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
19261 [&] (res::Func::MethodOrMissing2) -> R { always_assert(false); },
19262 [&] (res::Func::Isect2&) -> R { always_assert(false); }
19266 Index::ReturnType Index::lookup_return_type(Context caller,
19267 MethodsInfo* methods,
19268 const CompactVector<Type>& args,
19269 const Type& context,
19270 res::Func rfunc,
19271 Dep dep) const {
19272 using R = ReturnType;
19274 auto const funcFamily = [&] (FuncFamily* fam, bool regularOnly) {
19275 add_dependency(*m_data, fam, caller, dep);
19276 auto ret = fam->infoFor(regularOnly).m_returnTy.get(
19277 [&] {
19278 auto ty = TBottom;
19279 auto effectFree = true;
19280 for (auto const pf : fam->possibleFuncs()) {
19281 if (regularOnly && !pf.inRegular()) continue;
19282 auto const finfo = func_info(*m_data, pf.ptr());
19283 if (!finfo->func) return R{ TInitCell, false };
19284 ty |= finfo->returnTy;
19285 effectFree &= finfo->effectFree;
19286 if (!ty.strictSubtypeOf(BInitCell) && !effectFree) break;
19288 return R{ std::move(ty), effectFree };
19291 return R{
19292 return_with_context(std::move(ret.t), context),
19293 ret.effectFree
19296 auto const meth = [&] (const php::Func* func) {
19297 auto const finfo = func_info(*m_data, func);
19298 if (!finfo->func) return R{ TInitCell, false };
19300 auto returnType = [&] {
19301 if (methods) {
19302 if (auto ret = methods->lookupReturnType(*func)) {
19303 return *ret;
19306 add_dependency(*m_data, func, caller, dep);
19307 return R{ finfo->returnTy, finfo->effectFree };
19308 }();
19310 return context_sensitive_return_type(
19311 *m_data,
19312 caller,
19313 { finfo->func, args, context },
19314 std::move(returnType)
19318 return match<R>(
19319 rfunc.val,
19320 [&] (res::Func::FuncName) {
19321 return lookup_return_type(caller, methods, rfunc, dep);
19323 [&] (res::Func::MethodName) {
19324 return lookup_return_type(caller, methods, rfunc, dep);
19326 [&] (res::Func::Fun f) {
19327 add_dependency(*m_data, f.finfo->func, caller, dep);
19328 return context_sensitive_return_type(
19329 *m_data,
19330 caller,
19331 { f.finfo->func, args, context },
19332 R{ f.finfo->returnTy, f.finfo->effectFree }
19335 [&] (res::Func::Method m) { return meth(m.finfo->func); },
19336 [&] (res::Func::MethodFamily fam) {
19337 return funcFamily(fam.family, fam.regularOnly);
19339 [&] (res::Func::MethodOrMissing m) { return meth(m.finfo->func); },
19340 [&] (res::Func::MissingFunc) { return R { TBottom, false }; },
19341 [&] (res::Func::MissingMethod) { return R { TBottom, false }; },
19342 [&] (const res::Func::Isect& i) {
19343 auto ty = TInitCell;
19344 auto anyEffectFree = false;
19345 for (auto const ff : i.families) {
19346 auto const [t, e] = funcFamily(ff, i.regularOnly);
19347 ty &= t;
19348 if (e) anyEffectFree = true;
19350 return R{ std::move(ty), anyEffectFree };
19352 [&] (res::Func::Fun2) -> R { always_assert(false); },
19353 [&] (res::Func::Method2) -> R { always_assert(false); },
19354 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
19355 [&] (res::Func::MethodOrMissing2) -> R { always_assert(false); },
19356 [&] (res::Func::Isect2&) -> R { always_assert(false); }
19360 std::pair<Index::ReturnType, size_t>
19361 Index::lookup_return_type_raw(const php::Func* f) const {
19362 auto it = func_info(*m_data, f);
19363 if (it->func) {
19364 assertx(it->func == f);
19365 return {
19366 ReturnType{ it->returnTy, it->effectFree },
19367 it->returnRefinements
19370 return { ReturnType{ TInitCell, false }, 0 };
19373 CompactVector<Type>
19374 Index::lookup_closure_use_vars(const php::Func* func,
19375 bool move) const {
19376 assertx(func->isClosureBody);
19378 auto const numUseVars = closure_num_use_vars(func);
19379 if (!numUseVars) return {};
19380 auto const it = m_data->closureUseVars.find(func->cls);
19381 if (it == end(m_data->closureUseVars)) {
19382 return CompactVector<Type>(numUseVars, TCell);
19384 if (move) return std::move(it->second);
19385 return it->second;
19388 PropState
19389 Index::lookup_private_props(const php::Class* cls,
19390 bool move) const {
19391 auto it = m_data->privatePropInfo.find(cls);
19392 if (it != end(m_data->privatePropInfo)) {
19393 if (move) return std::move(it->second);
19394 return it->second;
19396 return make_unknown_propstate(
19397 IndexAdaptor { *this },
19398 *cls,
19399 [&] (const php::Prop& prop) {
19400 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
19405 PropState
19406 Index::lookup_private_statics(const php::Class* cls,
19407 bool move) const {
19408 auto it = m_data->privateStaticPropInfo.find(cls);
19409 if (it != end(m_data->privateStaticPropInfo)) {
19410 if (move) return std::move(it->second);
19411 return it->second;
19413 return make_unknown_propstate(
19414 IndexAdaptor { *this },
19415 *cls,
19416 [&] (const php::Prop& prop) {
19417 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
19422 PropState Index::lookup_public_statics(const php::Class* cls) const {
19423 auto const cinfo = [&] () -> const ClassInfo* {
19424 auto const it = m_data->classInfo.find(cls->name);
19425 if (it == end(m_data->classInfo)) return nullptr;
19426 return it->second;
19427 }();
19429 PropState state;
19430 for (auto const& prop : cls->properties) {
19431 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
19432 !(prop.attrs & AttrStatic)) {
19433 continue;
19436 auto [ty, everModified] = [&] {
19437 if (!cinfo) return std::make_pair(TInitCell, true);
19439 if (!m_data->seenPublicSPropMutations) {
19440 return std::make_pair(
19441 union_of(
19442 adjust_type_for_prop(
19443 IndexAdaptor { *this },
19444 *cls,
19445 &prop.typeConstraint,
19446 TInitCell
19448 initial_type_for_public_sprop(*this, *cls, prop)
19450 true
19454 auto const it = cinfo->publicStaticProps.find(prop.name);
19455 if (it == end(cinfo->publicStaticProps)) {
19456 return std::make_pair(
19457 initial_type_for_public_sprop(*this, *cls, prop),
19458 false
19461 return std::make_pair(
19462 it->second.inferredType,
19463 it->second.everModified
19465 }();
19466 state.emplace(
19467 prop.name,
19468 PropStateElem{
19469 std::move(ty),
19470 &prop.typeConstraint,
19471 prop.attrs,
19472 everModified
19476 return state;
19480 * Entry point for static property lookups from the Index. Return
19481 * metadata about a `cls'::`name' static property access in the given
19482 * context.
19484 PropLookupResult Index::lookup_static(Context ctx,
19485 const PropertiesInfo& privateProps,
19486 const Type& cls,
19487 const Type& name) const {
19488 ITRACE(4, "lookup_static: {} {}::${}\n", show(ctx), show(cls), show(name));
19489 Trace::Indent _;
19491 using R = PropLookupResult;
19493 // First try to obtain the property name as a static string
19494 auto const sname = [&] () -> SString {
19495 // Treat non-string names conservatively, but the caller should be
19496 // checking this.
19497 if (!is_specialized_string(name)) return nullptr;
19498 return sval_of(name);
19499 }();
19501 // Conservative result when we can't do any better. The type can be
19502 // anything, and anything might throw.
19503 auto const conservative = [&] {
19504 ITRACE(4, "conservative\n");
19505 return R{
19506 TInitCell,
19507 sname,
19508 TriBool::Maybe,
19509 TriBool::Maybe,
19510 TriBool::Maybe,
19511 TriBool::Maybe,
19512 TriBool::Maybe,
19513 true
19517 // If we don't know what `cls' is, there's not much we can do.
19518 if (!is_specialized_cls(cls)) return conservative();
19520 // Turn the context class into a ClassInfo* for convenience.
19521 const ClassInfo* ctxCls = nullptr;
19522 if (ctx.cls) {
19523 // I don't think this can ever fail (we should always be able to
19524 // resolve the class since we're currently processing it). If it
19525 // does, be conservative.
19526 auto const rCtx = resolve_class(ctx.cls->name);
19527 if (!rCtx) return conservative();
19528 ctxCls = rCtx->cinfo();
19529 if (!ctxCls) return conservative();
19532 auto const& dcls = dcls_of(cls);
19533 auto const start = dcls.cls();
19535 Optional<R> result;
19536 auto const resolved = visit_every_dcls_cls(
19537 dcls,
19538 [&] (const ClassInfo* cinfo) {
19539 auto r = lookup_static_impl(
19540 *m_data,
19541 ctx,
19542 ctxCls,
19543 privateProps,
19544 cinfo,
19545 sname,
19546 dcls.isSub() && !sname && cinfo != start.cinfo()
19548 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
19549 if (!result) {
19550 result.emplace(std::move(r));
19551 } else {
19552 *result |= r;
19554 return true;
19557 if (!resolved) return conservative();
19558 assertx(result.has_value());
19560 ITRACE(4, "union -> {}\n", show(*result));
19561 return *result;
19564 Type Index::lookup_public_prop(const Type& obj, const Type& name) const {
19565 if (!is_specialized_obj(obj)) return TCell;
19567 if (!is_specialized_string(name)) return TCell;
19568 auto const sname = sval_of(name);
19570 auto ty = TBottom;
19571 auto const resolved = visit_every_dcls_cls(
19572 dobj_of(obj),
19573 [&] (const ClassInfo* cinfo) {
19574 ty |= lookup_public_prop_impl(
19575 *m_data,
19576 cinfo,
19577 sname
19579 return ty.strictSubtypeOf(TCell);
19582 if (!resolved) return TCell;
19583 return ty;
19586 Type Index::lookup_public_prop(const php::Class* cls, SString name) const {
19587 auto const it = m_data->classInfo.find(cls->name);
19588 if (it == end(m_data->classInfo)) {
19589 return TCell;
19591 return lookup_public_prop_impl(*m_data, it->second, name);
19594 bool Index::lookup_class_init_might_raise(Context ctx, res::Class cls) const {
19595 if (auto const ci = cls.cinfo()) {
19596 return class_init_might_raise(*m_data, ctx, ci);
19597 } else if (cls.cinfo2()) {
19598 // Not implemented yet
19599 always_assert(false);
19600 } else {
19601 return true;
19605 Slot
19606 Index::lookup_iface_vtable_slot(const php::Class* cls) const {
19607 return folly::get_default(m_data->ifaceSlotMap, cls->name, kInvalidSlot);
19610 //////////////////////////////////////////////////////////////////////
19613 * Entry point for static property type mutation from the Index. Merge
19614 * `val' into the known type for any accessible `cls'::`name' static
19615 * property. The mutation will be recovered into either
19616 * `publicMutations' or `privateProps' depending on the properties
19617 * found. Mutations to AttrConst properties are ignored, unless
19618 * `ignoreConst' is true.
19620 PropMergeResult Index::merge_static_type(
19621 Context ctx,
19622 PublicSPropMutations& publicMutations,
19623 PropertiesInfo& privateProps,
19624 const Type& cls,
19625 const Type& name,
19626 const Type& val,
19627 bool checkUB,
19628 bool ignoreConst,
19629 bool mustBeReadOnly) const {
19630 ITRACE(
19631 4, "merge_static_type: {} {}::${} {}\n",
19632 show(ctx), show(cls), show(name), show(val)
19634 Trace::Indent _;
19636 assertx(val.subtypeOf(BInitCell));
19638 using R = PropMergeResult;
19640 // In some cases we might try to merge Bottom if we're in
19641 // unreachable code. This won't affect anything, so just skip out
19642 // early.
19643 if (val.subtypeOf(BBottom)) return R{ TBottom, TriBool::No };
19645 // Try to turn the given property name into a static string
19646 auto const sname = [&] () -> SString {
19647 // Non-string names are treated conservatively here. The caller
19648 // should be checking for these and doing the right thing.
19649 if (!is_specialized_string(name)) return nullptr;
19650 return sval_of(name);
19651 }();
19653 // The case where we don't know `cls':
19654 auto const unknownCls = [&] {
19655 if (!sname) {
19656 // Very bad case. We don't know `cls' or the property name. This
19657 // mutation can be affecting anything, so merge it into all
19658 // properties (this drops type information for public
19659 // properties).
19660 ITRACE(4, "unknown class and prop. merging everything\n");
19661 publicMutations.mergeUnknown(ctx);
19662 privateProps.mergeInAllPrivateStatics(
19663 IndexAdaptor { *this }, unctx(val), ignoreConst, mustBeReadOnly
19665 } else {
19666 // Otherwise we don't know `cls', but do know the property
19667 // name. We'll store this mutation separately and union it in to
19668 // any lookup with the same name.
19669 ITRACE(4, "unknown class. merging all props with name {}\n", sname);
19671 publicMutations.mergeUnknownClass(sname, unctx(val));
19673 // Assume that it could possibly affect any private property with
19674 // the same name.
19675 privateProps.mergeInPrivateStatic(
19676 IndexAdaptor { *this }, sname, unctx(val), ignoreConst, mustBeReadOnly
19680 // To be conservative, say we might throw and be conservative about
19681 // conversions.
19682 return PropMergeResult{
19683 loosen_likeness(val),
19684 TriBool::Maybe
19688 // check if we can determine the class.
19689 if (!is_specialized_cls(cls)) return unknownCls();
19691 const ClassInfo* ctxCls = nullptr;
19692 if (ctx.cls) {
19693 auto const rCtx = resolve_class(ctx.cls->name);
19694 // We should only be not able to resolve our own context if the
19695 // class is not instantiable. In that case, the merge can't
19696 // happen.
19697 if (!rCtx) return R{ TBottom, TriBool::No };
19698 ctxCls = rCtx->cinfo();
19699 if (!ctxCls) return unknownCls();
19702 auto const mergePublic = [&] (const ClassInfo* ci,
19703 const php::Prop& prop,
19704 const Type& val) {
19705 publicMutations.mergeKnown(ci, prop, val);
19708 auto const& dcls = dcls_of(cls);
19709 Optional<res::Class> start;
19710 if (dcls.isExact() || dcls.isSub()) {
19711 start = dcls.cls();
19712 } else if (dcls.isIsectAndExact()) {
19713 start = dcls.isectAndExact().first;
19716 Optional<R> result;
19717 auto const resolved = visit_every_dcls_cls(
19718 dcls,
19719 [&] (const ClassInfo* cinfo) {
19720 auto r = merge_static_type_impl(
19721 *m_data,
19722 ctx,
19723 mergePublic,
19724 privateProps,
19725 ctxCls,
19726 cinfo,
19727 sname,
19728 val,
19729 checkUB,
19730 ignoreConst,
19731 mustBeReadOnly,
19732 dcls.isSub() && !sname && cinfo != start->cinfo()
19734 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
19735 if (!result) {
19736 result.emplace(std::move(r));
19737 } else {
19738 *result |= r;
19740 return true;
19743 if (!resolved) return unknownCls();
19744 assertx(result.has_value());
19745 ITRACE(4, "union -> {}\n", show(*result));
19746 return *result;
19749 //////////////////////////////////////////////////////////////////////
19751 DependencyContext Index::dependency_context(const Context& ctx) const {
19752 return dep_context(*m_data, ctx);
19755 bool Index::using_class_dependencies() const {
19756 return m_data->useClassDependencies;
19759 void Index::use_class_dependencies(bool f) {
19760 if (f != m_data->useClassDependencies) {
19761 m_data->dependencyMap.clear();
19762 m_data->useClassDependencies = f;
19766 void Index::refine_class_constants(const Context& ctx,
19767 const ResolvedConstants& resolved,
19768 DependencyContextSet& deps) {
19769 if (resolved.empty()) return;
19771 auto changed = false;
19772 auto const cls = ctx.func->cls;
19773 assertx(cls);
19774 auto& constants = cls->constants;
19776 for (auto& c : resolved) {
19777 assertx(c.first < constants.size());
19778 auto& cnst = constants[c.first];
19779 assertx(cnst.kind == ConstModifiers::Kind::Value);
19781 always_assert(cnst.val && type(*cnst.val) == KindOfUninit);
19782 if (auto const val = tv(c.second.type)) {
19783 assertx(val->m_type != KindOfUninit);
19784 cnst.val = *val;
19785 // Deleting from the types map is too expensive, so just leave
19786 // any entry. We won't look at it if val is set.
19787 changed = true;
19788 } else if (auto const cinfo =
19789 folly::get_default(m_data->classInfo, cls->name)) {
19790 auto& old = [&] () -> ClsConstInfo& {
19791 if (c.first >= cinfo->clsConstTypes.size()) {
19792 auto const newSize = std::max(c.first+1, resolved.back().first+1);
19793 cinfo->clsConstTypes.resize(newSize, ClsConstInfo { TInitCell, 0 });
19795 return cinfo->clsConstTypes[c.first];
19796 }();
19798 if (c.second.type.strictlyMoreRefined(old.type)) {
19799 always_assert(c.second.refinements > old.refinements);
19800 old = std::move(c.second);
19801 changed = true;
19802 } else {
19803 always_assert_flog(
19804 c.second.type.moreRefined(old.type),
19805 "Class constant type invariant violated for {}::{}\n"
19806 " {} is not at least as refined as {}\n",
19807 ctx.func->cls->name,
19808 cnst.name,
19809 show(c.second.type),
19810 show(old.type)
19816 if (changed) {
19817 find_deps(*m_data, ctx.func, Dep::ClsConst, deps);
19821 void Index::refine_constants(const FuncAnalysisResult& fa,
19822 DependencyContextSet& deps) {
19823 auto const& func = fa.ctx.func;
19824 if (func->cls) return;
19826 auto const cns_name = Constant::nameFromFuncName(func->name);
19827 if (!cns_name) return;
19829 auto const cns = m_data->constants.at(cns_name);
19830 auto const val = tv(fa.inferredReturn);
19831 if (!val) {
19832 always_assert_flog(
19833 type(cns->val) == KindOfUninit,
19834 "Constant value invariant violated in {}.\n"
19835 " Value went from {} to {}",
19836 cns_name,
19837 show(from_cell(cns->val)),
19838 show(fa.inferredReturn)
19840 return;
19843 if (type(cns->val) != KindOfUninit) {
19844 always_assert_flog(
19845 from_cell(cns->val) == fa.inferredReturn,
19846 "Constant value invariant violated in {}.\n"
19847 " Value went from {} to {}",
19848 cns_name,
19849 show(from_cell(cns->val)),
19850 show(fa.inferredReturn)
19852 } else {
19853 cns->val = *val;
19856 find_deps(*m_data, func, Dep::ConstVal, deps);
19859 void Index::refine_return_info(const FuncAnalysisResult& fa,
19860 DependencyContextSet& deps) {
19861 auto const& func = fa.ctx.func;
19862 auto const finfo = func_info(*m_data, func);
19864 auto const error_loc = [&] {
19865 return folly::sformat(
19866 "{} {}{}",
19867 func->unit,
19868 func->cls
19869 ? folly::to<std::string>(func->cls->name->data(), "::")
19870 : std::string{},
19871 func->name
19875 auto dep = Dep{};
19876 if (finfo->retParam == NoLocalId && fa.retParam != NoLocalId) {
19877 // This is just a heuristic; it doesn't mean that the value passed
19878 // in was returned, but that the value of the parameter at the
19879 // point of the RetC was returned. We use it to make (heuristic)
19880 // decisions about whether to do inline interps, so we only allow
19881 // it to change once (otherwise later passes might not do the
19882 // inline interp, and get worse results, which could trigger other
19883 // assertions in Index::refine_*).
19884 dep = Dep::ReturnTy;
19885 finfo->retParam = fa.retParam;
19888 auto unusedParams = ~fa.usedParams;
19889 if (finfo->unusedParams != unusedParams) {
19890 dep = Dep::ReturnTy;
19891 always_assert_flog(
19892 (finfo->unusedParams | unusedParams) == unusedParams,
19893 "Index unusedParams decreased in {}.\n",
19894 error_loc()
19896 finfo->unusedParams = unusedParams;
19899 auto resetFuncFamilies = false;
19900 if (fa.inferredReturn.strictlyMoreRefined(finfo->returnTy)) {
19901 if (finfo->returnRefinements < options.returnTypeRefineLimit) {
19902 finfo->returnTy = fa.inferredReturn;
19903 // We've modifed the return type, so reset any cached FuncFamily
19904 // return types.
19905 resetFuncFamilies = true;
19906 dep = is_scalar(fa.inferredReturn)
19907 ? Dep::ReturnTy | Dep::InlineDepthLimit : Dep::ReturnTy;
19908 finfo->returnRefinements += fa.localReturnRefinements + 1;
19909 if (finfo->returnRefinements > options.returnTypeRefineLimit) {
19910 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19912 } else {
19913 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19915 } else {
19916 always_assert_flog(
19917 fa.inferredReturn.moreRefined(finfo->returnTy),
19918 "Index return type invariant violated in {}.\n"
19919 " {} is not at least as refined as {}\n",
19920 error_loc(),
19921 show(fa.inferredReturn),
19922 show(finfo->returnTy)
19926 always_assert_flog(
19927 !finfo->effectFree || fa.effectFree,
19928 "Index effectFree changed from true to false in {} {}.\n",
19929 func->unit,
19930 func_fullname(*func)
19933 if (finfo->effectFree != fa.effectFree) {
19934 finfo->effectFree = fa.effectFree;
19935 dep = Dep::InlineDepthLimit | Dep::ReturnTy;
19938 if (dep != Dep{}) {
19939 find_deps(*m_data, func, dep, deps);
19940 if (resetFuncFamilies) {
19941 assertx(has_dep(dep, Dep::ReturnTy));
19942 for (auto const ff : finfo->families) {
19943 // Reset the cached return type information for all the
19944 // FuncFamilies this function is a part of. Always reset the
19945 // "all" information, and if there's regular subset
19946 // information, reset that too.
19947 if (!ff->m_all.m_returnTy.reset() &&
19948 (!ff->m_regular || !ff->m_regular->m_returnTy.reset())) {
19949 continue;
19951 // Only load the deps for this func family if we're the ones
19952 // who successfully reset. Only one thread needs to do it.
19953 find_deps(*m_data, ff, Dep::ReturnTy, deps);
19959 bool Index::refine_closure_use_vars(const php::Class* cls,
19960 const CompactVector<Type>& vars) {
19961 assertx(is_closure(*cls));
19963 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
19964 always_assert_flog(
19965 vars[i].equivalentlyRefined(unctx(vars[i])),
19966 "Closure cannot have a used var with a context dependent type"
19970 auto& current = [&] () -> CompactVector<Type>& {
19971 std::lock_guard<std::mutex> _{closure_use_vars_mutex};
19972 return m_data->closureUseVars[cls];
19973 }();
19975 always_assert(current.empty() || current.size() == vars.size());
19976 if (current.empty()) {
19977 current = vars;
19978 return true;
19981 auto changed = false;
19982 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
19983 if (vars[i].strictSubtypeOf(current[i])) {
19984 changed = true;
19985 current[i] = vars[i];
19986 } else {
19987 always_assert_flog(
19988 vars[i].moreRefined(current[i]),
19989 "Index closure_use_var invariant violated in {}.\n"
19990 " {} is not at least as refined as {}\n",
19991 cls->name,
19992 show(vars[i]),
19993 show(current[i])
19998 return changed;
20001 template<class Container>
20002 void refine_private_propstate(Container& cont,
20003 const php::Class* cls,
20004 const PropState& state) {
20005 assertx(!is_used_trait(*cls));
20006 auto* elm = [&] () -> typename Container::value_type* {
20007 std::lock_guard<std::mutex> _{private_propstate_mutex};
20008 auto it = cont.find(cls);
20009 if (it == end(cont)) {
20010 if (!state.empty()) cont[cls] = state;
20011 return nullptr;
20013 return &*it;
20014 }();
20016 if (!elm) return;
20018 for (auto& kv : state) {
20019 auto& target = elm->second[kv.first];
20020 assertx(target.tc == kv.second.tc);
20021 always_assert_flog(
20022 kv.second.ty.moreRefined(target.ty),
20023 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
20024 cls->name->data(),
20025 kv.first->data(),
20026 show(kv.second.ty),
20027 show(target.ty)
20029 target.ty = kv.second.ty;
20031 if (kv.second.everModified) {
20032 always_assert_flog(
20033 target.everModified,
20034 "PropState refinement failed on {}::${} -- "
20035 "everModified flag went from false to true\n",
20036 cls->name->data(),
20037 kv.first->data()
20039 } else {
20040 target.everModified = false;
20045 void Index::refine_private_props(const php::Class* cls,
20046 const PropState& state) {
20047 refine_private_propstate(m_data->privatePropInfo, cls, state);
20050 void Index::refine_private_statics(const php::Class* cls,
20051 const PropState& state) {
20052 // We can't store context dependent types in private statics since they
20053 // could be accessed using different contexts.
20054 auto cleanedState = PropState{};
20055 for (auto const& prop : state) {
20056 auto& elem = cleanedState[prop.first];
20057 elem.ty = unctx(prop.second.ty);
20058 elem.tc = prop.second.tc;
20059 elem.attrs = prop.second.attrs;
20060 elem.everModified = prop.second.everModified;
20063 refine_private_propstate(m_data->privateStaticPropInfo, cls, cleanedState);
20066 void Index::record_public_static_mutations(const php::Func& func,
20067 PublicSPropMutations mutations) {
20068 if (!mutations.m_data) {
20069 m_data->publicSPropMutations.erase(&func);
20070 return;
20072 m_data->publicSPropMutations.insert_or_assign(&func, std::move(mutations));
20075 void Index::update_prop_initial_values(const Context& ctx,
20076 const ResolvedPropInits& resolved,
20077 DependencyContextSet& deps) {
20078 auto& props = const_cast<php::Class*>(ctx.cls)->properties;
20080 auto changed = false;
20081 for (auto const& [idx, info] : resolved) {
20082 assertx(idx < props.size());
20083 auto& prop = props[idx];
20085 auto const allResolved = [&] {
20086 if (prop.typeConstraint.isUnresolved()) return false;
20087 for (auto const& ub : prop.ubs.m_constraints) {
20088 if (ub.isUnresolved()) return false;
20090 return true;
20093 if (info.satisfies) {
20094 if (!(prop.attrs & AttrInitialSatisfiesTC) && allResolved()) {
20095 attribute_setter(prop.attrs, true, AttrInitialSatisfiesTC);
20096 changed = true;
20098 } else {
20099 always_assert_flog(
20100 !(prop.attrs & AttrInitialSatisfiesTC),
20101 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
20102 " Went from true to false",
20103 ctx.cls->name, prop.name
20107 always_assert_flog(
20108 IMPLIES(!(prop.attrs & AttrDeepInit), !info.deepInit),
20109 "AttrDeepInit invariant violated for {}::{}\n"
20110 " Went from false to true",
20111 ctx.cls->name, prop.name
20113 attribute_setter(prop.attrs, info.deepInit, AttrDeepInit);
20115 if (type(info.val) != KindOfUninit) {
20116 always_assert_flog(
20117 type(prop.val) == KindOfUninit ||
20118 from_cell(prop.val) == from_cell(info.val),
20119 "Property initial value invariant violated for {}::{}\n"
20120 " Value went from {} to {}",
20121 ctx.cls->name, prop.name,
20122 show(from_cell(prop.val)), show(from_cell(info.val))
20124 prop.val = info.val;
20125 } else {
20126 always_assert_flog(
20127 type(prop.val) == KindOfUninit,
20128 "Property initial value invariant violated for {}::{}\n"
20129 " Value went from {} to not set",
20130 ctx.cls->name, prop.name,
20131 show(from_cell(prop.val))
20135 if (!changed) return;
20137 auto const it = m_data->classInfo.find(ctx.cls->name);
20138 if (it == end(m_data->classInfo)) return;
20139 auto const cinfo = it->second;
20141 // Both a pinit and a sinit can have resolved property values. When
20142 // analyzing constants we'll process each function separately and
20143 // potentially in different threads. Both will want to inspect the
20144 // property Attrs and the hasBadInitialPropValues. So, if we reach
20145 // here, take a lock to ensure both don't stomp on each other.
20146 static std::array<std::mutex, 256> locks;
20147 auto& lock = locks[pointer_hash<const php::Class>{}(ctx.cls) % locks.size()];
20148 std::lock_guard<std::mutex> _{lock};
20150 auto const noBad = std::all_of(
20151 begin(props), end(props),
20152 [] (const php::Prop& prop) {
20153 return bool(prop.attrs & AttrInitialSatisfiesTC);
20157 if (cinfo->hasBadInitialPropValues) {
20158 if (noBad) {
20159 cinfo->hasBadInitialPropValues = false;
20160 find_deps(*m_data, ctx.cls, Dep::PropBadInitialValues, deps);
20162 } else {
20163 // If it's false, another thread got here before us and set it to
20164 // false.
20165 always_assert(noBad);
20169 void Index::refine_public_statics(DependencyContextSet& deps) {
20170 trace_time update("update public statics");
20172 // Union together the mutations for each function, including the functions
20173 // which weren't analyzed this round.
20174 auto nothing_known = false;
20175 PublicSPropMutations::UnknownMap unknown;
20176 PublicSPropMutations::KnownMap known;
20177 for (auto const& mutations : m_data->publicSPropMutations) {
20178 if (!mutations.second.m_data) continue;
20179 if (mutations.second.m_data->m_nothing_known) {
20180 nothing_known = true;
20181 break;
20184 for (auto const& kv : mutations.second.m_data->m_unknown) {
20185 auto const ret = unknown.insert(kv);
20186 if (!ret.second) ret.first->second |= kv.second;
20188 for (auto const& kv : mutations.second.m_data->m_known) {
20189 auto const ret = known.insert(kv);
20190 if (!ret.second) ret.first->second |= kv.second;
20194 if (nothing_known) {
20195 // We cannot go from knowing the types to not knowing the types (this is
20196 // equivalent to widening the types).
20197 always_assert(!m_data->seenPublicSPropMutations);
20198 return;
20200 m_data->seenPublicSPropMutations = true;
20202 // Refine known class state
20203 parallel::for_each(
20204 m_data->allClassInfos,
20205 [&] (std::unique_ptr<ClassInfo>& cinfo) {
20206 for (auto const& prop : cinfo->cls->properties) {
20207 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
20208 !(prop.attrs & AttrStatic)) {
20209 continue;
20212 auto knownClsType = [&] {
20213 auto const it = known.find(
20214 PublicSPropMutations::KnownKey { cinfo.get(), prop.name }
20216 // If we didn't see a mutation, the type is TBottom.
20217 return it == end(known) ? TBottom : it->second;
20218 }();
20220 auto unknownClsType = [&] {
20221 auto const it = unknown.find(prop.name);
20222 // If we didn't see a mutation, the type is TBottom.
20223 return it == end(unknown) ? TBottom : it->second;
20224 }();
20226 // We can't keep context dependent types in public properties.
20227 auto newType = adjust_type_for_prop(
20228 IndexAdaptor { *this },
20229 *cinfo->cls,
20230 &prop.typeConstraint,
20231 unctx(union_of(std::move(knownClsType), std::move(unknownClsType)))
20234 auto& entry = cinfo->publicStaticProps[prop.name];
20236 if (!newType.is(BBottom)) {
20237 always_assert_flog(
20238 entry.everModified,
20239 "Static property index invariant violated on {}::{}:\n"
20240 " everModified flag went from false to true",
20241 cinfo->cls->name,
20242 prop.name
20244 } else {
20245 entry.everModified = false;
20248 // The type from the mutations doesn't contain the in-class
20249 // initializer types. Add that here.
20250 auto effectiveType = union_of(
20251 std::move(newType),
20252 initial_type_for_public_sprop(*this, *cinfo->cls, prop)
20256 * We may only shrink the types we recorded for each property. (If a
20257 * property type ever grows, the interpreter could infer something
20258 * incorrect at some step.)
20260 always_assert_flog(
20261 effectiveType.subtypeOf(entry.inferredType),
20262 "Static property index invariant violated on {}::{}:\n"
20263 " {} is not a subtype of {}",
20264 cinfo->cls->name,
20265 prop.name,
20266 show(effectiveType),
20267 show(entry.inferredType)
20270 // Put a limit on the refinements to ensure termination. Since
20271 // we only ever refine types, we can stop at any point and still
20272 // maintain correctness.
20273 if (effectiveType.strictSubtypeOf(entry.inferredType)) {
20274 if (entry.refinements + 1 < options.publicSPropRefineLimit) {
20275 find_deps(*m_data, &prop, Dep::PublicSProp, deps);
20276 entry.inferredType = std::move(effectiveType);
20277 ++entry.refinements;
20278 } else {
20279 FTRACE(
20280 1, "maxed out public static property refinements for {}:{}\n",
20281 cinfo->cls->name,
20282 prop.name
20291 bool Index::frozen() const {
20292 return m_data->frozen;
20295 void Index::freeze() {
20296 m_data->frozen = true;
20297 m_data->ever_frozen = true;
20300 Type AnalysisIndex::unserialize_type(Type t) const {
20301 return unserialize_classes(AnalysisIndexAdaptor { *this }, std::move(t));
20305 * Note that these functions run in separate threads, and
20306 * intentionally don't bump Trace::hhbbc_time. If you want to see
20307 * these times, set TRACE=hhbbc_time:1
20309 #define CLEAR(x) \
20311 trace_time _{"clearing " #x}; \
20312 _.ignore_client_stats(); \
20313 (x).clear(); \
20316 void Index::cleanup_for_final() {
20317 trace_time _{"cleanup for final", m_data->sample};
20318 CLEAR(m_data->dependencyMap);
20321 void Index::cleanup_post_emit() {
20322 trace_time _{"cleanup post emit", m_data->sample};
20324 std::vector<std::function<void()>> clearers;
20325 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
20326 CLEAR_PARALLEL(m_data->classes);
20327 CLEAR_PARALLEL(m_data->funcs);
20328 CLEAR_PARALLEL(m_data->typeAliases);
20329 CLEAR_PARALLEL(m_data->enums);
20330 CLEAR_PARALLEL(m_data->constants);
20331 CLEAR_PARALLEL(m_data->modules);
20332 CLEAR_PARALLEL(m_data->units);
20334 CLEAR_PARALLEL(m_data->classClosureMap);
20335 CLEAR_PARALLEL(m_data->classExtraMethodMap);
20337 CLEAR_PARALLEL(m_data->classInfo);
20339 CLEAR_PARALLEL(m_data->privatePropInfo);
20340 CLEAR_PARALLEL(m_data->privateStaticPropInfo);
20341 CLEAR_PARALLEL(m_data->publicSPropMutations);
20342 CLEAR_PARALLEL(m_data->ifaceSlotMap);
20343 CLEAR_PARALLEL(m_data->closureUseVars);
20345 CLEAR_PARALLEL(m_data->methodFamilies);
20347 CLEAR_PARALLEL(m_data->funcFamilies);
20348 CLEAR_PARALLEL(m_data->funcFamilyStaticInfos);
20350 CLEAR_PARALLEL(m_data->clsConstLookupCache);
20352 CLEAR_PARALLEL(m_data->foldableReturnTypeMap);
20353 CLEAR_PARALLEL(m_data->contextualReturnTypes);
20355 parallel::for_each(clearers, [] (const std::function<void()>& f) { f(); });
20358 trace_time t{"reset funcInfo"};
20359 t.ignore_client_stats();
20360 parallel::for_each(
20361 m_data->funcInfo,
20362 [] (auto& u) {
20363 u.returnTy = TBottom;
20364 u.families.clear();
20367 m_data->funcInfo.clear();
20370 // Class-infos and program need to be freed after all Type instances
20371 // are destroyed, as Type::checkInvariants may try to access them.
20374 trace_time t{"reset allClassInfos"};
20375 t.ignore_client_stats();
20376 parallel::for_each(m_data->allClassInfos, [] (auto& u) { u.reset(); });
20377 m_data->allClassInfos.clear();
20381 trace_time t{"reset program"};
20382 t.ignore_client_stats();
20383 parallel::for_each(m_data->program->units, [] (auto& u) { u.reset(); });
20384 parallel::for_each(m_data->program->classes, [] (auto& u) { u.reset(); });
20385 parallel::for_each(m_data->program->funcs, [] (auto& f) { f.reset(); });
20386 m_data->program.reset();
20390 void Index::thaw() {
20391 m_data->frozen = false;
20394 //////////////////////////////////////////////////////////////////////
20396 FuncClsUnit AnalysisWorklist::next() {
20397 if (list.empty()) return FuncClsUnit{};
20398 auto n = list.front();
20399 in.erase(n);
20400 list.pop_front();
20401 return n;
20404 void AnalysisWorklist::schedule(FuncClsUnit fc) {
20405 assertx(IMPLIES(fc.cls(), !is_closure(*fc.cls())));
20406 if (!in.emplace(fc).second) return;
20407 ITRACE(2, "scheduling {} onto worklist\n", show(fc));
20408 list.emplace_back(fc);
20411 //////////////////////////////////////////////////////////////////////
20413 bool AnalysisDeps::add(Class c, bool inTypeCns) {
20414 return inTypeCns
20415 ? typeCnsClasses.emplace(c.name).second
20416 : classes.emplace(c.name).second;
20419 bool AnalysisDeps::add(ConstIndex cns, bool inTypeCns) {
20420 // Dependency on class constant implies a dependency on the class as
20421 // well.
20422 add(Class { cns.cls }, inTypeCns);
20423 return inTypeCns
20424 ? typeCnsClsConstants.emplace(cns).second
20425 : clsConstants.emplace(cns).second;
20428 bool AnalysisDeps::add(Constant cns) {
20429 // Dependency on top-level constant implies a dependency on the
20430 // 86cinit initialized as well (which may not even exist).
20431 add(Func { HPHP::Constant::funcNameFromName(cns.name) }, Type::Meta);
20432 return constants.emplace(cns.name).second;
20435 bool AnalysisDeps::add(AnyClassConstant any, bool inTypeCns) {
20436 // Dependency on class constant implies a dependency on the class as
20437 // well.
20438 add(Class { any.name }, inTypeCns);
20439 return inTypeCns
20440 ? typeCnsAnyClsConstants.emplace(any.name).second
20441 : anyClsConstants.emplace(any.name).second;
20444 AnalysisDeps::Type AnalysisDeps::add(const php::Func& f, Type t) {
20445 return f.cls
20446 ? add(MethRef { f }, t)
20447 : add(Func { f.name }, t);
20450 AnalysisDeps::Type AnalysisDeps::add(MethRef m, Type t) {
20451 add(Class { m.cls });
20452 return merge(methods[m], t | Type::Meta);
20455 AnalysisDeps::Type AnalysisDeps::add(Func f, Type t) {
20456 return merge(funcs[f.name], t | Type::Meta);
20459 AnalysisDeps::Type AnalysisDeps::merge(Type& o, Type n) {
20460 auto const added = n - o;
20461 o |= n;
20462 return added;
20465 AnalysisDeps& AnalysisDeps::operator|=(const AnalysisDeps& o) {
20466 clsConstants.insert(begin(o.clsConstants), end(o.clsConstants));
20467 classes.insert(begin(o.classes), end(o.classes));
20468 constants.insert(begin(o.constants), end(o.constants));
20469 anyClsConstants.insert(begin(o.anyClsConstants), end(o.anyClsConstants));
20470 typeCnsClasses.insert(begin(o.typeCnsClasses), end(o.typeCnsClasses));
20471 typeCnsClsConstants.insert(
20472 begin(o.typeCnsClsConstants),
20473 end(o.typeCnsClsConstants)
20475 typeCnsAnyClsConstants.insert(
20476 begin(o.typeCnsAnyClsConstants),
20477 end(o.typeCnsAnyClsConstants)
20479 for (auto const [name, t] : o.funcs) funcs[name] |= t;
20480 for (auto const [meth, t] : o.methods) methods[meth] |= t;
20481 return *this;
20484 std::string show(AnalysisDeps::Type t) {
20485 using T = AnalysisDeps::Type;
20486 std::string out;
20487 auto const add = [&] (const char* s) {
20488 folly::format(&out, "{}{}", out.empty() ? "" : ",", s);
20490 if (t & T::Meta) add("meta");
20491 if (t & T::RetType) add("return type");
20492 if (t & T::ScalarRetType) add("scalar return type");
20493 if (t & T::RetParam) add("returned param");
20494 if (t & T::UnusedParams) add("unused params");
20495 if (t & T::Bytecode) add("bytecode");
20496 return out;
20499 std::string show(const AnalysisDeps& d) {
20500 using namespace folly::gen;
20501 auto const toCpp = [] (SString n) { return n->toCppString(); };
20503 std::string out;
20504 if (!d.classes.empty()) {
20505 folly::format(
20506 &out, " classes: {}\n",
20507 from(d.classes) | map(toCpp) | unsplit<std::string>(", ")
20510 if (!d.funcs.empty()) {
20511 folly::format(
20512 &out, " funcs: {}\n",
20513 from(d.funcs)
20514 | map([&] (auto const& p) {
20515 return folly::sformat("{} -> [{}]", toCpp(p.first), show(p.second));
20517 | unsplit<std::string>(", ")
20520 if (!d.methods.empty()) {
20521 folly::format(
20522 &out, " methods: {}\n",
20523 from(d.methods)
20524 | map([] (auto const& p) {
20525 return folly::sformat("{} -> [{}]", show(p.first), show(p.second));
20527 | unsplit<std::string>(", ")
20530 if (!d.clsConstants.empty()) {
20531 folly::format(
20532 &out, " class-constants: {}\n",
20533 from(d.clsConstants)
20534 | map([] (ConstIndex idx) { return show(idx); })
20535 | unsplit<std::string>(", ")
20538 if (!d.anyClsConstants.empty()) {
20539 folly::format(
20540 &out, " any class-constants: {}\n",
20541 from(d.anyClsConstants) | map(toCpp) | unsplit<std::string>(", ")
20544 if (!d.typeCnsClasses.empty()) {
20545 folly::format(
20546 &out, " type-cns classes: {}\n",
20547 from(d.typeCnsClasses) | map(toCpp) | unsplit<std::string>(", ")
20550 if (!d.typeCnsClsConstants.empty()) {
20551 folly::format(
20552 &out, " type-cns class-constants: {}\n",
20553 from(d.typeCnsClsConstants)
20554 | map([] (ConstIndex idx) { return show(idx); })
20555 | unsplit<std::string>(", ")
20558 if (!d.typeCnsAnyClsConstants.empty()) {
20559 folly::format(
20560 &out, " type-cns any class-constants: {}\n",
20561 from(d.typeCnsAnyClsConstants) | map(toCpp) | unsplit<std::string>(", ")
20565 if (out.empty()) out = " (none)\n";
20566 return out;
20569 //////////////////////////////////////////////////////////////////////
20571 void AnalysisChangeSet::changed(ConstIndex idx) {
20572 clsConstants.emplace(idx);
20575 void AnalysisChangeSet::changed(const php::Constant& c) {
20576 constants.emplace(c.name);
20579 void AnalysisChangeSet::changed(const php::Func& f, Type t) {
20580 assertx(AnalysisDeps::isValidForChanges(t));
20581 if (f.cls) {
20582 methods[MethRef { f }] |= t;
20583 } else {
20584 funcs[f.name] |= t;
20588 void AnalysisChangeSet::fixed(ConstIndex idx) {
20589 fixedClsConstants.emplace(idx);
20592 void AnalysisChangeSet::fixed(const php::Class& cls) {
20593 allClsConstantsFixed.emplace(cls.name);
20596 void AnalysisChangeSet::fixed(const php::Unit& unit) {
20597 unitsFixed.emplace(unit.filename);
20600 void AnalysisChangeSet::typeCnsName(const php::Class& cls,
20601 Class name) {
20602 if (cls.name->tsame(name.name)) return;
20603 clsTypeCnsNames[cls.name].emplace(name.name);
20606 void AnalysisChangeSet::typeCnsName(const php::Unit& unit,
20607 Class name) {
20608 unitTypeCnsNames[unit.filename].emplace(name.name);
20611 void AnalysisChangeSet::filter(const TSStringSet& keepClasses,
20612 const FSStringSet& keepFuncs,
20613 const SStringSet& keepUnits,
20614 const SStringSet& keepConstants) {
20615 folly::erase_if(
20616 funcs, [&] (auto const& p) { return !keepFuncs.count(p.first); }
20618 folly::erase_if(
20619 methods, [&] (auto const& p) { return !keepClasses.count(p.first.cls); }
20621 folly::erase_if(
20622 constants, [&] (SString s) { return !keepConstants.count(s); }
20624 folly::erase_if(
20625 clsConstants, [&] (ConstIndex idx) { return !keepClasses.count(idx.cls); }
20627 folly::erase_if(
20628 fixedClsConstants,
20629 [&] (ConstIndex idx) {
20630 return !keepClasses.count(idx.cls) || allClsConstantsFixed.count(idx.cls);
20633 folly::erase_if(
20634 allClsConstantsFixed, [&] (SString s) { return !keepClasses.count(s); }
20636 folly::erase_if(
20637 unitsFixed, [&] (SString s) { return !keepUnits.count(s); }
20639 folly::erase_if(
20640 clsTypeCnsNames, [&] (auto const& p) { return !keepClasses.count(p.first); }
20642 folly::erase_if(
20643 unitTypeCnsNames, [&] (auto const& p) { return !keepUnits.count(p.first); }
20647 //////////////////////////////////////////////////////////////////////
20649 namespace {
20651 template <typename V, typename H, typename E, typename C>
20652 std::vector<SString>
20653 map_to_sorted_key_vec(const hphp_fast_map<SString, V, H, E>& m,
20654 const C& c) {
20655 std::vector<SString> keys;
20656 keys.reserve(m.size());
20657 for (auto const& [k, _] : m) keys.emplace_back(k);
20658 std::sort(begin(keys), end(keys), c);
20659 return keys;
20662 template <typename V, typename H, typename E, typename C>
20663 std::vector<V> map_to_sorted_vec(const hphp_fast_map<SString, V, H, E>& m,
20664 const C& c) {
20665 auto const keys = map_to_sorted_key_vec(m, c);
20666 std::vector<V> out;
20667 out.reserve(keys.size());
20668 for (auto const k : keys) out.emplace_back(m.at(k));
20669 return out;
20674 std::vector<SString> AnalysisInput::classNames() const {
20675 return map_to_sorted_key_vec(classes, string_data_lt_type{});
20678 std::vector<SString> AnalysisInput::funcNames() const {
20679 return map_to_sorted_key_vec(funcs, string_data_lt_func{});
20682 std::vector<SString> AnalysisInput::unitNames() const {
20683 return map_to_sorted_key_vec(units, string_data_lt{});
20686 std::vector<SString> AnalysisInput::cinfoNames() const {
20687 using namespace folly::gen;
20688 return from(classNames())
20689 | filter([&] (SString n) { return (bool)cinfos.count(n); })
20690 | as<std::vector>();
20693 std::vector<SString> AnalysisInput::minfoNames() const {
20694 using namespace folly::gen;
20695 return from(classNames())
20696 | filter([&] (SString n) { return (bool)minfos.count(n); })
20697 | as<std::vector>();
20700 AnalysisInput::Tuple AnalysisInput::toTuple(Ref<Meta> meta) const {
20701 return Tuple{
20702 map_to_sorted_vec(classes, string_data_lt_type{}),
20703 map_to_sorted_vec(funcs, string_data_lt_func{}),
20704 map_to_sorted_vec(units, string_data_lt{}),
20705 map_to_sorted_vec(classBC, string_data_lt_type{}),
20706 map_to_sorted_vec(funcBC, string_data_lt_func{}),
20707 map_to_sorted_vec(cinfos, string_data_lt_type{}),
20708 map_to_sorted_vec(finfos, string_data_lt_func{}),
20709 map_to_sorted_vec(minfos, string_data_lt_type{}),
20710 map_to_sorted_vec(depClasses, string_data_lt_type{}),
20711 map_to_sorted_vec(depFuncs, string_data_lt_func{}),
20712 map_to_sorted_vec(depUnits, string_data_lt{}),
20713 std::move(meta)
20717 //////////////////////////////////////////////////////////////////////
20719 namespace {
20721 // Many BucketSets are identical, so we intern them in the below
20722 // table, which saves a lot of memory.
20724 struct BucketSetHasher {
20725 size_t operator()(const AnalysisInput::BucketSet& b) const {
20726 return b.hash();
20730 folly_concurrent_hash_map_simd<
20731 AnalysisInput::BucketSet,
20732 std::nullptr_t,
20733 BucketSetHasher
20734 > s_bucketSetIntern;
20736 AnalysisInput::BucketSet s_emptyBucketSet;
20738 // Likewise, when we serde them, we can refer to them by indices
20739 // rather than encoding the same set multiple times.
20740 struct BucketSetSerdeTable {
20741 using A = AnalysisInput;
20743 hphp_fast_map<const A::BucketSet*, size_t> bToIdx;
20744 std::vector<const A::BucketSet*> idxToB;
20746 void encode(BlobEncoder& sd, const A::BucketSet& b) {
20747 if (auto const idx = folly::get_ptr(bToIdx, &b)) {
20748 sd(*idx);
20749 } else {
20750 idxToB.emplace_back(&b);
20751 bToIdx.try_emplace(&b, idxToB.size());
20752 sd(0)(b);
20755 const AnalysisInput::BucketSet* decode(BlobDecoder& sd) {
20756 auto const idx = sd.make<size_t>();
20757 if (!idx) {
20758 auto const b = A::BucketSet::intern(sd.make<A::BucketSet>());
20759 idxToB.emplace_back(b);
20760 return b;
20761 } else {
20762 assertx(idx <= idxToB.size());
20763 return idxToB[idx-1];
20767 thread_local Optional<BucketSetSerdeTable> tl_bucketSetTable;
20771 bool AnalysisInput::BucketSet::isSubset(const BucketSet& o) const {
20772 return
20773 this == &o ||
20774 std::includes(o.buckets.begin(), o.buckets.end(),
20775 buckets.begin(), buckets.end());
20778 bool AnalysisInput::BucketSet::contains(size_t idx) const {
20779 assertx(idx < std::numeric_limits<uint32_t>::max());
20780 return buckets.contains(idx);
20783 size_t AnalysisInput::BucketSet::hash() const {
20784 return folly::hash::hash_range(buckets.begin(), buckets.end());
20787 bool AnalysisInput::BucketSet::empty() const {
20788 return buckets.empty();
20791 void AnalysisInput::BucketSet::add(size_t idx) {
20792 assertx(idx < std::numeric_limits<uint32_t>::max());
20793 buckets.emplace_hint(buckets.end(), idx);
20796 void AnalysisInput::BucketSet::clear() {
20797 buckets.clear();
20800 AnalysisInput::BucketSet&
20801 AnalysisInput::BucketSet::operator|=(const BucketSet& o) {
20802 buckets.insert(folly::sorted_unique, begin(o.buckets), end(o.buckets));
20803 return *this;
20806 const AnalysisInput::BucketSet*
20807 AnalysisInput::BucketSet::intern(BucketSet b) {
20808 if (b.buckets.empty()) return &s_emptyBucketSet;
20809 b.buckets.shrink_to_fit();
20810 return &s_bucketSetIntern.try_emplace(std::move(b)).first->first;
20813 void AnalysisInput::BucketSet::clearIntern() {
20814 s_bucketSetIntern = decltype(s_bucketSetIntern){};
20817 std::string AnalysisInput::BucketSet::toString() const {
20818 using namespace folly::gen;
20819 if (buckets.empty()) return "-";
20820 return from(buckets)
20821 | map([] (uint32_t i) { return std::to_string(i); })
20822 | unsplit<std::string>(",");
20825 void AnalysisInput::BucketPresence::serdeStart() {
20826 assertx(!tl_bucketSetTable);
20827 tl_bucketSetTable.emplace();
20830 void AnalysisInput::BucketPresence::serdeEnd() {
20831 assertx(tl_bucketSetTable);
20832 tl_bucketSetTable.reset();
20835 void AnalysisInput::BucketPresence::serde(BlobEncoder& sd) {
20836 assertx(tl_bucketSetTable);
20837 assertx(present);
20838 assertx(withBC);
20839 assertx(process);
20840 tl_bucketSetTable->encode(sd, *present);
20841 tl_bucketSetTable->encode(sd, *withBC);
20842 tl_bucketSetTable->encode(sd, *process);
20845 void AnalysisInput::BucketPresence::serde(BlobDecoder& sd) {
20846 assertx(tl_bucketSetTable);
20847 present = tl_bucketSetTable->decode(sd);
20848 withBC = tl_bucketSetTable->decode(sd);
20849 process = tl_bucketSetTable->decode(sd);
20850 assertx(present);
20851 assertx(withBC);
20852 assertx(process);
20855 std::string show(const AnalysisInput::BucketPresence& b) {
20856 return folly::sformat(
20857 "present: {} BC: {} process: {}",
20858 b.present->toString(),
20859 b.withBC->toString(),
20860 b.process->toString()
20864 //////////////////////////////////////////////////////////////////////
20866 struct AnalysisScheduler::Bucket {
20867 HierarchicalWorkBucket b;
20870 //////////////////////////////////////////////////////////////////////
20872 AnalysisScheduler::AnalysisScheduler(Index& index)
20873 : index{index}
20874 , totalWorkItems{0}
20875 , lock{std::make_unique<std::mutex>()} {}
20877 AnalysisScheduler::~AnalysisScheduler() {
20878 // Free the BucketSet intern table when the scheduler finishes, as
20879 // it can take a non-trivial amount of memory.
20880 AnalysisInput::BucketSet::clearIntern();
20883 void AnalysisScheduler::registerClass(SString name) {
20884 // Closures are only scheduled as part of the class or func they're
20885 // declared in.
20886 if (is_closure_name(name)) return;
20887 FTRACE(5, "AnalysisScheduler: registering class {}\n", name);
20889 auto const [cState, emplaced1] = classState.try_emplace(name, name);
20890 if (!emplaced1) return;
20892 ++totalWorkItems;
20894 auto const [tState, emplaced2] = traceState.try_emplace(name);
20895 if (emplaced2) traceNames.emplace_back(name);
20896 tState->second.depStates.emplace_back(&cState->second.depState);
20898 classNames.emplace_back(name);
20899 auto const& closures =
20900 folly::get_default(index.m_data->classToClosures, name);
20901 for (auto const clo : closures) {
20902 FTRACE(
20903 5, "AnalysisScheduler: registering closure {} associated with class {}\n",
20904 clo, name
20906 always_assert(classState.try_emplace(clo, clo).second);
20910 void AnalysisScheduler::registerFunc(SString name) {
20911 FTRACE(5, "AnalysisScheduler: registering func {}\n", name);
20913 auto const [fState, emplaced1] = funcState.try_emplace(name, name);
20914 if (!emplaced1) return;
20916 ++totalWorkItems;
20918 funcNames.emplace_back(name);
20920 auto const& closures = folly::get_default(index.m_data->funcToClosures, name);
20921 for (auto const clo : closures) {
20922 FTRACE(
20923 5, "AnalysisScheduler: registering closure {} associated with func {}\n",
20924 clo, name
20926 always_assert(classState.try_emplace(clo, clo).second);
20929 // If this func is a 86cinit, then register the associated constant
20930 // as well.
20931 if (auto const cns = Constant::nameFromFuncName(name)) {
20932 FTRACE(5, "AnalysisScheduler: registering constant {}\n", cns);
20933 always_assert(cnsChanged.try_emplace(cns).second);
20934 auto const unit = index.m_data->funcToUnit.at(name);
20935 // Modifying a global constant func implies the unit will be
20936 // changed too, so the unit must be registered as well.
20937 registerUnit(unit);
20938 traceState.at(unit).depStates.emplace_back(&fState->second.depState);
20939 } else {
20940 auto const [tState, emplaced2] = traceState.try_emplace(name);
20941 if (emplaced2) traceNames.emplace_back(name);
20942 tState->second.depStates.emplace_back(&fState->second.depState);
20946 void AnalysisScheduler::registerUnit(SString name) {
20947 FTRACE(5, "AnalysisScheduler: registering unit {}\n", name);
20949 auto const [uState, emplaced1] = unitState.try_emplace(name, name);
20950 if (!emplaced1) return;
20952 ++totalWorkItems;
20954 auto const [tState, emplaced2] = traceState.try_emplace(name);
20955 if (emplaced2) traceNames.emplace_back(name);
20956 tState->second.depStates.emplace_back(&uState->second.depState);
20958 unitNames.emplace_back(name);
20961 // Called when an analysis job reports back its changes. This makes
20962 // any dependencies affected by the change eligible to run in the next
20963 // analysis round.
20964 void AnalysisScheduler::recordChanges(const AnalysisOutput& output) {
20965 const TSStringSet classes{begin(output.classNames), end(output.classNames)};
20966 const FSStringSet funcs{begin(output.funcNames), end(output.funcNames)};
20967 const SStringSet units{begin(output.unitNames), end(output.unitNames)};
20969 auto const& changed = output.meta.changed;
20971 // Sanity check that this bucket should actually be modifying the
20972 // entity.
20973 auto const valid = [&] (SString name, DepState::Kind kind) {
20974 switch (kind) {
20975 case DepState::Func:
20976 return funcs.count(name) || output.meta.removedFuncs.count(name);
20977 case DepState::Class: {
20978 if (!is_closure_name(name)) return (bool)classes.count(name);
20979 auto const ctx = folly::get_default(index.m_data->closureToClass, name);
20980 if (ctx) return (bool)classes.count(ctx);
20981 auto const f = folly::get_default(index.m_data->closureToFunc, name);
20982 always_assert(f);
20983 return (bool)funcs.count(f);
20985 case DepState::Unit:
20986 return (bool)units.count(name);
20990 for (auto const [name, type] : changed.funcs) {
20991 FTRACE(4, "AnalysisScheduler: func {} changed ({})\n", name, show(type));
20992 auto state = folly::get_ptr(funcState, name);
20993 always_assert_flog(
20994 state,
20995 "Trying to mark un-tracked func {} changed",
20996 name
20998 always_assert_flog(
20999 valid(name, DepState::Func),
21000 "Trying to mark func {} as changed from wrong shard",
21001 name
21003 assertx(AnalysisDeps::isValidForChanges(type));
21004 assertx(state->changed == Type::None);
21005 state->changed = type;
21008 for (auto const [meth, type] : changed.methods) {
21009 FTRACE(4, "AnalysisScheduler: method {} changed ({})\n",
21010 show(meth), show(type));
21011 auto state = folly::get_ptr(classState, meth.cls);
21012 always_assert_flog(
21013 state,
21014 "Trying to mark method for un-tracked class {} changed",
21015 meth.cls
21017 always_assert_flog(
21018 valid(meth.cls, DepState::Class),
21019 "Trying to mark method for class {} as changed from wrong shard",
21020 meth.cls
21022 assertx(AnalysisDeps::isValidForChanges(type));
21023 auto& t = state->methodChanges.ensure(meth.idx);
21024 assertx(t == Type::None);
21025 t = type;
21028 for (auto const cns : changed.clsConstants) {
21029 auto state = folly::get_ptr(classState, cns.cls);
21030 always_assert_flog(
21031 state,
21032 "Trying to mark constant for un-tracked class {} changed",
21033 cns.cls
21035 always_assert_flog(
21036 valid(cns.cls, DepState::Class),
21037 "Trying to mark constant for class {} as changed from wrong shard",
21038 cns.cls
21041 if (state->allCnsFixed) continue;
21042 if (cns.idx < state->cnsFixed.size() && state->cnsFixed[cns.idx]) continue;
21043 FTRACE(4, "AnalysisScheduler: class constant {} changed\n", show(cns));
21044 if (cns.idx >= state->cnsChanges.size()) {
21045 state->cnsChanges.resize(cns.idx+1);
21047 assertx(!state->cnsChanges[cns.idx]);
21048 state->cnsChanges[cns.idx] = true;
21051 for (auto const cns : changed.fixedClsConstants) {
21052 auto state = folly::get_ptr(classState, cns.cls);
21053 always_assert_flog(
21054 state,
21055 "Trying to mark constant for un-tracked class {} as fixed",
21056 cns.cls
21058 always_assert_flog(
21059 valid(cns.cls, DepState::Class),
21060 "Trying to mark constant for class {} as fixed from wrong shard",
21061 cns.cls
21064 if (state->allCnsFixed) continue;
21065 if (cns.idx >= state->cnsFixed.size()) {
21066 state->cnsFixed.resize(cns.idx+1);
21068 if (!state->cnsFixed[cns.idx]) {
21069 FTRACE(4, "AnalysisScheduler: class constant {} now fixed\n", show(cns));
21070 state->cnsFixed[cns.idx] = true;
21074 for (auto const cls : changed.allClsConstantsFixed) {
21075 auto state = folly::get_ptr(classState, cls);
21076 always_assert_flog(
21077 state,
21078 "Trying to mark all constants for un-tracked class {} as fixed",
21081 always_assert_flog(
21082 valid(cls, DepState::Class),
21083 "Trying to mark all constants for class {} as fixed from wrong shard",
21086 if (!state->allCnsFixed) {
21087 FTRACE(
21088 4, "AnalysisScheduler: all class constants for {} now fixed\n",
21091 state->allCnsFixed = true;
21092 state->cnsFixed.clear();
21096 for (auto const name : changed.constants) {
21097 FTRACE(4, "AnalysisScheduler: constant {} changed\n", name);
21098 auto state = folly::get_ptr(cnsChanged, name);
21099 always_assert_flog(
21100 state,
21101 "Trying to mark un-tracked constant {} changed",
21102 name
21104 auto const initName = Constant::funcNameFromName(name);
21105 always_assert_flog(
21106 valid(initName, DepState::Func),
21107 "Trying to mark constant {} as changed from wrong shard",
21108 name
21110 assertx(!state->load(std::memory_order_acquire));
21111 state->store(true, std::memory_order_release);
21114 for (auto const unit : changed.unitsFixed) {
21115 auto state = folly::get_ptr(unitState, unit);
21116 always_assert_flog(
21117 state,
21118 "Trying to mark all type-aliases for un-tracked unit {} as fixed",
21119 unit
21121 always_assert_flog(
21122 valid(unit, DepState::Unit),
21123 "Trying to mark all type-aliases for unit {} as fixed from wrong shard",
21124 unit
21126 if (!state->fixed) {
21127 FTRACE(
21128 4, "AnalysisScheduler: all type-aliases for unit {} now fixed\n",
21129 unit
21131 state->fixed = true;
21135 for (auto& [cls, names] : changed.clsTypeCnsNames) {
21136 auto state = folly::get_ptr(classState, cls);
21137 always_assert_flog(
21138 state,
21139 "Trying to record type constant names "
21140 "for un-tracked class {}",
21143 always_assert_flog(
21144 valid(cls, DepState::Class),
21145 "Trying to record type constant names "
21146 "for class {} from wrong shard",
21149 state->typeCnsNames = std::move(names);
21152 for (auto& [unit, names] : changed.unitTypeCnsNames) {
21153 auto state = folly::get_ptr(unitState, unit);
21154 always_assert_flog(
21155 state,
21156 "Trying to record type constant names "
21157 "for un-tracked unit {}",
21158 unit
21160 always_assert_flog(
21161 valid(unit, DepState::Unit),
21162 "Trying to record type constant names "
21163 "for unit {} from wrong shard",
21164 unit
21166 state->typeCnsNames = std::move(names);
21170 // Update the dependencies stored in the scheduler to take into
21171 // account the new set of dependencies reported by an analysis job.
21172 void AnalysisScheduler::updateDepState(AnalysisOutput& output) {
21173 for (size_t i = 0, size = output.classNames.size(); i < size; ++i) {
21174 auto const name = output.classNames[i];
21175 auto it = classState.find(name);
21176 always_assert_flog(
21177 it != end(classState),
21178 "Trying to set deps for un-tracked class {}",
21179 name
21181 auto& state = it->second.depState;
21182 if (is_closure_name(name)) {
21183 assertx(output.meta.classDeps[i].empty());
21184 assertx(state.deps.empty());
21185 continue;
21187 state.deps = std::move(output.meta.classDeps[i]);
21189 for (size_t i = 0, size = output.funcNames.size(); i < size; ++i) {
21190 auto const name = output.funcNames[i];
21191 auto it = funcState.find(name);
21192 always_assert_flog(
21193 it != end(funcState),
21194 "Trying to set deps for un-tracked func {}",
21195 name
21197 auto& state = it->second.depState;
21198 state.deps = std::move(output.meta.funcDeps[i]);
21200 for (size_t i = 0, size = output.unitNames.size(); i < size; ++i) {
21201 auto const name = output.unitNames[i];
21202 auto it = unitState.find(name);
21203 if (it == end(unitState)) {
21204 always_assert_flog(
21205 output.meta.unitDeps[i].empty(),
21206 "Trying to set non-empty deps for un-tracked unit {}",
21207 name
21209 continue;
21211 auto& state = it->second.depState;
21212 state.deps = std::move(output.meta.unitDeps[i]);
21215 // Remove deps for any removed functions, to avoid them spuriously
21216 // being rescheduled again.
21217 for (auto const name : output.meta.removedFuncs) {
21218 auto it = funcState.find(name);
21219 always_assert_flog(
21220 it != end(funcState),
21221 "Trying to reset deps for un-tracked func {}",
21222 name
21224 auto& state = it->second.depState;
21225 state.deps = AnalysisDeps{};
21228 for (auto& [cls, bases] : output.meta.cnsBases) {
21229 auto const state = folly::get_ptr(classState, cls);
21230 always_assert_flog(
21231 state,
21232 "Trying to update cns bases for untracked class {}",
21235 auto old = folly::get_ptr(index.m_data->classToCnsBases, cls);
21236 if (!old) {
21237 assertx(bases.empty());
21238 continue;
21240 if (debug) {
21241 // Class constant base classes should only shrink.
21242 for (auto const b : bases) always_assert(old->contains(b));
21244 *old = std::move(bases);
21248 // Record the output of an analysis job. This means updating the
21249 // various Refs to their new versions, recording new dependencies, and
21250 // recording what has changed (to schedule the next round).
21251 void AnalysisScheduler::record(AnalysisOutput output) {
21252 auto const numClasses = output.classNames.size();
21253 auto const numCInfos = output.cinfoNames.size();
21254 auto const numMInfos = output.minfoNames.size();
21255 auto const numFuncs = output.funcNames.size();
21256 auto const numUnits = output.unitNames.size();
21257 assertx(numClasses == output.classes.size());
21258 assertx(numClasses == output.clsBC.size());
21259 assertx(numCInfos == output.cinfos.size());
21260 assertx(numMInfos == output.minfos.size());
21261 assertx(numClasses == output.meta.classDeps.size());
21262 assertx(numFuncs == output.funcs.size());
21263 assertx(numFuncs == output.funcBC.size());
21264 assertx(numFuncs == output.finfos.size());
21265 assertx(numFuncs == output.meta.funcDeps.size());
21266 assertx(numUnits == output.units.size());
21267 assertx(numUnits == output.meta.unitDeps.size());
21269 // Update Ref mappings:
21271 for (size_t i = 0; i < numUnits; ++i) {
21272 auto const name = output.unitNames[i];
21273 index.m_data->unitRefs.at(name) = std::move(output.units[i]);
21276 for (size_t i = 0; i < numClasses; ++i) {
21277 auto const name = output.classNames[i];
21278 index.m_data->classRefs.at(name) = std::move(output.classes[i]);
21279 index.m_data->classBytecodeRefs.at(name) = std::move(output.clsBC[i]);
21281 for (size_t i = 0; i < numCInfos; ++i) {
21282 auto const name = output.cinfoNames[i];
21283 index.m_data->classInfoRefs.at(name) =
21284 output.cinfos[i].cast<std::unique_ptr<ClassInfo2>>();
21286 for (size_t i = 0; i < numMInfos; ++i) {
21287 auto const name = output.minfoNames[i];
21288 index.m_data->uninstantiableClsMethRefs.at(name) =
21289 output.minfos[i].cast<std::unique_ptr<MethodsWithoutCInfo>>();
21291 for (size_t i = 0; i < numFuncs; ++i) {
21292 auto const name = output.funcNames[i];
21293 index.m_data->funcRefs.at(name) = std::move(output.funcs[i]);
21294 index.m_data->funcBytecodeRefs.at(name) = std::move(output.funcBC[i]);
21295 index.m_data->funcInfoRefs.at(name) =
21296 output.finfos[i].cast<std::unique_ptr<FuncInfo2>>();
21299 recordChanges(output);
21300 updateDepState(output);
21302 // If the analysis job optimized away any 86cinit functions, record
21303 // that here so they can be later removed from our tables.
21304 if (!output.meta.removedFuncs.empty()) {
21305 // This is relatively rare, so a lock is fine.
21306 std::lock_guard<std::mutex> _{*lock};
21307 funcsToRemove.insert(
21308 begin(output.meta.removedFuncs),
21309 end(output.meta.removedFuncs)
21314 // Remove metadata for any 86cinit function that an analysis job
21315 // optimized away. This must be done *after* calculating the next
21316 // round of work.
21317 void AnalysisScheduler::removeFuncs() {
21318 if (funcsToRemove.empty()) return;
21320 TSStringSet traceNamesToRemove;
21321 for (auto const name : funcsToRemove) {
21322 FTRACE(4, "AnalysisScheduler: removing function {}\n", name);
21324 auto fstate = folly::get_ptr(funcState, name);
21325 always_assert(fstate);
21327 auto tstate = [&] {
21328 if (!Constant::nameFromFuncName(name)) {
21329 return folly::get_ptr(traceState, name);
21331 auto const unit = index.m_data->funcToUnit.at(name);
21332 return folly::get_ptr(traceState, unit);
21333 }();
21334 always_assert(tstate);
21336 tstate->depStates.eraseTail(
21337 std::remove_if(
21338 tstate->depStates.begin(), tstate->depStates.end(),
21339 [&] (const DepState* d) { return d == &fstate->depState; }
21342 if (tstate->depStates.empty()) {
21343 traceState.erase(name);
21344 traceNamesToRemove.emplace(name);
21347 always_assert(index.m_data->funcRefs.erase(name));
21348 always_assert(index.m_data->funcBytecodeRefs.erase(name));
21349 always_assert(index.m_data->funcInfoRefs.erase(name));
21350 always_assert(index.m_data->funcToUnit.erase(name));
21351 always_assert(funcState.erase(name));
21352 index.m_data->funcToClosures.erase(name);
21353 if (auto const cns = Constant::nameFromFuncName(name)) {
21354 always_assert(index.m_data->constantInitFuncs.erase(name));
21355 always_assert(cnsChanged.erase(cns));
21356 index.m_data->constantToUnit.at(cns).second = false;
21360 funcNames.erase(
21361 std::remove_if(
21362 begin(funcNames), end(funcNames),
21363 [&] (SString name) { return funcsToRemove.count(name); }
21365 end(funcNames)
21368 if (!traceNamesToRemove.empty()) {
21369 traceNames.erase(
21370 std::remove_if(
21371 begin(traceNames), end(traceNames),
21372 [&] (SString name) { return traceNamesToRemove.count(name); }
21374 end(traceNames)
21378 funcsToRemove.clear();
21381 const AnalysisScheduler::TraceState*
21382 AnalysisScheduler::lookupTrace(DepState::Kind k, SString n) const {
21383 auto const s = folly::get_ptr(traceState, n);
21384 if (!s) return nullptr;
21385 for (auto const d : s->depStates) {
21386 if (d->kind != k) continue;
21387 if (!d->name->tsame(n)) continue;
21388 return s;
21390 return nullptr;
21393 // Retrieve the TraceState appropriate for the class with the given
21394 // name.
21395 const AnalysisScheduler::TraceState*
21396 AnalysisScheduler::traceForClass(SString cls) const {
21397 if (is_closure_name(cls)) {
21398 if (auto const n = folly::get_default(index.m_data->closureToClass, cls)) {
21399 return traceForClass(n);
21401 if (auto const n = folly::get_default(index.m_data->closureToFunc, cls)) {
21402 return traceForFunc(n);
21405 return lookupTrace(DepState::Class, cls);
21408 // Retrieve the TraceState appropriate for the func with the given
21409 // name.
21410 const AnalysisScheduler::TraceState*
21411 AnalysisScheduler::traceForFunc(SString func) const {
21412 if (auto const cns = Constant::nameFromFuncName(func)) {
21413 return traceForConstant(cns);
21415 return lookupTrace(DepState::Func, func);
21418 // Retrieve the TraceState appropriate for the unit with the given
21419 // path.
21420 const AnalysisScheduler::TraceState*
21421 AnalysisScheduler::traceForUnit(SString unit) const {
21422 return lookupTrace(DepState::Unit, unit);
21425 // Retrive the TraceState appropriate for the constant with the given
21426 // name.
21427 const AnalysisScheduler::TraceState*
21428 AnalysisScheduler::traceForConstant(SString cns) const {
21429 auto const unit = folly::get_ptr(index.m_data->constantToUnit, cns);
21430 if (!unit) return nullptr;
21431 return traceForUnit(unit->first);
21434 // Retrieve the TraceState appropriate for the type-alias with the
21435 // given name.
21436 const AnalysisScheduler::TraceState*
21437 AnalysisScheduler::traceForTypeAlias(SString typeAlias) const {
21438 auto const unit =
21439 folly::get_default(index.m_data->typeAliasToUnit, typeAlias);
21440 if (!unit) return nullptr;
21441 return traceForUnit(unit);
21444 // Retrieve the TraceState appropriate for the class or type-alias
21445 // with the given name.
21446 const AnalysisScheduler::TraceState*
21447 AnalysisScheduler::traceForClassOrTypeAlias(SString name) const {
21448 if (auto const t = traceForClass(name)) return t;
21449 if (auto const t = traceForTypeAlias(name)) return t;
21450 return nullptr;
21453 // Maps a DepState to it's associated TraceState.
21454 const AnalysisScheduler::TraceState*
21455 AnalysisScheduler::traceForDepState(const DepState& d) const {
21456 switch (d.kind) {
21457 case DepState::Func: return traceForFunc(d.name);
21458 case DepState::Class: return traceForClass(d.name);
21459 case DepState::Unit: return traceForUnit(d.name);
21461 always_assert(false);
21464 Either<const AnalysisScheduler::ClassState*,
21465 const AnalysisScheduler::UnitState*>
21466 AnalysisScheduler::stateForClassOrTypeAlias(SString n) const {
21467 if (auto const s = folly::get_ptr(classState, n)) return s;
21468 if (auto const unit = folly::get_default(index.m_data->typeAliasToUnit, n)) {
21469 return folly::get_ptr(unitState, unit);
21471 return nullptr;
21474 AnalysisScheduler::Presence
21475 AnalysisScheduler::presenceOf(const AnalysisInput::BucketPresence& b1,
21476 const AnalysisInput::BucketPresence& b2) const {
21477 if (&b1 == &b2) return Presence::Full;
21478 assertx(b1.process);
21479 assertx(b2.process);
21480 assertx(b2.withBC);
21481 assertx(b2.present);
21482 if (b1.process->isSubset(*b2.process)) return Presence::Full;
21483 if (b1.process->isSubset(*b2.withBC)) return Presence::DepWithBytecode;
21484 if (b1.process->isSubset(*b2.present)) return Presence::Dep;
21485 return Presence::None;
21488 AnalysisScheduler::Presence
21489 AnalysisScheduler::presenceOfClass(const TraceState& trace,
21490 SString cls) const {
21491 if (is_closure_name(cls)) {
21492 if (auto const n = folly::get_default(index.m_data->closureToClass, cls)) {
21493 return presenceOfClass(trace, n);
21495 if (auto const n = folly::get_default(index.m_data->closureToFunc, cls)) {
21496 return presenceOfFunc(trace, n);
21500 if (auto const t = traceForClass(cls)) {
21501 return presenceOf(trace.buckets, t->buckets);
21503 if (auto const b = folly::get_ptr(untracked.classes, cls)) {
21504 return presenceOf(trace.buckets, *b);
21507 assertx(trace.buckets.process);
21508 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21511 AnalysisScheduler::Presence
21512 AnalysisScheduler::presenceOfClassOrTypeAlias(const TraceState& trace,
21513 SString cls) const {
21514 if (is_closure_name(cls)) {
21515 if (auto const n = folly::get_default(index.m_data->closureToClass, cls)) {
21516 return presenceOfClass(trace, n);
21518 if (auto const n = folly::get_default(index.m_data->closureToFunc, cls)) {
21519 return presenceOfFunc(trace, n);
21523 if (auto const t = traceForClassOrTypeAlias(cls)) {
21524 return presenceOf(trace.buckets, t->buckets);
21526 if (auto const u = folly::get_default(index.m_data->typeAliasToUnit, cls)) {
21527 if (auto const b = folly::get_ptr(untracked.units, u)) {
21528 return presenceOf(trace.buckets, *b);
21530 } else if (auto const b = folly::get_ptr(untracked.classes, cls)) {
21531 return presenceOf(trace.buckets, *b);
21534 assertx(trace.buckets.process);
21535 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21538 AnalysisScheduler::Presence
21539 AnalysisScheduler::presenceOfFunc(const TraceState& trace,
21540 SString func) const {
21541 if (auto const t = traceForFunc(func)) {
21542 return presenceOf(trace.buckets, t->buckets);
21544 if (auto const b = folly::get_ptr(untracked.funcs, func)) {
21545 return presenceOf(trace.buckets, *b);
21547 assertx(trace.buckets.process);
21548 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21551 AnalysisScheduler::Presence
21552 AnalysisScheduler::presenceOfConstant(const TraceState& trace,
21553 SString cns) const {
21554 if (auto const trace2 = traceForConstant(cns)) {
21555 return presenceOf(trace.buckets, trace2->buckets);
21557 if (auto const b = folly::get_ptr(untracked.badConstants, cns)) {
21558 return presenceOf(trace.buckets, *b);
21560 assertx(trace.buckets.process);
21561 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21564 // Calculate any classes or functions which should be scheduled to be
21565 // analyzed in the next round.
21566 void AnalysisScheduler::findToSchedule() {
21567 // Check if the given entity (class or function) needs to run again
21568 // due to one of its dependencies changing (or if it previously
21569 // registered a new dependency).
21570 auto const check = [&] (SString name, DepState& d) {
21571 // The algorithm for these are all similar: Compare the old
21572 // dependencies with the new dependencies. If the dependency is
21573 // new, or if it's not the same as the old, check the
21574 // ClassGroup. If they're in the same ClassGroup, ignore it (this
21575 // entity already incorporated the change inside the analysis
21576 // job). Otherwise schedule this class or func to run.
21578 if (d.kind == DepState::Class && is_closure_name(name)) {
21579 assertx(d.deps.empty());
21580 return false;
21583 auto const trace = traceForDepState(d);
21584 always_assert(trace);
21586 for (auto const cls : d.deps.classes) {
21587 if (presenceOfClassOrTypeAlias(*trace, cls) == Presence::None) {
21588 FTRACE(
21589 4, "AnalysisScheduler: {} new class/type-alias dependency on {},"
21590 " scheduling\n",
21591 name, cls
21593 return true;
21597 for (auto const cls : d.deps.typeCnsClasses) {
21598 if (presenceOfClassOrTypeAlias(*trace, cls) == Presence::None) {
21599 FTRACE(
21600 4, "AnalysisScheduler: {} new class/type-alias dependency "
21601 "(in type-cns) on {}, scheduling\n",
21602 name, cls
21604 return true;
21608 for (auto const cls : d.deps.anyClsConstants) {
21609 auto const schedule = [&] {
21610 switch (presenceOfClassOrTypeAlias(*trace, cls)) {
21611 case Presence::None:
21612 return true;
21613 case Presence::Dep:
21614 case Presence::DepWithBytecode: {
21615 auto const state = folly::get_ptr(classState, cls);
21616 return state && state->cnsChanges.any();
21618 case Presence::Full:
21619 return false;
21621 }();
21623 if (schedule) {
21624 FTRACE(
21625 4, "AnalysisScheduler: {} new/changed dependency on "
21626 "any class constant for {}, scheduling\n",
21627 name, cls
21629 return true;
21633 for (auto const cls : d.deps.typeCnsAnyClsConstants) {
21634 if (presenceOfClassOrTypeAlias(*trace, cls) == Presence::None) {
21635 FTRACE(
21636 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21637 "any class constant for {}, scheduling\n",
21638 name, cls
21640 return true;
21644 for (auto const cns : d.deps.typeCnsClsConstants) {
21645 if (presenceOfClassOrTypeAlias(*trace, cns.cls) == Presence::None) {
21646 FTRACE(
21647 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21648 "class constant {}, scheduling\n",
21649 name, show(cns)
21651 return true;
21655 for (auto const [meth, newT] : d.deps.methods) {
21656 if (newT == Type::None) continue;
21658 auto const schedule = [&, meth=meth, newT=newT] {
21659 switch (presenceOfClass(*trace, meth.cls)) {
21660 case Presence::None:
21661 return true;
21662 case Presence::Dep:
21663 if (newT & Type::Bytecode) return true;
21664 [[fallthrough]];
21665 case Presence::DepWithBytecode: {
21666 auto const state = folly::get_ptr(classState, meth.cls);
21667 if (!state) return false;
21668 auto const changed =
21669 state->methodChanges.get_default(meth.idx, Type::None);
21670 return (bool)(changed & newT);
21672 case Presence::Full:
21673 return false;
21675 }();
21677 if (schedule) {
21678 FTRACE(
21679 4, "AnalysisScheduler: {} new/changed dependency on method {},"
21680 " scheduling\n",
21681 name, show(meth)
21683 return true;
21687 for (auto const [func, newT] : d.deps.funcs) {
21688 if (newT == Type::None) continue;
21690 auto const schedule = [&, func=func, newT=newT] {
21691 switch (presenceOfFunc(*trace, func)) {
21692 case Presence::None:
21693 return true;
21694 case Presence::Dep:
21695 if (newT & Type::Bytecode) return true;
21696 [[fallthrough]];
21697 case Presence::DepWithBytecode: {
21698 auto const state = folly::get_ptr(funcState, func);
21699 if (!state) return false;
21700 return (bool)(state->changed & newT);
21702 case Presence::Full:
21703 return false;
21705 }();
21707 if (schedule) {
21708 FTRACE(
21709 4, "AnalysisScheduler: {} new/changed dependency on func {}, "
21710 "scheduling\n",
21711 name, func
21713 return true;
21717 for (auto const cns : d.deps.clsConstants) {
21718 auto const schedule = [&] {
21719 switch (presenceOfClass(*trace, cns.cls)) {
21720 case Presence::None:
21721 return true;
21722 case Presence::Dep:
21723 case Presence::DepWithBytecode: {
21724 auto const state = folly::get_ptr(classState, cns.cls);
21725 if (!state) return false;
21726 return
21727 cns.idx < state->cnsChanges.size() && state->cnsChanges[cns.idx];
21729 case Presence::Full:
21730 return false;
21732 }();
21734 if (schedule) {
21735 FTRACE(
21736 4, "AnalysisScheduler: {} new/changed dependency on "
21737 "class constant {}, scheduling\n",
21738 name, show(cns)
21740 return true;
21744 for (auto const cns : d.deps.constants) {
21745 auto const schedule = [&] {
21746 switch (presenceOfConstant(*trace, cns)) {
21747 case Presence::None:
21748 return true;
21749 case Presence::Dep:
21750 case Presence::DepWithBytecode: {
21751 auto const changed = folly::get_ptr(cnsChanged, cns);
21752 return changed && changed->load(std::memory_order_acquire);
21754 case Presence::Full:
21755 return false;
21757 }();
21759 if (schedule) {
21760 FTRACE(
21761 4, "AnalysisScheduler: {} new/changed dependency on "
21762 "constant {}, scheduling\n",
21763 name, cns
21765 return true;
21769 return false;
21772 parallel::for_each(
21773 classNames,
21774 [&] (SString name) {
21775 assertx(!is_closure_name(name));
21776 auto& state = classState.at(name).depState;
21777 state.toSchedule = check(name, state);
21778 if (state.toSchedule) ++totalWorkItems;
21781 parallel::for_each(
21782 funcNames,
21783 [&] (SString name) {
21784 auto& state = funcState.at(name).depState;
21785 state.toSchedule = check(name, state);
21786 if (state.toSchedule) ++totalWorkItems;
21789 parallel::for_each(
21790 unitNames,
21791 [&] (SString name) {
21792 auto& state = unitState.at(name).depState;
21793 state.toSchedule = check(name, state);
21794 if (state.toSchedule) ++totalWorkItems;
21799 // Reset any recorded changes from analysis jobs, in preparation for
21800 // another round.
21801 void AnalysisScheduler::resetChanges() {
21802 auto const resetClass = [&] (SString name) {
21803 auto& state = classState.at(name);
21804 std::fill(
21805 begin(state.methodChanges),
21806 end(state.methodChanges),
21807 Type::None
21809 state.cnsChanges.reset();
21812 parallel::for_each(
21813 classNames,
21814 [&] (SString name) {
21815 resetClass(name);
21816 auto const& closures =
21817 folly::get_default(index.m_data->classToClosures, name);
21818 for (auto const c : closures) resetClass(c);
21821 parallel::for_each(
21822 funcNames,
21823 [&] (SString name) {
21824 funcState.at(name).changed = Type::None;
21825 auto const& closures =
21826 folly::get_default(index.m_data->funcToClosures, name);
21827 for (auto const c : closures) resetClass(c);
21828 if (auto const cns = Constant::nameFromFuncName(name)) {
21829 cnsChanged.at(cns).store(false, std::memory_order_release);
21835 // Called when all analysis jobs are finished. "Finalize" the changes
21836 // and determine what should run in the next analysis round.
21837 void AnalysisScheduler::recordingDone() {
21838 findToSchedule();
21839 removeFuncs();
21840 resetChanges();
21843 void AnalysisScheduler::addClassToInput(SString name,
21844 AnalysisInput& input) const {
21845 FTRACE(4, "AnalysisScheduler: adding class {} to input\n", name);
21847 input.classes.emplace(name, index.m_data->classRefs.at(name));
21848 input.classBC.emplace(name, index.m_data->classBytecodeRefs.at(name));
21849 if (auto const ref = folly::get_ptr(index.m_data->classInfoRefs, name)) {
21850 input.cinfos.emplace(name, ref->cast<AnalysisIndexCInfo>());
21851 } else if (auto const ref =
21852 folly::get_ptr(index.m_data->uninstantiableClsMethRefs, name)) {
21853 input.minfos.emplace(name, ref->cast<AnalysisIndexMInfo>());
21854 } else {
21855 input.meta.badClasses.emplace(name);
21857 if (!input.m_key) input.m_key = name;
21860 void AnalysisScheduler::addFuncToInput(SString name,
21861 AnalysisInput& input) const {
21862 FTRACE(4, "AnalysisScheduler: adding func {} to input\n", name);
21864 input.funcs.emplace(name, index.m_data->funcRefs.at(name));
21865 input.funcBC.emplace(name, index.m_data->funcBytecodeRefs.at(name));
21866 input.finfos.emplace(
21867 name,
21868 index.m_data->funcInfoRefs.at(name).cast<AnalysisIndexFInfo>()
21870 if (!input.m_key) input.m_key = name;
21872 auto const& closures = folly::get_default(index.m_data->funcToClosures, name);
21873 for (auto const clo : closures) addClassToInput(clo, input);
21876 void AnalysisScheduler::addUnitToInput(SString name,
21877 AnalysisInput& input) const {
21878 FTRACE(4, "AnalysisScheduler: adding unit {} to input\n", name);
21879 input.units.emplace(name, index.m_data->unitRefs.at(name));
21880 if (!input.m_key) input.m_key = name;
21883 void AnalysisScheduler::addDepClassToInput(SString cls,
21884 SString depSrc,
21885 bool addBytecode,
21886 AnalysisInput& input,
21887 bool fromClosureCtx) const {
21888 if (auto const unit =
21889 folly::get_default(index.m_data->typeAliasToUnit, cls)) {
21890 addDepUnitToInput(unit, depSrc, input);
21891 return;
21894 if (input.classes.count(cls)) return;
21896 auto const badClass = [&] {
21897 if (input.meta.badClasses.emplace(cls).second) {
21898 FTRACE(4, "AnalysisScheduler: adding bad class {} to {} dep inputs\n",
21899 cls, depSrc);
21903 auto const clsRef = folly::get_ptr(index.m_data->classRefs, cls);
21904 if (!clsRef) {
21905 if (!is_closure_name(cls)) return badClass();
21906 assertx(!index.m_data->closureToFunc.count(cls));
21907 auto const ctx = folly::get_default(index.m_data->closureToClass, cls);
21908 if (!ctx) return badClass();
21909 if (fromClosureCtx) return;
21910 return addDepClassToInput(ctx, depSrc, addBytecode, input);
21912 assertx(!index.m_data->closureToClass.count(cls));
21914 if (input.depClasses.emplace(cls, *clsRef).second) {
21915 FTRACE(
21916 4, "AnalysisScheduler: adding class {} to {} dep inputs\n",
21917 cls, depSrc
21919 } else if (!addBytecode || input.classBC.count(cls)) {
21920 return;
21923 if (auto const r = folly::get_ptr(index.m_data->classInfoRefs, cls)) {
21924 input.cinfos.emplace(cls, r->cast<AnalysisIndexCInfo>());
21925 } else if (auto const r =
21926 folly::get_ptr(index.m_data->uninstantiableClsMethRefs, cls)) {
21927 input.minfos.emplace(cls, r->cast<AnalysisIndexMInfo>());
21928 } else {
21929 badClass();
21932 if (addBytecode) {
21933 if (input.classBC.emplace(cls,
21934 index.m_data->classBytecodeRefs.at(cls)).second) {
21935 FTRACE(
21936 4, "AnalysisScheduler: adding class {} bytecode to {} dep inputs\n",
21937 cls, depSrc
21942 addDepUnitToInput(index.m_data->classToUnit.at(cls), depSrc, input);
21944 if (is_closure_name(cls)) {
21945 auto const f = folly::get_default(index.m_data->closureToFunc, cls);
21946 always_assert(f);
21947 if (fromClosureCtx) return;
21948 return addDepFuncToInput(
21950 depSrc,
21951 addBytecode ? Type::Bytecode : Type::Meta,
21952 input
21957 void AnalysisScheduler::addDepFuncToInput(SString func,
21958 SString depSrc,
21959 Type type,
21960 AnalysisInput& input) const {
21961 assertx(type != Type::None);
21962 if (input.funcs.count(func)) return;
21964 auto const funcRef = folly::get_ptr(index.m_data->funcRefs, func);
21965 if (!funcRef) {
21966 if (input.meta.badFuncs.emplace(func).second) {
21967 FTRACE(4, "AnalysisScheduler: adding bad func {} to {} dep inputs\n",
21968 func, depSrc);
21970 return;
21973 if (input.depFuncs.emplace(func, *funcRef).second) {
21974 FTRACE(
21975 4, "AnalysisScheduler: adding func {} to {} dep inputs\n",
21976 func, depSrc
21978 } else if (!(type & Type::Bytecode) || input.funcBC.count(func)) {
21979 return;
21982 input.finfos.emplace(
21983 func,
21984 index.m_data->funcInfoRefs.at(func).cast<AnalysisIndexFInfo>()
21987 if (type & Type::Bytecode) {
21988 if (input.funcBC.emplace(func,
21989 index.m_data->funcBytecodeRefs.at(func)).second) {
21990 FTRACE(
21991 4, "AnalysisScheduler: adding func {} bytecode to {} dep inputs\n",
21992 func, depSrc
21997 addDepUnitToInput(index.m_data->funcToUnit.at(func), depSrc, input);
21999 auto const& closures = folly::get_default(index.m_data->funcToClosures, func);
22000 for (auto const clo : closures) {
22001 addDepClassToInput(clo, depSrc, type & Type::Bytecode, input, true);
22005 void AnalysisScheduler::addDepConstantToInput(SString cns,
22006 SString depSrc,
22007 AnalysisInput& input) const {
22008 auto const unit = folly::get_ptr(index.m_data->constantToUnit, cns);
22009 if (!unit) {
22010 if (input.meta.badConstants.emplace(cns).second) {
22011 FTRACE(4, "AnalysisScheduler: adding bad constant {} to {} dep inputs\n",
22012 cns, depSrc);
22014 return;
22017 addDepUnitToInput(unit->first, depSrc, input);
22018 if (!unit->second || is_native_unit(unit->first)) return;
22020 auto const initName = Constant::funcNameFromName(cns);
22021 always_assert_flog(
22022 index.m_data->funcRefs.count(initName),
22023 "Constant {} is missing expected initialization function {}",
22024 cns, initName
22027 addDepFuncToInput(initName, depSrc, Type::Meta, input);
22030 void AnalysisScheduler::addDepUnitToInput(SString unit,
22031 SString depSrc,
22032 AnalysisInput& input) const {
22033 if (input.units.count(unit)) return;
22034 if (!input.depUnits.emplace(unit, index.m_data->unitRefs.at(unit)).second) {
22035 return;
22037 FTRACE(4, "AnalysisScheduler: adding unit {} to {} dep inputs\n",
22038 unit, depSrc);
22041 void AnalysisScheduler::addTraceDepToInput(const DepState& d,
22042 AnalysisInput& input) const {
22043 switch (d.kind) {
22044 case DepState::Func:
22045 addDepFuncToInput(d.name, d.name, Type::Bytecode, input);
22046 break;
22047 case DepState::Class:
22048 addDepClassToInput(d.name, d.name, true, input);
22049 break;
22050 case DepState::Unit:
22051 addDepUnitToInput(d.name, d.name, input);
22052 break;
22054 addDepsToInput(d, input);
22057 void AnalysisScheduler::addDepsToInput(const DepState& deps,
22058 AnalysisInput& input) const {
22059 auto const& i = *index.m_data;
22061 switch (deps.kind) {
22062 case DepState::Func:
22063 addDepUnitToInput(
22064 i.funcToUnit.at(deps.name),
22065 deps.name,
22066 input
22068 break;
22069 case DepState::Class:
22070 addDepUnitToInput(
22071 i.classToUnit.at(deps.name),
22072 deps.name,
22073 input
22075 if (auto const bases = folly::get_ptr(i.classToCnsBases, deps.name)) {
22076 for (auto const b : *bases) {
22077 addDepClassToInput(b, deps.name, false, input);
22080 break;
22081 case DepState::Unit:
22082 break;
22085 stateForClassOrTypeAlias(deps.name).match(
22086 [&] (const ClassState* s) {
22087 if (!s) return;
22088 for (auto const t : s->typeCnsNames) {
22089 addDepClassToInput(t, deps.name, false, input);
22092 [&] (const UnitState* s) {
22093 if (!s) return;
22094 for (auto const t : s->typeCnsNames) {
22095 addDepClassToInput(t, deps.name, false, input);
22100 if (deps.deps.empty()) return;
22101 assertx(IMPLIES(deps.kind == DepState::Class, !is_closure_name(deps.name)));
22102 auto const& d = deps.deps;
22104 for (auto const cls : d.classes) {
22105 addDepClassToInput(cls, deps.name, false, input);
22107 for (auto const cls : d.typeCnsClasses) {
22108 addDepClassToInput(cls, deps.name, false, input);
22110 for (auto const [meth, type] : d.methods) {
22111 if (type != Type::None) {
22112 addDepClassToInput(
22113 meth.cls,
22114 deps.name,
22115 type & Type::Bytecode,
22116 input
22120 for (auto const [func, type] : d.funcs) {
22121 if (type != Type::None) addDepFuncToInput(func, deps.name, type, input);
22123 for (auto const cns : d.clsConstants) {
22124 addDepClassToInput(cns.cls, deps.name, false, input);
22126 for (auto const cns : d.typeCnsClsConstants) {
22127 addDepClassToInput(cns.cls, deps.name, false, input);
22129 for (auto const cls : d.anyClsConstants) {
22130 addDepClassToInput(cls, deps.name, false, input);
22131 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22132 for (auto const b : *bases) addDepClassToInput(b, deps.name, false, input);
22135 for (auto const cls : d.typeCnsAnyClsConstants) {
22136 addDepClassToInput(cls, deps.name, false, input);
22137 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22138 for (auto const b : *bases) addDepClassToInput(b, deps.name, false, input);
22141 for (auto const cns : d.constants) {
22142 addDepConstantToInput(cns, deps.name, input);
22146 // For every input in the AnalysisInput, add any associated
22147 // dependencies for those inputs.
22148 void AnalysisScheduler::addAllDepsToInput(AnalysisInput& input) const {
22149 for (auto const& [name, _] : input.classes) {
22150 addDepsToInput(classState.at(name).depState, input);
22152 for (auto const& [name, _] : input.funcs) {
22153 addDepsToInput(funcState.at(name).depState, input);
22155 for (auto const& [name, _] : input.units) {
22156 addDepsToInput(unitState.at(name).depState, input);
22159 // These are automatically included everywhere:
22160 for (auto const f : special_builtins()) {
22161 addDepFuncToInput(f, f, Type::Meta, input);
22163 // Wait handle information must always be available.
22164 addDepClassToInput(s_Awaitable.get(), s_Awaitable.get(), false, input);
22165 addDepClassToInput(s_Traversable.get(), s_Traversable.get(), false, input);
22168 void AnalysisScheduler::addDepsMeta(const DepState& deps,
22169 AnalysisInput& input) const {
22170 auto& d = [&] () -> AnalysisDeps& {
22171 switch (deps.kind) {
22172 case DepState::Func: return input.meta.funcDeps[deps.name];
22173 case DepState::Class: return input.meta.classDeps[deps.name];
22174 case DepState::Unit: return input.meta.unitDeps[deps.name];
22176 }();
22177 d |= deps.deps;
22180 // Call F for any dependency which should be included
22181 // transitively. This usually means the dependency has information
22182 // that can change between rounds. This is a subset of a DepState's
22183 // total dependencies.
22184 template <typename F>
22185 void AnalysisScheduler::onTransitiveDep(SString name,
22186 const DepState& state,
22187 const F& f) const {
22188 if (state.deps.empty() || workItems() >= 4000000) return;
22189 auto const& d = state.deps;
22190 auto const& i = *index.m_data;
22192 auto const filter = [&] (SString n) {
22193 if (!n->tsame(name)) f(n);
22196 auto const onFunc = [&] (SString n) {
22197 if (!Constant::nameFromFuncName(n)) return filter(n);
22198 auto const u = folly::get_default(i.funcToUnit, n);
22199 if (u) filter(u);
22202 auto const onCls = [&] (SString n) {
22203 if (auto const c = folly::get_default(i.closureToClass, n)) {
22204 return filter(c);
22206 if (auto const f = folly::get_default(i.closureToFunc, n)) {
22207 return onFunc(f);
22209 filter(n);
22212 auto const onClsOrTypeAlias = [&] (SString n) {
22213 if (auto const u = folly::get_default(i.typeAliasToUnit, n)) {
22214 filter(u);
22215 } else {
22216 onCls(n);
22220 stateForClassOrTypeAlias(state.name).match(
22221 [&] (const ClassState* s) {
22222 if (!s) return;
22223 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22225 [&] (const UnitState* s) {
22226 if (!s) return;
22227 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22230 if (auto const bases = folly::get_ptr(i.classToCnsBases, state.name)) {
22231 for (auto const b : *bases) onCls(b);
22234 for (auto const [meth, type] : d.methods) {
22235 if (type == Type::None) continue;
22236 if (!(type & AnalysisDeps::kValidForChanges)) continue;
22237 onCls(meth.cls);
22239 for (auto const [func, type] : d.funcs) {
22240 if (!(type & AnalysisDeps::kValidForChanges)) continue;
22241 onFunc(func);
22243 for (auto const cls : d.anyClsConstants) {
22244 stateForClassOrTypeAlias(cls).match(
22245 [&] (const ClassState* s) {
22246 if (s && !s->allCnsFixed) onCls(cls);
22248 [&] (const UnitState* s) {
22249 if (s && !s->fixed) filter(s->depState.name);
22253 for (auto const cls : d.typeCnsAnyClsConstants) onClsOrTypeAlias(cls);
22255 for (auto const cns : d.clsConstants) {
22256 stateForClassOrTypeAlias(cns.cls).match(
22257 [&] (const ClassState* s) {
22258 if (!s || s->allCnsFixed) return;
22259 if (cns.idx >= s->cnsFixed.size() || !s->cnsFixed[cns.idx]) {
22260 onCls(cns.cls);
22263 [&] (const UnitState* s) {
22264 if (s && !s->fixed) filter(s->depState.name);
22268 for (auto const cns : d.typeCnsClsConstants) onClsOrTypeAlias(cns.cls);
22270 for (auto const cns : d.constants) onFunc(Constant::funcNameFromName(cns));
22272 for (auto const ta : d.classes) {
22273 auto const unit = folly::get_default(i.typeAliasToUnit, ta);
22274 if (!unit) continue;
22275 auto const p = folly::get_ptr(unitState, unit);
22276 if (!p || p->fixed) continue;
22277 filter(unit);
22279 for (auto const c : d.typeCnsClasses) onClsOrTypeAlias(c);
22282 void AnalysisScheduler::addAllDeps(TSStringSet& out,
22283 const DepState& state) const {
22284 if (state.deps.empty()) return;
22285 auto const& d = state.deps;
22286 auto const& i = *index.m_data;
22288 auto const add = [&] (SString n) { out.emplace(n); };
22290 auto const onFunc = [&] (SString n) {
22291 if (!Constant::nameFromFuncName(n)) return add(n);
22292 auto const u = folly::get_default(i.funcToUnit, n);
22293 if (u) add(u);
22296 auto const onCls = [&] (SString n) {
22297 if (auto const c = folly::get_default(i.closureToClass, n)) {
22298 return add(c);
22300 if (auto const f = folly::get_default(i.closureToFunc, n)) {
22301 return onFunc(f);
22303 add(n);
22306 auto const onClsOrTypeAlias = [&] (SString n) {
22307 if (auto const u = folly::get_default(i.typeAliasToUnit, n)) {
22308 add(u);
22309 } else {
22310 onCls(n);
22314 stateForClassOrTypeAlias(state.name).match(
22315 [&] (const ClassState* s) {
22316 if (!s) return;
22317 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22319 [&] (const UnitState* s) {
22320 if (!s) return;
22321 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22324 if (auto const bases = folly::get_ptr(i.classToCnsBases, state.name)) {
22325 for (auto const b : *bases) onCls(b);
22328 for (auto const cls : d.classes) onClsOrTypeAlias(cls);
22329 for (auto const cls : d.typeCnsClasses) onClsOrTypeAlias(cls);
22331 for (auto const [meth, type] : d.methods) {
22332 if (type != Type::None) onCls(meth.cls);
22334 for (auto const [func, type] : d.funcs) {
22335 if (type != Type::None) onFunc(func);
22338 for (auto const cls : d.anyClsConstants) {
22339 onClsOrTypeAlias(cls);
22340 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22341 for (auto const b : *bases) onCls(b);
22344 for (auto const cls : d.typeCnsAnyClsConstants) {
22345 onClsOrTypeAlias(cls);
22346 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22347 for (auto const b : *bases) onCls(b);
22350 for (auto const cns : d.clsConstants) onClsOrTypeAlias(cns.cls);
22351 for (auto const cns : d.typeCnsClsConstants) onClsOrTypeAlias(cns.cls);
22353 for (auto const cns : d.constants) onFunc(Constant::funcNameFromName(cns));
22356 // Dump dependencies of any trace classes or functions.
22357 void AnalysisScheduler::maybeDumpTraces() const {
22358 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace, 1)) return;
22360 TSStringSet allRoots;
22361 TSStringSet allTraces;
22362 TSStringSet allDeps;
22363 TSStringToOneT<TSStringSet> allTraceEdges;
22364 TSStringToOneT<TSStringSet> allDepEdges;
22366 parallel::for_each(
22367 traceNames,
22368 [&] (SString name) {
22369 Trace::BumpRelease bumper{
22370 Trace::hhbbc_dump_trace,
22371 [&] {
22372 auto& state = traceState.at(name);
22373 int bump = std::numeric_limits<int>::max();
22374 for (auto const d : state.depStates) {
22375 SString cls = nullptr;
22376 SString func = nullptr;
22377 SString unit = nullptr;
22378 switch (d->kind) {
22379 case DepState::Func:
22380 func = d->name;
22381 unit = folly::get_default(index.m_data->funcToUnit, d->name);
22382 break;
22383 case DepState::Class:
22384 cls = d->name;
22385 unit = folly::get_default(index.m_data->classToUnit, d->name);
22386 break;
22387 case DepState::Unit:
22388 unit = d->name;
22389 break;
22391 bump = std::min(bump, trace_bump_for(cls, func, unit));
22393 assertx(bump < std::numeric_limits<int>::max());
22394 return bump;
22397 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace, 2)) return;
22399 TSStringSet traces;
22400 TSStringSet deps;
22401 TSStringToOneT<TSStringSet> traceEdges;
22402 TSStringToOneT<TSStringSet> depEdges;
22404 std::vector<SString> worklist;
22405 worklist.emplace_back(name);
22406 traces.emplace(name);
22408 while (!worklist.empty()) {
22409 auto const from = worklist.back();
22410 worklist.pop_back();
22411 auto const s = folly::get_ptr(traceState, from);
22412 if (!s) continue;
22413 for (auto const d : s->depStates) {
22414 onTransitiveDep(
22415 from, *d,
22416 [&] (SString to) {
22417 if (from->tsame(to)) return;
22418 if (!traceState.count(to)) return;
22419 traceEdges[from].emplace(to);
22420 if (!traces.emplace(to).second) return;
22421 worklist.emplace_back(to);
22427 for (auto const n : traces) {
22428 auto const s = folly::get_ptr(traceState, n);
22429 if (!s) continue;
22430 for (auto const d : s->depStates) {
22431 TSStringSet newDeps;
22432 addAllDeps(newDeps, *d);
22433 for (auto const n2 : newDeps) {
22434 if (n->tsame(n2)) continue;
22435 depEdges[n].emplace(n2);
22437 deps.insert(begin(newDeps), end(newDeps));
22441 std::lock_guard<std::mutex> _{*lock};
22442 allRoots.emplace(name);
22443 allTraces.insert(begin(traces), end(traces));
22444 allDeps.insert(begin(deps), end(deps));
22445 for (auto const& [n, e] : traceEdges) {
22446 allTraceEdges[n].insert(begin(e), end(e));
22448 for (auto const& [n, e] : depEdges) {
22449 allDepEdges[n].insert(begin(e), end(e));
22454 if (allRoots.empty() && allTraces.empty() && allDeps.empty()) return;
22456 size_t nextId = 0;
22457 TSStringToOneT<size_t> ids;
22458 auto const id = [&] (SString n) {
22459 if (auto const i = folly::get_ptr(ids, n)) return *i;
22460 ids.emplace(n, nextId);
22461 return nextId++;
22464 using namespace folly::gen;
22466 auto const nodes = [&] (const TSStringSet& ns,
22467 const char* color,
22468 const TSStringSet* f1 = nullptr,
22469 const TSStringSet* f2 = nullptr) {
22470 return from(ns)
22471 | filter([&] (SString n) { return !f1 || !f1->count(n); })
22472 | filter([&] (SString n) { return !f2 || !f2->count(n); })
22473 | orderBy(folly::identity, string_data_lt_type{})
22474 | map([&] (SString n) {
22475 return folly::sformat(
22476 " {} [label=\"{}\" color={}];",
22477 id(n), folly::cEscape<std::string>(n->slice()), color
22480 | unsplit<std::string>("\n");
22483 auto const edges = [&] (const TSStringToOneT<TSStringSet>& es,
22484 const char* color,
22485 const TSStringToOneT<TSStringSet>* f1 = nullptr) {
22486 std::vector<SString> fromE;
22487 for (auto const& [n, _] : es) fromE.emplace_back(n);
22488 std::sort(begin(fromE), end(fromE), string_data_lt_type{});
22489 return from(fromE)
22490 | map([&] (SString n) {
22491 return folly::sformat(
22492 " {} -> {{{}}} [color={}];",
22493 id(n),
22494 from(es.at(n))
22495 | filter([&] (SString n2) {
22496 if (!f1) return true;
22497 auto const t = folly::get_ptr(*f1, n);
22498 return !t || !t->count(n2);
22500 | orderBy(folly::identity, string_data_lt_type{})
22501 | map([&] (SString n2) { return folly::sformat("{}", id(n2)); })
22502 | unsplit<std::string>(" "),
22503 color
22506 | unsplit<std::string>("\n");
22509 namespace fs = std::filesystem;
22511 auto const path = [] {
22512 auto const base = [] {
22513 if (auto const e = getenv("HHBBC_DUMP_DIR")) return fs::path(e);
22514 return fs::temp_directory_path();
22515 }();
22516 return fs::weakly_canonical(
22517 base / boost::filesystem::unique_path("hhbbc-%%%%%%%%").native()
22519 }();
22521 std::ofstream out{path};
22522 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
22523 out << folly::format(
22524 "digraph \"Round #{}\" {{\n"
22525 "{}\n"
22526 "{}\n"
22527 "{}\n"
22528 "{}\n"
22529 "{}\n"
22530 "}}\n",
22531 round,
22532 nodes(allRoots, "red"),
22533 nodes(allTraces, "blue", &allRoots),
22534 nodes(allDeps, "gray", &allRoots, &allTraces),
22535 edges(allTraceEdges, "blue"),
22536 edges(allDepEdges, "gray", &allTraceEdges)
22539 std::cout << "Trace dump for round #" << round
22540 << " written to " << path
22541 << std::endl;
22544 // First pass: initialize all data in TraceStates.
22545 void AnalysisScheduler::tracePass1() {
22546 untracked = Untracked{};
22548 std::atomic<size_t> idx{0};
22549 parallel::for_each(
22550 traceNames,
22551 [&] (SString n) {
22552 auto& state = traceState.at(n);
22553 state.eligible = false;
22554 state.leaf.store(true);
22555 state.covered.store(false);
22556 state.trace.clear();
22557 state.deps.clear();
22558 state.buckets.present = nullptr;
22559 state.buckets.withBC = nullptr;
22560 state.buckets.process = nullptr;
22561 state.idx = idx++;
22566 // Second pass: Build traces for each entity and mark eligible.
22567 void AnalysisScheduler::tracePass2() {
22568 parallel::for_each(
22569 traceNames,
22570 [&] (SString name) {
22571 auto& state = traceState.at(name);
22573 // Build the trace up by using the transitive dependencies.
22574 std::vector<SString> worklist;
22575 auto const add = [&] (SString n) {
22576 if (!traceState.count(n)) return;
22577 if (!state.trace.emplace(n).second) return;
22578 if (n->tsame(name)) return;
22579 worklist.emplace_back(n);
22582 worklist.emplace_back(name);
22583 while (!worklist.empty()) {
22584 auto const n = worklist.back();
22585 worklist.pop_back();
22586 auto const s = folly::get_ptr(traceState, n);
22587 if (!s) continue;
22588 for (auto const d : s->depStates) onTransitiveDep(n, *d, add);
22591 // An entity is eligible if any of its components changed, or if
22592 // anything in the trace is eligible.
22593 auto const eligible = [&] (const TraceState& t) {
22594 return std::any_of(
22595 t.depStates.begin(), t.depStates.end(),
22596 [] (const DepState* d) { return d->toSchedule; }
22600 state.eligible =
22601 eligible(state) ||
22602 std::any_of(
22603 begin(state.trace), end(state.trace),
22604 [&] (SString n) {
22605 auto const s = folly::get_ptr(traceState, n);
22606 return s && eligible(*s);
22613 // Third pass: "Expand" every TraceState with it's total dependencies,
22614 // which is a superset of those in the trace.
22615 void AnalysisScheduler::tracePass3() {
22616 parallel::for_each(
22617 traceNames,
22618 [&] (SString name) {
22619 auto& state = traceState.at(name);
22621 if (!state.eligible) return;
22623 folly::erase_if(
22624 state.trace,
22625 [&] (SString n) {
22626 auto const s = folly::get_ptr(traceState, n);
22627 return !s || !s->eligible;
22631 auto const expand = [&] (SString n) {
22632 auto const s = folly::get_ptr(traceState, n);
22633 if (!s) return;
22634 for (auto const d : s->depStates) addAllDeps(state.deps, *d);
22637 state.deps = state.trace;
22638 expand(name);
22639 for (auto const n : state.trace) expand(n);
22644 // Fourth pass: Determine leafs for the purpose of scheduling.
22645 std::vector<SString> AnalysisScheduler::tracePass4() {
22646 // A leaf here means that nothing else depends on it.
22648 // Mark any TraceState which is on another TraceState's trace as not
22649 // being a leaf.
22650 parallel::for_each(
22651 traceNames,
22652 [&] (SString name) {
22653 auto& state = traceState.at(name);
22654 if (!state.eligible) return;
22655 for (auto const n : state.trace) {
22656 auto const s = folly::get_ptr(traceState, n);
22657 if (s) s->leaf.store(false);
22662 // Mark "covered" TraceStates. A covered TraceState is processed and
22663 // shouldn't be considered anymore in this function.
22664 std::vector<SString> traceLeafs;
22665 parallel::for_each(
22666 traceNames,
22667 [&] (SString name) {
22668 auto& state = traceState.at(name);
22669 if (!state.eligible || !state.leaf) return;
22671 // Right now a TraceState is covered if it's a leaf, along with
22672 // anything on this TraceState's trace.
22673 state.covered.store(true);
22674 for (auto const n : state.trace) {
22675 auto const s = folly::get_ptr(traceState, n);
22676 if (s) s->covered.store(true);
22679 std::lock_guard<std::mutex> _{*lock};
22680 traceLeafs.emplace_back(name);
22684 // Any uncovered TraceState's must now be part of a cycle (or depend
22685 // on a TraceState in a cycle).
22686 std::vector<SString> traceInCycle;
22687 parallel::for_each(
22688 traceNames,
22689 [&] (SString n) {
22690 auto const& state = traceState.at(n);
22691 if (!state.eligible || state.covered) return;
22692 assertx(!state.leaf);
22693 std::lock_guard<std::mutex> _{*lock};
22694 traceInCycle.emplace_back(n);
22698 // Sort all TraceStates in a cycle. This isn't strictly necessary,
22699 // but leads to better results.
22700 std::sort(
22701 begin(traceInCycle),
22702 end(traceInCycle),
22703 [&] (SString n1, SString n2) {
22704 auto const& state1 = traceState.at(n1);
22705 auto const& state2 = traceState.at(n2);
22706 assertx(state1.eligible);
22707 assertx(!state1.covered);
22708 assertx(!state1.leaf);
22709 assertx(state2.eligible);
22710 assertx(!state2.covered);
22711 assertx(!state2.leaf);
22712 if (state1.trace.size() > state2.trace.size()) return true;
22713 if (state1.trace.size() < state2.trace.size()) return false;
22714 return string_data_lt_type{}(n1, n2);
22718 // Pick an arbitrary TraceState from the cycle set, declare it a
22719 // leaf by fiat, then mark it and any trace dependencies as being
22720 // covered. This process repeats until all TraceStates are covered.
22721 for (auto const name : traceInCycle) {
22722 auto& s = traceState.at(name);
22723 if (s.covered) continue;
22724 assertx(!s.leaf);
22725 s.leaf.store(true);
22726 s.covered.store(true);
22727 for (auto const n : s.trace) {
22728 auto const s2 = folly::get_ptr(traceState, n);
22729 if (!s2 || s2->covered) continue;
22730 assertx(!s2->leaf);
22731 s2->covered.store(true);
22733 traceLeafs.emplace_back(name);
22736 if (debug) {
22737 // Every TraceState at this point should be covered (or not
22738 // eligible).
22739 parallel::for_each(
22740 traceNames,
22741 [&] (SString n) {
22742 auto const s = folly::get_ptr(traceState, n);
22743 always_assert(s);
22744 always_assert_flog(
22745 s->covered || !s->eligible,
22746 "{} is not covered", n
22752 return traceLeafs;
22755 // Fifth pass: Assign leafs and their dependencies to
22756 // buckets. Non-leafs (which must have some TraceState depending on
22757 // them) will be assigned to one of the buckets of the TraceState that
22758 // depends on it.
22759 std::vector<AnalysisScheduler::Bucket>
22760 AnalysisScheduler::tracePass5(size_t bucketSize,
22761 size_t maxBucketSize,
22762 std::vector<SString> leafs) {
22763 using namespace folly::gen;
22765 auto w = assign_hierarchical_work(
22766 leafs,
22767 traceState.size(),
22768 bucketSize,
22769 maxBucketSize,
22770 [&] (SString n) {
22771 return std::make_pair(&traceState.at(n).deps, true);
22773 [&] (const TSStringSet& roots,
22774 size_t bucketIdx,
22775 SString n) -> Optional<size_t> {
22776 // To determine whether we want to promote this TraceState or
22777 // not, we need to check if it's present in any of the bucket's
22778 // traces. This can be expensive to calculate, so utilize
22779 // caching. Only recalculate the set if we're referring to a
22780 // different bucket than last call.
22781 thread_local Optional<size_t> last;
22782 thread_local TSStringSet inTrace;
22783 if (!last || *last != bucketIdx) {
22784 inTrace.clear();
22785 for (auto const root : roots) {
22786 auto const& state = traceState.at(root);
22787 inTrace.insert(begin(state.trace), end(state.trace));
22789 last = bucketIdx;
22792 // If the TraceState is a leaf, or isn't in any of the traces
22793 // for this bucket, we don't want to promote it.
22794 auto const s = folly::get_ptr(traceState, n);
22795 if (!s || !s->eligible || s->leaf.load() || !inTrace.count(n)) {
22796 return std::nullopt;
22798 return s->idx;
22802 if (debug) {
22803 // Sanity check that every eligible TraceState belongs to at least
22804 // one bucket.
22805 TSStringSet inputs;
22806 for (auto const& b : w) {
22807 inputs.insert(begin(b.classes), end(b.classes));
22810 parallel::for_each(
22811 traceNames,
22812 [&] (SString n) {
22813 auto const s = folly::get_ptr(traceState, n);
22814 always_assert(s);
22815 if (!s->eligible) return;
22816 always_assert_flog(
22817 inputs.count(n),
22818 "{} should be present in at least one bucket's inputs, but is not",
22825 using H = HierarchicalWorkBucket;
22826 return from(w)
22827 | move
22828 | map([] (H&& b) { return Bucket{std::move(b)}; })
22829 | as<std::vector>();
22832 // Sixth pass: Make AnalysisInputs for each bucket.
22833 AnalysisScheduler::InputsAndUntracked
22834 AnalysisScheduler::tracePass6(const std::vector<Bucket>& buckets) {
22835 TSStringToOneConcurrentT<std::nullptr_t> seenClasses;
22836 FSStringToOneConcurrentT<std::nullptr_t> seenFuncs;
22837 SStringToOneConcurrentT<std::nullptr_t> seenUnits;
22838 SStringToOneConcurrentT<std::nullptr_t> seenConstants;
22840 auto outputs = parallel::gen(
22841 buckets.size(),
22842 [&] (size_t idx) {
22843 auto const& b = buckets[idx].b;
22844 assertx(b.uninstantiable.empty());
22846 TSStringSet inTrace;
22848 // Make an AnalysisInput by adding all the appropriate inputs
22849 // for each TraceState and dependencies.
22850 AnalysisInput input;
22851 input.meta.bucketIdx = idx;
22853 for (auto const item : b.classes) {
22854 auto const& state = traceState.at(item);
22855 assertx(!state.depStates.empty());
22856 for (auto const d : state.depStates) {
22857 switch (d->kind) {
22858 case DepState::Func:
22859 addFuncToInput(d->name, input);
22860 break;
22861 case DepState::Class:
22862 addClassToInput(d->name, input);
22863 break;
22864 case DepState::Unit:
22865 addUnitToInput(d->name, input);
22866 break;
22869 if (!d->toSchedule) addDepsMeta(*d, input);
22872 inTrace.insert(begin(state.trace), end(state.trace));
22875 for (auto const item : b.deps) {
22876 auto const s = folly::get_ptr(traceState, item);
22877 if (!s || !s->eligible || !inTrace.count(item)) continue;
22878 assertx(!s->depStates.empty());
22880 for (auto const d : s->depStates) {
22881 addTraceDepToInput(*d, input);
22883 if (!d->toSchedule) {
22884 addDepsMeta(*d, input);
22885 continue;
22888 switch (d->kind) {
22889 case DepState::Func:
22890 input.meta.processDepFunc.emplace(d->name);
22891 break;
22892 case DepState::Class:
22893 input.meta.processDepCls.emplace(d->name);
22894 break;
22895 case DepState::Unit:
22896 input.meta.processDepUnit.emplace(d->name);
22897 break;
22902 addAllDepsToInput(input);
22904 InputsAndUntracked out;
22906 // Record untracked items. These are inputs to this bucket which
22907 // do not have TraceState. They must be dealt with differently
22908 // later on.
22909 auto const untracked = [&] (auto const& i1,
22910 auto const& i2,
22911 auto& s,
22912 auto t) {
22913 using D1 = std::decay_t<decltype(i1)>;
22914 using D2 = std::decay_t<decltype(i2)>;
22916 std::vector<SString> out;
22918 if constexpr (!std::is_same_v<D1, std::nullptr_t>) {
22919 for (auto const& [n, _] : i1) {
22920 if (std::invoke(t, this, n)) continue;
22921 if (!s.try_emplace(n).second) continue;
22922 out.emplace_back(n);
22925 if constexpr (!std::is_same_v<D2, std::nullptr_t>) {
22926 for (auto const n : i2) {
22927 if (std::invoke(t, this, n)) continue;
22928 if (!s.try_emplace(n).second) continue;
22929 out.emplace_back(n);
22932 return out;
22935 using A = AnalysisScheduler;
22936 out.untrackedClasses = untracked(
22937 input.depClasses,
22938 input.meta.badClasses,
22939 seenClasses,
22940 &A::traceForClass
22942 out.untrackedFuncs = untracked(
22943 input.depFuncs,
22944 input.meta.badFuncs,
22945 seenFuncs,
22946 &A::traceForFunc
22948 out.untrackedUnits = untracked(
22949 input.depUnits,
22950 nullptr,
22951 seenUnits,
22952 &A::traceForUnit
22954 out.badConstants = untracked(
22955 nullptr,
22956 input.meta.badConstants,
22957 seenConstants,
22958 &A::traceForConstant
22961 out.inputs.emplace_back(std::move(input));
22962 return out;
22966 // We created untracked data for each bucket. We must now combine
22967 // that into one unified set.
22969 auto const combine = [&] (auto f, auto& m) {
22970 std::vector<SString> out;
22971 for (auto const& output : outputs) {
22972 auto& in = output.*f;
22973 out.insert(end(out), begin(in), end(in));
22975 for (auto const n : out) m.try_emplace(n);
22976 return out;
22979 using I = InputsAndUntracked;
22980 I combined;
22981 parallel::parallel(
22982 [&] {
22983 combined.inputs.reserve(outputs.size());
22984 for (auto& output : outputs) {
22985 assertx(output.inputs.size() == 1);
22986 combined.inputs.emplace_back(std::move(output.inputs[0]));
22989 [&] {
22990 combined.untrackedClasses =
22991 combine(&I::untrackedClasses, untracked.classes);
22993 [&] {
22994 combined.untrackedFuncs =
22995 combine(&I::untrackedFuncs, untracked.funcs);
22997 [&] {
22998 combined.untrackedUnits =
22999 combine(&I::untrackedUnits, untracked.units);
23001 [&] {
23002 combined.badConstants =
23003 combine(&I::badConstants, untracked.badConstants);
23007 return combined;
23010 // Seventh pass: Calculate BucketSets for each TraceSet. This will
23011 // determine how different items can utilize information from another.
23012 void AnalysisScheduler::tracePass7(InputsAndUntracked& inputs) {
23013 using A = AnalysisInput;
23015 auto const onClass = [&] (SString name,
23016 A::BucketSet& present,
23017 A::BucketSet& withBC,
23018 A::BucketSet& process) {
23019 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23020 auto const& input = inputs.inputs[i];
23021 if (input.classes.count(name)) {
23022 assertx(input.classBC.count(name));
23023 present.add(i);
23024 process.add(i);
23026 if (input.depClasses.count(name)) {
23027 present.add(i);
23028 if (input.meta.classDeps.count(name) ||
23029 input.meta.processDepCls.count(name)) {
23030 assertx(input.classBC.count(name));
23031 process.add(i);
23034 if (input.classBC.count(name)) withBC.add(i);
23035 if (input.meta.badClasses.count(name)) present.add(i);
23039 auto const onFunc = [&] (SString name,
23040 A::BucketSet& present,
23041 A::BucketSet& withBC,
23042 A::BucketSet& process) {
23043 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23044 auto const& input = inputs.inputs[i];
23045 if (input.funcs.count(name)) {
23046 assertx(input.funcBC.count(name));
23047 present.add(i);
23048 process.add(i);
23050 if (input.depFuncs.count(name)) {
23051 present.add(i);
23052 if (input.meta.funcDeps.count(name) ||
23053 input.meta.processDepFunc.count(name)) {
23054 assertx(input.funcBC.count(name));
23055 process.add(i);
23058 if (input.funcBC.count(name)) withBC.add(i);
23059 if (input.meta.badFuncs.count(name)) present.add(i);
23063 auto const onUnit = [&] (SString name,
23064 A::BucketSet& present,
23065 A::BucketSet&,
23066 A::BucketSet& process) {
23067 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23068 auto const& input = inputs.inputs[i];
23069 if (input.units.count(name)) {
23070 present.add(i);
23071 process.add(i);
23073 if (input.depUnits.count(name)) {
23074 present.add(i);
23075 if (input.meta.unitDeps.count(name) ||
23076 input.meta.processDepUnit.count(name)) {
23077 process.add(i);
23083 parallel::for_each(
23084 traceNames,
23085 [&] (SString name) {
23086 auto& state = traceState.at(name);
23087 state.trace.clear();
23088 state.deps.clear();
23090 A::BucketSet present;
23091 A::BucketSet withBC;
23092 A::BucketSet process;
23094 auto first = true;
23095 A::BucketSet temp1, temp2, temp3;
23096 for (auto const d : state.depStates) {
23097 temp1.clear();
23098 temp2.clear();
23099 temp3.clear();
23100 auto& s1 = first ? present : temp1;
23101 auto& s2 = first ? withBC : temp2;
23102 auto& s3 = first ? process : temp3;
23103 switch (d->kind) {
23104 case DepState::Func:
23105 onFunc(d->name, s1, s2, s3);
23106 break;
23107 case DepState::Class:
23108 onClass(d->name, s1, s2, s3);
23109 break;
23110 case DepState::Unit:
23111 onUnit(d->name, s1, s2, s3);
23112 break;
23114 if (!first) {
23115 present |= temp1;
23116 withBC |= temp2;
23117 process |= temp3;
23118 } else {
23119 first = false;
23123 assertx(!state.buckets.present);
23124 assertx(!state.buckets.withBC);
23125 assertx(!state.buckets.process);
23127 state.buckets.present = A::BucketSet::intern(std::move(present));
23128 state.buckets.withBC = A::BucketSet::intern(std::move(withBC));
23129 state.buckets.process = A::BucketSet::intern(std::move(process));
23133 // Do the same for untracked items:
23135 auto const addUntracked = [&] (const std::vector<SString>& v,
23136 auto& m,
23137 auto const& o) {
23138 parallel::for_each(
23140 [&] (SString name) {
23141 auto& state = m.at(name);
23143 A::BucketSet present;
23144 A::BucketSet withBC;
23145 A::BucketSet process;
23146 o(name, present, withBC, process);
23148 assertx(!present.empty());
23149 assertx(process.empty());
23151 assertx(!state.present);
23152 assertx(!state.withBC);
23153 assertx(!state.process);
23155 state.present = A::BucketSet::intern(std::move(present));
23156 state.withBC = A::BucketSet::intern(std::move(withBC));
23157 state.process = A::BucketSet::intern(std::move(process));
23161 addUntracked(inputs.untrackedClasses, untracked.classes, onClass);
23162 addUntracked(inputs.untrackedFuncs, untracked.funcs, onFunc);
23163 addUntracked(inputs.untrackedUnits, untracked.units, onUnit);
23164 addUntracked(
23165 inputs.badConstants,
23166 untracked.badConstants,
23167 [&] (SString name,
23168 A::BucketSet& present,
23169 A::BucketSet&,
23170 A::BucketSet&) {
23171 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23172 auto const& input = inputs.inputs[i];
23173 if (input.meta.badConstants.count(name)) {
23174 present.add(i);
23180 inputs.untrackedClasses.clear();
23181 inputs.untrackedFuncs.clear();
23182 inputs.untrackedUnits.clear();
23183 inputs.badConstants.clear();
23186 // Eighth pass: Store BucketSets in each AnalysisInput's metadata.
23187 void AnalysisScheduler::tracePass8(std::vector<Bucket> buckets,
23188 std::vector<AnalysisInput>& inputs) {
23189 parallel::gen(
23190 inputs.size(),
23191 [&] (size_t i) {
23192 auto& input = inputs[i];
23193 assertx(i < buckets.size());
23194 auto& bucket = buckets[i].b;
23196 for (auto const item : bucket.classes) {
23197 auto& state = traceState.at(item);
23198 assertx(!state.depStates.empty());
23199 assertx(state.buckets.present->contains(i));
23200 assertx(state.buckets.process->contains(i));
23202 for (auto const d : state.depStates) {
23203 switch (d->kind) {
23204 case DepState::Func:
23205 always_assert(
23206 input.meta.funcBuckets.emplace(d->name, state.buckets).second
23208 break;
23209 case DepState::Class:
23210 always_assert(
23211 input.meta.classBuckets.emplace(d->name, state.buckets).second
23213 break;
23214 case DepState::Unit:
23215 always_assert(
23216 input.meta.unitBuckets.emplace(d->name, state.buckets).second
23218 break;
23223 for (auto const item : bucket.deps) {
23224 auto const s = folly::get_ptr(traceState, item);
23225 if (!s) continue;
23226 assertx(!s->depStates.empty());
23227 if (!s->buckets.present->contains(i)) {
23228 // If this trace state isn't in the bucket, an untracked
23229 // item (with the same name) should be.
23230 auto const b = [&] () -> const AnalysisInput::BucketPresence* {
23231 auto const& u = untracked;
23232 if (auto const b = folly::get_ptr(u.classes, item)) return b;
23233 if (auto const b = folly::get_ptr(u.funcs, item)) return b;
23234 if (auto const b = folly::get_ptr(u.units, item)) return b;
23235 if (auto const b = folly::get_ptr(u.badConstants, item)) return b;
23236 return nullptr;
23237 }();
23238 always_assert(b);
23239 always_assert(b->present->contains(i));
23240 continue;
23243 for (auto const d : s->depStates) {
23244 switch (d->kind) {
23245 case DepState::Func:
23246 always_assert(
23247 input.meta.funcBuckets.emplace(d->name, s->buckets).second
23249 break;
23250 case DepState::Class:
23251 always_assert(
23252 input.meta.classBuckets.emplace(d->name, s->buckets).second
23254 break;
23255 case DepState::Unit:
23256 always_assert(
23257 input.meta.unitBuckets.emplace(d->name, s->buckets).second
23259 break;
23264 for (auto const& [name, _] : input.depClasses) {
23265 if (auto const b = folly::get_ptr(untracked.classes, name)) {
23266 assertx(!traceForClass(name));
23267 assertx(b->present->contains(i));
23268 always_assert(input.meta.classBuckets.emplace(name, *b).second);
23269 } else if (!input.meta.classBuckets.count(name)) {
23270 auto const t = traceForClass(name);
23271 always_assert_flog(
23272 t, "{} is on input dep classes, but has no tracking state",
23273 name
23275 always_assert(
23276 input.meta.classBuckets.emplace(name, t->buckets).second
23278 assertx(t->buckets.present->contains(i));
23281 for (auto const& [name, _] : input.depFuncs) {
23282 if (auto const b = folly::get_ptr(untracked.funcs, name)) {
23283 assertx(!traceForFunc(name));
23284 assertx(b->present->contains(i));
23285 always_assert(input.meta.funcBuckets.emplace(name, *b).second);
23286 } else if (!input.meta.funcBuckets.count(name)) {
23287 auto const t = traceForFunc(name);
23288 always_assert_flog(
23289 t, "{} is on input dep funcs, but has no tracking state",
23290 name
23292 always_assert(
23293 input.meta.funcBuckets.emplace(name, t->buckets).second
23295 assertx(t->buckets.present->contains(i));
23298 for (auto const& [name, _] : input.depUnits) {
23299 if (auto const b = folly::get_ptr(untracked.units, name)) {
23300 assertx(!traceForUnit(name));
23301 assertx(b->present->contains(i));
23302 always_assert(input.meta.unitBuckets.emplace(name, *b).second);
23303 } else if (!input.meta.unitBuckets.count(name)) {
23304 auto const t = traceForUnit(name);
23305 always_assert_flog(
23306 t, "{} is on input dep units, but has no tracking state",
23307 name
23309 always_assert(
23310 input.meta.unitBuckets.emplace(name, t->buckets).second
23312 assertx(t->buckets.present->contains(i));
23316 for (auto const name : input.meta.badClasses) {
23317 if (input.meta.classBuckets.count(name)) continue;
23318 if (auto const b = folly::get_ptr(untracked.classes, name)) {
23319 assertx(!traceForClass(name));
23320 assertx(b->present->contains(i));
23321 always_assert(input.meta.classBuckets.emplace(name, *b).second);
23322 } else {
23323 auto const t = traceForClass(name);
23324 always_assert_flog(
23325 t, "{} is on input bad classes, but has no tracking state",
23326 name
23328 input.meta.classBuckets.emplace(name, t->buckets);
23329 assertx(t->buckets.present->contains(i));
23332 for (auto const name : input.meta.badFuncs) {
23333 if (auto const b = folly::get_ptr(untracked.funcs, name)) {
23334 assertx(!traceForFunc(name));
23335 assertx(b->present->contains(i));
23336 always_assert(input.meta.funcBuckets.emplace(name, *b).second);
23337 } else if (!input.meta.funcBuckets.count(name)) {
23338 auto const t = traceForFunc(name);
23339 always_assert_flog(
23340 t, "{} is on input bad funcs, but has no tracking state",
23341 name
23343 always_assert(
23344 input.meta.funcBuckets.emplace(name, t->buckets).second
23348 for (auto const name : input.meta.badConstants) {
23349 if (auto const b = folly::get_ptr(untracked.badConstants, name)) {
23350 assertx(!traceForConstant(name));
23351 assertx(b->present->contains(i));
23352 always_assert(input.meta.badConstantBuckets.emplace(name, *b).second);
23353 } else {
23354 auto const t = traceForConstant(name);
23355 always_assert_flog(
23356 t, "{} is on input bad constants, but has no tracking state",
23357 name
23359 always_assert(
23360 input.meta.badConstantBuckets.emplace(name, t->buckets).second
23362 assertx(t->buckets.present->contains(i));
23366 bucket.classes.clear();
23367 bucket.deps.clear();
23368 return nullptr;
23373 // Ninth pass: Strictly debug. Check various invariants.
23374 void AnalysisScheduler::tracePass9(const std::vector<AnalysisInput>& inputs) {
23375 if (!debug) return;
23377 TSStringToOneT<size_t> classes;
23378 FSStringToOneT<size_t> funcs;
23379 SStringToOneT<size_t> units;
23381 // Every class/func/unit on an AnalysisInput should only be one
23382 // AnalysisInput.
23383 for (auto const& i : inputs) {
23384 for (auto const& [n, _] : i.classes) {
23385 auto const [it, e] = classes.try_emplace(n, i.meta.bucketIdx);
23386 always_assert_flog(
23388 "Class {} is in both bucket {} and {}!\n",
23389 n, it->second, i.meta.bucketIdx
23392 for (auto const& [n, _] : i.funcs) {
23393 auto const [it, e] = funcs.try_emplace(n, i.meta.bucketIdx);
23394 if (e) continue;
23395 always_assert_flog(
23397 "Func {} is in both bucket {} and {}!\n",
23398 n, it->second, i.meta.bucketIdx
23401 for (auto const& [n, _] : i.units) {
23402 auto const [it, e] = units.try_emplace(n, i.meta.bucketIdx);
23403 if (e) continue;
23404 always_assert_flog(
23406 "Unit {} is in both bucket {} and {}!\n",
23407 n, it->second, i.meta.bucketIdx
23411 // Dep class/funcs/units can be in multiple AnalysisInputs, but
23412 // can't appear more than once in the same AnalysisInput.
23413 for (auto const& [n, _] : i.depClasses) {
23414 always_assert_flog(
23415 !i.classes.count(n),
23416 "Class {} is in both classes and depClasses in bucket {}\n",
23417 n, i.meta.bucketIdx
23420 for (auto const& [n, _] : i.depFuncs) {
23421 always_assert_flog(
23422 !i.funcs.count(n),
23423 "Func {} is in both funcs and depFuncs in bucket {}\n",
23424 n, i.meta.bucketIdx
23427 for (auto const& [n, _] : i.depUnits) {
23428 always_assert_flog(
23429 !i.units.count(n),
23430 "Unit {} is in both units and depUnits in bucket {}\n",
23431 n, i.meta.bucketIdx
23435 // Ditto for "bad" classes or funcs.
23436 for (auto const n : i.meta.badClasses) {
23437 always_assert_flog(
23438 !i.classes.count(n),
23439 "Class {} is in both classes and badClasses in bucket {}\n",
23440 n, i.meta.bucketIdx
23442 always_assert_flog(
23443 !i.depClasses.count(n),
23444 "Class {} is in both depClasses and badClasses in bucket {}\n",
23445 n, i.meta.bucketIdx
23448 for (auto const n : i.meta.badFuncs) {
23449 always_assert_flog(
23450 !i.funcs.count(n),
23451 "Func {} is in both funcs and badFuncs in bucket {}\n",
23452 n, i.meta.bucketIdx
23454 always_assert_flog(
23455 !i.depFuncs.count(n),
23456 "Func {} is in both depFuncs and badFuncs in bucket {}\n",
23457 n, i.meta.bucketIdx
23463 // Group the work that needs to run into buckets of the given size.
23464 std::vector<AnalysisInput> AnalysisScheduler::schedule(size_t bucketSize,
23465 size_t maxBucketSize) {
23466 FTRACE(2, "AnalysisScheduler: scheduling {} items into buckets of "
23467 "size {} (max {})\n",
23468 workItems(), bucketSize, maxBucketSize);
23470 tracePass1();
23471 maybeDumpTraces();
23472 tracePass2();
23473 tracePass3();
23474 auto leafs = tracePass4();
23475 auto buckets = tracePass5(bucketSize, maxBucketSize, std::move(leafs));
23476 auto inputs = tracePass6(buckets);
23477 tracePass7(inputs);
23478 tracePass8(std::move(buckets), inputs.inputs);
23479 tracePass9(inputs.inputs);
23481 totalWorkItems.store(0);
23482 FTRACE(2, "AnalysisScheduler: scheduled {} buckets\n", inputs.inputs.size());
23483 ++round;
23484 return std::move(inputs.inputs);
23487 //////////////////////////////////////////////////////////////////////
23489 namespace {
23491 //////////////////////////////////////////////////////////////////////
23493 // If we optimized a top-level constant's value to a scalar, we no
23494 // longer need the associated 86cinit function. This fixes up the
23495 // metadata to remove it.
23496 FSStringSet strip_unneeded_constant_inits(AnalysisIndex::IndexData& index) {
23497 FSStringSet stripped;
23499 for (auto const name : index.outFuncNames) {
23500 auto const cnsName = Constant::nameFromFuncName(name);
23501 if (!cnsName) continue;
23502 auto const it = index.constants.find(cnsName);
23503 if (it == end(index.constants)) continue;
23504 auto const& cns = *it->second.first;
23505 if (type(cns.val) == KindOfUninit) continue;
23506 stripped.emplace(name);
23508 if (stripped.empty()) return stripped;
23510 for (auto const name : stripped) {
23511 index.deps->getChanges().remove(*index.funcs.at(name));
23512 index.funcs.erase(name);
23513 index.finfos.erase(name);
23516 index.outFuncNames.erase(
23517 std::remove_if(
23518 begin(index.outFuncNames), end(index.outFuncNames),
23519 [&] (SString f) { return stripped.count(f); }
23521 end(index.outFuncNames)
23524 for (auto& [_, unit] : index.units) {
23525 unit->funcs.erase(
23526 std::remove_if(
23527 begin(unit->funcs), end(unit->funcs),
23528 [&, unit=unit] (SString f) {
23529 if (!stripped.count(f)) return false;
23530 assertx(
23531 index.deps->bucketFor(unit).process->contains(index.bucketIdx)
23533 return true;
23536 end(unit->funcs)
23540 return stripped;
23543 // Record the new set of classes which this class has inherited
23544 // constants from. This set can change (always shrink) due to
23545 // optimizations rewriting constants.
23546 TSStringSet record_cns_bases(const php::Class& cls,
23547 const AnalysisIndex::IndexData& index) {
23548 TSStringSet out;
23549 if (!cls.cinfo) return out;
23550 for (auto const& [_, idx] : cls.cinfo->clsConstants) {
23551 if (!cls.name->tsame(idx.idx.cls)) out.emplace(idx.idx.cls);
23552 if (auto const cnsCls = folly::get_default(index.classes, idx.idx.cls)) {
23553 assertx(idx.idx.idx < cnsCls->constants.size());
23554 auto const& cns = cnsCls->constants[idx.idx.idx];
23555 if (!cls.name->tsame(cns.cls)) out.emplace(cns.cls);
23558 return out;
23561 // Mark all "fixed" class constants. A class constant is fixed if it's
23562 // value can't change going forward. This transforms any dependency on
23563 // that constant from a transitive one (that gets put on the trace) to
23564 // a strict dependency (which only goes on the dep list).
23565 void mark_fixed_class_constants(const php::Class& cls,
23566 AnalysisIndex::IndexData& index) {
23567 auto& changes = index.deps->getChanges();
23569 auto all = true;
23570 for (size_t i = 0, size = cls.constants.size(); i < size; ++i) {
23571 auto const& cns = cls.constants[i];
23572 auto const fixed = [&] {
23573 if (!cns.val) return true;
23574 if (cns.kind == ConstModifiers::Kind::Type) {
23575 // A type-constant is fixed if it's been resolved.
23576 return cns.resolvedTypeStructure && cns.contextInsensitive;
23577 } else if (cns.kind == ConstModifiers::Kind::Value) {
23578 // A normal constant is fixed if it's a scalar.
23579 return type(*cns.val) != KindOfUninit;
23580 } else {
23581 // Anything else never changes.
23582 return true;
23584 }();
23585 if (fixed) {
23586 changes.fixed(ConstIndex { cls.name, (unsigned int)i });
23587 } else {
23588 all = false;
23591 if (all) changes.fixed(cls);
23593 // While we're at it, record all the names present in this class'
23594 // type-constants. This will be used in forming dependencies.
23595 for (auto const& [_, idx] :
23596 index.index.lookup_flattened_class_type_constants(cls)) {
23597 auto const cinfo = folly::get_default(index.cinfos, idx.cls);
23598 if (!cinfo) continue;
23599 assertx(idx.idx < cinfo->cls->constants.size());
23600 auto const& cns = cinfo->cls->constants[idx.idx];
23601 if (cns.kind != ConstModifiers::Kind::Type) continue;
23602 if (!cns.val.has_value()) continue;
23603 auto const ts = [&] () -> SArray {
23604 if (cns.resolvedTypeStructure &&
23605 (cns.contextInsensitive || cls.name->tsame(cns.cls))) {
23606 return cns.resolvedTypeStructure;
23608 assertx(tvIsDict(*cns.val));
23609 return val(*cns.val).parr;
23610 }();
23611 auto const name = type_structure_name(ts);
23612 if (!name) continue;
23613 changes.typeCnsName(cls, AnalysisChangeSet::Class { name });
23617 // Mark whether this unit is "fixed". An unit is fixed if all the
23618 // type-aliases in it are resolved.
23619 void mark_fixed_unit(const php::Unit& unit,
23620 AnalysisChangeSet& changes) {
23621 auto all = true;
23622 for (auto const& ta : unit.typeAliases) {
23623 if (!ta->resolvedTypeStructure) all = false;
23624 auto const ts = [&] {
23625 if (ta->resolvedTypeStructure) return ta->resolvedTypeStructure;
23626 return ta->typeStructure;
23627 }();
23628 auto const name = type_structure_name(ts);
23629 if (!name) continue;
23630 changes.typeCnsName(unit, AnalysisChangeSet::Class { name });
23632 if (all) changes.fixed(unit);
23635 //////////////////////////////////////////////////////////////////////
23639 //////////////////////////////////////////////////////////////////////
23641 AnalysisIndex::AnalysisIndex(
23642 AnalysisWorklist& worklist,
23643 VU<php::Class> classes,
23644 VU<php::Func> funcs,
23645 VU<php::Unit> units,
23646 VU<php::ClassBytecode> clsBC,
23647 VU<php::FuncBytecode> funcBC,
23648 V<AnalysisIndexCInfo> cinfos,
23649 V<AnalysisIndexFInfo> finfos,
23650 V<AnalysisIndexMInfo> minfos,
23651 VU<php::Class> depClasses,
23652 VU<php::Func> depFuncs,
23653 VU<php::Unit> depUnits,
23654 AnalysisInput::Meta meta,
23655 Mode mode
23656 ) : m_data{std::make_unique<IndexData>(*this, worklist, mode)}
23658 m_data->bucketIdx = meta.bucketIdx;
23660 m_data->badClasses = std::move(meta.badClasses);
23661 m_data->badFuncs = std::move(meta.badFuncs);
23662 m_data->badConstants = std::move(meta.badConstants);
23664 std::vector<SString> depClassNames;
23666 m_data->outClassNames.reserve(classes.size());
23667 m_data->allClasses.reserve(classes.size() + depClasses.size());
23668 depClassNames.reserve(depClasses.size());
23669 for (auto& cls : classes) {
23670 for (auto& clo : cls->closures) {
23671 always_assert(
23672 m_data->classes.emplace(clo->name, clo.get()).second
23675 auto const name = cls->name;
23676 always_assert(m_data->classes.emplace(name, cls.get()).second);
23677 m_data->outClassNames.emplace_back(name);
23678 m_data->allClasses.emplace(name, std::move(cls));
23680 for (auto& cls : depClasses) {
23681 for (auto& clo : cls->closures) {
23682 always_assert(
23683 m_data->classes.emplace(clo->name, clo.get()).second
23686 auto const name = cls->name;
23687 always_assert(m_data->classes.emplace(name, cls.get()).second);
23688 depClassNames.emplace_back(name);
23689 m_data->allClasses.emplace(name, std::move(cls));
23692 std::vector<SString> depFuncNames;
23694 m_data->outFuncNames.reserve(funcs.size());
23695 m_data->allFuncs.reserve(funcs.size() + depFuncs.size());
23696 m_data->funcs.reserve(funcs.size() + depFuncs.size());
23697 depFuncNames.reserve(depFuncs.size());
23698 for (auto& func : funcs) {
23699 auto const name = func->name;
23700 always_assert(m_data->funcs.emplace(name, func.get()).second);
23701 m_data->outFuncNames.emplace_back(name);
23702 m_data->allFuncs.emplace(name, std::move(func));
23704 for (auto& func : depFuncs) {
23705 auto const name = func->name;
23706 always_assert(m_data->funcs.emplace(name, func.get()).second);
23707 depFuncNames.emplace_back(name);
23708 m_data->allFuncs.emplace(name, std::move(func));
23711 auto const assignFInfoIdx = [&] (php::Func& func, FuncInfo2& finfo) {
23712 always_assert(func.idx == std::numeric_limits<uint32_t>::max());
23713 always_assert(!finfo.func);
23714 finfo.func = &func;
23715 func.idx = m_data->finfosByIdx.size();
23716 m_data->finfosByIdx.emplace_back(&finfo);
23719 auto const addCInfo = [&] (ClassInfo2* cinfo) {
23720 auto cls = folly::get_default(m_data->classes, cinfo->name);
23721 always_assert(cls);
23722 always_assert(!cls->cinfo);
23723 always_assert(!cinfo->cls);
23724 cls->cinfo = cinfo;
23725 cinfo->cls = cls;
23727 auto const numMethods = cls->methods.size();
23728 always_assert(cinfo->funcInfos.size() == numMethods);
23729 for (size_t i = 0; i < numMethods; ++i) {
23730 assignFInfoIdx(*cls->methods[i], *cinfo->funcInfos[i]);
23732 always_assert(m_data->cinfos.emplace(cinfo->name, cinfo).second);
23735 m_data->allCInfos.reserve(cinfos.size());
23736 for (auto& wrapper : cinfos) {
23737 for (auto& clo : wrapper.ptr->closures) addCInfo(clo.get());
23738 addCInfo(wrapper.ptr.get());
23739 auto const name = wrapper.ptr->name;
23740 m_data->allCInfos.emplace(name, wrapper.ptr.release());
23743 m_data->finfos.reserve(finfos.size());
23744 m_data->allFInfos.reserve(finfos.size());
23745 for (auto& wrapper : finfos) {
23746 auto func = folly::get_default(m_data->funcs, wrapper.ptr->name);
23747 always_assert(func);
23748 assignFInfoIdx(*func, *wrapper.ptr);
23749 auto const name = wrapper.ptr->name;
23750 always_assert(m_data->finfos.emplace(name, wrapper.ptr.get()).second);
23751 m_data->allFInfos.emplace(name, wrapper.ptr.release());
23754 m_data->minfos.reserve(minfos.size());
23755 m_data->allMInfos.reserve(minfos.size());
23756 for (auto& wrapper : minfos) {
23757 auto const minfo = wrapper.ptr.get();
23758 auto cls = folly::get_default(m_data->classes, minfo->cls);
23759 always_assert(cls);
23760 always_assert(!cls->cinfo);
23762 auto const numMethods = cls->methods.size();
23763 always_assert(minfo->finfos.size() == numMethods);
23764 for (size_t i = 0; i < numMethods; ++i) {
23765 assignFInfoIdx(*cls->methods[i], *minfo->finfos[i]);
23767 auto const numClosures = cls->closures.size();
23768 always_assert(minfo->closureInvokes.size() == numClosures);
23769 for (size_t i = 0; i < numClosures; ++i) {
23770 auto& clo = cls->closures[i];
23771 auto& finfo = minfo->closureInvokes[i];
23772 assertx(clo->methods.size() == 1);
23773 assignFInfoIdx(*clo->methods[0], *finfo);
23776 auto const name = minfo->cls;
23777 always_assert(m_data->minfos.emplace(name, minfo).second);
23778 m_data->allMInfos.emplace(name, wrapper.ptr.release());
23781 for (auto& bc : clsBC) {
23782 auto cls = folly::get_default(m_data->classes, bc->cls);
23783 always_assert(cls);
23785 size_t idx = 0;
23786 for (auto& meth : cls->methods) {
23787 assertx(idx < bc->methodBCs.size());
23788 auto& methBC = bc->methodBCs[idx++];
23789 always_assert(methBC.name == meth->name);
23790 meth->rawBlocks = std::move(methBC.bc);
23792 for (auto& clo : cls->closures) {
23793 assertx(idx < bc->methodBCs.size());
23794 auto& methBC = bc->methodBCs[idx++];
23795 assertx(clo->methods.size() == 1);
23796 always_assert(methBC.name == clo->methods[0]->name);
23797 clo->methods[0]->rawBlocks = std::move(methBC.bc);
23799 assertx(idx == bc->methodBCs.size());
23801 for (auto& bc : funcBC) {
23802 auto func = folly::get_default(m_data->funcs, bc->name);
23803 always_assert(func);
23804 func->rawBlocks = std::move(bc->bc);
23807 std::vector<SString> depUnitNames;
23809 m_data->outUnitNames.reserve(units.size());
23810 m_data->units.reserve(units.size() + depUnits.size());
23811 m_data->allUnits.reserve(units.size() + depUnits.size());
23812 depUnitNames.reserve(depUnits.size());
23814 auto const addUnit = [&] (php::Unit* unit) {
23815 auto const isNative = is_native_unit(*unit);
23816 for (auto& cns : unit->constants) {
23817 always_assert(
23818 m_data->constants.try_emplace(cns->name, cns.get(), unit).second
23820 assertx(!m_data->badConstants.count(cns->name));
23821 if (isNative && type(cns->val) == KindOfUninit) {
23822 m_data->dynamicConstants.emplace(cns->name);
23825 for (auto& typeAlias : unit->typeAliases) {
23826 always_assert(
23827 m_data->typeAliases.try_emplace(
23828 typeAlias->name,
23829 typeAlias.get(),
23830 unit
23831 ).second
23833 assertx(!m_data->badClasses.count(typeAlias->name));
23835 always_assert(m_data->units.emplace(unit->filename, unit).second);
23837 for (auto& unit : units) {
23838 addUnit(unit.get());
23839 auto const name = unit->filename;
23840 m_data->outUnitNames.emplace_back(name);
23841 m_data->allUnits.emplace(name, std::move(unit));
23843 for (auto& unit : depUnits) {
23844 addUnit(unit.get());
23845 auto const name = unit->filename;
23846 m_data->allUnits.emplace(name, std::move(unit));
23847 depUnitNames.emplace_back(name);
23850 for (auto const& [_, cls] : m_data->allClasses) {
23851 if (!cls->cinfo) continue;
23852 always_assert(cls.get() == cls->cinfo->cls);
23854 for (auto const& [_, cinfo]: m_data->allCInfos) {
23855 always_assert(cinfo->cls);
23856 always_assert(cinfo.get() == cinfo->cls->cinfo);
23858 for (size_t i = 0, size = m_data->finfosByIdx.size(); i < size; ++i) {
23859 auto finfo = m_data->finfosByIdx[i];
23860 always_assert(finfo->func);
23861 always_assert(finfo->func->idx == i);
23864 ClassGraph::setAnalysisIndex(*m_data);
23866 // Use the BucketSet information in the input metadata to set up the
23867 // permissions.
23868 for (auto& [n, b] : meta.classBuckets) {
23869 if (auto const c = folly::get_default(m_data->classes, n)) {
23870 m_data->deps->restrict(c, std::move(b));
23871 } else if (m_data->badClasses.count(n)) {
23872 m_data->deps->restrict(DepTracker::Class { n }, std::move(b));
23875 for (auto& [n, b] : meta.funcBuckets) {
23876 if (auto const f = folly::get_default(m_data->funcs, n)) {
23877 m_data->deps->restrict(f, std::move(b));
23878 } else if (m_data->badFuncs.count(n)) {
23879 m_data->deps->restrict(DepTracker::Func { n }, std::move(b));
23882 for (auto& [n, b] : meta.unitBuckets) {
23883 if (auto const u = folly::get_default(m_data->units, n)) {
23884 m_data->deps->restrict(u, std::move(b));
23887 for (auto& [n, b] : meta.badConstantBuckets) {
23888 assertx(m_data->badConstants.count(n));
23889 m_data->deps->restrict(DepTracker::Constant { n }, std::move(b));
23892 initialize_worklist(
23893 meta,
23894 std::move(depClassNames),
23895 std::move(depFuncNames),
23896 std::move(depUnitNames)
23900 AnalysisIndex::~AnalysisIndex() {
23901 ClassGraph::clearAnalysisIndex();
23904 // Initialize the worklist with the items we know we must
23905 // process. Also add dependency information for the items which *may*
23906 // run.
23907 void AnalysisIndex::initialize_worklist(const AnalysisInput::Meta& meta,
23908 std::vector<SString> depClasses,
23909 std::vector<SString> depFuncs,
23910 std::vector<SString> depUnits) {
23911 auto const add = [&] (FuncClsUnit src, const AnalysisDeps& deps) {
23912 for (auto const [name, t] : deps.funcs) {
23913 m_data->deps->preadd(src, DepTracker::Func { name }, t);
23915 for (auto const [meth, t] : deps.methods) {
23916 m_data->deps->preadd(src, meth, t);
23918 for (auto const cns : deps.clsConstants) {
23919 m_data->deps->preadd(src, cns);
23921 for (auto const cls : deps.anyClsConstants) {
23922 m_data->deps->preadd(src, DepTracker::AnyClassConstant { cls });
23924 for (auto const cns : deps.constants) {
23925 m_data->deps->preadd(src, DepTracker::Constant { cns });
23929 for (auto const name : m_data->outClassNames) {
23930 auto const cls = m_data->classes.at(name);
23931 if (is_closure(*cls)) {
23932 assertx(!cls->closureContextCls);
23933 assertx(cls->closureDeclFunc);
23934 assertx(!meta.classDeps.count(name));
23935 continue;
23937 assertx(m_data->deps->bucketFor(cls).process->contains(m_data->bucketIdx));
23938 if (auto const deps = folly::get_ptr(meta.classDeps, name)) {
23939 add(cls, *deps);
23940 } else {
23941 m_data->worklist.schedule(cls);
23945 for (auto const name : depClasses) {
23946 if (auto const deps = folly::get_ptr(meta.classDeps, name)) {
23947 assertx(
23948 m_data->deps->bucketFor(m_data->classes.at(name))
23949 .process->contains(m_data->bucketIdx)
23951 add(m_data->classes.at(name), *deps);
23952 } else if (meta.processDepCls.count(name)) {
23953 assertx(
23954 m_data->deps->bucketFor(m_data->classes.at(name))
23955 .process->contains(m_data->bucketIdx)
23957 m_data->worklist.schedule(m_data->classes.at(name));
23961 for (auto const name : m_data->outFuncNames) {
23962 auto const func = m_data->funcs.at(name);
23963 assertx(m_data->deps->bucketFor(func).process->contains(m_data->bucketIdx));
23964 if (auto const deps = folly::get_ptr(meta.funcDeps, name)) {
23965 add(func, *deps);
23966 } else {
23967 m_data->worklist.schedule(func);
23971 for (auto const name : depFuncs) {
23972 if (auto const deps = folly::get_ptr(meta.funcDeps, name)) {
23973 assertx(
23974 m_data->deps->bucketFor(m_data->funcs.at(name))
23975 .process->contains(m_data->bucketIdx)
23977 add(m_data->funcs.at(name), *deps);
23978 } else if (meta.processDepFunc.count(name)) {
23979 assertx(
23980 m_data->deps->bucketFor(m_data->funcs.at(name))
23981 .process->contains(m_data->bucketIdx)
23983 m_data->worklist.schedule(m_data->funcs.at(name));
23987 for (auto const name : m_data->outUnitNames) {
23988 auto const unit = m_data->units.at(name);
23989 assertx(
23990 m_data->deps->bucketFor(unit).process->contains(m_data->bucketIdx)
23992 if (auto const deps = folly::get_ptr(meta.unitDeps, name)) {
23993 add(unit, *deps);
23994 } else {
23995 m_data->worklist.schedule(unit);
23999 for (auto const name : depUnits) {
24000 if (auto const deps = folly::get_ptr(meta.unitDeps, name)) {
24001 assertx(
24002 m_data->deps->bucketFor(m_data->units.at(name))
24003 .process->contains(m_data->bucketIdx)
24005 add(m_data->units.at(name), *deps);
24006 } else if (meta.processDepUnit.count(name)) {
24007 assertx(
24008 m_data->deps->bucketFor(m_data->units.at(name))
24009 .process->contains(m_data->bucketIdx)
24011 m_data->worklist.schedule(m_data->units.at(name));
24016 void AnalysisIndex::start() { ClassGraph::init(); }
24017 void AnalysisIndex::stop() { ClassGraph::destroy(); }
24019 void AnalysisIndex::push_context(const Context& ctx) {
24020 m_data->contexts.emplace_back(ctx);
24023 void AnalysisIndex::pop_context() {
24024 assertx(!m_data->contexts.empty());
24025 m_data->contexts.pop_back();
24028 bool AnalysisIndex::set_in_type_cns(bool b) {
24029 auto const was = m_data->inTypeCns;
24030 m_data->inTypeCns = b;
24031 return was;
24034 void AnalysisIndex::freeze() {
24035 FTRACE(2, "Freezing index...\n");
24036 assertx(!m_data->frozen);
24037 m_data->frozen = true;
24039 // We're going to do a final set of analysis to record dependencies,
24040 // so we must re-add the original set of items to the worklist.
24041 assertx(!m_data->worklist.next());
24042 for (auto const name : m_data->outClassNames) {
24043 auto const cls = m_data->classes.at(name);
24044 if (is_closure(*cls)) continue;
24045 m_data->deps->reset(cls);
24046 m_data->worklist.schedule(cls);
24048 for (auto const name : m_data->outFuncNames) {
24049 auto const func = m_data->funcs.at(name);
24050 m_data->deps->reset(func);
24051 m_data->worklist.schedule(func);
24053 for (auto const name : m_data->outUnitNames) {
24054 auto const unit = m_data->units.at(name);
24055 m_data->deps->reset(unit);
24056 m_data->worklist.schedule(unit);
24060 bool AnalysisIndex::frozen() const { return m_data->frozen; }
24062 const php::Unit& AnalysisIndex::lookup_func_unit(const php::Func& f) const {
24063 auto const it = m_data->units.find(f.unit);
24064 always_assert_flog(
24065 it != end(m_data->units),
24066 "Attempting to access missing unit {} for func {}",
24067 f.unit, func_fullname(f)
24069 return *it->second;
24072 const php::Unit& AnalysisIndex::lookup_class_unit(const php::Class& c) const {
24073 auto const it = m_data->units.find(c.unit);
24074 always_assert_flog(
24075 it != end(m_data->units),
24076 "Attempting to access missing unit {} for class {}",
24077 c.unit, c.name
24079 return *it->second;
24082 const php::Unit& AnalysisIndex::lookup_unit(SString n) const {
24083 auto const it = m_data->units.find(n);
24084 always_assert_flog(
24085 it != end(m_data->units),
24086 "Attempting to access missing unit {}",
24089 return *it->second;
24092 const php::Class*
24093 AnalysisIndex::lookup_const_class(const php::Const& cns) const {
24094 if (!m_data->deps->add(AnalysisDeps::Class { cns.cls })) return nullptr;
24095 return folly::get_default(m_data->classes, cns.cls);
24098 const php::Class* AnalysisIndex::lookup_class(SString name) const {
24099 return folly::get_default(m_data->classes, name);
24102 const php::Class&
24103 AnalysisIndex::lookup_closure_context(const php::Class& cls) const {
24104 always_assert_flog(
24105 !cls.closureContextCls,
24106 "AnalysisIndex does not yet support closure contexts (for {})",
24107 cls.name
24109 return cls;
24112 res::Func AnalysisIndex::resolve_func(SString n) const {
24113 n = normalizeNS(n);
24114 if (m_data->mode == Mode::Constants) {
24115 return res::Func { res::Func::FuncName { n } };
24118 if (m_data->deps->add(AnalysisDeps::Func { n })) {
24119 if (auto const finfo = folly::get_default(m_data->finfos, n)) {
24120 return res::Func { res::Func::Fun2 { finfo } };
24122 if (m_data->badFuncs.count(n)) {
24123 return res::Func { res::Func::MissingFunc { n } };
24126 return res::Func { res::Func::FuncName { n } };
24129 Optional<res::Class> AnalysisIndex::resolve_class(SString n) const {
24130 n = normalizeNS(n);
24131 if (!m_data->deps->add(AnalysisDeps::Class { n })) {
24132 // If we can't use information about the class, there's no point
24133 // in checking, and just use the unknown case.
24134 return res::Class::getOrCreate(n);
24136 if (auto const cinfo = folly::get_default(m_data->cinfos, n)) {
24137 return res::Class::get(*cinfo);
24139 if (m_data->typeAliases.count(n)) return std::nullopt;
24140 // A php::Class should always be accompanied by it's ClassInfo,
24141 // unless if it's uninstantiable. So, if we have a php::Class here,
24142 // we know it's uninstantiable.
24143 if (m_data->badClasses.count(n) || m_data->classes.count(n)) {
24144 return std::nullopt;
24146 return res::Class::getOrCreate(n);
24149 Optional<res::Class> AnalysisIndex::resolve_class(const php::Class& cls) const {
24150 if (!m_data->deps->add(AnalysisDeps::Class { cls.name })) {
24151 return res::Class::getOrCreate(cls.name);
24153 if (cls.cinfo) return res::Class::get(*cls.cinfo);
24154 return std::nullopt;
24157 res::Func AnalysisIndex::resolve_func_or_method(const php::Func& f) const {
24158 if (!m_data->deps->add(f)) {
24159 if (!f.cls) {
24160 return res::Func { res::Func::FuncName { f.name } };
24162 return res::Func { res::Func::MethodName { f.cls->name, f.name } };
24164 if (!f.cls) return res::Func { res::Func::Fun2 { &func_info(*m_data, f) } };
24165 return res::Func { res::Func::Method2 { &func_info(*m_data, f) } };
24168 Type AnalysisIndex::lookup_constant(SString name) const {
24169 if (!m_data->deps->add(AnalysisDeps::Constant { name })) return TInitCell;
24171 if (auto const p = folly::get_ptr(m_data->constants, name)) {
24172 auto const cns = p->first;
24173 if (type(cns->val) != KindOfUninit) return from_cell(cns->val);
24174 if (m_data->dynamicConstants.count(name)) return TInitCell;
24175 auto const fname = Constant::funcNameFromName(name);
24176 auto const fit = m_data->finfos.find(fname);
24177 // We might have the unit present by chance, but without an
24178 // explicit dependence on the constant, we might not have the init
24179 // func present.
24180 if (fit == end(m_data->finfos)) return TInitCell;
24181 return unctx(unserialize_type(fit->second->returnTy));
24183 return m_data->badConstants.count(name) ? TBottom : TInitCell;
24186 std::vector<std::pair<SString, ConstIndex>>
24187 AnalysisIndex::lookup_flattened_class_type_constants(
24188 const php::Class& cls
24189 ) const {
24190 std::vector<std::pair<SString, ConstIndex>> out;
24192 auto const cinfo = cls.cinfo;
24193 if (!cinfo) return out;
24195 out.reserve(cinfo->clsConstants.size());
24196 for (auto const& [name, idx] : cinfo->clsConstants) {
24197 if (idx.kind != ConstModifiers::Kind::Type) continue;
24198 out.emplace_back(name, idx.idx);
24200 std::sort(
24201 begin(out), end(out),
24202 [] (auto const& p1, auto const& p2) {
24203 return string_data_lt{}(p1.first, p2.first);
24206 return out;
24209 std::vector<std::pair<SString, ClsConstInfo>>
24210 AnalysisIndex::lookup_class_constants(const php::Class& cls) const {
24211 std::vector<std::pair<SString, ClsConstInfo>> out;
24212 out.reserve(cls.constants.size());
24213 for (auto const& cns : cls.constants) {
24214 if (cns.kind != ConstModifiers::Kind::Value) continue;
24215 if (!cns.val) continue;
24216 if (cns.val->m_type != KindOfUninit) {
24217 out.emplace_back(cns.name, ClsConstInfo{ from_cell(*cns.val), 0 });
24218 } else if (!cls.cinfo) {
24219 out.emplace_back(cns.name, ClsConstInfo{ TInitCell, 0 });
24220 } else {
24221 auto info = folly::get_default(
24222 cls.cinfo->clsConstantInfo,
24223 cns.name,
24224 ClsConstInfo{ TInitCell, 0 }
24226 info.type = unserialize_type(std::move(info.type));
24227 out.emplace_back(cns.name, std::move(info));
24230 return out;
24233 ClsConstLookupResult
24234 AnalysisIndex::lookup_class_constant(const Type& cls,
24235 const Type& name) const {
24236 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
24237 show(current_context(*m_data)), show(cls), show(name));
24238 Trace::Indent _;
24240 AnalysisIndexAdaptor adaptor{*this};
24241 InTypeCns _2{adaptor, false};
24243 using R = ClsConstLookupResult;
24245 auto const conservative = [] {
24246 ITRACE(4, "conservative\n");
24247 return R{ TInitCell, TriBool::Maybe, true };
24250 auto const notFound = [] {
24251 ITRACE(4, "not found\n");
24252 return R{ TBottom, TriBool::No, false };
24255 if (!is_specialized_cls(cls)) return conservative();
24257 // We could easily support the case where we don't know the constant
24258 // name, but know the class (like we do for properties), by unioning
24259 // together all possible constants. However it very rarely happens,
24260 // but when it does, the set of constants to union together can be
24261 // huge and it becomes very expensive.
24262 if (!is_specialized_string(name)) return conservative();
24263 auto const sname = sval_of(name);
24265 auto const& dcls = dcls_of(cls);
24266 if (dcls.isExact()) {
24267 auto const rcls = dcls.cls();
24268 auto const cinfo = rcls.cinfo2();
24269 if (!cinfo || !m_data->deps->add(AnalysisDeps::Class { rcls.name() })) {
24270 (void)m_data->deps->add(AnalysisDeps::AnyClassConstant { rcls.name() });
24271 return conservative();
24274 ITRACE(4, "{}:\n", cinfo->name);
24275 Trace::Indent _;
24277 auto const idxIt = cinfo->clsConstants.find(sname);
24278 if (idxIt == end(cinfo->clsConstants)) return notFound();
24279 auto const& idx = idxIt->second;
24281 assertx(!m_data->badClasses.count(idx.idx.cls));
24282 if (idx.kind != ConstModifiers::Kind::Value) return notFound();
24284 if (!m_data->deps->add(idx.idx)) return conservative();
24286 auto const cnsClsIt = m_data->classes.find(idx.idx.cls);
24287 if (cnsClsIt == end(m_data->classes)) return conservative();
24288 auto const& cnsCls = cnsClsIt->second;
24290 assertx(idx.idx.idx < cnsCls->constants.size());
24291 auto const& cns = cnsCls->constants[idx.idx.idx];
24292 assertx(cns.kind == ConstModifiers::Kind::Value);
24294 if (!cns.val.has_value()) return notFound();
24296 auto const r = [&] {
24297 if (type(*cns.val) != KindOfUninit) {
24298 // Fully resolved constant with a known value. We don't need
24299 // to register a dependency on the constant because the value
24300 // will never change.
24301 auto mightThrow = bool(cinfo->cls->attrs & AttrInternal);
24302 if (!mightThrow) {
24303 auto const& unit = lookup_class_unit(*cinfo->cls);
24304 auto const moduleName = unit.moduleName;
24305 auto const packageInfo = unit.packageInfo;
24306 if (auto const activeDeployment = packageInfo.getActiveDeployment()) {
24307 if (!packageInfo.moduleInDeployment(
24308 moduleName, *activeDeployment, DeployKind::Hard)) {
24309 mightThrow = true;
24313 return R{ from_cell(*cns.val), TriBool::Yes, mightThrow };
24316 ITRACE(4, "(dynamic)\n");
24317 if (!cnsCls->cinfo) return conservative();
24318 auto const info =
24319 folly::get_ptr(cnsCls->cinfo->clsConstantInfo, cns.name);
24320 return R{
24321 info ? unserialize_type(info->type) : TInitCell,
24322 TriBool::Yes,
24323 true
24325 }();
24326 ITRACE(4, "-> {}\n", show(r));
24327 return r;
24330 // Subclasses not yet implemented
24331 return conservative();
24334 ClsTypeConstLookupResult
24335 AnalysisIndex::lookup_class_type_constant(
24336 const Type& cls,
24337 const Type& name,
24338 const Index::ClsTypeConstLookupResolver& resolver
24339 ) const {
24340 ITRACE(4, "lookup_class_type_constant: ({}) {}::{}\n",
24341 show(current_context(*m_data)), show(cls), show(name));
24342 Trace::Indent _;
24344 using R = ClsTypeConstLookupResult;
24346 auto const conservative = [&] (SString n = nullptr) {
24347 ITRACE(4, "conservative\n");
24348 return R{
24349 TypeStructureResolution { TSDictN, true },
24350 TriBool::Maybe,
24351 TriBool::Maybe
24355 auto const notFound = [] {
24356 ITRACE(4, "not found\n");
24357 return R {
24358 TypeStructureResolution { TBottom, false },
24359 TriBool::No,
24360 TriBool::No
24364 // Unlike lookup_class_constant, we distinguish abstract from
24365 // not-found, as the runtime sometimes treats them differently.
24366 auto const abstract = [] {
24367 ITRACE(4, "abstract\n");
24368 return R {
24369 TypeStructureResolution { TBottom, false },
24370 TriBool::No,
24371 TriBool::Yes
24375 if (!is_specialized_cls(cls)) return conservative();
24377 // As in lookup_class_constant, we could handle this, but it's not
24378 // worth it.
24379 if (!is_specialized_string(name)) return conservative();
24380 auto const sname = sval_of(name);
24382 auto const& dcls = dcls_of(cls);
24383 if (dcls.isExact()) {
24384 auto const rcls = dcls.cls();
24385 auto const cinfo = rcls.cinfo2();
24386 if (!cinfo || !m_data->deps->add(AnalysisDeps::Class { rcls.name() })) {
24387 (void)m_data->deps->add(AnalysisDeps::AnyClassConstant { rcls.name() });
24388 return conservative(rcls.name());
24391 ITRACE(4, "{}:\n", cinfo->name);
24392 Trace::Indent _;
24394 auto const idxIt = cinfo->clsConstants.find(sname);
24395 if (idxIt == end(cinfo->clsConstants)) return notFound();
24396 auto const& idx = idxIt->second;
24398 assertx(!m_data->badClasses.count(idx.idx.cls));
24399 if (idx.kind != ConstModifiers::Kind::Type) return notFound();
24401 if (!m_data->deps->add(idx.idx)) return conservative(rcls.name());
24403 auto const cnsClsIt = m_data->classes.find(idx.idx.cls);
24404 if (cnsClsIt == end(m_data->classes)) return conservative(rcls.name());
24405 auto const& cnsCls = cnsClsIt->second;
24407 assertx(idx.idx.idx < cnsCls->constants.size());
24408 auto const& cns = cnsCls->constants[idx.idx.idx];
24409 assertx(cns.kind == ConstModifiers::Kind::Type);
24410 if (!cns.val.has_value()) return abstract();
24411 assertx(tvIsDict(*cns.val));
24413 ITRACE(4, "({}) {}\n", cns.cls, show(dict_val(val(*cns.val).parr)));
24415 // If we've been given a resolver, use it. Otherwise resolve it in
24416 // the normal way.
24417 auto resolved = resolver
24418 ? resolver(cns, *cinfo->cls)
24419 : resolve_type_structure(
24420 AnalysisIndexAdaptor { *this }, cns, *cinfo->cls
24423 // The result of resolve_type_structure isn't, in general,
24424 // static. However a type-constant will always be, so force that
24425 // here.
24426 assertx(resolved.type.is(BBottom) || resolved.type.couldBe(BUnc));
24427 resolved.type &= TUnc;
24428 auto const r = R{
24429 std::move(resolved),
24430 TriBool::Yes,
24431 TriBool::No
24433 ITRACE(4, "-> {}\n", show(r));
24434 return r;
24437 // Subclasses not yet implemented
24438 return conservative();
24441 ClsTypeConstLookupResult
24442 AnalysisIndex::lookup_class_type_constant(const php::Class& ctx,
24443 SString name,
24444 ConstIndex idx) const {
24445 ITRACE(4, "lookup_class_type_constant: ({}) {}::{} (from {})\n",
24446 show(current_context(*m_data)),
24447 ctx.name, name,
24448 show(idx, AnalysisIndexAdaptor{ *this }));
24449 Trace::Indent _;
24451 assertx(!m_data->badClasses.count(idx.cls));
24453 using R = ClsTypeConstLookupResult;
24455 auto const conservative = [] {
24456 ITRACE(4, "conservative\n");
24457 return R {
24458 TypeStructureResolution { TSDictN, true },
24459 TriBool::Maybe,
24460 TriBool::Maybe
24464 auto const notFound = [] {
24465 ITRACE(4, "not found\n");
24466 return R {
24467 TypeStructureResolution { TBottom, false },
24468 TriBool::No,
24469 TriBool::No
24473 // Unlike lookup_class_constant, we distinguish abstract from
24474 // not-found, as the runtime sometimes treats them differently.
24475 auto const abstract = [] {
24476 ITRACE(4, "abstract\n");
24477 return R {
24478 TypeStructureResolution { TBottom, false },
24479 TriBool::No,
24480 TriBool::Yes
24484 if (!m_data->deps->add(idx)) return conservative();
24485 auto const cinfo = folly::get_default(m_data->cinfos, idx.cls);
24486 if (!cinfo) return conservative();
24488 assertx(idx.idx < cinfo->cls->constants.size());
24489 auto const& cns = cinfo->cls->constants[idx.idx];
24490 if (cns.kind != ConstModifiers::Kind::Type) return notFound();
24491 if (!cns.val.has_value()) return abstract();
24493 assertx(tvIsDict(*cns.val));
24495 ITRACE(4, "({}) {}\n", cns.cls, show(dict_val(val(*cns.val).parr)));
24497 auto resolved = resolve_type_structure(
24498 AnalysisIndexAdaptor { *this }, cns, ctx
24501 // The result of resolve_type_structure isn't, in general,
24502 // static. However a type-constant will always be, so force that
24503 // here.
24504 assertx(resolved.type.is(BBottom) || resolved.type.couldBe(BUnc));
24505 resolved.type &= TUnc;
24506 auto const r = R{
24507 std::move(resolved),
24508 TriBool::Yes,
24509 TriBool::No
24511 ITRACE(4, "-> {}\n", show(r));
24512 return r;
24515 PropState AnalysisIndex::lookup_private_props(const php::Class& cls) const {
24516 // Private property tracking not yet implemented, so be
24517 // conservative.
24518 return make_unknown_propstate(
24519 AnalysisIndexAdaptor { *this },
24520 cls,
24521 [&] (const php::Prop& prop) {
24522 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
24527 PropState AnalysisIndex::lookup_private_statics(const php::Class& cls) const {
24528 // Private static property tracking not yet implemented, so be
24529 // conservative.
24530 return make_unknown_propstate(
24531 AnalysisIndexAdaptor { *this },
24532 cls,
24533 [&] (const php::Prop& prop) {
24534 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
24539 Index::ReturnType AnalysisIndex::lookup_return_type(MethodsInfo* methods,
24540 res::Func rfunc) const {
24541 using R = Index::ReturnType;
24543 auto const meth = [&] (const FuncInfo2& finfo) {
24544 if (methods) {
24545 if (auto ret = methods->lookupReturnType(*finfo.func)) {
24546 return R{ unctx(std::move(ret->t)), ret->effectFree };
24549 if (!m_data->deps->add(*finfo.func, AnalysisDeps::Type::RetType)) {
24550 return R{ TInitCell, false };
24552 return R{
24553 unctx(unserialize_type(finfo.returnTy)),
24554 finfo.effectFree
24558 return match<R>(
24559 rfunc.val,
24560 [&] (res::Func::FuncName f) {
24561 (void)m_data->deps->add(
24562 AnalysisDeps::Func { f.name },
24563 AnalysisDeps::Type::RetType
24565 return R{ TInitCell, false };
24567 [&] (res::Func::MethodName m) {
24568 // If we know the name of the class, we can register a
24569 // dependency on it. If not, nothing we can do.
24570 if (m.cls) (void)m_data->deps->add(AnalysisDeps::Class { m.cls });
24571 return R{ TInitCell, false };
24573 [&] (res::Func::Fun) -> R { always_assert(false); },
24574 [&] (res::Func::Method) -> R { always_assert(false); },
24575 [&] (res::Func::MethodFamily) -> R { always_assert(false); },
24576 [&] (res::Func::MethodOrMissing) -> R { always_assert(false); },
24577 [&] (res::Func::MissingFunc) { return R{ TBottom, false }; },
24578 [&] (res::Func::MissingMethod) { return R{ TBottom, false }; },
24579 [&] (const res::Func::Isect&) -> R { always_assert(false); },
24580 [&] (res::Func::Fun2 f) {
24581 if (!m_data->deps->add(*f.finfo->func, AnalysisDeps::Type::RetType)) {
24582 return R{ TInitCell, false };
24584 return R{
24585 unctx(unserialize_type(f.finfo->returnTy)),
24586 f.finfo->effectFree
24589 [&] (res::Func::Method2 m) { return meth(*m.finfo); },
24590 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
24591 [&] (res::Func::MethodOrMissing2 m) { return meth(*m.finfo); },
24592 [&] (const res::Func::Isect2&) -> R { always_assert(false); }
24596 Index::ReturnType
24597 AnalysisIndex::lookup_return_type(MethodsInfo* methods,
24598 const CompactVector<Type>& args,
24599 const Type& context,
24600 res::Func rfunc) const {
24601 using R = Index::ReturnType;
24603 auto ty = lookup_return_type(methods, rfunc);
24605 auto const contextual = [&] (const FuncInfo2& finfo) {
24606 return context_sensitive_return_type(
24607 *m_data,
24608 { finfo.func, args, context },
24609 std::move(ty)
24613 return match<R>(
24614 rfunc.val,
24615 [&] (res::Func::FuncName) { return ty; },
24616 [&] (res::Func::MethodName) { return ty; },
24617 [&] (res::Func::Fun) -> R { always_assert(false); },
24618 [&] (res::Func::Method) -> R { always_assert(false); },
24619 [&] (res::Func::MethodFamily) -> R { always_assert(false); },
24620 [&] (res::Func::MethodOrMissing) -> R { always_assert(false); },
24621 [&] (res::Func::MissingFunc) { return R{ TBottom, false }; },
24622 [&] (res::Func::MissingMethod) { return R{ TBottom, false }; },
24623 [&] (const res::Func::Isect&) -> R { always_assert(false); },
24624 [&] (res::Func::Fun2 f) { return contextual(*f.finfo); },
24625 [&] (res::Func::Method2 m) { return contextual(*m.finfo); },
24626 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
24627 [&] (res::Func::MethodOrMissing2 m) { return contextual(*m.finfo); },
24628 [&] (const res::Func::Isect2&) -> R { always_assert(false); }
24632 Index::ReturnType
24633 AnalysisIndex::lookup_foldable_return_type(const CallContext& calleeCtx) const {
24634 constexpr size_t maxNestingLevel = 2;
24636 using R = Index::ReturnType;
24638 auto const& func = *calleeCtx.callee;
24640 if (m_data->mode == Mode::Constants) {
24641 ITRACE_MOD(
24642 Trace::hhbbc, 4,
24643 "Skipping inline interp of {} because analyzing constants\n",
24644 func_fullname(func)
24646 return R{ TInitCell, false };
24649 auto const ctxType =
24650 adjust_closure_context(AnalysisIndexAdaptor { *this }, calleeCtx);
24652 // Don't fold functions when staticness mismatches
24653 if (!func.isClosureBody) {
24654 if ((func.attrs & AttrStatic) && ctxType.couldBe(TObj)) {
24655 return R{ TInitCell, false };
24657 if (!(func.attrs & AttrStatic) && ctxType.couldBe(TCls)) {
24658 return R{ TInitCell, false };
24662 auto const& finfo = func_info(*m_data, func);
24663 // No need to call unserialize_type here. If it's a scalar, there's
24664 // nothing to unserialize anyways.
24665 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
24666 return R{ finfo.returnTy, finfo.effectFree };
24669 auto const& caller = *context_for_deps(*m_data).func;
24671 if (!m_data->deps->add(
24672 func,
24673 AnalysisDeps::Type::ScalarRetType |
24674 AnalysisDeps::Type::Bytecode
24675 )) {
24676 return R{ TInitCell, false };
24678 if (m_data->foldableInterpNestingLevel > maxNestingLevel) {
24679 return R{ TInitCell, false };
24682 if (!func.rawBlocks) {
24683 ITRACE_MOD(
24684 Trace::hhbbc, 4,
24685 "Skipping inline interp of {} because bytecode not present\n",
24686 func_fullname(func)
24688 return R{ TInitCell, false };
24691 auto const contextualRet = [&] () -> Optional<Type> {
24692 ++m_data->foldableInterpNestingLevel;
24693 SCOPE_EXIT { --m_data->foldableInterpNestingLevel; };
24695 auto const wf = php::WideFunc::cns(&func);
24696 auto const fa = analyze_func_inline(
24697 AnalysisIndexAdaptor { *this },
24698 AnalysisContext {
24699 func.unit,
24701 func.cls,
24702 &context_for_deps(*m_data)
24704 ctxType,
24705 calleeCtx.args,
24706 nullptr,
24707 CollectionOpts::EffectFreeOnly
24709 if (!fa.effectFree) return std::nullopt;
24710 return fa.inferredReturn;
24711 }();
24713 if (!contextualRet) {
24714 ITRACE_MOD(
24715 Trace::hhbbc, 4,
24716 "Foldable inline analysis failed due to possible side-effects\n"
24718 return R{ TInitCell, false };
24721 ITRACE_MOD(
24722 Trace::hhbbc, 4,
24723 "Foldable return type: {}\n",
24724 show(*contextualRet)
24727 auto const error_context = [&] {
24728 using namespace folly::gen;
24729 return folly::sformat(
24730 "{} calling {} (context: {}, args: {})",
24731 func_fullname(caller),
24732 func_fullname(func),
24733 show(calleeCtx.context),
24734 from(calleeCtx.args)
24735 | map([] (const Type& t) { return show(t); })
24736 | unsplit<std::string>(",")
24740 auto const insensitive = unserialize_type(finfo.returnTy);
24741 always_assert_flog(
24742 contextualRet->subtypeOf(insensitive),
24743 "Context sensitive return type for {} is {} "
24744 "which not at least as refined as context insensitive "
24745 "return type {}\n",
24746 error_context(),
24747 show(*contextualRet),
24748 show(insensitive)
24750 if (!is_scalar(*contextualRet)) return R{ TInitCell, false };
24752 return R{ *contextualRet, true };
24755 std::pair<Index::ReturnType, size_t>
24756 AnalysisIndex::lookup_return_type_raw(const php::Func& f) const {
24757 auto const& finfo = func_info(*m_data, f);
24758 return std::make_pair(
24759 Index::ReturnType{
24760 unserialize_type(finfo.returnTy),
24761 finfo.effectFree
24763 finfo.returnRefinements
24767 bool AnalysisIndex::func_depends_on_arg(const php::Func& func,
24768 size_t arg) const {
24769 if (!m_data->deps->add(func, AnalysisDeps::Type::UnusedParams)) return true;
24770 auto const& finfo = func_info(*m_data, func);
24771 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
24775 * Given a DCls, return the most specific res::Func for that DCls. For
24776 * intersections, this will call process/general on every component of
24777 * the intersection and combine the results. process is called to
24778 * obtain a res::Func from a ClassInfo. If a ClassInfo isn't
24779 * available, general will be called instead.
24781 template <typename P, typename G>
24782 res::Func AnalysisIndex::rfunc_from_dcls(const DCls& dcls,
24783 SString name,
24784 const P& process,
24785 const G& general) const {
24786 using Func = res::Func;
24787 using Class = res::Class;
24790 * Combine together multiple res::Funcs together. Since the DCls
24791 * represents a class which is a subtype of every ClassInfo in the
24792 * list, every res::Func we get is true.
24794 * The relevant res::Func types in order from most general to more
24795 * specific are:
24797 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
24799 * Since every res::Func in the intersection is true, we take the
24800 * res::Func which is most specific. Two different res::Funcs cannot
24801 * be contradict. For example, we shouldn't get a Method and a
24802 * Missing since one implies there's no func and the other implies
24803 * one specific func. Or two different res::Funcs shouldn't resolve
24804 * to two different methods.
24806 auto missing = TriBool::Maybe;
24807 Func::Isect2 isect;
24808 const php::Func* singleMethod = nullptr;
24810 auto const onFunc = [&] (Func func) {
24811 match<void>(
24812 func.val,
24813 [&] (Func::MethodName) {},
24814 [&] (Func::Method) { always_assert(false); },
24815 [&] (Func::MethodFamily) { always_assert(false); },
24816 [&] (Func::MethodOrMissing) { always_assert(false); },
24817 [&] (Func::MissingMethod) {
24818 assertx(missing != TriBool::No);
24819 singleMethod = nullptr;
24820 isect.families.clear();
24821 missing = TriBool::Yes;
24823 [&] (Func::FuncName) { always_assert(false); },
24824 [&] (Func::Fun) { always_assert(false); },
24825 [&] (Func::Fun2) { always_assert(false); },
24826 [&] (Func::Method2 m) {
24827 assertx(IMPLIES(singleMethod, singleMethod == m.finfo->func));
24828 assertx(IMPLIES(singleMethod, isect.families.empty()));
24829 assertx(missing != TriBool::Yes);
24830 if (!singleMethod) {
24831 singleMethod = m.finfo->func;
24832 isect.families.clear();
24834 missing = TriBool::No;
24836 [&] (Func::MethodFamily2) { always_assert(false); },
24837 [&] (Func::MethodOrMissing2 m) {
24838 assertx(IMPLIES(singleMethod, singleMethod == m.finfo->func));
24839 assertx(IMPLIES(singleMethod, isect.families.empty()));
24840 if (missing == TriBool::Yes) {
24841 assertx(!singleMethod);
24842 assertx(isect.families.empty());
24843 return;
24845 if (!singleMethod) {
24846 singleMethod = m.finfo->func;
24847 isect.families.clear();
24850 [&] (Func::MissingFunc) { always_assert(false); },
24851 [&] (const Func::Isect&) { always_assert(false); },
24852 [&] (const Func::Isect2&) { always_assert(false); }
24856 auto const onClass = [&] (Class cls, bool isExact) {
24857 auto const g = cls.graph();
24858 if (!g.ensureCInfo()) {
24859 onFunc(general(dcls.containsNonRegular()));
24860 return;
24863 if (auto const cinfo = g.cinfo2()) {
24864 onFunc(process(cinfo, isExact, dcls.containsNonRegular()));
24865 } else {
24866 // The class doesn't have a ClassInfo present, so we cannot call
24867 // process. We can, however, look at any parents that do have a
24868 // ClassInfo. This won't result in as good results, but it
24869 // preserves monotonicity.
24870 onFunc(general(dcls.containsNonRegular()));
24871 if (g.isMissing()) return;
24873 if (!dcls.containsNonRegular() &&
24874 !g.mightBeRegular() &&
24875 g.hasCompleteChildren()) {
24876 if (isExact) {
24877 onFunc(
24878 Func { Func::MissingMethod { dcls.smallestCls().name(), name } }
24880 return;
24883 hphp_fast_set<ClassGraph, ClassGraphHasher> commonParents;
24884 auto first = true;
24885 for (auto const c : g.children()) {
24886 assertx(!c.isMissing());
24887 if (!c.mightBeRegular()) continue;
24889 hphp_fast_set<ClassGraph, ClassGraphHasher> newCommon;
24890 c.walkParents(
24891 [&] (ClassGraph p) {
24892 if (first || commonParents.count(p)) {
24893 newCommon.emplace(p);
24895 return true;
24898 first = false;
24899 commonParents = std::move(newCommon);
24902 if (first) {
24903 onFunc(
24904 Func { Func::MissingMethod { dcls.smallestCls().name(), name } }
24906 return;
24909 assertx(!commonParents.empty());
24910 for (auto const p : commonParents) {
24911 if (!p.ensureCInfo()) continue;
24912 if (auto const cinfo = p.cinfo2()) {
24913 onFunc(process(cinfo, false, false));
24916 return;
24919 g.walkParents(
24920 [&] (ClassGraph p) {
24921 if (!p.ensureCInfo()) return true;
24922 if (auto const cinfo = p.cinfo2()) {
24923 onFunc(process(cinfo, false, dcls.containsNonRegular()));
24924 return false;
24926 return true;
24932 if (dcls.isExact() || dcls.isSub()) {
24933 // If this isn't an intersection, there's only one class to
24934 // process and we're done.
24935 onClass(dcls.cls(), dcls.isExact());
24936 } else if (dcls.isIsect()) {
24937 for (auto const c : dcls.isect()) onClass(c, false);
24938 } else {
24939 assertx(dcls.isIsectAndExact());
24940 auto const [e, i] = dcls.isectAndExact();
24941 onClass(e, true);
24942 for (auto const c : *i) onClass(c, false);
24945 // If we got a method, that always wins. Again, every res::Func is
24946 // true, and method is more specific than a FuncFamily, so it is
24947 // preferred.
24948 if (singleMethod) {
24949 assertx(missing != TriBool::Yes);
24950 // If missing is Maybe, then *every* resolution was to a
24951 // MethodName or MethodOrMissing, so include that fact here by
24952 // using MethodOrMissing.
24953 if (missing == TriBool::Maybe) {
24954 return Func {
24955 Func::MethodOrMissing2 { &func_info(*m_data, *singleMethod) }
24958 return Func { Func::Method2 { &func_info(*m_data, *singleMethod) } };
24960 // We only got unresolved classes. If missing is TriBool::Yes, the
24961 // function doesn't exist. Otherwise be pessimistic.
24962 if (isect.families.empty()) {
24963 if (missing == TriBool::Yes) {
24964 return Func { Func::MissingMethod { dcls.smallestCls().name(), name } };
24966 assertx(missing == TriBool::Maybe);
24967 return general(dcls.containsNonRegular());
24969 // Isect case. Isects always might contain missing funcs.
24970 assertx(missing == TriBool::Maybe);
24972 // We could add a FuncFamily multiple times, so remove duplicates.
24973 std::sort(begin(isect.families), end(isect.families));
24974 isect.families.erase(
24975 std::unique(begin(isect.families), end(isect.families)),
24976 end(isect.families)
24978 // If everything simplifies down to a single FuncFamily, just use
24979 // that.
24980 if (isect.families.size() == 1) {
24981 return Func {
24982 Func::MethodFamily2 { isect.families[0], isect.regularOnly }
24985 return Func { std::move(isect) };
24988 res::Func AnalysisIndex::resolve_method(const Type& thisType,
24989 SString name) const {
24990 assertx(thisType.subtypeOf(BCls) || thisType.subtypeOf(BObj));
24992 using Func = res::Func;
24994 auto const general = [&] (SString maybeCls, bool) {
24995 assertx(name != s_construct.get());
24996 return Func { Func::MethodName { maybeCls, name } };
24999 if (m_data->mode == Mode::Constants) return general(nullptr, true);
25001 auto const process = [&] (ClassInfo2* cinfo,
25002 bool isExact,
25003 bool includeNonRegular) {
25004 assertx(name != s_construct.get());
25006 auto const meth = folly::get_ptr(cinfo->methods, name);
25007 if (!meth) {
25008 // We don't store metadata for special methods, so be pessimistic
25009 // (the lack of a method entry does not mean the call might fail
25010 // at runtme).
25011 if (is_special_method_name(name)) {
25012 return Func { Func::MethodName { cinfo->name, name } };
25014 // We're only considering this class, not it's subclasses. Since
25015 // it doesn't exist here, the resolution will always fail.
25016 if (isExact) {
25017 return Func { Func::MissingMethod { cinfo->name, name } };
25019 // The method isn't present on this class, but it might be in the
25020 // subclasses. In most cases try a general lookup to get a
25021 // slightly better type than nothing.
25022 return general(cinfo->name, includeNonRegular);
25025 if (!m_data->deps->add(meth->meth())) {
25026 return general(cinfo->name, includeNonRegular);
25028 auto const func = func_from_meth_ref(*m_data, meth->meth());
25029 if (!func) return general(cinfo->name, includeNonRegular);
25031 // We don't store method family information about special methods
25032 // and they have special inheritance semantics.
25033 if (is_special_method_name(name)) {
25034 // If we know the class exactly, we can use ftarget.
25035 if (isExact) {
25036 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25038 // The method isn't overwritten, but they don't inherit, so it
25039 // could be missing.
25040 if (meth->attrs & AttrNoOverride) {
25041 return Func { Func::MethodOrMissing2 { &func_info(*m_data, *func) } };
25043 // Otherwise be pessimistic.
25044 return Func { Func::MethodName { cinfo->name, name } };
25047 // Private method handling: Private methods have special lookup
25048 // rules. If we're in the context of a particular class, and that
25049 // class defines a private method, an instance of the class will
25050 // always call that private method (even if overridden) in that
25051 // context.
25052 assertx(cinfo->cls);
25053 auto const& ctx = current_context(*m_data);
25054 if (ctx.cls == cinfo->cls) {
25055 // The context matches the current class. If we've looked up a
25056 // private method (defined on this class), then that's what
25057 // we'll call.
25058 if ((meth->attrs & AttrPrivate) && meth->topLevel()) {
25059 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25061 } else if ((meth->attrs & AttrPrivate) || meth->hasPrivateAncestor()) {
25062 // Otherwise the context doesn't match the current class. If the
25063 // looked up method is private, or has a private ancestor,
25064 // there's a chance we'll call that method (or
25065 // ancestor). Otherwise there's no private method in the
25066 // inheritance tree we'll call.
25067 auto conservative = false;
25068 auto const ancestor = [&] () -> const php::Func* {
25069 if (!ctx.cls) return nullptr;
25070 if (!m_data->deps->add(AnalysisDeps::Class { ctx.cls->name })) {
25071 conservative = true;
25072 return nullptr;
25074 // Look up the ClassInfo corresponding to the context.
25075 auto const ctxCInfo = ctx.cls->cinfo;
25076 if (!ctxCInfo) return nullptr;
25077 // Is this context a parent of our class?
25078 if (!cinfo->classGraph.isChildOf(ctxCInfo->classGraph)) {
25079 return nullptr;
25081 // It is. See if it defines a private method.
25082 auto const ctxMeth = folly::get_ptr(ctxCInfo->methods, name);
25083 if (!ctxMeth) return nullptr;
25084 // If it defines a private method, use it.
25085 if ((ctxMeth->attrs & AttrPrivate) && ctxMeth->topLevel()) {
25086 if (!m_data->deps->add(ctxMeth->meth())) {
25087 conservative = true;
25088 return nullptr;
25090 auto const ctxFunc = func_from_meth_ref(*m_data, ctxMeth->meth());
25091 if (!ctxFunc) conservative = true;
25092 return ctxFunc;
25094 // Otherwise do normal lookup.
25095 return nullptr;
25096 }();
25097 if (ancestor) {
25098 return Func { Func::Method2 { &func_info(*m_data, *ancestor) } };
25099 } else if (conservative) {
25100 return Func { Func::MethodName { cinfo->name, name } };
25104 // If we're only including regular subclasses, and this class
25105 // itself isn't regular, the result may not necessarily include
25106 // func.
25107 if (!includeNonRegular && !is_regular_class(*cinfo->cls)) {
25108 // We're not including this base class. If we're exactly this
25109 // class, there's no method at all. It will always be missing.
25110 if (isExact) {
25111 return Func { Func::MissingMethod { cinfo->name, name } };
25113 if (meth->noOverrideRegular()) {
25114 // The method isn't overridden in a subclass, but we can't use
25115 // the base class either. This leaves two cases. Either the
25116 // method isn't overridden because there are no regular
25117 // subclasses (in which case there's no resolution at all), or
25118 // because there's regular subclasses, but they use the same
25119 // method (in which case the result is just func).
25120 if (!cinfo->classGraph.mightHaveRegularSubclass()) {
25121 return Func { Func::MissingMethod { cinfo->name, name } };
25123 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25125 } else if (isExact ||
25126 meth->attrs & AttrNoOverride ||
25127 (!includeNonRegular && meth->noOverrideRegular())) {
25128 // Either we want all classes, or the base class is regular. If
25129 // the method isn't overridden we know it must be just func (the
25130 // override bits include it being missing in a subclass, so we
25131 // know it cannot be missing either).
25132 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25135 // Be pessimistic for the rest of cases
25136 return general(cinfo->name, includeNonRegular);
25139 auto const isClass = thisType.subtypeOf(BCls);
25140 if (name == s_construct.get()) {
25141 if (isClass) {
25142 return Func { Func::MethodName { nullptr, s_construct.get() } };
25144 return resolve_ctor(thisType);
25147 if (isClass) {
25148 if (!is_specialized_cls(thisType)) return general(nullptr, true);
25149 } else if (!is_specialized_obj(thisType)) {
25150 return general(nullptr, false);
25153 auto const& dcls = isClass ? dcls_of(thisType) : dobj_of(thisType);
25154 return rfunc_from_dcls(
25155 dcls,
25156 name,
25157 process,
25158 [&] (bool i) { return general(dcls.smallestCls().name(), i); }
25162 res::Func AnalysisIndex::resolve_ctor(const Type& obj) const {
25163 assertx(obj.subtypeOf(BObj));
25165 using Func = res::Func;
25167 if (m_data->mode == Mode::Constants) {
25168 return Func { Func::MethodName { nullptr, s_construct.get() } };
25171 // Can't say anything useful if we don't know the object type.
25172 if (!is_specialized_obj(obj)) {
25173 return Func { Func::MethodName { nullptr, s_construct.get() } };
25176 auto const& dcls = dobj_of(obj);
25177 return rfunc_from_dcls(
25178 dcls,
25179 s_construct.get(),
25180 [&] (ClassInfo2* cinfo, bool isExact, bool includeNonRegular) {
25181 // We're dealing with an object here, which never uses
25182 // non-regular classes.
25183 assertx(!includeNonRegular);
25185 // See if this class has a ctor.
25186 auto const meth = folly::get_ptr(cinfo->methods, s_construct.get());
25187 if (!meth) {
25188 // There's no ctor on this class. This doesn't mean the ctor
25189 // won't exist at runtime, it might get the default ctor, so
25190 // we have to be conservative.
25191 return Func { Func::MethodName { cinfo->name, s_construct.get() } };
25194 if (!m_data->deps->add(meth->meth())) {
25195 return Func {
25196 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
25200 // We have a ctor, but it might be overridden in a subclass.
25201 assertx(!(meth->attrs & AttrStatic));
25202 auto const func = func_from_meth_ref(*m_data, meth->meth());
25203 if (!func) {
25204 // Relevant function doesn't exist on the AnalysisIndex. Be
25205 // conservative.
25206 return Func { Func::MethodName { cinfo->name, s_construct.get() } };
25208 assertx(!(func->attrs & AttrStatic));
25210 // If this class is known exactly, or we know nothing overrides
25211 // this ctor, we know this ctor is precisely it.
25212 if (isExact || meth->noOverrideRegular()) {
25213 // If this class isn't regular, and doesn't have any regular
25214 // subclasses (or if it's exact), this resolution will always
25215 // fail.
25216 if (!is_regular_class(*cinfo->cls) &&
25217 (isExact || !cinfo->classGraph.mightHaveRegularSubclass())) {
25218 return Func {
25219 Func::MissingMethod { cinfo->name, s_construct.get() }
25222 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25225 // Be pessimistic for the rest of cases
25226 return Func {
25227 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
25230 [&] (bool includeNonRegular) {
25231 assertx(!includeNonRegular);
25232 return Func {
25233 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
25239 std::pair<const php::TypeAlias*, bool>
25240 AnalysisIndex::lookup_type_alias(SString name) const {
25241 if (!m_data->deps->add(AnalysisDeps::Class { name })) {
25242 return std::make_pair(nullptr, true);
25244 if (m_data->classes.count(name)) return std::make_pair(nullptr, false);
25245 if (auto const ta = folly::get_ptr(m_data->typeAliases, name)) {
25246 return std::make_pair(ta->first, true);
25248 return std::make_pair(nullptr, !m_data->badClasses.count(name));
25251 Index::ClassOrTypeAlias
25252 AnalysisIndex::lookup_class_or_type_alias(SString n) const {
25253 n = normalizeNS(n);
25254 if (!m_data->deps->add(AnalysisDeps::Class { n })) {
25255 return Index::ClassOrTypeAlias{nullptr, nullptr, true};
25257 if (auto const cls = folly::get_default(m_data->classes, n)) {
25258 return Index::ClassOrTypeAlias{cls, nullptr, true};
25260 if (auto const ta = folly::get_ptr(m_data->typeAliases, n)) {
25261 return Index::ClassOrTypeAlias{nullptr, ta->first, true};
25263 return Index::ClassOrTypeAlias{
25264 nullptr,
25265 nullptr,
25266 !m_data->badClasses.count(n)
25270 PropMergeResult AnalysisIndex::merge_static_type(
25271 PublicSPropMutations& publicMutations,
25272 PropertiesInfo& privateProps,
25273 const Type& cls,
25274 const Type& name,
25275 const Type& val,
25276 bool checkUB,
25277 bool ignoreConst,
25278 bool mustBeReadOnly) const {
25279 // Not yet implemented
25280 return PropMergeResult{ TInitCell, TriBool::Maybe };
25283 void AnalysisIndex::refine_constants(const FuncAnalysisResult& fa) {
25284 auto const& func = *fa.ctx.func;
25285 if (func.cls) return;
25287 auto const name = Constant::nameFromFuncName(func.name);
25288 if (!name) return;
25290 auto const cnsPtr = folly::get_ptr(m_data->constants, name);
25291 always_assert_flog(
25292 cnsPtr,
25293 "Attempting to refine constant {} "
25294 "which we don't have meta-data for",
25295 name
25297 auto const cns = cnsPtr->first;
25298 auto const val = tv(fa.inferredReturn);
25299 if (!val) {
25300 always_assert_flog(
25301 type(cns->val) == KindOfUninit,
25302 "Constant value invariant violated in {}.\n"
25303 " Value went from {} to {}",
25304 name,
25305 show(from_cell(cns->val)),
25306 show(fa.inferredReturn)
25308 return;
25311 if (type(cns->val) != KindOfUninit) {
25312 always_assert_flog(
25313 from_cell(cns->val) == fa.inferredReturn,
25314 "Constant value invariant violated in {}.\n"
25315 " Value went from {} to {}",
25316 name,
25317 show(from_cell(cns->val)),
25318 show(fa.inferredReturn)
25320 } else {
25321 always_assert_flog(
25322 !m_data->frozen,
25323 "Attempting to refine constant {} to {} when index is frozen",
25324 name,
25325 show(fa.inferredReturn)
25327 cns->val = *val;
25328 m_data->deps->update(*cns);
25332 void AnalysisIndex::refine_class_constants(const FuncAnalysisResult& fa) {
25333 auto const resolved = fa.resolvedInitializers.left();
25334 if (!resolved || resolved->empty()) return;
25336 assertx(fa.ctx.func->cls);
25337 auto& constants = fa.ctx.func->cls->constants;
25339 for (auto const& c : *resolved) {
25340 assertx(c.first < constants.size());
25341 auto& cns = constants[c.first];
25342 assertx(cns.kind == ConstModifiers::Kind::Value);
25343 always_assert(cns.val.has_value());
25344 always_assert(type(*cns.val) == KindOfUninit);
25346 auto cinfo = fa.ctx.func->cls->cinfo;
25347 if (auto const val = tv(c.second.type)) {
25348 assertx(type(*val) != KindOfUninit);
25349 always_assert_flog(
25350 !m_data->frozen,
25351 "Attempting to refine class constant {}::{} to {} "
25352 "when index is frozen",
25353 fa.ctx.func->cls->name,
25354 cns.name,
25355 show(c.second.type)
25357 cns.val = *val;
25358 if (cinfo) cinfo->clsConstantInfo.erase(cns.name);
25359 m_data->deps->update(
25360 cns,
25361 ConstIndex { fa.ctx.func->cls->name, c.first }
25363 } else if (cinfo) {
25364 auto old = folly::get_default(
25365 cinfo->clsConstantInfo,
25366 cns.name,
25367 ClsConstInfo{ TInitCell, 0 }
25369 old.type = unserialize_type(std::move(old.type));
25371 if (c.second.type.strictlyMoreRefined(old.type)) {
25372 always_assert(c.second.refinements > old.refinements);
25373 always_assert_flog(
25374 !m_data->frozen,
25375 "Attempting to refine class constant {}::{} to {} "
25376 "when index is frozen",
25377 cinfo->name,
25378 cns.name,
25379 show(c.second.type)
25381 cinfo->clsConstantInfo.insert_or_assign(cns.name, c.second);
25382 m_data->deps->update(cns, ConstIndex { cinfo->name, c.first });
25383 } else {
25384 always_assert_flog(
25385 c.second.type.moreRefined(old.type),
25386 "Class constant type invariant violated for {}::{}\n"
25387 " {} is not at least as refined as {}\n",
25388 fa.ctx.func->cls->name,
25389 cns.name,
25390 show(c.second.type),
25391 show(old.type)
25398 void AnalysisIndex::refine_return_info(const FuncAnalysisResult& fa) {
25399 auto const& func = *fa.ctx.func;
25400 auto& finfo = func_info(*m_data, func);
25402 auto const error_loc = [&] {
25403 return folly::sformat("{} {}", func.unit, func_fullname(func));
25406 auto changes = AnalysisDeps::Type::None;
25408 if (finfo.retParam == NoLocalId) {
25409 // This is just a heuristic; it doesn't mean that the value passed
25410 // in was returned, but that the value of the parameter at the
25411 // point of the RetC was returned. We use it to make (heuristic)
25412 // decisions about whether to do inline interps, so we only allow
25413 // it to change once. (otherwise later passes might not do the
25414 // inline interp, and get worse results, which breaks
25415 // monotonicity).
25416 if (fa.retParam != NoLocalId) {
25417 finfo.retParam = fa.retParam;
25418 changes |= AnalysisDeps::Type::RetParam;
25420 } else {
25421 always_assert_flog(
25422 finfo.retParam == fa.retParam,
25423 "Index return param invariant violated in {}.\n"
25424 " Went from {} to {}\n",
25425 finfo.retParam,
25426 fa.retParam,
25427 error_loc()
25431 auto const unusedParams = ~fa.usedParams;
25432 if (finfo.unusedParams != unusedParams) {
25433 always_assert_flog(
25434 (finfo.unusedParams | unusedParams) == unusedParams,
25435 "Index unused params decreased in {}.\n",
25436 error_loc()
25438 finfo.unusedParams = unusedParams;
25439 changes |= AnalysisDeps::Type::UnusedParams;
25442 auto const oldReturnTy = unserialize_type(finfo.returnTy);
25443 if (fa.inferredReturn.strictlyMoreRefined(oldReturnTy)) {
25444 if (finfo.returnRefinements < options.returnTypeRefineLimit) {
25445 finfo.returnTy = fa.inferredReturn;
25446 finfo.returnRefinements += fa.localReturnRefinements + 1;
25447 if (finfo.returnRefinements > options.returnTypeRefineLimit) {
25448 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25450 changes |= AnalysisDeps::Type::RetType;
25451 if (is_scalar(finfo.returnTy)) {
25452 changes |= AnalysisDeps::Type::ScalarRetType;
25454 } else {
25455 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25457 } else {
25458 always_assert_flog(
25459 fa.inferredReturn.moreRefined(oldReturnTy),
25460 "Index return type invariant violated in {}.\n"
25461 " {} is not at least as refined as {}\n",
25462 error_loc(),
25463 show(fa.inferredReturn),
25464 show(oldReturnTy)
25468 always_assert_flog(
25469 !finfo.effectFree || fa.effectFree,
25470 "Index effect-free invariant violated in {}.\n"
25471 " Went from true to false\n",
25472 error_loc()
25475 if (finfo.effectFree != fa.effectFree) {
25476 finfo.effectFree = fa.effectFree;
25477 changes |= AnalysisDeps::Type::RetType;
25480 always_assert_flog(
25481 !m_data->frozen || changes == AnalysisDeps::Type::None,
25482 "Attempting to refine return info for {} ({}) "
25483 "when index is frozen",
25484 error_loc(),
25485 show(changes)
25488 if (changes & AnalysisDeps::Type::RetType) {
25489 if (auto const name = Constant::nameFromFuncName(func.name)) {
25490 auto const cns = folly::get_ptr(m_data->constants, name);
25491 always_assert_flog(
25492 cns,
25493 "Attempting to update constant {} type, but constant is not present!",
25494 name
25496 m_data->deps->update(*cns->first);
25499 m_data->deps->update(func, changes);
25502 void AnalysisIndex::update_prop_initial_values(const FuncAnalysisResult& fa) {
25503 auto const resolved = fa.resolvedInitializers.right();
25504 if (!resolved || resolved->empty()) return;
25506 assertx(fa.ctx.cls);
25507 auto& props = const_cast<php::Class*>(fa.ctx.cls)->properties;
25509 auto changed = false;
25510 for (auto const& [idx, info] : *resolved) {
25511 assertx(idx < props.size());
25512 auto& prop = props[idx];
25514 if (info.satisfies) {
25515 if (!(prop.attrs & AttrInitialSatisfiesTC)) {
25516 always_assert_flog(
25517 !m_data->frozen,
25518 "Attempting to update AttrInitialSatisfiesTC for {}::{} "
25519 "when index is frozen",
25520 fa.ctx.cls->name,
25521 prop.name
25523 attribute_setter(prop.attrs, true, AttrInitialSatisfiesTC);
25524 changed = true;
25526 } else {
25527 always_assert_flog(
25528 !(prop.attrs & AttrInitialSatisfiesTC),
25529 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
25530 " Went from true to false",
25531 fa.ctx.cls->name, prop.name
25535 always_assert_flog(
25536 IMPLIES(!(prop.attrs & AttrDeepInit), !info.deepInit),
25537 "AttrDeepInit invariant violated for {}::{}\n"
25538 " Went from false to true",
25539 fa.ctx.cls->name, prop.name
25541 if (bool(prop.attrs & AttrDeepInit) != info.deepInit) {
25542 always_assert_flog(
25543 !m_data->frozen,
25544 "Attempting to update AttrDeepInit for {}::{} "
25545 "when index is frozen",
25546 fa.ctx.cls->name,
25547 prop.name
25549 attribute_setter(prop.attrs, info.deepInit, AttrDeepInit);
25552 if (type(info.val) != KindOfUninit) {
25553 always_assert_flog(
25554 !m_data->frozen,
25555 "Attempting to update property initial value for {}::{} "
25556 "to {} when index is frozen",
25557 fa.ctx.cls->name,
25558 prop.name,
25559 show(from_cell(info.val))
25561 always_assert_flog(
25562 type(prop.val) == KindOfUninit ||
25563 from_cell(prop.val) == from_cell(info.val),
25564 "Property initial value invariant violated for {}::{}\n"
25565 " Value went from {} to {}",
25566 fa.ctx.cls->name, prop.name,
25567 show(from_cell(prop.val)), show(from_cell(info.val))
25569 prop.val = info.val;
25570 } else {
25571 always_assert_flog(
25572 type(prop.val) == KindOfUninit,
25573 "Property initial value invariant violated for {}::{}\n"
25574 " Value went from {} to not set",
25575 fa.ctx.cls->name, prop.name,
25576 show(from_cell(prop.val))
25580 if (!changed) return;
25582 auto const cinfo = fa.ctx.cls->cinfo;
25583 if (!cinfo) return;
25585 assertx(cinfo->hasBadInitialPropValues);
25586 auto const noBad = std::all_of(
25587 begin(props), end(props),
25588 [] (const php::Prop& prop) {
25589 return bool(prop.attrs & AttrInitialSatisfiesTC);
25593 if (noBad) {
25594 cinfo->hasBadInitialPropValues = false;
25598 void AnalysisIndex::update_type_consts(const ClassAnalysis& analysis) {
25599 if (analysis.resolvedTypeConsts.empty()) return;
25601 always_assert_flog(
25602 !m_data->frozen,
25603 "Attempting to update type constants for {} when index is frozen",
25604 analysis.ctx.cls->name
25607 auto const cls = const_cast<php::Class*>(analysis.ctx.cls);
25608 auto const cinfo = cls->cinfo;
25609 if (!cinfo) return;
25611 for (auto const& update : analysis.resolvedTypeConsts) {
25612 auto const srcCls = folly::get_default(m_data->classes, update.from.cls);
25613 assertx(srcCls);
25614 assertx(update.from.idx < srcCls->constants.size());
25616 auto& newCns = [&] () -> php::Const& {
25617 auto& srcCns = srcCls->constants[update.from.idx];
25618 if (srcCls == cls) {
25619 assertx(!srcCns.resolvedTypeStructure);
25620 return srcCns;
25622 cinfo->clsConstants[srcCns.name] =
25623 ClassInfo2::ConstIndexAndKind {
25624 ConstIndex { cinfo->name, (ConstIndex::Idx)cls->constants.size() },
25625 srcCns.kind
25627 cls->constants.emplace_back(srcCns);
25628 return cls->constants.back();
25629 }();
25631 newCns.resolvedTypeStructure = update.resolved;
25632 newCns.contextInsensitive = update.contextInsensitive;
25633 newCns.resolvedLocally = true;
25637 void AnalysisIndex::update_bytecode(FuncAnalysisResult& fa) {
25638 auto func = php::WideFunc::mut(const_cast<php::Func*>(fa.ctx.func));
25639 auto const update = HHBBC::update_bytecode(func, std::move(fa.blockUpdates));
25640 if (update == UpdateBCResult::None) return;
25642 always_assert_flog(
25643 !m_data->frozen,
25644 "Attempting to update bytecode for {} when index is frozen",
25645 func_fullname(*fa.ctx.func)
25648 if (update == UpdateBCResult::ChangedAnalyze ||
25649 fa.ctx.func->name == s_86cinit.get()) {
25650 ITRACE(2, "Updated bytecode for {} in a way that requires re-analysis\n",
25651 func_fullname(*fa.ctx.func));
25652 m_data->worklist.schedule(fc_from_context(fa.ctx, *m_data));
25655 m_data->deps->update(*fa.ctx.func, AnalysisDeps::Type::Bytecode);
25658 void AnalysisIndex::update_type_aliases(const UnitAnalysis& ua) {
25659 assertx(ua.ctx.unit);
25660 if (ua.resolvedTypeAliases.empty()) return;
25662 always_assert_flog(
25663 !m_data->frozen,
25664 "Attempting to update type-aliases for unit {} when index is frozen\n",
25665 ua.ctx.unit
25668 auto const& unit = lookup_unit(ua.ctx.unit);
25669 for (auto const& update : ua.resolvedTypeAliases) {
25670 assertx(update.idx < unit.typeAliases.size());
25671 auto& ta = *unit.typeAliases[update.idx];
25672 ta.resolvedTypeStructure = update.resolved;
25673 ta.resolvedLocally = true;
25677 // Finish using the AnalysisIndex and calculate the output to be
25678 // returned back from the job.
25679 AnalysisIndex::Output AnalysisIndex::finish() {
25680 Variadic<std::unique_ptr<php::Class>> classes;
25681 Variadic<std::unique_ptr<php::Func>> funcs;
25682 Variadic<std::unique_ptr<php::Unit>> units;
25683 Variadic<std::unique_ptr<php::ClassBytecode>> clsBC;
25684 Variadic<std::unique_ptr<php::FuncBytecode>> funcBC;
25685 Variadic<AnalysisIndexCInfo> cinfos;
25686 Variadic<AnalysisIndexFInfo> finfos;
25687 Variadic<AnalysisIndexMInfo> minfos;
25688 AnalysisOutput::Meta meta;
25690 assertx(m_data->frozen);
25692 // Remove any 86cinits that are now unneeded.
25693 meta.removedFuncs = strip_unneeded_constant_inits(*m_data);
25695 for (auto const name : m_data->outClassNames) {
25696 auto const& cls = m_data->allClasses.at(name);
25697 mark_fixed_class_constants(*cls, *m_data);
25698 meta.cnsBases[cls->name] = record_cns_bases(*cls, *m_data);
25699 for (auto const& clo : cls->closures) {
25700 mark_fixed_class_constants(*clo, *m_data);
25704 for (auto const name : m_data->outUnitNames) {
25705 auto const& unit = m_data->allUnits.at(name);
25706 mark_fixed_unit(*unit, m_data->deps->getChanges());
25709 auto const moveNewAuxs = [&] (AuxClassGraphs& auxs) {
25710 auxs.noChildren = std::move(auxs.newNoChildren);
25711 auxs.withChildren = std::move(auxs.newWithChildren);
25714 TSStringSet outClasses;
25716 classes.vals.reserve(m_data->outClassNames.size());
25717 clsBC.vals.reserve(m_data->outClassNames.size());
25718 cinfos.vals.reserve(m_data->outClassNames.size());
25719 meta.classDeps.reserve(m_data->outClassNames.size());
25720 for (auto const name : m_data->outClassNames) {
25721 auto& cls = m_data->allClasses.at(name);
25723 outClasses.emplace(name);
25725 meta.classDeps.emplace_back(m_data->deps->take(cls.get()));
25726 always_assert(IMPLIES(is_closure(*cls), meta.classDeps.back().empty()));
25727 for (auto const& clo : cls->closures) {
25728 assertx(m_data->deps->take(clo.get()).empty());
25729 outClasses.emplace(clo->name);
25732 clsBC.vals.emplace_back(
25733 std::make_unique<php::ClassBytecode>(cls->name)
25735 auto& bc = *clsBC.vals.back();
25736 for (auto& meth : cls->methods) {
25737 bc.methodBCs.emplace_back(meth->name, std::move(meth->rawBlocks));
25739 for (auto& clo : cls->closures) {
25740 assertx(clo->methods.size() == 1);
25741 auto& meth = clo->methods[0];
25742 bc.methodBCs.emplace_back(meth->name, std::move(meth->rawBlocks));
25745 if (auto cinfo = folly::get_ptr(m_data->allCInfos, name)) {
25746 moveNewAuxs(cinfo->get()->auxClassGraphs);
25748 AnalysisIndexCInfo acinfo;
25749 acinfo.ptr = decltype(acinfo.ptr){cinfo->release()};
25750 cinfos.vals.emplace_back(std::move(acinfo));
25751 } else if (auto minfo = folly::get_ptr(m_data->allMInfos, name)) {
25752 AnalysisIndexMInfo aminfo;
25753 aminfo.ptr = decltype(aminfo.ptr){minfo->release()};
25754 minfos.vals.emplace_back(std::move(aminfo));
25757 classes.vals.emplace_back(std::move(cls));
25760 FSStringSet outFuncs;
25761 outFuncs.reserve(m_data->outFuncNames.size());
25763 funcs.vals.reserve(m_data->outFuncNames.size());
25764 funcBC.vals.reserve(m_data->outFuncNames.size());
25765 finfos.vals.reserve(m_data->outFuncNames.size());
25766 meta.funcDeps.reserve(m_data->outFuncNames.size());
25767 for (auto const name : m_data->outFuncNames) {
25768 assertx(!meta.removedFuncs.count(name));
25770 auto& func = m_data->allFuncs.at(name);
25771 auto& finfo = m_data->allFInfos.at(name);
25773 outFuncs.emplace(name);
25774 meta.funcDeps.emplace_back(m_data->deps->take(func.get()));
25776 funcBC.vals.emplace_back(
25777 std::make_unique<php::FuncBytecode>(name, std::move(func->rawBlocks))
25779 funcs.vals.emplace_back(std::move(func));
25781 if (finfo->auxClassGraphs) moveNewAuxs(*finfo->auxClassGraphs);
25783 AnalysisIndexFInfo afinfo;
25784 afinfo.ptr = decltype(afinfo.ptr){finfo.release()};
25785 finfos.vals.emplace_back(std::move(afinfo));
25788 hphp_fast_set<php::Unit*> outUnits;
25789 outUnits.reserve(m_data->outUnitNames.size());
25791 units.vals.reserve(m_data->outUnitNames.size());
25792 meta.unitDeps.reserve(m_data->outUnitNames.size());
25793 for (auto const name : m_data->outUnitNames) {
25794 auto& unit = m_data->allUnits.at(name);
25795 outUnits.emplace(unit.get());
25796 meta.unitDeps.emplace_back(m_data->deps->take(unit.get()));
25797 units.vals.emplace_back(std::move(unit));
25800 SStringSet outConstants;
25801 for (auto const& [_, p] : m_data->constants) {
25802 if (!outUnits.count(p.second)) continue;
25803 outConstants.emplace(p.first->name);
25806 const SStringSet outUnitNames{
25807 begin(m_data->outUnitNames),
25808 end(m_data->outUnitNames)
25811 meta.changed = std::move(m_data->deps->getChanges());
25812 meta.changed.filter(outClasses, outFuncs, outUnitNames, outConstants);
25814 return std::make_tuple(
25815 std::move(classes),
25816 std::move(funcs),
25817 std::move(units),
25818 std::move(clsBC),
25819 std::move(funcBC),
25820 std::move(cinfos),
25821 std::move(finfos),
25822 std::move(minfos),
25823 std::move(meta)
25827 //////////////////////////////////////////////////////////////////////
25829 PublicSPropMutations::PublicSPropMutations(bool enabled) : m_enabled{enabled} {}
25831 PublicSPropMutations::Data& PublicSPropMutations::get() {
25832 if (!m_data) m_data = std::make_unique<Data>();
25833 return *m_data;
25836 void PublicSPropMutations::mergeKnown(const ClassInfo* ci,
25837 const php::Prop& prop,
25838 const Type& val) {
25839 if (!m_enabled) return;
25840 ITRACE(4, "PublicSPropMutations::mergeKnown: {} {} {}\n",
25841 ci->cls->name->data(), prop.name, show(val));
25843 auto const res = get().m_known.emplace(
25844 KnownKey { const_cast<ClassInfo*>(ci), prop.name }, val
25846 if (!res.second) res.first->second |= val;
25849 void PublicSPropMutations::mergeUnknownClass(SString prop, const Type& val) {
25850 if (!m_enabled) return;
25851 ITRACE(4, "PublicSPropMutations::mergeUnknownClass: {} {}\n",
25852 prop, show(val));
25854 auto const res = get().m_unknown.emplace(prop, val);
25855 if (!res.second) res.first->second |= val;
25858 void PublicSPropMutations::mergeUnknown(Context ctx) {
25859 if (!m_enabled) return;
25860 ITRACE(4, "PublicSPropMutations::mergeUnknown\n");
25863 * We have a case here where we know neither the class nor the static
25864 * property name. This means we have to pessimize public static property
25865 * types for the entire program.
25867 * We could limit it to pessimizing them by merging the `val' type, but
25868 * instead we just throw everything away---this optimization is not
25869 * expected to be particularly useful on programs that contain any
25870 * instances of this situation.
25872 std::fprintf(
25873 stderr,
25874 "NOTE: had to mark everything unknown for public static "
25875 "property types due to dynamic code. -fanalyze-public-statics "
25876 "will not help for this program.\n"
25877 "NOTE: The offending code occured in this context: %s\n",
25878 show(ctx).c_str()
25880 get().m_nothing_known = true;
25883 //////////////////////////////////////////////////////////////////////
25885 #define UNIMPLEMENTED always_assert_flog(false, "{} not implemented for AnalysisIndex", __func__)
25887 bool AnalysisIndexAdaptor::frozen() const {
25888 return index.frozen();
25891 void AnalysisIndexAdaptor::push_context(const Context& ctx) const {
25892 index.push_context(ctx);
25895 void AnalysisIndexAdaptor::pop_context() const {
25896 index.pop_context();
25899 bool AnalysisIndexAdaptor::set_in_type_cns(bool b) const {
25900 return index.set_in_type_cns(b);
25903 const php::Unit* AnalysisIndexAdaptor::lookup_func_unit(const php::Func& func) const {
25904 return &index.lookup_func_unit(func);
25906 const php::Unit* AnalysisIndexAdaptor::lookup_class_unit(const php::Class& cls) const {
25907 return &index.lookup_class_unit(cls);
25909 const php::Class* AnalysisIndexAdaptor::lookup_const_class(const php::Const& cns) const {
25910 return index.lookup_const_class(cns);
25912 const php::Class* AnalysisIndexAdaptor::lookup_closure_context(const php::Class& cls) const {
25913 return &index.lookup_closure_context(cls);
25915 const php::Class* AnalysisIndexAdaptor::lookup_class(SString c) const {
25916 return index.lookup_class(c);
25919 const CompactVector<const php::Class*>*
25920 AnalysisIndexAdaptor::lookup_closures(const php::Class*) const {
25921 UNIMPLEMENTED;
25924 const hphp_fast_set<const php::Func*>*
25925 AnalysisIndexAdaptor::lookup_extra_methods(const php::Class*) const {
25926 UNIMPLEMENTED;
25929 Optional<res::Class> AnalysisIndexAdaptor::resolve_class(SString n) const {
25930 return index.resolve_class(n);
25932 Optional<res::Class>
25933 AnalysisIndexAdaptor::resolve_class(const php::Class& c) const {
25934 return index.resolve_class(c);
25936 std::pair<const php::TypeAlias*, bool>
25937 AnalysisIndexAdaptor::lookup_type_alias(SString n) const {
25938 return index.lookup_type_alias(n);
25940 Index::ClassOrTypeAlias
25941 AnalysisIndexAdaptor::lookup_class_or_type_alias(SString n) const {
25942 return index.lookup_class_or_type_alias(n);
25945 res::Func AnalysisIndexAdaptor::resolve_func_or_method(const php::Func& f) const {
25946 return index.resolve_func_or_method(f);
25948 res::Func AnalysisIndexAdaptor::resolve_func(SString f) const {
25949 return index.resolve_func(f);
25951 res::Func AnalysisIndexAdaptor::resolve_method(Context,
25952 const Type& t,
25953 SString n) const {
25954 return index.resolve_method(t, n);
25956 res::Func AnalysisIndexAdaptor::resolve_ctor(const Type& obj) const {
25957 return index.resolve_ctor(obj);
25960 std::vector<std::pair<SString, ClsConstInfo>>
25961 AnalysisIndexAdaptor::lookup_class_constants(const php::Class& cls) const {
25962 return index.lookup_class_constants(cls);
25965 ClsConstLookupResult
25966 AnalysisIndexAdaptor::lookup_class_constant(Context,
25967 const Type& cls,
25968 const Type& name) const {
25969 return index.lookup_class_constant(cls, name);
25972 ClsTypeConstLookupResult
25973 AnalysisIndexAdaptor::lookup_class_type_constant(
25974 const Type& cls,
25975 const Type& name,
25976 const Index::ClsTypeConstLookupResolver& resolver
25977 ) const {
25978 return index.lookup_class_type_constant(cls, name, resolver);
25981 ClsTypeConstLookupResult
25982 AnalysisIndexAdaptor::lookup_class_type_constant(const php::Class& ctx,
25983 SString n,
25984 ConstIndex idx) const {
25985 return index.lookup_class_type_constant(ctx, n, idx);
25988 std::vector<std::pair<SString, ConstIndex>>
25989 AnalysisIndexAdaptor::lookup_flattened_class_type_constants(
25990 const php::Class& cls
25991 ) const {
25992 return index.lookup_flattened_class_type_constants(cls);
25995 Type AnalysisIndexAdaptor::lookup_constant(Context, SString n) const {
25996 return index.lookup_constant(n);
25998 bool AnalysisIndexAdaptor::func_depends_on_arg(const php::Func* f,
25999 size_t arg) const {
26000 return index.func_depends_on_arg(*f, arg);
26002 Index::ReturnType
26003 AnalysisIndexAdaptor::lookup_foldable_return_type(Context,
26004 const CallContext& callee) const {
26005 return index.lookup_foldable_return_type(callee);
26007 Index::ReturnType AnalysisIndexAdaptor::lookup_return_type(Context,
26008 MethodsInfo* methods,
26009 res::Func func,
26010 Dep) const {
26011 return index.lookup_return_type(methods, func);
26013 Index::ReturnType AnalysisIndexAdaptor::lookup_return_type(Context,
26014 MethodsInfo* methods,
26015 const CompactVector<Type>& args,
26016 const Type& context,
26017 res::Func func,
26018 Dep) const {
26019 return index.lookup_return_type(methods, args, context, func);
26022 std::pair<Index::ReturnType, size_t>
26023 AnalysisIndexAdaptor::lookup_return_type_raw(const php::Func* f) const {
26024 return index.lookup_return_type_raw(*f);
26026 CompactVector<Type>
26027 AnalysisIndexAdaptor::lookup_closure_use_vars(const php::Func*, bool) const {
26028 UNIMPLEMENTED;
26031 PropState AnalysisIndexAdaptor::lookup_private_props(const php::Class* cls,
26032 bool) const {
26033 return index.lookup_private_props(*cls);
26035 PropState AnalysisIndexAdaptor::lookup_private_statics(const php::Class* cls,
26036 bool) const {
26037 return index.lookup_private_statics(*cls);
26040 PropLookupResult AnalysisIndexAdaptor::lookup_static(Context,
26041 const PropertiesInfo&,
26042 const Type&,
26043 const Type& name) const {
26044 // Not implemented yet, be conservative.
26046 auto const sname = [&] () -> SString {
26047 // Treat non-string names conservatively, but the caller should be
26048 // checking this.
26049 if (!is_specialized_string(name)) return nullptr;
26050 return sval_of(name);
26051 }();
26053 return PropLookupResult{
26054 TInitCell,
26055 sname,
26056 TriBool::Maybe,
26057 TriBool::Maybe,
26058 TriBool::Maybe,
26059 TriBool::Maybe,
26060 TriBool::Maybe,
26061 true
26065 Type AnalysisIndexAdaptor::lookup_public_prop(const Type&, const Type&) const {
26066 return TInitCell;
26069 PropMergeResult
26070 AnalysisIndexAdaptor::merge_static_type(Context,
26071 PublicSPropMutations& publicMutations,
26072 PropertiesInfo& privateProps,
26073 const Type& cls,
26074 const Type& name,
26075 const Type& val,
26076 bool checkUB,
26077 bool ignoreConst,
26078 bool mustBeReadOnly) const {
26079 return index.merge_static_type(
26080 publicMutations,
26081 privateProps,
26082 cls,
26083 name,
26084 val,
26085 checkUB,
26086 ignoreConst,
26087 mustBeReadOnly
26091 bool AnalysisIndexAdaptor::using_class_dependencies() const {
26092 return false;
26095 #undef UNIMPLEMENTED
26097 //////////////////////////////////////////////////////////////////////
26099 template <typename T>
26100 void AnalysisIndexParam<T>::Deleter::operator()(T* t) const {
26101 delete t;
26104 template <typename T>
26105 void AnalysisIndexParam<T>::serde(BlobEncoder& sd) const {
26106 sd(ptr);
26109 template <typename T>
26110 void AnalysisIndexParam<T>::serde(BlobDecoder& sd) {
26111 sd(ptr);
26114 template struct AnalysisIndexParam<ClassInfo2>;
26115 template struct AnalysisIndexParam<FuncInfo2>;
26116 template struct AnalysisIndexParam<MethodsWithoutCInfo>;
26118 //////////////////////////////////////////////////////////////////////
26122 //////////////////////////////////////////////////////////////////////
26124 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::ClassInfo2);
26125 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncInfo2);
26126 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncFamily2);
26127 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::MethodsWithoutCInfo);
26128 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::BuildSubclassListJob::Split);
26130 //////////////////////////////////////////////////////////////////////