Updating submodules
[hiphop-php.git] / hphp / hhbbc / index.cpp
blob59a9edc15c27aac4596d04dec08a30c43f3d5f49
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& cache = bytecode ? allowCacheBC : allowCache;
6399 if (auto const b = folly::get_ptr(cache, fc, c.name)) return *b;
6401 auto const a = folly::get_ptr(allows, fc);
6402 assertx(a);
6403 assertx(a->process->contains(index.bucketIdx));
6405 auto const b = [&] {
6406 if (auto const cls = folly::get_default(index.classes, c.name)) {
6407 auto const canon = canonicalize(*cls);
6408 if (auto const c2 = canon.cls()) {
6409 auto const ca = folly::get_ptr(allows, c2);
6410 assertx(ca);
6411 assertx(ca->present->contains(index.bucketIdx));
6412 return a->process->isSubset(
6413 bytecode ? *ca->withBC : *ca->present
6416 if (auto const f = canon.func()) {
6417 auto const fa = folly::get_ptr(allows, f);
6418 assertx(fa);
6419 assertx(fa->present->contains(index.bucketIdx));
6420 return a->process->isSubset(
6421 bytecode ? *fa->withBC : *fa->present
6424 always_assert(false);
6426 if (auto const ta = folly::get_ptr(index.typeAliases, c.name)) {
6427 auto const ua = folly::get_ptr(allows, ta->second);
6428 assertx(ua);
6429 assertx(ua->present->contains(index.bucketIdx));
6430 return a->process->isSubset(*ua->present);
6432 if (index.badClasses.count(c.name)) {
6433 auto const ca = folly::get_ptr(badClassAllows, c.name);
6434 assertx(ca);
6435 assertx(ca->present->contains(index.bucketIdx));
6436 return a->process->isSubset(*ca->present);
6438 return false;
6439 }();
6440 cache[fc][c.name] = b;
6441 return b;
6444 bool allowed(FuncClsUnit fc, Func f, bool bytecode) const {
6445 assertx(!fc.unit());
6446 auto const a = folly::get_ptr(allows, fc);
6447 assertx(a);
6448 assertx(a->process->contains(index.bucketIdx));
6449 if (auto const func = folly::get_default(index.funcs, f.name)) {
6450 auto const fa = folly::get_ptr(allows, func);
6451 assertx(fa);
6452 assertx(fa->present->contains(index.bucketIdx));
6453 return a->process->isSubset(
6454 bytecode ? *fa->withBC : *fa->present
6457 if (index.badFuncs.count(f.name)) {
6458 auto const fa = folly::get_ptr(badFuncAllows, f.name);
6459 assertx(fa);
6460 assertx(fa->present->contains(index.bucketIdx));
6461 return a->process->isSubset(*fa->present);
6463 return false;
6466 bool allowed(FuncClsUnit fc, Constant cns) const {
6467 assertx(!fc.unit());
6468 auto const a = folly::get_ptr(allows, fc);
6469 assertx(a);
6470 assertx(a->process->contains(index.bucketIdx));
6471 if (auto const c = folly::get_ptr(index.constants, cns.name)) {
6472 auto const unit = folly::get_default(index.units, c->second->filename);
6473 assertx(unit);
6474 auto const ua = folly::get_ptr(allows, unit);
6475 assertx(ua);
6476 assertx(ua->present->contains(index.bucketIdx));
6477 return a->process->isSubset(*ua->present);
6479 if (index.badConstants.count(cns.name)) {
6480 auto const ca = folly::get_ptr(badConstantAllows, cns.name);
6481 assertx(ca);
6482 assertx(ca->present->contains(index.bucketIdx));
6483 return a->process->isSubset(*ca->present);
6485 return false;
6488 // Return appropriate entity to attribute the dependency to. If
6489 // we're analyzing a function within a class, use the class. If it's
6490 // a top-level function, use that.
6491 FuncClsUnit context() const {
6492 auto const fc = fc_from_context(context_for_deps(index), index);
6493 if (auto const c = fc.cls()) return canonicalize(*c);
6494 return fc;
6497 // If a class is a closure, the correct context might actually be a
6498 // function.
6499 FuncClsUnit canonicalize(const php::Class& cls) const {
6500 // If this is a closure, the context is the closure's declaring
6501 // class or function.
6502 if (cls.closureContextCls) {
6503 auto const c =
6504 folly::get_default(index.classes, cls.closureContextCls);
6505 always_assert(c);
6506 return c;
6508 if (cls.closureDeclFunc) {
6509 auto const f = folly::get_default(index.funcs, cls.closureDeclFunc);
6510 always_assert(f);
6511 return f;
6513 return &cls;
6516 const php::Func* from(MethRef m) const {
6517 if (auto const cls = folly::get_default(index.classes, m.cls)) {
6518 assertx(m.idx < cls->methods.size());
6519 return cls->methods[m.idx].get();
6521 return nullptr;
6524 const php::Const* from(ConstIndex cns) const {
6525 if (auto const cls = folly::get_default(index.classes, cns.cls)) {
6526 assertx(cns.idx < cls->constants.size());
6527 return &cls->constants[cns.idx];
6529 return nullptr;
6532 std::string display(MethRef m) const {
6533 if (auto const p = from(m)) return func_fullname(*p);
6534 return show(m);
6537 std::string display(ConstIndex cns) const {
6538 if (auto const p = from(cns)) {
6539 return folly::sformat("{}::{}", p->cls, p->name);
6541 return show(cns, AnalysisIndexAdaptor { index.index });
6544 static std::string displayAdded(Type t) {
6545 auto out = show(t - Type::Meta);
6546 if (!out.empty()) folly::format(&out, " of ");
6547 return out;
6550 using FuncClsUnitSet =
6551 hphp_fast_set<FuncClsUnit, FuncClsUnitHasher>;
6552 using FuncClsUnitToType =
6553 hphp_fast_map<FuncClsUnit, Type, FuncClsUnitHasher>;
6555 void schedule(const FuncClsUnitSet* fcs) {
6556 if (!fcs || fcs->empty()) return;
6557 TinyVector<FuncClsUnit, 4> v;
6558 v.insert(begin(*fcs), end(*fcs));
6559 addToWorklist(v);
6562 void schedule(const FuncClsUnitToType* fcs, Type t) {
6563 assertx(!(t & Type::Meta));
6564 if (!fcs || fcs->empty()) return;
6565 TinyVector<FuncClsUnit, 4> v;
6566 for (auto const [fc, t2] : *fcs) {
6567 if (t & t2) v.emplace_back(fc);
6569 addToWorklist(v);
6572 void addToWorklist(TinyVector<FuncClsUnit, 4>& fcs) {
6573 if (fcs.empty()) return;
6574 std::sort(
6575 fcs.begin(), fcs.end(),
6576 [] (FuncClsUnit fc1, FuncClsUnit fc2) {
6577 if (auto const c1 = fc1.cls()) {
6578 if (auto const c2 = fc2.cls()) {
6579 return string_data_lt_type{}(c1->name, c2->name);
6581 return true;
6583 if (auto const f1 = fc1.func()) {
6584 if (auto const f2 = fc2.func()) {
6585 return string_data_lt_func{}(f1->name, f2->name);
6587 return !fc2.cls();
6589 if (auto const u1 = fc1.unit()) {
6590 if (auto const u2 = fc2.unit()) {
6591 return string_data_lt{}(u1->filename, u2->filename);
6593 return !fc2.cls() && !fc2.func();
6595 return false;
6598 Trace::Indent _;
6599 for (auto const fc : fcs) index.worklist.schedule(fc);
6602 const AnalysisIndex::IndexData& index;
6603 AnalysisChangeSet changes;
6604 hphp_fast_map<FuncClsUnit, AnalysisDeps, FuncClsUnitHasher> deps;
6606 hphp_fast_map<FuncClsUnit, BucketPresence, FuncClsUnitHasher> allows;
6607 TSStringToOneT<BucketPresence> badClassAllows;
6608 FSStringToOneT<BucketPresence> badFuncAllows;
6609 SStringToOneT<BucketPresence> badConstantAllows;
6611 hphp_fast_map<const php::Func*, FuncClsUnitToType> funcs;
6612 hphp_fast_map<const php::Const*, FuncClsUnitSet> clsConstants;
6613 hphp_fast_map<const php::Constant*, FuncClsUnitSet> constants;
6614 hphp_fast_map<const php::Class*, FuncClsUnitSet> anyClsConstants;
6616 // We do a lot of permission checks on the same items, so cache the
6617 // results.
6618 mutable hphp_fast_map<FuncClsUnit, TSStringToOneT<bool>,
6619 FuncClsUnitHasher> allowCache;
6620 mutable hphp_fast_map<FuncClsUnit, TSStringToOneT<bool>,
6621 FuncClsUnitHasher> allowCacheBC;
6624 //////////////////////////////////////////////////////////////////////
6626 using IndexData = Index::IndexData;
6628 std::mutex closure_use_vars_mutex;
6629 std::mutex private_propstate_mutex;
6631 DependencyContext make_dep(const php::Func* func) {
6632 return DependencyContext{DependencyContextType::Func, func};
6634 DependencyContext make_dep(const php::Class* cls) {
6635 return DependencyContext{DependencyContextType::Class, cls};
6637 DependencyContext make_dep(const php::Prop* prop) {
6638 return DependencyContext{DependencyContextType::Prop, prop};
6640 DependencyContext make_dep(const FuncFamily* family) {
6641 return DependencyContext{DependencyContextType::FuncFamily, family};
6644 DependencyContext dep_context(IndexData& data, const Context& baseCtx) {
6645 auto const& ctx = baseCtx.forDep();
6646 if (!ctx.cls || !data.useClassDependencies) return make_dep(ctx.func);
6647 auto const cls = ctx.cls->closureContextCls
6648 ? data.classes.at(ctx.cls->closureContextCls)
6649 : ctx.cls;
6650 if (is_used_trait(*cls)) return make_dep(ctx.func);
6651 return make_dep(cls);
6654 template <typename T>
6655 void add_dependency(IndexData& data,
6656 T src,
6657 const Context& dst,
6658 Dep newMask) {
6659 if (data.frozen) return;
6661 auto d = dep_context(data, dst);
6662 DepMap::accessor acc;
6663 data.dependencyMap.insert(acc, make_dep(src));
6664 auto& current = acc->second[d];
6665 current = current | newMask;
6667 // We should only have a return type dependency on func families.
6668 assertx(
6669 IMPLIES(
6670 acc->first.tag() == DependencyContextType::FuncFamily,
6671 newMask == Dep::ReturnTy
6676 template <typename T>
6677 void find_deps(IndexData& data,
6678 T src,
6679 Dep mask,
6680 DependencyContextSet& deps) {
6681 auto const srcDep = make_dep(src);
6683 // We should only ever have return type dependencies on func family.
6684 assertx(
6685 IMPLIES(
6686 srcDep.tag() == DependencyContextType::FuncFamily,
6687 mask == Dep::ReturnTy
6691 DepMap::const_accessor acc;
6692 if (data.dependencyMap.find(acc, srcDep)) {
6693 for (auto const& kv : acc->second) {
6694 if (has_dep(kv.second, mask)) deps.insert(kv.first);
6699 //////////////////////////////////////////////////////////////////////
6701 FuncInfo* func_info(IndexData& data, const php::Func* f) {
6702 assertx(f->idx < data.funcInfo.size());
6703 auto const fi = &data.funcInfo[f->idx];
6704 assertx(fi->func == f);
6705 return fi;
6708 FuncInfo2& func_info(AnalysisIndex::IndexData& data, const php::Func& f) {
6709 assertx(f.idx < data.finfosByIdx.size());
6710 auto const fi = data.finfosByIdx[f.idx];
6711 assertx(fi->func == &f);
6712 return *fi;
6715 //////////////////////////////////////////////////////////////////////
6717 // Obtain the php::Func* represented by a MethRef.
6718 const php::Func* func_from_meth_ref(const IndexData& index,
6719 const MethRef& meth) {
6720 auto const cls = index.classes.at(meth.cls);
6721 assertx(meth.idx < cls->methods.size());
6722 return cls->methods[meth.idx].get();
6725 const php::Func* func_from_meth_ref(const AnalysisIndex::IndexData& index,
6726 const MethRef& meth) {
6727 if (!index.deps->add(AnalysisDeps::Class { meth.cls })) return nullptr;
6728 auto const cls = folly::get_default(index.classes, meth.cls);
6729 if (!cls) {
6730 always_assert_flog(
6731 !index.badClasses.count(meth.cls),
6732 "MethRef references non-existent class {}\n",
6733 meth.cls
6735 return nullptr;
6737 assertx(meth.idx < cls->methods.size());
6738 return cls->methods[meth.idx].get();
6741 //////////////////////////////////////////////////////////////////////
6745 //////////////////////////////////////////////////////////////////////
6747 // Defined here so that AnalysisIndex::IndexData is a complete type.
6749 bool ClassGraph::storeAuxs(AnalysisIndex::IndexData& i, bool children) const {
6750 assertx(i.frozen);
6752 auto const add = [&] (AuxClassGraphs& auxs) {
6753 if (children) {
6754 if (!auxs.newWithChildren.emplace(*this).second) return false;
6755 auxs.newNoChildren.erase(*this);
6756 return true;
6757 } else {
6758 if (auxs.newWithChildren.count(*this)) return false;
6759 return auxs.newNoChildren.emplace(*this).second;
6763 // Get the current context and store this ClassGraph on it's aux
6764 // list.
6765 auto const fc = fc_from_context(context_for_deps(i), i);
6766 if (auto const c = fc.cls()) {
6767 if (!c->cinfo) return false;
6768 if (c->cinfo == cinfo2()) return true;
6769 if (add(c->cinfo->auxClassGraphs)) {
6770 FTRACE(
6771 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6772 c->name, name(),
6773 children ? " (with children)" : ""
6776 return true;
6777 } else if (auto const f = fc.func()) {
6778 auto& fi = func_info(i, *f);
6779 if (!fi.auxClassGraphs) {
6780 fi.auxClassGraphs = std::make_unique<AuxClassGraphs>();
6782 if (add(*fi.auxClassGraphs)) {
6783 FTRACE(
6784 2, "{} now stores {} as an auxiliary ClassGraph{}\n",
6785 f->name, name(),
6786 children ? " (with children)" : ""
6789 return true;
6792 return false;
6795 bool ClassGraph::onAuxs(AnalysisIndex::IndexData& i, bool children) const {
6796 // Check if this ClassGraph is on the current context's aux set *or*
6797 // if it is implied by another ClassGraph on the aux set (for
6798 // example, if this ClassGraph is a parent of a ClassGraph already
6799 // present).
6800 auto const check = [&] (const AuxClassGraphs& auxs, Node* target) {
6801 assertx(!target || !target->isMissing());
6803 if (target == this_) return true;
6804 // Check for direct membership first
6805 if (auxs.withChildren.count(*this)) return true;
6806 if (auxs.noChildren.count(*this)) return !children;
6807 if (this_->isMissing()) return false;
6809 // Check if any parents of this Node are on the set.
6810 if (!auxs.withChildren.empty()) {
6811 auto const a = forEachParent(
6812 *this_,
6813 [&] (Node& p) {
6814 if (target == &p) return Action::Stop;
6815 return auxs.withChildren.count(ClassGraph { &p })
6816 ? Action::Stop
6817 : Action::Continue;
6820 if (a == Action::Stop) return true;
6823 if (children) return false;
6825 TLNodeIdxSet visited;
6826 for (auto const n : auxs.noChildren) {
6827 if (n.this_->isMissing()) continue;
6828 if (findParent(*n.this_, *this_, *visited)) return true;
6830 for (auto const n : auxs.withChildren) {
6831 if (n.this_->isMissing()) continue;
6832 if (findParent(*n.this_, *this_, *visited)) return true;
6834 if (target && findParent(*target, *this_, *visited)) return true;
6836 return false;
6839 auto const fc = fc_from_context(context_for_deps(i), i);
6840 if (auto const c = fc.cls()) {
6841 if (!c->cinfo) return false;
6842 if (c->cinfo == cinfo2()) return true;
6843 return check(c->cinfo->auxClassGraphs, c->cinfo->classGraph.this_);
6846 if (auto const f = fc.func()) {
6847 auto const& fi = func_info(i, *f);
6848 if (!fi.auxClassGraphs) return false;
6849 return check(*fi.auxClassGraphs, nullptr);
6852 return false;
6855 // Ensure ClassGraph is not missing
6856 bool ClassGraph::ensure() const {
6857 assertx(this_);
6858 auto const i = table().index;
6859 if (!i) return true;
6860 if (onAuxs(*i, false)) {
6861 if (i->frozen) always_assert(storeAuxs(*i, false));
6862 return true;
6863 } else if (i->deps->allowed(AnalysisDeps::Class { name() })) {
6864 if (this_->isMissing()) {
6865 always_assert(i->deps->add(AnalysisDeps::Class { name() }));
6867 if (!i->frozen) return true;
6868 if (!storeAuxs(*i, false)) {
6869 (void)i->deps->add(AnalysisDeps::Class { name() });
6871 return true;
6872 } else {
6873 (void)i->deps->add(AnalysisDeps::Class { name() });
6874 return false;
6878 // Ensure ClassGraph is not missing and has complete child
6879 // information.
6880 bool ClassGraph::ensureWithChildren() const {
6881 assertx(this_);
6882 auto const i = table().index;
6883 if (!i) return true;
6884 if (onAuxs(*i, true)) {
6885 if (i->frozen) always_assert(storeAuxs(*i, true));
6886 return true;
6887 } else if (i->deps->allowed(AnalysisDeps::Class { name() })) {
6888 if (this_->isMissing() ||
6889 (!this_->hasCompleteChildren() && !this_->isConservative())) {
6890 always_assert(i->deps->add(AnalysisDeps::Class { name() }));
6892 if (!i->frozen) return true;
6893 if (!storeAuxs(*i, true)) {
6894 (void)i->deps->add(AnalysisDeps::Class { name() });
6896 return true;
6897 } else {
6898 (void)i->deps->add(AnalysisDeps::Class { name() });
6899 return false;
6903 // Ensure ClassGraph is not missing and has an associated ClassInfo2
6904 // (strongest condition).
6905 bool ClassGraph::ensureCInfo() const {
6906 auto const i = table().index;
6907 return !i || i->deps->add(AnalysisDeps::Class { name() });
6910 bool ClassGraph::allowed(bool children) const {
6911 auto const i = table().index;
6912 return !i || onAuxs(*i, children) ||
6913 i->deps->allowed(AnalysisDeps::Class { name() });
6916 //////////////////////////////////////////////////////////////////////
6918 namespace {
6920 //////////////////////////////////////////////////////////////////////
6922 struct TraitMethod {
6923 using class_type = std::pair<const ClassInfo2*, const php::Class*>;
6924 using method_type = const php::Func*;
6925 using origin_type = SString;
6927 TraitMethod(class_type trait_, method_type method_, Attr modifiers_)
6928 : trait(trait_)
6929 , method(method_)
6930 , modifiers(modifiers_)
6933 class_type trait;
6934 method_type method;
6935 Attr modifiers;
6938 struct TMIOps {
6939 using string_type = LSString;
6940 using class_type = TraitMethod::class_type;
6941 using method_type = TraitMethod::method_type;
6942 using origin_type = TraitMethod::origin_type;
6944 struct TMIException : std::exception {
6945 explicit TMIException(std::string msg) : msg(msg) {}
6946 const char* what() const noexcept override { return msg.c_str(); }
6947 private:
6948 std::string msg;
6951 // Return the name for the trait class.
6952 static const string_type clsName(class_type traitCls) {
6953 return traitCls.first->name;
6956 // Return the name of the trait where the method was originally defined
6957 static origin_type originalClass(method_type meth) {
6958 return meth->originalClass ? meth->originalClass : meth->cls->name;
6961 // Is-a methods.
6962 static bool isAbstract(Attr modifiers) {
6963 return modifiers & AttrAbstract;
6966 // Whether to exclude methods with name `methName' when adding.
6967 static bool exclude(string_type methName) {
6968 return Func::isSpecial(methName);
6971 // Errors.
6972 static void errorDuplicateMethod(class_type cls,
6973 string_type methName,
6974 const std::vector<const StringData*>&) {
6975 auto const& m = cls.second->methods;
6976 if (std::find_if(m.begin(), m.end(),
6977 [&] (auto const& f) {
6978 return f->name->same(methName);
6979 }) != m.end()) {
6980 // the duplicate methods will be overridden by the class method.
6981 return;
6983 throw TMIException(folly::sformat("DuplicateMethod: {}", methName));
6987 using TMIData = TraitMethodImportData<TraitMethod, TMIOps>;
6989 //////////////////////////////////////////////////////////////////////
6991 template <typename T, typename R>
6992 void add_symbol_to_index(R& map, T t, const char* type) {
6993 auto const name = t->name;
6994 auto const ret = map.emplace(name, std::move(t));
6995 always_assert_flog(
6996 ret.second,
6997 "More than one {} with the name {} "
6998 "(should have been caught by parser)",
6999 type,
7000 name
7004 template <typename T, typename R, typename E>
7005 void add_symbol_to_index(R& map, T t, const char* type, const E& other) {
7006 auto const it = other.find(t->name);
7007 always_assert_flog(
7008 it == other.end(),
7009 "More than one symbol with the name {} "
7010 "(should have been caught by parser)",
7011 t->name
7013 add_symbol_to_index(map, std::move(t), type);
7016 // We want const qualifiers on various index data structures for php
7017 // object pointers, but during index creation time we need to
7018 // manipulate some of their attributes (changing the representation).
7019 // This little wrapper keeps the const_casting out of the main line of
7020 // code below.
7021 void attribute_setter(const Attr& attrs, bool set, Attr attr) {
7022 attrSetter(const_cast<Attr&>(attrs), set, attr);
7025 void add_system_constants_to_index(IndexData& index) {
7026 for (auto cnsPair : Native::getConstants()) {
7027 assertx(cnsPair.second.m_type != KindOfUninit);
7028 auto pc = new php::Constant {
7029 cnsPair.first,
7030 cnsPair.second,
7031 AttrPersistent
7033 add_symbol_to_index(index.constants, pc, "constant");
7037 void add_unit_to_index(IndexData& index, php::Unit& unit) {
7038 always_assert_flog(
7039 index.units.emplace(unit.filename, &unit).second,
7040 "More than one unit with the same name {} "
7041 "(should have been caught by parser)",
7042 unit.filename
7045 for (auto& ta : unit.typeAliases) {
7046 add_symbol_to_index(
7047 index.typeAliases,
7048 ta.get(),
7049 "type alias",
7050 index.classes
7054 for (auto& c : unit.constants) {
7055 add_symbol_to_index(index.constants, c.get(), "constant");
7058 for (auto& m : unit.modules) {
7059 add_symbol_to_index(index.modules, m.get(), "module");
7063 void add_class_to_index(IndexData& index, php::Class& c) {
7064 if (c.attrs & AttrEnum) {
7065 add_symbol_to_index(index.enums, &c, "enum");
7068 add_symbol_to_index(index.classes, &c, "class", index.typeAliases);
7070 for (auto& m : c.methods) {
7071 attribute_setter(m->attrs, false, AttrNoOverride);
7072 m->idx = index.nextFuncId++;
7076 void add_func_to_index(IndexData& index, php::Func& func) {
7077 add_symbol_to_index(index.funcs, &func, "function");
7078 func.idx = index.nextFuncId++;
7081 void add_program_to_index(IndexData& index) {
7082 trace_time timer{"add program to index", index.sample};
7083 timer.ignore_client_stats();
7085 auto& program = *index.program;
7086 for (auto const& u : program.units) {
7087 add_unit_to_index(index, *u);
7089 for (auto const& c : program.classes) {
7090 add_class_to_index(index, *c);
7091 for (auto const& clo : c->closures) {
7092 add_class_to_index(index, *clo);
7095 for (auto const& f : program.funcs) {
7096 add_func_to_index(index, *f);
7099 for (auto const& c : program.classes) {
7100 assertx(!c->closureContextCls);
7101 for (auto const& clo : c->closures) {
7102 assertx(clo->closureContextCls);
7103 auto& s = index.classClosureMap[index.classes.at(clo->closureContextCls)];
7104 s.emplace_back(clo.get());
7108 // All funcs have been assigned indices above. Now for each func we
7109 // initialize a default FuncInfo in the funcInfo vec at the
7110 // appropriate index.
7112 trace_time timer2{"create func-infos"};
7113 timer2.ignore_client_stats();
7115 index.funcInfo.resize(index.nextFuncId);
7117 auto const create = [&] (const php::Func& f) {
7118 assertx(f.idx < index.funcInfo.size());
7119 auto& fi = index.funcInfo[f.idx];
7120 assertx(!fi.func);
7121 fi.func = &f;
7124 parallel::for_each(
7125 program.classes,
7126 [&] (const std::unique_ptr<php::Class>& cls) {
7127 for (auto const& m : cls->methods) create(*m);
7128 for (auto const& clo : cls->closures) {
7129 assertx(clo->methods.size() == 1);
7130 create(*clo->methods[0]);
7135 parallel::for_each(
7136 program.funcs,
7137 [&] (const std::unique_ptr<php::Func>& func) { create(*func); }
7141 //////////////////////////////////////////////////////////////////////
7144 * Lists of interfaces that conflict with each other due to being
7145 * implemented by the same class.
7148 struct InterfaceConflicts {
7149 SString name{nullptr};
7150 // The number of classes which implements this interface (used to
7151 // prioritize lower slots for more heavily used interfaces).
7152 size_t usage{0};
7153 TSStringSet conflicts;
7154 template <typename SerDe> void serde(SerDe& sd) {
7155 sd(name)(usage)(conflicts, string_data_lt_type{});
7159 void compute_iface_vtables(IndexData& index,
7160 std::vector<InterfaceConflicts> conflicts) {
7161 trace_time tracer{"compute interface vtables"};
7162 tracer.ignore_client_stats();
7164 if (conflicts.empty()) return;
7166 // Sort interfaces by usage frequencies. We assign slots greedily,
7167 // so sort the interface list so the most frequently implemented
7168 // ones come first.
7169 std::sort(
7170 begin(conflicts),
7171 end(conflicts),
7172 [&] (const InterfaceConflicts& a, const InterfaceConflicts& b) {
7173 if (a.usage != b.usage) return a.usage > b.usage;
7174 return string_data_lt_type{}(a.name, b.name);
7178 // Assign slots, keeping track of the largest assigned slot and the
7179 // total number of uses for each slot.
7181 Slot maxSlot = 0;
7182 hphp_fast_map<Slot, int> slotUses;
7183 boost::dynamic_bitset<> used;
7185 for (auto const& iface : conflicts) {
7186 used.reset();
7188 // Find the lowest Slot that doesn't conflict with anything in the
7189 // conflict set for iface.
7190 auto const slot = [&] () -> Slot {
7191 // No conflicts. This is the only interface implemented by the
7192 // classes that implement it.
7193 if (iface.conflicts.empty()) return 0;
7195 for (auto const conflict : iface.conflicts) {
7196 auto const it = index.ifaceSlotMap.find(conflict);
7197 if (it == end(index.ifaceSlotMap)) continue;
7198 auto const s = it->second;
7199 if (used.size() <= s) used.resize(s + 1);
7200 used.set(s);
7203 used.flip();
7204 return used.any() ? used.find_first() : used.size();
7205 }();
7207 always_assert(
7208 index.ifaceSlotMap.emplace(iface.name, slot).second
7210 maxSlot = std::max(maxSlot, slot);
7211 slotUses[slot] += iface.usage;
7214 if (debug) {
7215 // Make sure we have an initialized entry for each slot for the sort below.
7216 for (Slot slot = 0; slot < maxSlot; ++slot) {
7217 always_assert(slotUses.count(slot));
7221 // Finally, sort and reassign slots so the most frequently used
7222 // slots come first. This slightly reduces the number of wasted
7223 // vtable vector entries at runtime.
7225 auto const slots = [&] {
7226 std::vector<std::pair<Slot, int>> flattened{
7227 begin(slotUses), end(slotUses)
7229 std::sort(
7230 begin(flattened),
7231 end(flattened),
7232 [&] (auto const& a, auto const& b) {
7233 if (a.second != b.second) return a.second > b.second;
7234 return a.first < b.first;
7237 std::vector<Slot> out;
7238 out.reserve(flattened.size());
7239 for (auto const& [slot, _] : flattened) out.emplace_back(slot);
7240 return out;
7241 }();
7243 std::vector<Slot> slotsPermute(maxSlot + 1, 0);
7244 for (size_t i = 0; i <= maxSlot; ++i) slotsPermute[slots[i]] = i;
7246 // re-map interfaces to permuted slots
7247 for (auto& [cls, slot] : index.ifaceSlotMap) {
7248 slot = slotsPermute[slot];
7252 //////////////////////////////////////////////////////////////////////
7254 struct CheckClassInfoInvariantsJob {
7255 static std::string name() { return "hhbbc-check-cinfo-invariants"; }
7256 static void init(const Config& config) {
7257 process_init(config.o, config.gd, false);
7258 ClassGraph::init();
7260 static void fini() { ClassGraph::destroy(); }
7262 static bool run(std::unique_ptr<ClassInfo2> cinfo,
7263 std::unique_ptr<php::Class> cls) {
7264 SCOPE_ASSERT_DETAIL("class") { return cls->name->toCppString(); };
7266 always_assert(check(*cls, false));
7268 auto const check = [] (const ClassInfo2* cinfo,
7269 const php::Class* cls) {
7270 // ClassGraph stored in a ClassInfo should not be missing, always
7271 // have the ClassInfo stored, and have complete children
7272 // information.
7273 always_assert(cinfo->classGraph);
7274 always_assert(!cinfo->classGraph.isMissing());
7275 always_assert(cinfo->classGraph.name()->tsame(cinfo->name));
7276 always_assert(cinfo->classGraph.cinfo2() == cinfo);
7277 if (is_closure_base(cinfo->name)) {
7278 // The closure base class is special. We don't store it's
7279 // children information because it's too large.
7280 always_assert(!cinfo->classGraph.hasCompleteChildren());
7281 always_assert(cinfo->classGraph.isConservative());
7282 } else {
7283 always_assert(cinfo->classGraph.hasCompleteChildren() ||
7284 cinfo->classGraph.isConservative());
7287 // This class and withoutNonRegular should be equivalent when
7288 // ignoring non-regular classes. The withoutNonRegular class
7289 // should be a fixed-point.
7290 if (auto const without = cinfo->classGraph.withoutNonRegular()) {
7291 always_assert(without.hasCompleteChildren() ||
7292 without.isConservative());
7293 always_assert(without.subSubtypeOf(cinfo->classGraph, false, false));
7294 always_assert(cinfo->classGraph.subSubtypeOf(without, false, false));
7295 always_assert(without.withoutNonRegular() == without);
7296 always_assert(cinfo->classGraph.mightBeRegular() ||
7297 cinfo->classGraph.mightHaveRegularSubclass());
7298 always_assert(IMPLIES(cinfo->classGraph.mightBeRegular(),
7299 without == cinfo->classGraph));
7300 } else if (!is_used_trait(*cls)) {
7301 always_assert(!cinfo->classGraph.mightBeRegular());
7302 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7305 // AttrNoOverride is a superset of AttrNoOverrideRegular
7306 always_assert(
7307 IMPLIES(!(cls->attrs & AttrNoOverrideRegular),
7308 !(cls->attrs & AttrNoOverride))
7311 // Override attrs and what we know about the subclasses should be in
7312 // agreement.
7313 if (cls->attrs & AttrNoOverride) {
7314 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7315 always_assert(!cinfo->classGraph.mightHaveNonRegularSubclass());
7316 } else if (cls->attrs & AttrNoOverrideRegular) {
7317 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7318 always_assert(cinfo->classGraph.mightHaveNonRegularSubclass());
7321 // Make sure the information stored on the ClassInfo matches that
7322 // which the ClassGraph reports.
7323 if (cls->attrs & AttrNoMock) {
7324 always_assert(!cinfo->isMocked);
7325 always_assert(!cinfo->isSubMocked);
7326 } else {
7327 always_assert(cinfo->isSubMocked);
7330 always_assert(
7331 bool(cls->attrs & AttrNoExpandTrait) ==
7332 cinfo->classGraph.usedTraits().empty()
7335 for (auto const& [name, mte] : cinfo->methods) {
7336 // Interface method tables should only contain its own methods.
7337 if (cls->attrs & AttrInterface) {
7338 always_assert(mte.meth().cls->tsame(cinfo->name));
7341 // AttrNoOverride implies noOverrideRegular
7342 always_assert(IMPLIES(mte.attrs & AttrNoOverride, mte.noOverrideRegular()));
7344 if (!is_special_method_name(name)) {
7345 // If the class isn't overridden, none of it's methods can be
7346 // either.
7347 always_assert(IMPLIES(cls->attrs & AttrNoOverride,
7348 mte.attrs & AttrNoOverride));
7349 } else {
7350 always_assert(!(mte.attrs & AttrNoOverride));
7351 always_assert(!mte.noOverrideRegular());
7354 if (cinfo->name->tsame(s_Closure.get()) || is_closure_name(cinfo->name)) {
7355 always_assert(mte.attrs & AttrNoOverride);
7358 // Don't store method families for special methods.
7359 auto const famIt = cinfo->methodFamilies.find(name);
7360 if (is_special_method_name(name)) {
7361 always_assert(famIt == end(cinfo->methodFamilies));
7362 continue;
7364 if (famIt == end(cinfo->methodFamilies)) {
7365 always_assert(is_closure_name(cinfo->name));
7366 continue;
7368 auto const& entry = famIt->second;
7370 // No override methods should always have a single entry.
7371 if (mte.attrs & AttrNoOverride) {
7372 always_assert(
7373 boost::get<FuncFamilyEntry::BothSingle>(&entry.m_meths) ||
7374 boost::get<FuncFamilyEntry::SingleAndNone>(&entry.m_meths)
7376 continue;
7379 if (cinfo->isRegularClass) {
7380 // "all" should only be a func family. It can't be empty,
7381 // because we know there's at least one method in it (the one in
7382 // cinfo->methods). It can't be a single func, because one of
7383 // the methods must be the cinfo->methods method, and we know it
7384 // isn't AttrNoOverride, so there *must* be another method. So,
7385 // it must be a func family.
7386 always_assert(
7387 boost::get<FuncFamilyEntry::BothFF>(&entry.m_meths) ||
7388 boost::get<FuncFamilyEntry::FFAndSingle>(&entry.m_meths)
7390 // This is a regular class, so we cannot have an incomplete
7391 // entry (can only happen with interfaces).
7392 always_assert(!entry.m_regularIncomplete);
7396 // If the class is marked as having not having bad initial prop
7397 // values, all of it's properties should have
7398 // AttrInitialSatisfiesTC set. Likewise, if it is, at least one
7399 // property should not have it set.
7400 if (!cinfo->hasBadInitialPropValues) {
7401 auto const all = std::all_of(
7402 begin(cls->properties),
7403 end(cls->properties),
7404 [] (const php::Prop& p) {
7405 return p.attrs & AttrInitialSatisfiesTC;
7408 always_assert(all);
7409 } else {
7410 auto const someBad = std::any_of(
7411 begin(cls->properties),
7412 end(cls->properties),
7413 [] (const php::Prop& p) {
7414 return !(p.attrs & AttrInitialSatisfiesTC);
7417 always_assert(someBad);
7420 if (is_closure_name(cinfo->name)) {
7421 assertx(cinfo->classGraph.hasCompleteChildren());
7422 // Closures have no children.
7423 auto const subclasses = cinfo->classGraph.children();
7424 always_assert(subclasses.size() == 1);
7425 always_assert(subclasses[0].name()->tsame(cinfo->name));
7426 } else if (cinfo->classGraph.hasCompleteChildren()) {
7427 // Otherwise the children list is non-empty, contains this
7428 // class, and contains only unique elements.
7429 auto const subclasses = cinfo->classGraph.children();
7430 always_assert(
7431 std::find_if(
7432 begin(subclasses),
7433 end(subclasses),
7434 [&] (ClassGraph g) { return g.name()->tsame(cinfo->name); }
7435 ) != end(subclasses)
7437 auto cpy = subclasses;
7438 std::sort(begin(cpy), end(cpy));
7439 cpy.erase(std::unique(begin(cpy), end(cpy)), end(cpy));
7440 always_assert(cpy.size() == subclasses.size());
7443 // The base list is non-empty, and the last element is this class.
7444 auto const bases = cinfo->classGraph.bases();
7445 always_assert(!bases.empty());
7446 always_assert(cinfo->classGraph == bases.back());
7447 if (is_closure_base(cinfo->name)) {
7448 always_assert(bases.size() == 1);
7449 } else if (is_closure_name(cinfo->name)) {
7450 always_assert(bases.size() == 2);
7451 always_assert(bases[0].name()->tsame(s_Closure.get()));
7454 always_assert(IMPLIES(is_closure(*cls), cls->closures.empty()));
7455 always_assert(cls->closures.size() == cinfo->closures.size());
7458 check(cinfo.get(), cls.get());
7459 for (size_t i = 0, size = cls->closures.size(); i < size; ++i) {
7460 always_assert(cls->closures[i]->name->tsame(cinfo->closures[i]->name));
7461 always_assert(is_closure(*cls->closures[i]));
7462 check(cinfo->closures[i].get(), cls->closures[i].get());
7465 return true;
7469 struct CheckFuncFamilyInvariantsJob {
7470 static std::string name() { return "hhbbc-check-ff-invariants"; }
7471 static void init(const Config& config) {
7472 process_init(config.o, config.gd, false);
7474 static void fini() {}
7476 static bool run(FuncFamilyGroup group) {
7477 for (auto const& ff : group.m_ffs) {
7478 // FuncFamily should always have more than one func on it.
7479 always_assert(
7480 ff->m_regular.size() +
7481 ff->m_nonRegularPrivate.size() +
7482 ff->m_nonRegular.size()
7486 // Every method should be sorted in its respective list. We
7487 // should never see a method for the same Class more than once.
7488 TSStringSet classes;
7489 Optional<MethRef> lastReg;
7490 Optional<MethRef> lastPrivate;
7491 Optional<MethRef> lastNonReg;
7492 for (auto const& meth : ff->m_regular) {
7493 if (lastReg) always_assert(*lastReg < meth);
7494 lastReg = meth;
7495 always_assert(classes.emplace(meth.cls).second);
7497 for (auto const& meth : ff->m_nonRegularPrivate) {
7498 if (lastPrivate) always_assert(*lastPrivate < meth);
7499 lastPrivate = meth;
7500 always_assert(classes.emplace(meth.cls).second);
7502 for (auto const& meth : ff->m_nonRegular) {
7503 if (lastNonReg) always_assert(*lastNonReg < meth);
7504 lastNonReg = meth;
7505 always_assert(classes.emplace(meth.cls).second);
7508 always_assert(ff->m_allStatic.has_value());
7509 always_assert(
7510 ff->m_regularStatic.has_value() ==
7511 (!ff->m_regular.empty() || !ff->m_nonRegularPrivate.empty())
7514 return true;
7518 Job<CheckClassInfoInvariantsJob> s_checkCInfoInvariantsJob;
7519 Job<CheckFuncFamilyInvariantsJob> s_checkFuncFamilyInvariantsJob;
7521 void check_invariants(const IndexData& index) {
7522 if (!debug) return;
7524 trace_time trace{"check-invariants", index.sample};
7526 constexpr size_t kCInfoBucketSize = 3000;
7527 constexpr size_t kFFBucketSize = 500;
7529 auto cinfoBuckets = consistently_bucketize(
7530 [&] {
7531 std::vector<SString> roots;
7532 roots.reserve(index.classInfoRefs.size());
7533 for (auto const& [name, _] : index.classInfoRefs) {
7534 roots.emplace_back(name);
7536 return roots;
7537 }(),
7538 kCInfoBucketSize
7541 SStringToOneT<Ref<FuncFamilyGroup>> nameToFuncFamilyGroup;
7543 auto ffBuckets = consistently_bucketize(
7544 [&] {
7545 std::vector<SString> roots;
7546 roots.reserve(index.funcFamilyRefs.size());
7547 for (auto const& [_, ref] : index.funcFamilyRefs) {
7548 auto const name = makeStaticString(ref.id().toString());
7549 if (nameToFuncFamilyGroup.emplace(name, ref).second) {
7550 roots.emplace_back(name);
7553 return roots;
7554 }(),
7555 kFFBucketSize
7558 using namespace folly::gen;
7560 auto const runCInfo = [&] (std::vector<SString> work) -> coro::Task<void> {
7561 co_await coro::co_reschedule_on_current_executor;
7563 if (work.empty()) co_return;
7565 auto inputs = from(work)
7566 | map([&] (SString name) {
7567 return std::make_tuple(
7568 index.classInfoRefs.at(name),
7569 index.classRefs.at(name)
7572 | as<std::vector>();
7574 Client::ExecMetadata metadata{
7575 .job_key = folly::sformat("check cinfo invariants {}", work[0])
7577 auto config = co_await index.configRef->getCopy();
7578 auto outputs = co_await index.client->exec(
7579 s_checkCInfoInvariantsJob,
7580 std::move(config),
7581 std::move(inputs),
7582 std::move(metadata)
7584 assertx(outputs.size() == work.size());
7586 co_return;
7589 auto const runFF = [&] (std::vector<SString> work) -> coro::Task<void> {
7590 co_await coro::co_reschedule_on_current_executor;
7592 if (work.empty()) co_return;
7594 auto inputs = from(work)
7595 | map([&] (SString name) {
7596 return std::make_tuple(nameToFuncFamilyGroup.at(name));
7598 | as<std::vector>();
7600 Client::ExecMetadata metadata{
7601 .job_key = folly::sformat("check func-family invariants {}", work[0])
7603 auto config = co_await index.configRef->getCopy();
7604 auto outputs = co_await index.client->exec(
7605 s_checkFuncFamilyInvariantsJob,
7606 std::move(config),
7607 std::move(inputs),
7608 std::move(metadata)
7610 assertx(outputs.size() == work.size());
7612 co_return;
7615 std::vector<coro::TaskWithExecutor<void>> tasks;
7616 for (auto& work : cinfoBuckets) {
7617 tasks.emplace_back(
7618 runCInfo(std::move(work)).scheduleOn(index.executor->sticky())
7621 for (auto& work : ffBuckets) {
7622 tasks.emplace_back(
7623 runFF(std::move(work)).scheduleOn(index.executor->sticky())
7626 coro::blockingWait(coro::collectAllRange(std::move(tasks)));
7629 void check_local_invariants(const IndexData& index, const ClassInfo* cinfo) {
7630 SCOPE_ASSERT_DETAIL("class") { return cinfo->cls->name->toCppString(); };
7632 always_assert(check(*cinfo->cls));
7634 // AttrNoOverride is a superset of AttrNoOverrideRegular
7635 always_assert(
7636 IMPLIES(!(cinfo->cls->attrs & AttrNoOverrideRegular),
7637 !(cinfo->cls->attrs & AttrNoOverride))
7640 always_assert(cinfo->classGraph);
7641 always_assert(!cinfo->classGraph.isMissing());
7642 always_assert(cinfo->classGraph.name()->tsame(cinfo->cls->name));
7643 always_assert(cinfo->classGraph.cinfo() == cinfo);
7644 if (is_closure_base(cinfo->cls->name)) {
7645 // The closure base class is special. We don't store it's children
7646 // information because it's too large.
7647 always_assert(!cinfo->classGraph.hasCompleteChildren());
7648 always_assert(cinfo->classGraph.isConservative());
7649 } else {
7650 always_assert(cinfo->classGraph.hasCompleteChildren() ||
7651 cinfo->classGraph.isConservative());
7654 // This class and withoutNonRegular should be equivalent when
7655 // ignoring non-regular classes. The withoutNonRegular class should
7656 // be a fixed-point.
7657 if (auto const without = cinfo->classGraph.withoutNonRegular()) {
7658 always_assert(without.hasCompleteChildren() ||
7659 without.isConservative());
7660 always_assert(without.subSubtypeOf(cinfo->classGraph, false, false));
7661 always_assert(cinfo->classGraph.subSubtypeOf(without, false, false));
7662 always_assert(without.withoutNonRegular() == without);
7663 always_assert(cinfo->classGraph.mightBeRegular() ||
7664 cinfo->classGraph.mightHaveRegularSubclass());
7665 always_assert(IMPLIES(cinfo->classGraph.mightBeRegular(),
7666 without == cinfo->classGraph));
7667 } else if (!is_used_trait(*cinfo->cls)) {
7668 always_assert(!cinfo->classGraph.mightBeRegular());
7669 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7672 // Override attrs and what we know about the subclasses should be in
7673 // agreement.
7674 if (cinfo->cls->attrs & AttrNoOverride) {
7675 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7676 always_assert(!cinfo->classGraph.mightHaveNonRegularSubclass());
7677 } else if (cinfo->cls->attrs & AttrNoOverrideRegular) {
7678 always_assert(!cinfo->classGraph.mightHaveRegularSubclass());
7679 always_assert(cinfo->classGraph.mightHaveNonRegularSubclass());
7682 if (cinfo->cls->attrs & AttrNoMock) {
7683 always_assert(!cinfo->isMocked);
7684 always_assert(!cinfo->isSubMocked);
7687 // An AttrNoExpand class shouldn't have any used traits.
7688 always_assert(
7689 bool(cinfo->cls->attrs & AttrNoExpandTrait) ==
7690 cinfo->usedTraits.empty()
7693 for (size_t idx = 0; idx < cinfo->cls->methods.size(); ++idx) {
7694 // Each method in a class has an entry in its ClassInfo method
7695 // table.
7696 auto const& m = cinfo->cls->methods[idx];
7697 auto const it = cinfo->methods.find(m->name);
7698 always_assert(it != cinfo->methods.end());
7699 always_assert(it->second.meth().cls->tsame(cinfo->cls->name));
7700 always_assert(it->second.meth().idx == idx);
7702 // Every method (except for constructors and special methods
7703 // should be in the global name-only tables.
7704 auto const nameIt = index.methodFamilies.find(m->name);
7705 if (!has_name_only_func_family(m->name)) {
7706 always_assert(nameIt == end(index.methodFamilies));
7707 continue;
7709 always_assert(nameIt != end(index.methodFamilies));
7711 auto const& entry = nameIt->second;
7712 // The global name-only tables are never complete.
7713 always_assert(entry.m_all.isIncomplete());
7714 always_assert(entry.m_regular.isEmpty() || entry.m_regular.isIncomplete());
7716 // "all" should always be non-empty and contain this method.
7717 always_assert(!entry.m_all.isEmpty());
7718 if (auto const ff = entry.m_all.funcFamily()) {
7719 always_assert(ff->possibleFuncs().size() > 1);
7720 // The FuncFamily shouldn't have a section for regular results
7721 // if "regular" isn't using it.
7722 if (entry.m_regular.func() || entry.m_regular.isEmpty()) {
7723 always_assert(!ff->m_regular);
7724 } else {
7725 // "all" and "regular" always share the same func family.
7726 always_assert(entry.m_regular.funcFamily() == ff);
7728 } else {
7729 auto const func = entry.m_all.func();
7730 always_assert(func);
7731 always_assert(func == m.get());
7732 // "regular" is always a subset of "all", so it can either be a
7733 // single func (the same as "all"), or empty.
7734 always_assert(entry.m_regular.func() || entry.m_regular.isEmpty());
7735 if (auto const func2 = entry.m_regular.func()) {
7736 always_assert(func == func2);
7740 // If this is a regular class, "regular" should be non-empty and
7741 // contain this method.
7742 if (auto const ff = entry.m_regular.funcFamily()) {
7743 always_assert(ff->possibleFuncs().size() > 1);
7744 } else if (auto const func = entry.m_regular.func()) {
7745 if (is_regular_class(*cinfo->cls)) {
7746 always_assert(func == m.get());
7748 } else {
7749 always_assert(!is_regular_class(*cinfo->cls));
7753 // Interface ClassInfo method table should only contain methods from
7754 // the interface itself.
7755 if (cinfo->cls->attrs & AttrInterface) {
7756 always_assert(cinfo->cls->methods.size() == cinfo->methods.size());
7759 // If a class isn't overridden, it shouldn't have any func families
7760 // (because the method table is sufficient).
7761 if (cinfo->cls->attrs & AttrNoOverride) {
7762 always_assert(cinfo->methodFamilies.empty());
7763 always_assert(cinfo->methodFamiliesAux.empty());
7766 // The auxiliary method families map is only used by non-regular
7767 // classes.
7768 if (is_regular_class(*cinfo->cls)) {
7769 always_assert(cinfo->methodFamiliesAux.empty());
7772 for (auto const& [name, mte] : cinfo->methods) {
7773 // Interface method tables should only contain its own methods.
7774 if (cinfo->cls->attrs & AttrInterface) {
7775 always_assert(mte.meth().cls->tsame(cinfo->cls->name));
7776 } else {
7777 // Non-interface method tables should not contain any methods
7778 // defined by an interface.
7779 auto const func = func_from_meth_ref(index, mte.meth());
7780 always_assert(!(func->cls->attrs & AttrInterface));
7783 // AttrNoOverride implies noOverrideRegular
7784 always_assert(IMPLIES(mte.attrs & AttrNoOverride, mte.noOverrideRegular()));
7786 if (!is_special_method_name(name)) {
7787 // If the class isn't overridden, none of it's methods can be
7788 // either.
7789 always_assert(IMPLIES(cinfo->cls->attrs & AttrNoOverride,
7790 mte.attrs & AttrNoOverride));
7791 } else {
7792 always_assert(!(mte.attrs & AttrNoOverride));
7793 always_assert(!mte.noOverrideRegular());
7796 if (is_closure_base(*cinfo->cls) || is_closure(*cinfo->cls)) {
7797 always_assert(mte.attrs & AttrNoOverride);
7800 auto const famIt = cinfo->methodFamilies.find(name);
7801 // Don't store method families for special methods, or if there's
7802 // no override.
7803 if (is_special_method_name(name) || (mte.attrs & AttrNoOverride)) {
7804 always_assert(famIt == end(cinfo->methodFamilies));
7805 always_assert(!cinfo->methodFamiliesAux.count(name));
7806 continue;
7807 } else {
7808 always_assert(famIt != end(cinfo->methodFamilies));
7810 auto const& entry = famIt->second;
7812 if (is_regular_class(*cinfo->cls)) {
7813 // "all" should only be a func family. It can't be empty,
7814 // because we know there's at least one method in it (the one in
7815 // cinfo->methods). It can't be a single func, because one of
7816 // the methods must be the cinfo->methods method, and we know it
7817 // isn't AttrNoOverride, so there *must* be another method. So,
7818 // it must be a func family.
7819 always_assert(entry.funcFamily());
7820 // This is a regular class, so we cannot have an incomplete
7821 // entry (can only happen with interfaces).
7822 always_assert(entry.isComplete());
7823 } else {
7824 // This class isn't AttrNoOverride, and since the method is on
7825 // this class, it should at least contain that.
7826 always_assert(!entry.isEmpty());
7827 // Only interfaces can have incomplete entries.
7828 always_assert(
7829 IMPLIES(entry.isIncomplete(), cinfo->cls->attrs & AttrInterface)
7831 // If we got a single func, it should be the func on this
7832 // class. Since this isn't AttrNoOverride, it implies the entry
7833 // should be incomplete.
7834 always_assert(IMPLIES(entry.func(), entry.isIncomplete()));
7835 always_assert(
7836 IMPLIES(entry.func(),
7837 entry.func() == func_from_meth_ref(index, mte.meth()))
7840 // The "aux" entry is optional. If it isn't present, it's the
7841 // same as the normal table.
7842 auto const auxIt = cinfo->methodFamiliesAux.find(name);
7843 if (auxIt != end(cinfo->methodFamiliesAux)) {
7844 auto const& aux = auxIt->second;
7846 // We shouldn't store in the aux table if the entry is the
7847 // same or if there's no override.
7848 always_assert(!mte.noOverrideRegular());
7849 always_assert(
7850 aux.isIncomplete() ||
7851 aux.func() != entry.func() ||
7852 aux.funcFamily() != entry.funcFamily()
7855 // Normally the aux should be non-empty and complete. However
7856 // if this class is an interface, they could be.
7857 always_assert(
7858 IMPLIES(aux.isEmpty(), cinfo->cls->attrs & AttrInterface)
7860 always_assert(
7861 IMPLIES(aux.isIncomplete(), cinfo->cls->attrs & AttrInterface)
7864 // Since we know this was overridden (it wouldn't be in the
7865 // aux table otherwise), it must either be incomplete, or if
7866 // it has a single func, it cannot be the same func as this
7867 // class.
7868 always_assert(
7869 aux.isIncomplete() ||
7870 ((mte.attrs & AttrPrivate) && mte.topLevel()) ||
7871 aux.func() != func_from_meth_ref(index, mte.meth())
7874 // Aux entry is a subset of the normal entry. If they both
7875 // have a func family or func, they must be the same. If the
7876 // normal entry has a func family, but aux doesn't, that func
7877 // family shouldn't have extra space allocated.
7878 always_assert(IMPLIES(entry.func(), !aux.funcFamily()));
7879 always_assert(IMPLIES(entry.funcFamily() && aux.funcFamily(),
7880 entry.funcFamily() == aux.funcFamily()));
7881 always_assert(IMPLIES(entry.func() && aux.func(),
7882 entry.func() == aux.func()));
7883 always_assert(IMPLIES(entry.funcFamily() && !aux.funcFamily(),
7884 !entry.funcFamily()->m_regular));
7889 // "Aux" entries should only exist for methods on this class, and
7890 // with a corresponding methodFamilies entry.
7891 for (auto const& [name, _] : cinfo->methodFamiliesAux) {
7892 always_assert(cinfo->methods.count(name));
7893 always_assert(cinfo->methodFamilies.count(name));
7896 // We should only have func families for methods declared on this
7897 // class (except for interfaces and abstract classes).
7898 for (auto const& [name, entry] : cinfo->methodFamilies) {
7899 if (cinfo->methods.count(name)) continue;
7900 // Interfaces and abstract classes can have func families for
7901 // methods not defined on this class.
7902 always_assert(cinfo->cls->attrs & (AttrInterface|AttrAbstract));
7903 // We don't expand func families for these.
7904 always_assert(name != s_construct.get() && !is_special_method_name(name));
7906 // We only expand entries for interfaces and abstract classes if
7907 // it appears in every regular subclass. Therefore it cannot be
7908 // empty and is complete.
7909 always_assert(!entry.isEmpty());
7910 always_assert(entry.isComplete());
7911 if (auto const ff = entry.funcFamily()) {
7912 always_assert(!ff->m_regular);
7913 } else if (auto const func = entry.func()) {
7914 always_assert(func->cls != cinfo->cls);
7918 // If the class is marked as having not having bad initial prop
7919 // values, all of it's properties should have AttrInitialSatisfiesTC
7920 // set. Likewise, if it is, at least one property should not have it
7921 // set.
7922 if (!cinfo->hasBadInitialPropValues) {
7923 auto const all = std::all_of(
7924 begin(cinfo->cls->properties),
7925 end(cinfo->cls->properties),
7926 [] (const php::Prop& p) {
7927 return p.attrs & AttrInitialSatisfiesTC;
7930 always_assert(all);
7931 } else {
7932 auto const someBad = std::any_of(
7933 begin(cinfo->cls->properties),
7934 end(cinfo->cls->properties),
7935 [] (const php::Prop& p) {
7936 return !(p.attrs & AttrInitialSatisfiesTC);
7939 always_assert(someBad);
7942 if (is_closure_name(cinfo->cls->name)) {
7943 assertx(cinfo->classGraph.hasCompleteChildren());
7944 // Closures have no children.
7945 auto const subclasses = cinfo->classGraph.children();
7946 always_assert(subclasses.size() == 1);
7947 always_assert(subclasses[0].name()->tsame(cinfo->cls->name));
7948 } else if (cinfo->classGraph.hasCompleteChildren()) {
7949 // Otherwise the children list is non-empty, contains this
7950 // class, and contains only unique elements.
7951 auto const subclasses = cinfo->classGraph.children();
7952 always_assert(
7953 std::find_if(
7954 begin(subclasses),
7955 end(subclasses),
7956 [&] (ClassGraph g) { return g.name()->tsame(cinfo->cls->name); }
7957 ) != end(subclasses)
7959 auto cpy = subclasses;
7960 std::sort(begin(cpy), end(cpy));
7961 cpy.erase(std::unique(begin(cpy), end(cpy)), end(cpy));
7962 always_assert(cpy.size() == subclasses.size());
7965 // The base list is non-empty, and the last element is this class.
7966 auto const bases = cinfo->classGraph.bases();
7967 always_assert(!bases.empty());
7968 always_assert(cinfo->classGraph == bases.back());
7969 if (is_closure_base(cinfo->cls->name)) {
7970 always_assert(bases.size() == 1);
7971 } else if (is_closure_name(cinfo->cls->name)) {
7972 always_assert(bases.size() == 2);
7973 always_assert(bases[0].name()->tsame(s_Closure.get()));
7977 void check_local_invariants(const IndexData& data, const FuncFamily& ff) {
7978 // FuncFamily should always have more than one func on it.
7979 always_assert(ff.possibleFuncs().size() > 1);
7981 SString name{nullptr};
7982 FuncFamily::PossibleFunc last{nullptr, false};
7983 for (auto const pf : ff.possibleFuncs()) {
7984 // Should only contain methods
7985 always_assert(pf.ptr()->cls);
7987 // Every method on the list should have the same name.
7988 if (!name) {
7989 name = pf.ptr()->name;
7990 } else {
7991 always_assert(name == pf.ptr()->name);
7994 // Verify the list is sorted and doesn't contain any duplicates.
7995 hphp_fast_set<const php::Func*> seen;
7996 if (last.ptr()) {
7997 always_assert(
7998 [&] {
7999 if (last.inRegular() && !pf.inRegular()) return true;
8000 if (!last.inRegular() && pf.inRegular()) return false;
8001 return string_data_lt_type{}(last.ptr()->cls->name, pf.ptr()->cls->name);
8005 always_assert(seen.emplace(pf.ptr()).second);
8006 last = pf;
8009 if (!ff.possibleFuncs().front().inRegular() ||
8010 ff.possibleFuncs().back().inRegular()) {
8011 // If there's no funcs on a regular class, or if all functions are
8012 // on a regular class, we don't need to keep separate information
8013 // for the regular subset (it either doesn't exist, or it's equal to
8014 // the entire list).
8015 always_assert(!ff.m_regular);
8019 void check_local_invariants(const IndexData& data) {
8020 if (!debug) return;
8022 trace_time timer{"check-local-invariants"};
8024 parallel::for_each(
8025 data.allClassInfos,
8026 [&] (const std::unique_ptr<ClassInfo>& cinfo) {
8027 check_local_invariants(data, cinfo.get());
8031 std::vector<const FuncFamily*> funcFamilies;
8032 funcFamilies.reserve(data.funcFamilies.size());
8033 for (auto const& [ff, _] : data.funcFamilies) {
8034 funcFamilies.emplace_back(ff.get());
8036 parallel::for_each(
8037 funcFamilies,
8038 [&] (const FuncFamily* ff) { check_local_invariants(data, *ff); }
8042 //////////////////////////////////////////////////////////////////////
8044 Type adjust_closure_context(const IIndex& index, const CallContext& ctx) {
8045 if (ctx.callee->cls && ctx.callee->cls->closureContextCls) {
8046 auto const withClosureContext = Context {
8047 ctx.callee->unit,
8048 ctx.callee,
8049 index.lookup_closure_context(*ctx.callee->cls)
8051 if (auto const s = selfCls(index, withClosureContext)) {
8052 return setctx(toobj(*s));
8054 return TObj;
8056 return ctx.context;
8059 Index::ReturnType context_sensitive_return_type(IndexData& data,
8060 const Context& ctx,
8061 CallContext callCtx,
8062 Index::ReturnType returnType) {
8063 constexpr auto max_interp_nexting_level = 2;
8064 static __thread uint32_t interp_nesting_level;
8065 auto const finfo = func_info(data, callCtx.callee);
8067 auto const adjustedCtx = adjust_closure_context(
8068 IndexAdaptor { *data.m_index },
8069 callCtx
8071 returnType.t = return_with_context(std::move(returnType.t), adjustedCtx);
8073 auto const checkParam = [&] (int i) {
8074 auto const& constraint = finfo->func->params[i].typeConstraint;
8075 if (constraint.hasConstraint() &&
8076 !constraint.isTypeVar() &&
8077 !constraint.isTypeConstant()) {
8078 auto const ctx = Context {
8079 finfo->func->unit,
8080 finfo->func,
8081 finfo->func->cls
8083 return callCtx.args[i].strictlyMoreRefined(
8084 lookup_constraint(IndexAdaptor { *data.m_index }, ctx, constraint).upper
8087 return callCtx.args[i].strictSubtypeOf(TInitCell);
8090 // TODO(#3788877): more heuristics here would be useful.
8091 auto const tryContextSensitive = [&] {
8092 if (finfo->func->noContextSensitiveAnalysis ||
8093 finfo->func->params.empty() ||
8094 interp_nesting_level + 1 >= max_interp_nexting_level ||
8095 returnType.t.is(BBottom)) {
8096 return false;
8099 if (finfo->retParam != NoLocalId &&
8100 callCtx.args.size() > finfo->retParam &&
8101 checkParam(finfo->retParam)) {
8102 return true;
8105 if (!options.ContextSensitiveInterp) return false;
8107 if (callCtx.args.size() < finfo->func->params.size()) return true;
8108 for (auto i = 0; i < finfo->func->params.size(); i++) {
8109 if (checkParam(i)) return true;
8111 return false;
8112 }();
8114 if (!tryContextSensitive) return returnType;
8117 ContextRetTyMap::const_accessor acc;
8118 if (data.contextualReturnTypes.find(acc, callCtx)) {
8119 if (data.frozen ||
8120 acc->second.t.is(BBottom) ||
8121 is_scalar(acc->second.t)) {
8122 return acc->second;
8127 if (data.frozen) return returnType;
8129 auto contextType = [&] {
8130 ++interp_nesting_level;
8131 SCOPE_EXIT { --interp_nesting_level; };
8133 auto const func = finfo->func;
8134 auto const wf = php::WideFunc::cns(func);
8135 auto const calleeCtx = AnalysisContext {
8136 func->unit,
8138 func->cls,
8139 &ctx.forDep()
8141 auto fa = analyze_func_inline(
8142 IndexAdaptor { *data.m_index },
8143 calleeCtx,
8144 adjustedCtx,
8145 callCtx.args
8147 return Index::ReturnType{
8148 return_with_context(std::move(fa.inferredReturn), adjustedCtx),
8149 fa.effectFree
8151 }();
8153 if (!interp_nesting_level) {
8154 FTRACE(3,
8155 "Context sensitive type: {}\n"
8156 "Context insensitive type: {}\n",
8157 show(contextType.t), show(returnType.t));
8160 if (!returnType.t.subtypeOf(BUnc)) {
8161 // If the context insensitive return type could be non-static, staticness
8162 // could be a result of temporary context sensitive bytecode optimizations.
8163 contextType.t = loosen_staticness(std::move(contextType.t));
8166 auto ret = Index::ReturnType{
8167 intersection_of(std::move(returnType.t), std::move(contextType.t)),
8168 returnType.effectFree && contextType.effectFree
8171 if (!interp_nesting_level) {
8172 FTRACE(3, "Context sensitive result: {}\n", show(ret.t));
8175 ContextRetTyMap::accessor acc;
8176 if (data.contextualReturnTypes.insert(acc, callCtx) ||
8177 ret.t.strictSubtypeOf(acc->second.t) ||
8178 (ret.effectFree && !acc->second.effectFree)) {
8179 acc->second = ret;
8182 return ret;
8185 //////////////////////////////////////////////////////////////////////
8187 Index::ReturnType context_sensitive_return_type(AnalysisIndex::IndexData& data,
8188 const CallContext& callCtx,
8189 Index::ReturnType returnType) {
8190 constexpr size_t maxNestingLevel = 2;
8192 using R = Index::ReturnType;
8194 auto const& func = *callCtx.callee;
8196 if (data.mode == AnalysisIndex::Mode::Constants) {
8197 ITRACE_MOD(
8198 Trace::hhbbc, 4,
8199 "Skipping inline interp of {} because analyzing constants\n",
8200 func_fullname(func)
8202 return returnType;
8205 auto const& finfo = func_info(data, func);
8206 auto const& caller = *context_for_deps(data).func;
8208 auto const adjustedCtx = adjust_closure_context(
8209 AnalysisIndexAdaptor { data.index },
8210 callCtx
8212 returnType.t = return_with_context(std::move(returnType.t), adjustedCtx);
8214 auto const checkParam = [&] (size_t i) {
8215 auto const& constraint = func.params[i].typeConstraint;
8216 if (constraint.hasConstraint() &&
8217 !constraint.isTypeVar() &&
8218 !constraint.isTypeConstant()) {
8219 return callCtx.args[i].strictlyMoreRefined(
8220 lookup_constraint(
8221 AnalysisIndexAdaptor { data.index },
8222 Context { func.unit, &func, func.cls },
8223 constraint
8224 ).upper
8227 return callCtx.args[i].strictSubtypeOf(TInitCell);
8230 // TODO(#3788877): more heuristics here would be useful.
8231 auto const tryContextSensitive = [&] {
8232 if (func.noContextSensitiveAnalysis ||
8233 func.params.empty() ||
8234 data.contextualInterpNestingLevel + 1 >= maxNestingLevel ||
8235 returnType.t.is(BBottom)) {
8236 return false;
8239 if (data.deps->add(func, AnalysisDeps::RetParam)) {
8240 if (finfo.retParam != NoLocalId &&
8241 callCtx.args.size() > finfo.retParam &&
8242 checkParam(finfo.retParam)) {
8243 return true;
8247 if (!options.ContextSensitiveInterp) return false;
8249 auto const numParams = func.params.size();
8250 if (callCtx.args.size() < numParams) return true;
8251 for (size_t i = 0; i < numParams; ++i) {
8252 if (checkParam(i)) return true;
8254 return false;
8255 }();
8257 if (!tryContextSensitive) {
8258 ITRACE_MOD(Trace::hhbbc, 4, "not trying context sensitive\n");
8259 return returnType;
8262 if (!data.deps->add(func, AnalysisDeps::Bytecode)) {
8263 ITRACE_MOD(
8264 Trace::hhbbc, 4,
8265 "Skipping inline interp of {} because inspecting "
8266 "bytecode is not allowed\n",
8267 func_fullname(func)
8269 return R{ TInitCell, false };
8271 if (!func.rawBlocks) {
8272 ITRACE_MOD(
8273 Trace::hhbbc, 4,
8274 "Skipping inline interp of {} because bytecode not present\n",
8275 func_fullname(func)
8277 return R{ TInitCell, false };
8280 auto const contextType = [&] {
8281 ++data.contextualInterpNestingLevel;
8282 SCOPE_EXIT { --data.contextualInterpNestingLevel; };
8284 auto const wf = php::WideFunc::cns(&func);
8285 auto fa = analyze_func_inline(
8286 AnalysisIndexAdaptor { data.index },
8287 AnalysisContext {
8288 func.unit,
8290 func.cls,
8291 &context_for_deps(data)
8293 adjustedCtx,
8294 callCtx.args
8296 return R{
8297 return_with_context(std::move(fa.inferredReturn), std::move(adjustedCtx)),
8298 fa.effectFree
8300 }();
8302 ITRACE_MOD(
8303 Trace::hhbbc, 4,
8304 "Context sensitive type: {}, context insensitive type: {}\n",
8305 show(contextType.t), show(returnType.t)
8308 auto const error_context = [&] {
8309 using namespace folly::gen;
8310 return folly::sformat(
8311 "{} calling {} (context: {}, args: {})",
8312 func_fullname(caller),
8313 func_fullname(func),
8314 show(callCtx.context),
8315 from(callCtx.args)
8316 | map([] (const Type& t) { return show(t); })
8317 | unsplit<std::string>(",")
8321 always_assert_flog(
8322 contextType.t.subtypeOf(returnType.t),
8323 "Context sensitive return type for {} is {} ",
8324 "which is not at least as refined as context insensitive "
8325 "return type {}\n",
8326 error_context(),
8327 show(contextType.t),
8328 show(returnType.t)
8330 always_assert_flog(
8331 contextType.effectFree || !returnType.effectFree,
8332 "Context sensitive effect-free for {} is {} ",
8333 "which is not at least as refined as context insensitive "
8334 "effect-free {}\n",
8335 error_context(),
8336 contextType.effectFree,
8337 returnType.effectFree
8340 return contextType;
8343 //////////////////////////////////////////////////////////////////////
8345 template<typename F> auto
8346 visit_parent_cinfo(const ClassInfo* cinfo, F fun) -> decltype(fun(cinfo)) {
8347 for (auto ci = cinfo; ci != nullptr; ci = ci->parent) {
8348 if (auto const ret = fun(ci)) return ret;
8349 for (auto const trait : ci->usedTraits) {
8350 if (auto const ret = visit_parent_cinfo(trait, fun)) {
8351 return ret;
8355 return {};
8358 //////////////////////////////////////////////////////////////////////
8360 // The type of a public static property, considering only it's initial
8361 // value.
8362 Type initial_type_for_public_sprop(const Index& index,
8363 const php::Class& cls,
8364 const php::Prop& prop) {
8366 * If the initializer type is TUninit, it means an 86sinit provides
8367 * the actual initialization type or it is AttrLateInit. So we don't
8368 * want to include the Uninit (which isn't really a user-visible
8369 * type for the property) or by the time we union things in we'll
8370 * have inferred nothing much.
8372 auto const ty = from_cell(prop.val);
8373 if (ty.subtypeOf(BUninit)) return TBottom;
8374 if (prop.attrs & AttrSystemInitialValue) return ty;
8375 return adjust_type_for_prop(
8376 IndexAdaptor { index },
8377 cls,
8378 &prop.typeConstraint,
8383 Type lookup_public_prop_impl(
8384 const IndexData& data,
8385 const ClassInfo* cinfo,
8386 SString propName
8388 // Find a property declared in this class (or a parent) with the same name.
8389 const php::Class* knownCls = nullptr;
8390 auto const prop = visit_parent_cinfo(
8391 cinfo,
8392 [&] (const ClassInfo* ci) -> const php::Prop* {
8393 for (auto const& prop : ci->cls->properties) {
8394 if (prop.name == propName) {
8395 knownCls = ci->cls;
8396 return &prop;
8399 return nullptr;
8403 if (!prop) return TCell;
8404 // Make sure its non-static and public. Otherwise its another function's
8405 // problem.
8406 if (prop->attrs & (AttrStatic | AttrPrivate)) return TCell;
8408 // Get a type corresponding to its declared type-hint (if any).
8409 auto ty = adjust_type_for_prop(
8410 IndexAdaptor { *data.m_index }, *knownCls, &prop->typeConstraint, TCell
8412 // We might have to include the initial value which might be outside of the
8413 // type-hint.
8414 auto initialTy = loosen_all(from_cell(prop->val));
8415 if (!initialTy.subtypeOf(TUninit) && (prop->attrs & AttrSystemInitialValue)) {
8416 ty |= initialTy;
8418 return ty;
8421 // Test if the given property (declared in `cls') is accessible in the
8422 // given context (null if we're not in a class).
8423 bool static_is_accessible(const ClassInfo* clsCtx,
8424 const ClassInfo* cls,
8425 const php::Prop& prop) {
8426 assertx(prop.attrs & AttrStatic);
8427 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
8428 case AttrPublic:
8429 // Public is accessible everywhere
8430 return true;
8431 case AttrProtected:
8432 // Protected is accessible from both derived classes and parent
8433 // classes
8434 return clsCtx &&
8435 (clsCtx->classGraph.exactSubtypeOf(cls->classGraph, true, true) ||
8436 cls->classGraph.exactSubtypeOf(clsCtx->classGraph, true, true));
8437 case AttrPrivate:
8438 // Private is only accessible from within the declared class
8439 return clsCtx == cls;
8441 always_assert(false);
8444 // Return true if the given class can possibly throw when its
8445 // initialized. Initialization can happen when an object of that class
8446 // is instantiated, or (more importantly) when static properties are
8447 // accessed.
8448 bool class_init_might_raise(IndexData& data,
8449 Context ctx,
8450 const ClassInfo* cinfo) {
8451 // Check this class and all of its parents for possible inequivalent
8452 // redeclarations or bad initial values.
8453 do {
8454 // Be conservative for now if we have unflattened traits.
8455 if (!cinfo->traitProps.empty()) return true;
8456 if (cinfo->hasBadRedeclareProp) return true;
8457 if (cinfo->hasBadInitialPropValues) {
8458 add_dependency(data, cinfo->cls, ctx, Dep::PropBadInitialValues);
8459 return true;
8461 cinfo = cinfo->parent;
8462 } while (cinfo);
8463 return false;
8467 * Calculate the effects of applying the given type against the
8468 * type-constraints for the given prop. This includes the subtype
8469 * which will succeed (if any), and if the type-constraint check might
8470 * throw.
8472 PropMergeResult prop_tc_effects(const Index& index,
8473 const ClassInfo* ci,
8474 const php::Prop& prop,
8475 const Type& val,
8476 bool checkUB) {
8477 assertx(prop.typeConstraint.validForProp());
8479 using R = PropMergeResult;
8481 // If we're not actually checking property type-hints, everything
8482 // goes
8483 if (Cfg::Eval::CheckPropTypeHints <= 0) return R{ val, TriBool::No };
8485 auto const ctx = Context { nullptr, nullptr, ci->cls };
8487 auto const check = [&] (const TypeConstraint& tc, const Type& t) {
8488 // If the type as is satisfies the constraint, we won't throw and
8489 // the type is unchanged.
8490 if (t.moreRefined(
8491 lookup_constraint(IndexAdaptor { index }, ctx, tc, t).lower)
8493 return R{ t, TriBool:: No };
8495 // Otherwise adjust the type. If we get a Bottom we'll definitely
8496 // throw. We already know the type doesn't completely satisfy the
8497 // constraint, so we'll at least maybe throw.
8498 auto adjusted =
8499 adjust_type_for_prop(IndexAdaptor { index }, *ctx.cls, &tc, t);
8500 auto const throws = yesOrMaybe(adjusted.subtypeOf(BBottom));
8501 return R{ std::move(adjusted), throws };
8504 // First check the main type-constraint.
8505 auto result = check(prop.typeConstraint, val);
8506 // If we're not checking generics upper-bounds, or if we already
8507 // know we'll fail, we're done.
8508 if (!checkUB || result.throws == TriBool::Yes) {
8509 return result;
8512 // Otherwise check every generic upper-bound. We'll feed the
8513 // narrowed type into each successive round. If we reach the point
8514 // where we'll know we'll definitely fail, just stop.
8515 for (auto const& ub : prop.ubs.m_constraints) {
8516 auto r = check(ub, result.adjusted);
8517 result.throws &= r.throws;
8518 result.adjusted = std::move(r.adjusted);
8519 if (result.throws == TriBool::Yes) break;
8522 return result;
8526 * Lookup data for the static property named `propName', starting from
8527 * the specified class `start'. If `propName' is nullptr, then any
8528 * accessible static property in the class hierarchy is considered. If
8529 * `startOnly' is specified, if the property isn't found in `start',
8530 * it is treated as a lookup failure. Otherwise the lookup continues
8531 * in all parent classes of `start', until a property is found, or
8532 * until all parent classes have been exhausted (`startOnly' is used
8533 * to avoid redundant class hierarchy walks). `clsCtx' is the current
8534 * context, converted to a ClassInfo* (or nullptr if not in a class).
8536 PropLookupResult lookup_static_impl(IndexData& data,
8537 Context ctx,
8538 const ClassInfo* clsCtx,
8539 const PropertiesInfo& privateProps,
8540 const ClassInfo* start,
8541 SString propName,
8542 bool startOnly) {
8543 ITRACE(
8544 6, "lookup_static_impl: {} {} {}\n",
8545 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
8546 start->cls->name,
8547 propName ? propName->toCppString() : std::string{"*"}
8549 Trace::Indent _;
8551 auto const type = [&] (const php::Prop& prop,
8552 const ClassInfo* ci) {
8553 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
8554 case AttrPublic:
8555 case AttrProtected: {
8556 if (ctx.unit) add_dependency(data, &prop, ctx, Dep::PublicSProp);
8557 if (!data.seenPublicSPropMutations) {
8558 // If we haven't recorded any mutations yet, we need to be
8559 // conservative and consider only the type-hint and initial
8560 // value.
8561 return union_of(
8562 adjust_type_for_prop(
8563 IndexAdaptor { *data.m_index },
8564 *ci->cls,
8565 &prop.typeConstraint,
8566 TInitCell
8568 initial_type_for_public_sprop(*data.m_index, *ci->cls, prop)
8571 auto const it = ci->publicStaticProps.find(propName);
8572 if (it == end(ci->publicStaticProps)) {
8573 // We've recorded mutations, but have information for this
8574 // property. That means there's no mutations so only
8575 // consider the initial value.
8576 return initial_type_for_public_sprop(*data.m_index, *ci->cls, prop);
8578 return it->second.inferredType;
8580 case AttrPrivate: {
8581 assertx(clsCtx == ci);
8582 auto const elem = privateProps.readPrivateStatic(prop.name);
8583 if (!elem) return TInitCell;
8584 return remove_uninit(elem->ty);
8587 always_assert(false);
8590 auto const initMightRaise = class_init_might_raise(data, ctx, start);
8592 auto const fromProp = [&] (const php::Prop& prop,
8593 const ClassInfo* ci) {
8594 // The property was definitely found. Compute its attributes
8595 // from the prop metadata.
8596 return PropLookupResult{
8597 type(prop, ci),
8598 propName,
8599 TriBool::Yes,
8600 yesOrNo(prop.attrs & AttrIsConst),
8601 yesOrNo(prop.attrs & AttrIsReadonly),
8602 yesOrNo(prop.attrs & AttrLateInit),
8603 yesOrNo(prop.attrs & AttrInternal),
8604 initMightRaise
8608 auto const notFound = [&] {
8609 // The property definitely wasn't found.
8610 return PropLookupResult{
8611 TBottom,
8612 propName,
8613 TriBool::No,
8614 TriBool::No,
8615 TriBool::No,
8616 TriBool::No,
8617 TriBool::No,
8618 false
8622 if (!propName) {
8623 // We don't statically know the prop name. Walk up the hierarchy
8624 // and union the data for any accessible static property.
8625 ITRACE(4, "no prop name, considering all accessible\n");
8626 auto result = notFound();
8627 visit_parent_cinfo(
8628 start,
8629 [&] (const ClassInfo* ci) {
8630 for (auto const& prop : ci->cls->properties) {
8631 if (!(prop.attrs & AttrStatic) ||
8632 !static_is_accessible(clsCtx, ci, prop)) {
8633 ITRACE(
8634 6, "skipping inaccessible {}::${}\n",
8635 ci->cls->name, prop.name
8637 continue;
8639 auto const r = fromProp(prop, ci);
8640 ITRACE(6, "including {}:${} {}\n", ci->cls->name, prop.name, show(r));
8641 result |= r;
8643 // If we're only interested in the starting class, don't walk
8644 // up to the parents.
8645 return startOnly;
8648 return result;
8651 // We statically know the prop name. Walk up the hierarchy and stop
8652 // at the first matching property and use that data.
8653 assertx(!startOnly);
8654 auto const result = visit_parent_cinfo(
8655 start,
8656 [&] (const ClassInfo* ci) -> Optional<PropLookupResult> {
8657 for (auto const& prop : ci->cls->properties) {
8658 if (prop.name != propName) continue;
8659 // We have a matching prop. If its not static or not
8660 // accessible, the access will not succeed.
8661 if (!(prop.attrs & AttrStatic) ||
8662 !static_is_accessible(clsCtx, ci, prop)) {
8663 ITRACE(
8664 6, "{}::${} found but inaccessible, stopping\n",
8665 ci->cls->name, propName
8667 return notFound();
8669 // Otherwise its a match
8670 auto const r = fromProp(prop, ci);
8671 ITRACE(6, "found {}:${} {}\n", ci->cls->name, propName, show(r));
8672 return r;
8674 return std::nullopt;
8677 if (!result) {
8678 // We walked up to all of the base classes and didn't find a
8679 // property with a matching name. The access will fail.
8680 ITRACE(6, "nothing found\n");
8681 return notFound();
8683 return *result;
8687 * Lookup the static property named `propName', starting from the
8688 * specified class `start'. If an accessible property is found, then
8689 * merge the given type `val' into the already known type for that
8690 * property. If `propName' is nullptr, then any accessible static
8691 * property in the class hierarchy is considered. If `startOnly' is
8692 * specified, if the property isn't found in `start', then the nothing
8693 * is done. Otherwise the lookup continues in all parent classes of
8694 * `start', until a property is found, or until all parent classes
8695 * have been exhausted (`startOnly' is to avoid redundant class
8696 * hierarchy walks). `clsCtx' is the current context, converted to a
8697 * ClassInfo* (or nullptr if not in a class). If `ignoreConst' is
8698 * false, then AttrConst properties will not have their type
8699 * modified. `mergePublic' is a lambda with the logic to merge a type
8700 * for a public property (this is needed to avoid cyclic
8701 * dependencies).
8703 template <typename F>
8704 PropMergeResult merge_static_type_impl(IndexData& data,
8705 Context ctx,
8706 F mergePublic,
8707 PropertiesInfo& privateProps,
8708 const ClassInfo* clsCtx,
8709 const ClassInfo* start,
8710 SString propName,
8711 const Type& val,
8712 bool checkUB,
8713 bool ignoreConst,
8714 bool mustBeReadOnly,
8715 bool startOnly) {
8716 ITRACE(
8717 6, "merge_static_type_impl: {} {} {} {}\n",
8718 clsCtx ? clsCtx->cls->name->toCppString() : std::string{"-"},
8719 start->cls->name,
8720 propName ? propName->toCppString() : std::string{"*"},
8721 show(val)
8723 Trace::Indent _;
8725 assertx(!val.subtypeOf(BBottom));
8727 // Perform the actual merge for a given property, returning the
8728 // effects of that merge.
8729 auto const merge = [&] (const php::Prop& prop, const ClassInfo* ci) {
8730 // First calculate the effects of the type-constraint.
8731 auto const effects = prop_tc_effects(*data.m_index, ci, prop, val, checkUB);
8732 // No point in merging if the type-constraint will always fail.
8733 if (effects.throws == TriBool::Yes) {
8734 ITRACE(
8735 6, "tc would throw on {}::${} with {}, skipping\n",
8736 ci->cls->name, prop.name, show(val)
8738 return effects;
8740 assertx(!effects.adjusted.subtypeOf(BBottom));
8742 ITRACE(
8743 6, "merging {} into {}::${}\n",
8744 show(effects), ci->cls->name, prop.name
8747 switch (prop.attrs & (AttrPublic|AttrProtected|AttrPrivate)) {
8748 case AttrPublic:
8749 case AttrProtected:
8750 mergePublic(ci, prop, unctx(effects.adjusted));
8751 // If the property is internal, accessing it may throw
8752 // TODO(T131951529): we can do better by checking modules here
8753 if ((prop.attrs & AttrInternal) && effects.throws == TriBool::No) {
8754 ITRACE(6, "{}::${} is internal, "
8755 "being pessimistic with regards to throwing\n",
8756 ci->cls->name, prop.name);
8757 return PropMergeResult{
8758 effects.adjusted,
8759 TriBool::Maybe
8762 return effects;
8763 case AttrPrivate: {
8764 assertx(clsCtx == ci);
8765 privateProps.mergeInPrivateStaticPreAdjusted(
8766 prop.name,
8767 unctx(effects.adjusted)
8769 return effects;
8772 always_assert(false);
8775 // If we don't find a property, then the mutation will definitely
8776 // fail.
8777 auto const notFound = [&] {
8778 return PropMergeResult{
8779 TBottom,
8780 TriBool::Yes
8784 if (!propName) {
8785 // We don't statically know the prop name. Walk up the hierarchy
8786 // and merge the type for any accessible static property.
8787 ITRACE(6, "no prop name, considering all accessible\n");
8788 auto result = notFound();
8789 visit_parent_cinfo(
8790 start,
8791 [&] (const ClassInfo* ci) {
8792 for (auto const& prop : ci->cls->properties) {
8793 if (!(prop.attrs & AttrStatic) ||
8794 !static_is_accessible(clsCtx, ci, prop)) {
8795 ITRACE(
8796 6, "skipping inaccessible {}::${}\n",
8797 ci->cls->name, prop.name
8799 continue;
8801 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
8802 ITRACE(6, "skipping const {}::${}\n", ci->cls->name, prop.name);
8803 continue;
8805 if (mustBeReadOnly && !(prop.attrs & AttrIsReadonly)) {
8806 ITRACE(6, "skipping mutable property that must be readonly {}::${}\n",
8807 ci->cls->name, prop.name);
8808 continue;
8810 result |= merge(prop, ci);
8812 return startOnly;
8815 return result;
8818 // We statically know the prop name. Walk up the hierarchy and stop
8819 // at the first matching property and merge the type there.
8820 assertx(!startOnly);
8821 auto result = visit_parent_cinfo(
8822 start,
8823 [&] (const ClassInfo* ci) -> Optional<PropMergeResult> {
8824 for (auto const& prop : ci->cls->properties) {
8825 if (prop.name != propName) continue;
8826 // We found a property with the right name, but its
8827 // inaccessible from this context (or not even static). This
8828 // mutation will fail, so we don't need to modify the type.
8829 if (!(prop.attrs & AttrStatic) ||
8830 !static_is_accessible(clsCtx, ci, prop)) {
8831 ITRACE(
8832 6, "{}::${} found but inaccessible, stopping\n",
8833 ci->cls->name, propName
8835 return notFound();
8837 // Mutations to AttrConst properties will fail as well, unless
8838 // it we want to override that behavior.
8839 if (!ignoreConst && (prop.attrs & AttrIsConst)) {
8840 ITRACE(
8841 6, "{}:${} found but const, stopping\n",
8842 ci->cls->name, propName
8844 return notFound();
8846 if (mustBeReadOnly && !(prop.attrs & AttrIsReadonly)) {
8847 ITRACE(
8848 6, "{}:${} found but is mutable and must be readonly, stopping\n",
8849 ci->cls->name, propName
8851 return notFound();
8853 return merge(prop, ci);
8855 return std::nullopt;
8858 if (!result) {
8859 ITRACE(6, "nothing found\n");
8860 return notFound();
8863 // If the mutation won't throw, we still need to check if the class
8864 // initialization can throw. If we might already throw (or
8865 // definitely will throw), this doesn't matter.
8866 if (result->throws == TriBool::No) {
8867 return PropMergeResult{
8868 std::move(result->adjusted),
8869 maybeOrNo(class_init_might_raise(data, ctx, start))
8872 return *result;
8875 //////////////////////////////////////////////////////////////////////
8878 * Split a group of buckets so that no bucket is larger (including its
8879 * dependencies) than the given max size. The given callable is used
8880 * to obtain the dependencies of bucket item.
8882 * Note: if a single item has dependencies larger than maxSize, you'll
8883 * get a bucket with just that and its dependencies (which will be
8884 * larger than maxSize). This is the only situation where a returned
8885 * bucket will be larger than maxSize.
8887 template <typename GetDeps>
8888 std::vector<std::vector<SString>>
8889 split_buckets(const std::vector<std::vector<SString>>& items,
8890 size_t maxSize,
8891 const GetDeps& getDeps) {
8892 // Split all of the buckets in parallel
8893 auto rebuckets = parallel::map(
8894 items,
8895 [&] (const std::vector<SString>& bucket) {
8896 // If there's only one thing in a bucket, there's no point in
8897 // splitting it.
8898 if (bucket.size() <= 1) return singleton_vec(bucket);
8900 // The splitting algorithm is simple. Iterate over each element
8901 // in the bucket. As long as all the dependencies are less than
8902 // the maximum size, we put it into a new bucket. If we exceed
8903 // the max size, create a new bucket.
8904 std::vector<std::vector<SString>> out;
8905 out.emplace_back();
8906 out.back().emplace_back(bucket[0]);
8908 auto allDeps = getDeps(bucket[0]);
8909 for (size_t i = 1, size = bucket.size(); i < size; ++i) {
8910 auto const& d = getDeps(bucket[i]);
8911 allDeps.insert(begin(d), end(d));
8912 auto const newSize = allDeps.size() + out.back().size() + 1;
8913 if (newSize > maxSize) {
8914 allDeps = d;
8915 out.emplace_back();
8917 out.back().emplace_back(bucket[i]);
8919 return out;
8923 // Flatten all of the new buckets into a single list of buckets.
8924 std::vector<std::vector<SString>> flattened;
8925 flattened.reserve(items.size());
8926 for (auto& r : rebuckets) {
8927 for (auto& b : r) flattened.emplace_back(std::move(b));
8929 return flattened;
8932 //////////////////////////////////////////////////////////////////////
8935 * For efficiency reasons, we often want to process classes in as few
8936 * passes as possible. However, this is tricky because the algorithms
8937 * are usually naturally iterative. You start at the root classes
8938 * (which may be the top classes in the hierarchy, or leaf classes),
8939 * and flow data down to each of their parents or children. This
8940 * requires N passes, where N is the maximum depth of the class
8941 * hierarchy. N can get large.
8943 * Instead when we process a class, we ensure that all of it's
8944 * dependencies (all the way up to the roots) are also present in the
8945 * job. Since we're doing this in one pass, none of the dependencies
8946 * will have any calculated information, and the job will have to do
8947 * this first.
8949 * It is not, in general, possible to ensure that each dependency is
8950 * present in exactly one job (because the dependency may be shared by
8951 * lots of classes which are not bucketed together). So, any given
8952 * dependency may end up on multiple jobs and have the same
8953 * information calculated for it. This is fine, as it just results in
8954 * some wasted work.
8956 * We perform flattening using the following approach:
8958 * - First we Bucketize the root classes (using the standard
8959 * consistent hashing algorithm) into N buckets.
8961 * - We split any buckets which are larger than the specified maximum
8962 * size. This prevents buckets from becoming pathologically large if
8963 * there's many dependencies.
8965 * - For each bucket, find all of the (transitive) dependencies of the
8966 * leaves and add them to that bucket (as dependencies). As stated
8967 * above, the same class may end up in multiple buckets as
8968 * dependencies.
8970 * - So far for each bucket (each bucket will map to one job), we have
8971 * a set of input classes (the roots), and all of the dependencies
8972 * for each roots.
8974 * - We want results for every class, not just the roots, so the
8975 * dependencies need to become inputs of the first kind in at least
8976 * one bucket. So, for each dependency, in one of the buckets
8977 * they're already present in, we "promote" it to a full input (and
8978 * will receive output for it). This is done by hashing the bucket
8979 * index and class name and picking the bucket that results in the
8980 * lowest hash. In some situations we don't want a dependency to
8981 * ever be promoted, so those will be skipped.
8984 // Single output bucket for assign_hierarchical_work. Each bucket
8985 // contains classes which will be processed and returned as output,
8986 // and a set of dependency classes which will just be used as inputs.
8987 struct HierarchicalWorkBucket {
8988 std::vector<SString> classes;
8989 std::vector<SString> deps;
8990 std::vector<SString> uninstantiable;
8994 * Assign work for a set of root classes (using the above
8995 * algorithm). The function is named because it's meant for situations
8996 * where we're processing classes in a "hierarchical" manner (either
8997 * from parent class to children, or from leaf class to parents).
8999 * The dependencies for each class is provided by the getDeps
9000 * callable. For the purposes of promoting a class to a full output
9001 * (see above algorithm description), each class must be assigned an
9002 * index. The (optional) index for a class is provided by the getIdx
9003 * callable. If getIdx returns std::nullopt, then that class won't be
9004 * considered for promotion. The given "numClasses" parameter is an
9005 * upper bound on the possible returned indices.
9007 template <typename GetDeps, typename GetIdx>
9008 std::vector<HierarchicalWorkBucket>
9009 build_hierarchical_work(std::vector<std::vector<SString>>& buckets,
9010 size_t numClasses,
9011 const GetDeps& getDeps,
9012 const GetIdx& getIdx) {
9013 struct DepHashState {
9014 std::mutex lock;
9015 size_t lowestHash{std::numeric_limits<size_t>::max()};
9016 size_t lowestBucket{std::numeric_limits<size_t>::max()};
9018 std::vector<DepHashState> depHashState{numClasses};
9020 // For each bucket (which right now just contains the root classes),
9021 // find all the transitive dependencies those root classes need. A
9022 // dependency might end up in multiple buckets (because multiple
9023 // roots in different buckets depend on it). We only want to
9024 // actually perform the flattening for those dependencies in one of
9025 // the buckets. So, we need a tie-breaker. We hash the name of the
9026 // dependency along with the bucket number. The bucket that the
9027 // dependency is present in with the lowest hash is what "wins".
9028 auto const bucketDeps = parallel::gen(
9029 buckets.size(),
9030 [&] (size_t bucketIdx) {
9031 assertx(bucketIdx < buckets.size());
9032 auto& bucket = buckets[bucketIdx];
9033 const TSStringSet roots{begin(bucket), end(bucket)};
9035 // Gather up all dependencies for this bucket
9036 TSStringSet deps;
9037 for (auto const cls : bucket) {
9038 auto const d = getDeps(cls).first;
9039 deps.insert(begin(*d), end(*d));
9042 // Make sure dependencies and roots are disjoint.
9043 for (auto const c : bucket) deps.erase(c);
9045 // For each dependency, store the bucket with the lowest hash.
9046 for (auto const d : deps) {
9047 auto const idx = getIdx(roots, bucketIdx, d);
9048 if (!idx.has_value()) continue;
9049 assertx(*idx < depHashState.size());
9050 auto& s = depHashState[*idx];
9051 auto const hash = hash_int64_pair(
9052 d->hashStatic(),
9053 bucketIdx
9055 std::lock_guard<std::mutex> _{s.lock};
9056 if (hash < s.lowestHash) {
9057 s.lowestHash = hash;
9058 s.lowestBucket = bucketIdx;
9059 } else if (hash == s.lowestHash) {
9060 s.lowestBucket = std::min(s.lowestBucket, bucketIdx);
9064 return deps;
9068 // Now for each bucket, "promote" dependencies into a full input
9069 // class. The dependency is promoted in the bucket with the lowest
9070 // hash, which we've already calculated.
9071 assertx(buckets.size() == bucketDeps.size());
9072 return parallel::gen(
9073 buckets.size(),
9074 [&] (size_t bucketIdx) {
9075 auto& bucket = buckets[bucketIdx];
9076 auto const& deps = bucketDeps[bucketIdx];
9077 const TSStringSet roots{begin(bucket), end(bucket)};
9079 std::vector<SString> depOut;
9080 depOut.reserve(deps.size());
9082 for (auto const d : deps) {
9083 // Calculate the hash for the dependency for this bucket. If
9084 // the hash equals the already calculated lowest hash, promote
9085 // this dependency.
9086 auto const idx = getIdx(roots, bucketIdx, d);
9087 if (!idx.has_value()) {
9088 depOut.emplace_back(d);
9089 continue;
9091 assertx(*idx < depHashState.size());
9092 auto const& s = depHashState[*idx];
9093 auto const hash = hash_int64_pair(
9094 d->hashStatic(),
9095 bucketIdx
9097 if (hash == s.lowestHash && bucketIdx == s.lowestBucket) {
9098 bucket.emplace_back(d);
9099 } else if (getDeps(d).second) {
9100 // Otherwise keep it as a dependency, but only if it's
9101 // actually instantiable.
9102 depOut.emplace_back(d);
9106 // Split off any uninstantiable classes in the bucket.
9107 auto const bucketEnd = std::partition(
9108 begin(bucket),
9109 end(bucket),
9110 [&] (SString cls) { return getDeps(cls).second; }
9112 std::vector<SString> uninstantiable{bucketEnd, end(bucket)};
9113 bucket.erase(bucketEnd, end(bucket));
9115 // Keep deterministic ordering. Make sure there's no duplicates.
9116 std::sort(bucket.begin(), bucket.end(), string_data_lt_type{});
9117 std::sort(depOut.begin(), depOut.end(), string_data_lt_type{});
9118 std::sort(uninstantiable.begin(), uninstantiable.end(),
9119 string_data_lt_type{});
9120 assertx(std::adjacent_find(bucket.begin(), bucket.end()) == bucket.end());
9121 assertx(std::adjacent_find(depOut.begin(), depOut.end()) == depOut.end());
9122 assertx(
9123 std::adjacent_find(uninstantiable.begin(), uninstantiable.end()) ==
9124 uninstantiable.end()
9127 bucket.shrink_to_fit();
9128 depOut.shrink_to_fit();
9129 uninstantiable.shrink_to_fit();
9131 return HierarchicalWorkBucket{
9132 std::move(bucket),
9133 std::move(depOut),
9134 std::move(uninstantiable)
9140 template <typename GetDeps, typename GetIdx>
9141 std::vector<HierarchicalWorkBucket>
9142 assign_hierarchical_work(std::vector<SString> roots,
9143 size_t numClasses,
9144 size_t bucketSize,
9145 size_t maxSize,
9146 const GetDeps& getDeps,
9147 const GetIdx& getIdx) {
9148 // First turn roots into buckets, and split if any exceed the
9149 // maximum size.
9150 auto buckets = split_buckets(
9151 consistently_bucketize(roots, bucketSize),
9152 maxSize,
9153 [&] (SString cls) -> const TSStringSet& {
9154 auto const [d, _] = getDeps(cls);
9155 return *d;
9158 return build_hierarchical_work(buckets, numClasses, getDeps, getIdx);
9161 //////////////////////////////////////////////////////////////////////
9162 // Class flattening:
9164 const StaticString
9165 s___Sealed("__Sealed"),
9166 s___EnableMethodTraitDiamond("__EnableMethodTraitDiamond"),
9167 s___ModuleLevelTrait("__ModuleLevelTrait");
9170 * Extern-worker job to build ClassInfo2s (which involves flattening
9171 * data across the hierarchy) and flattening traits.
9173 struct FlattenJob {
9174 static std::string name() { return "hhbbc-flatten"; }
9175 static void init(const Config& config) {
9176 process_init(config.o, config.gd, false);
9177 ClassGraph::init();
9179 static void fini() { ClassGraph::destroy(); }
9182 * Metadata representing results of flattening. This is information
9183 * that the local coordinator (as opposed to later remote jobs) will
9184 * need to consume.
9186 struct OutputMeta {
9187 // Classes which have been determined to be uninstantiable
9188 // (therefore have no result output data).
9189 TSStringSet uninstantiable;
9190 // New closures produced from trait flattening. Such new closures
9191 // will require "fixups" in the php::Program data.
9192 struct NewClosure {
9193 SString unit;
9194 SString name;
9195 SString context;
9196 template <typename SerDe> void serde(SerDe& sd) {
9197 sd(unit)(name)(context);
9200 std::vector<NewClosure> newClosures;
9201 // Report parents of each class. A class is a parent of another if
9202 // it would appear on a subclass list. The parents of a closure
9203 // are not reported because that's implicit.
9204 struct Parents {
9205 std::vector<SString> names;
9206 template <typename SerDe> void serde(SerDe& sd) { sd(names); }
9208 std::vector<Parents> parents;
9209 // Classes which are interfaces.
9210 TSStringSet interfaces;
9211 // Classes which have 86init functions. A class can gain a 86init
9212 // from flattening even if it didn't have it before.
9213 TSStringSet with86init;
9214 // The types used by the type-constraints of input classes and
9215 // functions.
9216 std::vector<TSStringSet> classTypeUses;
9217 std::vector<TSStringSet> funcTypeUses;
9218 std::vector<InterfaceConflicts> interfaceConflicts;
9219 template <typename SerDe> void serde(SerDe& sd) {
9220 ScopedStringDataIndexer _;
9221 sd(uninstantiable, string_data_lt_type{})
9222 (newClosures)
9223 (parents)
9224 (interfaces, string_data_lt_type{})
9225 (with86init, string_data_lt_type{})
9226 (classTypeUses, string_data_lt_type{})
9227 (funcTypeUses, string_data_lt_type{})
9228 (interfaceConflicts)
9234 * Job returns a list of (potentially modified) php::Class, a list
9235 * of new ClassInfo2, a list of (potentially modified) php::Func,
9236 * and metadata for the entire job. The order of the lists reflects
9237 * the order of the input classes and functions (skipping over
9238 * classes marked as uninstantiable in the metadata).
9240 using Output = Multi<
9241 Variadic<std::unique_ptr<php::Class>>,
9242 Variadic<std::unique_ptr<php::ClassBytecode>>,
9243 Variadic<std::unique_ptr<ClassInfo2>>,
9244 Variadic<std::unique_ptr<php::Func>>,
9245 Variadic<std::unique_ptr<FuncInfo2>>,
9246 Variadic<std::unique_ptr<MethodsWithoutCInfo>>,
9247 OutputMeta
9251 * Job takes a list of classes which are to be flattened. In
9252 * addition to this, it also takes a list of classes which are
9253 * dependencies of the classes to be flattened. (A class might be
9254 * one of the inputs *and* a dependency, in which case it should
9255 * just be on the input list). It is expected that *all*
9256 * dependencies are provided. All instantiable classes will have
9257 * their type-constraints resolved to their ultimate type, or left
9258 * as unresolved if it refers to a missing/invalid type. The
9259 * provided functions only have their type-constraints updated. The
9260 * provided type-mappings and list of missing types is used for
9261 * type-constraint resolution (if a type isn't in a type mapping and
9262 * isn't a missing type, it is assumed to be a object type).
9264 * Bytecode needs to be provided for every provided class. The
9265 * bytecode must be provided in the same order as the bytecode's
9266 * associated class.
9268 static Output run(Variadic<std::unique_ptr<php::Class>> classes,
9269 Variadic<std::unique_ptr<php::Class>> deps,
9270 Variadic<std::unique_ptr<php::ClassBytecode>> classBytecode,
9271 Variadic<std::unique_ptr<php::Func>> funcs,
9272 Variadic<std::unique_ptr<php::Class>> uninstantiable,
9273 std::vector<TypeMapping> typeMappings,
9274 std::vector<SString> missingTypes) {
9275 LocalIndex index;
9277 for (auto& tc : typeMappings) {
9278 auto const name = tc.name;
9279 always_assert(index.m_typeMappings.emplace(name, std::move(tc)).second);
9281 for (auto const m : missingTypes) {
9282 always_assert(index.m_missingTypes.emplace(m).second);
9284 typeMappings.clear();
9285 missingTypes.clear();
9287 // Bytecode should have been provided for every class provided.
9288 always_assert(
9289 classBytecode.vals.size() == (classes.vals.size() + deps.vals.size())
9291 // Store the provided bytecode in the matching php::Class.
9292 for (size_t i = 0,
9293 size = classBytecode.vals.size(),
9294 classesSize = classes.vals.size();
9295 i < size; ++i) {
9296 auto& cls = i < classesSize
9297 ? classes.vals[i]
9298 : deps.vals[i - classesSize];
9299 auto& bytecode = classBytecode.vals[i];
9301 // We shouldn't have closures here. They're children of the
9302 // declaring class.
9303 assertx(!cls->closureContextCls);
9304 auto const numMethods = cls->methods.size();
9305 auto const numClosures = cls->closures.size();
9306 always_assert(bytecode->methodBCs.size() == numMethods + numClosures);
9308 for (size_t j = 0, bcSize = bytecode->methodBCs.size(); j < bcSize; ++j) {
9309 if (j < numMethods) {
9310 cls->methods[j]->rawBlocks = std::move(bytecode->methodBCs[j].bc);
9311 } else {
9312 assertx(cls->closures[j-numMethods]->methods.size() == 1);
9313 cls->closures[j-numMethods]->methods[0]->rawBlocks =
9314 std::move(bytecode->methodBCs[j].bc);
9318 classBytecode.vals.clear();
9320 // Some classes might be dependencies of another. Moreover, some
9321 // classes might share dependencies. Topologically sort all of the
9322 // classes and process them in that order. Information will flow
9323 // from parent classes to their children.
9324 auto const worklist = prepare(
9325 index,
9326 [&] {
9327 TSStringToOneT<php::Class*> out;
9328 out.reserve(classes.vals.size() + deps.vals.size());
9329 for (auto const& c : classes.vals) {
9330 always_assert(out.emplace(c->name, c.get()).second);
9331 for (auto const& clo : c->closures) {
9332 always_assert(out.emplace(clo->name, clo.get()).second);
9335 for (auto const& c : deps.vals) {
9336 always_assert(out.emplace(c->name, c.get()).second);
9337 for (auto const& clo : c->closures) {
9338 always_assert(out.emplace(clo->name, clo.get()).second);
9341 return out;
9345 for (auto const& cls : uninstantiable.vals) {
9346 always_assert(index.m_uninstantiable.emplace(cls->name).second);
9347 for (auto const& clo : cls->closures) {
9348 always_assert(index.m_uninstantiable.emplace(clo->name).second);
9352 std::vector<const php::Class*> newClosures;
9354 for (auto const cls : worklist) {
9355 Trace::Bump bumper{
9356 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(cls->unit)
9359 ITRACE(2, "flatten class: {}\n", cls->name);
9360 Trace::Indent indent;
9362 index.m_ctx = cls;
9363 SCOPE_EXIT { index.m_ctx = nullptr; };
9365 auto state = std::make_unique<State>();
9366 // Attempt to make the ClassInfo2 for this class. If we can't,
9367 // it means the class is not instantiable.
9368 auto newInfo = make_info(index, *cls, *state);
9369 if (!newInfo) {
9370 ITRACE(4, "{} is not instantiable\n", cls->name);
9371 always_assert(index.m_uninstantiable.emplace(cls->name).second);
9372 continue;
9374 auto const cinfo = newInfo.get();
9376 ITRACE(5, "adding state for class '{}' to local index\n", cls->name);
9377 assertx(cinfo->name->tsame(cls->name));
9379 // We might look up this class when flattening itself, so add it
9380 // to the local index before we start.
9381 always_assert(index.m_classes.emplace(cls->name, cls).second);
9382 always_assert(
9383 index.m_classInfos.emplace(cls->name, std::move(newInfo)).second
9386 auto const [stateIt, stateSuccess] =
9387 index.m_states.emplace(cls->name, std::move(state));
9388 always_assert(stateSuccess);
9390 auto closureIdx = cls->closures.size();
9391 auto closures = flatten_traits(index, *cls, *cinfo, *stateIt->second);
9393 // Trait flattening may produce new closures, so those need to
9394 // be added to the local index as well.
9395 for (auto& i : closures) {
9396 assertx(closureIdx < cls->closures.size());
9397 auto& c = cls->closures[closureIdx++];
9398 ITRACE(5, "adding state for closure '{}' to local index\n", c->name);
9399 assertx(!is_closure(*cls));
9400 assertx(c->name->tsame(i->name));
9401 assertx(is_closure(*c));
9402 assertx(c->closureContextCls);
9403 assertx(c->closures.empty());
9404 assertx(i->closures.empty());
9405 always_assert(index.m_classes.emplace(c->name, c.get()).second);
9406 always_assert(index.m_classInfos.emplace(c->name, std::move(i)).second);
9407 newClosures.emplace_back(c.get());
9410 std::sort(
9411 begin(cls->closures), end(cls->closures),
9412 [] (const std::unique_ptr<php::Class>& c1,
9413 const std::unique_ptr<php::Class>& c2) {
9414 return string_data_lt_type{}(c1->name, c2->name);
9418 // We're done with this class. All of it's parents are now
9419 // added.
9420 cinfo->classGraph.finalizeParents();
9423 // Format the output data and put it in a deterministic order.
9424 Variadic<std::unique_ptr<php::Class>> outClasses;
9425 Variadic<std::unique_ptr<ClassInfo2>> outInfos;
9426 Variadic<std::unique_ptr<MethodsWithoutCInfo>> outMethods;
9427 OutputMeta outMeta;
9428 TSStringSet outNames;
9430 outClasses.vals.reserve(classes.vals.size());
9431 outInfos.vals.reserve(classes.vals.size());
9432 outNames.reserve(classes.vals.size());
9433 outMeta.parents.reserve(classes.vals.size());
9434 outMeta.newClosures.reserve(newClosures.size());
9436 auto const makeMethodsWithoutCInfo = [&] (const php::Class& cls) {
9437 always_assert(outMeta.uninstantiable.emplace(cls.name).second);
9438 // Even though the class is uninstantiable, we still need to
9439 // create FuncInfos for it's methods. These are stored
9440 // separately (there's no ClassInfo to store it in!)
9441 auto methods = std::make_unique<MethodsWithoutCInfo>();
9442 methods->cls = cls.name;
9443 for (auto const& func : cls.methods) {
9444 methods->finfos.emplace_back(make_func_info(index, *func));
9446 for (auto const& clo : cls.closures) {
9447 assertx(clo->methods.size() == 1);
9448 methods->closureInvokes.emplace_back(
9449 make_func_info(index, *clo->methods[0])
9452 outMethods.vals.emplace_back(std::move(methods));
9455 // Do the processing which relies on a fully accessible
9456 // LocalIndex
9458 TSStringToOneT<InterfaceConflicts> ifaceConflicts;
9459 for (auto& cls : classes.vals) {
9460 assertx(!cls->closureContextCls);
9461 auto const cinfoIt = index.m_classInfos.find(cls->name);
9462 if (cinfoIt == end(index.m_classInfos)) {
9463 ITRACE(
9464 4, "{} discovered to be not instantiable, instead "
9465 "creating MethodsWithoutCInfo for it\n",
9466 cls->name
9468 always_assert(index.uninstantiable(cls->name));
9469 makeMethodsWithoutCInfo(*cls);
9470 continue;
9472 auto& cinfo = cinfoIt->second;
9474 index.m_ctx = cls.get();
9475 SCOPE_EXIT { index.m_ctx = nullptr; };
9477 outMeta.classTypeUses.emplace_back();
9478 update_type_constraints(index, *cls, &outMeta.classTypeUses.back());
9479 optimize_properties(index, *cls, *cinfo);
9480 for (auto const& func : cls->methods) {
9481 cinfo->funcInfos.emplace_back(make_func_info(index, *func));
9484 assertx(cinfo->closures.empty());
9485 for (auto& clo : cls->closures) {
9486 auto const it = index.m_classInfos.find(clo->name);
9487 always_assert(it != end(index.m_classInfos));
9488 auto& cloinfo = it->second;
9489 update_type_constraints(index, *clo, &outMeta.classTypeUses.back());
9490 optimize_properties(index, *clo, *cloinfo);
9491 assertx(clo->methods.size() == 1);
9492 cloinfo->funcInfos.emplace_back(
9493 make_func_info(index, *clo->methods[0])
9497 outNames.emplace(cls->name);
9499 // Record interface conflicts
9501 // Only consider normal or abstract classes
9502 if (cls->attrs &
9503 (AttrInterface | AttrTrait | AttrEnum | AttrEnumClass)) {
9504 continue;
9507 auto const interfaces = cinfo->classGraph.interfaces();
9509 if (debug) {
9510 always_assert(IMPLIES(is_closure(*cls), interfaces.empty()));
9511 for (auto const& cloinfo : cinfo->closures) {
9512 always_assert(cloinfo->classGraph.interfaces().empty());
9516 for (auto const i1 : interfaces) {
9517 auto& conflicts = ifaceConflicts[i1.name()];
9518 conflicts.name = i1.name();
9519 ++conflicts.usage;
9520 for (auto const i2 : interfaces) {
9521 if (i1 == i2) continue;
9522 conflicts.conflicts.emplace(i2.name());
9527 outMeta.interfaceConflicts.reserve(ifaceConflicts.size());
9528 for (auto& [_, c] : ifaceConflicts) {
9529 outMeta.interfaceConflicts.emplace_back(std::move(c));
9531 std::sort(
9532 begin(outMeta.interfaceConflicts),
9533 end(outMeta.interfaceConflicts),
9534 [] (auto const& c1, auto const& c2) {
9535 return string_data_lt_type{}(c1.name, c2.name);
9539 // We don't process classes marked as uninstantiable beforehand,
9540 // except for creating method FuncInfos for them.
9541 for (auto const& cls : uninstantiable.vals) {
9542 ITRACE(
9543 4, "{} already known to be not instantiable, creating "
9544 "MethodsWithoutCInfo for it\n",
9545 cls->name
9547 makeMethodsWithoutCInfo(*cls);
9550 // Now move the classes out of LocalIndex and into the output. At
9551 // this point, it's not safe to access the LocalIndex unless
9552 // you're sure something hasn't been moved yet.
9553 for (auto& cls : classes.vals) {
9554 auto const name = cls->name;
9556 auto const cinfoIt = index.m_classInfos.find(name);
9557 if (cinfoIt == end(index.m_classInfos)) {
9558 assertx(outMeta.uninstantiable.count(name));
9559 continue;
9561 auto& cinfo = cinfoIt->second;
9563 // Check if this class has a 86*init function (it might have
9564 // already or might have gained one from trait flattening).
9565 auto const has86init =
9566 std::any_of(
9567 begin(cls->methods), end(cls->methods),
9568 [] (auto const& m) { return is_86init_func(*m); }
9569 ) ||
9570 std::any_of(
9571 begin(cinfo->clsConstants), end(cinfo->clsConstants),
9572 [] (auto const& cns) {
9573 return cns.second.kind == ConstModifiers::Kind::Type;
9576 if (has86init) {
9577 assertx(!is_closure(*cls));
9578 outMeta.with86init.emplace(name);
9581 index.m_ctx = cls.get();
9582 SCOPE_EXIT { index.m_ctx = nullptr; };
9584 // For building FuncFamily::StaticInfo, we need to ensure that
9585 // every method has an entry in methodFamilies. Make all of the
9586 // initial entries here (they'll be created assuming this method
9587 // is AttrNoOverride).
9588 for (auto const& [methname, mte] : cinfo->methods) {
9589 if (is_special_method_name(methname)) continue;
9590 auto entry = make_initial_func_family_entry(*cls, index.meth(mte), mte);
9591 always_assert(
9592 cinfo->methodFamilies.emplace(methname, std::move(entry)).second
9596 if (!is_closure(*cls)) {
9597 auto const& state = index.m_states.at(name);
9598 outMeta.parents.emplace_back();
9599 auto& parents = outMeta.parents.back().names;
9600 parents.reserve(state->m_parents.size());
9601 for (auto const p : state->m_parents) {
9602 parents.emplace_back(p->name);
9606 if (cls->attrs & AttrInterface) {
9607 outMeta.interfaces.emplace(name);
9610 // We always know the subclass status of closures and the
9611 // closure base class.
9612 if (is_closure_base(*cls)) {
9613 cinfo->classGraph.setClosureBase();
9614 } else if (is_closure(*cls)) {
9615 cinfo->classGraph.setComplete();
9618 assertx(cinfo->closures.empty());
9619 for (auto& clo : cls->closures) {
9620 auto const it = index.m_classInfos.find(clo->name);
9621 always_assert(it != end(index.m_classInfos));
9622 auto& cloinfo = it->second;
9624 // Closures are always leafs.
9625 cloinfo->classGraph.setComplete();
9626 assertx(!cloinfo->classGraph.mightHaveRegularSubclass());
9627 assertx(!cloinfo->classGraph.mightHaveNonRegularSubclass());
9629 for (auto const& [methname, mte] : cloinfo->methods) {
9630 if (is_special_method_name(methname)) continue;
9631 auto entry =
9632 make_initial_func_family_entry(*clo, index.meth(mte), mte);
9633 always_assert(
9634 cloinfo->methodFamilies.emplace(methname, std::move(entry)).second
9638 cinfo->closures.emplace_back(std::move(cloinfo));
9641 outClasses.vals.emplace_back(std::move(cls));
9642 outInfos.vals.emplace_back(std::move(cinfo));
9645 std::sort(
9646 begin(newClosures), end(newClosures),
9647 [] (const php::Class* c1, const php::Class* c2) {
9648 return string_data_lt_type{}(c1->name, c2->name);
9651 for (auto clo : newClosures) {
9652 assertx(clo->closureContextCls);
9653 if (!outNames.count(clo->closureContextCls)) continue;
9654 outMeta.newClosures.emplace_back(
9655 OutputMeta::NewClosure{clo->unit, clo->name, clo->closureContextCls}
9659 Variadic<std::unique_ptr<FuncInfo2>> funcInfos;
9660 funcInfos.vals.reserve(funcs.vals.size());
9661 for (auto& func : funcs.vals) {
9662 outMeta.funcTypeUses.emplace_back();
9663 update_type_constraints(index, *func, &outMeta.funcTypeUses.back());
9664 funcInfos.vals.emplace_back(make_func_info(index, *func));
9667 // Provide any updated bytecode back to the caller.
9668 Variadic<std::unique_ptr<php::ClassBytecode>> outBytecode;
9669 outBytecode.vals.reserve(outClasses.vals.size());
9670 for (auto& cls : outClasses.vals) {
9671 auto bytecode = std::make_unique<php::ClassBytecode>();
9672 bytecode->cls = cls->name;
9673 bytecode->methodBCs.reserve(cls->methods.size());
9674 for (auto& method : cls->methods) {
9675 bytecode->methodBCs.emplace_back(
9676 method->name,
9677 std::move(method->rawBlocks)
9680 for (auto& clo : cls->closures) {
9681 assertx(clo->methods.size() == 1);
9682 auto& method = clo->methods[0];
9683 bytecode->methodBCs.emplace_back(
9684 method->name,
9685 std::move(method->rawBlocks)
9688 outBytecode.vals.emplace_back(std::move(bytecode));
9691 return std::make_tuple(
9692 std::move(outClasses),
9693 std::move(outBytecode),
9694 std::move(outInfos),
9695 std::move(funcs),
9696 std::move(funcInfos),
9697 std::move(outMethods),
9698 std::move(outMeta)
9702 private:
9704 * State which needs to be propagated from a dependency to a child
9705 * class during flattening, but not required after flattening (so
9706 * doesn't belong in ClassInfo2).
9708 struct State {
9709 struct PropTuple {
9710 SString name;
9711 SString src;
9712 php::Prop prop;
9714 // Maintain order of properties as we inherit them.
9715 CompactVector<PropTuple> m_props;
9716 SStringToOneT<size_t> m_propIndices;
9717 CompactVector<php::Const> m_traitCns;
9718 SStringSet m_cnsFromTrait;
9719 SStringToOneT<size_t> m_methodIndices;
9720 CompactVector<const php::Class*> m_parents;
9722 size_t& methodIdx(SString context, SString cls, SString name) {
9723 auto const it = m_methodIndices.find(name);
9724 always_assert_flog(
9725 it != m_methodIndices.end(),
9726 "While processing '{}', "
9727 "tried to access missing method index for '{}::{}'",
9728 context, cls, name
9730 return it->second;
9733 size_t methodIdx(SString context, SString cls, SString name) const {
9734 return const_cast<State*>(this)->methodIdx(context, cls, name);
9739 * LocalIndex is similar to Index, but for this job. It maps names
9740 * to class information needed during flattening. It also verifies
9741 * we don't try to access information about a class until it's
9742 * actually available (which shouldn't happen if our dataflow is
9743 * correct).
9745 struct LocalIndex {
9746 const php::Class* m_ctx{nullptr};
9748 TSStringToOneT<const php::Class*> m_classes;
9749 TSStringToOneT<std::unique_ptr<ClassInfo2>> m_classInfos;
9750 TSStringToOneT<std::unique_ptr<State>> m_states;
9752 TSStringSet m_uninstantiable;
9754 TSStringToOneT<TypeMapping> m_typeMappings;
9755 TSStringSet m_missingTypes;
9757 const php::Class& cls(SString name) const {
9758 if (m_ctx->name->tsame(name)) return *m_ctx;
9759 auto const it = m_classes.find(name);
9760 always_assert_flog(
9761 it != m_classes.end(),
9762 "While processing '{}', tried to access missing class '{}' from index",
9763 m_ctx->name,
9764 name
9766 assertx(it->second);
9767 return *it->second;
9770 const ClassInfo2& classInfo(SString name) const {
9771 auto const it = m_classInfos.find(name);
9772 always_assert_flog(
9773 it != m_classInfos.end(),
9774 "While processing '{}', tried to access missing class-info for '{}' "
9775 "from index",
9776 m_ctx->name,
9777 name
9779 assertx(it->second.get());
9780 return *it->second;
9783 const State& state(SString name) const {
9784 auto const it = m_states.find(name);
9785 always_assert_flog(
9786 it != m_states.end(),
9787 "While processing '{}', tried to access missing flatten state for '{}' "
9788 "from index",
9789 m_ctx->name,
9790 name
9792 assertx(it->second.get());
9793 return *it->second;
9796 bool uninstantiable(SString name) const {
9797 return m_uninstantiable.count(name);
9800 const TypeMapping* typeMapping(SString name) const {
9801 return folly::get_ptr(m_typeMappings, name);
9804 bool missingType(SString name) const {
9805 return m_missingTypes.count(name);
9808 const php::Func& meth(const MethRef& r) const {
9809 auto const& mcls = cls(r.cls);
9810 assertx(r.idx < mcls.methods.size());
9811 return *mcls.methods[r.idx];
9813 const php::Func& meth(const MethTabEntry& mte) const {
9814 return meth(mte.meth());
9817 const php::Const& cns(const ConstIndex& idx) const {
9818 auto const& c = cls(idx.cls);
9819 assertx(idx.idx < c.constants.size());
9820 return c.constants[idx.idx];
9823 size_t methodIdx(SString cls, SString name) const {
9824 return state(cls).methodIdx(m_ctx->name, cls, name);
9829 * Calculate the order in which the classes should be flattened,
9830 * taking into account dependencies.
9832 static std::vector<php::Class*> prepare(
9833 LocalIndex& index,
9834 const TSStringToOneT<php::Class*>& classes
9836 // We might not have any classes if we're just processing funcs.
9837 if (classes.empty()) return {};
9839 auto const get = [&] (SString name) -> php::Class& {
9840 auto const it = classes.find(name);
9841 always_assert_flog(
9842 it != classes.end(),
9843 "Tried to access missing class '{}' while calculating flattening order",
9844 name
9846 return *it->second;
9849 auto const forEachDep = [&] (php::Class& c, auto const& f) {
9850 if (c.parentName) f(get(c.parentName));
9851 for (auto const i : c.interfaceNames) f(get(i));
9852 for (auto const e : c.includedEnumNames) f(get(e));
9853 for (auto const t : c.usedTraitNames) f(get(t));
9854 for (auto const& clo : c.closures) {
9855 f(const_cast<php::Class&>(*clo));
9860 * Perform a standard topological sort:
9862 * - For each class, calculate the number of classes which depend on it.
9864 * - Any class which has a use count of zero is not depended on by
9865 * anyone and goes onto the intitial worklist.
9867 * - For every class on the worklist, push it onto the output
9868 * list, and decrement the use count of all of it's
9869 * dependencies.
9871 * - For any class which now has a use count of zero, push it onto
9872 * the worklist and repeat above step until all classes are
9873 * pushed onto the output list.
9875 * - Reverse the list.
9877 * - This does not handle cycles, but we should not encounter any
9878 * here, as such cycles should be detected earlier and not be
9879 * scheduled in a job.
9881 hphp_fast_map<const php::Class*, size_t> uses;
9882 uses.reserve(classes.size());
9883 for (auto const& [_, cls] : classes) {
9884 forEachDep(*cls, [&] (const php::Class& d) { ++uses[&d]; });
9887 std::vector<php::Class*> worklist;
9888 for (auto const [_, cls] : classes) {
9889 if (!uses[cls]) worklist.emplace_back(cls);
9891 always_assert(!worklist.empty());
9892 std::sort(
9893 worklist.begin(),
9894 worklist.end(),
9895 [] (const php::Class* c1, const php::Class* c2) {
9896 return string_data_lt_type{}(c1->name, c2->name);
9900 std::vector<php::Class*> ordered;
9901 ordered.reserve(classes.size());
9902 do {
9903 auto const cls = worklist.back();
9904 assertx(!uses[cls]);
9905 worklist.pop_back();
9906 forEachDep(
9907 *cls,
9908 [&] (php::Class& d) {
9909 if (!--uses.at(&d)) worklist.emplace_back(&d);
9912 ordered.emplace_back(cls);
9913 } while (!worklist.empty());
9915 for (auto const& [_, cls] : classes) always_assert(!uses.at(cls));
9916 std::reverse(ordered.begin(), ordered.end());
9917 return ordered;
9921 * Create a FuncFamilyEntry for the give method. This
9922 * FuncFamilyEntry assumes that the method is AttrNoOverride, and
9923 * hence reflects just this method. The method isn't necessarily
9924 * actually AttrNoOverride, but if not, it will be updated in
9925 * BuildSubclassListJob (that job needs the initial entries).
9927 static FuncFamilyEntry make_initial_func_family_entry(
9928 const php::Class& cls,
9929 const php::Func& meth,
9930 const MethTabEntry& mte
9932 FuncFamilyEntry entry;
9933 entry.m_allIncomplete = false;
9934 entry.m_regularIncomplete = false;
9935 entry.m_privateAncestor = is_regular_class(cls) && mte.hasPrivateAncestor();
9937 FuncFamilyEntry::MethMetadata meta;
9939 for (size_t i = 0; i < meth.params.size(); ++i) {
9940 meta.m_prepKinds.emplace_back(func_param_prep(&meth, i));
9942 // Any param beyond the size of m_paramPreps is implicitly
9943 // TriBool::No, so we can drop trailing entries which are
9944 // TriBool::No.
9945 while (!meta.m_prepKinds.empty()) {
9946 auto& back = meta.m_prepKinds.back();
9947 if (back.inOut != TriBool::No || back.readonly != TriBool::No) break;
9948 meta.m_prepKinds.pop_back();
9950 meta.m_numInOut = func_num_inout(&meth);
9951 meta.m_nonVariadicParams = numNVArgs(meth);
9952 meta.m_coeffectRules = meth.coeffectRules;
9953 meta.m_requiredCoeffects = meth.requiredCoeffects;
9954 meta.m_isReadonlyReturn = meth.isReadonlyReturn;
9955 meta.m_isReadonlyThis = meth.isReadonlyThis;
9956 meta.m_supportsAER = func_supports_AER(&meth);
9957 meta.m_isReified = meth.isReified;
9958 meta.m_caresAboutDyncalls = (dyn_call_error_level(&meth) > 0);
9959 meta.m_builtin = meth.attrs & AttrBuiltin;
9961 if (is_regular_class(cls)) {
9962 entry.m_meths =
9963 FuncFamilyEntry::BothSingle{mte.meth(), std::move(meta), false};
9964 } else if (bool(mte.attrs & AttrPrivate) && mte.topLevel()) {
9965 entry.m_meths =
9966 FuncFamilyEntry::BothSingle{mte.meth(), std::move(meta), true};
9967 } else {
9968 entry.m_meths =
9969 FuncFamilyEntry::SingleAndNone{mte.meth(), std::move(meta)};
9972 return entry;
9975 static std::unique_ptr<ClassInfo2> make_info(const LocalIndex& index,
9976 php::Class& cls,
9977 State& state) {
9978 if (debug && (is_closure(cls) || is_closure_base(cls))) {
9979 if (is_closure(cls)) {
9980 always_assert(cls.parentName->tsame(s_Closure.get()));
9981 } else {
9982 always_assert(!cls.parentName);
9984 always_assert(cls.interfaceNames.empty());
9985 always_assert(cls.includedEnumNames.empty());
9986 always_assert(cls.usedTraitNames.empty());
9987 always_assert(cls.requirements.empty());
9988 always_assert(cls.constants.empty());
9989 always_assert(cls.userAttributes.empty());
9990 always_assert(!(cls.attrs & (AttrTrait | AttrInterface | AttrAbstract)));
9993 // Set up some initial values for ClassInfo properties. If this
9994 // class is a leaf (we can't actually determine that yet), these
9995 // will be valid and remain as-is. If not, they'll be updated
9996 // properly when calculating subclass information in another pass.
9997 auto cinfo = std::make_unique<ClassInfo2>();
9998 cinfo->name = cls.name;
9999 cinfo->hasConstProp = cls.hasConstProp;
10000 cinfo->hasReifiedParent = cls.hasReifiedGenerics;
10001 cinfo->hasReifiedGeneric = cls.userAttributes.count(s___Reified.get());
10002 cinfo->subHasReifiedGeneric = cinfo->hasReifiedGeneric;
10003 cinfo->initialNoReifiedInit = cls.attrs & AttrNoReifiedInit;
10004 cinfo->isMockClass = is_mock_class(&cls);
10005 cinfo->isRegularClass = is_regular_class(cls);
10007 // Create a ClassGraph for this class. If we decide to not keep
10008 // the ClassInfo, reset the ClassGraph to keep it from ending up
10009 // in the graph.
10010 cinfo->classGraph = ClassGraph::create(cls);
10011 auto success = false;
10012 SCOPE_EXIT { if (!success) cinfo->classGraph.reset(); };
10014 // Assume the class isn't overridden. This is true for leafs and
10015 // non-leafs will get updated when we build subclass information.
10016 if (!is_closure_base(cls)) {
10017 attribute_setter(cls.attrs, true, AttrNoOverride);
10018 attribute_setter(cls.attrs, true, AttrNoOverrideRegular);
10019 } else {
10020 attribute_setter(cls.attrs, false, AttrNoOverride);
10021 attribute_setter(cls.attrs, false, AttrNoOverrideRegular);
10024 // Assume this. If not a leaf, will be updated in
10025 // BuildSubclassList job.
10026 attribute_setter(cls.attrs, true, AttrNoMock);
10028 for (auto const& clo : cls.closures) {
10029 if (index.uninstantiable(clo->name)) {
10030 ITRACE(2,
10031 "Making class-info failed for `{}' because "
10032 "its closure `{}' is uninstantiable\n",
10033 cls.name, clo->name);
10034 return nullptr;
10038 if (cls.parentName) {
10039 assertx(!is_closure_base(cls));
10040 assertx(is_closure(cls) == cls.parentName->tsame(s_Closure.get()));
10042 if (index.uninstantiable(cls.parentName)) {
10043 ITRACE(2,
10044 "Making class-info failed for `{}' because "
10045 "its parent `{}' is uninstantiable\n",
10046 cls.name, cls.parentName);
10047 return nullptr;
10049 auto const& parent = index.cls(cls.parentName);
10050 auto const& parentInfo = index.classInfo(cls.parentName);
10052 assertx(!is_closure(parent));
10053 if (parent.attrs & (AttrInterface | AttrTrait)) {
10054 ITRACE(2,
10055 "Making class-info failed for `{}' because "
10056 "its parent `{}' is not a class\n",
10057 cls.name, cls.parentName);
10058 return nullptr;
10060 if (!enforce_sealing(*cinfo, cls, parent)) return nullptr;
10062 cinfo->parent = cls.parentName;
10063 cinfo->hasConstProp |= parentInfo.hasConstProp;
10064 cinfo->hasReifiedParent |= parentInfo.hasReifiedParent;
10066 state.m_parents.emplace_back(&parent);
10067 cinfo->classGraph.setBase(parentInfo.classGraph);
10068 } else if (!cinfo->hasReifiedGeneric) {
10069 attribute_setter(cls.attrs, true, AttrNoReifiedInit);
10072 for (auto const iname : cls.interfaceNames) {
10073 assertx(!is_closure(cls));
10074 assertx(!is_closure_base(cls));
10075 if (index.uninstantiable(iname)) {
10076 ITRACE(2,
10077 "Making class-info failed for `{}' because "
10078 "{} is uninstantiable\n",
10079 cls.name, iname);
10080 return nullptr;
10082 auto const& iface = index.cls(iname);
10083 auto const& ifaceInfo = index.classInfo(iname);
10085 assertx(!is_closure(iface));
10086 if (!(iface.attrs & AttrInterface)) {
10087 ITRACE(2,
10088 "Making class-info failed for `{}' because `{}' "
10089 "is not an interface\n",
10090 cls.name, iname);
10091 return nullptr;
10093 if (!enforce_sealing(*cinfo, cls, iface)) return nullptr;
10095 cinfo->hasReifiedParent |= ifaceInfo.hasReifiedParent;
10097 state.m_parents.emplace_back(&iface);
10098 cinfo->classGraph.addParent(ifaceInfo.classGraph);
10101 for (auto const ename : cls.includedEnumNames) {
10102 assertx(!is_closure(cls));
10103 assertx(!is_closure_base(cls));
10104 if (index.uninstantiable(ename)) {
10105 ITRACE(2,
10106 "Making class-info failed for `{}' because "
10107 "{} is uninstantiable\n",
10108 cls.name, ename);
10109 return nullptr;
10111 auto const& e = index.cls(ename);
10112 auto const& einfo = index.classInfo(ename);
10114 assertx(!is_closure(e));
10115 auto const wantAttr = cls.attrs & (AttrEnum | AttrEnumClass);
10116 if (!(e.attrs & wantAttr)) {
10117 ITRACE(2,
10118 "Making class-info failed for `{}' because `{}' "
10119 "is not an enum{}\n",
10120 cls.name, ename,
10121 wantAttr & AttrEnumClass ? " class" : "");
10122 return nullptr;
10124 if (!enforce_sealing(*cinfo, cls, e)) return nullptr;
10126 for (auto const iface : einfo.classGraph.declInterfaces()) {
10127 cinfo->classGraph.addParent(iface);
10131 auto const clsHasModuleLevelTrait =
10132 cls.userAttributes.count(s___ModuleLevelTrait.get());
10133 if (clsHasModuleLevelTrait &&
10134 (!(cls.attrs & AttrTrait) || (cls.attrs & AttrInternal))) {
10135 ITRACE(2,
10136 "Making class-info failed for `{}' because "
10137 "attribute <<__ModuleLevelTrait>> can only be "
10138 "specified on public traits\n",
10139 cls.name);
10140 return nullptr;
10143 for (auto const tname : cls.usedTraitNames) {
10144 assertx(!is_closure(cls));
10145 assertx(!is_closure_base(cls));
10146 if (index.uninstantiable(tname)) {
10147 ITRACE(2,
10148 "Making class-info failed for `{}' because "
10149 "{} is uninstantiable\n",
10150 cls.name, tname);
10151 return nullptr;
10153 auto const& trait = index.cls(tname);
10154 auto const& traitInfo = index.classInfo(tname);
10156 assertx(!is_closure(trait));
10157 if (!(trait.attrs & AttrTrait)) {
10158 ITRACE(2,
10159 "Making class-info failed for `{}' because `{}' "
10160 "is not a trait\n",
10161 cls.name, tname);
10162 return nullptr;
10164 if (!enforce_sealing(*cinfo, cls, trait)) return nullptr;
10166 cinfo->hasConstProp |= traitInfo.hasConstProp;
10167 cinfo->hasReifiedParent |= traitInfo.hasReifiedParent;
10169 state.m_parents.emplace_back(&trait);
10170 cinfo->classGraph.addParent(traitInfo.classGraph);
10173 if (cls.attrs & AttrEnum) {
10174 auto const baseType = [&] {
10175 auto const& base = cls.enumBaseTy;
10176 if (!base.isUnresolved()) return base.type();
10177 auto const tm = index.typeMapping(base.typeName());
10178 if (!tm) return AnnotType::Unresolved;
10179 // enums cannot use case types
10180 assertx(!tm->value.isUnion());
10181 return tm->value.type();
10182 }();
10183 if (!enumSupportsAnnot(baseType)) {
10184 ITRACE(2,
10185 "Making class-info failed for `{}' because {} "
10186 "is not a valid enum base type\n",
10187 cls.name, annotName(baseType));
10188 return nullptr;
10192 if (!build_methods(index, cls, *cinfo, state)) return nullptr;
10193 if (!build_properties(index, cls, *cinfo, state)) return nullptr;
10194 if (!build_constants(index, cls, *cinfo, state)) return nullptr;
10196 std::sort(
10197 begin(state.m_parents),
10198 end(state.m_parents),
10199 [] (const php::Class* a, const php::Class* b) {
10200 return string_data_lt_type{}(a->name, b->name);
10203 state.m_parents.erase(
10204 std::unique(begin(state.m_parents), end(state.m_parents)),
10205 end(state.m_parents)
10207 assertx(
10208 std::none_of(
10209 begin(state.m_parents), end(state.m_parents),
10210 [] (const php::Class* c) { return is_closure(*c); }
10214 cinfo->subHasConstProp = cinfo->hasConstProp;
10216 // All methods are originally not overridden (we'll update this as
10217 // necessary later), except for special methods, which are always
10218 // considered to be overridden.
10219 for (auto& [name, mte] : cinfo->methods) {
10220 assertx(!cinfo->missingMethods.count(name));
10221 if (is_special_method_name(name)) {
10222 attribute_setter(mte.attrs, false, AttrNoOverride);
10223 mte.clearNoOverrideRegular();
10224 } else {
10225 attribute_setter(mte.attrs, true, AttrNoOverride);
10226 mte.setNoOverrideRegular();
10230 // We don't calculate subclass information for closures, so make
10231 // sure their initial values are all what they should be.
10232 if (debug && (is_closure(cls) || is_closure_base(cls))) {
10233 if (is_closure(cls)) {
10234 always_assert(is_closure_name(cls.name));
10235 always_assert(state.m_parents.size() == 1);
10236 always_assert(state.m_parents[0]->name->tsame(s_Closure.get()));
10237 always_assert(!(cls.attrs & AttrNoReifiedInit));
10238 } else {
10239 always_assert(state.m_parents.empty());
10240 always_assert(cls.attrs & AttrNoReifiedInit);
10242 always_assert(cinfo->missingMethods.empty());
10243 always_assert(!cinfo->hasConstProp);
10244 always_assert(!cinfo->subHasConstProp);
10245 always_assert(!cinfo->hasReifiedParent);
10246 always_assert(!cinfo->hasReifiedGeneric);
10247 always_assert(!cinfo->subHasReifiedGeneric);
10248 always_assert(!cinfo->initialNoReifiedInit);
10249 always_assert(!cinfo->isMockClass);
10250 always_assert(cinfo->isRegularClass);
10251 always_assert(!is_mock_class(&cls));
10254 ITRACE(2, "new class-info: {}\n", cls.name);
10255 if (Trace::moduleEnabled(Trace::hhbbc_index, 3)) {
10256 if (cinfo->parent) {
10257 ITRACE(3, " parent: {}\n", cinfo->parent);
10259 auto const cg = cinfo->classGraph;
10260 for (auto const DEBUG_ONLY base : cg.bases()) {
10261 ITRACE(3, " base: {}\n", base.name());
10263 for (auto const DEBUG_ONLY iface : cls.interfaceNames) {
10264 ITRACE(3, " decl implements: {}\n", iface);
10266 for (auto const DEBUG_ONLY iface : cg.interfaces()) {
10267 ITRACE(3, " implements: {}\n", iface.name());
10269 for (auto const DEBUG_ONLY e : cls.includedEnumNames) {
10270 ITRACE(3, " enum: {}\n", e);
10272 for (auto const DEBUG_ONLY trait : cls.usedTraitNames) {
10273 ITRACE(3, " uses: {}\n", trait);
10275 for (auto const& DEBUG_ONLY closure : cls.closures) {
10276 ITRACE(3, " closure: {}\n", closure->name);
10280 // We're going to use this ClassInfo.
10281 success = true;
10282 return cinfo;
10285 static bool enforce_sealing(const ClassInfo2& cinfo,
10286 const php::Class& cls,
10287 const php::Class& parent) {
10288 if (is_mock_class(&cls)) return true;
10289 if (!(parent.attrs & AttrSealed)) return true;
10290 auto const it = parent.userAttributes.find(s___Sealed.get());
10291 assertx(it != parent.userAttributes.end());
10292 assertx(tvIsArrayLike(it->second));
10293 auto allowed = false;
10294 IterateV(
10295 it->second.m_data.parr,
10296 [&] (TypedValue v) {
10297 assertx(tvIsStringLike(v));
10298 if (tvAssertStringLike(v)->tsame(cinfo.name)) {
10299 allowed = true;
10300 return true;
10302 return false;
10305 if (!allowed) {
10306 ITRACE(
10308 "Making class-info failed for `{}' because "
10309 "`{}' is sealed\n",
10310 cinfo.name, parent.name
10313 return allowed;
10316 static bool build_properties(const LocalIndex& index,
10317 const php::Class& cls,
10318 ClassInfo2& cinfo,
10319 State& state) {
10320 if (cls.parentName) {
10321 auto const& parentState = index.state(cls.parentName);
10322 state.m_props = parentState.m_props;
10323 state.m_propIndices = parentState.m_propIndices;
10326 for (auto const iface : cls.interfaceNames) {
10327 if (!merge_properties(cinfo, state, index.state(iface))) {
10328 return false;
10331 for (auto const trait : cls.usedTraitNames) {
10332 if (!merge_properties(cinfo, state, index.state(trait))) {
10333 return false;
10336 for (auto const e : cls.includedEnumNames) {
10337 if (!merge_properties(cinfo, state, index.state(e))) {
10338 return false;
10342 if (cls.attrs & AttrInterface) return true;
10344 auto const cannotDefineInternalProperties =
10345 // public traits cannot define internal properties unless they
10346 // have the __ModuleLevelTrait attribute
10347 ((cls.attrs & AttrTrait) && (cls.attrs & AttrPublic)) &&
10348 !(cls.userAttributes.count(s___ModuleLevelTrait.get()));
10350 for (auto const& p : cls.properties) {
10351 if (cannotDefineInternalProperties && (p.attrs & AttrInternal)) {
10352 ITRACE(2,
10353 "Adding property failed for `{}' because property `{}' "
10354 "is internal and public traits cannot define internal properties\n",
10355 cinfo.name, p.name);
10356 return false;
10358 if (!add_property(cinfo, state, p.name, p, cinfo.name, false)) {
10359 return false;
10363 // There's no need to do this work if traits have been flattened
10364 // already, or if the top level class has no traits. In those
10365 // cases, we might be able to rule out some instantiations, but it
10366 // doesn't seem worth it.
10367 if (cls.attrs & AttrNoExpandTrait) return true;
10369 for (auto const traitName : cls.usedTraitNames) {
10370 auto const& trait = index.cls(traitName);
10371 auto const& traitInfo = index.classInfo(traitName);
10372 for (auto const& p : trait.properties) {
10373 if (!add_property(cinfo, state, p.name, p, cinfo.name, true)) {
10374 return false;
10377 for (auto const& p : traitInfo.traitProps) {
10378 if (!add_property(cinfo, state, p.name, p, cinfo.name, true)) {
10379 return false;
10384 return true;
10387 static bool add_property(ClassInfo2& cinfo,
10388 State& state,
10389 SString name,
10390 const php::Prop& prop,
10391 SString src,
10392 bool trait) {
10393 auto const [it, emplaced] =
10394 state.m_propIndices.emplace(name, state.m_props.size());
10395 if (emplaced) {
10396 state.m_props.emplace_back(State::PropTuple{name, src, prop});
10397 if (trait) cinfo.traitProps.emplace_back(prop);
10398 return true;
10400 assertx(it->second < state.m_props.size());
10401 auto& prevTuple = state.m_props[it->second];
10402 auto const& prev = prevTuple.prop;
10403 auto const prevSrc = prevTuple.src;
10405 if (cinfo.name->tsame(prevSrc)) {
10406 if ((prev.attrs ^ prop.attrs) &
10407 (AttrStatic | AttrPublic | AttrProtected | AttrPrivate) ||
10408 (!(prop.attrs & AttrSystemInitialValue) &&
10409 !(prev.attrs & AttrSystemInitialValue) &&
10410 !Class::compatibleTraitPropInit(prev.val, prop.val))) {
10411 ITRACE(2,
10412 "Adding property failed for `{}' because "
10413 "two declarations of `{}' at the same level had "
10414 "different attributes\n",
10415 cinfo.name, prop.name);
10416 return false;
10418 return true;
10421 if (!(prev.attrs & AttrPrivate)) {
10422 if ((prev.attrs ^ prop.attrs) & AttrStatic) {
10423 ITRACE(2,
10424 "Adding property failed for `{}' because "
10425 "`{}' was defined both static and non-static\n",
10426 cinfo.name, prop.name);
10427 return false;
10429 if (prop.attrs & AttrPrivate) {
10430 ITRACE(2,
10431 "Adding property failed for `{}' because "
10432 "`{}' was re-declared private\n",
10433 cinfo.name, prop.name);
10434 return false;
10436 if (prop.attrs & AttrProtected && !(prev.attrs & AttrProtected)) {
10437 ITRACE(2,
10438 "Adding property failed for `{}' because "
10439 "`{}' was redeclared protected from public\n",
10440 cinfo.name, prop.name);
10441 return false;
10445 if (trait) cinfo.traitProps.emplace_back(prop);
10446 prevTuple = State::PropTuple{name, src, prop};
10447 return true;
10450 static bool merge_properties(ClassInfo2& cinfo,
10451 State& dst,
10452 const State& src) {
10453 for (auto const& [name, src, prop] : src.m_props) {
10454 if (!add_property(cinfo, dst, name, prop, src, false)) {
10455 return false;
10458 return true;
10461 static bool build_constants(const LocalIndex& index,
10462 php::Class& cls,
10463 ClassInfo2& cinfo,
10464 State& state) {
10465 if (cls.parentName) {
10466 cinfo.clsConstants = index.classInfo(cls.parentName).clsConstants;
10467 state.m_cnsFromTrait = index.state(cls.parentName).m_cnsFromTrait;
10470 for (auto const iname : cls.interfaceNames) {
10471 auto const& iface = index.classInfo(iname);
10472 auto const& ifaceState = index.state(iname);
10473 for (auto const& [cnsName, cnsIdx] : iface.clsConstants) {
10474 auto const added = add_constant(
10475 index, cinfo, state, cnsName,
10476 cnsIdx, ifaceState.m_cnsFromTrait.count(cnsName)
10478 if (!added) return false;
10482 auto const addShallowConstants = [&] {
10483 auto const numConstants = cls.constants.size();
10484 for (uint32_t idx = 0; idx < numConstants; ++idx) {
10485 auto const& cns = cls.constants[idx];
10486 auto const added = add_constant(
10487 index, cinfo, state,
10488 cns.name,
10489 ClassInfo2::ConstIndexAndKind {
10490 ConstIndex { cls.name, idx },
10491 cns.kind
10493 false
10495 if (!added) return false;
10497 return true;
10500 auto const addTraitConstants = [&] {
10501 for (auto const tname : cls.usedTraitNames) {
10502 auto const& trait = index.classInfo(tname);
10503 for (auto const& [cnsName, cnsIdx] : trait.clsConstants) {
10504 auto const added = add_constant(
10505 index, cinfo, state, cnsName,
10506 cnsIdx, true
10508 if (!added) return false;
10511 return true;
10514 if (Cfg::Eval::TraitConstantInterfaceBehavior) {
10515 // trait constants must be inserted before constants shallowly
10516 // declared on the class to match the interface semantics
10517 if (!addTraitConstants()) return false;
10518 if (!addShallowConstants()) return false;
10519 } else {
10520 if (!addShallowConstants()) return false;
10521 if (!addTraitConstants()) return false;
10524 for (auto const ename : cls.includedEnumNames) {
10525 auto const& e = index.classInfo(ename);
10526 for (auto const& [cnsName, cnsIdx] : e.clsConstants) {
10527 auto const added = add_constant(
10528 index, cinfo, state, cnsName,
10529 cnsIdx, false
10531 if (!added) return false;
10535 auto const addTraitConst = [&] (const php::Const& c) {
10537 * Only copy in constants that win. Otherwise, in the runtime, if
10538 * we have a constant from an interface implemented by a trait
10539 * that wins over this fromTrait constant, we won't know which
10540 * trait it came from, and therefore won't know which constant
10541 * should win. Dropping losing constants here works because if
10542 * they fatal with constants in declared interfaces, we catch that
10543 * above.
10545 auto const& existing = cinfo.clsConstants.find(c.name);
10546 if (existing->second.idx.cls->tsame(c.cls)) {
10547 state.m_traitCns.emplace_back(c);
10548 state.m_traitCns.back().isFromTrait = true;
10551 for (auto const tname : cls.usedTraitNames) {
10552 auto const& trait = index.cls(tname);
10553 auto const& traitState = index.state(tname);
10554 for (auto const& c : trait.constants) addTraitConst(c);
10555 for (auto const& c : traitState.m_traitCns) addTraitConst(c);
10558 if (cls.attrs & (AttrAbstract | AttrInterface | AttrTrait)) return true;
10560 std::vector<SString> sortedClsConstants;
10561 sortedClsConstants.reserve(cinfo.clsConstants.size());
10562 for (auto const& [name, _] : cinfo.clsConstants) {
10563 sortedClsConstants.emplace_back(name);
10565 std::sort(
10566 sortedClsConstants.begin(),
10567 sortedClsConstants.end(),
10568 string_data_lt{}
10571 for (auto const name : sortedClsConstants) {
10572 auto& cnsIdx = cinfo.clsConstants.find(name)->second;
10573 if (cnsIdx.idx.cls->tsame(cls.name)) continue;
10575 auto const& cns = index.cns(cnsIdx.idx);
10576 if (!cns.isAbstract || !cns.val) continue;
10578 if (cns.val->m_type == KindOfUninit) {
10579 auto const& cnsCls = index.cls(cnsIdx.idx.cls);
10580 assertx(!cnsCls.methods.empty());
10581 assertx(cnsCls.methods.back()->name == s_86cinit.get());
10582 auto const& cnsCInit = *cnsCls.methods.back();
10584 if (cls.methods.empty() ||
10585 cls.methods.back()->name != s_86cinit.get()) {
10586 ClonedClosures clonedClosures;
10587 auto cloned = clone(
10588 index,
10589 cnsCInit,
10590 cnsCInit.name,
10591 cnsCInit.attrs,
10592 cls,
10593 clonedClosures,
10594 true
10596 assertx(cloned);
10597 assertx(clonedClosures.empty());
10598 assertx(cloned->cls == &cls);
10599 cloned->clsIdx = cls.methods.size();
10600 auto const DEBUG_ONLY emplaced =
10601 cinfo.methods.emplace(cloned->name, MethTabEntry { *cloned });
10602 assertx(emplaced.second);
10603 cls.methods.emplace_back(std::move(cloned));
10604 } else {
10605 auto const DEBUG_ONLY succeeded =
10606 append_86cinit(cls.methods.back().get(), cnsCInit);
10607 assertx(succeeded);
10611 // This is similar to trait constant flattening
10612 auto copy = cns;
10613 copy.cls = cls.name;
10614 copy.isAbstract = false;
10615 state.m_cnsFromTrait.erase(copy.name);
10617 cnsIdx.idx.cls = cls.name;
10618 cnsIdx.idx.idx = cls.constants.size();
10619 cnsIdx.kind = copy.kind;
10620 cls.constants.emplace_back(std::move(copy));
10623 return true;
10626 static bool add_constant(const LocalIndex& index,
10627 ClassInfo2& cinfo,
10628 State& state,
10629 SString name,
10630 const ClassInfo2::ConstIndexAndKind& cnsIdx,
10631 bool fromTrait) {
10632 auto [it, emplaced] = cinfo.clsConstants.emplace(name, cnsIdx);
10633 if (emplaced) {
10634 if (fromTrait) {
10635 always_assert(state.m_cnsFromTrait.emplace(name).second);
10636 } else {
10637 always_assert(!state.m_cnsFromTrait.count(name));
10639 return true;
10641 auto& existingIdx = it->second;
10643 // Same constant (from an interface via two different paths) is ok
10644 if (existingIdx.idx.cls->tsame(cnsIdx.idx.cls)) return true;
10646 auto const& existingCnsCls = index.cls(existingIdx.idx.cls);
10647 auto const& existing = index.cns(existingIdx.idx);
10648 auto const& cns = index.cns(cnsIdx.idx);
10650 if (existing.kind != cns.kind) {
10651 ITRACE(
10653 "Adding constant failed for `{}' because `{}' was defined by "
10654 "`{}' as a {} and by `{}' as a {}\n",
10655 cinfo.name,
10656 name,
10657 cnsIdx.idx.cls,
10658 ConstModifiers::show(cns.kind),
10659 existingIdx.idx.cls,
10660 ConstModifiers::show(existing.kind)
10662 return false;
10665 // Ignore abstract constants
10666 if (cns.isAbstract && !cns.val) return true;
10667 // If the existing constant in the map is concrete, then don't
10668 // overwrite it with an incoming abstract constant's default
10669 if (!existing.isAbstract && cns.isAbstract) return true;
10671 if (existing.val) {
10673 * A constant from a declared interface collides with a constant
10674 * (Excluding constants from interfaces a trait implements).
10676 * Need this check otherwise constants from traits that conflict
10677 * with declared interfaces will silently lose and not conflict
10678 * in the runtime.
10680 * Type and Context constants can be overridden.
10682 auto const& cnsCls = index.cls(cnsIdx.idx.cls);
10683 if (cns.kind == ConstModifiers::Kind::Value &&
10684 !existing.isAbstract &&
10685 (existingCnsCls.attrs & AttrInterface) &&
10686 !((cnsCls.attrs & AttrInterface) && fromTrait)) {
10687 auto const& cls = index.cls(cinfo.name);
10688 for (auto const iface : cls.interfaceNames) {
10689 if (existingIdx.idx.cls->tsame(iface)) {
10690 ITRACE(
10692 "Adding constant failed for `{}' because "
10693 "`{}' was defined by both `{}' and `{}'\n",
10694 cinfo.name,
10695 name,
10696 cnsIdx.idx.cls,
10697 existingIdx.idx.cls
10699 return false;
10704 // Constants from traits silently lose
10705 if (!Cfg::Eval::TraitConstantInterfaceBehavior && fromTrait) return true;
10707 if ((cnsCls.attrs & AttrInterface ||
10708 (Cfg::Eval::TraitConstantInterfaceBehavior &&
10709 (cnsCls.attrs & AttrTrait))) &&
10710 (existing.isAbstract ||
10711 cns.kind == ConstModifiers::Kind::Type)) {
10712 // Because existing has val, this covers the case where it is
10713 // abstract with default allow incoming to win. Also, type
10714 // constants from interfaces may be overridden even if they're
10715 // not abstract.
10716 } else {
10717 // A constant from an interface or from an included enum
10718 // collides with an existing constant.
10719 if (cnsCls.attrs & (AttrInterface | AttrEnum | AttrEnumClass) ||
10720 (Cfg::Eval::TraitConstantInterfaceBehavior &&
10721 (cnsCls.attrs & AttrTrait))) {
10722 ITRACE(
10724 "Adding constant failed for `{}' because "
10725 "`{}' was defined by both `{}' and `{}'\n",
10726 cinfo.name,
10727 name,
10728 cnsIdx.idx.cls,
10729 existingIdx.idx.cls
10731 return false;
10736 if (fromTrait) {
10737 state.m_cnsFromTrait.emplace(name);
10738 } else {
10739 state.m_cnsFromTrait.erase(name);
10741 existingIdx = cnsIdx;
10742 return true;
10746 * Make a flattened table of the methods on this class.
10748 * Duplicate method names override parent methods, unless the parent
10749 * method is final and the class is not a __MockClass, in which case
10750 * this class definitely would fatal if ever defined.
10752 * Note: we're leaving non-overridden privates in their subclass
10753 * method table, here. This isn't currently "wrong", because calling
10754 * it would be a fatal, but note that resolve_method needs to be
10755 * pretty careful about privates and overriding in general.
10757 static bool build_methods(const LocalIndex& index,
10758 const php::Class& cls,
10759 ClassInfo2& cinfo,
10760 State& state) {
10761 // Since interface methods are not inherited, any methods in
10762 // interfaces this class implements are automatically missing.
10763 assertx(cinfo.methods.empty());
10764 for (auto const iname : cls.interfaceNames) {
10765 auto const& iface = index.classInfo(iname);
10766 for (auto const& [name, _] : iface.methods) {
10767 if (is_special_method_name(name)) continue;
10768 cinfo.missingMethods.emplace(name);
10770 for (auto const name : iface.missingMethods) {
10771 assertx(!is_special_method_name(name));
10772 cinfo.missingMethods.emplace(name);
10776 // Interface methods are just stubs which return null. They don't
10777 // get inherited by their implementations.
10778 if (cls.attrs & AttrInterface) {
10779 assertx(!cls.parentName);
10780 assertx(cls.usedTraitNames.empty());
10781 uint32_t idx = cinfo.methods.size();
10782 assertx(!idx);
10783 for (auto const& m : cls.methods) {
10784 auto const res = cinfo.methods.emplace(m->name, MethTabEntry { *m });
10785 always_assert(res.second);
10786 always_assert(state.m_methodIndices.emplace(m->name, idx++).second);
10787 if (cinfo.missingMethods.count(m->name)) {
10788 assertx(!res.first->second.firstName());
10789 cinfo.missingMethods.erase(m->name);
10790 } else {
10791 res.first->second.setFirstName();
10793 ITRACE(4, " {}: adding method {}::{}\n",
10794 cls.name, cls.name, m->name);
10796 return true;
10799 auto const overridden = [&] (MethTabEntry& existing,
10800 MethRef meth,
10801 Attr attrs) {
10802 auto const& existingMeth = index.meth(existing);
10803 if (existingMeth.attrs & AttrFinal) {
10804 if (!is_mock_class(&cls)) {
10805 ITRACE(
10807 "Adding methods failed for `{}' because "
10808 "it tried to override final method `{}::{}'\n",
10809 cls.name,
10810 existing.meth().cls,
10811 existingMeth.name
10813 return false;
10816 ITRACE(
10818 "{}: overriding method {}::{} with {}::{}\n",
10819 cls.name,
10820 existing.meth().cls,
10821 existingMeth.name,
10822 meth.cls,
10823 existingMeth.name
10825 if (existingMeth.attrs & AttrPrivate) {
10826 existing.setHasPrivateAncestor();
10828 existing.setMeth(meth);
10829 existing.attrs = attrs;
10830 existing.setTopLevel();
10831 return true;
10834 // If there's a parent, start by copying its methods
10835 if (cls.parentName) {
10836 auto const& parentInfo = index.classInfo(cls.parentName);
10838 assertx(cinfo.methods.empty());
10839 cinfo.missingMethods.insert(
10840 begin(parentInfo.missingMethods),
10841 end(parentInfo.missingMethods)
10844 for (auto const& mte : parentInfo.methods) {
10845 // Don't inherit the 86* methods
10846 if (HPHP::Func::isSpecial(mte.first)) continue;
10848 auto const emplaced = cinfo.methods.emplace(mte);
10849 always_assert(emplaced.second);
10850 emplaced.first->second.clearTopLevel();
10851 emplaced.first->second.clearFirstName();
10853 always_assert(
10854 state.m_methodIndices.emplace(
10855 mte.first,
10856 index.methodIdx(cls.parentName, mte.first)
10857 ).second
10860 cinfo.missingMethods.erase(mte.first);
10862 ITRACE(
10864 "{}: inheriting method {}::{}\n",
10865 cls.name,
10866 cls.parentName,
10867 mte.first
10872 auto idx = cinfo.methods.size();
10873 auto const clsHasModuleLevelTrait =
10874 cls.userAttributes.count(s___ModuleLevelTrait.get());
10876 // Now add our methods.
10877 for (auto const& m : cls.methods) {
10878 if ((cls.attrs & AttrTrait) &&
10879 (!((cls.attrs & AttrInternal) || clsHasModuleLevelTrait)) &&
10880 (m->attrs & AttrInternal)) {
10881 ITRACE(2,
10882 "Adding methods failed for `{}' because "
10883 "method `{}' is internal and public traits "
10884 "cannot define internal methods unless they have "
10885 "the <<__ModuleLevelTrait>> attribute\n",
10886 cls.name, m->name);
10887 return false;
10889 auto const emplaced = cinfo.methods.emplace(m->name, MethTabEntry { *m });
10890 if (emplaced.second) {
10891 ITRACE(
10893 "{}: adding method {}::{}\n",
10894 cls.name,
10895 cls.name,
10896 m->name
10898 always_assert(state.m_methodIndices.emplace(m->name, idx++).second);
10899 if (cinfo.missingMethods.count(m->name)) {
10900 assertx(!emplaced.first->second.firstName());
10901 cinfo.missingMethods.erase(m->name);
10902 } else {
10903 emplaced.first->second.setFirstName();
10905 continue;
10908 // If the method is already in our table, it shouldn't be
10909 // missing.
10910 assertx(!cinfo.missingMethods.count(m->name));
10912 assertx(!emplaced.first->second.firstName());
10914 if ((m->attrs & AttrTrait) && (m->attrs & AttrAbstract)) {
10915 // Abstract methods from traits never override anything.
10916 continue;
10918 if (!overridden(emplaced.first->second, MethRef { *m }, m->attrs)) {
10919 return false;
10923 // If our traits were previously flattened, we're done.
10924 if (cls.attrs & AttrNoExpandTrait) return true;
10926 try {
10927 TMIData tmid;
10928 for (auto const tname : cls.usedTraitNames) {
10929 auto const& tcls = index.cls(tname);
10930 auto const& t = index.classInfo(tname);
10931 std::vector<std::pair<SString, const MethTabEntry*>>
10932 methods(t.methods.size());
10933 for (auto const& [name, mte] : t.methods) {
10934 if (HPHP::Func::isSpecial(name)) continue;
10935 auto const idx = index.methodIdx(tname, name);
10936 assertx(!methods[idx].first);
10937 methods[idx] = std::make_pair(name, &mte);
10938 if (auto it = cinfo.methods.find(name);
10939 it != end(cinfo.methods)) {
10940 it->second.clearFirstName();
10944 for (auto const name : t.missingMethods) {
10945 assertx(!is_special_method_name(name));
10946 if (cinfo.methods.count(name)) continue;
10947 cinfo.missingMethods.emplace(name);
10950 for (auto const& [name, mte] : methods) {
10951 if (!name) continue;
10952 auto const& meth = index.meth(*mte);
10953 tmid.add(
10954 TraitMethod { std::make_pair(&t, &tcls), &meth, mte->attrs },
10955 name
10958 for (auto const& clo : tcls.closures) {
10959 auto const invoke = find_method(clo.get(), s_invoke.get());
10960 assertx(invoke);
10961 cinfo.extraMethods.emplace(MethRef { *invoke });
10965 auto const traitMethods = tmid.finish(
10966 std::make_pair(&cinfo, &cls),
10967 cls.userAttributes.count(s___EnableMethodTraitDiamond.get())
10970 // Import the methods.
10971 for (auto const& mdata : traitMethods) {
10972 auto const method = mdata.tm.method;
10973 auto attrs = mdata.tm.modifiers;
10975 if (attrs == AttrNone) {
10976 attrs = method->attrs;
10977 } else {
10978 auto const attrMask =
10979 (Attr)(AttrPublic | AttrProtected | AttrPrivate |
10980 AttrAbstract | AttrFinal);
10981 attrs = (Attr)((attrs & attrMask) |
10982 (method->attrs & ~attrMask));
10985 auto const emplaced = cinfo.methods.emplace(
10986 mdata.name,
10987 MethTabEntry { *method, attrs }
10989 if (emplaced.second) {
10990 ITRACE(
10992 "{}: adding trait method {}::{} as {}\n",
10993 cls.name,
10994 method->cls->name, method->name, mdata.name
10996 always_assert(
10997 state.m_methodIndices.emplace(mdata.name, idx++).second
10999 cinfo.missingMethods.erase(mdata.name);
11000 } else {
11001 assertx(!cinfo.missingMethods.count(mdata.name));
11002 if (attrs & AttrAbstract) continue;
11003 if (emplaced.first->second.meth().cls->tsame(cls.name)) continue;
11004 if (!overridden(emplaced.first->second, MethRef { *method }, attrs)) {
11005 return false;
11007 state.methodIdx(index.m_ctx->name, cinfo.name, mdata.name) = idx++;
11009 cinfo.extraMethods.emplace(MethRef { *method });
11011 } catch (const TMIOps::TMIException& exn) {
11012 ITRACE(
11014 "Adding methods failed for `{}' importing traits: {}\n",
11015 cls.name, exn.what()
11017 return false;
11020 return true;
11023 using ClonedClosures =
11024 hphp_fast_map<const php::Class*, std::unique_ptr<php::Class>>;
11026 static SString rename_closure(const php::Class& closure,
11027 const php::Class& newContext) {
11028 auto n = closure.name->slice();
11029 auto const p = n.find(';');
11030 if (p != std::string::npos) n = n.subpiece(0, p);
11031 return makeStaticString(folly::sformat("{};{}", n, newContext.name));
11034 static std::unique_ptr<php::Class>
11035 clone_closure(const LocalIndex& index,
11036 const php::Class& closure,
11037 const php::Class& newContext,
11038 bool requiresFromOriginalModule,
11039 ClonedClosures& clonedClosures) {
11040 auto clone = std::make_unique<php::Class>(closure);
11041 assertx(clone->closureContextCls);
11043 clone->name = rename_closure(closure, newContext);
11044 clone->closureContextCls = newContext.name;
11045 clone->unit = newContext.unit;
11047 ITRACE(4, "- cloning closure {} as {} (with context {})\n",
11048 closure.name, clone->name, newContext.name);
11050 for (size_t i = 0, numMeths = clone->methods.size(); i < numMeths; ++i) {
11051 auto meth = std::move(clone->methods[i]);
11052 meth->cls = clone.get();
11053 assertx(meth->clsIdx == i);
11054 if (!meth->originalFilename) meth->originalFilename = meth->unit;
11055 if (!meth->originalUnit) meth->originalUnit = meth->unit;
11056 if (!meth->originalClass) meth->originalClass = closure.name;
11057 meth->requiresFromOriginalModule = requiresFromOriginalModule;
11058 meth->unit = newContext.unit;
11060 clone->methods[i] =
11061 clone_closures(index, std::move(meth), requiresFromOriginalModule, clonedClosures);
11062 if (!clone->methods[i]) return nullptr;
11065 return clone;
11068 static std::unique_ptr<php::Func>
11069 clone_closures(const LocalIndex& index,
11070 std::unique_ptr<php::Func> cloned,
11071 bool requiresFromOriginalModule,
11072 ClonedClosures& clonedClosures) {
11073 if (!cloned->hasCreateCl) return cloned;
11075 auto const onClosure = [&] (LSString& closureName) {
11076 auto const& cls = index.cls(closureName);
11077 assertx(is_closure(cls));
11079 // CreateCls are allowed to refer to the same closure within the
11080 // same func. If this is a duplicate, use the already cloned
11081 // closure name.
11082 if (auto const it = clonedClosures.find(&cls);
11083 it != clonedClosures.end()) {
11084 closureName = it->second->name;
11085 return true;
11088 // Otherwise clone the closure (which gives it a new name), and
11089 // update the name in the CreateCl to match.
11090 auto closure = clone_closure(
11091 index,
11092 cls,
11093 cloned->cls->closureContextCls
11094 ? index.cls(cloned->cls->closureContextCls)
11095 : *cloned->cls,
11096 requiresFromOriginalModule,
11097 clonedClosures
11099 if (!closure) return false;
11100 closureName = closure->name;
11101 always_assert(clonedClosures.emplace(&cls, std::move(closure)).second);
11102 return true;
11105 auto mf = php::WideFunc::mut(cloned.get());
11106 assertx(!mf.blocks().empty());
11107 for (size_t bid = 0; bid < mf.blocks().size(); bid++) {
11108 auto const b = mf.blocks()[bid].mutate();
11109 for (size_t ix = 0; ix < b->hhbcs.size(); ix++) {
11110 auto& bc = b->hhbcs[ix];
11111 switch (bc.op) {
11112 case Op::CreateCl: {
11113 if (!onClosure(bc.CreateCl.str2)) return nullptr;
11114 break;
11116 default:
11117 break;
11122 return cloned;
11125 static std::unique_ptr<php::Func> clone(const LocalIndex& index,
11126 const php::Func& orig,
11127 SString name,
11128 Attr attrs,
11129 const php::Class& dstCls,
11130 ClonedClosures& clonedClosures,
11131 bool internal = false) {
11132 auto cloned = std::make_unique<php::Func>(orig);
11133 cloned->name = name;
11134 cloned->attrs = attrs;
11135 if (!internal) cloned->attrs |= AttrTrait;
11136 cloned->cls = const_cast<php::Class*>(&dstCls);
11137 cloned->unit = dstCls.unit;
11139 if (!cloned->originalFilename) cloned->originalFilename = orig.unit;
11140 if (!cloned->originalUnit) cloned->originalUnit = orig.unit;
11141 cloned->originalClass = orig.originalClass
11142 ? orig.originalClass
11143 : orig.cls->name;
11144 cloned->originalModuleName = orig.originalModuleName;
11146 // If the "module level traits" semantics is enabled, whenever HHBBC
11147 // inlines a method from a trait defined in module A into a trait/class
11148 // defined in module B, it sets the requiresFromOriginalModule flag of the
11149 // method to true. This flag causes the originalModuleName field to be
11150 // copied in the HHVM extendedSharedData section of the method, so that
11151 // HHVM is able to resolve correctly the original module of the method.
11152 // Preserving the original module of a method is also needed when a
11153 // method is defined in an internal trait that is used by a module level
11154 // trait.
11155 const bool requiresFromOriginalModule = [&] () {
11156 bool copyFromModuleLevelTrait =
11157 orig.fromModuleLevelTrait && !orig.requiresFromOriginalModule &&
11158 orig.originalModuleName != dstCls.moduleName;
11159 bool copyFromInternal =
11160 (orig.cls->attrs & AttrInternal)
11161 && dstCls.userAttributes.count(s___ModuleLevelTrait.get());
11163 if (Cfg::Eval::ModuleLevelTraits &&
11164 (copyFromModuleLevelTrait || copyFromInternal)) {
11165 return true;
11166 } else {
11167 return orig.requiresFromOriginalModule;
11169 }();
11170 cloned->requiresFromOriginalModule = requiresFromOriginalModule;
11172 // cloned method isn't in any method table yet, so trash its
11173 // index.
11174 cloned->clsIdx = std::numeric_limits<uint32_t>::max();
11175 return clone_closures(index, std::move(cloned), requiresFromOriginalModule, clonedClosures);
11178 static bool merge_inits(const LocalIndex& index,
11179 const php::Class& cls,
11180 const ClassInfo2& cinfo,
11181 SString name,
11182 std::vector<std::unique_ptr<php::Func>>& clones) {
11183 auto const existing = [&] () -> const php::Func* {
11184 for (auto const& m : cls.methods) {
11185 if (m->name == name) return m.get();
11187 return nullptr;
11188 }();
11190 std::unique_ptr<php::Func> cloned;
11192 auto const merge = [&] (const php::Func& f) {
11193 if (!cloned) {
11194 ClonedClosures clonedClosures;
11195 if (existing) {
11196 cloned = clone(
11197 index,
11198 *existing,
11199 existing->name,
11200 existing->attrs,
11201 cls,
11202 clonedClosures,
11203 true
11205 assertx(clonedClosures.empty());
11206 if (!cloned) return false;
11207 } else {
11208 ITRACE(4, "- cloning {}::{} as {}::{}\n",
11209 f.cls->name, f.name, cls.name, name);
11210 cloned = clone(index, f, f.name, f.attrs, cls, clonedClosures, true);
11211 assertx(clonedClosures.empty());
11212 return (bool)cloned;
11216 ITRACE(4, "- appending {}::{} into {}::{}\n",
11217 f.cls->name, f.name, cls.name, name);
11218 if (name == s_86cinit.get()) return append_86cinit(cloned.get(), f);
11219 return append_func(cloned.get(), f);
11222 for (auto const tname : cls.usedTraitNames) {
11223 auto const& trait = index.classInfo(tname);
11224 auto const it = trait.methods.find(name);
11225 if (it == trait.methods.end()) continue;
11226 auto const& meth = index.meth(it->second);
11227 if (!merge(meth)) {
11228 ITRACE(4, "merge_inits: failed to merge {}::{}\n",
11229 meth.cls->name, name);
11230 return false;
11234 if (cloned) {
11235 ITRACE(4, "merge_inits: adding {}::{} to method table\n",
11236 cloned->cls->name, cloned->name);
11237 clones.emplace_back(std::move(cloned));
11240 return true;
11243 static bool merge_xinits(const LocalIndex& index,
11244 const php::Class& cls,
11245 const ClassInfo2& cinfo,
11246 const State& state,
11247 std::vector<std::unique_ptr<php::Func>>& clones) {
11248 auto const merge_one = [&] (SString name, Attr attr) {
11249 auto const unnecessary = std::all_of(
11250 cinfo.traitProps.begin(),
11251 cinfo.traitProps.end(),
11252 [&] (const php::Prop& p) {
11253 if ((p.attrs & (AttrStatic | AttrLSB)) != attr) return true;
11254 if (p.val.m_type != KindOfUninit) return true;
11255 if (p.attrs & AttrLateInit) return true;
11256 return false;
11259 if (unnecessary) return true;
11260 return merge_inits(index, cls, cinfo, name, clones);
11263 if (!merge_one(s_86pinit.get(), AttrNone)) return false;
11264 if (!merge_one(s_86sinit.get(), AttrStatic)) return false;
11265 if (!merge_one(s_86linit.get(), AttrStatic | AttrLSB)) return false;
11267 auto const unnecessary = std::all_of(
11268 state.m_traitCns.begin(),
11269 state.m_traitCns.end(),
11270 [&] (const php::Const& c) {
11271 return !c.val || c.val->m_type != KindOfUninit;
11274 if (unnecessary) return true;
11275 return merge_inits(index, cls, cinfo, s_86cinit.get(), clones);
11278 static std::vector<std::unique_ptr<ClassInfo2>>
11279 flatten_traits(const LocalIndex& index,
11280 php::Class& cls,
11281 ClassInfo2& cinfo,
11282 State& state) {
11283 if (cls.attrs & AttrNoExpandTrait) return {};
11284 if (cls.usedTraitNames.empty()) {
11285 cls.attrs |= AttrNoExpandTrait;
11286 return {};
11289 ITRACE(4, "flatten traits: {}\n", cls.name);
11290 Trace::Indent indent;
11292 assertx(!is_closure(cls));
11294 auto traitHasConstProp = cls.hasConstProp;
11295 for (auto const tname : cls.usedTraitNames) {
11296 auto const& trait = index.cls(tname);
11297 auto const& tinfo = index.classInfo(tname);
11298 if (!(trait.attrs & AttrNoExpandTrait)) {
11299 ITRACE(4, "Not flattening {} because of {}\n", cls.name, trait.name);
11300 return {};
11302 if (is_noflatten_trait(&trait)) {
11303 ITRACE(
11304 4, "Not flattening {} because {} is annotated with __NoFlatten\n",
11305 cls.name, trait.name
11307 return {};
11309 if (tinfo.hasConstProp) traitHasConstProp = true;
11312 std::vector<std::pair<SString, MethTabEntry*>> toAdd;
11313 for (auto& [name, mte] : cinfo.methods) {
11314 if (!mte.topLevel()) continue;
11315 if (mte.meth().cls->tsame(cls.name)) continue;
11316 assertx(index.cls(mte.meth().cls).attrs & AttrTrait);
11317 toAdd.emplace_back(name, &mte);
11320 if (!toAdd.empty()) {
11321 assertx(!cinfo.extraMethods.empty());
11322 std::sort(
11323 toAdd.begin(), toAdd.end(),
11324 [&] (auto const& a, auto const& b) {
11325 return
11326 state.methodIdx(index.m_ctx->name, cinfo.name, a.first) <
11327 state.methodIdx(index.m_ctx->name, cinfo.name, b.first);
11330 } else if (debug) {
11331 // When building the ClassInfos, we proactively added all
11332 // closures from usedTraits to the extraMethods map; but now
11333 // we're going to start from the used methods, and deduce which
11334 // closures actually get pulled in. Its possible *none* of the
11335 // methods got used, in which case, we won't need their closures
11336 // either. To be safe, verify that the only things in the map
11337 // are closures.
11338 for (auto const& mte : cinfo.extraMethods) {
11339 auto const& meth = index.meth(mte);
11340 always_assert(meth.isClosureBody);
11344 std::vector<std::unique_ptr<php::Func>> clones;
11345 ClonedClosures clonedClosures;
11347 for (auto const& [name, mte] : toAdd) {
11348 auto const& meth = index.meth(*mte);
11349 auto cloned = clone(
11350 index,
11351 meth,
11352 name,
11353 mte->attrs,
11354 cls,
11355 clonedClosures
11357 if (!cloned) {
11358 ITRACE(4, "Not flattening {} because {}::{} could not be cloned\n",
11359 cls.name, mte->meth().cls, name);
11360 return {};
11362 assertx(cloned->attrs & AttrTrait);
11363 clones.emplace_back(std::move(cloned));
11366 if (!merge_xinits(index, cls, cinfo, state, clones)) {
11367 ITRACE(4, "Not flattening {} because we couldn't merge the 86xinits\n",
11368 cls.name);
11369 return {};
11372 // We're now committed to flattening.
11373 ITRACE(3, "Flattening {}\n", cls.name);
11375 if (traitHasConstProp) {
11376 assertx(cinfo.hasConstProp);
11377 cls.hasConstProp = true;
11379 cinfo.extraMethods.clear();
11381 for (auto [_, mte] : toAdd) mte->attrs |= AttrTrait;
11383 for (auto& p : cinfo.traitProps) {
11384 ITRACE(4, "- prop {}\n", p.name);
11385 cls.properties.emplace_back(std::move(p));
11386 cls.properties.back().attrs |= AttrTrait;
11388 cinfo.traitProps.clear();
11390 for (auto& c : state.m_traitCns) {
11391 ITRACE(4, "- const {}\n", c.name);
11393 auto it = cinfo.clsConstants.find(c.name);
11394 assertx(it != cinfo.clsConstants.end());
11395 auto& cnsIdx = it->second;
11397 c.cls = cls.name;
11398 state.m_cnsFromTrait.erase(c.name);
11399 cnsIdx.idx.cls = cls.name;
11400 cnsIdx.idx.idx = cls.constants.size();
11401 cls.constants.emplace_back(std::move(c));
11403 state.m_traitCns.clear();
11405 // A class should inherit any declared interfaces of any traits
11406 // that are flattened into it.
11407 for (auto const tname : cls.usedTraitNames) {
11408 auto const& tinfo = index.classInfo(tname);
11409 cinfo.classGraph.flattenTraitInto(tinfo.classGraph);
11412 // If we flatten the traits into us, they're no longer actual
11413 // parents.
11414 state.m_parents.erase(
11415 std::remove_if(
11416 begin(state.m_parents),
11417 end(state.m_parents),
11418 [] (const php::Class* c) { return bool(c->attrs & AttrTrait); }
11420 end(state.m_parents)
11423 for (auto const tname : cls.usedTraitNames) {
11424 auto const& traitState = index.state(tname);
11425 state.m_parents.insert(
11426 end(state.m_parents),
11427 begin(traitState.m_parents),
11428 end(traitState.m_parents)
11431 std::sort(
11432 begin(state.m_parents),
11433 end(state.m_parents),
11434 [] (const php::Class* a, const php::Class* b) {
11435 return string_data_lt_type{}(a->name, b->name);
11438 state.m_parents.erase(
11439 std::unique(begin(state.m_parents), end(state.m_parents)),
11440 end(state.m_parents)
11443 std::vector<std::unique_ptr<ClassInfo2>> newClosures;
11444 if (!clones.empty()) {
11445 auto const add = [&] (std::unique_ptr<php::Func> clone) {
11446 assertx(clone->cls == &cls);
11447 clone->clsIdx = cls.methods.size();
11449 if (!is_special_method_name(clone->name)) {
11450 auto it = cinfo.methods.find(clone->name);
11451 assertx(it != cinfo.methods.end());
11452 assertx(!it->second.meth().cls->tsame(cls.name));
11453 it->second.setMeth(MethRef { cls.name, clone->clsIdx });
11454 } else {
11455 auto const [existing, emplaced] =
11456 cinfo.methods.emplace(clone->name, MethTabEntry { *clone });
11457 if (!emplaced) {
11458 assertx(existing->second.meth().cls->tsame(cls.name));
11459 if (clone->name != s_86cinit.get()) {
11460 auto const idx = existing->second.meth().idx;
11461 clone->clsIdx = idx;
11462 cls.methods[idx] = std::move(clone);
11463 return;
11464 } else {
11465 existing->second.setMeth(MethRef { cls.name, clone->clsIdx });
11470 cls.methods.emplace_back(std::move(clone));
11473 auto cinit = [&] () -> std::unique_ptr<php::Func> {
11474 if (cls.methods.empty()) return nullptr;
11475 if (cls.methods.back()->name != s_86cinit.get()) return nullptr;
11476 auto init = std::move(cls.methods.back());
11477 cls.methods.pop_back();
11478 return init;
11479 }();
11481 for (auto& clone : clones) {
11482 ITRACE(4, "- meth {}\n", clone->name);
11483 if (clone->name == s_86cinit.get()) {
11484 cinit = std::move(clone);
11485 continue;
11487 add(std::move(clone));
11489 if (cinit) add(std::move(cinit));
11491 for (auto& [orig, clo] : clonedClosures) {
11492 ITRACE(4, "- closure {} as {}\n", orig->name, clo->name);
11493 assertx(is_closure(*orig));
11494 assertx(is_closure(*clo));
11495 assertx(clo->closureContextCls->tsame(cls.name));
11496 assertx(clo->unit == cls.unit);
11498 assertx(clo->usedTraitNames.empty());
11499 State cloState;
11500 auto cloinfo = make_info(index, *clo, cloState);
11501 assertx(cloinfo);
11502 assertx(cloState.m_traitCns.empty());
11503 assertx(cloState.m_cnsFromTrait.empty());
11504 assertx(cloState.m_parents.size() == 1);
11505 assertx(cloState.m_parents[0]->name->tsame(s_Closure.get()));
11507 cls.closures.emplace_back(std::move(clo));
11508 newClosures.emplace_back(std::move(cloinfo));
11512 // Flattening methods into traits can turn methods from not "first
11513 // name" to "first name", so recalculate that here.
11514 for (auto& [name, mte] : cinfo.methods) {
11515 if (mte.firstName()) continue;
11516 auto const firstName = [&, name=name] {
11517 if (cls.parentName) {
11518 auto const& parentInfo = index.classInfo(cls.parentName);
11519 if (parentInfo.methods.count(name)) return false;
11520 if (parentInfo.missingMethods.count(name)) return false;
11522 for (auto const iname : cinfo.classGraph.interfaces()) {
11523 auto const& iface = index.classInfo(iname.name());
11524 if (iface.methods.count(name)) return false;
11525 if (iface.missingMethods.count(name)) return false;
11527 return true;
11528 }();
11529 if (firstName) mte.setFirstName();
11532 struct EqHash {
11533 bool operator()(const PreClass::ClassRequirement& a,
11534 const PreClass::ClassRequirement& b) const {
11535 return a.is_same(&b);
11537 size_t operator()(const PreClass::ClassRequirement& a) const {
11538 return a.hash();
11541 hphp_fast_set<PreClass::ClassRequirement, EqHash, EqHash> reqs{
11542 cls.requirements.begin(),
11543 cls.requirements.end()
11546 for (auto const tname : cls.usedTraitNames) {
11547 auto const& trait = index.cls(tname);
11548 for (auto const& req : trait.requirements) {
11549 if (reqs.emplace(req).second) cls.requirements.emplace_back(req);
11553 cls.attrs |= AttrNoExpandTrait;
11554 return newClosures;
11557 static std::unique_ptr<FuncInfo2> make_func_info(const LocalIndex& index,
11558 const php::Func& f) {
11559 auto finfo = std::make_unique<FuncInfo2>();
11560 finfo->name = f.name;
11561 return finfo;
11564 static bool resolve_one(TypeConstraint& tc,
11565 const TypeConstraint& tv,
11566 SString firstEnum,
11567 TSStringSet* uses,
11568 bool isProp,
11569 bool isUnion) {
11570 assertx(!tv.isUnion());
11571 // Whatever it's an alias of isn't valid, so leave unresolved.
11572 if (tv.isUnresolved()) return false;
11573 if (isProp && !propSupportsAnnot(tv.type())) return false;
11574 auto const value = [&] () -> SString {
11575 // Store the first enum encountered during resolution. This
11576 // lets us fixup the type later if needed.
11577 if (firstEnum) return firstEnum;
11578 if (tv.isSubObject()) {
11579 auto clsName = tv.clsName();
11580 assertx(clsName);
11581 return clsName;
11583 return nullptr;
11584 }();
11585 if (isUnion) tc.unresolve();
11586 tc.resolveType(tv.type(), tv.isNullable(), value);
11587 assertx(IMPLIES(isProp, tc.validForProp()));
11588 if (uses && value) uses->emplace(value);
11589 return true;
11592 // Update a type constraint to it's ultimate type, or leave it as
11593 // unresolved if it resolves to nothing valid. Record the new type
11594 // in case it needs to be fixed up later.
11595 static void update_type_constraint(const LocalIndex& index,
11596 TypeConstraint& tc,
11597 bool isProp,
11598 TSStringSet* uses) {
11599 always_assert(IMPLIES(isProp, tc.validForProp()));
11601 if (!tc.isUnresolved()) {
11602 // Any TC already resolved is assumed to be correct.
11603 if (uses) {
11604 for (auto& part : eachTypeConstraintInUnion(tc)) {
11605 if (auto clsName = part.clsName()) {
11606 uses->emplace(clsName);
11610 return;
11612 auto const name = tc.typeName();
11614 if (tc.isUnion()) {
11615 // This is a union that contains unresolved names.
11616 not_implemented(); // TODO(T151885113)
11619 // This is an unresolved name that can resolve to either a single type or
11620 // a union.
11622 // Is this name a type-alias or enum?
11623 if (auto const tm = index.typeMapping(name)) {
11624 if (tm->value.isUnion()) {
11625 auto flags =
11626 tc.flags() & (TypeConstraintFlags::Nullable
11627 | TypeConstraintFlags::TypeVar
11628 | TypeConstraintFlags::Soft
11629 | TypeConstraintFlags::TypeConstant
11630 | TypeConstraintFlags::DisplayNullable
11631 | TypeConstraintFlags::UpperBound);
11632 std::vector<TypeConstraint> members;
11633 for (auto& tv : eachTypeConstraintInUnion(tm->value)) {
11634 TypeConstraint copy = tv;
11635 copy.addFlags(flags);
11636 if (!resolve_one(copy, tv, tm->firstEnum, uses, isProp, true)) {
11637 return;
11639 members.emplace_back(std::move(copy));
11641 tc = TypeConstraint::makeUnion(name, members);
11642 return;
11645 // This unresolved name resolves to a single type.
11646 assertx(!tm->value.isUnion());
11647 resolve_one(tc, tm->value, tm->firstEnum, uses, isProp, false);
11648 return;
11651 // Not a type-alias or enum. If it's explicitly marked as missing,
11652 // leave it unresolved. Otherwise assume it's an object with that
11653 // name.
11654 if (index.missingType(name)) return;
11655 tc.resolveType(AnnotType::SubObject, tc.isNullable(), name);
11656 if (uses) uses->emplace(name);
11659 static void update_type_constraints(const LocalIndex& index,
11660 php::Func& func,
11661 TSStringSet* uses) {
11662 for (auto& p : func.params) {
11663 update_type_constraint(index, p.typeConstraint, false, uses);
11664 for (auto& ub : p.upperBounds.m_constraints) {
11665 update_type_constraint(index, ub, false, uses);
11668 update_type_constraint(index, func.retTypeConstraint, false, uses);
11669 for (auto& ub : func.returnUBs.m_constraints) {
11670 update_type_constraint(index, ub, false, uses);
11674 static void update_type_constraints(const LocalIndex& index,
11675 php::Class& cls,
11676 TSStringSet* uses) {
11677 if (cls.attrs & AttrEnum) {
11678 update_type_constraint(index, cls.enumBaseTy, false, uses);
11680 for (auto& meth : cls.methods) update_type_constraints(index, *meth, uses);
11681 for (auto& prop : cls.properties) {
11682 update_type_constraint(index, prop.typeConstraint, true, uses);
11683 for (auto& ub : prop.ubs.m_constraints) {
11684 update_type_constraint(index, ub, true, uses);
11690 * Mark any properties in cls that definitely do not redeclare a
11691 * property in the parent with an inequivalent type-hint.
11693 * Rewrite the initial values for any AttrSystemInitialValue
11694 * properties. If the properties' type-hint does not admit null
11695 * values, change the initial value to one that is not null
11696 * (if possible). This is only safe to do so if the property is not
11697 * redeclared in a derived class or if the redeclaration does not
11698 * have a null system provided default value. Otherwise, a property
11699 * can have a null value (even if its type-hint doesn't allow it)
11700 * without the JIT realizing that its possible.
11702 * Note that this ignores any unflattened traits. This is okay
11703 * because properties pulled in from traits which match an already
11704 * existing property can't change the initial value. The runtime
11705 * will clear AttrNoImplicitNullable on any property pulled from the
11706 * trait if it doesn't match an existing property.
11708 static void optimize_properties(const LocalIndex& index,
11709 php::Class& cls,
11710 ClassInfo2& cinfo) {
11711 assertx(cinfo.hasBadRedeclareProp);
11713 auto const isClosure = is_closure(cls);
11715 cinfo.hasBadRedeclareProp = false;
11716 for (auto& prop : cls.properties) {
11717 assertx(!(prop.attrs & AttrNoBadRedeclare));
11718 assertx(!(prop.attrs & AttrNoImplicitNullable));
11720 auto const noBadRedeclare = [&] {
11721 // Closures should never have redeclared properties.
11722 if (isClosure) return true;
11723 // Static and private properties never redeclare anything so
11724 // need not be considered.
11725 if (prop.attrs & (AttrStatic | AttrPrivate)) return true;
11727 for (auto const base : cinfo.classGraph.bases()) {
11728 if (base.name()->tsame(cls.name)) continue;
11730 auto& baseCInfo = index.classInfo(base.name());
11731 auto& baseCls = index.cls(base.name());
11733 auto const parentProp = [&] () -> php::Prop* {
11734 for (auto& p : baseCls.properties) {
11735 if (p.name == prop.name) return const_cast<php::Prop*>(&p);
11737 for (auto& p : baseCInfo.traitProps) {
11738 if (p.name == prop.name) return const_cast<php::Prop*>(&p);
11740 return nullptr;
11741 }();
11742 if (!parentProp) continue;
11743 if (parentProp->attrs & (AttrStatic | AttrPrivate)) continue;
11745 // This property's type-constraint might not have been
11746 // resolved (if the parent is not on the output list for
11747 // this job), so do so here.
11748 update_type_constraint(
11749 index,
11750 parentProp->typeConstraint,
11751 true,
11752 nullptr
11755 // This check is safe, but conservative. It might miss a few
11756 // rare cases, but it's sufficient and doesn't require class
11757 // hierarchies.
11758 if (prop.typeConstraint.maybeInequivalentForProp(
11759 parentProp->typeConstraint
11760 )) {
11761 return false;
11764 for (auto const& ub : prop.ubs.m_constraints) {
11765 for (auto& pub : parentProp->ubs.m_constraints) {
11766 update_type_constraint(index, pub, true, nullptr);
11767 if (ub.maybeInequivalentForProp(pub)) return false;
11772 return true;
11773 }();
11775 if (noBadRedeclare) {
11776 attribute_setter(prop.attrs, true, AttrNoBadRedeclare);
11777 } else {
11778 cinfo.hasBadRedeclareProp = true;
11781 auto const nullable = [&] {
11782 if (isClosure) return true;
11783 if (!(prop.attrs & AttrSystemInitialValue)) return false;
11784 return prop.typeConstraint.defaultValue().m_type == KindOfNull;
11785 }();
11787 attribute_setter(prop.attrs, !nullable, AttrNoImplicitNullable);
11788 if (!(prop.attrs & AttrSystemInitialValue)) continue;
11789 if (prop.val.m_type == KindOfUninit) {
11790 assertx(isClosure || bool(prop.attrs & AttrLateInit));
11791 continue;
11794 prop.val = [&] {
11795 if (nullable) return make_tv<KindOfNull>();
11796 // Give the 86reified_prop a special default value to avoid
11797 // pessimizing the inferred type (we want it to always be a
11798 // vec of a specific size).
11799 if (prop.name == s_86reified_prop.get()) {
11800 return get_default_value_of_reified_list(cls.userAttributes);
11802 return prop.typeConstraint.defaultValue();
11803 }();
11809 Job<FlattenJob> s_flattenJob;
11812 * For efficiency reasons, we want to do class flattening all in one
11813 * pass. So, we use assign_hierarchical_work (described above) to
11814 * calculate work buckets to allow us to do this.
11816 * - The "root" classes are the leaf classes in the hierarchy. These are
11817 * the buckets which are not dependencies of anything.
11819 * - The dependencies of a class are all of the (transitive) parent
11820 * classes of that class (up to the top classes with no parents).
11822 * - Each job takes two kinds of input. The first is the set of
11823 * classes which are actually to be flattened. These will have the
11824 * flattening results returned as output from the job. The second is
11825 * the set of dependencies that are required to perform flattening
11826 * on the first set of inputs. These will have the same flattening
11827 * algorithm applied to them, but only to obtain intermediate state
11828 * to calculate the output for the first set of inputs. Their
11829 * results will be thrown away.
11831 * - When we run the jobs, we'll take the outputs and turn that into a set of
11832 * updates, which we then apply to the Index data structures. Some
11833 * of these updates require changes to the php::Unit, which we do a
11834 * in separate set of "fixup" jobs at the end.
11837 // Input class metadata to be turned into work buckets.
11838 struct IndexFlattenMetadata {
11839 struct ClassMeta {
11840 TSStringSet deps;
11841 // All types mentioned in type-constraints in this class.
11842 std::vector<SString> unresolvedTypes;
11843 size_t idx; // Index into allCls vector
11844 bool isClosure{false};
11845 bool uninstantiable{false};
11847 TSStringToOneT<ClassMeta> cls;
11848 // All classes to be flattened
11849 std::vector<SString> allCls;
11850 // Mapping of units to classes which should be deleted from that
11851 // unit. This is typically from duplicate meth callers. This is
11852 // performed as part of "fixing up" the unit after flattening
11853 // because it's convenient to do so there.
11854 SStringToOneT<std::vector<SString>> unitDeletions;
11855 struct FuncMeta {
11856 // All types mentioned in type-constraints in this func.
11857 std::vector<SString> unresolvedTypes;
11859 FSStringToOneT<FuncMeta> func;
11860 std::vector<SString> allFuncs;
11861 TSStringToOneT<TypeMapping> typeMappings;
11864 //////////////////////////////////////////////////////////////////////
11866 constexpr size_t kNumTypeMappingRounds = 20;
11869 * Update the type-mappings in the program so they all point to their
11870 * ultimate type. After this step, every type-mapping that still has
11871 * an unresolved type points to an invalid type.
11873 void flatten_type_mappings(IndexData& index,
11874 IndexFlattenMetadata& meta) {
11875 trace_time tracer{"flatten type mappings"};
11876 tracer.ignore_client_stats();
11878 std::vector<const TypeMapping*> work;
11879 work.reserve(meta.typeMappings.size());
11880 for (auto const& [_, tm] : meta.typeMappings) work.emplace_back(&tm);
11882 auto resolved = parallel::map(
11883 work,
11884 [&] (const TypeMapping* typeMapping) {
11885 Optional<TSStringSet> seen;
11886 TypeConstraintFlags flags =
11887 typeMapping->value.flags() & (TypeConstraintFlags::Nullable
11888 | TypeConstraintFlags::TypeVar
11889 | TypeConstraintFlags::Soft
11890 | TypeConstraintFlags::TypeConstant
11891 | TypeConstraintFlags::DisplayNullable
11892 | TypeConstraintFlags::UpperBound);
11893 auto firstEnum = typeMapping->firstEnum;
11894 auto const isUnion = typeMapping->value.isUnion();
11895 bool anyUnresolved = false;
11897 auto enumMeta = folly::get_ptr(meta.cls, typeMapping->name);
11899 std::vector<TypeConstraint> tvu;
11901 for (auto const& tc : eachTypeConstraintInUnion(typeMapping->value)) {
11902 const auto type = tc.type();
11903 const auto value = tc.typeName();
11904 auto name = value;
11905 LSString curEnum;
11907 if (type != AnnotType::Unresolved) {
11908 // If the type-mapping is already resolved, we mainly take it
11909 // as is. The exception is if it's an enum, in which case we
11910 // validate the underlying base type.
11911 assertx(type != AnnotType::SubObject);
11912 if (!enumMeta) {
11913 tvu.emplace_back(tc);
11914 continue;
11916 if (!enumSupportsAnnot(type)) {
11917 FTRACE(
11918 2, "Type-mapping '{}' is invalid because it resolves to "
11919 "invalid enum type {}\n",
11920 typeMapping->name,
11921 annotName(type)
11923 tvu.emplace_back(AnnotType::Unresolved, tc.flags(), value);
11924 continue;
11926 tvu.emplace_back(type, tc.flags() | flags, value);
11927 anyUnresolved = true;
11928 continue;
11931 std::queue<LSString> queue;
11932 queue.push(name);
11934 for (size_t rounds = 0;; ++rounds) {
11935 if (queue.empty()) break;
11936 name = normalizeNS(queue.front());
11937 queue.pop();
11939 if (auto const next = folly::get_ptr(meta.typeMappings, name)) {
11940 flags |= next->value.flags() & (TypeConstraintFlags::Nullable
11941 | TypeConstraintFlags::TypeVar
11942 | TypeConstraintFlags::Soft
11943 | TypeConstraintFlags::TypeConstant
11944 | TypeConstraintFlags::DisplayNullable
11945 | TypeConstraintFlags::UpperBound);
11946 auto const nextEnum = next->firstEnum;
11947 if (!curEnum) curEnum = nextEnum;
11948 if (!firstEnum && !isUnion) firstEnum = curEnum;
11950 if (enumMeta && nextEnum) {
11951 enumMeta->deps.emplace(nextEnum);
11954 for (auto const& next_tc : eachTypeConstraintInUnion(next->value)) {
11955 auto next_type = next_tc.type();
11956 auto next_value = next_tc.typeName();
11957 if (next_type == AnnotType::Unresolved) {
11958 queue.push(next_value);
11959 continue;
11961 assertx(next_type != AnnotType::SubObject);
11962 if (curEnum && !enumSupportsAnnot(next_type)) {
11963 FTRACE(
11964 2, "Type-mapping '{}' is invalid because it resolves to "
11965 "invalid enum type {}{}\n",
11966 typeMapping->name,
11967 annotName(next_type),
11968 curEnum->tsame(typeMapping->name)
11969 ? "" : folly::sformat(" (via {})", curEnum)
11971 tvu.emplace_back(AnnotType::Unresolved, tc.flags() | flags, name);
11972 anyUnresolved = true;
11973 continue;
11975 tvu.emplace_back(next_type, tc.flags() | flags, next_value);
11977 } else if (index.classRefs.count(name)) {
11978 if (curEnum) {
11979 FTRACE(
11980 2, "Type-mapping '{}' is invalid because it resolves to "
11981 "invalid object '{}' for enum type (via {})\n",
11982 typeMapping->name,
11983 name,
11984 curEnum
11988 tvu.emplace_back(
11989 curEnum ? AnnotType::Unresolved : AnnotType::SubObject,
11990 tc.flags() | flags,
11991 name
11993 if (curEnum) anyUnresolved = true;
11994 continue;
11995 } else {
11996 FTRACE(
11997 2, "Type-mapping '{}' is invalid because it involves "
11998 "non-existent type '{}'{}\n",
11999 typeMapping->name,
12000 name,
12001 (curEnum && !curEnum->tsame(typeMapping->name))
12002 ? folly::sformat(" (via {})", curEnum) : ""
12004 tvu.emplace_back(AnnotType::Unresolved, tc.flags() | flags, name);
12005 anyUnresolved = true;
12006 continue;
12009 // Deal with cycles. Since we don't expect to encounter them, just
12010 // use a counter until we hit a chain length of kNumTypeMappingRounds,
12011 // then start tracking the names we resolve.
12012 if (rounds == kNumTypeMappingRounds) {
12013 seen.emplace();
12014 seen->insert(name);
12015 } else if (rounds > kNumTypeMappingRounds) {
12016 if (!seen->insert(name).second) {
12017 FTRACE(
12018 2, "Type-mapping '{}' is invalid because it's definition "
12019 "is circular with '{}'\n",
12020 typeMapping->name,
12021 name
12023 return TypeMapping {
12024 typeMapping->name,
12025 firstEnum,
12026 TypeConstraint{AnnotType::Unresolved, flags, name},
12032 if (isUnion && anyUnresolved) {
12033 // Unions cannot contain a mix of resolved an unresolved class names so
12034 // if one of the names failed to resolve we must mark all of them as
12035 // unresolved.
12036 for (auto& tc : tvu) if (tc.isSubObject()) tc.unresolve();
12038 assertx(!tvu.empty());
12039 // If any of the subtypes end up unresolved then the final union will also
12040 // be unresolved. But it's important to try the `makeUnion` anyway because
12041 // it will deal with some of the canonicalizations like `bool`.
12042 auto value = TypeConstraint::makeUnion(typeMapping->name, std::move(tvu));
12043 return TypeMapping { typeMapping->name, firstEnum, value };
12047 for (auto& after : resolved) {
12048 auto const name = after.name;
12049 using namespace folly::gen;
12050 FTRACE(
12051 4, "Type-mapping '{}' flattened to {}{}\n",
12052 name,
12053 after.value.debugName(),
12054 (after.firstEnum && !after.firstEnum->tsame(name))
12055 ? folly::sformat(" (via {})", after.firstEnum) : ""
12057 if (after.value.isUnresolved() && meta.cls.count(name)) {
12058 FTRACE(4, " Marking enum '{}' as uninstantiable\n", name);
12059 meta.cls.at(name).uninstantiable = true;
12061 meta.typeMappings.at(name) = std::move(after);
12065 //////////////////////////////////////////////////////////////////////
12067 struct FlattenClassesWork {
12068 std::vector<SString> classes;
12069 std::vector<SString> deps;
12070 std::vector<SString> funcs;
12071 std::vector<SString> uninstantiable;
12074 std::vector<FlattenClassesWork>
12075 flatten_classes_assign(IndexFlattenMetadata& meta) {
12076 trace_time trace{"flatten classes assign"};
12077 trace.ignore_client_stats();
12079 // First calculate the classes which *aren't* leafs. A class is a
12080 // leaf if it is not depended on by another class. The sense is
12081 // inverted because we want to default construct the atomics.
12082 std::vector<std::atomic<bool>> isNotLeaf(meta.allCls.size());
12083 parallel::for_each(
12084 meta.allCls,
12085 [&] (SString cls) {
12086 auto const& clsMeta = meta.cls.at(cls);
12087 for (auto const d : clsMeta.deps) {
12088 auto const it = meta.cls.find(d);
12089 if (it == meta.cls.end()) continue;
12090 assertx(it->second.idx < isNotLeaf.size());
12091 isNotLeaf[it->second.idx] = true;
12096 // Store all of the (transitive) dependencies for every class,
12097 // calculated lazily. LockFreeLazy ensures that multiple classes can
12098 // access this concurrently and safely calculate it on demand.
12099 struct DepLookup {
12100 TSStringSet deps;
12101 // Whether this class is instantiable
12102 bool instantiable{false};
12104 std::vector<LockFreeLazy<DepLookup>> allDeps{meta.allCls.size()};
12106 // Look up all of the transitive dependencies for the given class.
12107 auto const findAllDeps = [&] (SString cls,
12108 TSStringSet& visited,
12109 auto const& self) -> const DepLookup& {
12110 static const DepLookup empty;
12112 auto const it = meta.cls.find(cls);
12113 if (it == meta.cls.end()) {
12114 FTRACE(
12115 4, "{} is not instantiable because it is missing\n",
12118 return empty;
12121 // The class exists, so look up it's dependency information.
12122 auto const idx = it->second.idx;
12123 auto const& deps = it->second.deps;
12125 // Check for cycles. A class involved in cyclic inheritance is not
12126 // instantiable (and has no dependencies). This needs to be done
12127 // before accessing the LockFreeLazy below, because if we are in a
12128 // cycle, we'll deadlock when we do so.
12129 auto const emplaced = visited.emplace(cls).second;
12130 if (!emplaced) {
12131 FTRACE(
12132 4, "{} is not instantiable because it forms a dependency "
12133 "cycle with itself\n", cls
12135 it->second.uninstantiable = true;
12136 return empty;
12138 SCOPE_EXIT { visited.erase(cls); };
12140 assertx(idx < allDeps.size());
12141 return allDeps[idx].get(
12142 [&] {
12143 // Otherwise get all of the transitive dependencies of it's
12144 // dependencies and combine them.
12145 DepLookup out;
12146 out.instantiable = !it->second.uninstantiable;
12148 for (auto const d : deps) {
12149 auto const& lookup = self(d, visited, self);
12150 if (lookup.instantiable || meta.cls.count(d)) {
12151 out.deps.emplace(d);
12153 out.deps.insert(begin(lookup.deps), end(lookup.deps));
12154 if (lookup.instantiable) continue;
12155 // If the dependency is not instantiable, this isn't
12156 // either. Note, however, we still need to preserve the
12157 // already gathered dependencies, since they'll have to be
12158 // placed in some bucket.
12159 if (out.instantiable) {
12160 FTRACE(
12161 4, "{} is not instantiable because it depends on {}, "
12162 "which is not instantiable\n",
12163 cls, d
12165 it->second.uninstantiable = true;
12167 out.instantiable = false;
12170 return out;
12175 constexpr size_t kBucketSize = 2000;
12176 constexpr size_t kMaxBucketSize = 30000;
12178 auto assignments = assign_hierarchical_work(
12179 [&] {
12180 std::vector<SString> l;
12181 auto const size = meta.allCls.size();
12182 assertx(size == isNotLeaf.size());
12183 l.reserve(size);
12184 for (size_t i = 0; i < size; ++i) {
12185 if (!isNotLeaf[i]) l.emplace_back(meta.allCls[i]);
12187 return l;
12188 }(),
12189 meta.allCls.size(),
12190 kBucketSize,
12191 kMaxBucketSize,
12192 [&] (SString c) {
12193 TSStringSet visited;
12194 auto const& lookup = findAllDeps(c, visited, findAllDeps);
12195 return std::make_pair(&lookup.deps, lookup.instantiable);
12197 [&] (const TSStringSet&, size_t, SString c) -> Optional<size_t> {
12198 return meta.cls.at(c).idx;
12202 // Bucketize functions separately
12204 constexpr size_t kFuncBucketSize = 5000;
12206 auto funcBuckets = consistently_bucketize(meta.allFuncs, kFuncBucketSize);
12208 std::vector<FlattenClassesWork> work;
12209 // If both the class and func assignments map to a single bucket,
12210 // combine them both together. This is an optimization for things
12211 // like unit tests, where the total amount of work is low and we
12212 // want to run it all in a single job if possible.
12213 if (assignments.size() == 1 && funcBuckets.size() == 1) {
12214 work.emplace_back(
12215 FlattenClassesWork{
12216 std::move(assignments[0].classes),
12217 std::move(assignments[0].deps),
12218 std::move(funcBuckets[0]),
12219 std::move(assignments[0].uninstantiable)
12222 } else {
12223 // Otherwise split the classes and func work.
12224 work.reserve(assignments.size() + funcBuckets.size());
12225 for (auto& assignment : assignments) {
12226 work.emplace_back(
12227 FlattenClassesWork{
12228 std::move(assignment.classes),
12229 std::move(assignment.deps),
12231 std::move(assignment.uninstantiable)
12235 for (auto& bucket : funcBuckets) {
12236 work.emplace_back(
12237 FlattenClassesWork{ {}, {}, std::move(bucket), {} }
12242 if (Trace::moduleEnabled(Trace::hhbbc_index, 5)) {
12243 for (size_t i = 0; i < work.size(); ++i) {
12244 auto const& [classes, deps, funcs, uninstantiable] = work[i];
12245 FTRACE(5, "flatten work item #{}:\n", i);
12246 FTRACE(5, " classes ({}):\n", classes.size());
12247 for (auto const DEBUG_ONLY c : classes) FTRACE(5, " {}\n", c);
12248 FTRACE(5, " deps ({}):\n", deps.size());
12249 for (auto const DEBUG_ONLY d : deps) FTRACE(5, " {}\n", d);
12250 FTRACE(5, " funcs ({}):\n", funcs.size());
12251 for (auto const DEBUG_ONLY f : funcs) FTRACE(5, " {}\n", f);
12252 FTRACE(5, " uninstantiable classes ({}):\n", uninstantiable.size());
12253 for (auto const DEBUG_ONLY c : uninstantiable) FTRACE(5, " {}\n", c);
12257 return work;
12260 // Metadata used to assign work buckets for building subclasses. This
12261 // is produced from flattening classes. We don't put closures (or
12262 // Closure base class) into here. There's a lot of them, but we can
12263 // predict their results without running build subclass pass on them.
12264 struct SubclassMetadata {
12265 // Immediate children and parents of class (not transitive!).
12266 struct Meta {
12267 std::vector<SString> children;
12268 std::vector<SString> parents;
12269 size_t idx; // Index into all classes vector.
12271 TSStringToOneT<Meta> meta;
12272 // All classes to be processed
12273 std::vector<SString> all;
12276 // Metadata used to drive the init-types pass. This is produced from
12277 // flattening classes and added to when building subclasses.
12278 struct InitTypesMetadata {
12279 struct ClsMeta {
12280 // Dependencies of the class. A dependency is a class in a
12281 // property/param/return type-hint.
12282 TSStringSet deps;
12283 TSStringSet candidateRegOnlyEquivs;
12285 struct FuncMeta {
12286 // Same as ClsMeta, but for the func
12287 TSStringSet deps;
12289 // Modifications to make to an unit
12290 struct Fixup {
12291 std::vector<SString> addClass;
12292 std::vector<SString> removeFunc;
12293 template <typename SerDe> void serde(SerDe& sd) {
12294 sd(addClass)(removeFunc);
12297 TSStringToOneT<ClsMeta> classes;
12298 FSStringToOneT<FuncMeta> funcs;
12299 SStringToOneT<Fixup> fixups;
12300 SStringToOneT<std::vector<FuncFamilyEntry>> nameOnlyFF;
12303 std::tuple<SubclassMetadata, InitTypesMetadata, std::vector<InterfaceConflicts>>
12304 flatten_classes(IndexData& index, IndexFlattenMetadata meta) {
12305 trace_time trace("flatten classes", index.sample);
12306 trace.ignore_client_stats();
12308 using namespace folly::gen;
12310 struct ClassUpdate {
12311 SString name;
12312 UniquePtrRef<php::Class> cls;
12313 UniquePtrRef<php::ClassBytecode> bytecode;
12314 UniquePtrRef<ClassInfo2> cinfo;
12315 SString unitToAddTo;
12316 TSStringSet typeUses;
12317 bool isInterface{false};
12318 bool has86init{false};
12319 CompactVector<SString> parents;
12321 struct FuncUpdate {
12322 SString name;
12323 UniquePtrRef<php::Func> func;
12324 UniquePtrRef<FuncInfo2> finfo;
12325 TSStringSet typeUses;
12327 struct ClosureUpdate {
12328 SString name;
12329 SString context;
12330 SString unit;
12332 struct MethodUpdate {
12333 SString name;
12334 UniquePtrRef<MethodsWithoutCInfo> methods;
12336 using Update =
12337 boost::variant<ClassUpdate, FuncUpdate, ClosureUpdate, MethodUpdate>;
12338 using UpdateVec = std::vector<Update>;
12340 tbb::concurrent_hash_map<
12341 SString,
12342 InterfaceConflicts,
12343 string_data_hash_tsame
12344 > ifaceConflicts;
12346 auto const run = [&] (FlattenClassesWork work) -> coro::Task<UpdateVec> {
12347 co_await coro::co_reschedule_on_current_executor;
12349 if (work.classes.empty() &&
12350 work.funcs.empty() &&
12351 work.uninstantiable.empty()) {
12352 assertx(work.deps.empty());
12353 co_return UpdateVec{};
12356 Client::ExecMetadata metadata{
12357 .job_key = folly::sformat(
12358 "flatten classes {}",
12359 work.classes.empty()
12360 ? (work.uninstantiable.empty()
12361 ? work.funcs[0]
12362 : work.uninstantiable[0])
12363 : work.classes[0]
12366 auto classes = from(work.classes)
12367 | map([&] (SString c) { return index.classRefs.at(c); })
12368 | as<std::vector>();
12369 auto deps = from(work.deps)
12370 | map([&] (SString c) { return index.classRefs.at(c); })
12371 | as<std::vector>();
12372 auto bytecode = (from(work.classes) + from(work.deps))
12373 | map([&] (SString c) { return index.classBytecodeRefs.at(c); })
12374 | as<std::vector>();
12375 auto funcs = from(work.funcs)
12376 | map([&] (SString f) { return index.funcRefs.at(f); })
12377 | as<std::vector>();
12378 auto uninstantiableRefs = from(work.uninstantiable)
12379 | map([&] (SString c) { return index.classRefs.at(c); })
12380 | as<std::vector>();
12382 // Gather any type-mappings or missing types referenced by these
12383 // classes or funcs.
12384 std::vector<TypeMapping> typeMappings;
12385 std::vector<SString> missingTypes;
12387 TSStringSet seen;
12389 auto const addUnresolved = [&] (SString u) {
12390 if (!seen.emplace(u).second) return;
12391 if (auto const m = folly::get_ptr(meta.typeMappings, u)) {
12392 // If the type-mapping maps an enum, and that enum is
12393 // uninstantiable, just treat it as a missing type.
12394 if (m->firstEnum && meta.cls.at(m->firstEnum).uninstantiable) {
12395 missingTypes.emplace_back(u);
12396 } else {
12397 typeMappings.emplace_back(*m);
12399 } else if (!index.classRefs.count(u) ||
12400 meta.cls.at(u).uninstantiable) {
12401 missingTypes.emplace_back(u);
12405 auto const addClass = [&] (SString c) {
12406 for (auto const u : meta.cls.at(c).unresolvedTypes) addUnresolved(u);
12408 auto const addFunc = [&] (SString f) {
12409 for (auto const u : meta.func.at(f).unresolvedTypes) addUnresolved(u);
12412 for (auto const c : work.classes) addClass(c);
12413 for (auto const d : work.deps) addClass(d);
12414 for (auto const f : work.funcs) addFunc(f);
12416 std::sort(
12417 begin(typeMappings), end(typeMappings),
12418 [] (const TypeMapping& a, const TypeMapping& b) {
12419 return string_data_lt_type{}(a.name, b.name);
12422 std::sort(begin(missingTypes), end(missingTypes), string_data_lt_type{});
12425 auto [typeMappingsRef, missingTypesRef, config] = co_await
12426 coro::collectAll(
12427 index.client->store(std::move(typeMappings)),
12428 index.client->store(std::move(missingTypes)),
12429 index.configRef->getCopy()
12432 auto results = co_await
12433 index.client->exec(
12434 s_flattenJob,
12435 std::move(config),
12436 singleton_vec(
12437 std::make_tuple(
12438 std::move(classes),
12439 std::move(deps),
12440 std::move(bytecode),
12441 std::move(funcs),
12442 std::move(uninstantiableRefs),
12443 std::move(typeMappingsRef),
12444 std::move(missingTypesRef)
12447 std::move(metadata)
12449 // Every flattening job is a single work-unit, so we should only
12450 // ever get one result for each one.
12451 assertx(results.size() == 1);
12452 auto& [clsRefs, bytecodeRefs, cinfoRefs, funcRefs,
12453 finfoRefs, methodRefs, classMetaRef] = results[0];
12454 assertx(clsRefs.size() == cinfoRefs.size());
12455 assertx(clsRefs.size() == bytecodeRefs.size());
12456 assertx(funcRefs.size() == work.funcs.size());
12457 assertx(funcRefs.size() == finfoRefs.size());
12459 // We need the output metadata, but everything else stays
12460 // uploaded.
12461 auto clsMeta = co_await index.client->load(std::move(classMetaRef));
12462 assertx(methodRefs.size() == clsMeta.uninstantiable.size());
12464 // Create the updates by combining the job output (but skipping
12465 // over uninstantiable classes).
12466 UpdateVec updates;
12467 updates.reserve(work.classes.size() * 3);
12469 size_t outputIdx = 0;
12470 size_t parentIdx = 0;
12471 size_t methodIdx = 0;
12472 for (auto const name : work.classes) {
12473 if (clsMeta.uninstantiable.count(name)) {
12474 assertx(methodIdx < methodRefs.size());
12475 updates.emplace_back(
12476 MethodUpdate{ name, std::move(methodRefs[methodIdx]) }
12478 ++methodIdx;
12479 continue;
12481 assertx(outputIdx < clsRefs.size());
12482 assertx(outputIdx < clsMeta.classTypeUses.size());
12484 auto const& flattenMeta = meta.cls.at(name);
12485 updates.emplace_back(
12486 ClassUpdate{
12487 name,
12488 std::move(clsRefs[outputIdx]),
12489 std::move(bytecodeRefs[outputIdx]),
12490 std::move(cinfoRefs[outputIdx]),
12491 nullptr,
12492 std::move(clsMeta.classTypeUses[outputIdx]),
12493 (bool)clsMeta.interfaces.count(name),
12494 (bool)clsMeta.with86init.count(name)
12498 // Ignore closures. We don't run the build subclass pass for
12499 // closures, so we don't need information for them.
12500 if (!flattenMeta.isClosure) {
12501 assertx(parentIdx < clsMeta.parents.size());
12502 auto const& parents = clsMeta.parents[parentIdx].names;
12503 auto& update = boost::get<ClassUpdate>(updates.back());
12504 update.parents.insert(
12505 end(update.parents), begin(parents), end(parents)
12507 ++parentIdx;
12510 ++outputIdx;
12512 assertx(outputIdx == clsRefs.size());
12513 assertx(outputIdx == clsMeta.classTypeUses.size());
12515 for (auto const& [unit, name, context] : clsMeta.newClosures) {
12516 updates.emplace_back(ClosureUpdate{ name, context, unit });
12519 for (auto const name : work.uninstantiable) {
12520 assertx(clsMeta.uninstantiable.count(name));
12521 assertx(methodIdx < methodRefs.size());
12522 updates.emplace_back(
12523 MethodUpdate{ name, std::move(methodRefs[methodIdx]) }
12525 ++methodIdx;
12527 assertx(methodIdx == methodRefs.size());
12529 assertx(work.funcs.size() == clsMeta.funcTypeUses.size());
12530 for (size_t i = 0, size = work.funcs.size(); i < size; ++i) {
12531 updates.emplace_back(
12532 FuncUpdate{
12533 work.funcs[i],
12534 std::move(funcRefs[i]),
12535 std::move(finfoRefs[i]),
12536 std::move(clsMeta.funcTypeUses[i])
12541 for (auto const& c : clsMeta.interfaceConflicts) {
12542 decltype(ifaceConflicts)::accessor acc;
12543 ifaceConflicts.insert(acc, c.name);
12544 acc->second.name = c.name;
12545 acc->second.usage += c.usage;
12546 acc->second.conflicts.insert(begin(c.conflicts), end(c.conflicts));
12549 co_return updates;
12552 // Calculate the grouping of classes into work units for flattening,
12553 // perform the flattening, and gather all updates from the jobs.
12554 auto allUpdates = [&] {
12555 auto assignments = flatten_classes_assign(meta);
12557 trace_time trace2("flatten classes work", index.sample);
12558 return coro::blockingWait(coro::collectAllRange(
12559 from(assignments)
12560 | move
12561 | map([&] (FlattenClassesWork w) {
12562 return run(std::move(w)).scheduleOn(index.executor->sticky());
12564 | as<std::vector>()
12566 }();
12568 // Now take the updates and apply them to the Index tables. This
12569 // needs to be done in a single threaded context (per data
12570 // structure). This also gathers up all the fixups needed.
12572 SubclassMetadata subclassMeta;
12573 InitTypesMetadata initTypesMeta;
12576 trace_time trace2("flatten classes update");
12577 trace2.ignore_client_stats();
12579 parallel::parallel(
12580 [&] {
12581 for (auto& updates : allUpdates) {
12582 for (auto& update : updates) {
12583 auto u = boost::get<ClassUpdate>(&update);
12584 if (!u) continue;
12585 index.classRefs.insert_or_assign(
12586 u->name,
12587 std::move(u->cls)
12592 [&] {
12593 for (auto& updates : allUpdates) {
12594 for (auto& update : updates) {
12595 auto u = boost::get<ClassUpdate>(&update);
12596 if (!u) continue;
12597 always_assert(
12598 index.classInfoRefs.emplace(
12599 u->name,
12600 std::move(u->cinfo)
12601 ).second
12606 [&] {
12607 for (auto& updates : allUpdates) {
12608 for (auto& update : updates) {
12609 auto u = boost::get<ClassUpdate>(&update);
12610 if (!u) continue;
12611 index.classBytecodeRefs.insert_or_assign(
12612 u->name,
12613 std::move(u->bytecode)
12618 [&] {
12619 for (auto& updates : allUpdates) {
12620 for (auto& update : updates) {
12621 auto u = boost::get<FuncUpdate>(&update);
12622 if (!u) continue;
12623 index.funcRefs.at(u->name) = std::move(u->func);
12627 [&] {
12628 for (auto& updates : allUpdates) {
12629 for (auto& update : updates) {
12630 auto u = boost::get<FuncUpdate>(&update);
12631 if (!u) continue;
12632 always_assert(
12633 index.funcInfoRefs.emplace(
12634 u->name,
12635 std::move(u->finfo)
12636 ).second
12641 [&] {
12642 for (auto& updates : allUpdates) {
12643 for (auto& update : updates) {
12644 // Keep closure mappings up to date.
12645 auto u = boost::get<ClosureUpdate>(&update);
12646 if (!u) continue;
12647 initTypesMeta.fixups[u->unit].addClass.emplace_back(u->name);
12648 assertx(u->context);
12649 always_assert(
12650 index.closureToClass.emplace(u->name, u->context).second
12652 index.classToClosures[u->context].emplace(u->name);
12655 for (auto& [unit, deletions] : meta.unitDeletions) {
12656 initTypesMeta.fixups[unit].removeFunc = std::move(deletions);
12659 [&] {
12660 // A class which didn't have an 86*init function previously
12661 // can gain one due to trait flattening. Update that here.
12662 for (auto const& updates : allUpdates) {
12663 for (auto const& update : updates) {
12664 auto u = boost::get<ClassUpdate>(&update);
12665 if (!u || !u->has86init) continue;
12666 index.classesWith86Inits.emplace(u->name);
12670 [&] {
12671 // Build metadata for the next build subclass pass.
12672 auto& all = subclassMeta.all;
12673 auto& meta = subclassMeta.meta;
12674 for (auto& updates : allUpdates) {
12675 for (auto& update : updates) {
12676 auto u = boost::get<ClassUpdate>(&update);
12677 if (!u) continue;
12679 // We shouldn't have parents for closures because we
12680 // special case those explicitly.
12681 if (is_closure_name(u->name) || is_closure_base(u->name)) {
12682 assertx(u->parents.empty());
12683 continue;
12685 // Otherwise build the children lists from the parents.
12686 all.emplace_back(u->name);
12687 for (auto const p : u->parents) {
12688 meta[p].children.emplace_back(u->name);
12690 auto& parents = meta[u->name].parents;
12691 assertx(parents.empty());
12692 parents.insert(
12693 end(parents),
12694 begin(u->parents), end(u->parents)
12699 std::sort(begin(all), end(all), string_data_lt_type{});
12700 // Make sure there's no duplicates:
12701 assertx(std::adjacent_find(begin(all), end(all)) == end(all));
12703 for (size_t i = 0; i < all.size(); ++i) meta[all[i]].idx = i;
12705 [&] {
12706 for (auto& updates : allUpdates) {
12707 for (auto& update : updates) {
12708 auto u = boost::get<MethodUpdate>(&update);
12709 if (!u) continue;
12710 always_assert(
12711 index.uninstantiableClsMethRefs.emplace(
12712 u->name,
12713 std::move(u->methods)
12714 ).second
12719 [&] {
12720 for (auto& updates : allUpdates) {
12721 for (auto& update : updates) {
12722 if (auto const u = boost::get<ClassUpdate>(&update)) {
12723 auto& meta = initTypesMeta.classes[u->name];
12724 assertx(meta.deps.empty());
12725 meta.deps.insert(begin(u->typeUses), end(u->typeUses));
12726 } else if (auto const u = boost::get<FuncUpdate>(&update)) {
12727 auto& meta = initTypesMeta.funcs[u->name];
12728 assertx(meta.deps.empty());
12729 meta.deps.insert(begin(u->typeUses), end(u->typeUses));
12737 return std::make_tuple(
12738 std::move(subclassMeta),
12739 std::move(initTypesMeta),
12740 [&] {
12741 std::vector<InterfaceConflicts> out;
12742 out.reserve(ifaceConflicts.size());
12743 for (auto& [_, c] : ifaceConflicts) out.emplace_back(std::move(c));
12744 return out;
12749 //////////////////////////////////////////////////////////////////////
12750 // Subclass list
12753 * Subclass lists are built in a similar manner as flattening classes,
12754 * except the order is reversed.
12756 * However, there is one complication: the transitive children of each
12757 * class can be huge. In fact, for large hierarchies, they can easily
12758 * be too large to (efficiently) handle in a single job.
12760 * Rather than (always) processing everything in a single pass, we
12761 * might need to use multiple passes to keep the fan-in down. When
12762 * calculating the work buckets, we keep the size of each bucket into
12763 * account and don't allow any bucket to grow too large. If it does,
12764 * we'll just process that bucket, and process any dependencies in the
12765 * next pass.
12767 * This isn't sufficient. A single class have (far) more direct
12768 * children than we want in a single bucket. Multiple passes don't
12769 * help here because there's no intermediate classes to use as an
12770 * output. To fix this, we insert "splits", which serve to "summarize"
12771 * some subset of a class' direct children.
12773 * For example, suppose a class has 10k direct children, and our
12774 * maximum bucket size is 1k. On the first pass we'll process all of
12775 * the children in ~10 different jobs, each one processing 1k of the
12776 * children, and producing a single split node. The second pass will
12777 * actually process the class and take all of the splits as inputs
12778 * (not the actual children). The inputs to the job has been reduced
12779 * from 10k to 10. This is a simplification. In reality a job can
12780 * produce multiple splits, and inputs can be a mix of splits and
12781 * actual classes. In extreme cases, you might need multiple rounds of
12782 * splits before processing the class.
12784 * There is one other difference between this and the flatten classes
12785 * pass. Unlike in flatten classes, every class (except leafs) are
12786 * "roots" here. We do not promote any dependencies. This causes more
12787 * work overall, but it lets us process more classes in parallel.
12791 * Extern-worker job to build ClassInfo2 subclass lists, and calculate
12792 * various properties on the ClassInfo2 from it.
12794 struct BuildSubclassListJob {
12795 static std::string name() { return "hhbbc-build-subclass"; }
12796 static void init(const Config& config) {
12797 process_init(config.o, config.gd, false);
12798 ClassGraph::init();
12800 static void fini() { ClassGraph::destroy(); }
12802 // Aggregated data for some group of classes. The data can either
12803 // come from a split node, or inferred from a group of classes.
12804 struct Data {
12805 // Information about all of the methods with a particular name
12806 // between all of the classes in this Data.
12807 struct MethInfo {
12808 // Methods which are present on at least one regular class.
12809 MethRefSet regularMeths;
12810 // Methods which are only present on non-regular classes, but is
12811 // private on at least one class. These are sometimes treated
12812 // like a regular class.
12813 MethRefSet nonRegularPrivateMeths;
12814 // Methods which are only present on non-regular classes (and
12815 // never private). These three sets are always disjoint.
12816 MethRefSet nonRegularMeths;
12818 Optional<FuncFamily2::StaticInfo> allStatic;
12819 Optional<FuncFamily2::StaticInfo> regularStatic;
12821 // Whether all classes in this Data have a method with this
12822 // name.
12823 bool complete{true};
12824 // Whether all regular classes in this Data have a method with
12825 // this name.
12826 bool regularComplete{true};
12827 // Whether any of the methods has a private ancestor.
12828 bool privateAncestor{false};
12830 template <typename SerDe> void serde(SerDe& sd) {
12831 sd(regularMeths, std::less<MethRef>{})
12832 (nonRegularPrivateMeths, std::less<MethRef>{})
12833 (nonRegularMeths, std::less<MethRef>{})
12834 (allStatic)
12835 (regularStatic)
12836 (complete)
12837 (regularComplete)
12838 (privateAncestor)
12842 SStringToOneT<MethInfo> methods;
12844 // The name of properties which might have null values even if the
12845 // type-constraint doesn't allow it (due to system provided
12846 // initial values).
12847 SStringSet propsWithImplicitNullable;
12849 // The classes for whom isMocked would be true due to one of the
12850 // classes making up this Data. The classes in this set may not
12851 // necessarily be also part of this Data.
12852 TSStringSet mockedClasses;
12854 bool hasConstProp{false};
12855 bool hasReifiedGeneric{false};
12857 bool isSubMocked{false};
12859 // The meaning of these differ depending on whether the ClassInfo
12860 // contains just it's info, or all of it's subclass info.
12861 bool hasRegularClass{false};
12862 bool hasRegularClassFull{false};
12864 template <typename SerDe> void serde(SerDe& sd) {
12865 sd(methods, string_data_lt{})
12866 (propsWithImplicitNullable, string_data_lt{})
12867 (mockedClasses, string_data_lt_type{})
12868 (hasConstProp)
12869 (hasReifiedGeneric)
12870 (isSubMocked)
12871 (hasRegularClass)
12872 (hasRegularClassFull)
12877 // Split node. Used to wrap a Data when summarizing some subset of a
12878 // class' children.
12879 struct Split {
12880 Split() = default;
12881 Split(SString name, SString cls) : name{name}, cls{cls} {}
12883 SString name;
12884 SString cls;
12885 CompactVector<SString> children;
12886 CompactVector<ClassGraph> classGraphs;
12887 Data data;
12889 template <typename SerDe> void serde(SerDe& sd) {
12890 ScopedStringDataIndexer _;
12891 ClassGraph::ScopedSerdeState _2;
12892 sd(name)
12893 (cls)
12894 (children)
12895 (classGraphs, nullptr)
12896 (data)
12901 // Mark a dependency on a class to a split node. Since the splits
12902 // are not actually part of the hierarchy, the relationship between
12903 // classes and splits cannot be inferred otherwise.
12904 struct EdgeToSplit {
12905 SString cls;
12906 SString split;
12907 template <typename SerDe> void serde(SerDe& sd) {
12908 sd(cls)
12909 (split)
12914 // Job output meant to be downloaded and drive the next round.
12915 struct OutputMeta {
12916 // For every input ClassInfo, the set of func families present in
12917 // that ClassInfo's method family table. If the ClassInfo is used
12918 // as a dep later, these func families need to be provided as
12919 // well.
12920 std::vector<hphp_fast_set<FuncFamily2::Id>> funcFamilyDeps;
12921 // The ids of new (not provided as an input) func families
12922 // produced. The ids are grouped together to become
12923 // FuncFamilyGroups.
12924 std::vector<std::vector<FuncFamily2::Id>> newFuncFamilyIds;
12925 // Func family entries corresponding to all methods with a
12926 // particular name encountered in this job. Multiple jobs will
12927 // generally produce func family entries for the same name, so
12928 // they must be aggregated together afterwards.
12929 std::vector<std::pair<SString, FuncFamilyEntry>> nameOnly;
12930 std::vector<std::vector<SString>> regOnlyEquivCandidates;
12932 // For every output class, the set of classes which that class has
12933 // inherited class constants from.
12934 TSStringToOneT<TSStringSet> cnsBases;
12936 template <typename SerDe> void serde(SerDe& sd) {
12937 ScopedStringDataIndexer _;
12938 sd(funcFamilyDeps, std::less<FuncFamily2::Id>{})
12939 (newFuncFamilyIds)
12940 (nameOnly)
12941 (regOnlyEquivCandidates)
12942 (cnsBases, string_data_lt_type{}, string_data_lt_type{})
12946 using Output = Multi<
12947 Variadic<std::unique_ptr<ClassInfo2>>,
12948 Variadic<std::unique_ptr<Split>>,
12949 Variadic<std::unique_ptr<php::Class>>,
12950 Variadic<FuncFamilyGroup>,
12951 Variadic<std::unique_ptr<ClassInfo2>>,
12952 OutputMeta
12955 // Each job takes the list of classes and splits which should be
12956 // produced, dependency classes and splits (which are not updated),
12957 // edges between classes and splits, and func families (needed by
12958 // dependency classes). Leafs are like deps, except they'll be
12959 // considered as part of calculating the name-only func families
12960 // because its possible for them to be disjoint from classes.
12961 // (normal deps are not considered as their data is guaranteed to
12962 // be included by a class).
12963 static Output
12964 run(Variadic<std::unique_ptr<ClassInfo2>> classes,
12965 Variadic<std::unique_ptr<ClassInfo2>> deps,
12966 Variadic<std::unique_ptr<ClassInfo2>> leafs,
12967 Variadic<std::unique_ptr<Split>> splits,
12968 Variadic<std::unique_ptr<Split>> splitDeps,
12969 Variadic<std::unique_ptr<php::Class>> phpClasses,
12970 Variadic<EdgeToSplit> edges,
12971 Variadic<FuncFamilyGroup> funcFamilies) {
12972 // Store mappings of names to classes and edges.
12973 LocalIndex index;
12975 if (debug) {
12976 for (auto const& cinfo : classes.vals) {
12977 always_assert(!cinfo->classGraph.isMissing());
12978 always_assert(!cinfo->classGraph.hasCompleteChildren());
12979 always_assert(!cinfo->classGraph.isConservative());
12981 for (auto const& cinfo : leafs.vals) {
12982 always_assert(!cinfo->classGraph.isMissing());
12983 always_assert(!cinfo->classGraph.hasCompleteChildren());
12984 always_assert(!cinfo->classGraph.isConservative());
12986 for (auto const& cinfo : deps.vals) {
12987 always_assert(!cinfo->classGraph.isMissing());
12989 for (auto const& split : splits.vals) {
12990 for (auto const child : split->classGraphs) {
12991 always_assert(!child.isMissing());
12992 always_assert(!child.hasCompleteChildren());
12993 always_assert(!child.isConservative());
12996 for (auto const& split : splitDeps.vals) {
12997 for (auto const child : split->classGraphs) {
12998 always_assert(!child.isMissing());
12999 always_assert(child.hasCompleteChildren() ||
13000 child.isConservative());
13005 for (auto& cinfo : classes.vals) {
13006 assertx(!is_closure_name(cinfo->name));
13007 always_assert(
13008 index.classInfos.emplace(cinfo->name, cinfo.get()).second
13010 index.top.emplace(cinfo->name);
13012 for (auto& cinfo : deps.vals) {
13013 assertx(!is_closure_name(cinfo->name));
13014 always_assert(
13015 index.classInfos.emplace(cinfo->name, cinfo.get()).second
13018 for (auto& cinfo : leafs.vals) {
13019 assertx(!is_closure_name(cinfo->name));
13020 always_assert(
13021 index.classInfos.emplace(cinfo->name, cinfo.get()).second
13024 for (auto& split : splits.vals) {
13025 always_assert(
13026 index.splits.emplace(split->name, split.get()).second
13028 index.top.emplace(split->name);
13030 for (auto& split : splitDeps.vals) {
13031 assertx(split->children.empty());
13032 always_assert(
13033 index.splits.emplace(split->name, split.get()).second
13036 for (auto& cls : phpClasses.vals) {
13037 assertx(!is_closure(*cls));
13038 always_assert(
13039 index.classes.emplace(cls->name, cls.get()).second
13042 for (auto& group : funcFamilies.vals) {
13043 for (auto& ff : group.m_ffs) {
13044 auto const id = ff->m_id;
13045 // We could have multiple groups which contain the same
13046 // FuncFamily, so don't assert uniqueness here. We'll just
13047 // take the first one we see (they should all be equivalent).
13048 index.funcFamilies.emplace(id, std::move(ff));
13052 index.aggregateData.reserve(index.top.size());
13054 OutputMeta meta;
13056 // Mark all of the classes (including leafs) as being complete
13057 // since their subclass lists are correct.
13058 for (auto& cinfo : leafs.vals) {
13059 if (cinfo->classGraph.hasCompleteChildren()) continue;
13060 cinfo->classGraph.setComplete();
13062 for (auto& cinfo : classes.vals) {
13063 if (cinfo->classGraph.hasCompleteChildren() ||
13064 cinfo->classGraph.isConservative()) {
13065 continue;
13067 cinfo->classGraph.setComplete();
13069 for (auto& cinfo : deps.vals) {
13070 if (cinfo->classGraph.hasCompleteChildren() ||
13071 cinfo->classGraph.isConservative()) {
13072 continue;
13074 cinfo->classGraph.setComplete();
13076 for (auto& split : splits.vals) {
13077 for (auto child : split->classGraphs) {
13078 if (child.hasCompleteChildren() ||
13079 child.isConservative()) {
13080 continue;
13082 child.setComplete();
13086 // Store the regular-only equivalent classes in the output
13087 // metadata. This will be used in the init-types pass.
13088 meta.regOnlyEquivCandidates.reserve(classes.vals.size());
13089 for (auto& cinfo : classes.vals) {
13090 meta.regOnlyEquivCandidates.emplace_back();
13091 auto& candidates = meta.regOnlyEquivCandidates.back();
13092 for (auto const g : cinfo->classGraph.candidateRegOnlyEquivs()) {
13093 candidates.emplace_back(g.name());
13097 // If there's no classes or splits, this job is doing nothing but
13098 // calculating name only func family entries (so should have at
13099 // least one leaf).
13100 if (!index.top.empty()) {
13101 build_children(index, edges.vals);
13102 process_roots(index, classes.vals, splits.vals);
13103 } else {
13104 assertx(classes.vals.empty());
13105 assertx(splits.vals.empty());
13106 assertx(index.splits.empty());
13107 assertx(index.funcFamilies.empty());
13108 assertx(!leafs.vals.empty());
13109 assertx(!index.classInfos.empty());
13112 meta.nameOnly =
13113 make_name_only_method_entries(index, classes.vals, leafs.vals);
13115 // Record dependencies for each input class. A func family is a
13116 // dependency of the class if it appears in the method families
13117 // table.
13118 meta.funcFamilyDeps.reserve(classes.vals.size());
13119 for (auto const& cinfo : classes.vals) {
13120 meta.funcFamilyDeps.emplace_back();
13121 auto& deps = meta.funcFamilyDeps.back();
13122 for (auto const& [_, entry] : cinfo->methodFamilies) {
13123 match<void>(
13124 entry.m_meths,
13125 [&] (const FuncFamilyEntry::BothFF& e) { deps.emplace(e.m_ff); },
13126 [&] (const FuncFamilyEntry::FFAndSingle& e) { deps.emplace(e.m_ff); },
13127 [&] (const FuncFamilyEntry::FFAndNone& e) { deps.emplace(e.m_ff); },
13128 [&] (const FuncFamilyEntry::BothSingle&) {},
13129 [&] (const FuncFamilyEntry::SingleAndNone&) {},
13130 [&] (const FuncFamilyEntry::None&) {}
13135 Variadic<FuncFamilyGroup> funcFamilyGroups;
13136 group_func_families(index, funcFamilyGroups.vals, meta.newFuncFamilyIds);
13138 auto const addCnsBase = [&] (const ClassInfo2& cinfo) {
13139 auto& bases = meta.cnsBases[cinfo.name];
13140 for (auto const& [_, idx] : cinfo.clsConstants) {
13141 if (!cinfo.name->tsame(idx.idx.cls)) bases.emplace(idx.idx.cls);
13144 for (auto const& cinfo : classes.vals) addCnsBase(*cinfo);
13145 for (auto const& cinfo : leafs.vals) addCnsBase(*cinfo);
13147 // We only need to provide php::Class which correspond to a class
13148 // which wasn't a dep.
13149 phpClasses.vals.erase(
13150 std::remove_if(
13151 begin(phpClasses.vals),
13152 end(phpClasses.vals),
13153 [&] (const std::unique_ptr<php::Class>& c) {
13154 return !index.top.count(c->name);
13157 end(phpClasses.vals)
13160 return std::make_tuple(
13161 std::move(classes),
13162 std::move(splits),
13163 std::move(phpClasses),
13164 std::move(funcFamilyGroups),
13165 std::move(leafs),
13166 std::move(meta)
13170 protected:
13173 * MethInfo caching
13175 * When building a func family, MethRefSets must be sorted, then
13176 * hashed in order to generate the unique id. Once we do so, we can
13177 * then check if that func family already exists. For large func
13178 * families, this can very expensive and we might have to do this
13179 * (wasted) work multiple times.
13181 * To avoid this, we add a cache before the sorting/hashing
13182 * step. Instead of using a func family id (which is the expensive
13183 * thing to generate), the cache is keyed by the set of methods
13184 * directly. A commutative hash is used so that we don't actually
13185 * have to sort the MethRefSets, and equality is just equality of
13186 * the MethRefSets. Moreover, we make use of hetereogenous lookup to
13187 * avoid having to copy any MethRefSets (again they can be large)
13188 * when doing the lookup.
13191 // What is actually stored in the cache. Keeps a copy of the
13192 // MethRefSets.
13193 struct MethInfoTuple {
13194 MethRefSet regular;
13195 MethRefSet nonRegularPrivate;
13196 MethRefSet nonRegular;
13198 // Used for lookups. Just has pointers to the MethRefSets, so we
13199 // don't have to do any copying for the lookup.
13200 struct MethInfoTupleProxy {
13201 const MethRefSet* regular;
13202 const MethRefSet* nonRegularPrivate;
13203 const MethRefSet* nonRegular;
13206 struct MethInfoTupleHasher {
13207 using is_transparent = void;
13209 size_t operator()(const MethInfoTuple& t) const {
13210 auto const h1 = folly::hash::commutative_hash_combine_range_generic(
13211 0, MethRef::Hash{}, begin(t.regular), end(t.regular)
13213 auto const h2 = folly::hash::commutative_hash_combine_range_generic(
13214 0, MethRef::Hash{}, begin(t.nonRegularPrivate), end(t.nonRegularPrivate)
13216 auto const h3 = folly::hash::commutative_hash_combine_range_generic(
13217 0, MethRef::Hash{}, begin(t.nonRegular), end(t.nonRegular)
13219 return folly::hash::hash_combine(h1, h2, h3);
13221 size_t operator()(const MethInfoTupleProxy& t) const {
13222 auto const h1 = folly::hash::commutative_hash_combine_range_generic(
13223 0, MethRef::Hash{}, begin(*t.regular), end(*t.regular)
13225 auto const h2 = folly::hash::commutative_hash_combine_range_generic(
13226 0, MethRef::Hash{}, begin(*t.nonRegularPrivate), end(*t.nonRegularPrivate)
13228 auto const h3 = folly::hash::commutative_hash_combine_range_generic(
13229 0, MethRef::Hash{}, begin(*t.nonRegular), end(*t.nonRegular)
13231 return folly::hash::hash_combine(h1, h2, h3);
13234 struct MethInfoTupleEquals {
13235 using is_transparent = void;
13237 bool operator()(const MethInfoTuple& t1, const MethInfoTuple& t2) const {
13238 return
13239 t1.regular == t2.regular &&
13240 t1.nonRegularPrivate == t2.nonRegularPrivate &&
13241 t1.nonRegular == t2.nonRegular;
13243 bool operator()(const MethInfoTupleProxy& t1,
13244 const MethInfoTuple& t2) const {
13245 return
13246 *t1.regular == t2.regular &&
13247 *t1.nonRegularPrivate == t2.nonRegularPrivate &&
13248 *t1.nonRegular == t2.nonRegular;
13252 struct LocalIndex {
13253 // All ClassInfos, whether inputs or dependencies.
13254 TSStringToOneT<ClassInfo2*> classInfos;
13255 // All splits, whether inputs or dependencies.
13256 TSStringToOneT<Split*> splits;
13257 // All php::Class, whether inputs or dependencies.
13258 TSStringToOneT<php::Class*> classes;
13260 // ClassInfos and splits which are inputs (IE, we want to
13261 // calculate data for).
13262 TSStringSet top;
13264 // Aggregated data for an input
13265 TSStringToOneT<Data> aggregateData;
13267 // Mapping of input ClassInfos/splits to all of their subclasses
13268 // present in this Job. Some of the children may be splits, which
13269 // means some subset of the children were processed in another
13270 // Job.
13271 TSStringToOneT<std::vector<SString>> children;
13273 // The leafs in this job. This isn't necessarily an actual leaf,
13274 // but one whose children haven't been provided in this job
13275 // (because they've already been processed). Classes which are
13276 // leafs have information in their ClassInfo2 which reflect all of
13277 // their subclasses, otherwise just their own information.
13278 TSStringSet leafs;
13280 // All func families available in this Job, either from inputs, or
13281 // created during processing.
13282 hphp_fast_map<FuncFamily2::Id, std::unique_ptr<FuncFamily2>> funcFamilies;
13284 // Above mentioned func family cache. If an entry is present here,
13285 // we know the func family already exists and don't need to do
13286 // expensive sorting/hashing.
13287 hphp_fast_map<
13288 MethInfoTuple,
13289 FuncFamily2::Id,
13290 MethInfoTupleHasher,
13291 MethInfoTupleEquals
13292 > funcFamilyCache;
13294 // funcFamilies contains all func families. If a func family is
13295 // created during processing, it will be inserted here (used to
13296 // determine outputs).
13297 std::vector<FuncFamily2::Id> newFuncFamilies;
13299 php::Class& cls(SString name) {
13300 auto const it = classes.find(name);
13301 always_assert(it != end(classes));
13302 return *it->second;
13306 // Take all of the func families produced by this job and group them
13307 // together into FuncFamilyGroups. We produce both the
13308 // FuncFamilyGroups themselves, but also the associated ids in each
13309 // group (which will be output as metadata).
13310 static void group_func_families(
13311 LocalIndex& index,
13312 std::vector<FuncFamilyGroup>& groups,
13313 std::vector<std::vector<FuncFamily2::Id>>& ids
13315 constexpr size_t kGroupSize = 5000;
13317 // The grouping algorithm is very simple. First we sort all of the
13318 // func families by size. We then just group adjacent families
13319 // until their total size exceeds some threshold. Once it does, we
13320 // start a new group.
13321 std::sort(
13322 begin(index.newFuncFamilies),
13323 end(index.newFuncFamilies),
13324 [&] (const FuncFamily2::Id& id1, const FuncFamily2::Id& id2) {
13325 auto const& ff1 = index.funcFamilies.at(id1);
13326 auto const& ff2 = index.funcFamilies.at(id2);
13327 auto const size1 =
13328 ff1->m_regular.size() +
13329 ff1->m_nonRegularPrivate.size() +
13330 ff1->m_nonRegular.size();
13331 auto const size2 =
13332 ff2->m_regular.size() +
13333 ff2->m_nonRegularPrivate.size() +
13334 ff2->m_nonRegular.size();
13335 if (size1 != size2) return size1 < size2;
13336 return id1 < id2;
13340 size_t current = 0;
13341 for (auto const& id : index.newFuncFamilies) {
13342 auto& ff = index.funcFamilies.at(id);
13343 auto const size =
13344 ff->m_regular.size() +
13345 ff->m_nonRegularPrivate.size() +
13346 ff->m_nonRegular.size();
13347 if (groups.empty() || current > kGroupSize) {
13348 groups.emplace_back();
13349 ids.emplace_back();
13350 current = 0;
13352 groups.back().m_ffs.emplace_back(std::move(ff));
13353 ids.back().emplace_back(id);
13354 current += size;
13358 // Produce a set of name-only func families from the given set of
13359 // roots and leafs. It is assumed that any roots have already been
13360 // processed by process_roots, so that they'll have any appropriate
13361 // method families on them. Only entries which are "first name" are
13362 // processed.
13363 static std::vector<std::pair<SString, FuncFamilyEntry>>
13364 make_name_only_method_entries(
13365 LocalIndex& index,
13366 const std::vector<std::unique_ptr<ClassInfo2>>& roots,
13367 const std::vector<std::unique_ptr<ClassInfo2>>& leafs
13369 SStringToOneT<Data::MethInfo> infos;
13371 // Use the already calculated method family and merge
13372 // it's contents into what we already have.
13373 auto const process = [&] (const ClassInfo2* cinfo,
13374 SString name) {
13375 auto const it = cinfo->methodFamilies.find(name);
13376 always_assert(it != end(cinfo->methodFamilies));
13377 auto entryInfo = meth_info_from_func_family_entry(index, it->second);
13379 auto& info = infos[name];
13380 info.complete = false;
13381 info.regularComplete = false;
13383 for (auto const& meth : entryInfo.regularMeths) {
13384 if (info.regularMeths.count(meth)) continue;
13385 info.regularMeths.emplace(meth);
13386 info.nonRegularPrivateMeths.erase(meth);
13387 info.nonRegularMeths.erase(meth);
13389 for (auto const& meth : entryInfo.nonRegularPrivateMeths) {
13390 if (info.regularMeths.count(meth) ||
13391 info.nonRegularPrivateMeths.count(meth)) {
13392 continue;
13394 info.nonRegularPrivateMeths.emplace(meth);
13395 info.nonRegularMeths.erase(meth);
13397 for (auto const& meth : entryInfo.nonRegularMeths) {
13398 if (info.regularMeths.count(meth) ||
13399 info.nonRegularPrivateMeths.count(meth) ||
13400 info.nonRegularMeths.count(meth)) {
13401 continue;
13403 info.nonRegularMeths.emplace(meth);
13406 // Merge any StaticInfo entries we have for this method.
13407 if (entryInfo.allStatic) {
13408 if (!info.allStatic) {
13409 info.allStatic = std::move(*entryInfo.allStatic);
13410 } else {
13411 *info.allStatic |= *entryInfo.allStatic;
13414 if (entryInfo.regularStatic) {
13415 if (!info.regularStatic) {
13416 info.regularStatic = std::move(*entryInfo.regularStatic);
13417 } else {
13418 *info.regularStatic |= *entryInfo.regularStatic;
13423 // First process the roots. These methods might be overridden or
13424 // not.
13425 for (auto const& cinfo : roots) {
13426 for (auto const& [name, mte] : cinfo->methods) {
13427 if (!mte.firstName()) continue;
13428 if (!has_name_only_func_family(name)) continue;
13429 process(cinfo.get(), name);
13433 // Leafs are by definition always AttrNoOverride.
13434 for (auto const& cinfo : leafs) {
13435 for (auto const& [name, mte] : cinfo->methods) {
13436 if (!mte.firstName()) continue;
13437 if (!has_name_only_func_family(name)) continue;
13438 process(cinfo.get(), name);
13442 // Make the MethInfo order deterministic
13443 std::vector<SString> sorted;
13444 sorted.reserve(infos.size());
13445 for (auto const& [name, _] : infos) sorted.emplace_back(name);
13446 std::sort(begin(sorted), end(sorted), string_data_lt{});
13448 std::vector<std::pair<SString, FuncFamilyEntry>> entries;
13449 entries.reserve(infos.size());
13451 // Turn the MethInfos into FuncFamilyEntries
13452 for (auto const name : sorted) {
13453 auto& info = infos.at(name);
13454 entries.emplace_back(
13455 name,
13456 make_method_family_entry(index, name, std::move(info))
13459 return entries;
13462 // From the information present in the inputs, calculate a mapping
13463 // of classes and splits to their children (which can be other
13464 // classes or split nodes). This is not just direct children, but
13465 // all transitive subclasses.
13466 static void build_children(LocalIndex& index,
13467 const std::vector<EdgeToSplit>& edges) {
13468 TSStringToOneT<TSStringSet> children;
13469 // First record direct children. This can be inferred from the
13470 // parents of all present ClassInfos:
13472 // Everything starts out as a leaf.
13473 index.leafs.reserve(index.classInfos.size());
13474 for (auto const [name, _] : index.classInfos) {
13475 index.leafs.emplace(name);
13478 auto const onParent = [&] (SString parent, const ClassInfo2* child) {
13479 // Due to how work is divided, a class might have parents not
13480 // present in this job. Ignore those.
13481 if (!index.classInfos.count(parent)) return;
13482 children[parent].emplace(child->name);
13483 // If you're a parent, you're not a leaf.
13484 index.leafs.erase(parent);
13487 for (auto const [name, cinfo] : index.classInfos) {
13488 if (cinfo->parent) onParent(cinfo->parent, cinfo);
13489 for (auto const iface : cinfo->classGraph.declInterfaces()) {
13490 onParent(iface.name(), cinfo);
13492 for (auto const trait : cinfo->classGraph.usedTraits()) {
13493 onParent(trait.name(), cinfo);
13497 // Use the edges provided to the Job to know the mapping from
13498 // ClassInfo to split (it cannot be inferred otherwise).
13499 for (auto const& edge : edges) {
13500 SCOPE_ASSERT_DETAIL("Edge not present in job") {
13501 return folly::sformat("{} -> {}", edge.cls, edge.split);
13503 assertx(index.classInfos.count(edge.cls));
13504 assertx(index.splits.count(edge.split));
13505 children[edge.cls].emplace(edge.split);
13508 // Every "top" ClassInfo also has itself as a subclass (this
13509 // matches the semantics of the subclass list and simplifies the
13510 // processing).
13511 for (auto const name : index.top) {
13512 if (auto const split = folly::get_default(index.splits, name)) {
13513 // Copy the children list out of the split and add it to the
13514 // map.
13515 auto& c = children[name];
13516 for (auto const child : split->children) {
13517 assertx(index.classInfos.count(child) ||
13518 index.splits.count(child));
13519 c.emplace(child);
13524 // Calculate the indegree for all children. The indegree for a given node
13525 // differs depending on the top used, so these are calculated separately.
13526 auto const getIndegree = [&](SString root) {
13527 TSStringSet visited;
13528 TSStringSet toExplore{root};
13529 TSStringSet toExploreNext;
13530 TSStringToOneT<uint32_t> indegree;
13532 while (!toExplore.empty()) {
13533 toExploreNext.clear();
13534 for (auto const child : toExplore) {
13535 if (visited.count(child)) continue;
13536 visited.emplace(child);
13537 auto const it = children.find(child);
13538 // May not exist in children if processed in earlier round.
13539 if (it == end(children)) continue;
13540 for (auto const c : it->second) {
13541 indegree[c]++;
13542 toExploreNext.emplace(c);
13545 std::swap(toExplore, toExploreNext);
13547 return indegree;
13550 // Topological sort the transitive children for each node.
13551 for (auto& [name, _] : children) {
13552 auto indegree = getIndegree(name);
13553 std::vector<SString> sorted{name};
13555 int sortedBegin = 0;
13556 int sortedEnd = sorted.size();
13558 while (sortedBegin != sortedEnd) {
13559 for (int i = sortedBegin; i < sortedEnd; i++) {
13560 auto const cls = sorted[i];
13561 auto const it = children.find(cls);
13562 if (it == end(children)) continue;
13563 for (auto const c : it->second) {
13564 indegree[c]--;
13565 if (indegree[c] == 0) sorted.emplace_back(c);
13568 sortedBegin = sortedEnd;
13569 sortedEnd = sorted.size();
13571 assertx(indegree.size() + 1 == sorted.size());
13572 index.children[name] = std::move(sorted);
13576 static FuncFamily2::StaticInfo static_info_from_meth_meta(
13577 const FuncFamilyEntry::MethMetadata& meta
13579 FuncFamily2::StaticInfo info;
13580 info.m_numInOut = meta.m_numInOut;
13581 info.m_requiredCoeffects = meta.m_requiredCoeffects;
13582 info.m_coeffectRules = meta.m_coeffectRules;
13583 info.m_paramPreps = meta.m_prepKinds;
13584 info.m_minNonVariadicParams = info.m_maxNonVariadicParams =
13585 meta.m_nonVariadicParams;
13586 info.m_isReadonlyReturn = yesOrNo(meta.m_isReadonlyReturn);
13587 info.m_isReadonlyThis = yesOrNo(meta.m_isReadonlyThis);
13588 info.m_supportsAER = yesOrNo(meta.m_supportsAER);
13589 info.m_maybeReified = meta.m_isReified;
13590 info.m_maybeCaresAboutDynCalls = meta.m_caresAboutDyncalls;
13591 info.m_maybeBuiltin = meta.m_builtin;
13592 return info;
13595 // Turn a FuncFamilyEntry into an equivalent Data::MethInfo.
13596 static Data::MethInfo
13597 meth_info_from_func_family_entry(LocalIndex& index,
13598 const FuncFamilyEntry& entry) {
13599 Data::MethInfo info;
13600 info.complete = !entry.m_allIncomplete;
13601 info.regularComplete = !entry.m_regularIncomplete;
13602 info.privateAncestor = entry.m_privateAncestor;
13604 auto const getFF = [&] (const FuncFamily2::Id& id)
13605 -> const FuncFamily2& {
13606 auto const it = index.funcFamilies.find(id);
13607 always_assert_flog(
13608 it != end(index.funcFamilies),
13609 "Tried to access non-existent func-family '{}'",
13610 id.toString()
13612 return *it->second;
13615 match<void>(
13616 entry.m_meths,
13617 [&] (const FuncFamilyEntry::BothFF& e) {
13618 auto const& ff = getFF(e.m_ff);
13619 info.regularMeths.insert(
13620 begin(ff.m_regular),
13621 end(ff.m_regular)
13623 info.nonRegularPrivateMeths.insert(
13624 begin(ff.m_nonRegularPrivate),
13625 end(ff.m_nonRegularPrivate)
13627 info.nonRegularMeths.insert(
13628 begin(ff.m_nonRegular),
13629 end(ff.m_nonRegular)
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::FFAndSingle& e) {
13637 auto const& ff = getFF(e.m_ff);
13638 info.nonRegularMeths.insert(
13639 begin(ff.m_nonRegular),
13640 end(ff.m_nonRegular)
13642 if (e.m_nonRegularPrivate) {
13643 assertx(ff.m_nonRegularPrivate.size() == 1);
13644 assertx(ff.m_nonRegularPrivate[0] == e.m_regular);
13645 info.nonRegularPrivateMeths.emplace(e.m_regular);
13646 } else {
13647 assertx(ff.m_regular.size() == 1);
13648 assertx(ff.m_regular[0] == e.m_regular);
13649 info.regularMeths.emplace(e.m_regular);
13651 assertx(ff.m_allStatic);
13652 assertx(ff.m_regularStatic);
13653 info.allStatic = ff.m_allStatic;
13654 info.regularStatic = ff.m_regularStatic;
13656 [&] (const FuncFamilyEntry::FFAndNone& e) {
13657 auto const& ff = getFF(e.m_ff);
13658 assertx(ff.m_regular.empty());
13659 info.nonRegularMeths.insert(
13660 begin(ff.m_nonRegular),
13661 end(ff.m_nonRegular)
13663 assertx(ff.m_allStatic);
13664 assertx(!ff.m_regularStatic);
13665 info.allStatic = ff.m_allStatic;
13667 [&] (const FuncFamilyEntry::BothSingle& e) {
13668 if (e.m_nonRegularPrivate) {
13669 info.nonRegularPrivateMeths.emplace(e.m_all);
13670 } else {
13671 info.regularMeths.emplace(e.m_all);
13673 info.allStatic = info.regularStatic =
13674 static_info_from_meth_meta(e.m_meta);
13676 [&] (const FuncFamilyEntry::SingleAndNone& e) {
13677 info.nonRegularMeths.emplace(e.m_all);
13678 info.allStatic = static_info_from_meth_meta(e.m_meta);
13680 [&] (const FuncFamilyEntry::None&) {
13681 assertx(!info.complete);
13685 return info;
13688 // Create a Data representing the single ClassInfo or split with the
13689 // name "clsname".
13690 static Data build_data(LocalIndex& index, SString clsname) {
13691 // Does this name represent a class?
13692 if (auto const cinfo = folly::get_default(index.classInfos, clsname)) {
13693 // It's a class. We need to build a Data from what's in the
13694 // ClassInfo. If the ClassInfo hasn't been processed already
13695 // (it's a leaf or its the first round), the data will reflect
13696 // just that class. However if the ClassInfo has been processed
13697 // (it's a dependencies and it's past the first round), it will
13698 // reflect any subclasses of that ClassInfo as well.
13699 Data data;
13701 // Use the method family table to build initial MethInfos (if
13702 // the ClassInfo hasn't been processed this will be empty).
13703 for (auto const& [name, entry] : cinfo->methodFamilies) {
13704 data.methods.emplace(
13705 name,
13706 meth_info_from_func_family_entry(index, entry)
13710 auto const& cls = index.cls(cinfo->name);
13712 if (debug) {
13713 for (auto const& [name, mte] : cinfo->methods) {
13714 if (is_special_method_name(name)) continue;
13716 // Every method should have a methodFamilies entry. If this
13717 // method is AttrNoOverride, it shouldn't have a FuncFamily
13718 // associated with it.
13719 auto const it = cinfo->methodFamilies.find(name);
13720 always_assert(it != end(cinfo->methodFamilies));
13722 if (mte.attrs & AttrNoOverride) {
13723 always_assert(
13724 boost::get<FuncFamilyEntry::BothSingle>(&it->second.m_meths) ||
13725 boost::get<FuncFamilyEntry::SingleAndNone>(&it->second.m_meths)
13731 // Create a MethInfo for any missing methods as well.
13732 for (auto const name : cinfo->missingMethods) {
13733 assertx(!cinfo->methods.count(name));
13734 if (data.methods.count(name)) continue;
13735 // The MethInfo will be empty, and be marked as incomplete.
13736 auto& info = data.methods[name];
13737 info.complete = false;
13738 if (cinfo->isRegularClass) info.regularComplete = false;
13741 data.hasConstProp = cinfo->subHasConstProp;
13742 data.hasReifiedGeneric = cinfo->subHasReifiedGeneric;
13744 // If this is a mock class, any direct parent of this class
13745 // should be marked as mocked.
13746 if (cinfo->isMockClass) {
13747 for (auto const p : cinfo->classGraph.directParents()) {
13748 data.mockedClasses.emplace(p.name());
13751 data.isSubMocked = cinfo->isMocked || cinfo->isSubMocked;
13753 data.hasRegularClass = cinfo->isRegularClass;
13754 data.hasRegularClassFull =
13755 data.hasRegularClass || cinfo->classGraph.mightHaveRegularSubclass();
13756 if (!data.hasRegularClass && index.leafs.count(clsname)) {
13757 data.hasRegularClass = data.hasRegularClassFull;
13760 for (auto const& prop : cls.properties) {
13761 if (!(prop.attrs & (AttrStatic|AttrPrivate|AttrNoImplicitNullable))) {
13762 data.propsWithImplicitNullable.emplace(prop.name);
13766 return data;
13769 // It doesn't represent a class. It should represent a
13770 // split.
13772 // A split cannot be both a root and a dependency due to how we
13773 // set up the buckets.
13774 assertx(!index.top.count(clsname));
13775 auto const split = folly::get_default(index.splits, clsname);
13776 always_assert(split != nullptr);
13777 assertx(split->children.empty());
13778 // Split already contains the Data, so nothing to do but return
13779 // it.
13780 return split->data;
13783 static void update_data(Data& data, Data childData) {
13784 // Combine MethInfos for each method name:
13785 folly::erase_if(
13786 data.methods,
13787 [&] (std::pair<const SString, Data::MethInfo>& p) {
13788 auto const name = p.first;
13789 auto& info = p.second;
13791 if (auto const childInfo =
13792 folly::get_ptr(childData.methods, name)) {
13793 // There's a MethInfo with that name in the
13794 // child. "Promote" the MethRefs if they're in a superior
13795 // status in the child.
13796 for (auto const& meth : childInfo->regularMeths) {
13797 if (info.regularMeths.count(meth)) continue;
13798 info.regularMeths.emplace(meth);
13799 info.nonRegularPrivateMeths.erase(meth);
13800 info.nonRegularMeths.erase(meth);
13802 for (auto const& meth : childInfo->nonRegularPrivateMeths) {
13803 if (info.regularMeths.count(meth) ||
13804 info.nonRegularPrivateMeths.count(meth)) {
13805 continue;
13807 info.nonRegularPrivateMeths.emplace(meth);
13808 info.nonRegularMeths.erase(meth);
13810 for (auto const& meth : childInfo->nonRegularMeths) {
13811 if (info.regularMeths.count(meth) ||
13812 info.nonRegularPrivateMeths.count(meth) ||
13813 info.nonRegularMeths.count(meth)) {
13814 continue;
13816 info.nonRegularMeths.emplace(meth);
13818 info.complete &= childInfo->complete;
13819 if (childData.hasRegularClassFull) {
13820 info.regularComplete &= childInfo->regularComplete;
13821 info.privateAncestor |= childInfo->privateAncestor;
13822 } else {
13823 assertx(childInfo->regularComplete);
13824 assertx(!childInfo->privateAncestor);
13827 if (childInfo->allStatic) {
13828 if (!info.allStatic) {
13829 info.allStatic = std::move(*childInfo->allStatic);
13830 } else {
13831 *info.allStatic |= *childInfo->allStatic;
13834 if (childInfo->regularStatic) {
13835 if (!info.regularStatic) {
13836 info.regularStatic = std::move(*childInfo->regularStatic);
13837 } else {
13838 *info.regularStatic |= *childInfo->regularStatic;
13842 return false;
13845 // There's no MethInfo with that name in the child. We might
13846 // still want to keep the MethInfo because it will be needed
13847 // for expanding abstract class/interface method
13848 // families. If the child has a regular class, we can remove
13849 // it (it won't be part of the expansion).
13850 return
13851 childData.hasRegularClass ||
13852 !info.regularComplete ||
13853 info.privateAncestor ||
13854 is_special_method_name(name) ||
13855 name == s_construct.get();
13859 // Since we drop non-matching method names only if the class has
13860 // a regular class, it introduces an ordering dependency. If the
13861 // first class we encounter has a regular class, everything
13862 // works fine. However, if the first class we encounter does not
13863 // have a regular class, the Data will have its methods. If we
13864 // eventually process a class which does have a regular class,
13865 // we'll never process it's non-matching methods (because we
13866 // iterate over data.methods). They won't end up in data.methods
13867 // whereas they would if a class with regular class was
13868 // processed first. Detect this condition and manually add such
13869 // methods to data.methods.
13870 if (!data.hasRegularClass && childData.hasRegularClass) {
13871 for (auto& [name, info] : childData.methods) {
13872 if (!info.regularComplete || info.privateAncestor) continue;
13873 if (is_special_method_name(name)) continue;
13874 if (name == s_construct.get()) continue;
13875 if (data.methods.count(name)) continue;
13876 auto& newInfo = data.methods[name];
13877 newInfo.regularMeths = std::move(info.regularMeths);
13878 newInfo.nonRegularPrivateMeths =
13879 std::move(info.nonRegularPrivateMeths);
13880 newInfo.nonRegularMeths = std::move(info.nonRegularMeths);
13881 newInfo.allStatic = std::move(info.allStatic);
13882 newInfo.regularStatic = std::move(info.regularStatic);
13883 newInfo.complete = false;
13884 newInfo.regularComplete = true;
13885 newInfo.privateAncestor = false;
13889 data.propsWithImplicitNullable.insert(
13890 begin(childData.propsWithImplicitNullable),
13891 end(childData.propsWithImplicitNullable)
13894 data.mockedClasses.insert(
13895 begin(childData.mockedClasses),
13896 end(childData.mockedClasses)
13899 // The rest are booleans which can just be unioned together.
13900 data.hasConstProp |= childData.hasConstProp;
13901 data.hasReifiedGeneric |= childData.hasReifiedGeneric;
13902 data.isSubMocked |= childData.isSubMocked;
13903 data.hasRegularClass |= childData.hasRegularClass;
13904 data.hasRegularClassFull |= childData.hasRegularClassFull;
13907 // Obtain a Data for the given class/split named "top".
13908 // @param calculatedAcc: an accumulator passed in to track nodes we process
13909 // while processing children recursively
13910 static Data aggregate_data(LocalIndex& index,
13911 SString top,
13912 TSStringSet& calculatedAcc) {
13913 assertx(index.top.contains(top));
13915 auto const& children = [&]() -> const std::vector<SString>& {
13916 auto const it = index.children.find(top);
13917 always_assert(it != end(index.children));
13918 assertx(!it->second.empty());
13919 return it->second;
13920 }();
13922 auto const it = index.aggregateData.find(top);
13923 if (it != end(index.aggregateData)) {
13924 for (auto const child : children) calculatedAcc.emplace(child);
13925 return it->second;
13928 Data data;
13929 auto first = true;
13930 // Set of children calculated for current top to ensure we don't
13931 // duplicate work.
13932 TSStringSet calculatedForTop;
13934 // For each child of the class/split (for classes this includes
13935 // the top class itself), we create a Data, then union it together
13936 // with the rest.
13937 size_t childIdx = 0;
13938 while (calculatedForTop.size() < children.size()) {
13939 auto child = children[childIdx++];
13940 if (calculatedForTop.contains(child)) continue;
13941 // Top Splits have no associated data yet.
13942 if (index.top.count(child) && index.splits.count(child)) {
13943 calculatedForTop.emplace(child);
13944 continue;
13947 auto childData = [&]() {
13948 if (index.top.contains(child) && !child->tsame(top)) {
13949 return aggregate_data(index, child, calculatedForTop);
13950 } else {
13951 calculatedForTop.emplace(child);
13952 return build_data(index, child);
13954 }();
13956 // The first Data has nothing to union with, so just use it as is.
13957 if (first) {
13958 data = std::move(childData);
13959 first = false;
13960 continue;
13962 update_data(data, std::move(childData));
13965 for (auto const cls : calculatedForTop) calculatedAcc.emplace(cls);
13966 always_assert(index.aggregateData.emplace(top, data).second);
13967 return data;
13970 // Obtain a Data for the given class/split named "top".
13971 static Data aggregate_data(LocalIndex& index, SString top) {
13972 TSStringSet calculated;
13973 return aggregate_data(index, top, calculated);
13976 // Create (or re-use an existing) FuncFamily for the given MethInfo.
13977 static FuncFamily2::Id make_func_family(
13978 LocalIndex& index,
13979 SString name,
13980 Data::MethInfo info
13982 // We should have more than one method because otherwise we
13983 // shouldn't be trying to create a FuncFamily for it.
13984 assertx(
13985 info.regularMeths.size() +
13986 info.nonRegularPrivateMeths.size() +
13987 info.nonRegularMeths.size() > 1
13990 // Before doing the expensive sorting and hashing, see if this
13991 // FuncFamily already exists. If so, just return the id.
13992 if (auto const id = folly::get_ptr(
13993 index.funcFamilyCache,
13994 MethInfoTupleProxy{
13995 &info.regularMeths,
13996 &info.nonRegularPrivateMeths,
13997 &info.nonRegularMeths
13999 )) {
14000 return *id;
14003 // Nothing in the cache. We need to do the expensive step of
14004 // actually creating the FuncFamily.
14006 // First sort the methods so they're in deterministic order.
14007 std::vector<MethRef> regular{
14008 begin(info.regularMeths), end(info.regularMeths)
14010 std::vector<MethRef> nonRegularPrivate{
14011 begin(info.nonRegularPrivateMeths), end(info.nonRegularPrivateMeths)
14013 std::vector<MethRef> nonRegular{
14014 begin(info.nonRegularMeths), end(info.nonRegularMeths)
14016 std::sort(begin(regular), end(regular));
14017 std::sort(begin(nonRegularPrivate), end(nonRegularPrivate));
14018 std::sort(begin(nonRegular), end(nonRegular));
14020 // Create the id by hashing the methods:
14021 SHA1Hasher hasher;
14023 auto const size1 = regular.size();
14024 auto const size2 = nonRegularPrivate.size();
14025 auto const size3 = nonRegular.size();
14026 hasher.update((const char*)&size1, sizeof(size1));
14027 hasher.update((const char*)&size2, sizeof(size2));
14028 hasher.update((const char*)&size3, sizeof(size3));
14030 for (auto const& m : regular) {
14031 hasher.update(m.cls->data(), m.cls->size());
14032 hasher.update((const char*)&m.idx, sizeof(m.idx));
14034 for (auto const& m : nonRegularPrivate) {
14035 hasher.update(m.cls->data(), m.cls->size());
14036 hasher.update((const char*)&m.idx, sizeof(m.idx));
14038 for (auto const& m : nonRegular) {
14039 hasher.update(m.cls->data(), m.cls->size());
14040 hasher.update((const char*)&m.idx, sizeof(m.idx));
14042 auto const id = hasher.finish();
14044 // See if this id exists already. If so, record it in the cache
14045 // and we're done.
14046 if (index.funcFamilies.count(id)) {
14047 index.funcFamilyCache.emplace(
14048 MethInfoTuple{
14049 std::move(info.regularMeths),
14050 std::move(info.nonRegularPrivateMeths),
14051 std::move(info.nonRegularMeths)
14055 return id;
14058 // It's a new id. Create the actual FuncFamily:
14060 regular.shrink_to_fit();
14061 nonRegularPrivate.shrink_to_fit();
14062 nonRegular.shrink_to_fit();
14064 auto ff = std::make_unique<FuncFamily2>();
14065 ff->m_id = id;
14066 ff->m_name = name;
14067 ff->m_regular = std::move(regular);
14068 ff->m_nonRegularPrivate = std::move(nonRegularPrivate);
14069 ff->m_nonRegular = std::move(nonRegular);
14070 ff->m_allStatic = std::move(info.allStatic);
14071 ff->m_regularStatic = std::move(info.regularStatic);
14073 always_assert(
14074 index.funcFamilies.emplace(id, std::move(ff)).second
14076 index.newFuncFamilies.emplace_back(id);
14077 index.funcFamilyCache.emplace(
14078 MethInfoTuple{
14079 std::move(info.regularMeths),
14080 std::move(info.nonRegularPrivateMeths),
14081 std::move(info.nonRegularMeths)
14086 return id;
14089 // Turn a FuncFamily::StaticInfo into an equivalent
14090 // FuncFamilyEntry::MethMetadata. The StaticInfo must be valid for a
14091 // single method.
14092 static FuncFamilyEntry::MethMetadata single_meth_meta_from_static_info(
14093 const FuncFamily2::StaticInfo& info
14095 assertx(info.m_numInOut);
14096 assertx(info.m_requiredCoeffects);
14097 assertx(info.m_coeffectRules);
14098 assertx(info.m_minNonVariadicParams == info.m_maxNonVariadicParams);
14099 assertx(info.m_isReadonlyReturn != TriBool::Maybe);
14100 assertx(info.m_isReadonlyThis != TriBool::Maybe);
14101 assertx(info.m_supportsAER != TriBool::Maybe);
14103 FuncFamilyEntry::MethMetadata meta;
14104 meta.m_prepKinds = info.m_paramPreps;
14105 meta.m_coeffectRules = *info.m_coeffectRules;
14106 meta.m_numInOut = *info.m_numInOut;
14107 meta.m_requiredCoeffects = *info.m_requiredCoeffects;
14108 meta.m_nonVariadicParams = info.m_minNonVariadicParams;
14109 meta.m_isReadonlyReturn = info.m_isReadonlyReturn == TriBool::Yes;
14110 meta.m_isReadonlyThis = info.m_isReadonlyThis == TriBool::Yes;
14111 meta.m_supportsAER = info.m_supportsAER == TriBool::Yes;
14112 meta.m_isReified = info.m_maybeReified;
14113 meta.m_caresAboutDyncalls = info.m_maybeCaresAboutDynCalls;
14114 meta.m_builtin = info.m_maybeBuiltin;
14115 return meta;
14118 // Translate a MethInfo into the appropriate FuncFamilyEntry
14119 static FuncFamilyEntry make_method_family_entry(
14120 LocalIndex& index,
14121 SString name,
14122 Data::MethInfo info
14124 FuncFamilyEntry entry;
14125 entry.m_allIncomplete = !info.complete;
14126 entry.m_regularIncomplete = !info.regularComplete;
14127 entry.m_privateAncestor = info.privateAncestor;
14129 if (info.regularMeths.size() + info.nonRegularPrivateMeths.size() > 1) {
14130 // There's either multiple regularMeths, multiple
14131 // nonRegularPrivateMeths, or one of each (remember they are
14132 // disjoint). In either case, there's more than one method, so
14133 // we need a func family.
14134 assertx(info.allStatic);
14135 assertx(info.regularStatic);
14136 auto const ff = make_func_family(index, name, std::move(info));
14137 entry.m_meths = FuncFamilyEntry::BothFF{ff};
14138 } else if (!info.regularMeths.empty() ||
14139 !info.nonRegularPrivateMeths.empty()) {
14140 // We know their sum isn't greater than one, so only one of them
14141 // can be non-empty (and the one that is has only a single
14142 // method).
14143 assertx(info.allStatic);
14144 assertx(info.regularStatic);
14145 auto const r = !info.regularMeths.empty()
14146 ? *begin(info.regularMeths)
14147 : *begin(info.nonRegularPrivateMeths);
14148 if (info.nonRegularMeths.empty()) {
14149 // There's only one method and it covers both variants.
14150 entry.m_meths = FuncFamilyEntry::BothSingle{
14152 single_meth_meta_from_static_info(*info.allStatic),
14153 info.regularMeths.empty()
14155 } else {
14156 // nonRegularMeths is non-empty. Since the MethRefSets are
14157 // disjoint, overall there's more than one method so need a
14158 // func family.
14159 auto const nonRegularPrivate = info.regularMeths.empty();
14160 auto const ff = make_func_family(index, name, std::move(info));
14161 entry.m_meths = FuncFamilyEntry::FFAndSingle{ff, r, nonRegularPrivate};
14163 } else if (info.nonRegularMeths.size() > 1) {
14164 // Both regularMeths and nonRegularPrivateMeths is empty. If
14165 // there's multiple nonRegularMeths, we need a func family for
14166 // the non-regular variant, but the regular variant is empty.
14167 assertx(info.allStatic);
14168 assertx(!info.regularStatic);
14169 auto const ff = make_func_family(index, name, std::move(info));
14170 entry.m_meths = FuncFamilyEntry::FFAndNone{ff};
14171 } else if (!info.nonRegularMeths.empty()) {
14172 // There's exactly one nonRegularMeths method (and nothing for
14173 // the regular variant).
14174 assertx(info.allStatic);
14175 assertx(!info.regularStatic);
14176 entry.m_meths = FuncFamilyEntry::SingleAndNone{
14177 *begin(info.nonRegularMeths),
14178 single_meth_meta_from_static_info(*info.allStatic)
14180 } else {
14181 // No methods at all
14182 assertx(!info.complete);
14183 assertx(!info.allStatic);
14184 assertx(!info.regularStatic);
14185 entry.m_meths = FuncFamilyEntry::None{};
14188 return entry;
14191 // Calculate the data for each root (those which will we'll provide
14192 // outputs for) and update the ClassInfo or Split as appropriate.
14193 static void process_roots(
14194 LocalIndex& index,
14195 const std::vector<std::unique_ptr<ClassInfo2>>& roots,
14196 const std::vector<std::unique_ptr<Split>>& splits
14198 for (auto const& cinfo : roots) {
14199 assertx(index.top.count(cinfo->name));
14200 // Process the children of this class and build a unified Data
14201 // for it.
14202 auto data = aggregate_data(index, cinfo->name);
14204 // These are just copied directly from Data.
14205 cinfo->subHasConstProp = data.hasConstProp;
14206 cinfo->subHasReifiedGeneric = data.hasReifiedGeneric;
14208 auto& cls = index.cls(cinfo->name);
14210 // This class is mocked if its on the mocked classes list.
14211 cinfo->isMocked = (bool)data.mockedClasses.count(cinfo->name);
14212 cinfo->isSubMocked = data.isSubMocked || cinfo->isMocked;
14213 attribute_setter(cls.attrs, !cinfo->isSubMocked, AttrNoMock);
14215 // We can use whether we saw regular/non-regular subclasses to
14216 // infer if this class is overridden.
14217 if (cinfo->classGraph.mightHaveRegularSubclass()) {
14218 attribute_setter(cls.attrs, false, AttrNoOverrideRegular);
14219 attribute_setter(cls.attrs, false, AttrNoOverride);
14220 } else if (cinfo->classGraph.mightHaveNonRegularSubclass()) {
14221 attribute_setter(cls.attrs, true, AttrNoOverrideRegular);
14222 attribute_setter(cls.attrs, false, AttrNoOverride);
14223 } else {
14224 attribute_setter(cls.attrs, true, AttrNoOverrideRegular);
14225 attribute_setter(cls.attrs, true, AttrNoOverride);
14228 assertx(
14229 IMPLIES(
14230 cinfo->initialNoReifiedInit,
14231 cls.attrs & AttrNoReifiedInit
14235 attribute_setter(
14236 cls.attrs,
14237 [&] {
14238 if (cinfo->initialNoReifiedInit) return true;
14239 if (cinfo->parent) return false;
14240 if (cls.attrs & AttrInterface) return true;
14241 return !data.hasReifiedGeneric;
14242 }(),
14243 AttrNoReifiedInit
14246 for (auto& [name, mte] : cinfo->methods) {
14247 if (is_special_method_name(name)) continue;
14249 // Since this is the first time we're processing this class,
14250 // all of the methods should be marked as AttrNoOverride.
14251 assertx(mte.attrs & AttrNoOverride);
14252 assertx(mte.noOverrideRegular());
14254 auto& info = [&, name=name] () -> Data::MethInfo& {
14255 auto it = data.methods.find(name);
14256 always_assert(it != end(data.methods));
14257 return it->second;
14258 }();
14260 auto const meth = mte.meth();
14262 // Is this method overridden?
14263 auto const noOverride = [&] {
14264 // An incomplete method family is always overridden because
14265 // the call could fail.
14266 if (!info.complete) return false;
14267 // If more than one method then no.
14268 if (info.regularMeths.size() +
14269 info.nonRegularPrivateMeths.size() +
14270 info.nonRegularMeths.size() > 1) {
14271 return false;
14273 // NB: All of the below checks all return true. The
14274 // different conditions are just for checking the right
14275 // invariants.
14276 if (info.regularMeths.empty()) {
14277 // The (single) method isn't on a regular class. This
14278 // class shouldn't have any regular classes (the set is
14279 // complete so if we did, the method would have been on
14280 // it). The (single) method must be on nonRegularMeths or
14281 // nonRegularPrivateMeths.
14282 assertx(!cinfo->isRegularClass);
14283 if (info.nonRegularPrivateMeths.empty()) {
14284 assertx(info.nonRegularMeths.count(meth));
14285 return true;
14287 assertx(info.nonRegularMeths.empty());
14288 assertx(info.nonRegularPrivateMeths.count(meth));
14289 return true;
14291 assertx(info.nonRegularPrivateMeths.empty());
14292 assertx(info.nonRegularMeths.empty());
14293 assertx(info.regularMeths.count(meth));
14294 return true;
14297 // Is this method overridden in a regular class? (weaker
14298 // condition)
14299 auto const noOverrideRegular = [&] {
14300 // An incomplete method family is always overridden because
14301 // the call could fail.
14302 if (!info.regularComplete) return false;
14303 // If more than one method then no. For the purposes of this
14304 // check, non-regular but private methods are included.
14305 if (info.regularMeths.size() +
14306 info.nonRegularPrivateMeths.size() > 1) {
14307 return false;
14309 if (info.regularMeths.empty()) {
14310 // The method isn't on a regular class. Like in
14311 // noOverride(), the class shouldn't have any regular
14312 // classes. If nonRegularPrivateMethos is empty, this
14313 // means any possible override is non-regular, so we're
14314 // good.
14315 assertx(!cinfo->isRegularClass);
14316 if (info.nonRegularPrivateMeths.empty()) return true;
14317 return (bool)info.nonRegularPrivateMeths.count(meth);
14319 if (cinfo->isRegularClass) {
14320 // If this class is regular, the method on this class
14321 // should be marked as regular.
14322 assertx(info.regularMeths.count(meth));
14323 return true;
14325 // We know regularMeths is non-empty, and the size is at
14326 // most one. If this method is the (only) one in
14327 // regularMeths, it's not overridden by anything.
14328 return (bool)info.regularMeths.count(meth);
14331 if (!noOverrideRegular()) {
14332 mte.clearNoOverrideRegular();
14333 attribute_setter(mte.attrs, false, AttrNoOverride);
14334 } else if (!noOverride()) {
14335 attribute_setter(mte.attrs, false, AttrNoOverride);
14338 auto& entry = cinfo->methodFamilies.at(name);
14339 assertx(
14340 boost::get<FuncFamilyEntry::BothSingle>(&entry.m_meths) ||
14341 boost::get<FuncFamilyEntry::SingleAndNone>(&entry.m_meths)
14344 if (debug) {
14345 if (mte.attrs & AttrNoOverride) {
14346 always_assert(info.complete);
14347 always_assert(info.regularComplete);
14349 if (cinfo->isRegularClass ||
14350 cinfo->classGraph.mightHaveRegularSubclass()) {
14351 always_assert(info.regularMeths.size() == 1);
14352 always_assert(info.regularMeths.count(meth));
14353 always_assert(info.nonRegularPrivateMeths.empty());
14354 always_assert(info.nonRegularMeths.empty());
14355 } else {
14356 // If this class isn't regular, it could still have a
14357 // regular method which it inherited from a (regular)
14358 // parent. There should only be one method across all the
14359 // sets though.
14360 always_assert(
14361 info.regularMeths.size() +
14362 info.nonRegularPrivateMeths.size() +
14363 info.nonRegularMeths.size() == 1
14365 always_assert(
14366 info.regularMeths.count(meth) ||
14367 info.nonRegularPrivateMeths.count(meth) ||
14368 info.nonRegularMeths.count(meth)
14372 if (mte.hasPrivateAncestor() &&
14373 (cinfo->isRegularClass ||
14374 cinfo->classGraph.mightHaveRegularSubclass())) {
14375 always_assert(info.privateAncestor);
14376 } else {
14377 always_assert(!info.privateAncestor);
14379 } else {
14380 always_assert(!(cls.attrs & AttrNoOverride));
14384 // NB: Even if the method is AttrNoOverride, we might need to
14385 // change the FuncFamilyEntry. This class could be non-regular
14386 // and a child class could be regular. Even if the child class
14387 // doesn't override the method, it changes it from non-regular
14388 // to regular.
14389 entry = make_method_family_entry(index, name, std::move(info));
14391 if (mte.attrs & AttrNoOverride) {
14392 // However, even if the entry changes with AttrNoOverride,
14393 // it can only be these two cases.
14394 always_assert(
14395 boost::get<FuncFamilyEntry::BothSingle>(&entry.m_meths) ||
14396 boost::get<FuncFamilyEntry::SingleAndNone>(&entry.m_meths)
14402 * Interfaces can cause monotonicity violations. Suppose we have two
14403 * interfaces: I2 and I2. I1 declares a method named Foo. Every
14404 * class which implements I2 also implements I1 (therefore I2
14405 * implies I1). During analysis, a type is initially Obj<=I1 and we
14406 * resolve a call to Foo using I1's func families. After further
14407 * optimization, we narrow the type to Obj<=I2. Now when we go to
14408 * resolve a call to Foo using I2's func families, we find
14409 * nothing. Foo is declared in I1, not in I2, and interface methods
14410 * are not inherited. We use the fall back name-only tables, which
14411 * might give us a worse type than before. This is a monotonicity
14412 * violation because refining the object type gave us worse
14413 * analysis.
14415 * To avoid this, we expand an interface's (and abstract class'
14416 * which has similar issues) func families to include all methods
14417 * defined by *all* of it's (regular) implementations. So, in the
14418 * example above, we'd expand I2's func families to include Foo,
14419 * since all of I2's implements should define a Foo method (since
14420 * they also all implement I1).
14422 * Any MethInfos which are part of the abstract class/interface
14423 * method table has already been processed above. Any ones which
14424 * haven't are candidates for the above expansion and must also
14425 * be placed in the method families table. Note: we do not just
14426 * restrict this to just abstract classes or interfaces. This
14427 * class may be a child of an abstract class or interfaces and
14428 * we need to propagate these "expanded" methods so they're
14429 * available in the dependency when we actually process the
14430 * abstract class/interface in a later round.
14432 for (auto& [name, info] : data.methods) {
14433 if (cinfo->methods.count(name)) continue;
14434 assertx(!is_special_method_name(name));
14435 auto entry = make_method_family_entry(index, name, std::move(info));
14436 always_assert(
14437 cinfo->methodFamilies.emplace(name, std::move(entry)).second
14441 for (auto& prop : cls.properties) {
14442 if (bool(prop.attrs & AttrNoImplicitNullable) &&
14443 !(prop.attrs & (AttrStatic | AttrPrivate))) {
14444 attribute_setter(
14445 prop.attrs,
14446 !data.propsWithImplicitNullable.count(prop.name),
14447 AttrNoImplicitNullable
14451 if (!(prop.attrs & AttrSystemInitialValue)) continue;
14452 if (prop.val.m_type == KindOfUninit) {
14453 assertx(prop.attrs & AttrLateInit);
14454 continue;
14457 prop.val = [&] {
14458 if (!(prop.attrs & AttrNoImplicitNullable)) {
14459 return make_tv<KindOfNull>();
14461 // Give the 86reified_prop a special default value to
14462 // avoid pessimizing the inferred type (we want it to
14463 // always be a vec of a specific size).
14464 if (prop.name == s_86reified_prop.get()) {
14465 return get_default_value_of_reified_list(cls.userAttributes);
14467 return prop.typeConstraint.defaultValue();
14468 }();
14472 // Splits just store the data directly. Since this split hasn't
14473 // been processed yet (and no other job should process it), all of
14474 // the fields should be their default settings.
14475 for (auto& split : splits) {
14476 assertx(index.top.count(split->name));
14477 split->data = aggregate_data(index, split->name);
14478 // This split inherits all of the splits of their children.
14479 for (auto const child : split->children) {
14480 if (auto const c = folly::get_default(index.classInfos, child)) {
14481 split->classGraphs.emplace_back(c->classGraph);
14482 continue;
14484 auto const s = folly::get_default(index.splits, child);
14485 always_assert(s);
14486 split->classGraphs.insert(
14487 end(split->classGraphs),
14488 begin(s->classGraphs),
14489 end(s->classGraphs)
14492 std::sort(begin(split->classGraphs), end(split->classGraphs));
14493 split->classGraphs.erase(
14494 std::unique(begin(split->classGraphs), end(split->classGraphs)),
14495 end(split->classGraphs)
14497 split->children.clear();
14502 Job<BuildSubclassListJob> s_buildSubclassJob;
14504 struct SubclassWork {
14505 TSStringToOneT<std::unique_ptr<BuildSubclassListJob::Split>> allSplits;
14506 struct Bucket {
14507 std::vector<SString> classes;
14508 std::vector<SString> deps;
14509 std::vector<SString> splits;
14510 std::vector<SString> splitDeps;
14511 std::vector<SString> leafs;
14512 std::vector<BuildSubclassListJob::EdgeToSplit> edges;
14513 size_t cost{0};
14515 std::vector<std::vector<Bucket>> buckets;
14519 * Algorithm for assigning work for building subclass lists:
14521 * - Keep track of which classes have been processed and which ones
14522 * have not yet been.
14524 * - Keep looping until all classes have been processed. Each round of
14525 * the algorithm becomes a round of output.
14527 * - Iterate over all classes which haven't been
14528 * processed. Distinguish classes which are eligible for processing
14529 * or not. A class is eligible for processing if its transitive
14530 * dependencies are below the maximum size.
14532 * - Non-eligible classes are ignored and will be processed again next
14533 * round. However, if the class has more eligible direct children
14534 * than the bucket size, the class' children will be turned into
14535 * split nodes.
14537 * - Create split nodes. For each class (who we're splitting), use the
14538 * typical consistent hashing algorithm to assign each child to a
14539 * split node. Change the class' child list to contain the split
14540 * nodes instead of the children (this should shrink it
14541 * considerably). Each new split becomes a root.
14543 * - Assign each eligible class to a bucket. Use
14544 * assign_hierachial_work to map each eligible class to a bucket.
14546 * - Update the processed set. Any class which hasn't been processed
14547 * that round should have their dependency set shrunken. Processing
14548 * a class makes its dependency set be empty. So if a class wasn't
14549 * eligible, it should have a dependency which was. Therefore the
14550 * class' transitive dependencies should shrink. It should continue
14551 * to shrink until its eventually becomes eligible. The same happens
14552 * if the class' children are turned into split nodes. Each N
14553 * children is replaced with a single split (with no other
14554 * dependencies), so the class' dependencies should shrink. Thus,
14555 * the algorithm eventually terminates.
14558 // Dependency information for a class or split node.
14559 struct DepData {
14560 // Transitive dependencies (children) for this class.
14561 TSStringSet deps;
14562 // Any split nodes which are dependencies of this class.
14563 TSStringSet edges;
14564 // The number of direct children of this class which will be
14565 // processed this round.
14566 size_t processChildren{0};
14570 // Given a set of roots, greedily add roots and their children to buckets
14571 // via DFS traversal.
14572 template <typename GetDeps>
14573 std::vector<HierarchicalWorkBucket>
14574 dfs_bucketize(SubclassMetadata& subclassMeta,
14575 std::vector<SString> roots,
14576 const TSStringToOneT<std::vector<SString>>& splitImmDeps,
14577 size_t kMaxBucketSize,
14578 size_t maxClassIdx,
14579 bool alwaysCreateNew,
14580 const TSStringSet& leafs,
14581 const TSStringSet& processed, // already processed
14582 const GetDeps& getDeps) {
14583 TSStringSet visited;
14584 std::vector<std::vector<SString>> rootsToProcess;
14585 rootsToProcess.emplace_back();
14586 std::vector<size_t> rootsCost;
14588 auto const depsSize = [&] (SString cls) {
14589 return getDeps(cls, getDeps).deps.size();
14592 size_t cost = 0;
14594 auto const finishBucket = [&]() {
14595 if (!cost) return;
14596 rootsToProcess.emplace_back();
14597 rootsCost.emplace_back(cost);
14598 cost = 0;
14601 auto const addRoot = [&](SString c) {
14602 rootsToProcess.back().emplace_back(c);
14603 cost += depsSize(c);
14606 auto const processSubgraph = [&](SString cls) {
14607 assertx(!processed.count(cls));
14609 addRoot(cls);
14610 for (auto const& child : getDeps(cls, getDeps).deps) {
14611 if (processed.count(child)) continue;
14612 if (visited.count(child)) continue;
14613 visited.insert(child);
14614 // Leaves use special leaf-promotion logic in assign_hierarchial_work
14615 if (leafs.count(child)) continue;
14616 addRoot(child);
14618 if (cost < kMaxBucketSize) return;
14619 finishBucket();
14622 // Visit immediate children. Recurse until you find a node that has small
14623 // enough transitive deps.
14624 auto const visitSubgraph = [&](SString root, auto const& self) {
14625 if (processed.count(root) || visited.count(root)) return false;
14626 if (!depsSize(root)) return false;
14627 auto progress = false;
14628 visited.insert(root);
14630 assertx(IMPLIES(splitImmDeps.count(root), depsSize(root) <= kMaxBucketSize));
14631 if (depsSize(root) <= kMaxBucketSize) {
14632 processSubgraph(root);
14633 progress = true;
14634 } else {
14635 auto const immChildren = [&] {
14636 auto const it = subclassMeta.meta.find(root);
14637 assertx(it != end(subclassMeta.meta));
14638 return it->second.children;
14639 }();
14640 for (auto const& child : immChildren) progress |= self(child, self);
14642 return progress;
14645 // Sort the roots to keep it deterministic
14646 std::sort(
14647 begin(roots), end(roots),
14648 [&] (SString a, SString b) {
14649 auto const s1 = getDeps(a, getDeps).deps.size();
14650 auto const s2 = getDeps(b, getDeps).deps.size();
14651 if (s1 != s2) return s1 > s2;
14652 return string_data_lt_type{}(a, b);
14656 auto progress = false;
14657 for (auto const r : roots) {
14658 assertx(depsSize(r)); // Should never be processing one leaf
14659 progress |= visitSubgraph(r, visitSubgraph);
14661 assertx(progress);
14662 finishBucket();
14664 if (rootsToProcess.back().empty()) rootsToProcess.pop_back();
14666 auto const buckets = parallel::gen(
14667 rootsToProcess.size(),
14668 [&] (size_t bucketIdx) {
14669 auto numBuckets =
14670 (rootsCost[bucketIdx] + (kMaxBucketSize/2)) / kMaxBucketSize;
14671 if (!numBuckets) numBuckets = 1;
14672 return consistently_bucketize_by_num_buckets(rootsToProcess[bucketIdx],
14673 alwaysCreateNew ? rootsToProcess[bucketIdx].size() : numBuckets);
14677 std::vector<std::vector<SString>> flattened;
14678 for (auto const& b : buckets) {
14679 flattened.insert(flattened.end(), b.begin(), b.end());
14682 auto const work = build_hierarchical_work(
14683 flattened,
14684 maxClassIdx,
14685 [&] (SString c) {
14686 auto const& deps = getDeps(c, getDeps).deps;
14687 return std::make_pair(&deps, true);
14689 [&] (const TSStringSet&, size_t, SString c) -> Optional<size_t> {
14690 if (!leafs.count(c)) return std::nullopt;
14691 return subclassMeta.meta.at(c).idx;
14694 return work;
14697 // For each round:
14698 // While toProcess is not empty:
14699 // 1. Find transitive dep counts
14700 // 2. For each class, calculate splits, find roots, find rootLeafs
14701 // 3. For rootLeafs, consistently hash to make buckets
14702 // 4. For roots, assign subgraphs to buckets via greedy DFS. If buckets get too big,
14703 // split them via consistent hashing.
14704 SubclassWork build_subclass_lists_assign(SubclassMetadata subclassMeta) {
14705 trace_time trace{"build subclass lists assign"};
14706 trace.ignore_client_stats();
14708 constexpr size_t kBucketSize = 2000;
14709 constexpr size_t kMaxBucketSize = 25000;
14711 SubclassWork out;
14713 auto const maxClassIdx = subclassMeta.all.size();
14715 // A processed class/split is considered processed once it's
14716 // assigned to a bucket in a round. Once considered processed, it
14717 // will have no dependencies.
14718 TSStringSet processed;
14720 TSStringToOneT<std::unique_ptr<DepData>> splitDeps;
14721 TSStringToOneT<std::unique_ptr<BuildSubclassListJob::Split>> splitPtrs;
14722 TSStringSet leafs;
14723 TSStringToOneT<std::vector<SString>> splitImmDeps;
14725 // Keep creating rounds until all of the classes are assigned to a
14726 // bucket in a round.
14727 auto toProcess = std::move(subclassMeta.all);
14728 TSStringSet tp;
14729 if (debug) tp.insert(toProcess.begin(), toProcess.end());
14731 for (size_t round = 0; !toProcess.empty(); ++round) {
14732 // If we have this many rounds, something has gone wrong, because
14733 // it should require an astronomical amount of classes.
14734 always_assert_flog(
14735 round < 10,
14736 "Worklist still has {} items after {} rounds. "
14737 "This almost certainly means it's stuck in an infinite loop",
14738 toProcess.size(),
14739 round
14742 // The dependency information for every class, for just this
14743 // round. The information is calculated lazily and recursively by
14744 // findDeps.
14745 std::vector<LockFreeLazy<DepData>> deps{maxClassIdx};
14747 auto const findDeps = [&] (SString cls,
14748 auto const& self) -> const DepData& {
14749 // If it's processed, there's implicitly no dependencies
14750 static DepData empty;
14751 if (processed.count(cls)) return empty;
14753 // Look up the metadata for this class. If we don't find any,
14754 // assume that it's for a split.
14755 auto const it = subclassMeta.meta.find(cls);
14756 if (it == end(subclassMeta.meta)) {
14757 auto const it2 = splitDeps.find(cls);
14758 always_assert(it2 != end(splitDeps));
14759 return *it2->second;
14761 auto const& meta = it->second;
14762 auto const idx = meta.idx;
14763 assertx(idx < deps.size());
14765 // Now that we have the index into the dependency vector, look
14766 // it up, calculating it if it hasn't been already.
14767 return deps[idx].get(
14768 [&] {
14769 DepData out;
14770 for (auto const c : meta.children) {
14771 // At a minimum, we need the immediate deps in order to
14772 // construct the subclass lists for the parent.
14773 out.deps.emplace(c);
14774 if (splitDeps.count(c)) out.edges.emplace(c);
14775 auto const& childDeps = self(c, self);
14776 if (childDeps.deps.size() <= kMaxBucketSize) ++out.processChildren;
14777 out.deps.insert(begin(childDeps.deps), end(childDeps.deps));
14779 return out;
14784 auto const depsSize = [&] (SString cls) {
14785 return findDeps(cls, findDeps).deps.size();
14787 // If this class' children needs to be split into split nodes this
14788 // round. This happens if the number of direct children of this
14789 // class which are eligible for processing exceeds the bucket
14790 // size.
14791 auto const willSplitChildren = [&] (SString cls) {
14792 return findDeps(cls, findDeps).processChildren > kBucketSize;
14794 // If this class will be processed this round. A class will be
14795 // processed if it's dependencies are less than the maximum bucket
14796 // size.
14797 auto const willProcess = [&] (SString cls) {
14798 // NB: Not <=. When calculating splits, a class is included
14799 // among it's own dependencies so we need to leave space for one
14800 // more.
14801 return depsSize(cls) < kMaxBucketSize;
14804 // Process every remaining class in parallel and assign an action
14805 // to each:
14807 // This class will be processed this round and is a root.
14808 struct Root { SString cls; };
14809 struct RootLeaf { SString cls; };
14810 struct Child { SString cls; };
14811 // This class' children should be split. The class' child list
14812 // will be replaced with the new child list and splits created.
14813 struct Split {
14814 SString cls;
14815 std::vector<SString> children;
14816 struct Data {
14817 SString name;
14818 std::unique_ptr<DepData> deps;
14819 std::unique_ptr<BuildSubclassListJob::Split> ptr;
14820 std::vector<SString> children;
14822 std::vector<Data> splits;
14824 using Action = boost::variant<Root, Split, Child, RootLeaf>;
14826 auto const actions = parallel::map(
14827 toProcess,
14828 [&] (SString cls) {
14829 auto const& meta = subclassMeta.meta.at(cls);
14831 if (!willSplitChildren(cls)) {
14832 if (!meta.parents.empty()) return Action{ Child{cls} };
14833 if (meta.children.empty()) return Action{ RootLeaf{cls} };
14834 return Action{ Root{cls} };
14837 // Otherwise we're going to split some/all of this class'
14838 // children. Once we process those in this round, this class'
14839 // dependencies should be smaller and be able to be processed.
14840 Split split;
14841 split.cls = cls;
14842 split.splits = [&] {
14843 // Group all of the eligible children into buckets, and
14844 // split the buckets to ensure they remain below the maximum
14845 // size.
14846 auto const buckets = split_buckets(
14847 [&] {
14848 auto const numChildren = findDeps(cls, findDeps).processChildren;
14849 auto const numBuckets =
14850 (numChildren + kMaxBucketSize - 1) / kMaxBucketSize;
14851 assertx(numBuckets > 0);
14853 std::vector<std::vector<SString>> buckets;
14854 buckets.resize(numBuckets);
14855 for (auto const child : meta.children) {
14856 if (!willProcess(child)) continue;
14857 auto const idx =
14858 consistent_hash(child->hashStatic(), numBuckets);
14859 assertx(idx < numBuckets);
14860 buckets[idx].emplace_back(child);
14863 buckets.erase(
14864 std::remove_if(
14865 begin(buckets),
14866 end(buckets),
14867 [] (const std::vector<SString>& b) { return b.empty(); }
14869 end(buckets)
14872 assertx(!buckets.empty());
14873 return buckets;
14874 }(),
14875 kMaxBucketSize,
14876 [&] (SString child) -> const TSStringSet& {
14877 return findDeps(child, findDeps).deps;
14880 // Each bucket corresponds to a new split node, which will
14881 // contain the results for the children in that bucket.
14882 auto const numSplits = buckets.size();
14884 // Actually make the splits and fill their children list.
14885 std::vector<Split::Data> splits;
14886 splits.reserve(numSplits);
14887 for (size_t i = 0; i < numSplits; ++i) {
14888 // The names of a split node are arbitrary, but must be
14889 // unique and not collide with any actual classes.
14890 auto const name = makeStaticString(
14891 folly::sformat("{}_{}_split;{}", round, i, cls)
14894 auto deps = std::make_unique<DepData>();
14895 auto split =
14896 std::make_unique<BuildSubclassListJob::Split>(name, cls);
14897 std::vector<SString> children;
14899 for (auto const child : buckets[i]) {
14900 split->children.emplace_back(child);
14901 children.emplace_back(child);
14902 auto const& childDeps = findDeps(child, findDeps).deps;
14903 deps->deps.insert(begin(childDeps), end(childDeps));
14904 deps->deps.emplace(child);
14906 assertx(deps->deps.size() <= kMaxBucketSize);
14908 std::sort(
14909 begin(split->children),
14910 end(split->children),
14911 string_data_lt_type{}
14914 splits.emplace_back(
14915 Split::Data{
14916 name,
14917 std::move(deps),
14918 std::move(split),
14919 std::move(children)
14923 return splits;
14924 }();
14926 // Create the new children list for this class. The new
14927 // children list are any children which won't be processed,
14928 // and the new splits.
14929 for (auto const child : meta.children) {
14930 if (willProcess(child)) continue;
14931 split.children.emplace_back(child);
14933 for (auto const& [name, _, _2, _3] : split.splits) {
14934 split.children.emplace_back(name);
14937 return Action{ std::move(split) };
14941 assertx(actions.size() == toProcess.size());
14942 std::vector<SString> roots;
14943 roots.reserve(actions.size());
14944 std::vector<SString> rootLeafs;
14946 for (auto const& action : actions) {
14947 match<void>(
14948 action,
14949 [&] (Root r) {
14950 assertx(!subclassMeta.meta.at(r.cls).children.empty());
14951 roots.emplace_back(r.cls);
14953 [&] (RootLeaf r) {
14954 assertx(subclassMeta.meta.at(r.cls).children.empty());
14955 rootLeafs.emplace_back(r.cls);
14956 leafs.emplace(r.cls);
14958 [&] (Child n) {
14959 auto const& meta = subclassMeta.meta.at(n.cls);
14960 if (meta.children.empty()) leafs.emplace(n.cls);
14962 [&] (const Split& s) {
14963 auto& meta = subclassMeta.meta.at(s.cls);
14964 meta.children = s.children;
14965 if (meta.parents.empty()) {
14966 roots.emplace_back(s.cls);
14968 auto& splits = const_cast<std::vector<Split::Data>&>(s.splits);
14969 for (auto& [name, deps, ptr, children] : splits) {
14970 splitImmDeps.emplace(name, children);
14971 roots.emplace_back(name);
14972 splitDeps.emplace(name, std::move(deps));
14973 splitPtrs.emplace(name, std::move(ptr));
14979 auto work = dfs_bucketize(
14980 subclassMeta,
14981 std::move(roots),
14982 splitImmDeps,
14983 kMaxBucketSize,
14984 maxClassIdx,
14985 round > 0,
14986 leafs,
14987 processed,
14988 findDeps
14991 // Bucketize root leafs.
14992 // These are cheaper since we will only be calculating
14993 // name-only func family entries.
14994 for (auto& b : consistently_bucketize(rootLeafs, kMaxBucketSize)) {
14995 work.emplace_back(HierarchicalWorkBucket{ std::move(b) });
14998 std::vector<SString> markProcessed;
14999 markProcessed.reserve(actions.size());
15001 // The output of assign_hierarchical_work is just buckets with the
15002 // names. We need to map those to classes or edge nodes and put
15003 // them in the correct data structure in the output. If there's a
15004 // class dependency on a split node, we also need to record an
15005 // edge between them.
15006 auto const add = [&] (SString cls, auto& clsList,
15007 auto& splitList, auto& edgeList) {
15008 auto const it = splitPtrs.find(cls);
15009 if (it == end(splitPtrs)) {
15010 clsList.emplace_back(cls);
15011 for (auto const s : findDeps(cls, findDeps).edges) {
15012 edgeList.emplace_back(BuildSubclassListJob::EdgeToSplit{cls, s});
15014 } else {
15015 splitList.emplace_back(it->second->name);
15019 out.buckets.emplace_back();
15020 for (auto const& w : work) {
15021 assertx(w.uninstantiable.empty());
15022 out.buckets.back().emplace_back();
15023 auto& bucket = out.buckets.back().back();
15024 // Separate out any of the "roots" which are actually leafs.
15025 for (auto const cls : w.classes) {
15026 bucket.cost += depsSize(cls);
15027 markProcessed.emplace_back(cls);
15028 if (leafs.count(cls)) {
15029 leafs.erase(cls);
15030 bucket.leafs.emplace_back(cls);
15031 } else {
15032 add(cls, bucket.classes, bucket.splits, bucket.edges);
15035 for (auto const cls : w.deps) {
15036 add(cls, bucket.deps, bucket.splitDeps, bucket.edges);
15039 std::sort(
15040 begin(bucket.edges), end(bucket.edges),
15041 [] (const BuildSubclassListJob::EdgeToSplit& a,
15042 const BuildSubclassListJob::EdgeToSplit& b) {
15043 if (string_data_lt_type{}(a.cls, b.cls)) return true;
15044 if (string_data_lt_type{}(b.cls, a.cls)) return false;
15045 return string_data_lt_type{}(a.split, b.split);
15048 std::sort(begin(bucket.leafs), end(bucket.leafs), string_data_lt_type{});
15051 std::sort(
15052 begin(out.buckets.back()), end(out.buckets.back()),
15053 [] (const SubclassWork::Bucket& a,
15054 const SubclassWork::Bucket& b) {
15055 return a.cost > b.cost;
15059 // Update the processed set. We have to defer that until here
15060 // because we'd check it when building the buckets.
15061 processed.insert(begin(markProcessed), end(markProcessed));
15063 auto const before = toProcess.size();
15064 toProcess.erase(
15065 std::remove_if(
15066 begin(toProcess), end(toProcess),
15067 [&] (SString c) { return processed.count(c); }
15069 end(toProcess)
15071 always_assert(toProcess.size() < before);
15074 // Keep all split nodes created in the output
15075 for (auto& [name, p] : splitPtrs) out.allSplits.emplace(name, std::move(p));
15077 // Ensure we create an output for everything exactly once
15078 if (debug) {
15079 for (size_t round = 0; round < out.buckets.size(); ++round) {
15080 auto const& r = out.buckets[round];
15081 for (size_t i = 0; i < r.size(); ++i) {
15082 auto const& bucket = r[i];
15083 for (auto const c : bucket.classes) always_assert(tp.erase(c));
15084 for (auto const l : bucket.leafs) always_assert(tp.erase(l));
15087 assertx(tp.empty());
15090 if (Trace::moduleEnabled(Trace::hhbbc_index, 4)) {
15091 for (size_t round = 0; round < out.buckets.size(); ++round) {
15092 size_t nc = 0;
15093 size_t ns = 0;
15094 size_t nd = 0;
15095 size_t nsd = 0;
15096 size_t nl = 0;
15098 auto const& r = out.buckets[round];
15099 for (size_t i = 0; i < r.size(); ++i) {
15100 auto const& bucket = r[i];
15101 FTRACE(5, "build subclass lists round #{} work item #{}:\n", round, i);
15102 FTRACE(5, " classes ({}):\n", bucket.classes.size());
15103 for (auto const DEBUG_ONLY c : bucket.classes) FTRACE(6, " {}\n", c);
15104 FTRACE(5, " splits ({}):\n", bucket.splits.size());
15105 for (auto const DEBUG_ONLY s : bucket.splits) FTRACE(6, " {}\n", s);
15106 FTRACE(5, " deps ({}):\n", bucket.deps.size());
15107 for (auto const DEBUG_ONLY d : bucket.deps) FTRACE(6, " {}\n", d);
15108 FTRACE(5, " split deps ({}):\n", bucket.splitDeps.size());
15109 for (auto const DEBUG_ONLY s : bucket.splitDeps) {
15110 FTRACE(6, " {}\n", s);
15112 FTRACE(5, " leafs ({}):\n", bucket.leafs.size());
15113 for (auto const DEBUG_ONLY c : bucket.leafs) FTRACE(6, " {}\n", c);
15114 FTRACE(5, " edges ({}):\n", bucket.edges.size());
15115 for (DEBUG_ONLY auto const& e : bucket.edges) {
15116 FTRACE(6, " {} -> {}\n", e.cls, e.split);
15118 nc += bucket.classes.size();
15119 ns += bucket.splits.size();
15120 nd += bucket.deps.size();
15121 nsd += bucket.splitDeps.size();
15122 nl += bucket.leafs.size();
15124 FTRACE(4, "BSL round #{} stats\n"
15125 " {} buckets\n"
15126 " {} classes\n"
15127 " {} splits\n"
15128 " {} deps\n"
15129 " {} split deps\n"
15130 " {} leafs\n",
15131 round, r.size(), nc, ns, nd, nsd, nl
15136 return out;
15139 void build_subclass_lists(IndexData& index,
15140 SubclassMetadata meta,
15141 InitTypesMetadata& initTypesMeta) {
15142 trace_time tracer{"build subclass lists", index.sample};
15143 tracer.ignore_client_stats();
15145 using namespace folly::gen;
15147 // Mapping of splits to their Ref. We only upload a split when we're
15148 // going to run a job which it is part of the output.
15149 TSStringToOneT<UniquePtrRef<BuildSubclassListJob::Split>> splitsToRefs;
15151 FSStringToOneT<hphp_fast_set<FuncFamily2::Id>> funcFamilyDeps;
15153 // Use the metadata to assign to rounds and buckets.
15154 auto work = build_subclass_lists_assign(std::move(meta));
15156 // We need to defer updates to data structures until after all the
15157 // jobs in a round have completed. Otherwise we could update a ref
15158 // to a class at the same time another thread is reading it.
15159 struct Updates {
15160 std::vector<
15161 std::tuple<SString, UniquePtrRef<ClassInfo2>, UniquePtrRef<php::Class>>
15162 > classes;
15163 std::vector<
15164 std::pair<SString, UniquePtrRef<BuildSubclassListJob::Split>>
15165 > splits;
15166 std::vector<
15167 std::pair<FuncFamily2::Id, Ref<FuncFamilyGroup>>
15168 > funcFamilies;
15169 std::vector<
15170 std::pair<SString, hphp_fast_set<FuncFamily2::Id>>
15171 > funcFamilyDeps;
15172 std::vector<std::pair<SString, UniquePtrRef<ClassInfo2>>> leafs;
15173 std::vector<std::pair<SString, FuncFamilyEntry>> nameOnly;
15174 std::vector<std::pair<SString, SString>> candidateRegOnlyEquivs;
15175 TSStringToOneT<TSStringSet> cnsBases;
15178 auto const run = [&] (SubclassWork::Bucket bucket, size_t round)
15179 -> coro::Task<Updates> {
15180 co_await coro::co_reschedule_on_current_executor;
15182 if (bucket.classes.empty() &&
15183 bucket.splits.empty() &&
15184 bucket.leafs.empty()) {
15185 assertx(bucket.splitDeps.empty());
15186 co_return Updates{};
15189 // We shouldn't get closures or Closure in any of this.
15190 if (debug) {
15191 for (auto const c : bucket.classes) {
15192 always_assert(!c->tsame(s_Closure.get()));
15193 always_assert(!is_closure_name(c));
15195 for (auto const c : bucket.deps) {
15196 always_assert(!c->tsame(s_Closure.get()));
15197 always_assert(!is_closure_name(c));
15201 auto classes = from(bucket.classes)
15202 | map([&] (SString c) { return index.classInfoRefs.at(c); })
15203 | as<std::vector>();
15204 auto deps = from(bucket.deps)
15205 | map([&] (SString c) { return index.classInfoRefs.at(c); })
15206 | as<std::vector>();
15207 auto leafs = from(bucket.leafs)
15208 | map([&] (SString c) { return index.classInfoRefs.at(c); })
15209 | as<std::vector>();
15210 auto splits = from(bucket.splits)
15211 | map([&] (SString s) {
15212 std::unique_ptr<BuildSubclassListJob::Split> split =
15213 std::move(work.allSplits.at(s));
15214 assertx(split);
15215 return split;
15217 | as<std::vector>();
15218 auto splitDeps = from(bucket.splitDeps)
15219 | map([&] (SString s) { return splitsToRefs.at(s); })
15220 | as<std::vector>();
15221 auto phpClasses =
15222 (from(bucket.classes) + from(bucket.deps) + from(bucket.leafs))
15223 | map([&] (SString c) { return index.classRefs.at(c); })
15224 | as<std::vector>();
15226 std::vector<Ref<FuncFamilyGroup>> funcFamilies;
15227 if (round > 0) {
15228 // Provide the func families associated with any dependency
15229 // classes going into this job. We only need to do this after
15230 // the first round because in the first round all dependencies
15231 // are leafs and won't have any func families.
15232 for (auto const c : bucket.deps) {
15233 if (auto const deps = folly::get_ptr(funcFamilyDeps, c)) {
15234 for (auto const& d : *deps) {
15235 funcFamilies.emplace_back(index.funcFamilyRefs.at(d));
15239 // Keep the func families in deterministic order and avoid
15240 // duplicates.
15241 std::sort(begin(funcFamilies), end(funcFamilies));
15242 funcFamilies.erase(
15243 std::unique(begin(funcFamilies), end(funcFamilies)),
15244 end(funcFamilies)
15246 } else {
15247 assertx(funcFamilyDeps.empty());
15248 assertx(index.funcFamilyRefs.empty());
15251 // ClassInfos and any dependency splits should already be
15252 // stored. Any splits as output of the job, or edges need to be
15253 // uploaded, however.
15254 auto [splitRefs, edges, config] = co_await coro::collectAll(
15255 index.client->storeMulti(std::move(splits)),
15256 index.client->storeMulti(std::move(bucket.edges)),
15257 index.configRef->getCopy()
15260 Client::ExecMetadata metadata{
15261 .job_key = folly::sformat(
15262 "build subclass list {}",
15263 [&] {
15264 if (!bucket.classes.empty()) return bucket.classes[0];
15265 if (!bucket.splits.empty()) return bucket.splits[0];
15266 assertx(!bucket.leafs.empty());
15267 return bucket.leafs[0];
15272 auto results = co_await
15273 index.client->exec(
15274 s_buildSubclassJob,
15275 std::move(config),
15276 singleton_vec(
15277 std::make_tuple(
15278 std::move(classes),
15279 std::move(deps),
15280 std::move(leafs),
15281 std::move(splitRefs),
15282 std::move(splitDeps),
15283 std::move(phpClasses),
15284 std::move(edges),
15285 std::move(funcFamilies)
15288 std::move(metadata)
15290 // Every job is a single work-unit, so we should only ever get one
15291 // result for each one.
15292 assertx(results.size() == 1);
15293 auto& [cinfoRefs, outSplitRefs, clsRefs, ffRefs, leafRefs, outMetaRef]
15294 = results[0];
15295 assertx(cinfoRefs.size() == bucket.classes.size());
15296 assertx(outSplitRefs.size() == bucket.splits.size());
15297 assertx(clsRefs.size() == bucket.classes.size());
15298 assertx(leafRefs.size() == bucket.leafs.size());
15300 auto outMeta = co_await index.client->load(std::move(outMetaRef));
15301 assertx(outMeta.newFuncFamilyIds.size() == ffRefs.size());
15302 assertx(outMeta.funcFamilyDeps.size() == cinfoRefs.size());
15303 assertx(outMeta.regOnlyEquivCandidates.size() == cinfoRefs.size());
15305 Updates updates;
15306 updates.classes.reserve(bucket.classes.size());
15307 updates.splits.reserve(bucket.splits.size());
15308 updates.funcFamilies.reserve(outMeta.newFuncFamilyIds.size());
15309 updates.funcFamilyDeps.reserve(outMeta.funcFamilyDeps.size());
15310 updates.nameOnly.reserve(outMeta.nameOnly.size());
15311 updates.leafs.reserve(bucket.leafs.size());
15313 for (size_t i = 0, size = bucket.classes.size(); i < size; ++i) {
15314 updates.classes.emplace_back(bucket.classes[i], cinfoRefs[i], clsRefs[i]);
15316 for (size_t i = 0, size = bucket.splits.size(); i < size; ++i) {
15317 updates.splits.emplace_back(bucket.splits[i], outSplitRefs[i]);
15319 for (size_t i = 0, size = bucket.leafs.size(); i < size; ++i) {
15320 updates.leafs.emplace_back(bucket.leafs[i], leafRefs[i]);
15322 for (size_t i = 0, size = outMeta.newFuncFamilyIds.size(); i < size; ++i) {
15323 auto const ref = ffRefs[i];
15324 for (auto const& id : outMeta.newFuncFamilyIds[i]) {
15325 updates.funcFamilies.emplace_back(id, ref);
15328 for (size_t i = 0, size = outMeta.funcFamilyDeps.size(); i < size; ++i) {
15329 updates.funcFamilyDeps.emplace_back(
15330 bucket.classes[i],
15331 std::move(outMeta.funcFamilyDeps[i])
15334 updates.nameOnly = std::move(outMeta.nameOnly);
15335 for (size_t i = 0, size = outMeta.regOnlyEquivCandidates.size();
15336 i < size; ++i) {
15337 auto const name = bucket.classes[i];
15338 for (auto const c : outMeta.regOnlyEquivCandidates[i]) {
15339 updates.candidateRegOnlyEquivs.emplace_back(name, c);
15342 updates.cnsBases = std::move(outMeta.cnsBases);
15344 co_return updates;
15348 trace_time tracer2{"build subclass lists work", index.sample};
15350 for (size_t roundNum = 0; roundNum < work.buckets.size(); ++roundNum) {
15351 auto& round = work.buckets[roundNum];
15352 // In each round, run all of the work for each bucket
15353 // simultaneously, gathering up updates from each job.
15354 auto const updates = coro::blockingWait(coro::collectAllRange(
15355 from(round)
15356 | move
15357 | map([&] (SubclassWork::Bucket&& b) {
15358 return run(std::move(b), roundNum)
15359 .scheduleOn(index.executor->sticky());
15361 | as<std::vector>()
15364 // Apply the updates to ClassInfo refs. We can do this
15365 // concurrently because every ClassInfo is already in the map, so
15366 // we can update in place (without mutating the map).
15367 parallel::for_each(
15368 updates,
15369 [&] (const Updates& u) {
15370 for (auto const& [name, cinfo, cls] : u.classes) {
15371 index.classInfoRefs.at(name) = cinfo;
15372 index.classRefs.at(name) = cls;
15374 for (auto const& [name, cinfo] : u.leafs) {
15375 index.classInfoRefs.at(name) = cinfo;
15380 // However updating splitsToRefs cannot be, because we're mutating
15381 // the map by inserting into it. However there's a relatively
15382 // small number of splits, so this should be fine.
15383 parallel::parallel(
15384 [&] {
15385 for (auto const& u : updates) {
15386 for (auto const& [name, ref] : u.splits) {
15387 always_assert(splitsToRefs.emplace(name, ref).second);
15391 [&] {
15392 for (auto const& u : updates) {
15393 for (auto const& [id, ref] : u.funcFamilies) {
15394 // The same FuncFamily can be grouped into multiple
15395 // different groups. Prefer the group that's smaller and
15396 // if they're the same size, use the one with the lowest
15397 // id to keep determinism.
15398 auto const& [existing, inserted] =
15399 index.funcFamilyRefs.emplace(id, ref);
15400 if (inserted) continue;
15401 if (existing->second.id().m_size < ref.id().m_size) continue;
15402 if (ref.id().m_size < existing->second.id().m_size) {
15403 existing->second = ref;
15404 continue;
15406 if (existing->second.id() <= ref.id()) continue;
15407 existing->second = ref;
15411 [&] {
15412 for (auto& u : updates) {
15413 for (auto& [name, ids] : u.funcFamilyDeps) {
15414 always_assert(
15415 funcFamilyDeps.emplace(name, std::move(ids)).second
15420 [&] {
15421 for (auto& u : updates) {
15422 for (auto& [name, entry] : u.nameOnly) {
15423 initTypesMeta.nameOnlyFF[name].emplace_back(std::move(entry));
15425 for (auto [name, candidate] : u.candidateRegOnlyEquivs) {
15426 initTypesMeta.classes[name]
15427 .candidateRegOnlyEquivs.emplace(candidate);
15431 [&] {
15432 for (auto& u : updates) {
15433 for (auto& [n, o] : u.cnsBases) {
15434 always_assert(
15435 index.classToCnsBases.emplace(n, std::move(o)).second
15444 splitsToRefs.clear();
15445 funcFamilyDeps.clear();
15446 work.buckets.clear();
15447 work.allSplits.clear();
15450 //////////////////////////////////////////////////////////////////////
15453 * Initialize the return-types of functions and methods from their
15454 * type-hints. Also set AttrInitialSatisfiesTC on properties if
15455 * appropriate (which must be done after types are initialized).
15457 struct InitTypesJob {
15458 static std::string name() { return "hhbbc-init-types"; }
15459 static void init(const Config& config) {
15460 process_init(config.o, config.gd, false);
15461 ClassGraph::init();
15463 static void fini() { ClassGraph::destroy(); }
15465 using Output = Multi<
15466 Variadic<std::unique_ptr<php::Class>>,
15467 Variadic<std::unique_ptr<ClassInfo2>>,
15468 Variadic<std::unique_ptr<php::Func>>,
15469 Variadic<std::unique_ptr<FuncInfo2>>
15471 static Output run(Variadic<std::unique_ptr<php::Class>> classes,
15472 Variadic<std::unique_ptr<ClassInfo2>> cinfos,
15473 Variadic<std::unique_ptr<php::Func>> funcs,
15474 Variadic<std::unique_ptr<FuncInfo2>> finfos,
15475 Variadic<std::unique_ptr<ClassInfo2>> cinfoDeps) {
15476 LocalIndex index;
15478 for (auto const& cls : classes.vals) {
15479 always_assert(index.classes.emplace(cls->name, cls.get()).second);
15480 for (auto const& clo : cls->closures) {
15481 always_assert(index.classes.emplace(clo->name, clo.get()).second);
15485 // All of the classes which might be a regular only equivalent
15486 // have been provided to the job. So, we can now definitely set
15487 // the regular only equivalent (if necessary). We need to do this
15488 // before setting the initial types because we need that
15489 // information to canonicalize.
15490 for (auto const& cinfo : cinfos.vals) {
15491 always_assert(index.classInfos.emplace(cinfo->name, cinfo.get()).second);
15492 cinfo->classGraph.setRegOnlyEquivs();
15493 // If this is a regular class, we don't need the "expanded"
15494 // method family information anymore, so clear it here to save
15495 // memory.
15496 if (cinfo->isRegularClass) {
15497 folly::erase_if(
15498 cinfo->methodFamilies,
15499 [&] (auto const& e) { return !cinfo->methods.count(e.first); }
15503 for (auto const& clo : cinfo->closures) {
15504 always_assert(index.classInfos.emplace(clo->name, clo.get()).second);
15505 clo->classGraph.setRegOnlyEquivs();
15508 for (auto const& cinfo : cinfoDeps.vals) {
15509 always_assert(index.classInfos.emplace(cinfo->name, cinfo.get()).second);
15510 cinfo->classGraph.setRegOnlyEquivs();
15511 for (auto const& clo : cinfo->closures) {
15512 always_assert(index.classInfos.emplace(clo->name, clo.get()).second);
15513 clo->classGraph.setRegOnlyEquivs();
15517 auto const onCls = [&] (php::Class& cls, ClassInfo2& cinfo) {
15518 assertx(cls.name->tsame(cinfo.name));
15519 assertx(cinfo.funcInfos.size() == cls.methods.size());
15521 unresolve_missing(index, cls);
15522 set_bad_initial_prop_values(index, cls, cinfo);
15523 for (size_t i = 0, size = cls.methods.size(); i < size; ++i) {
15524 auto const& func = cls.methods[i];
15525 auto& finfo = cinfo.funcInfos[i];
15526 assertx(func->name == finfo->name);
15527 assertx(finfo->returnTy.is(BInitCell));
15528 finfo->returnTy = initial_return_type(index, *func);
15532 assertx(classes.vals.size() == cinfos.vals.size());
15533 for (size_t i = 0, size = classes.vals.size(); i < size; ++i) {
15534 auto& cls = classes.vals[i];
15535 auto& cinfo = cinfos.vals[i];
15536 onCls(*cls, *cinfo);
15538 assertx(cls->closures.size() == cinfo->closures.size());
15539 for (size_t j = 0, size2 = cls->closures.size(); j < size2; ++j) {
15540 auto& clo = cls->closures[j];
15541 auto& cloinfo = cinfo->closures[j];
15542 onCls(*clo, *cloinfo);
15546 assertx(funcs.vals.size() == finfos.vals.size());
15547 for (size_t i = 0, size = funcs.vals.size(); i < size; ++i) {
15548 auto const& func = funcs.vals[i];
15549 auto& finfo = finfos.vals[i];
15550 assertx(func->name == finfo->name);
15551 assertx(finfo->returnTy.is(BInitCell));
15552 unresolve_missing(index, *func);
15553 finfo->returnTy = initial_return_type(index, *func);
15556 return std::make_tuple(
15557 std::move(classes),
15558 std::move(cinfos),
15559 std::move(funcs),
15560 std::move(finfos)
15564 private:
15566 struct LocalIndex {
15567 TSStringToOneT<const ClassInfo2*> classInfos;
15568 TSStringToOneT<const php::Class*> classes;
15571 static void unresolve_missing(const LocalIndex& index, TypeConstraint& tc) {
15572 if (!tc.isSubObject()) return;
15573 auto const name = tc.clsName();
15574 if (index.classInfos.count(name)) return;
15575 FTRACE(
15576 4, "Unresolving type-constraint for '{}' because it does not exist\n",
15577 name
15579 tc.unresolve();
15582 static void unresolve_missing(const LocalIndex& index, php::Func& func) {
15583 for (auto& p : func.params) {
15584 unresolve_missing(index, p.typeConstraint);
15585 for (auto& ub : p.upperBounds.m_constraints) unresolve_missing(index, ub);
15587 unresolve_missing(index, func.retTypeConstraint);
15588 for (auto& ub : func.returnUBs.m_constraints) unresolve_missing(index, ub);
15591 static void unresolve_missing(const LocalIndex& index, php::Class& cls) {
15592 if (cls.attrs & AttrEnum) unresolve_missing(index, cls.enumBaseTy);
15593 for (auto& meth : cls.methods) unresolve_missing(index, *meth);
15594 for (auto& prop : cls.properties) {
15595 unresolve_missing(index, prop.typeConstraint);
15596 for (auto& ub : prop.ubs.m_constraints) unresolve_missing(index, ub);
15600 static Type initial_return_type(const LocalIndex& index, const php::Func& f) {
15601 Trace::Bump _{
15602 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(f.unit)
15605 auto const ty = [&] {
15606 // Return type of native functions is calculated differently.
15607 if (f.isNative) return native_function_return_type(&f);
15609 if ((f.attrs & AttrBuiltin) || f.isMemoizeWrapper) return TInitCell;
15611 if (f.isGenerator) {
15612 if (f.isAsync) {
15613 // Async generators always return AsyncGenerator object.
15614 return objExact(res::Class::get(s_AsyncGenerator.get()));
15616 // Non-async generators always return Generator object.
15617 return objExact(res::Class::get(s_Generator.get()));
15620 auto const make_type = [&] (const TypeConstraint& tc) {
15621 auto lookup = type_from_constraint(
15623 TInitCell,
15624 [&] (SString name) -> Optional<res::Class> {
15625 if (auto const ci = folly::get_default(index.classInfos, name)) {
15626 auto const c = res::Class::get(*ci);
15627 assertx(c.isComplete());
15628 return c;
15630 return std::nullopt;
15632 [&] () -> Optional<Type> {
15633 if (!f.cls) return std::nullopt;
15634 auto const& cls = [&] () -> const php::Class& {
15635 if (!f.cls->closureContextCls) return *f.cls;
15636 auto const c =
15637 folly::get_default(index.classes, f.cls->closureContextCls);
15638 always_assert_flog(
15640 "When processing return-type for {}, "
15641 "tried to access missing class {}",
15642 func_fullname(f),
15643 f.cls->closureContextCls
15645 return *c;
15646 }();
15647 if (cls.attrs & AttrTrait) return std::nullopt;
15648 auto const c = res::Class::get(cls.name);
15649 assertx(c.isComplete());
15650 return subCls(c, true);
15653 if (lookup.coerceClassToString == TriBool::Yes) {
15654 lookup.upper = promote_classish(std::move(lookup.upper));
15655 } else if (lookup.coerceClassToString == TriBool::Maybe) {
15656 lookup.upper |= TSStr;
15658 return unctx(std::move(lookup.upper));
15661 auto const process = [&] (const TypeConstraint& tc,
15662 const TypeIntersectionConstraint& ubs) {
15663 auto ret = TInitCell;
15664 ret = intersection_of(std::move(ret), make_type(tc));
15665 for (auto const& ub : ubs.m_constraints) {
15666 ret = intersection_of(std::move(ret), make_type(ub));
15668 return ret;
15671 auto ret = process(f.retTypeConstraint, f.returnUBs);
15672 if (f.hasInOutArgs && !ret.is(BBottom)) {
15673 std::vector<Type> types;
15674 types.reserve(f.params.size() + 1);
15675 types.emplace_back(std::move(ret));
15676 for (auto const& p : f.params) {
15677 if (!p.inout) continue;
15678 auto t = process(p.typeConstraint, p.upperBounds);
15679 if (t.is(BBottom)) return TBottom;
15680 types.emplace_back(std::move(t));
15682 std::reverse(begin(types)+1, end(types));
15683 ret = vec(std::move(types));
15686 if (f.isAsync) {
15687 // Async functions always return WaitH<T>, where T is the type
15688 // returned internally.
15689 return wait_handle(std::move(ret));
15691 return ret;
15692 }();
15694 FTRACE(3, "Initial return type for {}: {}\n",
15695 func_fullname(f), show(ty));
15696 return ty;
15699 static void set_bad_initial_prop_values(const LocalIndex& index,
15700 php::Class& cls,
15701 ClassInfo2& cinfo) {
15702 Trace::Bump _{
15703 Trace::hhbbc_index, kSystemLibBump, is_systemlib_part(cls.unit)
15706 assertx(cinfo.hasBadInitialPropValues);
15708 auto const isClosure = is_closure(cls);
15710 cinfo.hasBadInitialPropValues = false;
15711 for (auto& prop : cls.properties) {
15712 assertx(!(prop.attrs & AttrInitialSatisfiesTC));
15714 // Check whether the property's initial value satisfies it's
15715 // type-hint.
15716 auto const initialSatisfies = [&] {
15717 if (isClosure) return true;
15718 if (is_used_trait(cls)) return false;
15720 // Any property with an unresolved type-constraint here might
15721 // fatal when we initialize the class.
15722 if (prop.typeConstraint.isUnresolved()) return false;
15723 for (auto const& ub : prop.ubs.m_constraints) {
15724 if (ub.isUnresolved()) return false;
15727 if (prop.attrs & (AttrSystemInitialValue | AttrLateInit)) return true;
15729 auto const initial = from_cell(prop.val);
15730 if (initial.subtypeOf(BUninit)) return false;
15732 auto const make_type = [&] (const TypeConstraint& tc) {
15733 auto lookup = type_from_constraint(
15735 initial,
15736 [&] (SString name) -> Optional<res::Class> {
15737 if (auto const ci = folly::get_default(index.classInfos, name)) {
15738 auto const c = res::Class::get(*ci);
15739 assertx(c.isComplete());
15740 return c;
15742 return std::nullopt;
15744 [&] () -> Optional<Type> {
15745 auto const& ctx = [&] () -> const php::Class& {
15746 if (!cls.closureContextCls) return cls;
15747 auto const c =
15748 folly::get_default(index.classes, cls.closureContextCls);
15749 always_assert_flog(
15751 "When processing bad initial prop values for {}, "
15752 "tried to access missing class {}",
15753 cls.name,
15754 cls.closureContextCls
15756 return *c;
15757 }();
15758 if (ctx.attrs & AttrTrait) return std::nullopt;
15759 auto const c = res::Class::get(ctx.name);
15760 assertx(c.isComplete());
15761 return subCls(c, true);
15764 return unctx(std::move(lookup.lower));
15767 if (!initial.subtypeOf(make_type(prop.typeConstraint))) return false;
15768 for (auto const& ub : prop.ubs.m_constraints) {
15769 if (!initial.subtypeOf(make_type(ub))) return false;
15771 return true;
15772 }();
15774 if (initialSatisfies) {
15775 attribute_setter(prop.attrs, true, AttrInitialSatisfiesTC);
15776 } else {
15777 cinfo.hasBadInitialPropValues = true;
15784 * "Fixups" a php::Unit by removing specified funcs from it, and
15785 * adding specified classes. This is needed to add closures created
15786 * from trait flattening into their associated units. While we're
15787 * doing this, we also remove redundant meth caller funcs here
15788 * (because it's convenient).
15790 struct UnitFixupJob {
15791 static std::string name() { return "hhbbc-unit-fixup"; }
15792 static void init(const Config& config) {
15793 process_init(config.o, config.gd, false);
15795 static void fini() {}
15797 static std::unique_ptr<php::Unit> run(std::unique_ptr<php::Unit> unit,
15798 const InitTypesMetadata::Fixup& fixup) {
15799 SCOPE_ASSERT_DETAIL("unit") { return unit->filename->toCppString(); };
15801 if (!fixup.removeFunc.empty()) {
15802 // If we want to remove a func, it should be in this unit.
15803 auto DEBUG_ONLY erased = false;
15804 unit->funcs.erase(
15805 std::remove_if(
15806 begin(unit->funcs),
15807 end(unit->funcs),
15808 [&] (SString func) {
15809 // This is a kinda dumb O(N^2) algorithm, but these lists
15810 // are typicaly size 1.
15811 auto const erase = std::any_of(
15812 begin(fixup.removeFunc),
15813 end(fixup.removeFunc),
15814 [&] (SString remove) { return remove == func; }
15816 if (erase) erased = true;
15817 return erase;
15820 end(unit->funcs)
15822 assertx(erased);
15825 auto const before = unit->classes.size();
15826 unit->classes.insert(
15827 end(unit->classes),
15828 begin(fixup.addClass),
15829 end(fixup.addClass)
15831 // Only sort the newly added classes. The order of the existing
15832 // classes is visible to programs.
15833 std::sort(
15834 begin(unit->classes) + before,
15835 end(unit->classes),
15836 string_data_lt_type{}
15838 always_assert(
15839 std::adjacent_find(
15840 begin(unit->classes), end(unit->classes),
15841 string_data_tsame{}) == end(unit->classes)
15843 return unit;
15848 * BuildSubclassListJob produces name-only func family entries. This
15849 * job merges entries for the same name into one.
15851 struct AggregateNameOnlyJob: public BuildSubclassListJob {
15852 static std::string name() { return "hhbbc-aggregate-name-only"; }
15854 struct OutputMeta {
15855 std::vector<std::vector<FuncFamily2::Id>> newFuncFamilyIds;
15856 std::vector<FuncFamilyEntry> nameOnly;
15857 template <typename SerDe> void serde(SerDe& sd) {
15858 ScopedStringDataIndexer _;
15859 sd(newFuncFamilyIds)
15860 (nameOnly)
15864 using Output = Multi<
15865 Variadic<FuncFamilyGroup>,
15866 OutputMeta
15869 static Output
15870 run(std::vector<std::pair<SString, std::vector<FuncFamilyEntry>>> allEntries,
15871 Variadic<FuncFamilyGroup> funcFamilies) {
15872 LocalIndex index;
15874 for (auto& group : funcFamilies.vals) {
15875 for (auto& ff : group.m_ffs) {
15876 auto const id = ff->m_id;
15877 // We could have multiple groups which contain the same
15878 // FuncFamily, so don't assert uniqueness here. We'll just
15879 // take the first one we see (they should all be equivalent).
15880 index.funcFamilies.emplace(id, std::move(ff));
15884 OutputMeta meta;
15886 for (auto const& [name, entries] : allEntries) {
15887 Data::MethInfo info;
15888 info.complete = false;
15889 info.regularComplete = false;
15891 for (auto const& entry : entries) {
15892 auto entryInfo = meth_info_from_func_family_entry(index, entry);
15893 for (auto const& meth : entryInfo.regularMeths) {
15894 if (info.regularMeths.count(meth)) continue;
15895 info.regularMeths.emplace(meth);
15896 info.nonRegularPrivateMeths.erase(meth);
15897 info.nonRegularMeths.erase(meth);
15899 for (auto const& meth : entryInfo.nonRegularPrivateMeths) {
15900 if (info.regularMeths.count(meth) ||
15901 info.nonRegularPrivateMeths.count(meth)) {
15902 continue;
15904 info.nonRegularPrivateMeths.emplace(meth);
15905 info.nonRegularMeths.erase(meth);
15907 for (auto const& meth : entryInfo.nonRegularMeths) {
15908 if (info.regularMeths.count(meth) ||
15909 info.nonRegularPrivateMeths.count(meth) ||
15910 info.nonRegularMeths.count(meth)) {
15911 continue;
15913 info.nonRegularMeths.emplace(meth);
15916 if (entryInfo.allStatic) {
15917 if (!info.allStatic) {
15918 info.allStatic = std::move(*entryInfo.allStatic);
15919 } else {
15920 *info.allStatic |= *entryInfo.allStatic;
15923 if (entryInfo.regularStatic) {
15924 if (!info.regularStatic) {
15925 info.regularStatic = std::move(*entryInfo.regularStatic);
15926 } else {
15927 *info.regularStatic |= *entryInfo.regularStatic;
15932 meta.nameOnly.emplace_back(
15933 make_method_family_entry(index, name, std::move(info))
15937 Variadic<FuncFamilyGroup> funcFamilyGroups;
15938 group_func_families(index, funcFamilyGroups.vals, meta.newFuncFamilyIds);
15940 return std::make_tuple(
15941 std::move(funcFamilyGroups),
15942 std::move(meta)
15947 Job<InitTypesJob> s_initTypesJob;
15948 Job<UnitFixupJob> s_unitFixupJob;
15949 Job<AggregateNameOnlyJob> s_aggregateNameOnlyJob;
15951 // Initialize return-types, fixup units, and aggregate name-only
15952 // func-families all at once.
15953 void init_types(IndexData& index, InitTypesMetadata meta) {
15954 trace_time tracer{"init types", index.sample};
15956 constexpr size_t kTypesBucketSize = 2000;
15957 constexpr size_t kFixupsBucketSize = 3000;
15958 constexpr size_t kAggregateBucketSize = 3000;
15960 auto typeBuckets = consistently_bucketize(
15961 [&] {
15962 // Temporarily suppress case collision logging
15963 auto oldLogLevel = Cfg::Eval::LogTsameCollisions;
15964 Cfg::Eval::LogTsameCollisions = 0;
15965 SCOPE_EXIT { Cfg::Eval::LogTsameCollisions = oldLogLevel; };
15967 std::vector<SString> roots;
15968 roots.reserve(meta.classes.size() + meta.funcs.size());
15969 for (auto const& [name, _] : meta.classes) {
15970 roots.emplace_back(name);
15972 for (auto const& [name, _] : meta.funcs) {
15973 // A class and a func could have the same name. Avoid
15974 // duplicates. If we do have a name collision it just means
15975 // the func and class will be assigned to the same bucket.
15976 if (meta.classes.count(name)) continue;
15977 roots.emplace_back(name);
15979 return roots;
15980 }(),
15981 kTypesBucketSize
15984 auto fixupBuckets = consistently_bucketize(
15985 [&] {
15986 std::vector<SString> sorted;
15987 sorted.reserve(meta.fixups.size());
15988 for (auto& [unit, _] : meta.fixups) sorted.emplace_back(unit);
15989 std::sort(sorted.begin(), sorted.end(), string_data_lt{});
15990 return sorted;
15991 }(),
15992 kFixupsBucketSize
15995 auto aggregateBuckets = consistently_bucketize(
15996 [&] {
15997 std::vector<SString> sorted;
15998 sorted.reserve(meta.nameOnlyFF.size());
15999 for (auto const& [name, entries] : meta.nameOnlyFF) {
16000 if (entries.size() <= 1) {
16001 // If there's only one entry for a name, there's nothing to
16002 // aggregate, and can be inserted directly as the final
16003 // result.
16004 always_assert(
16005 index.nameOnlyMethodFamilies.emplace(name, entries[0]).second
16007 continue;
16009 // Otherwise insert a dummy entry. This will let us update the
16010 // entry later from multiple threads without having to mutate
16011 // the map.
16012 always_assert(
16013 index.nameOnlyMethodFamilies.emplace(name, FuncFamilyEntry{}).second
16015 sorted.emplace_back(name);
16017 std::sort(begin(sorted), end(sorted), string_data_lt{});
16018 return sorted;
16019 }(),
16020 kAggregateBucketSize
16023 // We want to avoid updating any Index data-structures until after
16024 // all jobs have read their inputs. We use the latch to block tasks
16025 // until all tasks have passed the point of reading their inputs.
16026 CoroLatch typesLatch{typeBuckets.size()};
16028 auto const runTypes = [&] (std::vector<SString> work) -> coro::Task<void> {
16029 co_await coro::co_reschedule_on_current_executor;
16031 if (work.empty()) {
16032 typesLatch.count_down();
16033 co_return;
16036 std::vector<UniquePtrRef<php::Class>> classes;
16037 std::vector<UniquePtrRef<ClassInfo2>> cinfos;
16038 std::vector<UniquePtrRef<php::Func>> funcs;
16039 std::vector<UniquePtrRef<FuncInfo2>> finfos;
16040 std::vector<UniquePtrRef<ClassInfo2>> cinfoDeps;
16042 TSStringSet roots;
16043 std::vector<SString> classNames;
16044 std::vector<SString> funcNames;
16046 roots.reserve(work.size());
16047 classNames.reserve(work.size());
16049 for (auto const w : work) {
16050 if (meta.classes.count(w)) {
16051 always_assert(roots.emplace(w).second);
16052 classNames.emplace_back(w);
16054 if (meta.funcs.count(w)) funcNames.emplace_back(w);
16057 // Add a dependency to the job. A class is a dependency if it
16058 // shows up in a class' type-hints, or if it's a potential
16059 // reg-only equivalent.
16060 auto const addDep = [&] (SString dep, bool addEquiv) {
16061 if (!meta.classes.count(dep) || roots.count(dep)) return;
16062 cinfoDeps.emplace_back(index.classInfoRefs.at(dep));
16063 if (!addEquiv) return;
16064 if (auto const cls = folly::get_ptr(meta.classes, dep)) {
16065 for (auto const d : cls->candidateRegOnlyEquivs) {
16066 if (!meta.classes.count(d) || roots.count(d)) continue;
16067 cinfoDeps.emplace_back(index.classInfoRefs.at(d));
16072 for (auto const w : work) {
16073 if (auto const cls = folly::get_ptr(meta.classes, w)) {
16074 classes.emplace_back(index.classRefs.at(w));
16075 cinfos.emplace_back(index.classInfoRefs.at(w));
16076 for (auto const d : cls->deps) addDep(d, true);
16077 for (auto const d : cls->candidateRegOnlyEquivs) addDep(d, false);
16079 // Not else if. A name can correspond to both a class and a
16080 // func.
16081 if (auto const func = folly::get_ptr(meta.funcs, w)) {
16082 funcs.emplace_back(index.funcRefs.at(w));
16083 finfos.emplace_back(index.funcInfoRefs.at(w));
16084 for (auto const d : func->deps) addDep(d, true);
16087 addDep(s_Awaitable.get(), true);
16088 addDep(s_AsyncGenerator.get(), true);
16089 addDep(s_Generator.get(), true);
16091 // Record that we've read our inputs
16092 typesLatch.count_down();
16094 std::sort(begin(cinfoDeps), end(cinfoDeps));
16095 cinfoDeps.erase(
16096 std::unique(begin(cinfoDeps), end(cinfoDeps)),
16097 end(cinfoDeps)
16100 auto config = co_await index.configRef->getCopy();
16102 Client::ExecMetadata metadata{
16103 .job_key = folly::sformat("init types {}", work[0])
16106 auto results = co_await
16107 index.client->exec(
16108 s_initTypesJob,
16109 std::move(config),
16110 singleton_vec(
16111 std::make_tuple(
16112 std::move(classes),
16113 std::move(cinfos),
16114 std::move(funcs),
16115 std::move(finfos),
16116 std::move(cinfoDeps)
16119 std::move(metadata)
16121 assertx(results.size() == 1);
16122 auto& [classRefs, cinfoRefs, funcRefs, finfoRefs] = results[0];
16123 assertx(classRefs.size() == classNames.size());
16124 assertx(cinfoRefs.size() == classNames.size());
16125 assertx(funcRefs.size() == funcNames.size());
16126 assertx(finfoRefs.size() == funcNames.size());
16128 // Wait for all tasks to finish reading from the Index ref tables
16129 // before starting to overwrite them.
16130 co_await typesLatch.wait();
16132 for (size_t i = 0, size = classNames.size(); i < size; ++i) {
16133 auto const name = classNames[i];
16134 index.classRefs.at(name) = std::move(classRefs[i]);
16135 index.classInfoRefs.at(name) = std::move(cinfoRefs[i]);
16137 for (size_t i = 0, size = funcNames.size(); i < size; ++i) {
16138 auto const name = funcNames[i];
16139 index.funcRefs.at(name) = std::move(funcRefs[i]);
16140 index.funcInfoRefs.at(name) = std::move(finfoRefs[i]);
16143 co_return;
16146 auto const runFixups = [&] (std::vector<SString> units) -> coro::Task<void> {
16147 co_await coro::co_reschedule_on_current_executor;
16149 if (units.empty()) co_return;
16151 std::vector<InitTypesMetadata::Fixup> fixups;
16153 // Gather up the fixups and ensure a deterministic ordering.
16154 fixups.reserve(units.size());
16155 for (auto const unit : units) {
16156 auto f = std::move(meta.fixups.at(unit));
16157 assertx(!f.addClass.empty() || !f.removeFunc.empty());
16158 std::sort(f.addClass.begin(), f.addClass.end(), string_data_lt_type{});
16159 std::sort(f.removeFunc.begin(), f.removeFunc.end(), string_data_lt{});
16160 fixups.emplace_back(std::move(f));
16162 auto fixupRefs = co_await index.client->storeMulti(std::move(fixups));
16163 assertx(fixupRefs.size() == units.size());
16165 std::vector<
16166 std::tuple<UniquePtrRef<php::Unit>, Ref<InitTypesMetadata::Fixup>>
16167 > inputs;
16168 inputs.reserve(units.size());
16170 for (size_t i = 0, size = units.size(); i < size; ++i) {
16171 inputs.emplace_back(
16172 index.unitRefs.at(units[i]),
16173 std::move(fixupRefs[i])
16177 Client::ExecMetadata metadata{
16178 .job_key = folly::sformat("fixup units {}", units[0])
16181 auto config = co_await index.configRef->getCopy();
16182 auto outputs = co_await index.client->exec(
16183 s_unitFixupJob,
16184 std::move(config),
16185 std::move(inputs),
16186 std::move(metadata)
16188 assertx(outputs.size() == units.size());
16190 // Every unit is already in the Index table, so we can overwrite
16191 // them without locking.
16192 for (size_t i = 0, size = units.size(); i < size; ++i) {
16193 index.unitRefs.at(units[i]) = std::move(outputs[i]);
16196 co_return;
16199 struct AggregateUpdates {
16200 std::vector<
16201 std::pair<FuncFamily2::Id, Ref<FuncFamilyGroup>>
16202 > funcFamilies;
16205 auto const runAggregate = [&] (std::vector<SString> names)
16206 -> coro::Task<AggregateUpdates> {
16207 co_await coro::co_reschedule_on_current_executor;
16209 if (names.empty()) co_return AggregateUpdates{};
16211 std::vector<std::pair<SString, std::vector<FuncFamilyEntry>>> entries;
16212 std::vector<Ref<FuncFamilyGroup>> funcFamilies;
16214 entries.reserve(names.size());
16215 // Extract out any func families the entries refer to, so they can
16216 // be provided to the job.
16217 for (auto const n : names) {
16218 auto& e = meta.nameOnlyFF.at(n);
16219 entries.emplace_back(n, std::move(e));
16220 for (auto const& entry : entries.back().second) {
16221 match<void>(
16222 entry.m_meths,
16223 [&] (const FuncFamilyEntry::BothFF& e) {
16224 funcFamilies.emplace_back(index.funcFamilyRefs.at(e.m_ff));
16226 [&] (const FuncFamilyEntry::FFAndSingle& e) {
16227 funcFamilies.emplace_back(index.funcFamilyRefs.at(e.m_ff));
16229 [&] (const FuncFamilyEntry::FFAndNone& e) {
16230 funcFamilies.emplace_back(index.funcFamilyRefs.at(e.m_ff));
16232 [&] (const FuncFamilyEntry::BothSingle&) {},
16233 [&] (const FuncFamilyEntry::SingleAndNone&) {},
16234 [&] (const FuncFamilyEntry::None&) {}
16239 std::sort(begin(funcFamilies), end(funcFamilies));
16240 funcFamilies.erase(
16241 std::unique(begin(funcFamilies), end(funcFamilies)),
16242 end(funcFamilies)
16245 auto [entriesRef, config] = co_await coro::collectAll(
16246 index.client->store(std::move(entries)),
16247 index.configRef->getCopy()
16250 Client::ExecMetadata metadata{
16251 .job_key = folly::sformat("aggregate name-only {}", names[0])
16254 auto results = co_await
16255 index.client->exec(
16256 s_aggregateNameOnlyJob,
16257 std::move(config),
16258 singleton_vec(
16259 std::make_tuple(std::move(entriesRef), std::move(funcFamilies))
16261 std::move(metadata)
16263 assertx(results.size() == 1);
16264 auto& [ffRefs, outMetaRef] = results[0];
16266 auto outMeta = co_await index.client->load(std::move(outMetaRef));
16267 assertx(outMeta.newFuncFamilyIds.size() == ffRefs.size());
16268 assertx(outMeta.nameOnly.size() == names.size());
16270 // Update the dummy entries with the actual result.
16271 for (size_t i = 0, size = names.size(); i < size; ++i) {
16272 auto& old = index.nameOnlyMethodFamilies.at(names[i]);
16273 assertx(boost::get<FuncFamilyEntry::None>(&old.m_meths));
16274 old = std::move(outMeta.nameOnly[i]);
16277 AggregateUpdates updates;
16278 updates.funcFamilies.reserve(outMeta.newFuncFamilyIds.size());
16279 for (size_t i = 0, size = outMeta.newFuncFamilyIds.size(); i < size; ++i) {
16280 auto const ref = ffRefs[i];
16281 for (auto const& id : outMeta.newFuncFamilyIds[i]) {
16282 updates.funcFamilies.emplace_back(id, ref);
16286 co_return updates;
16289 auto const runAggregateCombine = [&] (auto tasks) -> coro::Task<void> {
16290 co_await coro::co_reschedule_on_current_executor;
16292 auto const updates = co_await coro::collectAllRange(std::move(tasks));
16294 for (auto const& u : updates) {
16295 for (auto const& [id, ref] : u.funcFamilies) {
16296 // The same FuncFamily can be grouped into multiple
16297 // different groups. Prefer the group that's smaller and
16298 // if they're the same size, use the one with the lowest
16299 // id to keep determinism.
16300 auto const& [existing, inserted] =
16301 index.funcFamilyRefs.emplace(id, ref);
16302 if (inserted) continue;
16303 if (existing->second.id().m_size < ref.id().m_size) continue;
16304 if (ref.id().m_size < existing->second.id().m_size) {
16305 existing->second = ref;
16306 continue;
16308 if (existing->second.id() <= ref.id()) continue;
16309 existing->second = ref;
16313 co_return;
16316 using namespace folly::gen;
16318 std::vector<coro::TaskWithExecutor<void>> tasks;
16319 tasks.reserve(typeBuckets.size() + fixupBuckets.size() + 1);
16321 // Temporarily suppress case collision logging
16322 auto oldTypeLogLevel = Cfg::Eval::LogTsameCollisions;
16323 Cfg::Eval::LogTsameCollisions = 0;
16324 SCOPE_EXIT {
16325 Cfg::Eval::LogTsameCollisions = oldTypeLogLevel;
16328 for (auto& work : typeBuckets) {
16329 tasks.emplace_back(
16330 runTypes(std::move(work)).scheduleOn(index.executor->sticky())
16333 for (auto& work : fixupBuckets) {
16334 tasks.emplace_back(
16335 runFixups(std::move(work)).scheduleOn(index.executor->sticky())
16338 auto subTasks = from(aggregateBuckets)
16339 | move
16340 | map([&] (std::vector<SString>&& work) {
16341 return runAggregate(
16342 std::move(work)
16343 ).scheduleOn(index.executor->sticky());
16345 | as<std::vector>();
16346 tasks.emplace_back(
16347 runAggregateCombine(
16348 std::move(subTasks)
16349 ).scheduleOn(index.executor->sticky())
16352 coro::blockingWait(coro::collectAllRange(std::move(tasks)));
16355 //////////////////////////////////////////////////////////////////////
16357 Index::Input::UnitMeta make_native_unit_meta(IndexData& index) {
16358 auto unit = make_native_unit();
16359 auto const name = unit->filename;
16361 std::vector<std::pair<SString, bool>> constants;
16362 constants.reserve(unit->constants.size());
16363 for (auto const& cns : unit->constants) {
16364 constants.emplace_back(cns->name, type(cns->val) == KindOfUninit);
16367 auto unitRef = coro::blockingWait(index.client->store(std::move(unit)));
16368 Index::Input::UnitMeta meta{ std::move(unitRef), name };
16369 meta.constants = std::move(constants);
16370 return meta;
16373 // Set up the async state, populate the (initial) table of
16374 // extern-worker refs in the Index, and build some metadata needed for
16375 // class flattening.
16376 IndexFlattenMetadata make_remote(IndexData& index,
16377 Config config,
16378 Index::Input input,
16379 std::unique_ptr<TicketExecutor> executor,
16380 std::unique_ptr<Client> client,
16381 DisposeCallback dispose) {
16382 trace_time tracer("make remote");
16383 tracer.ignore_client_stats();
16385 assertx(input.classes.size() == input.classBC.size());
16386 assertx(input.funcs.size() == input.funcBC.size());
16388 index.executor = std::move(executor);
16389 index.client = std::move(client);
16390 index.disposeClient = std::move(dispose);
16392 // Kick off the storage of the global config. We'll start early so
16393 // it will (hopefully) be done before we need it.
16394 index.configRef = std::make_unique<CoroAsyncValue<Ref<Config>>>(
16395 [&index, config = std::move(config)] () mutable {
16396 return index.client->store(std::move(config));
16398 index.executor->sticky()
16401 // Create a fake unit to store native constants and add it as an
16402 // input.
16403 input.units.emplace_back(make_native_unit_meta(index));
16405 IndexFlattenMetadata flattenMeta;
16406 SStringToOneT<SString> methCallerUnits;
16408 flattenMeta.cls.reserve(input.classes.size());
16409 flattenMeta.allCls.reserve(input.classes.size());
16410 flattenMeta.allFuncs.reserve(input.funcs.size());
16412 // Add unit and class information to their appropriate tables. This
16413 // is also where we'll detect duplicate funcs and class names (which
16414 // should be caught earlier during parsing).
16415 for (auto& unit : input.units) {
16416 FTRACE(5, "unit {} -> {}\n", unit.name, unit.unit.id().toString());
16418 for (auto& typeMapping : unit.typeMappings) {
16419 auto const name = typeMapping.name;
16420 auto const isTypeAlias = typeMapping.isTypeAlias;
16421 always_assert_flog(
16422 flattenMeta.typeMappings.emplace(name, std::move(typeMapping)).second,
16423 "Duplicate type-mapping: {}",
16424 name
16426 if (isTypeAlias) {
16427 always_assert(index.typeAliasToUnit.emplace(name, unit.name).second);
16428 index.unitsWithTypeAliases.emplace(unit.name);
16432 always_assert_flog(
16433 index.unitRefs.emplace(unit.name, std::move(unit.unit)).second,
16434 "Duplicate unit: {}",
16435 unit.name
16438 for (auto const& [cnsName, hasInit] : unit.constants) {
16439 always_assert_flog(
16440 index.constantToUnit.emplace(
16441 cnsName,
16442 std::make_pair(unit.name, hasInit)
16443 ).second,
16444 "Duplicate constant: {}",
16445 cnsName
16450 for (auto& cls : input.classes) {
16451 FTRACE(5, "class {} -> {}\n", cls.name, cls.cls.id().toString());
16452 always_assert_flog(
16453 index.classRefs.emplace(cls.name, std::move(cls.cls)).second,
16454 "Duplicate class: {}",
16455 cls.name
16457 always_assert(index.classToUnit.emplace(cls.name, cls.unit).second);
16459 auto& meta = flattenMeta.cls[cls.name];
16460 if (cls.closureFunc) {
16461 assertx(cls.closures.empty());
16462 index.funcToClosures[cls.closureFunc].emplace(cls.name);
16463 index.closureToFunc.emplace(cls.name, cls.closureFunc);
16464 meta.isClosure = true;
16466 index.classToClosures[cls.name].insert(
16467 begin(cls.closures),
16468 end(cls.closures)
16470 for (auto const clo : cls.closures) {
16471 index.closureToClass.emplace(clo, cls.name);
16474 meta.deps.insert(begin(cls.dependencies), end(cls.dependencies));
16475 meta.unresolvedTypes = std::move(cls.unresolvedTypes);
16476 meta.idx = flattenMeta.allCls.size();
16477 flattenMeta.allCls.emplace_back(cls.name);
16479 if (cls.has86init) index.classesWith86Inits.emplace(cls.name);
16481 if (cls.typeMapping) {
16482 auto const name = cls.typeMapping->name;
16483 always_assert_flog(
16484 flattenMeta.typeMappings.emplace(
16485 name, std::move(*cls.typeMapping)
16486 ).second,
16487 "Duplicate type-mapping: {}",
16488 name
16493 // Funcs have an additional wrinkle, however. A func might be a meth
16494 // caller. Meth callers are special in that they might be present
16495 // (with the same name) in multiple units. However only one "wins"
16496 // and is actually emitted in the repo. We detect that here and
16497 // select a winner. The "losing" meth callers will be actually
16498 // removed from their unit after class flattening.
16499 for (auto& func : input.funcs) {
16500 FTRACE(5, "func {} -> {}\n", func.name, func.func.id().toString());
16502 if (func.methCaller) {
16503 // If this meth caller a duplicate of one we've already seen?
16504 auto const [existing, emplaced] =
16505 methCallerUnits.emplace(func.name, func.unit);
16506 if (!emplaced) {
16507 // It is. The duplicate shouldn't be in the same unit,
16508 // however.
16509 always_assert_flog(
16510 existing->second != func.unit,
16511 "Duplicate meth-caller {} in same unit {}",
16512 func.name,
16513 func.unit
16515 // The winner is the one with the unit with the "lesser"
16516 // name. This is completely arbitrary.
16517 if (string_data_lt{}(func.unit, existing->second)) {
16518 // This one wins. Schedule the older entry for deletion and
16519 // take over it's position in the map.
16520 FTRACE(
16521 4, " meth caller {} from unit {} taking priority over unit {}",
16522 func.name, func.unit, existing->second
16524 flattenMeta.unitDeletions[existing->second].emplace_back(func.name);
16525 existing->second = func.unit;
16526 index.funcRefs.at(func.name) = std::move(func.func);
16527 index.funcToUnit.at(func.name) = func.unit;
16528 } else {
16529 // This one loses. Schedule it for deletion.
16530 flattenMeta.unitDeletions[func.unit].emplace_back(func.name);
16532 continue;
16534 // It's not. Treat it like anything else.
16537 // If not a meth caller, treat it like anything else.
16538 always_assert_flog(
16539 index.funcRefs.emplace(func.name, std::move(func.func)).second,
16540 "Duplicate func: {}",
16541 func.name
16544 index.funcToUnit.emplace(func.name, func.unit);
16545 if (Constant::nameFromFuncName(func.name)) {
16546 index.constantInitFuncs.emplace(func.name);
16549 auto& meta = flattenMeta.func[func.name];
16550 meta.unresolvedTypes = std::move(func.unresolvedTypes);
16552 flattenMeta.allFuncs.emplace_back(func.name);
16555 for (auto& bc : input.classBC) {
16556 FTRACE(5, "class bytecode {} -> {}\n", bc.name, bc.bc.id().toString());
16558 always_assert_flog(
16559 index.classRefs.count(bc.name),
16560 "Class bytecode for non-existent class {}",
16561 bc.name
16563 always_assert_flog(
16564 index.classBytecodeRefs.emplace(bc.name, std::move(bc.bc)).second,
16565 "Duplicate class bytecode: {}",
16566 bc.name
16570 for (auto& bc : input.funcBC) {
16571 FTRACE(5, "func bytecode {} -> {}\n", bc.name, bc.bc.id().toString());
16573 always_assert_flog(
16574 index.funcRefs.count(bc.name),
16575 "Func bytecode for non-existent func {}",
16576 bc.name
16579 if (bc.methCaller) {
16580 // Only record this bytecode if it's associated meth-caller was
16581 // kept.
16582 auto const it = methCallerUnits.find(bc.name);
16583 always_assert_flog(
16584 it != end(methCallerUnits),
16585 "Bytecode for func {} is marked as meth-caller, "
16586 "but func is not a meth-caller",
16587 bc.name
16589 auto const unit = it->second;
16590 if (bc.unit != unit) {
16591 FTRACE(
16593 "Bytecode for meth-caller func {} in unit {} "
16594 "skipped because the meth-caller was dropped\n",
16595 bc.name, bc.unit
16597 continue;
16599 } else {
16600 always_assert_flog(
16601 !methCallerUnits.count(bc.name),
16602 "Bytecode for func {} is not marked as meth-caller, "
16603 "but func is a meth-caller",
16604 bc.name
16608 always_assert_flog(
16609 index.funcBytecodeRefs.emplace(bc.name, std::move(bc.bc)).second,
16610 "Duplicate func bytecode: {}",
16611 bc.name
16615 if (debug) {
16616 for (auto const& [cns, unitAndInit] : index.constantToUnit) {
16617 if (!unitAndInit.second) continue;
16618 if (is_native_unit(unitAndInit.first)) continue;
16619 auto const initName = Constant::funcNameFromName(cns);
16620 always_assert_flog(
16621 index.funcRefs.count(initName) > 0,
16622 "Constant {} is marked as having initialization func {}, "
16623 "but it does not exist",
16624 cns, initName
16629 return flattenMeta;
16632 //////////////////////////////////////////////////////////////////////
16635 * Combines multiple classes/class-infos/funcs/units into a single
16636 * blob. Makes make_local() more efficient, as you can download a
16637 * smaller number of large blobs rather than many small blobs.
16639 struct AggregateJob {
16640 static std::string name() { return "hhbbc-aggregate"; }
16641 static void init(const Config& config) {
16642 process_init(config.o, config.gd, false);
16643 ClassGraph::init();
16645 static void fini() { ClassGraph::destroy(); }
16647 struct Bundle {
16648 std::vector<std::unique_ptr<php::Class>> classes;
16649 std::vector<std::unique_ptr<ClassInfo2>> classInfos;
16650 std::vector<std::unique_ptr<php::ClassBytecode>> classBytecode;
16651 std::vector<std::unique_ptr<php::Func>> funcs;
16652 std::vector<std::unique_ptr<FuncInfo2>> funcInfos;
16653 std::vector<std::unique_ptr<php::FuncBytecode>> funcBytecode;
16654 std::vector<std::unique_ptr<php::Unit>> units;
16655 std::vector<FuncFamilyGroup> funcFamilies;
16656 std::vector<std::unique_ptr<MethodsWithoutCInfo>> methInfos;
16658 template <typename SerDe> void serde(SerDe& sd) {
16659 ScopedStringDataIndexer _;
16660 ClassGraph::ScopedSerdeState _2;
16661 sd(classes)
16662 (classInfos)
16663 (classBytecode)
16664 (funcs)
16665 (funcInfos)
16666 (funcBytecode)
16667 (units)
16668 (funcFamilies)
16669 (methInfos)
16674 static Bundle run(Variadic<std::unique_ptr<php::Class>> classes,
16675 Variadic<std::unique_ptr<ClassInfo2>> classInfos,
16676 Variadic<std::unique_ptr<php::ClassBytecode>> classBytecode,
16677 Variadic<std::unique_ptr<php::Func>> funcs,
16678 Variadic<std::unique_ptr<FuncInfo2>> funcInfos,
16679 Variadic<std::unique_ptr<php::FuncBytecode>> funcBytecode,
16680 Variadic<std::unique_ptr<php::Unit>> units,
16681 Variadic<FuncFamilyGroup> funcFamilies,
16682 Variadic<std::unique_ptr<MethodsWithoutCInfo>> methInfos) {
16683 Bundle bundle;
16684 bundle.classes.reserve(classes.vals.size());
16685 bundle.classInfos.reserve(classInfos.vals.size());
16686 bundle.classBytecode.reserve(classBytecode.vals.size());
16687 bundle.funcs.reserve(funcs.vals.size());
16688 bundle.funcInfos.reserve(funcInfos.vals.size());
16689 bundle.funcBytecode.reserve(funcBytecode.vals.size());
16690 bundle.units.reserve(units.vals.size());
16691 bundle.funcFamilies.reserve(funcFamilies.vals.size());
16692 bundle.methInfos.reserve(methInfos.vals.size());
16693 for (auto& c : classes.vals) {
16694 bundle.classes.emplace_back(std::move(c));
16696 for (auto& c : classInfos.vals) {
16697 bundle.classInfos.emplace_back(std::move(c));
16699 for (auto& b : classBytecode.vals) {
16700 bundle.classBytecode.emplace_back(std::move(b));
16702 for (auto& f : funcs.vals) {
16703 bundle.funcs.emplace_back(std::move(f));
16705 for (auto& f : funcInfos.vals) {
16706 bundle.funcInfos.emplace_back(std::move(f));
16708 for (auto& b : funcBytecode.vals) {
16709 bundle.funcBytecode.emplace_back(std::move(b));
16711 for (auto& u : units.vals) {
16712 bundle.units.emplace_back(std::move(u));
16714 for (auto& group : funcFamilies.vals) {
16715 bundle.funcFamilies.emplace_back(std::move(group));
16717 for (auto& m : methInfos.vals) {
16718 bundle.methInfos.emplace_back(std::move(m));
16720 return bundle;
16724 Job<AggregateJob> s_aggregateJob;
16726 void remote_func_info_to_local(IndexData& index,
16727 const php::Func& func,
16728 FuncInfo2& rfinfo) {
16729 assertx(func.name == rfinfo.name);
16730 auto finfo = func_info(index, &func);
16731 assertx(finfo->returnTy.is(BInitCell));
16732 finfo->returnTy = std::move(rfinfo.returnTy);
16733 finfo->returnRefinements = rfinfo.returnRefinements;
16734 finfo->retParam = rfinfo.retParam;
16735 finfo->effectFree = rfinfo.effectFree;
16736 finfo->unusedParams = rfinfo.unusedParams;
16739 // Convert the FuncInfo2s we loaded from extern-worker into their
16740 // equivalent FuncInfos.
16741 void make_func_infos_local(IndexData& index,
16742 std::vector<std::unique_ptr<FuncInfo2>> remote) {
16743 trace_time tracer{"make func-infos local"};
16744 tracer.ignore_client_stats();
16746 parallel::for_each(
16747 remote,
16748 [&] (const std::unique_ptr<FuncInfo2>& rfinfo) {
16749 auto const it = index.funcs.find(rfinfo->name);
16750 always_assert_flog(
16751 it != end(index.funcs),
16752 "Func-info for {} has no associated php::Func in index",
16753 rfinfo->name
16755 remote_func_info_to_local(index, *it->second, *rfinfo);
16760 // Convert the ClassInfo2s we loaded from extern-worker into their
16761 // equivalent ClassInfos (and store it in the Index).
16762 void make_class_infos_local(
16763 IndexData& index,
16764 std::vector<std::unique_ptr<ClassInfo2>> remote,
16765 std::vector<std::unique_ptr<FuncFamily2>> funcFamilies
16767 trace_time tracer{"make class-infos local"};
16768 tracer.ignore_client_stats();
16770 assertx(index.allClassInfos.empty());
16771 assertx(index.classInfo.empty());
16773 // First create a ClassInfo for each ClassInfo2. Since a ClassInfo
16774 // can refer to other ClassInfos, we can't do much more at this
16775 // stage.
16776 auto newCInfos = parallel::map(
16777 remote,
16778 [&] (const std::unique_ptr<ClassInfo2>& in) {
16779 std::vector<std::unique_ptr<ClassInfo>> out;
16781 auto const make = [&] (const ClassInfo2& cinfo) {
16782 auto c = std::make_unique<ClassInfo>();
16783 auto const it = index.classes.find(cinfo.name);
16784 always_assert_flog(
16785 it != end(index.classes),
16786 "Class-info for {} has no associated php::Class in index",
16787 cinfo.name
16789 c->cls = it->second;
16790 out.emplace_back(std::move(c));
16793 make(*in);
16794 for (auto const& clo : in->closures) make(*clo);
16795 return out;
16799 // Build table mapping name to ClassInfo.
16800 for (auto& cinfos : newCInfos) {
16801 for (auto& cinfo : cinfos) {
16802 always_assert(
16803 index.classInfo.emplace(cinfo->cls->name, cinfo.get()).second
16805 index.allClassInfos.emplace_back(std::move(cinfo));
16808 newCInfos.clear();
16809 newCInfos.shrink_to_fit();
16810 index.allClassInfos.shrink_to_fit();
16812 // Set AttrNoOverride to true for all methods. If we determine it's
16813 // actually overridden below, we'll clear it.
16814 parallel::for_each(
16815 index.program->classes,
16816 [&] (std::unique_ptr<php::Class>& cls) {
16817 for (auto& m : cls->methods) {
16818 assertx(!(m->attrs & AttrNoOverride));
16819 if (is_special_method_name(m->name)) continue;
16820 attribute_setter(m->attrs, true, AttrNoOverride);
16822 for (auto& clo : cls->closures) {
16823 assertx(clo->methods.size() == 1);
16824 auto& m = clo->methods[0];
16825 assertx(!(m->attrs & AttrNoOverride));
16826 assertx(!is_special_method_name(m->name));
16827 attribute_setter(m->attrs, true, AttrNoOverride);
16832 auto const get = [&] (SString name) {
16833 auto const it = index.classInfo.find(name);
16834 always_assert_flog(
16835 it != end(index.classInfo),
16836 "Class-info for {} not found in index",
16837 name
16839 return it->second;
16842 struct FFState {
16843 explicit FFState(std::unique_ptr<FuncFamily2> ff) : m_ff{std::move(ff)} {}
16844 std::unique_ptr<FuncFamily2> m_ff;
16845 LockFreeLazyPtrNoDelete<FuncFamily> m_notExpanded;
16846 LockFreeLazyPtrNoDelete<FuncFamily> m_expanded;
16848 FuncFamily* notExpanded(IndexData& index) {
16849 return const_cast<FuncFamily*>(
16850 &m_notExpanded.get([&] { return make(index, false); })
16853 FuncFamily* expanded(IndexData& index) {
16854 return const_cast<FuncFamily*>(
16855 &m_expanded.get([&] { return make(index, true); })
16859 FuncFamily* make(IndexData& index, bool expanded) const {
16860 FuncFamily::PFuncVec funcs;
16861 funcs.reserve(
16862 m_ff->m_regular.size() +
16863 (expanded
16865 : (m_ff->m_nonRegularPrivate.size() + m_ff->m_nonRegular.size())
16869 for (auto const& m : m_ff->m_regular) {
16870 funcs.emplace_back(func_from_meth_ref(index, m), true);
16872 for (auto const& m : m_ff->m_nonRegularPrivate) {
16873 funcs.emplace_back(func_from_meth_ref(index, m), true);
16875 if (!expanded) {
16876 for (auto const& m : m_ff->m_nonRegular) {
16877 funcs.emplace_back(func_from_meth_ref(index, m), false);
16881 auto const extra = !expanded && !m_ff->m_nonRegular.empty() &&
16882 (m_ff->m_regular.size() + m_ff->m_nonRegularPrivate.size()) > 1;
16884 std::sort(
16885 begin(funcs), end(funcs),
16886 [] (FuncFamily::PossibleFunc a, const FuncFamily::PossibleFunc b) {
16887 if (a.inRegular() && !b.inRegular()) return true;
16888 if (!a.inRegular() && b.inRegular()) return false;
16889 return string_data_lt_type{}(a.ptr()->cls->name, b.ptr()->cls->name);
16892 funcs.shrink_to_fit();
16894 assertx(funcs.size() > 1);
16896 auto const convert = [&] (const FuncFamily2::StaticInfo& in) {
16897 FuncFamily::StaticInfo out;
16898 out.m_numInOut = in.m_numInOut;
16899 out.m_requiredCoeffects = in.m_requiredCoeffects;
16900 out.m_coeffectRules = in.m_coeffectRules;
16901 out.m_paramPreps = in.m_paramPreps;
16902 out.m_minNonVariadicParams = in.m_minNonVariadicParams;
16903 out.m_maxNonVariadicParams = in.m_maxNonVariadicParams;
16904 out.m_isReadonlyReturn = in.m_isReadonlyReturn;
16905 out.m_isReadonlyThis = in.m_isReadonlyThis;
16906 out.m_supportsAER = in.m_supportsAER;
16907 out.m_maybeReified = in.m_maybeReified;
16908 out.m_maybeCaresAboutDynCalls = in.m_maybeCaresAboutDynCalls;
16909 out.m_maybeBuiltin = in.m_maybeBuiltin;
16911 auto const it = index.funcFamilyStaticInfos.find(out);
16912 if (it != end(index.funcFamilyStaticInfos)) return it->first.get();
16913 return index.funcFamilyStaticInfos.insert(
16914 std::make_unique<FuncFamily::StaticInfo>(std::move(out)),
16915 false
16916 ).first->first.get();
16919 auto newFuncFamily =
16920 std::make_unique<FuncFamily>(std::move(funcs), extra);
16922 always_assert(m_ff->m_allStatic);
16923 if (m_ff->m_regularStatic) {
16924 const FuncFamily::StaticInfo* reg = nullptr;
16925 if (expanded || extra) reg = convert(*m_ff->m_regularStatic);
16926 newFuncFamily->m_all.m_static =
16927 expanded ? reg : convert(*m_ff->m_allStatic);
16928 if (extra) {
16929 newFuncFamily->m_regular = std::make_unique<FuncFamily::Info>();
16930 newFuncFamily->m_regular->m_static = reg;
16932 } else {
16933 newFuncFamily->m_all.m_static = convert(*m_ff->m_allStatic);
16936 return index.funcFamilies.insert(
16937 std::move(newFuncFamily),
16938 false
16939 ).first->first.get();
16943 hphp_fast_map<FuncFamily2::Id, std::unique_ptr<FFState>> ffState;
16944 for (auto& ff : funcFamilies) {
16945 auto const id = ff->m_id;
16946 always_assert(
16947 ffState.emplace(
16949 std::make_unique<FFState>(std::move(ff))
16950 ).second
16953 funcFamilies.clear();
16955 std::mutex extraMethodLock;
16957 // Now that we can map name to ClassInfo, we can populate the rest
16958 // of the fields in each ClassInfo.
16959 parallel::for_each(
16960 remote,
16961 [&] (std::unique_ptr<ClassInfo2>& rcinfos) {
16962 auto const process = [&] (std::unique_ptr<ClassInfo2> rcinfo) {
16963 auto const cinfo = get(rcinfo->name);
16964 if (rcinfo->parent) cinfo->parent = get(rcinfo->parent);
16966 if (!(cinfo->cls->attrs & AttrNoExpandTrait)) {
16967 auto const traits = rcinfo->classGraph.usedTraits();
16968 cinfo->usedTraits.reserve(traits.size());
16969 for (auto const trait : traits) {
16970 cinfo->usedTraits.emplace_back(get(trait.name()));
16972 cinfo->usedTraits.shrink_to_fit();
16974 cinfo->traitProps = std::move(rcinfo->traitProps);
16976 cinfo->clsConstants.reserve(rcinfo->clsConstants.size());
16977 for (auto const& [name, cns] : rcinfo->clsConstants) {
16978 auto const it = index.classes.find(cns.idx.cls);
16979 always_assert_flog(
16980 it != end(index.classes),
16981 "php::Class for {} not found in index",
16982 name
16984 cinfo->clsConstants.emplace(
16985 name,
16986 ClassInfo::ConstIndex { it->second, cns.idx.idx }
16990 for (size_t i = 0, size = cinfo->cls->constants.size(); i < size; ++i) {
16991 auto const& cns = cinfo->cls->constants[i];
16992 if (cns.kind != ConstModifiers::Kind::Value) continue;
16993 if (!cns.val.has_value()) continue;
16994 if (cns.val->m_type != KindOfUninit) continue;
16995 if (i >= cinfo->clsConstTypes.size()) {
16996 cinfo->clsConstTypes.resize(i+1, ClsConstInfo { TInitCell, 0 });
16998 cinfo->clsConstTypes[i] = folly::get_default(
16999 rcinfo->clsConstantInfo,
17000 cns.name,
17001 ClsConstInfo { TInitCell, 0 }
17004 cinfo->clsConstTypes.shrink_to_fit();
17007 std::vector<std::pair<SString, MethTabEntry>> methods;
17008 methods.reserve(cinfo->methods.size());
17009 for (auto const& [name, mte] : rcinfo->methods) {
17010 if (!(mte.attrs & AttrNoOverride)) {
17011 attribute_setter(
17012 func_from_meth_ref(index, mte.meth())->attrs,
17013 false,
17014 AttrNoOverride
17017 methods.emplace_back(name, mte);
17019 std::sort(
17020 begin(methods), end(methods),
17021 [] (auto const& p1, auto const& p2) { return p1.first < p2.first; }
17023 cinfo->methods.insert(
17024 folly::sorted_unique, begin(methods), end(methods)
17026 cinfo->methods.shrink_to_fit();
17029 cinfo->hasBadRedeclareProp = rcinfo->hasBadRedeclareProp;
17030 cinfo->hasBadInitialPropValues = rcinfo->hasBadInitialPropValues;
17031 cinfo->hasConstProp = rcinfo->hasConstProp;
17032 cinfo->hasReifiedParent = rcinfo->hasReifiedParent;
17033 cinfo->subHasConstProp = rcinfo->subHasConstProp;
17034 cinfo->isMocked = rcinfo->isMocked;
17035 cinfo->isSubMocked = rcinfo->isSubMocked;
17037 cinfo->classGraph = rcinfo->classGraph;
17038 cinfo->classGraph.setCInfo(*cinfo);
17040 auto const noOverride = [&] (SString name) {
17041 if (auto const mte = folly::get_ptr(cinfo->methods, name)) {
17042 return bool(mte->attrs & AttrNoOverride);
17044 return true;
17047 auto const noOverrideRegular = [&] (SString name) {
17048 if (auto const mte = folly::get_ptr(cinfo->methods, name)) {
17049 return mte->noOverrideRegular();
17051 return true;
17054 std::vector<std::pair<SString, FuncFamilyOrSingle>> entries;
17055 std::vector<std::pair<SString, FuncFamilyOrSingle>> aux;
17056 for (auto const& [name, entry] : rcinfo->methodFamilies) {
17057 assertx(!is_special_method_name(name));
17059 auto expanded = false;
17060 if (!cinfo->methods.count(name)) {
17061 if (!(cinfo->cls->attrs & (AttrAbstract|AttrInterface))) continue;
17062 if (!cinfo->classGraph.mightHaveRegularSubclass()) continue;
17063 if (entry.m_regularIncomplete || entry.m_privateAncestor) continue;
17064 if (name == s_construct.get()) continue;
17065 expanded = true;
17066 } else if (noOverride(name)) {
17067 continue;
17070 match<void>(
17071 entry.m_meths,
17072 [&, name=name, &entry=entry] (const FuncFamilyEntry::BothFF& e) {
17073 auto const it = ffState.find(e.m_ff);
17074 assertx(it != end(ffState));
17075 auto const& state = it->second;
17077 if (expanded) {
17078 if (state->m_ff->m_regular.empty()) return;
17079 if (state->m_ff->m_regular.size() == 1) {
17080 entries.emplace_back(
17081 name,
17082 FuncFamilyOrSingle{
17083 func_from_meth_ref(index, state->m_ff->m_regular[0]),
17084 false
17087 return;
17089 entries.emplace_back(
17090 name,
17091 FuncFamilyOrSingle{
17092 state->expanded(index),
17093 false
17096 return;
17099 entries.emplace_back(
17100 name,
17101 FuncFamilyOrSingle{
17102 state->notExpanded(index),
17103 entry.m_allIncomplete
17107 [&, name=name, &entry=entry] (const FuncFamilyEntry::FFAndSingle& e) {
17108 if (expanded) {
17109 if (e.m_nonRegularPrivate) return;
17110 entries.emplace_back(
17111 name,
17112 FuncFamilyOrSingle{
17113 func_from_meth_ref(index, e.m_regular),
17114 false
17117 return;
17120 auto const it = ffState.find(e.m_ff);
17121 assertx(it != end(ffState));
17123 entries.emplace_back(
17124 name,
17125 FuncFamilyOrSingle{
17126 it->second->notExpanded(index),
17127 entry.m_allIncomplete
17130 if (noOverrideRegular(name)) return;
17131 aux.emplace_back(
17132 name,
17133 FuncFamilyOrSingle{
17134 func_from_meth_ref(index, e.m_regular),
17135 false
17139 [&, name=name, &entry=entry] (const FuncFamilyEntry::FFAndNone& e) {
17140 if (expanded) return;
17141 auto const it = ffState.find(e.m_ff);
17142 assertx(it != end(ffState));
17144 entries.emplace_back(
17145 name,
17146 FuncFamilyOrSingle{
17147 it->second->notExpanded(index),
17148 entry.m_allIncomplete
17151 if (!noOverrideRegular(name)) {
17152 aux.emplace_back(name, FuncFamilyOrSingle{});
17155 [&, name=name, &entry=entry] (const FuncFamilyEntry::BothSingle& e) {
17156 if (expanded && e.m_nonRegularPrivate) return;
17157 entries.emplace_back(
17158 name,
17159 FuncFamilyOrSingle{
17160 func_from_meth_ref(index, e.m_all),
17161 !expanded && entry.m_allIncomplete
17165 [&, name=name, &entry=entry] (const FuncFamilyEntry::SingleAndNone& e) {
17166 if (expanded) return;
17167 entries.emplace_back(
17168 name,
17169 FuncFamilyOrSingle{
17170 func_from_meth_ref(index, e.m_all),
17171 entry.m_allIncomplete
17174 if (!noOverrideRegular(name)) {
17175 aux.emplace_back(name, FuncFamilyOrSingle{});
17178 [&, &entry=entry] (const FuncFamilyEntry::None&) {
17179 assertx(entry.m_allIncomplete);
17184 // Sort the lists of new entries, so we can insert them into the
17185 // method family maps (which are sorted_vector_maps) in bulk.
17186 std::sort(
17187 begin(entries), end(entries),
17188 [] (auto const& p1, auto const& p2) { return p1.first < p2.first; }
17190 std::sort(
17191 begin(aux), end(aux),
17192 [] (auto const& p1, auto const& p2) { return p1.first < p2.first; }
17194 if (!entries.empty()) {
17195 cinfo->methodFamilies.insert(
17196 folly::sorted_unique, begin(entries), end(entries)
17199 if (!aux.empty()) {
17200 cinfo->methodFamiliesAux.insert(
17201 folly::sorted_unique, begin(aux), end(aux)
17204 cinfo->methodFamilies.shrink_to_fit();
17205 cinfo->methodFamiliesAux.shrink_to_fit();
17207 if (!rcinfo->extraMethods.empty()) {
17208 // This is rare. Only happens with unflattened traits, so
17209 // taking a lock here is fine.
17210 std::lock_guard<std::mutex> _{extraMethodLock};
17211 auto& extra = index.classExtraMethodMap[cinfo->cls];
17212 for (auto const& meth : rcinfo->extraMethods) {
17213 extra.emplace(func_from_meth_ref(index, meth));
17217 // Build the FuncInfo for every method on this class. The
17218 // FuncInfos already have default types, so update them with the
17219 // type from the FuncInfo2. Any class types here will be
17220 // unresolved (will be resolved later).
17221 assertx(cinfo->cls->methods.size() == rcinfo->funcInfos.size());
17222 for (size_t i = 0, size = cinfo->cls->methods.size(); i < size; ++i) {
17223 auto& func = cinfo->cls->methods[i];
17224 auto& rfi = rcinfo->funcInfos[i];
17225 remote_func_info_to_local(index, *func, *rfi);
17229 for (auto& clo : rcinfos->closures) process(std::move(clo));
17230 process(std::move(rcinfos));
17234 remote.clear();
17235 remote.shrink_to_fit();
17237 for (auto const& [name, entry] : index.nameOnlyMethodFamilies) {
17238 match<void>(
17239 entry.m_meths,
17240 [&, name=name] (const FuncFamilyEntry::BothFF& e) {
17241 auto const it = ffState.find(e.m_ff);
17242 assertx(it != end(ffState));
17244 FuncFamilyOrSingle f{ it->second->notExpanded(index), true };
17245 index.methodFamilies.emplace(
17246 name,
17247 IndexData::MethodFamilyEntry { f, f }
17250 [&, name=name] (const FuncFamilyEntry::FFAndSingle& e) {
17251 auto const it = ffState.find(e.m_ff);
17252 assertx(it != end(ffState));
17254 index.methodFamilies.emplace(
17255 name,
17256 IndexData::MethodFamilyEntry {
17257 FuncFamilyOrSingle {
17258 it->second->notExpanded(index),
17259 true
17261 FuncFamilyOrSingle {
17262 func_from_meth_ref(index, e.m_regular),
17263 true
17268 [&, name=name] (const FuncFamilyEntry::FFAndNone& e) {
17269 auto const it = ffState.find(e.m_ff);
17270 assertx(it != end(ffState));
17272 index.methodFamilies.emplace(
17273 name,
17274 IndexData::MethodFamilyEntry {
17275 FuncFamilyOrSingle {
17276 it->second->notExpanded(index),
17277 true
17279 FuncFamilyOrSingle {}
17283 [&, name=name] (const FuncFamilyEntry::BothSingle& e) {
17284 FuncFamilyOrSingle f{ func_from_meth_ref(index, e.m_all), true };
17285 index.methodFamilies.emplace(
17286 name,
17287 IndexData::MethodFamilyEntry { f, f }
17290 [&, name=name] (const FuncFamilyEntry::SingleAndNone& e) {
17291 index.methodFamilies.emplace(
17292 name,
17293 IndexData::MethodFamilyEntry {
17294 FuncFamilyOrSingle {
17295 func_from_meth_ref(index, e.m_all),
17296 true
17298 FuncFamilyOrSingle {}
17302 [&] (const FuncFamilyEntry::None&) { always_assert(false); }
17306 ffState.clear();
17307 decltype(index.nameOnlyMethodFamilies){}.swap(index.nameOnlyMethodFamilies);
17309 // Now that all of the FuncFamilies have been created, generate the
17310 // back links from FuncInfo to their FuncFamilies.
17311 std::vector<FuncFamily*> work;
17312 work.reserve(index.funcFamilies.size());
17313 for (auto const& kv : index.funcFamilies) work.emplace_back(kv.first.get());
17315 // First calculate the needed capacity for each FuncInfo's family
17316 // list. We use this to presize the family list. This is superior
17317 // just pushing back and then shrinking the vectors, as that can
17318 // excessively fragment the heap.
17319 std::vector<std::atomic<size_t>> capacities(index.nextFuncId);
17321 parallel::for_each(
17322 work,
17323 [&] (FuncFamily* ff) {
17324 for (auto const pf : ff->possibleFuncs()) {
17325 ++capacities[pf.ptr()->idx];
17330 parallel::for_each(
17331 index.funcInfo,
17332 [&] (FuncInfo& fi) {
17333 if (!fi.func) return;
17334 fi.families.reserve(capacities[fi.func->idx]);
17337 capacities.clear();
17338 capacities.shrink_to_fit();
17340 // Different threads can touch the same FuncInfo when adding to the
17341 // func family list, so use sharded locking scheme.
17342 std::array<std::mutex, 256> locks;
17343 parallel::for_each(
17344 work,
17345 [&] (FuncFamily* ff) {
17346 for (auto const pf : ff->possibleFuncs()) {
17347 auto finfo = func_info(index, pf.ptr());
17348 auto& lock = locks[pointer_hash<FuncInfo>{}(finfo) % locks.size()];
17349 std::lock_guard<std::mutex> _{lock};
17350 finfo->families.emplace_back(ff);
17356 // Switch to "local" mode, in which all calculations are expected to
17357 // be done locally (not using extern-worker). This involves
17358 // downloading everything out of extern-worker and converting it. To
17359 // improve efficiency, we first aggregate many small(er) items into
17360 // larger aggregate blobs in external workers, then download the
17361 // larger blobs.
17362 void make_local(IndexData& index) {
17363 trace_time tracer("make local", index.sample);
17365 using namespace folly::gen;
17367 // These aren't needed below so we can free them immediately.
17368 decltype(index.funcToClosures){}.swap(index.funcToClosures);
17369 decltype(index.classToClosures){}.swap(index.classToClosures);
17370 decltype(index.classesWith86Inits){}.swap(index.classesWith86Inits);
17371 decltype(index.classToUnit){}.swap(index.classToUnit);
17372 decltype(index.funcToUnit){}.swap(index.funcToUnit);
17373 decltype(index.constantToUnit){}.swap(index.constantToUnit);
17374 decltype(index.constantInitFuncs){}.swap(index.constantInitFuncs);
17375 decltype(index.unitsWithTypeAliases){}.swap(index.unitsWithTypeAliases);
17376 decltype(index.closureToFunc){}.swap(index.closureToFunc);
17377 decltype(index.closureToClass){}.swap(index.closureToClass);
17378 decltype(index.classToCnsBases){}.swap(index.classToCnsBases);
17380 // Unlike other cases, we want to bound each bucket to roughly the
17381 // same total byte size (since ultimately we're going to download
17382 // everything).
17383 auto const usingSubprocess = index.client->usingSubprocess();
17384 // We can be more aggressive in subprocess mode because there's no
17385 // actual aggregation.
17386 auto const sizePerBucket = usingSubprocess
17387 ? 256*1024*1024
17388 : 128*1024*1024;
17391 * We'll use the names of the various items as the items to
17392 * bucketize. This is somewhat problematic because names between
17393 * units/funcs/classes/class-infos can collide (indeed classes and
17394 * class-infos will always collide). Adding to the complication is
17395 * that some of these are case sensitive and some aren't.
17397 * We'll store a case sensitive version of each name exactly *once*,
17398 * using a seen set. Since the hash for a static string (which
17399 * consistently_bucketize() uses) is case insensitive, all case
17400 * sensitive versions of the same name will always hash to the same
17401 * bucket.
17403 * When we obtain the Refs for a corresponding bucket, we'll load
17404 * all items with that given name, using a set to ensure each RefId
17405 * is only used once.
17408 SStringToOneT<Ref<FuncFamilyGroup>> nameToFuncFamilyGroup;
17409 auto const& [items, totalSize] = [&] {
17410 SStringSet seen;
17411 std::vector<SString> items;
17412 size_t totalSize = 0;
17413 items.reserve(
17414 index.unitRefs.size() + index.classRefs.size() +
17415 index.classInfoRefs.size() + index.funcRefs.size() +
17416 index.funcInfoRefs.size() + index.funcFamilyRefs.size() +
17417 index.uninstantiableClsMethRefs.size()
17419 for (auto const& [name, ref] : index.unitRefs) {
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.classRefs) {
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.classInfoRefs) {
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.classBytecodeRefs) {
17435 totalSize += ref.id().m_size;
17436 if (!seen.emplace(name).second) continue;
17437 items.emplace_back(name);
17439 for (auto const& [name, ref] : index.funcRefs) {
17440 totalSize += ref.id().m_size;
17441 if (!seen.emplace(name).second) continue;
17442 items.emplace_back(name);
17444 for (auto const& [name, ref] : index.funcInfoRefs) {
17445 totalSize += ref.id().m_size;
17446 if (!seen.emplace(name).second) continue;
17447 items.emplace_back(name);
17449 for (auto const& [name, ref] : index.funcBytecodeRefs) {
17450 totalSize += ref.id().m_size;
17451 if (!seen.emplace(name).second) continue;
17452 items.emplace_back(name);
17454 for (auto const& [name, ref] : index.uninstantiableClsMethRefs) {
17455 totalSize += ref.id().m_size;
17456 if (!seen.emplace(name).second) continue;
17457 items.emplace_back(name);
17460 for (auto const& [_, ref] : index.funcFamilyRefs) {
17461 auto const name = makeStaticString(ref.id().toString());
17462 nameToFuncFamilyGroup.emplace(name, ref);
17464 for (auto const& [name, ref] : nameToFuncFamilyGroup) {
17465 totalSize += ref.id().m_size;
17466 if (!seen.emplace(name).second) continue;
17467 items.emplace_back(name);
17469 std::sort(begin(items), end(items), string_data_lt{});
17470 return std::make_pair(items, totalSize);
17471 }();
17473 // Back out the number of buckets we want from the total size of
17474 // everything and the target size of a bucket.
17475 auto const numBuckets = (totalSize + sizePerBucket - 1) / sizePerBucket;
17476 auto buckets = consistently_bucketize(
17477 items,
17478 (items.size() + numBuckets - 1) / numBuckets
17481 // We're going to be downloading all bytecode, so to avoid wasted
17482 // memory, try to re-use identical bytecode.
17483 Optional<php::FuncBytecode::Reuser> reuser;
17484 reuser.emplace();
17485 php::FuncBytecode::s_reuser = reuser.get_pointer();
17486 SCOPE_EXIT { php::FuncBytecode::s_reuser = nullptr; };
17488 std::mutex lock;
17489 auto program = std::make_unique<php::Program>();
17491 // Index stores ClassInfos, not ClassInfo2s, so we need a place to
17492 // store them until we convert it.
17493 std::vector<std::unique_ptr<ClassInfo2>> remoteClassInfos;
17494 remoteClassInfos.reserve(index.classInfoRefs.size());
17496 std::vector<std::unique_ptr<FuncInfo2>> remoteFuncInfos;
17497 remoteFuncInfos.reserve(index.funcInfoRefs.size());
17499 std::vector<std::unique_ptr<MethodsWithoutCInfo>> remoteMethInfos;
17500 remoteMethInfos.reserve(index.uninstantiableClsMethRefs.size());
17502 hphp_fast_set<FuncFamily2::Id> remoteFuncFamilyIds;
17503 std::vector<std::unique_ptr<FuncFamily2>> remoteFuncFamilies;
17504 remoteFuncFamilies.reserve(index.funcFamilyRefs.size());
17506 auto const run = [&] (std::vector<SString> chunks) -> coro::Task<void> {
17507 co_await coro::co_reschedule_on_current_executor;
17509 if (chunks.empty()) co_return;
17511 std::vector<UniquePtrRef<php::Class>> classes;
17512 std::vector<UniquePtrRef<ClassInfo2>> classInfos;
17513 std::vector<UniquePtrRef<php::ClassBytecode>> classBytecode;
17514 std::vector<UniquePtrRef<php::Func>> funcs;
17515 std::vector<UniquePtrRef<FuncInfo2>> funcInfos;
17516 std::vector<UniquePtrRef<php::FuncBytecode>> funcBytecode;
17517 std::vector<UniquePtrRef<php::Unit>> units;
17518 std::vector<Ref<FuncFamilyGroup>> funcFamilies;
17519 std::vector<UniquePtrRef<MethodsWithoutCInfo>> methInfos;
17521 // Since a name can map to multiple items, and different case
17522 // sensitive version of the same name can appear in the same
17523 // bucket, use a set to make sure any given RefId only included
17524 // once. A set of case insensitive identical names will always
17525 // appear in the same bucket, so there's no need to track
17526 // duplicates globally.
17527 hphp_fast_set<RefId, RefId::Hasher> ids;
17529 for (auto const name : chunks) {
17530 if (auto const r = folly::get_optional(index.unitRefs, name)) {
17531 if (ids.emplace(r->id()).second) {
17532 units.emplace_back(*r);
17535 if (auto const r = folly::get_optional(index.classRefs, name)) {
17536 auto const bytecode = index.classBytecodeRefs.at(name);
17537 if (ids.emplace(r->id()).second) {
17538 classes.emplace_back(*r);
17539 classBytecode.emplace_back(bytecode);
17542 if (auto const r = folly::get_optional(index.classInfoRefs, name)) {
17543 if (ids.emplace(r->id()).second) {
17544 classInfos.emplace_back(*r);
17547 if (auto const r = folly::get_optional(index.funcRefs, name)) {
17548 auto const bytecode = index.funcBytecodeRefs.at(name);
17549 if (ids.emplace(r->id()).second) {
17550 funcs.emplace_back(*r);
17551 funcBytecode.emplace_back(bytecode);
17554 if (auto const r = folly::get_optional(index.funcInfoRefs, name)) {
17555 if (ids.emplace(r->id()).second) {
17556 funcInfos.emplace_back(*r);
17559 if (auto const r = folly::get_optional(nameToFuncFamilyGroup, name)) {
17560 if (ids.emplace(r->id()).second) {
17561 funcFamilies.emplace_back(*r);
17564 if (auto const r = folly::get_optional(index.uninstantiableClsMethRefs,
17565 name)) {
17566 if (ids.emplace(r->id()).second) {
17567 methInfos.emplace_back(*r);
17572 AggregateJob::Bundle chunk;
17573 if (!usingSubprocess) {
17574 Client::ExecMetadata metadata{
17575 .job_key = folly::sformat("aggregate {}", chunks[0])
17578 // Aggregate the data, which makes downloading it more efficient.
17579 auto config = co_await index.configRef->getCopy();
17580 auto outputs = co_await index.client->exec(
17581 s_aggregateJob,
17582 std::move(config),
17583 singleton_vec(
17584 std::make_tuple(
17585 std::move(classes),
17586 std::move(classInfos),
17587 std::move(classBytecode),
17588 std::move(funcs),
17589 std::move(funcInfos),
17590 std::move(funcBytecode),
17591 std::move(units),
17592 std::move(funcFamilies),
17593 std::move(methInfos)
17596 std::move(metadata)
17598 assertx(outputs.size() == 1);
17600 // Download the aggregate chunks.
17601 chunk = co_await index.client->load(std::move(outputs[0]));
17602 } else {
17603 // If we're using subprocess mode, we don't need to aggregate
17604 // and we can just download the items directly.
17605 auto [c, cinfo, cbc, f, finfo, fbc, u, ff, minfo] =
17606 co_await coro::collectAll(
17607 index.client->load(std::move(classes)),
17608 index.client->load(std::move(classInfos)),
17609 index.client->load(std::move(classBytecode)),
17610 index.client->load(std::move(funcs)),
17611 index.client->load(std::move(funcInfos)),
17612 index.client->load(std::move(funcBytecode)),
17613 index.client->load(std::move(units)),
17614 index.client->load(std::move(funcFamilies)),
17615 index.client->load(std::move(methInfos))
17617 chunk.classes.insert(
17618 end(chunk.classes),
17619 std::make_move_iterator(begin(c)),
17620 std::make_move_iterator(end(c))
17622 chunk.classInfos.insert(
17623 end(chunk.classInfos),
17624 std::make_move_iterator(begin(cinfo)),
17625 std::make_move_iterator(end(cinfo))
17627 chunk.classBytecode.insert(
17628 end(chunk.classBytecode),
17629 std::make_move_iterator(begin(cbc)),
17630 std::make_move_iterator(end(cbc))
17632 chunk.funcs.insert(
17633 end(chunk.funcs),
17634 std::make_move_iterator(begin(f)),
17635 std::make_move_iterator(end(f))
17637 chunk.funcInfos.insert(
17638 end(chunk.funcInfos),
17639 std::make_move_iterator(begin(finfo)),
17640 std::make_move_iterator(end(finfo))
17642 chunk.funcBytecode.insert(
17643 end(chunk.funcBytecode),
17644 std::make_move_iterator(begin(fbc)),
17645 std::make_move_iterator(end(fbc))
17647 chunk.units.insert(
17648 end(chunk.units),
17649 std::make_move_iterator(begin(u)),
17650 std::make_move_iterator(end(u))
17652 chunk.funcFamilies.insert(
17653 end(chunk.funcFamilies),
17654 std::make_move_iterator(begin(ff)),
17655 std::make_move_iterator(end(ff))
17657 chunk.methInfos.insert(
17658 end(chunk.methInfos),
17659 std::make_move_iterator(begin(minfo)),
17660 std::make_move_iterator(end(minfo))
17664 always_assert(chunk.classBytecode.size() == chunk.classes.size());
17665 for (size_t i = 0, size = chunk.classes.size(); i < size; ++i) {
17666 auto& bc = chunk.classBytecode[i];
17667 auto& cls = chunk.classes[i];
17669 size_t bcIdx = 0;
17670 for (auto& meth : cls->methods) {
17671 assertx(bcIdx < bc->methodBCs.size());
17672 auto& methBC = bc->methodBCs[bcIdx++];
17673 always_assert(methBC.name == meth->name);
17674 always_assert(!meth->rawBlocks);
17675 meth->rawBlocks = std::move(methBC.bc);
17677 for (auto& clo : cls->closures) {
17678 assertx(bcIdx < bc->methodBCs.size());
17679 auto& methBC = bc->methodBCs[bcIdx++];
17680 assertx(clo->methods.size() == 1);
17681 always_assert(methBC.name == clo->methods[0]->name);
17682 always_assert(!clo->methods[0]->rawBlocks);
17683 clo->methods[0]->rawBlocks = std::move(methBC.bc);
17685 assertx(bcIdx == bc->methodBCs.size());
17687 chunk.classBytecode.clear();
17689 always_assert(chunk.funcBytecode.size() == chunk.funcs.size());
17690 for (size_t i = 0, size = chunk.funcs.size(); i < size; ++i) {
17691 auto& bytecode = chunk.funcBytecode[i];
17692 chunk.funcs[i]->rawBlocks = std::move(bytecode->bc);
17694 chunk.funcBytecode.clear();
17696 // And add it to our php::Program.
17698 std::scoped_lock<std::mutex> _{lock};
17699 for (auto& unit : chunk.units) {
17700 // Local execution doesn't need the native unit, so strip it
17701 // out.
17702 if (is_native_unit(*unit)) continue;
17703 program->units.emplace_back(std::move(unit));
17705 for (auto& cls : chunk.classes) {
17706 program->classes.emplace_back(std::move(cls));
17708 for (auto& func : chunk.funcs) {
17709 program->funcs.emplace_back(std::move(func));
17711 remoteClassInfos.insert(
17712 end(remoteClassInfos),
17713 std::make_move_iterator(begin(chunk.classInfos)),
17714 std::make_move_iterator(end(chunk.classInfos))
17716 remoteFuncInfos.insert(
17717 end(remoteFuncInfos),
17718 std::make_move_iterator(begin(chunk.funcInfos)),
17719 std::make_move_iterator(end(chunk.funcInfos))
17721 remoteMethInfos.insert(
17722 end(remoteMethInfos),
17723 std::make_move_iterator(begin(chunk.methInfos)),
17724 std::make_move_iterator(end(chunk.methInfos))
17726 for (auto& group : chunk.funcFamilies) {
17727 for (auto& ff : group.m_ffs) {
17728 if (remoteFuncFamilyIds.emplace(ff->m_id).second) {
17729 remoteFuncFamilies.emplace_back(std::move(ff));
17735 co_return;
17738 // We're going to load ClassGraphs concurrently.
17739 ClassGraph::initConcurrent();
17742 // Temporarily suppress case collision logging
17743 auto oldTypeLogLevel = Cfg::Eval::LogTsameCollisions;
17744 Cfg::Eval::LogTsameCollisions = 0;
17745 SCOPE_EXIT {
17746 Cfg::Eval::LogTsameCollisions = oldTypeLogLevel;
17749 coro::blockingWait(coro::collectAllRange(
17750 from(buckets)
17751 | move
17752 | map([&] (std::vector<SString> chunks) {
17753 return run(std::move(chunks)).scheduleOn(index.executor->sticky());
17755 | as<std::vector>()
17759 // Deserialization done.
17760 ClassGraph::stopConcurrent();
17762 // We've used any refs we need. Free them now to save memory.
17763 decltype(index.unitRefs){}.swap(index.unitRefs);
17764 decltype(index.classRefs){}.swap(index.classRefs);
17765 decltype(index.funcRefs){}.swap(index.funcRefs);
17766 decltype(index.classInfoRefs){}.swap(index.classInfoRefs);
17767 decltype(index.funcInfoRefs){}.swap(index.funcInfoRefs);
17768 decltype(index.funcFamilyRefs){}.swap(index.funcFamilyRefs);
17769 decltype(index.classBytecodeRefs){}.swap(index.classBytecodeRefs);
17770 decltype(index.funcBytecodeRefs){}.swap(index.funcBytecodeRefs);
17771 decltype(index.uninstantiableClsMethRefs){}.swap(
17772 index.uninstantiableClsMethRefs
17775 // Done with any extern-worker stuff at this point:
17776 index.configRef.reset();
17778 Logger::FInfo(
17779 "{}",
17780 index.client->getStats().toString(
17781 "hhbbc",
17782 folly::sformat(
17783 "{:,} units, {:,} classes, {:,} class-infos, {:,} funcs",
17784 program->units.size(),
17785 program->classes.size(),
17786 remoteClassInfos.size(),
17787 program->funcs.size()
17792 if (index.sample) {
17793 index.client->getStats().logSample("hhbbc", *index.sample);
17796 index.disposeClient(
17797 std::move(index.executor),
17798 std::move(index.client)
17800 index.disposeClient = decltype(index.disposeClient){};
17802 php::FuncBytecode::s_reuser = nullptr;
17803 reuser.reset();
17805 buckets.clear();
17806 nameToFuncFamilyGroup.clear();
17807 remoteFuncFamilyIds.clear();
17809 program->units.shrink_to_fit();
17810 program->classes.shrink_to_fit();
17811 program->funcs.shrink_to_fit();
17812 index.program = std::move(program);
17814 // For now we don't require system constants in any extern-worker
17815 // stuff we do. So we can just add it to the Index now.
17816 add_system_constants_to_index(index);
17818 // Buid Index data structures from the php::Program.
17819 add_program_to_index(index);
17821 // Convert the FuncInfo2s into FuncInfos.
17822 make_func_infos_local(
17823 index,
17824 std::move(remoteFuncInfos)
17827 // Convert the ClassInfo2s into ClassInfos.
17828 make_class_infos_local(
17829 index,
17830 std::move(remoteClassInfos),
17831 std::move(remoteFuncFamilies)
17834 // Convert any "orphan" FuncInfo2s (those representing methods for a
17835 // class without a ClassInfo).
17836 parallel::for_each(
17837 remoteMethInfos,
17838 [&] (std::unique_ptr<MethodsWithoutCInfo>& meths) {
17839 auto const cls = folly::get_default(index.classes, meths->cls);
17840 always_assert_flog(
17841 cls,
17842 "php::Class for {} not found in index",
17843 meths->cls
17845 assertx(cls->methods.size() == meths->finfos.size());
17846 for (size_t i = 0, size = cls->methods.size(); i < size; ++i) {
17847 remote_func_info_to_local(index, *cls->methods[i], *meths->finfos[i]);
17850 assertx(cls->closures.size() == meths->closureInvokes.size());
17851 for (size_t i = 0, size = cls->closures.size(); i < size; ++i) {
17852 auto const& clo = cls->closures[i];
17853 assertx(clo->methods.size() == 1);
17854 remote_func_info_to_local(
17855 index,
17856 *clo->methods[0],
17857 *meths->closureInvokes[i]
17863 // Ensure that all classes are unserialized since all local
17864 // processing requires that.
17866 trace_time tracer2{"unserialize classes"};
17867 tracer2.ignore_client_stats();
17869 parallel::for_each(
17870 index.allClassInfos,
17871 [&] (std::unique_ptr<ClassInfo>& cinfo) {
17872 for (auto& [ty, _] : cinfo->clsConstTypes) {
17873 ty = unserialize_classes(
17874 IndexAdaptor { *index.m_index },
17875 std::move(ty)
17881 parallel::for_each(
17882 index.funcInfo,
17883 [&] (FuncInfo& fi) {
17884 if (!fi.func) return;
17885 fi.returnTy = unserialize_classes(
17886 IndexAdaptor { *index.m_index },
17887 std::move(fi.returnTy)
17893 //////////////////////////////////////////////////////////////////////
17897 //////////////////////////////////////////////////////////////////////
17899 std::vector<SString> Index::Input::makeDeps(const php::Class& cls) {
17900 std::vector<SString> deps;
17901 if (cls.parentName) deps.emplace_back(cls.parentName);
17902 deps.insert(deps.end(), cls.interfaceNames.begin(), cls.interfaceNames.end());
17903 deps.insert(deps.end(), cls.usedTraitNames.begin(), cls.usedTraitNames.end());
17904 deps.insert(
17905 deps.end(),
17906 cls.includedEnumNames.begin(),
17907 cls.includedEnumNames.end()
17909 return deps;
17912 //////////////////////////////////////////////////////////////////////
17914 Index::Index(Input input,
17915 Config config,
17916 std::unique_ptr<TicketExecutor> executor,
17917 std::unique_ptr<Client> client,
17918 DisposeCallback dispose,
17919 StructuredLogEntry* sample)
17920 : m_data{std::make_unique<IndexData>(this)}
17922 trace_time tracer("create index", sample);
17923 m_data->sample = sample;
17925 auto flattenMeta = make_remote(
17926 *m_data,
17927 std::move(config),
17928 std::move(input),
17929 std::move(executor),
17930 std::move(client),
17931 std::move(dispose)
17934 flatten_type_mappings(*m_data, flattenMeta);
17935 auto [subclassMeta, initTypesMeta, ifaceConflicts] =
17936 flatten_classes(*m_data, std::move(flattenMeta));
17937 build_subclass_lists(*m_data, std::move(subclassMeta), initTypesMeta);
17938 init_types(*m_data, std::move(initTypesMeta));
17939 compute_iface_vtables(*m_data, std::move(ifaceConflicts));
17940 check_invariants(*m_data);
17943 // Defined here so IndexData is a complete type for the unique_ptr
17944 // destructor.
17945 Index::~Index() = default;
17947 Index::Index(Index&&) = default;
17948 Index& Index::operator=(Index&&) = default;
17950 //////////////////////////////////////////////////////////////////////
17952 const php::Program& Index::program() const {
17953 return *m_data->program;
17956 StructuredLogEntry* Index::sample() const {
17957 return m_data->sample;
17960 //////////////////////////////////////////////////////////////////////
17962 TicketExecutor& Index::executor() const {
17963 return *m_data->executor;
17966 Client& Index::client() const {
17967 return *m_data->client;
17970 const CoroAsyncValue<Ref<Config>>& Index::configRef() const {
17971 return *m_data->configRef;
17974 //////////////////////////////////////////////////////////////////////
17976 const TSStringSet& Index::classes_with_86inits() const {
17977 return m_data->classesWith86Inits;
17980 const FSStringSet& Index::constant_init_funcs() const {
17981 return m_data->constantInitFuncs;
17984 const SStringSet& Index::units_with_type_aliases() const {
17985 return m_data->unitsWithTypeAliases;
17988 //////////////////////////////////////////////////////////////////////
17990 void Index::make_local() {
17991 HHBBC::make_local(*m_data);
17992 check_local_invariants(*m_data);
17995 //////////////////////////////////////////////////////////////////////
17997 const php::Class* Index::lookup_closure_context(const php::Class& cls) const {
17998 if (!cls.closureContextCls) return &cls;
17999 return m_data->classes.at(cls.closureContextCls);
18002 const php::Unit* Index::lookup_func_unit(const php::Func& func) const {
18003 return m_data->units.at(func.unit);
18006 const php::Unit* Index::lookup_func_original_unit(const php::Func& func) const {
18007 auto const unit = func.originalUnit ? func.originalUnit : func.unit;
18008 return m_data->units.at(unit);
18011 const php::Unit* Index::lookup_class_unit(const php::Class& cls) const {
18012 return m_data->units.at(cls.unit);
18015 const php::Class* Index::lookup_const_class(const php::Const& cns) const {
18016 return m_data->classes.at(cns.cls);
18019 const php::Class* Index::lookup_class(SString name) const {
18020 return folly::get_default(m_data->classes, name);
18023 //////////////////////////////////////////////////////////////////////
18025 void Index::for_each_unit_func(const php::Unit& unit,
18026 std::function<void(const php::Func&)> f) const {
18027 for (auto const func : unit.funcs) {
18028 f(*m_data->funcs.at(func));
18032 void Index::for_each_unit_func_mutable(php::Unit& unit,
18033 std::function<void(php::Func&)> f) {
18034 for (auto const func : unit.funcs) {
18035 f(*m_data->funcs.at(func));
18039 void Index::for_each_unit_class(
18040 const php::Unit& unit,
18041 std::function<void(const php::Class&)> f) const {
18042 for (auto const cls : unit.classes) {
18043 f(*m_data->classes.at(cls));
18047 void Index::for_each_unit_class_mutable(php::Unit& unit,
18048 std::function<void(php::Class&)> f) {
18049 for (auto const cls : unit.classes) {
18050 f(*m_data->classes.at(cls));
18054 //////////////////////////////////////////////////////////////////////
18056 void Index::preresolve_type_structures() {
18057 trace_time tracer("pre-resolve type-structures", m_data->sample);
18059 // Now that everything has been updated, calculate the invariance
18060 // for each resolved type-structure. For each class constant,
18061 // examine all subclasses and see how the resolved type-structure
18062 // changes.
18063 parallel::for_each(
18064 m_data->allClassInfos,
18065 [&] (std::unique_ptr<ClassInfo>& cinfo) {
18066 if (!cinfo->classGraph.hasCompleteChildren()) return;
18068 for (auto& cns : const_cast<php::Class*>(cinfo->cls)->constants) {
18069 assertx(cns.invariance == php::Const::Invariance::None);
18070 if (cns.kind != ConstModifiers::Kind::Type) continue;
18071 if (!cns.val.has_value()) continue;
18072 if (!cns.resolvedTypeStructure) continue;
18074 auto const checkClassname =
18075 tvIsString(cns.resolvedTypeStructure->get(s_classname));
18077 // Assume it doesn't change
18078 auto invariance = php::Const::Invariance::Same;
18079 for (auto const g : cinfo->classGraph.children()) {
18080 assertx(!g.isMissing());
18081 assertx(g.hasCompleteChildren());
18082 auto const s = g.cinfo();
18083 assertx(s);
18084 assertx(invariance != php::Const::Invariance::None);
18085 assertx(
18086 IMPLIES(!checkClassname,
18087 invariance != php::Const::Invariance::ClassnamePresent)
18089 if (s == cinfo.get()) continue;
18091 auto const it = s->clsConstants.find(cns.name);
18092 assertx(it != s->clsConstants.end());
18093 if (it->second.cls != s->cls) continue;
18094 auto const& scns = *it->second;
18096 // Overridden in some strange way. Be pessimistic.
18097 if (!scns.val.has_value() ||
18098 scns.kind != ConstModifiers::Kind::Type) {
18099 invariance = php::Const::Invariance::None;
18100 break;
18103 // The resolved type structure in this subclass is not the
18104 // same.
18105 if (scns.resolvedTypeStructure != cns.resolvedTypeStructure) {
18106 if (!scns.resolvedTypeStructure) {
18107 // It's not even resolved here, so we can't assume
18108 // anything.
18109 invariance = php::Const::Invariance::None;
18110 break;
18112 // We might still be able to assert that a classname is
18113 // always present, or a resolved type structure at least
18114 // exists.
18115 if (invariance == php::Const::Invariance::Same ||
18116 invariance == php::Const::Invariance::ClassnamePresent) {
18117 invariance =
18118 (checkClassname &&
18119 tvIsString(scns.resolvedTypeStructure->get(s_classname)))
18120 ? php::Const::Invariance::ClassnamePresent
18121 : php::Const::Invariance::Present;
18126 if (invariance != php::Const::Invariance::None) {
18127 cns.invariance = invariance;
18134 //////////////////////////////////////////////////////////////////////
18136 const CompactVector<const php::Class*>*
18137 Index::lookup_closures(const php::Class* cls) const {
18138 auto const it = m_data->classClosureMap.find(cls);
18139 if (it != end(m_data->classClosureMap)) {
18140 return &it->second;
18142 return nullptr;
18145 const hphp_fast_set<const php::Func*>*
18146 Index::lookup_extra_methods(const php::Class* cls) const {
18147 if (cls->attrs & AttrNoExpandTrait) return nullptr;
18148 auto const it = m_data->classExtraMethodMap.find(cls);
18149 if (it != end(m_data->classExtraMethodMap)) {
18150 return &it->second;
18152 return nullptr;
18155 //////////////////////////////////////////////////////////////////////
18157 Optional<res::Class> Index::resolve_class(SString clsName) const {
18158 clsName = normalizeNS(clsName);
18159 auto const it = m_data->classInfo.find(clsName);
18160 if (it == end(m_data->classInfo)) return std::nullopt;
18161 return res::Class::get(*it->second);
18164 Optional<res::Class> Index::resolve_class(const php::Class& cls) const {
18165 return resolve_class(cls.name);
18168 const php::TypeAlias* Index::lookup_type_alias(SString name) const {
18169 auto const it = m_data->typeAliases.find(name);
18170 if (it == m_data->typeAliases.end()) return nullptr;
18171 return it->second;
18174 Index::ClassOrTypeAlias Index::lookup_class_or_type_alias(SString name) const {
18175 auto const rcls = resolve_class(name);
18176 if (rcls) {
18177 auto const cls = [&] () -> const php::Class* {
18178 if (auto const ci = rcls->cinfo()) return ci->cls;
18179 return m_data->classes.at(rcls->name());
18180 }();
18181 return ClassOrTypeAlias{cls, nullptr, true};
18183 if (auto const ta = lookup_type_alias(name)) {
18184 return ClassOrTypeAlias{nullptr, ta, true};
18186 return ClassOrTypeAlias{nullptr, nullptr, false};
18189 // Given a DCls, return the most specific res::Func for that DCls. For
18190 // intersections, this will call process/general on every component of
18191 // the intersection and combine the results. For non-intersections, it
18192 // will call process/general on the sole member of the DCls. process
18193 // is called to obtain a res::Func from a ClassInfo. If a ClassInfo
18194 // isn't available, general will be called instead.
18195 template <typename P, typename G>
18196 res::Func Index::rfunc_from_dcls(const DCls& dcls,
18197 SString name,
18198 const P& process,
18199 const G& general) const {
18200 if (dcls.isExact() || dcls.isSub()) {
18201 // If this isn't an intersection, there's only one cinfo to
18202 // process and we're done.
18203 auto const cinfo = dcls.cls().cinfo();
18204 if (!cinfo) return general(dcls.containsNonRegular());
18205 return process(cinfo, dcls.isExact(), dcls.containsNonRegular());
18208 if (dcls.isIsectAndExact()) {
18209 // Even though this has an intersection list, it always must be
18210 // the exact class, so it sufficies to provide that.
18211 auto const e = dcls.isectAndExact().first;
18212 auto const cinfo = e.cinfo();
18213 if (!cinfo) return general(dcls.containsNonRegular());
18214 return process(cinfo, true, dcls.containsNonRegular());
18218 * Otherwise get a res::Func for all members of the intersection and
18219 * combine them together. Since the DCls represents a class which is
18220 * a subtype of every ClassInfo in the list, every res::Func we get
18221 * is true.
18223 * The relevant res::Func types in order from most general to more
18224 * specific are:
18226 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
18228 * Since every res::Func in the intersection is true, we take the
18229 * res::Func which is most specific. Two different res::Funcs cannot
18230 * be contradict. For example, we shouldn't get a Method and a
18231 * Missing since one implies there's no func and the other implies
18232 * one specific func. Or two different res::Funcs shouldn't resolve
18233 * to two different methods.
18236 assertx(dcls.isIsect());
18237 using Func = res::Func;
18239 auto missing = TriBool::Maybe;
18240 Func::Isect isect;
18241 const php::Func* singleMethod = nullptr;
18243 auto const DEBUG_ONLY allIncomplete = !debug || std::all_of(
18244 begin(dcls.isect()), end(dcls.isect()),
18245 [] (res::Class c) { return !c.hasCompleteChildren(); }
18248 for (auto const i : dcls.isect()) {
18249 auto const cinfo = i.cinfo();
18250 if (!cinfo) continue;
18252 auto const func = process(cinfo, false, dcls.containsNonRegular());
18253 match<void>(
18254 func.val,
18255 [&] (Func::MethodName) {},
18256 [&] (Func::Method m) {
18257 if (singleMethod) {
18258 assertx(missing != TriBool::Yes);
18259 assertx(isect.families.empty());
18260 if (singleMethod != m.finfo->func) {
18261 assertx(allIncomplete);
18262 singleMethod = nullptr;
18263 missing = TriBool::Yes;
18264 } else {
18265 missing = TriBool::No;
18267 } else if (missing != TriBool::Yes) {
18268 singleMethod = m.finfo->func;
18269 isect.families.clear();
18270 missing = TriBool::No;
18273 [&] (Func::MethodFamily fam) {
18274 if (missing == TriBool::Yes) {
18275 assertx(!singleMethod);
18276 assertx(isect.families.empty());
18277 return;
18279 if (singleMethod) {
18280 assertx(missing != TriBool::Yes);
18281 assertx(isect.families.empty());
18282 return;
18284 assertx(missing == TriBool::Maybe);
18285 isect.families.emplace_back(fam.family);
18286 isect.regularOnly |= fam.regularOnly;
18288 [&] (Func::MethodOrMissing m) {
18289 if (singleMethod) {
18290 assertx(missing != TriBool::Yes);
18291 assertx(isect.families.empty());
18292 if (singleMethod != m.finfo->func) {
18293 assertx(allIncomplete);
18294 singleMethod = nullptr;
18295 missing = TriBool::Yes;
18297 } else if (missing != TriBool::Yes) {
18298 singleMethod = m.finfo->func;
18299 isect.families.clear();
18302 [&] (Func::MissingMethod) {
18303 assertx(IMPLIES(missing == TriBool::No, allIncomplete));
18304 singleMethod = nullptr;
18305 isect.families.clear();
18306 missing = TriBool::Yes;
18308 [&] (Func::FuncName) { always_assert(false); },
18309 [&] (Func::Fun) { always_assert(false); },
18310 [&] (Func::Fun2) { always_assert(false); },
18311 [&] (Func::Method2) { always_assert(false); },
18312 [&] (Func::MethodFamily2) { always_assert(false); },
18313 [&] (Func::MethodOrMissing2) { always_assert(false); },
18314 [&] (Func::MissingFunc) { always_assert(false); },
18315 [&] (const Func::Isect&) { always_assert(false); },
18316 [&] (const Func::Isect2&) { always_assert(false); }
18320 // If we got a method, that always wins. Again, every res::Func is
18321 // true, and method is more specific than a FuncFamily, so it is
18322 // preferred.
18323 if (singleMethod) {
18324 assertx(missing != TriBool::Yes);
18325 // If missing is Maybe, then *every* resolution was to a
18326 // MethodName or MethodOrMissing, so include that fact here by
18327 // using MethodOrMissing.
18328 if (missing == TriBool::Maybe) {
18329 return Func {
18330 Func::MethodOrMissing { func_info(*m_data, singleMethod) }
18333 return Func { Func::Method { func_info(*m_data, singleMethod) } };
18335 // We only got unresolved classes. If missing is TriBool::Yes, the
18336 // function doesn't exist. Otherwise be pessimistic.
18337 if (isect.families.empty()) {
18338 if (missing == TriBool::Yes) {
18339 return Func { Func::MissingMethod { dcls.smallestCls().name(), name } };
18341 assertx(missing == TriBool::Maybe);
18342 return general(dcls.containsNonRegular());
18344 // Isect case. Isects always might contain missing funcs.
18345 assertx(missing == TriBool::Maybe);
18347 // We could add a FuncFamily multiple times, so remove duplicates.
18348 std::sort(begin(isect.families), end(isect.families));
18349 isect.families.erase(
18350 std::unique(begin(isect.families), end(isect.families)),
18351 end(isect.families)
18353 // If everything simplifies down to a single FuncFamily, just use
18354 // that.
18355 if (isect.families.size() == 1) {
18356 return Func { Func::MethodFamily { isect.families[0], isect.regularOnly } };
18358 return Func { std::move(isect) };
18361 res::Func Index::resolve_method(Context ctx,
18362 const Type& thisType,
18363 SString name) const {
18364 assertx(thisType.subtypeOf(BCls) || thisType.subtypeOf(BObj));
18366 using Func = res::Func;
18369 * Without using the class type, try to infer a set of methods
18370 * using just the method name. This will, naturally, not produce
18371 * as precise a set as when using the class type, but it's better
18372 * than nothing. For all of these results, we need to include the
18373 * possibility of the method not existing (we cannot rule that out
18374 * for this situation).
18376 auto const general = [&] (bool includeNonRegular, SString maybeCls) {
18377 assertx(name != s_construct.get());
18379 // We don't produce name-only global func families for special
18380 // methods, so be conservative. We don't call special methods in a
18381 // context where we'd expect to not know the class, so it's not
18382 // worthwhile. The same goes for __invoke and __debuginfo, which
18383 // is corresponds to every closure, and gets too large without
18384 // much value.
18385 if (!has_name_only_func_family(name)) {
18386 return Func { Func::MethodName { maybeCls, name } };
18389 // Lookup up the name-only global func families for this name. If
18390 // we don't have one, the method cannot exist because it contains
18391 // every method with that name in the program.
18392 auto const famIt = m_data->methodFamilies.find(name);
18393 if (famIt == end(m_data->methodFamilies)) {
18394 return Func { Func::MissingMethod { maybeCls, name } };
18397 // The entry exists. Consult the correct data in it, depending on
18398 // whether we're including non-regular classes or not.
18399 auto const& entry = includeNonRegular
18400 ? famIt->second.m_all
18401 : famIt->second.m_regular;
18402 assertx(entry.isEmpty() || entry.isIncomplete());
18404 if (auto const ff = entry.funcFamily()) {
18405 return Func { Func::MethodFamily { ff, !includeNonRegular } };
18406 } else if (auto const f = entry.func()) {
18407 return Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18408 } else {
18409 return Func { Func::MissingMethod { maybeCls, name } };
18413 auto const process = [&] (ClassInfo* cinfo,
18414 bool isExact,
18415 bool includeNonRegular) {
18416 assertx(name != s_construct.get());
18418 auto const methIt = cinfo->methods.find(name);
18419 if (methIt == end(cinfo->methods)) {
18420 // We don't store metadata for special methods, so be
18421 // pessimistic (the lack of a method entry does not mean the
18422 // call might fail at runtme).
18423 if (is_special_method_name(name)) {
18424 return Func { Func::MethodName { cinfo->cls->name, name } };
18426 // We're only considering this class, not it's subclasses. Since
18427 // it doesn't exist here, the resolution will always fail.
18428 if (isExact) {
18429 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18431 // The method isn't present on this class, but it might be in
18432 // the subclasses. In most cases try a general lookup to get a
18433 // slightly better type than nothing.
18434 if (includeNonRegular ||
18435 !(cinfo->cls->attrs & (AttrInterface|AttrAbstract))) {
18436 return general(includeNonRegular, cinfo->cls->name);
18439 // A special case is if we're only considering regular classes,
18440 // and this is an interface or abstract class. For those, we
18441 // "expand" the method families table to include any methods
18442 // defined in *all* regular subclasses. This is needed to
18443 // preserve monotonicity. Check this now.
18444 auto const famIt = cinfo->methodFamilies.find(name);
18445 // If no entry, treat it pessimistically like the rest of the
18446 // cases.
18447 if (famIt == end(cinfo->methodFamilies)) {
18448 return general(false, cinfo->cls->name);
18451 // We found an entry. This cannot be empty (remember the method
18452 // is guaranteed to exist on *all* regular subclasses), and must
18453 // be complete (for the same reason). Use it.
18454 auto const& entry = famIt->second;
18455 assertx(!entry.isEmpty());
18456 assertx(entry.isComplete());
18457 if (auto const ff = entry.funcFamily()) {
18458 return Func { Func::MethodFamily { ff, true } };
18459 } else if (auto const func = entry.func()) {
18460 return Func { Func::Method { func_info(*m_data, func) } };
18461 } else {
18462 always_assert(false);
18465 // The method on this class.
18466 auto const& meth = methIt->second;
18467 auto const ftarget = func_from_meth_ref(*m_data, meth.meth());
18469 // We don't store method family information about special methods
18470 // and they have special inheritance semantics.
18471 if (is_special_method_name(name)) {
18472 // If we know the class exactly, we can use ftarget.
18473 if (isExact) return Func { Func::Method { func_info(*m_data, ftarget) } };
18474 // The method isn't overwritten, but they don't inherit, so it
18475 // could be missing.
18476 if (meth.attrs & AttrNoOverride) {
18477 return Func { Func::MethodOrMissing { func_info(*m_data, ftarget) } };
18479 // Otherwise be pessimistic.
18480 return Func { Func::MethodName { cinfo->cls->name, name } };
18483 // Private method handling: Private methods have special lookup
18484 // rules. If we're in the context of a particular class, and that
18485 // class defines a private method, an instance of the class will
18486 // always call that private method (even if overridden) in that
18487 // context.
18488 assertx(cinfo->cls);
18489 if (ctx.cls == cinfo->cls) {
18490 // The context matches the current class. If we've looked up a
18491 // private method (defined on this class), then that's what
18492 // we'll call.
18493 if ((meth.attrs & AttrPrivate) && meth.topLevel()) {
18494 return Func { Func::Method { func_info(*m_data, ftarget) } };
18496 } else if ((meth.attrs & AttrPrivate) || meth.hasPrivateAncestor()) {
18497 // Otherwise the context doesn't match the current class. If the
18498 // looked up method is private, or has a private ancestor,
18499 // there's a chance we'll call that method (or
18500 // ancestor). Otherwise there's no private method in the
18501 // inheritance tree we'll call.
18502 auto const ancestor = [&] () -> const php::Func* {
18503 if (!ctx.cls) return nullptr;
18504 // Look up the ClassInfo corresponding to the context:
18505 auto const it = m_data->classInfo.find(ctx.cls->name);
18506 if (it == end(m_data->classInfo)) return nullptr;
18507 auto const ctxCInfo = it->second;
18508 // Is this context a parent of our class?
18509 if (!cinfo->classGraph.exactSubtypeOf(ctxCInfo->classGraph,
18510 true,
18511 true)) {
18512 return nullptr;
18514 // It is. See if it defines a private method.
18515 auto const it2 = ctxCInfo->methods.find(name);
18516 if (it2 == end(ctxCInfo->methods)) return nullptr;
18517 auto const& mte = it2->second;
18518 // If it defines a private method, use it.
18519 if ((mte.attrs & AttrPrivate) && mte.topLevel()) {
18520 return func_from_meth_ref(*m_data, mte.meth());
18522 // Otherwise do normal lookup.
18523 return nullptr;
18524 }();
18525 if (ancestor) {
18526 return Func { Func::Method { func_info(*m_data, ancestor) } };
18529 // If none of the above cases trigger, we still might call a
18530 // private method (in a child class), but the func-family logic
18531 // below will handle that.
18533 // If we're only including regular subclasses, and this class
18534 // itself isn't regular, the result may not necessarily include
18535 // ftarget.
18536 if (!includeNonRegular && !is_regular_class(*cinfo->cls)) {
18537 // We're not including this base class. If we're exactly this
18538 // class, there's no method at all. It will always be missing.
18539 if (isExact) {
18540 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18542 if (meth.noOverrideRegular()) {
18543 // The method isn't overridden in a subclass, but we can't
18544 // use the base class either. This leaves two cases. Either
18545 // the method isn't overridden because there are no regular
18546 // subclasses (in which case there's no resolution at all), or
18547 // because there's regular subclasses, but they use the same
18548 // method (in which case the result is just ftarget).
18549 if (!cinfo->classGraph.mightHaveRegularSubclass()) {
18550 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18552 return Func { Func::Method { func_info(*m_data, ftarget) } };
18554 // We can't use the base class (because it's non-regular), but
18555 // the method is overridden by a regular subclass.
18557 // Since this is a non-regular class and we want the result for
18558 // the regular subset, we need to consult the aux table first.
18559 auto const auxIt = cinfo->methodFamiliesAux.find(name);
18560 if (auxIt != end(cinfo->methodFamiliesAux)) {
18561 // Found an entry in the aux table. Use whatever it provides.
18562 auto const& aux = auxIt->second;
18563 if (auto const ff = aux.funcFamily()) {
18564 return Func { Func::MethodFamily { ff, true } };
18565 } else if (auto const f = aux.func()) {
18566 return aux.isComplete()
18567 ? Func { Func::Method { func_info(*m_data, f) } }
18568 : Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18569 } else {
18570 return Func { Func::MissingMethod { cinfo->cls->name, name } };
18573 // No entry in the aux table. The result is the same as the
18574 // normal table, so fall through and use that.
18575 } else if (isExact ||
18576 meth.attrs & AttrNoOverride ||
18577 (!includeNonRegular && meth.noOverrideRegular())) {
18578 // Either we want all classes, or the base class is regular. If
18579 // the method isn't overridden we know it must be just ftarget
18580 // (the override bits include it being missing in a subclass, so
18581 // we know it cannot be missing either).
18582 return Func { Func::Method { func_info(*m_data, ftarget) } };
18585 // Look up the entry in the normal method family table and use
18586 // whatever is there.
18587 auto const famIt = cinfo->methodFamilies.find(name);
18588 assertx(famIt != end(cinfo->methodFamilies));
18589 auto const& fam = famIt->second;
18590 assertx(!fam.isEmpty());
18592 if (auto const ff = fam.funcFamily()) {
18593 return Func { Func::MethodFamily { ff, !includeNonRegular } };
18594 } else if (auto const f = fam.func()) {
18595 return (!includeNonRegular || fam.isComplete())
18596 ? Func { Func::Method { func_info(*m_data, f) } }
18597 : Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18598 } else {
18599 always_assert(false);
18603 auto const isClass = thisType.subtypeOf(BCls);
18604 if (name == s_construct.get()) {
18605 if (isClass) {
18606 return Func { Func::MethodName { nullptr, s_construct.get() } };
18608 return resolve_ctor(thisType);
18611 if (isClass) {
18612 if (!is_specialized_cls(thisType)) return general(true, nullptr);
18613 } else if (!is_specialized_obj(thisType)) {
18614 return general(false, nullptr);
18617 auto const& dcls = isClass ? dcls_of(thisType) : dobj_of(thisType);
18618 return rfunc_from_dcls(
18619 dcls,
18620 name,
18621 process,
18622 [&] (bool i) { return general(i, dcls.smallestCls().name()); }
18626 res::Func Index::resolve_ctor(const Type& obj) const {
18627 assertx(obj.subtypeOf(BObj));
18629 using Func = res::Func;
18631 // Can't say anything useful if we don't know the object type.
18632 if (!is_specialized_obj(obj)) {
18633 return Func { Func::MethodName { nullptr, s_construct.get() } };
18636 auto const& dcls = dobj_of(obj);
18637 return rfunc_from_dcls(
18638 dcls,
18639 s_construct.get(),
18640 [&] (ClassInfo* cinfo, bool isExact, bool includeNonRegular) {
18641 // We're dealing with an object here, which never uses
18642 // non-regular classes.
18643 assertx(!includeNonRegular);
18645 // See if this class has a ctor.
18646 auto const methIt = cinfo->methods.find(s_construct.get());
18647 if (methIt == end(cinfo->methods)) {
18648 // There's no ctor on this class. This doesn't mean the ctor
18649 // won't exist at runtime, it might get the default ctor, so
18650 // we have to be conservative.
18651 return Func {
18652 Func::MethodName { cinfo->cls->name, s_construct.get() }
18656 // We have a ctor, but it might be overridden in a subclass.
18657 auto const& mte = methIt->second;
18658 assertx(!(mte.attrs & AttrStatic));
18659 auto const ftarget = func_from_meth_ref(*m_data, mte.meth());
18660 assertx(!(ftarget->attrs & AttrStatic));
18662 // If this class is known exactly, or we know nothing overrides
18663 // this ctor, we know this ctor is precisely it.
18664 if (isExact || mte.noOverrideRegular()) {
18665 // If this class isn't regular, and doesn't have any regular
18666 // subclasses (or if it's exact), this resolution will always
18667 // fail.
18668 if (!is_regular_class(*cinfo->cls) &&
18669 (isExact || !cinfo->classGraph.mightHaveRegularSubclass())) {
18670 return Func {
18671 Func::MissingMethod { cinfo->cls->name, s_construct.get() }
18674 return Func { Func::Method { func_info(*m_data, ftarget) } };
18677 // If this isn't a regular class, we need to check the "aux"
18678 // entry first (which always has priority when only looking at
18679 // the regular subset).
18680 if (!is_regular_class(*cinfo->cls)) {
18681 auto const auxIt = cinfo->methodFamiliesAux.find(s_construct.get());
18682 if (auxIt != end(cinfo->methodFamiliesAux)) {
18683 auto const& aux = auxIt->second;
18684 if (auto const ff = aux.funcFamily()) {
18685 return Func { Func::MethodFamily { ff, true } };
18686 } else if (auto const f = aux.func()) {
18687 return aux.isComplete()
18688 ? Func { Func::Method { func_info(*m_data, f) } }
18689 : Func { Func::MethodOrMissing { func_info(*m_data, f) } };
18690 } else {
18691 // Ctor doesn't exist in any regular subclasses. This can
18692 // happen with interfaces. The ctor might get the default
18693 // ctor at runtime, so be conservative.
18694 return Func {
18695 Func::MethodName { cinfo->cls->name, s_construct.get() }
18700 // Otherwise this class is regular (in which case there's just
18701 // method families, or there's no entry in aux, which means the
18702 // regular subset entry is the same as the full entry.
18704 auto const famIt = cinfo->methodFamilies.find(s_construct.get());
18705 assertx(famIt != cinfo->methodFamilies.end());
18706 auto const& fam = famIt->second;
18707 assertx(!fam.isEmpty());
18709 if (auto const ff = fam.funcFamily()) {
18710 return Func { Func::MethodFamily { ff, true } };
18711 } else if (auto const f = fam.func()) {
18712 // Since we're looking at the regular subset, we can assume
18713 // the set is complete, regardless of the flag on fam.
18714 return Func { Func::Method { func_info(*m_data, f) } };
18715 } else {
18716 always_assert(false);
18719 [&] (bool includeNonRegular) {
18720 assertx(!includeNonRegular);
18721 return Func {
18722 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
18728 res::Func Index::resolve_func(SString name) const {
18729 name = normalizeNS(name);
18730 auto const it = m_data->funcs.find(name);
18731 if (it == end(m_data->funcs)) {
18732 return res::Func { res::Func::MissingFunc { name } };
18734 auto const func = it->second;
18735 assertx(func->attrs & AttrPersistent);
18736 return res::Func { res::Func::Fun { func_info(*m_data, func) } };
18739 res::Func Index::resolve_func_or_method(const php::Func& f) const {
18740 if (!f.cls) return res::Func { res::Func::Fun { func_info(*m_data, &f) } };
18741 return res::Func { res::Func::Method { func_info(*m_data, &f) } };
18744 bool Index::func_depends_on_arg(const php::Func* func, size_t arg) const {
18745 auto const& finfo = *func_info(*m_data, func);
18746 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
18749 // Helper function: Given a DCls, visit every subclass it represents,
18750 // passing it to the given callable. If the callable returns false,
18751 // stop iteration. Return false if any of the classes is unresolved,
18752 // true otherwise. This is used to simplify the below functions which
18753 // need to iterate over all possible subclasses and union the results.
18754 template <typename F>
18755 bool Index::visit_every_dcls_cls(const DCls& dcls, const F& f) const {
18756 if (dcls.isExact()) {
18757 auto const cinfo = dcls.cls().cinfo();
18758 if (!cinfo) return false;
18759 if (dcls.containsNonRegular() || is_regular_class(*cinfo->cls)) {
18760 f(cinfo);
18762 return true;
18763 } else if (dcls.isSub()) {
18764 auto unresolved = false;
18765 res::Class::visitEverySub(
18766 std::array<res::Class, 1>{dcls.cls()},
18767 dcls.containsNonRegular(),
18768 [&] (res::Class c) {
18769 if (c.hasCompleteChildren()) {
18770 if (auto const cinfo = c.cinfo()) return f(cinfo);
18772 unresolved = true;
18773 return false;
18776 return !unresolved;
18777 } else if (dcls.isIsect()) {
18778 auto const& isect = dcls.isect();
18779 assertx(isect.size() > 1);
18781 auto unresolved = false;
18782 res::Class::visitEverySub(
18783 isect,
18784 dcls.containsNonRegular(),
18785 [&] (res::Class c) {
18786 if (c.hasCompleteChildren()) {
18787 if (auto const cinfo = c.cinfo()) return f(cinfo);
18789 unresolved = true;
18790 return false;
18793 return !unresolved;
18794 } else {
18795 // Even though this has an intersection list, it must be the exact
18796 // class, so it's sufficient to provide that.
18797 assertx(dcls.isIsectAndExact());
18798 auto const e = dcls.isectAndExact().first;
18799 auto const cinfo = e.cinfo();
18800 if (!cinfo) return false;
18801 if (dcls.containsNonRegular() || is_regular_class(*cinfo->cls)) {
18802 f(cinfo);
18804 return true;
18808 ClsConstLookupResult Index::lookup_class_constant(Context ctx,
18809 const Type& cls,
18810 const Type& name) const {
18811 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
18812 show(ctx), show(cls), show(name));
18813 Trace::Indent _;
18815 using R = ClsConstLookupResult;
18817 auto const conservative = [] {
18818 ITRACE(4, "conservative\n");
18819 return R{ TInitCell, TriBool::Maybe, true };
18822 auto const notFound = [] {
18823 ITRACE(4, "not found\n");
18824 return R{ TBottom, TriBool::No, false };
18827 if (!is_specialized_cls(cls)) return conservative();
18829 // We could easily support the case where we don't know the constant
18830 // name, but know the class (like we do for properties), by unioning
18831 // together all possible constants. However it very rarely happens,
18832 // but when it does, the set of constants to union together can be
18833 // huge and it becomes very expensive.
18834 if (!is_specialized_string(name)) return conservative();
18835 auto const sname = sval_of(name);
18837 // If this lookup is safe to cache. Some classes can have a huge
18838 // number of subclasses and unioning together all possible constants
18839 // can become very expensive. We can aleviate some of this expense
18840 // by caching results. We cannot cache a result when we use 86cinit
18841 // analysis since that can change.
18842 auto cachable = true;
18844 auto const process = [&] (const ClassInfo* ci) {
18845 ITRACE(4, "{}:\n", ci->cls->name);
18846 Trace::Indent _;
18848 // Does the constant exist on this class?
18849 auto const it = ci->clsConstants.find(sname);
18850 if (it == ci->clsConstants.end()) return notFound();
18852 // Is it a value and is it non-abstract (we only deal with
18853 // concrete constants).
18854 auto const& cns = *it->second.get();
18855 if (cns.kind != ConstModifiers::Kind::Value) return notFound();
18856 if (!cns.val.has_value()) return notFound();
18858 auto const cnsIdx = it->second.idx;
18860 // Determine the constant's value and return it
18861 auto const r = [&] {
18862 if (cns.val->m_type == KindOfUninit) {
18863 // Constant is defined by a 86cinit. Use the result from
18864 // analysis and add a dependency. We cannot cache in this
18865 // case.
18866 cachable = false;
18867 auto const cnsCls = m_data->classes.at(cns.cls);
18868 if (ctx.func) {
18869 auto const cinit = cnsCls->methods.back().get();
18870 assertx(cinit->name == s_86cinit.get());
18871 add_dependency(*m_data, cinit, ctx, Dep::ClsConst);
18874 ITRACE(4, "(dynamic)\n");
18875 auto const type = [&] {
18876 auto const cnsClsCi = folly::get_default(m_data->classInfo, cnsCls->name);
18877 if (!cnsClsCi || cnsIdx >= cnsClsCi->clsConstTypes.size()) {
18878 return TInitCell;
18880 return cnsClsCi->clsConstTypes[cnsIdx].type;
18881 }();
18882 return R{ type, TriBool::Yes, true };
18885 // Fully resolved constant with a known value
18886 auto mightThrow = bool(ci->cls->attrs & AttrInternal);
18887 if (!mightThrow) {
18888 auto const unit = lookup_class_unit(*ci->cls);
18889 auto const moduleName = unit->moduleName;
18890 auto const packageInfo = unit->packageInfo;
18891 if (auto const activeDeployment = packageInfo.getActiveDeployment()) {
18892 if (!packageInfo.moduleInDeployment(
18893 moduleName, *activeDeployment, DeployKind::Hard)) {
18894 mightThrow = true;
18898 return R{ from_cell(*cns.val), TriBool::Yes, mightThrow };
18899 }();
18900 ITRACE(4, "-> {}\n", show(r));
18901 return r;
18904 auto const& dcls = dcls_of(cls);
18905 if (dcls.isSub()) {
18906 // Before anything, look up this entry in the cache. We don't
18907 // bother with the cache for the exact case because it's quick and
18908 // there's little point.
18909 auto const cinfo = dcls.cls().cinfo();
18910 if (!cinfo) return conservative();
18911 if (auto const it =
18912 m_data->clsConstLookupCache.find(std::make_pair(cinfo->cls, sname));
18913 it != m_data->clsConstLookupCache.end()) {
18914 ITRACE(4, "cache hit: {}\n", show(it->second));
18915 return it->second;
18919 Optional<R> result;
18920 auto const resolved = visit_every_dcls_cls(
18921 dcls,
18922 [&] (const ClassInfo* cinfo) {
18923 if (result) ITRACE(5, "-> {}\n", show(*result));
18924 auto r = process(cinfo);
18925 if (!result) {
18926 result.emplace(std::move(r));
18927 } else {
18928 *result |= r;
18930 return true;
18933 if (!resolved) return conservative();
18934 assertx(result.has_value());
18936 // Save this for future lookups if we can
18937 if (dcls.isSub() && cachable) {
18938 auto const cinfo = dcls.cls().cinfo();
18939 assertx(cinfo);
18940 m_data->clsConstLookupCache.emplace(
18941 std::make_pair(cinfo->cls, sname),
18942 *result
18946 ITRACE(4, "-> {}\n", show(*result));
18947 return *result;
18950 std::vector<std::pair<SString, ConstIndex>>
18951 Index::lookup_flattened_class_type_constants(const php::Class&) const {
18952 // Should never be used with an Index.
18953 always_assert(false);
18956 std::vector<std::pair<SString, ClsConstInfo>>
18957 Index::lookup_class_constants(const php::Class& cls) const {
18958 std::vector<std::pair<SString, ClsConstInfo>> out;
18959 out.reserve(cls.constants.size());
18961 auto const cinfo = folly::get_default(m_data->classInfo, cls.name);
18962 for (size_t i = 0, size = cls.constants.size(); i < size; ++i) {
18963 auto const& cns = cls.constants[i];
18964 if (cns.kind != ConstModifiers::Kind::Value) continue;
18965 if (!cns.val) continue;
18966 if (cns.val->m_type != KindOfUninit) {
18967 out.emplace_back(cns.name, ClsConstInfo{ from_cell(*cns.val), 0 });
18968 } else if (cinfo && i < cinfo->clsConstTypes.size()) {
18969 out.emplace_back(cns.name, cinfo->clsConstTypes[i]);
18970 } else {
18971 out.emplace_back(cns.name, ClsConstInfo{ TInitCell, 0 });
18974 return out;
18977 ClsTypeConstLookupResult
18978 Index::lookup_class_type_constant(
18979 const Type& cls,
18980 const Type& name,
18981 const ClsTypeConstLookupResolver& resolver) const {
18982 ITRACE(4, "lookup_class_type_constant: {}::{}\n", show(cls), show(name));
18983 Trace::Indent _;
18985 using R = ClsTypeConstLookupResult;
18987 auto const conservative = [] {
18988 ITRACE(4, "conservative\n");
18989 return R{
18990 TypeStructureResolution { TSDictN, true },
18991 TriBool::Maybe,
18992 TriBool::Maybe
18996 auto const notFound = [] {
18997 ITRACE(4, "not found\n");
18998 return R {
18999 TypeStructureResolution { TBottom, false },
19000 TriBool::No,
19001 TriBool::No
19005 // Unlike lookup_class_constant, we distinguish abstract from
19006 // not-found, as the runtime sometimes treats them differently.
19007 auto const abstract = [] {
19008 ITRACE(4, "abstract\n");
19009 return R {
19010 TypeStructureResolution { TBottom, false },
19011 TriBool::No,
19012 TriBool::Yes
19016 if (!is_specialized_cls(cls)) return conservative();
19018 // As in lookup_class_constant, we could handle this, but it's not
19019 // worth it.
19020 if (!is_specialized_string(name)) return conservative();
19021 auto const sname = sval_of(name);
19023 auto const process = [&] (const ClassInfo* ci) {
19024 ITRACE(4, "{}:\n", ci->cls->name);
19025 Trace::Indent _;
19027 // Does the type constant exist on this class?
19028 auto const it = ci->clsConstants.find(sname);
19029 if (it == ci->clsConstants.end()) return notFound();
19031 // Is it an actual non-abstract type-constant?
19032 auto const& cns = *it->second;
19033 if (cns.kind != ConstModifiers::Kind::Type) return notFound();
19034 if (!cns.val.has_value()) return abstract();
19036 assertx(tvIsDict(*cns.val));
19037 ITRACE(4, "({}) {}\n", cns.cls, show(dict_val(val(*cns.val).parr)));
19039 // If we've been given a resolver, use it. Otherwise resolve it in
19040 // the normal way.
19041 auto resolved = resolver
19042 ? resolver(cns, *ci->cls)
19043 : resolve_type_structure(IndexAdaptor { *this }, cns, *ci->cls);
19045 // The result of resolve_type_structure isn't, in general,
19046 // static. However a type-constant will always be, so force that
19047 // here.
19048 assertx(resolved.type.is(BBottom) || resolved.type.couldBe(BUnc));
19049 resolved.type &= TUnc;
19050 auto const r = R{
19051 std::move(resolved),
19052 TriBool::Yes,
19053 TriBool::No
19055 ITRACE(4, "-> {}\n", show(r));
19056 return r;
19059 auto const& dcls = dcls_of(cls);
19061 Optional<R> result;
19062 auto const resolved = visit_every_dcls_cls(
19063 dcls,
19064 [&] (const ClassInfo* cinfo) {
19065 if (result) {
19066 ITRACE(5, "-> {}\n", show(*result));
19068 auto r = process(cinfo);
19069 if (!result) {
19070 result.emplace(std::move(r));
19071 } else {
19072 *result |= r;
19074 return true;
19077 if (!resolved) return conservative();
19078 assertx(result.has_value());
19080 ITRACE(4, "-> {}\n", show(*result));
19081 return *result;
19084 ClsTypeConstLookupResult
19085 Index::lookup_class_type_constant(const php::Class&,
19086 SString,
19087 HHBBC::ConstIndex) const {
19088 // Should never be called with an Index.
19089 always_assert(false);
19092 Type Index::lookup_constant(Context ctx, SString cnsName) const {
19093 auto iter = m_data->constants.find(cnsName);
19094 if (iter == end(m_data->constants)) return TBottom;
19096 auto constant = iter->second;
19097 if (type(constant->val) != KindOfUninit) {
19098 return from_cell(constant->val);
19101 // Assume a runtime call to Constant::get(), which will invoke
19102 // 86cinit_<cnsName>(). Look up it's return type.
19104 auto const func_name = Constant::funcNameFromName(cnsName);
19105 assertx(func_name && "func_name will never be nullptr");
19107 auto rfunc = resolve_func(func_name);
19108 assertx(rfunc.exactFunc());
19110 return lookup_return_type(ctx, nullptr, rfunc, Dep::ConstVal).t;
19113 Index::ReturnType
19114 Index::lookup_foldable_return_type(Context ctx,
19115 const CallContext& calleeCtx) const {
19116 auto const func = calleeCtx.callee;
19117 constexpr auto max_interp_nexting_level = 2;
19118 static __thread uint32_t interp_nesting_level;
19120 using R = ReturnType;
19122 auto const ctxType = adjust_closure_context(
19123 IndexAdaptor { *this },
19124 calleeCtx
19127 // Don't fold functions when staticness mismatches
19128 if (!func->isClosureBody) {
19129 if ((func->attrs & AttrStatic) && ctxType.couldBe(TObj)) {
19130 return R{ TInitCell, false };
19132 if (!(func->attrs & AttrStatic) && ctxType.couldBe(TCls)) {
19133 return R{ TInitCell, false };
19137 auto const& finfo = *func_info(*m_data, func);
19138 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
19139 return R{ finfo.returnTy, true };
19142 auto showArgs DEBUG_ONLY = [] (const CompactVector<Type>& a) {
19143 std::string ret, sep;
19144 for (auto& arg : a) {
19145 folly::format(&ret, "{}{}", sep, show(arg));
19146 sep = ",";
19148 return ret;
19152 ContextRetTyMap::const_accessor acc;
19153 if (m_data->foldableReturnTypeMap.find(acc, calleeCtx)) {
19154 FTRACE_MOD(
19155 Trace::hhbbc, 4,
19156 "Found foldableReturnType for {}{}{} with args {} (hash: {})\n",
19157 func->cls ? func->cls->name : staticEmptyString(),
19158 func->cls ? "::" : "",
19159 func->name,
19160 showArgs(calleeCtx.args),
19161 CallContextHashCompare{}.hash(calleeCtx));
19163 assertx(is_scalar(acc->second.t));
19164 assertx(acc->second.effectFree);
19165 return acc->second;
19169 if (frozen()) {
19170 FTRACE_MOD(
19171 Trace::hhbbc, 4,
19172 "MISSING: foldableReturnType for {}{}{} with args {} (hash: {})\n",
19173 func->cls ? func->cls->name : staticEmptyString(),
19174 func->cls ? "::" : "",
19175 func->name,
19176 showArgs(calleeCtx.args),
19177 CallContextHashCompare{}.hash(calleeCtx));
19178 return R{ TInitCell, false };
19181 if (interp_nesting_level > max_interp_nexting_level) {
19182 add_dependency(*m_data, func, ctx, Dep::InlineDepthLimit);
19183 return R{ TInitCell, false };
19186 auto const contextType = [&] {
19187 ++interp_nesting_level;
19188 SCOPE_EXIT { --interp_nesting_level; };
19190 auto const wf = php::WideFunc::cns(func);
19191 auto const fa = analyze_func_inline(
19192 IndexAdaptor { *this },
19193 AnalysisContext { func->unit, wf, func->cls, &ctx.forDep() },
19194 ctxType,
19195 calleeCtx.args,
19196 nullptr,
19197 CollectionOpts::EffectFreeOnly
19199 return R{
19200 fa.effectFree ? fa.inferredReturn : TInitCell,
19201 fa.effectFree
19203 }();
19205 if (!is_scalar(contextType.t)) return R{ TInitCell, false };
19207 ContextRetTyMap::accessor acc;
19208 if (m_data->foldableReturnTypeMap.insert(acc, calleeCtx)) {
19209 acc->second = contextType;
19210 } else {
19211 // someone beat us to it
19212 assertx(acc->second.t == contextType.t);
19214 return contextType;
19217 Index::ReturnType Index::lookup_return_type(Context ctx,
19218 MethodsInfo* methods,
19219 res::Func rfunc,
19220 Dep dep) const {
19221 using R = ReturnType;
19223 auto const funcFamily = [&] (FuncFamily* fam, bool regularOnly) {
19224 add_dependency(*m_data, fam, ctx, dep);
19225 return fam->infoFor(regularOnly).m_returnTy.get(
19226 [&] {
19227 auto ret = TBottom;
19228 auto effectFree = true;
19229 for (auto const pf : fam->possibleFuncs()) {
19230 if (regularOnly && !pf.inRegular()) continue;
19231 auto const finfo = func_info(*m_data, pf.ptr());
19232 if (!finfo->func) return R{ TInitCell, false };
19233 ret |= unctx(finfo->returnTy);
19234 effectFree &= finfo->effectFree;
19235 if (!ret.strictSubtypeOf(BInitCell) && !effectFree) break;
19237 return R{ std::move(ret), effectFree };
19241 auto const meth = [&] (const php::Func* func) {
19242 if (methods) {
19243 if (auto ret = methods->lookupReturnType(*func)) {
19244 return R{ unctx(std::move(ret->t)), ret->effectFree };
19247 add_dependency(*m_data, func, ctx, dep);
19248 auto const finfo = func_info(*m_data, func);
19249 if (!finfo->func) return R{ TInitCell, false };
19250 return R{ unctx(finfo->returnTy), finfo->effectFree };
19253 return match<R>(
19254 rfunc.val,
19255 [&] (res::Func::FuncName) { return R{ TInitCell, false }; },
19256 [&] (res::Func::MethodName) { return R{ TInitCell, false }; },
19257 [&] (res::Func::Fun f) {
19258 add_dependency(*m_data, f.finfo->func, ctx, dep);
19259 return R{ unctx(f.finfo->returnTy), f.finfo->effectFree };
19261 [&] (res::Func::Method m) { return meth(m.finfo->func); },
19262 [&] (res::Func::MethodFamily fam) {
19263 return funcFamily(fam.family, fam.regularOnly);
19265 [&] (res::Func::MethodOrMissing m) { return meth(m.finfo->func); },
19266 [&] (res::Func::MissingFunc) { return R{ TBottom, false }; },
19267 [&] (res::Func::MissingMethod) { return R{ TBottom, false }; },
19268 [&] (const res::Func::Isect& i) {
19269 auto ty = TInitCell;
19270 auto anyEffectFree = false;
19271 for (auto const ff : i.families) {
19272 auto const [t, e] = funcFamily(ff, i.regularOnly);
19273 ty &= t;
19274 if (e) anyEffectFree = true;
19276 return R{ std::move(ty), anyEffectFree };
19278 [&] (res::Func::Fun2) -> R { always_assert(false); },
19279 [&] (res::Func::Method2) -> R { always_assert(false); },
19280 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
19281 [&] (res::Func::MethodOrMissing2) -> R { always_assert(false); },
19282 [&] (res::Func::Isect2&) -> R { always_assert(false); }
19286 Index::ReturnType Index::lookup_return_type(Context caller,
19287 MethodsInfo* methods,
19288 const CompactVector<Type>& args,
19289 const Type& context,
19290 res::Func rfunc,
19291 Dep dep) const {
19292 using R = ReturnType;
19294 auto const funcFamily = [&] (FuncFamily* fam, bool regularOnly) {
19295 add_dependency(*m_data, fam, caller, dep);
19296 auto ret = fam->infoFor(regularOnly).m_returnTy.get(
19297 [&] {
19298 auto ty = TBottom;
19299 auto effectFree = true;
19300 for (auto const pf : fam->possibleFuncs()) {
19301 if (regularOnly && !pf.inRegular()) continue;
19302 auto const finfo = func_info(*m_data, pf.ptr());
19303 if (!finfo->func) return R{ TInitCell, false };
19304 ty |= finfo->returnTy;
19305 effectFree &= finfo->effectFree;
19306 if (!ty.strictSubtypeOf(BInitCell) && !effectFree) break;
19308 return R{ std::move(ty), effectFree };
19311 return R{
19312 return_with_context(std::move(ret.t), context),
19313 ret.effectFree
19316 auto const meth = [&] (const php::Func* func) {
19317 auto const finfo = func_info(*m_data, func);
19318 if (!finfo->func) return R{ TInitCell, false };
19320 auto returnType = [&] {
19321 if (methods) {
19322 if (auto ret = methods->lookupReturnType(*func)) {
19323 return *ret;
19326 add_dependency(*m_data, func, caller, dep);
19327 return R{ finfo->returnTy, finfo->effectFree };
19328 }();
19330 return context_sensitive_return_type(
19331 *m_data,
19332 caller,
19333 { finfo->func, args, context },
19334 std::move(returnType)
19338 return match<R>(
19339 rfunc.val,
19340 [&] (res::Func::FuncName) {
19341 return lookup_return_type(caller, methods, rfunc, dep);
19343 [&] (res::Func::MethodName) {
19344 return lookup_return_type(caller, methods, rfunc, dep);
19346 [&] (res::Func::Fun f) {
19347 add_dependency(*m_data, f.finfo->func, caller, dep);
19348 return context_sensitive_return_type(
19349 *m_data,
19350 caller,
19351 { f.finfo->func, args, context },
19352 R{ f.finfo->returnTy, f.finfo->effectFree }
19355 [&] (res::Func::Method m) { return meth(m.finfo->func); },
19356 [&] (res::Func::MethodFamily fam) {
19357 return funcFamily(fam.family, fam.regularOnly);
19359 [&] (res::Func::MethodOrMissing m) { return meth(m.finfo->func); },
19360 [&] (res::Func::MissingFunc) { return R { TBottom, false }; },
19361 [&] (res::Func::MissingMethod) { return R { TBottom, false }; },
19362 [&] (const res::Func::Isect& i) {
19363 auto ty = TInitCell;
19364 auto anyEffectFree = false;
19365 for (auto const ff : i.families) {
19366 auto const [t, e] = funcFamily(ff, i.regularOnly);
19367 ty &= t;
19368 if (e) anyEffectFree = true;
19370 return R{ std::move(ty), anyEffectFree };
19372 [&] (res::Func::Fun2) -> R { always_assert(false); },
19373 [&] (res::Func::Method2) -> R { always_assert(false); },
19374 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
19375 [&] (res::Func::MethodOrMissing2) -> R { always_assert(false); },
19376 [&] (res::Func::Isect2&) -> R { always_assert(false); }
19380 std::pair<Index::ReturnType, size_t>
19381 Index::lookup_return_type_raw(const php::Func* f) const {
19382 auto it = func_info(*m_data, f);
19383 if (it->func) {
19384 assertx(it->func == f);
19385 return {
19386 ReturnType{ it->returnTy, it->effectFree },
19387 it->returnRefinements
19390 return { ReturnType{ TInitCell, false }, 0 };
19393 CompactVector<Type>
19394 Index::lookup_closure_use_vars(const php::Func* func,
19395 bool move) const {
19396 assertx(func->isClosureBody);
19398 auto const numUseVars = closure_num_use_vars(func);
19399 if (!numUseVars) return {};
19400 auto const it = m_data->closureUseVars.find(func->cls);
19401 if (it == end(m_data->closureUseVars)) {
19402 return CompactVector<Type>(numUseVars, TCell);
19404 if (move) return std::move(it->second);
19405 return it->second;
19408 PropState
19409 Index::lookup_private_props(const php::Class* cls,
19410 bool move) const {
19411 auto it = m_data->privatePropInfo.find(cls);
19412 if (it != end(m_data->privatePropInfo)) {
19413 if (move) return std::move(it->second);
19414 return it->second;
19416 return make_unknown_propstate(
19417 IndexAdaptor { *this },
19418 *cls,
19419 [&] (const php::Prop& prop) {
19420 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
19425 PropState
19426 Index::lookup_private_statics(const php::Class* cls,
19427 bool move) const {
19428 auto it = m_data->privateStaticPropInfo.find(cls);
19429 if (it != end(m_data->privateStaticPropInfo)) {
19430 if (move) return std::move(it->second);
19431 return it->second;
19433 return make_unknown_propstate(
19434 IndexAdaptor { *this },
19435 *cls,
19436 [&] (const php::Prop& prop) {
19437 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
19442 PropState Index::lookup_public_statics(const php::Class* cls) const {
19443 auto const cinfo = [&] () -> const ClassInfo* {
19444 auto const it = m_data->classInfo.find(cls->name);
19445 if (it == end(m_data->classInfo)) return nullptr;
19446 return it->second;
19447 }();
19449 PropState state;
19450 for (auto const& prop : cls->properties) {
19451 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
19452 !(prop.attrs & AttrStatic)) {
19453 continue;
19456 auto [ty, everModified] = [&] {
19457 if (!cinfo) return std::make_pair(TInitCell, true);
19459 if (!m_data->seenPublicSPropMutations) {
19460 return std::make_pair(
19461 union_of(
19462 adjust_type_for_prop(
19463 IndexAdaptor { *this },
19464 *cls,
19465 &prop.typeConstraint,
19466 TInitCell
19468 initial_type_for_public_sprop(*this, *cls, prop)
19470 true
19474 auto const it = cinfo->publicStaticProps.find(prop.name);
19475 if (it == end(cinfo->publicStaticProps)) {
19476 return std::make_pair(
19477 initial_type_for_public_sprop(*this, *cls, prop),
19478 false
19481 return std::make_pair(
19482 it->second.inferredType,
19483 it->second.everModified
19485 }();
19486 state.emplace(
19487 prop.name,
19488 PropStateElem{
19489 std::move(ty),
19490 &prop.typeConstraint,
19491 prop.attrs,
19492 everModified
19496 return state;
19500 * Entry point for static property lookups from the Index. Return
19501 * metadata about a `cls'::`name' static property access in the given
19502 * context.
19504 PropLookupResult Index::lookup_static(Context ctx,
19505 const PropertiesInfo& privateProps,
19506 const Type& cls,
19507 const Type& name) const {
19508 ITRACE(4, "lookup_static: {} {}::${}\n", show(ctx), show(cls), show(name));
19509 Trace::Indent _;
19511 using R = PropLookupResult;
19513 // First try to obtain the property name as a static string
19514 auto const sname = [&] () -> SString {
19515 // Treat non-string names conservatively, but the caller should be
19516 // checking this.
19517 if (!is_specialized_string(name)) return nullptr;
19518 return sval_of(name);
19519 }();
19521 // Conservative result when we can't do any better. The type can be
19522 // anything, and anything might throw.
19523 auto const conservative = [&] {
19524 ITRACE(4, "conservative\n");
19525 return R{
19526 TInitCell,
19527 sname,
19528 TriBool::Maybe,
19529 TriBool::Maybe,
19530 TriBool::Maybe,
19531 TriBool::Maybe,
19532 TriBool::Maybe,
19533 true
19537 // If we don't know what `cls' is, there's not much we can do.
19538 if (!is_specialized_cls(cls)) return conservative();
19540 // Turn the context class into a ClassInfo* for convenience.
19541 const ClassInfo* ctxCls = nullptr;
19542 if (ctx.cls) {
19543 // I don't think this can ever fail (we should always be able to
19544 // resolve the class since we're currently processing it). If it
19545 // does, be conservative.
19546 auto const rCtx = resolve_class(ctx.cls->name);
19547 if (!rCtx) return conservative();
19548 ctxCls = rCtx->cinfo();
19549 if (!ctxCls) return conservative();
19552 auto const& dcls = dcls_of(cls);
19553 auto const start = dcls.cls();
19555 Optional<R> result;
19556 auto const resolved = visit_every_dcls_cls(
19557 dcls,
19558 [&] (const ClassInfo* cinfo) {
19559 auto r = lookup_static_impl(
19560 *m_data,
19561 ctx,
19562 ctxCls,
19563 privateProps,
19564 cinfo,
19565 sname,
19566 dcls.isSub() && !sname && cinfo != start.cinfo()
19568 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
19569 if (!result) {
19570 result.emplace(std::move(r));
19571 } else {
19572 *result |= r;
19574 return true;
19577 if (!resolved) return conservative();
19578 assertx(result.has_value());
19580 ITRACE(4, "union -> {}\n", show(*result));
19581 return *result;
19584 Type Index::lookup_public_prop(const Type& obj, const Type& name) const {
19585 if (!is_specialized_obj(obj)) return TCell;
19587 if (!is_specialized_string(name)) return TCell;
19588 auto const sname = sval_of(name);
19590 auto ty = TBottom;
19591 auto const resolved = visit_every_dcls_cls(
19592 dobj_of(obj),
19593 [&] (const ClassInfo* cinfo) {
19594 ty |= lookup_public_prop_impl(
19595 *m_data,
19596 cinfo,
19597 sname
19599 return ty.strictSubtypeOf(TCell);
19602 if (!resolved) return TCell;
19603 return ty;
19606 Type Index::lookup_public_prop(const php::Class* cls, SString name) const {
19607 auto const it = m_data->classInfo.find(cls->name);
19608 if (it == end(m_data->classInfo)) {
19609 return TCell;
19611 return lookup_public_prop_impl(*m_data, it->second, name);
19614 bool Index::lookup_class_init_might_raise(Context ctx, res::Class cls) const {
19615 if (auto const ci = cls.cinfo()) {
19616 return class_init_might_raise(*m_data, ctx, ci);
19617 } else if (cls.cinfo2()) {
19618 // Not implemented yet
19619 always_assert(false);
19620 } else {
19621 return true;
19625 Slot
19626 Index::lookup_iface_vtable_slot(const php::Class* cls) const {
19627 return folly::get_default(m_data->ifaceSlotMap, cls->name, kInvalidSlot);
19630 //////////////////////////////////////////////////////////////////////
19633 * Entry point for static property type mutation from the Index. Merge
19634 * `val' into the known type for any accessible `cls'::`name' static
19635 * property. The mutation will be recovered into either
19636 * `publicMutations' or `privateProps' depending on the properties
19637 * found. Mutations to AttrConst properties are ignored, unless
19638 * `ignoreConst' is true.
19640 PropMergeResult Index::merge_static_type(
19641 Context ctx,
19642 PublicSPropMutations& publicMutations,
19643 PropertiesInfo& privateProps,
19644 const Type& cls,
19645 const Type& name,
19646 const Type& val,
19647 bool checkUB,
19648 bool ignoreConst,
19649 bool mustBeReadOnly) const {
19650 ITRACE(
19651 4, "merge_static_type: {} {}::${} {}\n",
19652 show(ctx), show(cls), show(name), show(val)
19654 Trace::Indent _;
19656 assertx(val.subtypeOf(BInitCell));
19658 using R = PropMergeResult;
19660 // In some cases we might try to merge Bottom if we're in
19661 // unreachable code. This won't affect anything, so just skip out
19662 // early.
19663 if (val.subtypeOf(BBottom)) return R{ TBottom, TriBool::No };
19665 // Try to turn the given property name into a static string
19666 auto const sname = [&] () -> SString {
19667 // Non-string names are treated conservatively here. The caller
19668 // should be checking for these and doing the right thing.
19669 if (!is_specialized_string(name)) return nullptr;
19670 return sval_of(name);
19671 }();
19673 // The case where we don't know `cls':
19674 auto const unknownCls = [&] {
19675 if (!sname) {
19676 // Very bad case. We don't know `cls' or the property name. This
19677 // mutation can be affecting anything, so merge it into all
19678 // properties (this drops type information for public
19679 // properties).
19680 ITRACE(4, "unknown class and prop. merging everything\n");
19681 publicMutations.mergeUnknown(ctx);
19682 privateProps.mergeInAllPrivateStatics(
19683 IndexAdaptor { *this }, unctx(val), ignoreConst, mustBeReadOnly
19685 } else {
19686 // Otherwise we don't know `cls', but do know the property
19687 // name. We'll store this mutation separately and union it in to
19688 // any lookup with the same name.
19689 ITRACE(4, "unknown class. merging all props with name {}\n", sname);
19691 publicMutations.mergeUnknownClass(sname, unctx(val));
19693 // Assume that it could possibly affect any private property with
19694 // the same name.
19695 privateProps.mergeInPrivateStatic(
19696 IndexAdaptor { *this }, sname, unctx(val), ignoreConst, mustBeReadOnly
19700 // To be conservative, say we might throw and be conservative about
19701 // conversions.
19702 return PropMergeResult{
19703 loosen_likeness(val),
19704 TriBool::Maybe
19708 // check if we can determine the class.
19709 if (!is_specialized_cls(cls)) return unknownCls();
19711 const ClassInfo* ctxCls = nullptr;
19712 if (ctx.cls) {
19713 auto const rCtx = resolve_class(ctx.cls->name);
19714 // We should only be not able to resolve our own context if the
19715 // class is not instantiable. In that case, the merge can't
19716 // happen.
19717 if (!rCtx) return R{ TBottom, TriBool::No };
19718 ctxCls = rCtx->cinfo();
19719 if (!ctxCls) return unknownCls();
19722 auto const mergePublic = [&] (const ClassInfo* ci,
19723 const php::Prop& prop,
19724 const Type& val) {
19725 publicMutations.mergeKnown(ci, prop, val);
19728 auto const& dcls = dcls_of(cls);
19729 Optional<res::Class> start;
19730 if (dcls.isExact() || dcls.isSub()) {
19731 start = dcls.cls();
19732 } else if (dcls.isIsectAndExact()) {
19733 start = dcls.isectAndExact().first;
19736 Optional<R> result;
19737 auto const resolved = visit_every_dcls_cls(
19738 dcls,
19739 [&] (const ClassInfo* cinfo) {
19740 auto r = merge_static_type_impl(
19741 *m_data,
19742 ctx,
19743 mergePublic,
19744 privateProps,
19745 ctxCls,
19746 cinfo,
19747 sname,
19748 val,
19749 checkUB,
19750 ignoreConst,
19751 mustBeReadOnly,
19752 dcls.isSub() && !sname && cinfo != start->cinfo()
19754 ITRACE(4, "{} -> {}\n", cinfo->cls->name, show(r));
19755 if (!result) {
19756 result.emplace(std::move(r));
19757 } else {
19758 *result |= r;
19760 return true;
19763 if (!resolved) return unknownCls();
19764 assertx(result.has_value());
19765 ITRACE(4, "union -> {}\n", show(*result));
19766 return *result;
19769 //////////////////////////////////////////////////////////////////////
19771 DependencyContext Index::dependency_context(const Context& ctx) const {
19772 return dep_context(*m_data, ctx);
19775 bool Index::using_class_dependencies() const {
19776 return m_data->useClassDependencies;
19779 void Index::use_class_dependencies(bool f) {
19780 if (f != m_data->useClassDependencies) {
19781 m_data->dependencyMap.clear();
19782 m_data->useClassDependencies = f;
19786 void Index::refine_class_constants(const Context& ctx,
19787 const ResolvedConstants& resolved,
19788 DependencyContextSet& deps) {
19789 if (resolved.empty()) return;
19791 auto changed = false;
19792 auto const cls = ctx.func->cls;
19793 assertx(cls);
19794 auto& constants = cls->constants;
19796 for (auto& c : resolved) {
19797 assertx(c.first < constants.size());
19798 auto& cnst = constants[c.first];
19799 assertx(cnst.kind == ConstModifiers::Kind::Value);
19801 always_assert(cnst.val && type(*cnst.val) == KindOfUninit);
19802 if (auto const val = tv(c.second.type)) {
19803 assertx(val->m_type != KindOfUninit);
19804 cnst.val = *val;
19805 // Deleting from the types map is too expensive, so just leave
19806 // any entry. We won't look at it if val is set.
19807 changed = true;
19808 } else if (auto const cinfo =
19809 folly::get_default(m_data->classInfo, cls->name)) {
19810 auto& old = [&] () -> ClsConstInfo& {
19811 if (c.first >= cinfo->clsConstTypes.size()) {
19812 auto const newSize = std::max(c.first+1, resolved.back().first+1);
19813 cinfo->clsConstTypes.resize(newSize, ClsConstInfo { TInitCell, 0 });
19815 return cinfo->clsConstTypes[c.first];
19816 }();
19818 if (c.second.type.strictlyMoreRefined(old.type)) {
19819 always_assert(c.second.refinements > old.refinements);
19820 old = std::move(c.second);
19821 changed = true;
19822 } else {
19823 always_assert_flog(
19824 c.second.type.moreRefined(old.type),
19825 "Class constant type invariant violated for {}::{}\n"
19826 " {} is not at least as refined as {}\n",
19827 ctx.func->cls->name,
19828 cnst.name,
19829 show(c.second.type),
19830 show(old.type)
19836 if (changed) {
19837 find_deps(*m_data, ctx.func, Dep::ClsConst, deps);
19841 void Index::refine_constants(const FuncAnalysisResult& fa,
19842 DependencyContextSet& deps) {
19843 auto const& func = fa.ctx.func;
19844 if (func->cls) return;
19846 auto const cns_name = Constant::nameFromFuncName(func->name);
19847 if (!cns_name) return;
19849 auto const cns = m_data->constants.at(cns_name);
19850 auto const val = tv(fa.inferredReturn);
19851 if (!val) {
19852 always_assert_flog(
19853 type(cns->val) == KindOfUninit,
19854 "Constant value invariant violated in {}.\n"
19855 " Value went from {} to {}",
19856 cns_name,
19857 show(from_cell(cns->val)),
19858 show(fa.inferredReturn)
19860 return;
19863 if (type(cns->val) != KindOfUninit) {
19864 always_assert_flog(
19865 from_cell(cns->val) == fa.inferredReturn,
19866 "Constant value invariant violated in {}.\n"
19867 " Value went from {} to {}",
19868 cns_name,
19869 show(from_cell(cns->val)),
19870 show(fa.inferredReturn)
19872 } else {
19873 cns->val = *val;
19876 find_deps(*m_data, func, Dep::ConstVal, deps);
19879 void Index::refine_return_info(const FuncAnalysisResult& fa,
19880 DependencyContextSet& deps) {
19881 auto const& func = fa.ctx.func;
19882 auto const finfo = func_info(*m_data, func);
19884 auto const error_loc = [&] {
19885 return folly::sformat(
19886 "{} {}{}",
19887 func->unit,
19888 func->cls
19889 ? folly::to<std::string>(func->cls->name->data(), "::")
19890 : std::string{},
19891 func->name
19895 auto dep = Dep{};
19896 if (finfo->retParam == NoLocalId && fa.retParam != NoLocalId) {
19897 // This is just a heuristic; it doesn't mean that the value passed
19898 // in was returned, but that the value of the parameter at the
19899 // point of the RetC was returned. We use it to make (heuristic)
19900 // decisions about whether to do inline interps, so we only allow
19901 // it to change once (otherwise later passes might not do the
19902 // inline interp, and get worse results, which could trigger other
19903 // assertions in Index::refine_*).
19904 dep = Dep::ReturnTy;
19905 finfo->retParam = fa.retParam;
19908 auto unusedParams = ~fa.usedParams;
19909 if (finfo->unusedParams != unusedParams) {
19910 dep = Dep::ReturnTy;
19911 always_assert_flog(
19912 (finfo->unusedParams | unusedParams) == unusedParams,
19913 "Index unusedParams decreased in {}.\n",
19914 error_loc()
19916 finfo->unusedParams = unusedParams;
19919 auto resetFuncFamilies = false;
19920 if (fa.inferredReturn.strictlyMoreRefined(finfo->returnTy)) {
19921 if (finfo->returnRefinements < options.returnTypeRefineLimit) {
19922 finfo->returnTy = fa.inferredReturn;
19923 // We've modifed the return type, so reset any cached FuncFamily
19924 // return types.
19925 resetFuncFamilies = true;
19926 dep = is_scalar(fa.inferredReturn)
19927 ? Dep::ReturnTy | Dep::InlineDepthLimit : Dep::ReturnTy;
19928 finfo->returnRefinements += fa.localReturnRefinements + 1;
19929 if (finfo->returnRefinements > options.returnTypeRefineLimit) {
19930 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19932 } else {
19933 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
19935 } else {
19936 always_assert_flog(
19937 fa.inferredReturn.moreRefined(finfo->returnTy),
19938 "Index return type invariant violated in {}.\n"
19939 " {} is not at least as refined as {}\n",
19940 error_loc(),
19941 show(fa.inferredReturn),
19942 show(finfo->returnTy)
19946 always_assert_flog(
19947 !finfo->effectFree || fa.effectFree,
19948 "Index effectFree changed from true to false in {} {}.\n",
19949 func->unit,
19950 func_fullname(*func)
19953 if (finfo->effectFree != fa.effectFree) {
19954 finfo->effectFree = fa.effectFree;
19955 dep = Dep::InlineDepthLimit | Dep::ReturnTy;
19958 if (dep != Dep{}) {
19959 find_deps(*m_data, func, dep, deps);
19960 if (resetFuncFamilies) {
19961 assertx(has_dep(dep, Dep::ReturnTy));
19962 for (auto const ff : finfo->families) {
19963 // Reset the cached return type information for all the
19964 // FuncFamilies this function is a part of. Always reset the
19965 // "all" information, and if there's regular subset
19966 // information, reset that too.
19967 if (!ff->m_all.m_returnTy.reset() &&
19968 (!ff->m_regular || !ff->m_regular->m_returnTy.reset())) {
19969 continue;
19971 // Only load the deps for this func family if we're the ones
19972 // who successfully reset. Only one thread needs to do it.
19973 find_deps(*m_data, ff, Dep::ReturnTy, deps);
19979 bool Index::refine_closure_use_vars(const php::Class* cls,
19980 const CompactVector<Type>& vars) {
19981 assertx(is_closure(*cls));
19983 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
19984 always_assert_flog(
19985 vars[i].equivalentlyRefined(unctx(vars[i])),
19986 "Closure cannot have a used var with a context dependent type"
19990 auto& current = [&] () -> CompactVector<Type>& {
19991 std::lock_guard<std::mutex> _{closure_use_vars_mutex};
19992 return m_data->closureUseVars[cls];
19993 }();
19995 always_assert(current.empty() || current.size() == vars.size());
19996 if (current.empty()) {
19997 current = vars;
19998 return true;
20001 auto changed = false;
20002 for (auto i = uint32_t{0}; i < vars.size(); ++i) {
20003 if (vars[i].strictSubtypeOf(current[i])) {
20004 changed = true;
20005 current[i] = vars[i];
20006 } else {
20007 always_assert_flog(
20008 vars[i].moreRefined(current[i]),
20009 "Index closure_use_var invariant violated in {}.\n"
20010 " {} is not at least as refined as {}\n",
20011 cls->name,
20012 show(vars[i]),
20013 show(current[i])
20018 return changed;
20021 template<class Container>
20022 void refine_private_propstate(Container& cont,
20023 const php::Class* cls,
20024 const PropState& state) {
20025 assertx(!is_used_trait(*cls));
20026 auto* elm = [&] () -> typename Container::value_type* {
20027 std::lock_guard<std::mutex> _{private_propstate_mutex};
20028 auto it = cont.find(cls);
20029 if (it == end(cont)) {
20030 if (!state.empty()) cont[cls] = state;
20031 return nullptr;
20033 return &*it;
20034 }();
20036 if (!elm) return;
20038 for (auto& kv : state) {
20039 auto& target = elm->second[kv.first];
20040 assertx(target.tc == kv.second.tc);
20041 always_assert_flog(
20042 kv.second.ty.moreRefined(target.ty),
20043 "PropState refinement failed on {}::${} -- {} was not a subtype of {}\n",
20044 cls->name->data(),
20045 kv.first->data(),
20046 show(kv.second.ty),
20047 show(target.ty)
20049 target.ty = kv.second.ty;
20051 if (kv.second.everModified) {
20052 always_assert_flog(
20053 target.everModified,
20054 "PropState refinement failed on {}::${} -- "
20055 "everModified flag went from false to true\n",
20056 cls->name->data(),
20057 kv.first->data()
20059 } else {
20060 target.everModified = false;
20065 void Index::refine_private_props(const php::Class* cls,
20066 const PropState& state) {
20067 refine_private_propstate(m_data->privatePropInfo, cls, state);
20070 void Index::refine_private_statics(const php::Class* cls,
20071 const PropState& state) {
20072 // We can't store context dependent types in private statics since they
20073 // could be accessed using different contexts.
20074 auto cleanedState = PropState{};
20075 for (auto const& prop : state) {
20076 auto& elem = cleanedState[prop.first];
20077 elem.ty = unctx(prop.second.ty);
20078 elem.tc = prop.second.tc;
20079 elem.attrs = prop.second.attrs;
20080 elem.everModified = prop.second.everModified;
20083 refine_private_propstate(m_data->privateStaticPropInfo, cls, cleanedState);
20086 void Index::record_public_static_mutations(const php::Func& func,
20087 PublicSPropMutations mutations) {
20088 if (!mutations.m_data) {
20089 m_data->publicSPropMutations.erase(&func);
20090 return;
20092 m_data->publicSPropMutations.insert_or_assign(&func, std::move(mutations));
20095 void Index::update_prop_initial_values(const Context& ctx,
20096 const ResolvedPropInits& resolved,
20097 DependencyContextSet& deps) {
20098 auto& props = const_cast<php::Class*>(ctx.cls)->properties;
20100 auto changed = false;
20101 for (auto const& [idx, info] : resolved) {
20102 assertx(idx < props.size());
20103 auto& prop = props[idx];
20105 auto const allResolved = [&] {
20106 if (prop.typeConstraint.isUnresolved()) return false;
20107 for (auto const& ub : prop.ubs.m_constraints) {
20108 if (ub.isUnresolved()) return false;
20110 return true;
20113 if (info.satisfies) {
20114 if (!(prop.attrs & AttrInitialSatisfiesTC) && allResolved()) {
20115 attribute_setter(prop.attrs, true, AttrInitialSatisfiesTC);
20116 changed = true;
20118 } else {
20119 always_assert_flog(
20120 !(prop.attrs & AttrInitialSatisfiesTC),
20121 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
20122 " Went from true to false",
20123 ctx.cls->name, prop.name
20127 always_assert_flog(
20128 IMPLIES(!(prop.attrs & AttrDeepInit), !info.deepInit),
20129 "AttrDeepInit invariant violated for {}::{}\n"
20130 " Went from false to true",
20131 ctx.cls->name, prop.name
20133 attribute_setter(prop.attrs, info.deepInit, AttrDeepInit);
20135 if (type(info.val) != KindOfUninit) {
20136 always_assert_flog(
20137 type(prop.val) == KindOfUninit ||
20138 from_cell(prop.val) == from_cell(info.val),
20139 "Property initial value invariant violated for {}::{}\n"
20140 " Value went from {} to {}",
20141 ctx.cls->name, prop.name,
20142 show(from_cell(prop.val)), show(from_cell(info.val))
20144 prop.val = info.val;
20145 } else {
20146 always_assert_flog(
20147 type(prop.val) == KindOfUninit,
20148 "Property initial value invariant violated for {}::{}\n"
20149 " Value went from {} to not set",
20150 ctx.cls->name, prop.name,
20151 show(from_cell(prop.val))
20155 if (!changed) return;
20157 auto const it = m_data->classInfo.find(ctx.cls->name);
20158 if (it == end(m_data->classInfo)) return;
20159 auto const cinfo = it->second;
20161 // Both a pinit and a sinit can have resolved property values. When
20162 // analyzing constants we'll process each function separately and
20163 // potentially in different threads. Both will want to inspect the
20164 // property Attrs and the hasBadInitialPropValues. So, if we reach
20165 // here, take a lock to ensure both don't stomp on each other.
20166 static std::array<std::mutex, 256> locks;
20167 auto& lock = locks[pointer_hash<const php::Class>{}(ctx.cls) % locks.size()];
20168 std::lock_guard<std::mutex> _{lock};
20170 auto const noBad = std::all_of(
20171 begin(props), end(props),
20172 [] (const php::Prop& prop) {
20173 return bool(prop.attrs & AttrInitialSatisfiesTC);
20177 if (cinfo->hasBadInitialPropValues) {
20178 if (noBad) {
20179 cinfo->hasBadInitialPropValues = false;
20180 find_deps(*m_data, ctx.cls, Dep::PropBadInitialValues, deps);
20182 } else {
20183 // If it's false, another thread got here before us and set it to
20184 // false.
20185 always_assert(noBad);
20189 void Index::refine_public_statics(DependencyContextSet& deps) {
20190 trace_time update("update public statics");
20192 // Union together the mutations for each function, including the functions
20193 // which weren't analyzed this round.
20194 auto nothing_known = false;
20195 PublicSPropMutations::UnknownMap unknown;
20196 PublicSPropMutations::KnownMap known;
20197 for (auto const& mutations : m_data->publicSPropMutations) {
20198 if (!mutations.second.m_data) continue;
20199 if (mutations.second.m_data->m_nothing_known) {
20200 nothing_known = true;
20201 break;
20204 for (auto const& kv : mutations.second.m_data->m_unknown) {
20205 auto const ret = unknown.insert(kv);
20206 if (!ret.second) ret.first->second |= kv.second;
20208 for (auto const& kv : mutations.second.m_data->m_known) {
20209 auto const ret = known.insert(kv);
20210 if (!ret.second) ret.first->second |= kv.second;
20214 if (nothing_known) {
20215 // We cannot go from knowing the types to not knowing the types (this is
20216 // equivalent to widening the types).
20217 always_assert(!m_data->seenPublicSPropMutations);
20218 return;
20220 m_data->seenPublicSPropMutations = true;
20222 // Refine known class state
20223 parallel::for_each(
20224 m_data->allClassInfos,
20225 [&] (std::unique_ptr<ClassInfo>& cinfo) {
20226 for (auto const& prop : cinfo->cls->properties) {
20227 if (!(prop.attrs & (AttrPublic|AttrProtected)) ||
20228 !(prop.attrs & AttrStatic)) {
20229 continue;
20232 auto knownClsType = [&] {
20233 auto const it = known.find(
20234 PublicSPropMutations::KnownKey { cinfo.get(), prop.name }
20236 // If we didn't see a mutation, the type is TBottom.
20237 return it == end(known) ? TBottom : it->second;
20238 }();
20240 auto unknownClsType = [&] {
20241 auto const it = unknown.find(prop.name);
20242 // If we didn't see a mutation, the type is TBottom.
20243 return it == end(unknown) ? TBottom : it->second;
20244 }();
20246 // We can't keep context dependent types in public properties.
20247 auto newType = adjust_type_for_prop(
20248 IndexAdaptor { *this },
20249 *cinfo->cls,
20250 &prop.typeConstraint,
20251 unctx(union_of(std::move(knownClsType), std::move(unknownClsType)))
20254 auto& entry = cinfo->publicStaticProps[prop.name];
20256 if (!newType.is(BBottom)) {
20257 always_assert_flog(
20258 entry.everModified,
20259 "Static property index invariant violated on {}::{}:\n"
20260 " everModified flag went from false to true",
20261 cinfo->cls->name,
20262 prop.name
20264 } else {
20265 entry.everModified = false;
20268 // The type from the mutations doesn't contain the in-class
20269 // initializer types. Add that here.
20270 auto effectiveType = union_of(
20271 std::move(newType),
20272 initial_type_for_public_sprop(*this, *cinfo->cls, prop)
20276 * We may only shrink the types we recorded for each property. (If a
20277 * property type ever grows, the interpreter could infer something
20278 * incorrect at some step.)
20280 always_assert_flog(
20281 effectiveType.subtypeOf(entry.inferredType),
20282 "Static property index invariant violated on {}::{}:\n"
20283 " {} is not a subtype of {}",
20284 cinfo->cls->name,
20285 prop.name,
20286 show(effectiveType),
20287 show(entry.inferredType)
20290 // Put a limit on the refinements to ensure termination. Since
20291 // we only ever refine types, we can stop at any point and still
20292 // maintain correctness.
20293 if (effectiveType.strictSubtypeOf(entry.inferredType)) {
20294 if (entry.refinements + 1 < options.publicSPropRefineLimit) {
20295 find_deps(*m_data, &prop, Dep::PublicSProp, deps);
20296 entry.inferredType = std::move(effectiveType);
20297 ++entry.refinements;
20298 } else {
20299 FTRACE(
20300 1, "maxed out public static property refinements for {}:{}\n",
20301 cinfo->cls->name,
20302 prop.name
20311 bool Index::frozen() const {
20312 return m_data->frozen;
20315 void Index::freeze() {
20316 m_data->frozen = true;
20317 m_data->ever_frozen = true;
20320 Type AnalysisIndex::unserialize_type(Type t) const {
20321 return unserialize_classes(AnalysisIndexAdaptor { *this }, std::move(t));
20325 * Note that these functions run in separate threads, and
20326 * intentionally don't bump Trace::hhbbc_time. If you want to see
20327 * these times, set TRACE=hhbbc_time:1
20329 #define CLEAR(x) \
20331 trace_time _{"clearing " #x}; \
20332 _.ignore_client_stats(); \
20333 (x).clear(); \
20336 void Index::cleanup_for_final() {
20337 trace_time _{"cleanup for final", m_data->sample};
20338 CLEAR(m_data->dependencyMap);
20341 void Index::cleanup_post_emit() {
20342 trace_time _{"cleanup post emit", m_data->sample};
20344 std::vector<std::function<void()>> clearers;
20345 #define CLEAR_PARALLEL(x) clearers.push_back([&] CLEAR(x));
20346 CLEAR_PARALLEL(m_data->classes);
20347 CLEAR_PARALLEL(m_data->funcs);
20348 CLEAR_PARALLEL(m_data->typeAliases);
20349 CLEAR_PARALLEL(m_data->enums);
20350 CLEAR_PARALLEL(m_data->constants);
20351 CLEAR_PARALLEL(m_data->modules);
20352 CLEAR_PARALLEL(m_data->units);
20354 CLEAR_PARALLEL(m_data->classClosureMap);
20355 CLEAR_PARALLEL(m_data->classExtraMethodMap);
20357 CLEAR_PARALLEL(m_data->classInfo);
20359 CLEAR_PARALLEL(m_data->privatePropInfo);
20360 CLEAR_PARALLEL(m_data->privateStaticPropInfo);
20361 CLEAR_PARALLEL(m_data->publicSPropMutations);
20362 CLEAR_PARALLEL(m_data->ifaceSlotMap);
20363 CLEAR_PARALLEL(m_data->closureUseVars);
20365 CLEAR_PARALLEL(m_data->methodFamilies);
20367 CLEAR_PARALLEL(m_data->funcFamilies);
20368 CLEAR_PARALLEL(m_data->funcFamilyStaticInfos);
20370 CLEAR_PARALLEL(m_data->clsConstLookupCache);
20372 CLEAR_PARALLEL(m_data->foldableReturnTypeMap);
20373 CLEAR_PARALLEL(m_data->contextualReturnTypes);
20375 parallel::for_each(clearers, [] (const std::function<void()>& f) { f(); });
20378 trace_time t{"reset funcInfo"};
20379 t.ignore_client_stats();
20380 parallel::for_each(
20381 m_data->funcInfo,
20382 [] (auto& u) {
20383 u.returnTy = TBottom;
20384 u.families.clear();
20387 m_data->funcInfo.clear();
20390 // Class-infos and program need to be freed after all Type instances
20391 // are destroyed, as Type::checkInvariants may try to access them.
20394 trace_time t{"reset allClassInfos"};
20395 t.ignore_client_stats();
20396 parallel::for_each(m_data->allClassInfos, [] (auto& u) { u.reset(); });
20397 m_data->allClassInfos.clear();
20401 trace_time t{"reset program"};
20402 t.ignore_client_stats();
20403 parallel::for_each(m_data->program->units, [] (auto& u) { u.reset(); });
20404 parallel::for_each(m_data->program->classes, [] (auto& u) { u.reset(); });
20405 parallel::for_each(m_data->program->funcs, [] (auto& f) { f.reset(); });
20406 m_data->program.reset();
20410 void Index::thaw() {
20411 m_data->frozen = false;
20414 //////////////////////////////////////////////////////////////////////
20416 FuncClsUnit AnalysisWorklist::next() {
20417 if (list.empty()) return FuncClsUnit{};
20418 auto n = list.front();
20419 in.erase(n);
20420 list.pop_front();
20421 return n;
20424 void AnalysisWorklist::schedule(FuncClsUnit fc) {
20425 assertx(IMPLIES(fc.cls(), !is_closure(*fc.cls())));
20426 if (!in.emplace(fc).second) return;
20427 ITRACE(2, "scheduling {} onto worklist\n", show(fc));
20428 list.emplace_back(fc);
20431 //////////////////////////////////////////////////////////////////////
20433 bool AnalysisDeps::add(Class c, bool inTypeCns) {
20434 return inTypeCns
20435 ? typeCnsClasses.emplace(c.name).second
20436 : classes.emplace(c.name).second;
20439 bool AnalysisDeps::add(ConstIndex cns, bool inTypeCns) {
20440 // Dependency on class constant implies a dependency on the class as
20441 // well.
20442 add(Class { cns.cls }, inTypeCns);
20443 return inTypeCns
20444 ? typeCnsClsConstants.emplace(cns).second
20445 : clsConstants.emplace(cns).second;
20448 bool AnalysisDeps::add(Constant cns) {
20449 // Dependency on top-level constant implies a dependency on the
20450 // 86cinit initialized as well (which may not even exist).
20451 add(Func { HPHP::Constant::funcNameFromName(cns.name) }, Type::Meta);
20452 return constants.emplace(cns.name).second;
20455 bool AnalysisDeps::add(AnyClassConstant any, bool inTypeCns) {
20456 // Dependency on class constant implies a dependency on the class as
20457 // well.
20458 add(Class { any.name }, inTypeCns);
20459 return inTypeCns
20460 ? typeCnsAnyClsConstants.emplace(any.name).second
20461 : anyClsConstants.emplace(any.name).second;
20464 AnalysisDeps::Type AnalysisDeps::add(const php::Func& f, Type t) {
20465 return f.cls
20466 ? add(MethRef { f }, t)
20467 : add(Func { f.name }, t);
20470 AnalysisDeps::Type AnalysisDeps::add(MethRef m, Type t) {
20471 add(Class { m.cls });
20472 return merge(methods[m], t | Type::Meta);
20475 AnalysisDeps::Type AnalysisDeps::add(Func f, Type t) {
20476 return merge(funcs[f.name], t | Type::Meta);
20479 AnalysisDeps::Type AnalysisDeps::merge(Type& o, Type n) {
20480 auto const added = n - o;
20481 o |= n;
20482 return added;
20485 AnalysisDeps& AnalysisDeps::operator|=(const AnalysisDeps& o) {
20486 clsConstants.insert(begin(o.clsConstants), end(o.clsConstants));
20487 classes.insert(begin(o.classes), end(o.classes));
20488 constants.insert(begin(o.constants), end(o.constants));
20489 anyClsConstants.insert(begin(o.anyClsConstants), end(o.anyClsConstants));
20490 typeCnsClasses.insert(begin(o.typeCnsClasses), end(o.typeCnsClasses));
20491 typeCnsClsConstants.insert(
20492 begin(o.typeCnsClsConstants),
20493 end(o.typeCnsClsConstants)
20495 typeCnsAnyClsConstants.insert(
20496 begin(o.typeCnsAnyClsConstants),
20497 end(o.typeCnsAnyClsConstants)
20499 for (auto const [name, t] : o.funcs) funcs[name] |= t;
20500 for (auto const [meth, t] : o.methods) methods[meth] |= t;
20501 return *this;
20504 std::string show(AnalysisDeps::Type t) {
20505 using T = AnalysisDeps::Type;
20506 std::string out;
20507 auto const add = [&] (const char* s) {
20508 folly::format(&out, "{}{}", out.empty() ? "" : ",", s);
20510 if (t & T::Meta) add("meta");
20511 if (t & T::RetType) add("return type");
20512 if (t & T::ScalarRetType) add("scalar return type");
20513 if (t & T::RetParam) add("returned param");
20514 if (t & T::UnusedParams) add("unused params");
20515 if (t & T::Bytecode) add("bytecode");
20516 return out;
20519 std::string show(const AnalysisDeps& d) {
20520 using namespace folly::gen;
20521 auto const toCpp = [] (SString n) { return n->toCppString(); };
20523 std::string out;
20524 if (!d.classes.empty()) {
20525 folly::format(
20526 &out, " classes: {}\n",
20527 from(d.classes) | map(toCpp) | unsplit<std::string>(", ")
20530 if (!d.funcs.empty()) {
20531 folly::format(
20532 &out, " funcs: {}\n",
20533 from(d.funcs)
20534 | map([&] (auto const& p) {
20535 return folly::sformat("{} -> [{}]", toCpp(p.first), show(p.second));
20537 | unsplit<std::string>(", ")
20540 if (!d.methods.empty()) {
20541 folly::format(
20542 &out, " methods: {}\n",
20543 from(d.methods)
20544 | map([] (auto const& p) {
20545 return folly::sformat("{} -> [{}]", show(p.first), show(p.second));
20547 | unsplit<std::string>(", ")
20550 if (!d.clsConstants.empty()) {
20551 folly::format(
20552 &out, " class-constants: {}\n",
20553 from(d.clsConstants)
20554 | map([] (ConstIndex idx) { return show(idx); })
20555 | unsplit<std::string>(", ")
20558 if (!d.anyClsConstants.empty()) {
20559 folly::format(
20560 &out, " any class-constants: {}\n",
20561 from(d.anyClsConstants) | map(toCpp) | unsplit<std::string>(", ")
20564 if (!d.typeCnsClasses.empty()) {
20565 folly::format(
20566 &out, " type-cns classes: {}\n",
20567 from(d.typeCnsClasses) | map(toCpp) | unsplit<std::string>(", ")
20570 if (!d.typeCnsClsConstants.empty()) {
20571 folly::format(
20572 &out, " type-cns class-constants: {}\n",
20573 from(d.typeCnsClsConstants)
20574 | map([] (ConstIndex idx) { return show(idx); })
20575 | unsplit<std::string>(", ")
20578 if (!d.typeCnsAnyClsConstants.empty()) {
20579 folly::format(
20580 &out, " type-cns any class-constants: {}\n",
20581 from(d.typeCnsAnyClsConstants) | map(toCpp) | unsplit<std::string>(", ")
20585 if (out.empty()) out = " (none)\n";
20586 return out;
20589 //////////////////////////////////////////////////////////////////////
20591 void AnalysisChangeSet::changed(ConstIndex idx) {
20592 clsConstants.emplace(idx);
20595 void AnalysisChangeSet::changed(const php::Constant& c) {
20596 constants.emplace(c.name);
20599 void AnalysisChangeSet::changed(const php::Func& f, Type t) {
20600 assertx(AnalysisDeps::isValidForChanges(t));
20601 if (f.cls) {
20602 methods[MethRef { f }] |= t;
20603 } else {
20604 funcs[f.name] |= t;
20608 void AnalysisChangeSet::fixed(ConstIndex idx) {
20609 fixedClsConstants.emplace(idx);
20612 void AnalysisChangeSet::fixed(const php::Class& cls) {
20613 allClsConstantsFixed.emplace(cls.name);
20616 void AnalysisChangeSet::fixed(const php::Unit& unit) {
20617 unitsFixed.emplace(unit.filename);
20620 void AnalysisChangeSet::typeCnsName(const php::Class& cls,
20621 Class name) {
20622 if (cls.name->tsame(name.name)) return;
20623 clsTypeCnsNames[cls.name].emplace(name.name);
20626 void AnalysisChangeSet::typeCnsName(const php::Unit& unit,
20627 Class name) {
20628 unitTypeCnsNames[unit.filename].emplace(name.name);
20631 void AnalysisChangeSet::filter(const TSStringSet& keepClasses,
20632 const FSStringSet& keepFuncs,
20633 const SStringSet& keepUnits,
20634 const SStringSet& keepConstants) {
20635 folly::erase_if(
20636 funcs, [&] (auto const& p) { return !keepFuncs.count(p.first); }
20638 folly::erase_if(
20639 methods, [&] (auto const& p) { return !keepClasses.count(p.first.cls); }
20641 folly::erase_if(
20642 constants, [&] (SString s) { return !keepConstants.count(s); }
20644 folly::erase_if(
20645 clsConstants, [&] (ConstIndex idx) { return !keepClasses.count(idx.cls); }
20647 folly::erase_if(
20648 fixedClsConstants,
20649 [&] (ConstIndex idx) {
20650 return !keepClasses.count(idx.cls) || allClsConstantsFixed.count(idx.cls);
20653 folly::erase_if(
20654 allClsConstantsFixed, [&] (SString s) { return !keepClasses.count(s); }
20656 folly::erase_if(
20657 unitsFixed, [&] (SString s) { return !keepUnits.count(s); }
20659 folly::erase_if(
20660 clsTypeCnsNames, [&] (auto const& p) { return !keepClasses.count(p.first); }
20662 folly::erase_if(
20663 unitTypeCnsNames, [&] (auto const& p) { return !keepUnits.count(p.first); }
20667 //////////////////////////////////////////////////////////////////////
20669 namespace {
20671 template <typename H, typename V, typename E, typename F, typename C>
20672 std::vector<SString>
20673 map_to_sorted_key_vec(
20674 const hphp_fast_map<SString, V, H, E>& m,
20675 const F& f,
20676 const C& c
20678 std::vector<SString> keys;
20679 keys.reserve(m.size());
20680 for (auto const& [k, v] : m) {
20681 if (!f(v)) continue;
20682 keys.emplace_back(k);
20684 std::sort(begin(keys), end(keys), c);
20685 keys.shrink_to_fit();
20686 return keys;
20691 std::vector<SString> AnalysisInput::classNames() const {
20692 return map_to_sorted_key_vec(
20693 classes,
20694 [] (Kind k) { return any(k & Kind::Rep); },
20695 string_data_lt_type{}
20699 std::vector<SString> AnalysisInput::funcNames() const {
20700 return map_to_sorted_key_vec(
20701 funcs,
20702 [] (Kind k) { return any(k & Kind::Rep); },
20703 string_data_lt_func{}
20707 std::vector<SString> AnalysisInput::unitNames() const {
20708 return map_to_sorted_key_vec(
20709 units,
20710 [] (Kind k) { return any(k & Kind::Rep); },
20711 string_data_lt{}
20715 std::vector<SString> AnalysisInput::cinfoNames() const {
20716 return map_to_sorted_key_vec(
20717 classes,
20718 [] (Kind k) { return any(k & Kind::Rep) && any(k & Kind::Info); },
20719 string_data_lt_type{}
20723 std::vector<SString> AnalysisInput::minfoNames() const {
20724 return map_to_sorted_key_vec(
20725 classes,
20726 [] (Kind k) { return any(k & Kind::Rep) && any(k & Kind::MInfo); },
20727 string_data_lt_type{}
20731 AnalysisInput::Tuple AnalysisInput::toTuple(Ref<Meta> meta) {
20732 auto const sort = [] (auto const& m, auto const& c) {
20733 return map_to_sorted_key_vec(m, [] (Kind) { return true; }, c);
20736 UniquePtrRefVec<php::Class> outClasses;
20737 UniquePtrRefVec<php::ClassBytecode> outClassBC;
20738 RefVec<AnalysisIndexCInfo> outCInfos;
20739 RefVec<AnalysisIndexMInfo> outMInfos;
20740 UniquePtrRefVec<php::Class> outDepClasses;
20742 for (auto const n : sort(classes, string_data_lt_type{})) {
20743 auto const k = classes.at(n);
20744 if (any(k & Kind::Rep)) {
20745 outClasses.emplace_back(index->classRefs.at(n));
20747 if (any(k & Kind::Bytecode)) {
20748 outClassBC.emplace_back(index->classBytecodeRefs.at(n));
20750 if (any(k & Kind::Info)) {
20751 outCInfos.emplace_back(
20752 index->classInfoRefs.at(n).cast<AnalysisIndexCInfo>()
20755 if (any(k & Kind::MInfo)) {
20756 outMInfos.emplace_back(
20757 index->uninstantiableClsMethRefs.at(n).cast<AnalysisIndexMInfo>()
20760 if (any(k & Kind::Dep)) {
20761 outDepClasses.emplace_back(index->classRefs.at(n));
20764 decltype(classes){}.swap(classes);
20765 outClasses.shrink_to_fit();
20766 outClassBC.shrink_to_fit();
20767 outCInfos.shrink_to_fit();
20768 outMInfos.shrink_to_fit();
20769 outDepClasses.shrink_to_fit();
20771 UniquePtrRefVec<php::Func> outFuncs;
20772 UniquePtrRefVec<php::FuncBytecode> outFuncBC;
20773 RefVec<AnalysisIndexFInfo> outFInfos;
20774 UniquePtrRefVec<php::Func> outDepFuncs;
20776 for (auto const n : sort(funcs, string_data_lt_func{})) {
20777 auto const k = funcs.at(n);
20778 if (any(k & Kind::Rep)) {
20779 outFuncs.emplace_back(index->funcRefs.at(n));
20781 if (any(k & Kind::Bytecode)) {
20782 outFuncBC.emplace_back(index->funcBytecodeRefs.at(n));
20784 if (any(k & Kind::Info)) {
20785 outFInfos.emplace_back(
20786 index->funcInfoRefs.at(n).cast<AnalysisIndexFInfo>()
20789 if (any(k & Kind::Dep)) {
20790 outDepFuncs.emplace_back(index->funcRefs.at(n));
20793 decltype(funcs){}.swap(funcs);
20794 outFuncs.shrink_to_fit();
20795 outFuncBC.shrink_to_fit();
20796 outFInfos.shrink_to_fit();
20797 outDepFuncs.shrink_to_fit();
20799 UniquePtrRefVec<php::Unit> outUnits;
20800 UniquePtrRefVec<php::Unit> outDepUnits;
20802 for (auto const n : sort(units, string_data_lt{})) {
20803 auto const k = units.at(n);
20804 if (any(k & Kind::Rep)) {
20805 outUnits.emplace_back(index->unitRefs.at(n));
20807 if (any(k & Kind::Dep)) {
20808 outDepUnits.emplace_back(index->unitRefs.at(n));
20811 decltype(units){}.swap(units);
20812 outUnits.shrink_to_fit();
20813 outDepUnits.shrink_to_fit();
20815 return Tuple {
20816 std::move(outClasses),
20817 std::move(outFuncs),
20818 std::move(outUnits),
20819 std::move(outClassBC),
20820 std::move(outFuncBC),
20821 std::move(outCInfos),
20822 std::move(outFInfos),
20823 std::move(outMInfos),
20824 std::move(outDepClasses),
20825 std::move(outDepFuncs),
20826 std::move(outDepUnits),
20827 std::move(meta)
20831 //////////////////////////////////////////////////////////////////////
20833 namespace {
20835 // Many BucketSets are identical, so we intern them in the below
20836 // table, which saves a lot of memory.
20838 struct BucketSetHasher {
20839 size_t operator()(const AnalysisInput::BucketSet& b) const {
20840 return b.hash();
20844 folly_concurrent_hash_map_simd<
20845 AnalysisInput::BucketSet,
20846 std::nullptr_t,
20847 BucketSetHasher
20848 > s_bucketSetIntern;
20850 AnalysisInput::BucketSet s_emptyBucketSet;
20852 // Likewise, when we serde them, we can refer to them by indices
20853 // rather than encoding the same set multiple times.
20854 struct BucketSetSerdeTable {
20855 using A = AnalysisInput;
20857 hphp_fast_map<const A::BucketSet*, size_t> bToIdx;
20858 std::vector<const A::BucketSet*> idxToB;
20860 void encode(BlobEncoder& sd, const A::BucketSet& b) {
20861 if (auto const idx = folly::get_ptr(bToIdx, &b)) {
20862 sd(*idx);
20863 } else {
20864 idxToB.emplace_back(&b);
20865 bToIdx.try_emplace(&b, idxToB.size());
20866 sd(0)(b);
20869 const AnalysisInput::BucketSet* decode(BlobDecoder& sd) {
20870 auto const idx = sd.make<size_t>();
20871 if (!idx) {
20872 auto const b = A::BucketSet::intern(sd.make<A::BucketSet>());
20873 idxToB.emplace_back(b);
20874 return b;
20875 } else {
20876 assertx(idx <= idxToB.size());
20877 return idxToB[idx-1];
20881 thread_local Optional<BucketSetSerdeTable> tl_bucketSetTable;
20885 bool AnalysisInput::BucketSet::isSubset(const BucketSet& o) const {
20886 return
20887 this == &o ||
20888 std::includes(o.buckets.begin(), o.buckets.end(),
20889 buckets.begin(), buckets.end());
20892 bool AnalysisInput::BucketSet::contains(size_t idx) const {
20893 assertx(idx < std::numeric_limits<uint32_t>::max());
20894 return buckets.contains(idx);
20897 size_t AnalysisInput::BucketSet::hash() const {
20898 return folly::hash::hash_range(buckets.begin(), buckets.end());
20901 bool AnalysisInput::BucketSet::empty() const {
20902 return buckets.empty();
20905 void AnalysisInput::BucketSet::add(size_t idx) {
20906 assertx(idx < std::numeric_limits<uint32_t>::max());
20907 buckets.emplace_hint(buckets.end(), idx);
20910 void AnalysisInput::BucketSet::clear() {
20911 buckets.clear();
20914 AnalysisInput::BucketSet&
20915 AnalysisInput::BucketSet::operator|=(const BucketSet& o) {
20916 buckets.insert(folly::sorted_unique, begin(o.buckets), end(o.buckets));
20917 return *this;
20920 const AnalysisInput::BucketSet*
20921 AnalysisInput::BucketSet::intern(BucketSet b) {
20922 if (b.buckets.empty()) return &s_emptyBucketSet;
20923 b.buckets.shrink_to_fit();
20924 return &s_bucketSetIntern.try_emplace(std::move(b)).first->first;
20927 void AnalysisInput::BucketSet::clearIntern() {
20928 s_bucketSetIntern = decltype(s_bucketSetIntern){};
20931 std::string AnalysisInput::BucketSet::toString() const {
20932 using namespace folly::gen;
20933 if (buckets.empty()) return "-";
20934 return from(buckets)
20935 | map([] (uint32_t i) { return std::to_string(i); })
20936 | unsplit<std::string>(",");
20939 void AnalysisInput::BucketPresence::serdeStart() {
20940 assertx(!tl_bucketSetTable);
20941 tl_bucketSetTable.emplace();
20944 void AnalysisInput::BucketPresence::serdeEnd() {
20945 assertx(tl_bucketSetTable);
20946 tl_bucketSetTable.reset();
20949 void AnalysisInput::BucketPresence::serde(BlobEncoder& sd) {
20950 assertx(tl_bucketSetTable);
20951 assertx(present);
20952 assertx(withBC);
20953 assertx(process);
20954 tl_bucketSetTable->encode(sd, *present);
20955 tl_bucketSetTable->encode(sd, *withBC);
20956 tl_bucketSetTable->encode(sd, *process);
20959 void AnalysisInput::BucketPresence::serde(BlobDecoder& sd) {
20960 assertx(tl_bucketSetTable);
20961 present = tl_bucketSetTable->decode(sd);
20962 withBC = tl_bucketSetTable->decode(sd);
20963 process = tl_bucketSetTable->decode(sd);
20964 assertx(present);
20965 assertx(withBC);
20966 assertx(process);
20969 std::string show(const AnalysisInput::BucketPresence& b) {
20970 return folly::sformat(
20971 "present: {} BC: {} process: {}",
20972 b.present->toString(),
20973 b.withBC->toString(),
20974 b.process->toString()
20978 //////////////////////////////////////////////////////////////////////
20980 struct AnalysisScheduler::Bucket {
20981 HierarchicalWorkBucket b;
20984 //////////////////////////////////////////////////////////////////////
20986 AnalysisScheduler::AnalysisScheduler(Index& index)
20987 : index{index}
20988 , totalWorkItems{0}
20989 , lock{std::make_unique<std::mutex>()} {}
20991 AnalysisScheduler::~AnalysisScheduler() {
20992 // Free the BucketSet intern table when the scheduler finishes, as
20993 // it can take a non-trivial amount of memory.
20994 AnalysisInput::BucketSet::clearIntern();
20997 void AnalysisScheduler::registerClass(SString name) {
20998 // Closures are only scheduled as part of the class or func they're
20999 // declared in.
21000 if (is_closure_name(name)) return;
21001 FTRACE(5, "AnalysisScheduler: registering class {}\n", name);
21003 auto const [cState, emplaced1] = classState.try_emplace(name, name);
21004 if (!emplaced1) return;
21006 ++totalWorkItems;
21008 auto const [tState, emplaced2] = traceState.try_emplace(name);
21009 if (emplaced2) traceNames.emplace_back(name);
21010 tState->second.depStates.emplace_back(&cState->second.depState);
21012 classNames.emplace_back(name);
21013 auto const& closures =
21014 folly::get_default(index.m_data->classToClosures, name);
21015 for (auto const clo : closures) {
21016 FTRACE(
21017 5, "AnalysisScheduler: registering closure {} associated with class {}\n",
21018 clo, name
21020 always_assert(classState.try_emplace(clo, clo).second);
21024 void AnalysisScheduler::registerFunc(SString name) {
21025 FTRACE(5, "AnalysisScheduler: registering func {}\n", name);
21027 auto const [fState, emplaced1] = funcState.try_emplace(name, name);
21028 if (!emplaced1) return;
21030 ++totalWorkItems;
21032 funcNames.emplace_back(name);
21034 auto const& closures = folly::get_default(index.m_data->funcToClosures, name);
21035 for (auto const clo : closures) {
21036 FTRACE(
21037 5, "AnalysisScheduler: registering closure {} associated with func {}\n",
21038 clo, name
21040 always_assert(classState.try_emplace(clo, clo).second);
21043 // If this func is a 86cinit, then register the associated constant
21044 // as well.
21045 if (auto const cns = Constant::nameFromFuncName(name)) {
21046 FTRACE(5, "AnalysisScheduler: registering constant {}\n", cns);
21047 always_assert(cnsChanged.try_emplace(cns).second);
21048 auto const unit = index.m_data->funcToUnit.at(name);
21049 // Modifying a global constant func implies the unit will be
21050 // changed too, so the unit must be registered as well.
21051 registerUnit(unit);
21052 traceState.at(unit).depStates.emplace_back(&fState->second.depState);
21053 } else {
21054 auto const [tState, emplaced2] = traceState.try_emplace(name);
21055 if (emplaced2) traceNames.emplace_back(name);
21056 tState->second.depStates.emplace_back(&fState->second.depState);
21060 void AnalysisScheduler::registerUnit(SString name) {
21061 FTRACE(5, "AnalysisScheduler: registering unit {}\n", name);
21063 auto const [uState, emplaced1] = unitState.try_emplace(name, name);
21064 if (!emplaced1) return;
21066 ++totalWorkItems;
21068 auto const [tState, emplaced2] = traceState.try_emplace(name);
21069 if (emplaced2) traceNames.emplace_back(name);
21070 tState->second.depStates.emplace_back(&uState->second.depState);
21072 unitNames.emplace_back(name);
21075 // Called when an analysis job reports back its changes. This makes
21076 // any dependencies affected by the change eligible to run in the next
21077 // analysis round.
21078 void AnalysisScheduler::recordChanges(const AnalysisOutput& output) {
21079 const TSStringSet classes{begin(output.classNames), end(output.classNames)};
21080 const FSStringSet funcs{begin(output.funcNames), end(output.funcNames)};
21081 const SStringSet units{begin(output.unitNames), end(output.unitNames)};
21083 auto const& changed = output.meta.changed;
21085 // Sanity check that this bucket should actually be modifying the
21086 // entity.
21087 auto const valid = [&] (SString name, DepState::Kind kind) {
21088 switch (kind) {
21089 case DepState::Func:
21090 return funcs.count(name) || output.meta.removedFuncs.count(name);
21091 case DepState::Class: {
21092 if (!is_closure_name(name)) return (bool)classes.count(name);
21093 auto const ctx = folly::get_default(index.m_data->closureToClass, name);
21094 if (ctx) return (bool)classes.count(ctx);
21095 auto const f = folly::get_default(index.m_data->closureToFunc, name);
21096 always_assert(f);
21097 return (bool)funcs.count(f);
21099 case DepState::Unit:
21100 return (bool)units.count(name);
21104 for (auto const [name, type] : changed.funcs) {
21105 FTRACE(4, "AnalysisScheduler: func {} changed ({})\n", name, show(type));
21106 auto state = folly::get_ptr(funcState, name);
21107 always_assert_flog(
21108 state,
21109 "Trying to mark un-tracked func {} changed",
21110 name
21112 always_assert_flog(
21113 valid(name, DepState::Func),
21114 "Trying to mark func {} as changed from wrong shard",
21115 name
21117 assertx(AnalysisDeps::isValidForChanges(type));
21118 assertx(state->changed == Type::None);
21119 state->changed = type;
21122 for (auto const [meth, type] : changed.methods) {
21123 FTRACE(4, "AnalysisScheduler: method {} changed ({})\n",
21124 show(meth), show(type));
21125 auto state = folly::get_ptr(classState, meth.cls);
21126 always_assert_flog(
21127 state,
21128 "Trying to mark method for un-tracked class {} changed",
21129 meth.cls
21131 always_assert_flog(
21132 valid(meth.cls, DepState::Class),
21133 "Trying to mark method for class {} as changed from wrong shard",
21134 meth.cls
21136 assertx(AnalysisDeps::isValidForChanges(type));
21137 auto& t = state->methodChanges.ensure(meth.idx);
21138 assertx(t == Type::None);
21139 t = type;
21142 for (auto const cns : changed.clsConstants) {
21143 auto state = folly::get_ptr(classState, cns.cls);
21144 always_assert_flog(
21145 state,
21146 "Trying to mark constant for un-tracked class {} changed",
21147 cns.cls
21149 always_assert_flog(
21150 valid(cns.cls, DepState::Class),
21151 "Trying to mark constant for class {} as changed from wrong shard",
21152 cns.cls
21155 if (state->allCnsFixed) continue;
21156 if (cns.idx < state->cnsFixed.size() && state->cnsFixed[cns.idx]) continue;
21157 FTRACE(4, "AnalysisScheduler: class constant {} changed\n", show(cns));
21158 if (cns.idx >= state->cnsChanges.size()) {
21159 state->cnsChanges.resize(cns.idx+1);
21161 assertx(!state->cnsChanges[cns.idx]);
21162 state->cnsChanges[cns.idx] = true;
21165 for (auto const cns : changed.fixedClsConstants) {
21166 auto state = folly::get_ptr(classState, cns.cls);
21167 always_assert_flog(
21168 state,
21169 "Trying to mark constant for un-tracked class {} as fixed",
21170 cns.cls
21172 always_assert_flog(
21173 valid(cns.cls, DepState::Class),
21174 "Trying to mark constant for class {} as fixed from wrong shard",
21175 cns.cls
21178 if (state->allCnsFixed) continue;
21179 if (cns.idx >= state->cnsFixed.size()) {
21180 state->cnsFixed.resize(cns.idx+1);
21182 if (!state->cnsFixed[cns.idx]) {
21183 FTRACE(4, "AnalysisScheduler: class constant {} now fixed\n", show(cns));
21184 state->cnsFixed[cns.idx] = true;
21188 for (auto const cls : changed.allClsConstantsFixed) {
21189 auto state = folly::get_ptr(classState, cls);
21190 always_assert_flog(
21191 state,
21192 "Trying to mark all constants for un-tracked class {} as fixed",
21195 always_assert_flog(
21196 valid(cls, DepState::Class),
21197 "Trying to mark all constants for class {} as fixed from wrong shard",
21200 if (!state->allCnsFixed) {
21201 FTRACE(
21202 4, "AnalysisScheduler: all class constants for {} now fixed\n",
21205 state->allCnsFixed = true;
21206 state->cnsFixed.clear();
21210 for (auto const name : changed.constants) {
21211 FTRACE(4, "AnalysisScheduler: constant {} changed\n", name);
21212 auto state = folly::get_ptr(cnsChanged, name);
21213 always_assert_flog(
21214 state,
21215 "Trying to mark un-tracked constant {} changed",
21216 name
21218 auto const initName = Constant::funcNameFromName(name);
21219 always_assert_flog(
21220 valid(initName, DepState::Func),
21221 "Trying to mark constant {} as changed from wrong shard",
21222 name
21224 assertx(!state->load(std::memory_order_acquire));
21225 state->store(true, std::memory_order_release);
21228 for (auto const unit : changed.unitsFixed) {
21229 auto state = folly::get_ptr(unitState, unit);
21230 always_assert_flog(
21231 state,
21232 "Trying to mark all type-aliases for un-tracked unit {} as fixed",
21233 unit
21235 always_assert_flog(
21236 valid(unit, DepState::Unit),
21237 "Trying to mark all type-aliases for unit {} as fixed from wrong shard",
21238 unit
21240 if (!state->fixed) {
21241 FTRACE(
21242 4, "AnalysisScheduler: all type-aliases for unit {} now fixed\n",
21243 unit
21245 state->fixed = true;
21249 for (auto& [cls, names] : changed.clsTypeCnsNames) {
21250 auto state = folly::get_ptr(classState, cls);
21251 always_assert_flog(
21252 state,
21253 "Trying to record type constant names "
21254 "for un-tracked class {}",
21257 always_assert_flog(
21258 valid(cls, DepState::Class),
21259 "Trying to record type constant names "
21260 "for class {} from wrong shard",
21263 state->typeCnsNames = std::move(names);
21266 for (auto& [unit, names] : changed.unitTypeCnsNames) {
21267 auto state = folly::get_ptr(unitState, unit);
21268 always_assert_flog(
21269 state,
21270 "Trying to record type constant names "
21271 "for un-tracked unit {}",
21272 unit
21274 always_assert_flog(
21275 valid(unit, DepState::Unit),
21276 "Trying to record type constant names "
21277 "for unit {} from wrong shard",
21278 unit
21280 state->typeCnsNames = std::move(names);
21284 // Update the dependencies stored in the scheduler to take into
21285 // account the new set of dependencies reported by an analysis job.
21286 void AnalysisScheduler::updateDepState(AnalysisOutput& output) {
21287 for (size_t i = 0, size = output.classNames.size(); i < size; ++i) {
21288 auto const name = output.classNames[i];
21289 auto it = classState.find(name);
21290 always_assert_flog(
21291 it != end(classState),
21292 "Trying to set deps for un-tracked class {}",
21293 name
21295 auto& state = it->second.depState;
21296 if (is_closure_name(name)) {
21297 assertx(output.meta.classDeps[i].empty());
21298 assertx(state.deps.empty());
21299 continue;
21301 state.deps = std::move(output.meta.classDeps[i]);
21303 for (size_t i = 0, size = output.funcNames.size(); i < size; ++i) {
21304 auto const name = output.funcNames[i];
21305 auto it = funcState.find(name);
21306 always_assert_flog(
21307 it != end(funcState),
21308 "Trying to set deps for un-tracked func {}",
21309 name
21311 auto& state = it->second.depState;
21312 state.deps = std::move(output.meta.funcDeps[i]);
21314 for (size_t i = 0, size = output.unitNames.size(); i < size; ++i) {
21315 auto const name = output.unitNames[i];
21316 auto it = unitState.find(name);
21317 if (it == end(unitState)) {
21318 always_assert_flog(
21319 output.meta.unitDeps[i].empty(),
21320 "Trying to set non-empty deps for un-tracked unit {}",
21321 name
21323 continue;
21325 auto& state = it->second.depState;
21326 state.deps = std::move(output.meta.unitDeps[i]);
21329 // Remove deps for any removed functions, to avoid them spuriously
21330 // being rescheduled again.
21331 for (auto const name : output.meta.removedFuncs) {
21332 auto it = funcState.find(name);
21333 always_assert_flog(
21334 it != end(funcState),
21335 "Trying to reset deps for un-tracked func {}",
21336 name
21338 auto& state = it->second.depState;
21339 state.deps = AnalysisDeps{};
21342 for (auto& [cls, bases] : output.meta.cnsBases) {
21343 auto const state = folly::get_ptr(classState, cls);
21344 always_assert_flog(
21345 state,
21346 "Trying to update cns bases for untracked class {}",
21349 auto old = folly::get_ptr(index.m_data->classToCnsBases, cls);
21350 if (!old) {
21351 assertx(bases.empty());
21352 continue;
21354 if (debug) {
21355 // Class constant base classes should only shrink.
21356 for (auto const b : bases) always_assert(old->contains(b));
21358 *old = std::move(bases);
21362 // Record the output of an analysis job. This means updating the
21363 // various Refs to their new versions, recording new dependencies, and
21364 // recording what has changed (to schedule the next round).
21365 void AnalysisScheduler::record(AnalysisOutput output) {
21366 auto const numClasses = output.classNames.size();
21367 auto const numCInfos = output.cinfoNames.size();
21368 auto const numMInfos = output.minfoNames.size();
21369 auto const numFuncs = output.funcNames.size();
21370 auto const numUnits = output.unitNames.size();
21371 assertx(numClasses == output.classes.size());
21372 assertx(numClasses == output.clsBC.size());
21373 assertx(numCInfos == output.cinfos.size());
21374 assertx(numMInfos == output.minfos.size());
21375 assertx(numClasses == output.meta.classDeps.size());
21376 assertx(numFuncs == output.funcs.size());
21377 assertx(numFuncs == output.funcBC.size());
21378 assertx(numFuncs == output.finfos.size());
21379 assertx(numFuncs == output.meta.funcDeps.size());
21380 assertx(numUnits == output.units.size());
21381 assertx(numUnits == output.meta.unitDeps.size());
21383 // Update Ref mappings:
21385 for (size_t i = 0; i < numUnits; ++i) {
21386 auto const name = output.unitNames[i];
21387 index.m_data->unitRefs.at(name) = std::move(output.units[i]);
21390 for (size_t i = 0; i < numClasses; ++i) {
21391 auto const name = output.classNames[i];
21392 index.m_data->classRefs.at(name) = std::move(output.classes[i]);
21393 index.m_data->classBytecodeRefs.at(name) = std::move(output.clsBC[i]);
21395 for (size_t i = 0; i < numCInfos; ++i) {
21396 auto const name = output.cinfoNames[i];
21397 index.m_data->classInfoRefs.at(name) =
21398 output.cinfos[i].cast<std::unique_ptr<ClassInfo2>>();
21400 for (size_t i = 0; i < numMInfos; ++i) {
21401 auto const name = output.minfoNames[i];
21402 index.m_data->uninstantiableClsMethRefs.at(name) =
21403 output.minfos[i].cast<std::unique_ptr<MethodsWithoutCInfo>>();
21405 for (size_t i = 0; i < numFuncs; ++i) {
21406 auto const name = output.funcNames[i];
21407 index.m_data->funcRefs.at(name) = std::move(output.funcs[i]);
21408 index.m_data->funcBytecodeRefs.at(name) = std::move(output.funcBC[i]);
21409 index.m_data->funcInfoRefs.at(name) =
21410 output.finfos[i].cast<std::unique_ptr<FuncInfo2>>();
21413 recordChanges(output);
21414 updateDepState(output);
21416 // If the analysis job optimized away any 86cinit functions, record
21417 // that here so they can be later removed from our tables.
21418 if (!output.meta.removedFuncs.empty()) {
21419 // This is relatively rare, so a lock is fine.
21420 std::lock_guard<std::mutex> _{*lock};
21421 funcsToRemove.insert(
21422 begin(output.meta.removedFuncs),
21423 end(output.meta.removedFuncs)
21428 // Remove metadata for any 86cinit function that an analysis job
21429 // optimized away. This must be done *after* calculating the next
21430 // round of work.
21431 void AnalysisScheduler::removeFuncs() {
21432 if (funcsToRemove.empty()) return;
21434 TSStringSet traceNamesToRemove;
21435 for (auto const name : funcsToRemove) {
21436 FTRACE(4, "AnalysisScheduler: removing function {}\n", name);
21438 auto fstate = folly::get_ptr(funcState, name);
21439 always_assert(fstate);
21441 auto tstate = [&] {
21442 if (!Constant::nameFromFuncName(name)) {
21443 return folly::get_ptr(traceState, name);
21445 auto const unit = index.m_data->funcToUnit.at(name);
21446 return folly::get_ptr(traceState, unit);
21447 }();
21448 always_assert(tstate);
21450 tstate->depStates.eraseTail(
21451 std::remove_if(
21452 tstate->depStates.begin(), tstate->depStates.end(),
21453 [&] (const DepState* d) { return d == &fstate->depState; }
21456 if (tstate->depStates.empty()) {
21457 traceState.erase(name);
21458 traceNamesToRemove.emplace(name);
21461 always_assert(index.m_data->funcRefs.erase(name));
21462 always_assert(index.m_data->funcBytecodeRefs.erase(name));
21463 always_assert(index.m_data->funcInfoRefs.erase(name));
21464 always_assert(index.m_data->funcToUnit.erase(name));
21465 always_assert(funcState.erase(name));
21466 index.m_data->funcToClosures.erase(name);
21467 if (auto const cns = Constant::nameFromFuncName(name)) {
21468 always_assert(index.m_data->constantInitFuncs.erase(name));
21469 always_assert(cnsChanged.erase(cns));
21470 index.m_data->constantToUnit.at(cns).second = false;
21474 funcNames.erase(
21475 std::remove_if(
21476 begin(funcNames), end(funcNames),
21477 [&] (SString name) { return funcsToRemove.count(name); }
21479 end(funcNames)
21482 if (!traceNamesToRemove.empty()) {
21483 traceNames.erase(
21484 std::remove_if(
21485 begin(traceNames), end(traceNames),
21486 [&] (SString name) { return traceNamesToRemove.count(name); }
21488 end(traceNames)
21492 funcsToRemove.clear();
21495 const AnalysisScheduler::TraceState*
21496 AnalysisScheduler::lookupTrace(DepState::Kind k, SString n) const {
21497 auto const s = folly::get_ptr(traceState, n);
21498 if (!s) return nullptr;
21499 for (auto const d : s->depStates) {
21500 if (d->kind != k) continue;
21501 if (!d->name->tsame(n)) continue;
21502 return s;
21504 return nullptr;
21507 // Retrieve the TraceState appropriate for the class with the given
21508 // name.
21509 const AnalysisScheduler::TraceState*
21510 AnalysisScheduler::traceForClass(SString cls) const {
21511 if (is_closure_name(cls)) {
21512 if (auto const n = folly::get_default(index.m_data->closureToClass, cls)) {
21513 return traceForClass(n);
21515 if (auto const n = folly::get_default(index.m_data->closureToFunc, cls)) {
21516 return traceForFunc(n);
21519 return lookupTrace(DepState::Class, cls);
21522 // Retrieve the TraceState appropriate for the func with the given
21523 // name.
21524 const AnalysisScheduler::TraceState*
21525 AnalysisScheduler::traceForFunc(SString func) const {
21526 if (auto const cns = Constant::nameFromFuncName(func)) {
21527 return traceForConstant(cns);
21529 return lookupTrace(DepState::Func, func);
21532 // Retrieve the TraceState appropriate for the unit with the given
21533 // path.
21534 const AnalysisScheduler::TraceState*
21535 AnalysisScheduler::traceForUnit(SString unit) const {
21536 return lookupTrace(DepState::Unit, unit);
21539 // Retrive the TraceState appropriate for the constant with the given
21540 // name.
21541 const AnalysisScheduler::TraceState*
21542 AnalysisScheduler::traceForConstant(SString cns) const {
21543 auto const unit = folly::get_ptr(index.m_data->constantToUnit, cns);
21544 if (!unit) return nullptr;
21545 return traceForUnit(unit->first);
21548 // Retrieve the TraceState appropriate for the type-alias with the
21549 // given name.
21550 const AnalysisScheduler::TraceState*
21551 AnalysisScheduler::traceForTypeAlias(SString typeAlias) const {
21552 auto const unit =
21553 folly::get_default(index.m_data->typeAliasToUnit, typeAlias);
21554 if (!unit) return nullptr;
21555 return traceForUnit(unit);
21558 // Retrieve the TraceState appropriate for the class or type-alias
21559 // with the given name.
21560 const AnalysisScheduler::TraceState*
21561 AnalysisScheduler::traceForClassOrTypeAlias(SString name) const {
21562 if (auto const t = traceForClass(name)) return t;
21563 if (auto const t = traceForTypeAlias(name)) return t;
21564 return nullptr;
21567 // Maps a DepState to it's associated TraceState.
21568 const AnalysisScheduler::TraceState*
21569 AnalysisScheduler::traceForDepState(const DepState& d) const {
21570 switch (d.kind) {
21571 case DepState::Func: return traceForFunc(d.name);
21572 case DepState::Class: return traceForClass(d.name);
21573 case DepState::Unit: return traceForUnit(d.name);
21575 always_assert(false);
21578 Either<const AnalysisScheduler::ClassState*,
21579 const AnalysisScheduler::UnitState*>
21580 AnalysisScheduler::stateForClassOrTypeAlias(SString n) const {
21581 if (auto const s = folly::get_ptr(classState, n)) return s;
21582 if (auto const unit = folly::get_default(index.m_data->typeAliasToUnit, n)) {
21583 return folly::get_ptr(unitState, unit);
21585 return nullptr;
21588 AnalysisScheduler::Presence
21589 AnalysisScheduler::presenceOf(const AnalysisInput::BucketPresence& b1,
21590 const AnalysisInput::BucketPresence& b2) const {
21591 if (&b1 == &b2) return Presence::Full;
21592 assertx(b1.process);
21593 assertx(b2.process);
21594 assertx(b2.withBC);
21595 assertx(b2.present);
21596 if (b1.process->isSubset(*b2.process)) return Presence::Full;
21597 if (b1.process->isSubset(*b2.withBC)) return Presence::DepWithBytecode;
21598 if (b1.process->isSubset(*b2.present)) return Presence::Dep;
21599 return Presence::None;
21602 AnalysisScheduler::Presence
21603 AnalysisScheduler::presenceOfClass(const TraceState& trace,
21604 SString cls) const {
21605 if (is_closure_name(cls)) {
21606 if (auto const n = folly::get_default(index.m_data->closureToClass, cls)) {
21607 return presenceOfClass(trace, n);
21609 if (auto const n = folly::get_default(index.m_data->closureToFunc, cls)) {
21610 return presenceOfFunc(trace, n);
21614 if (auto const t = traceForClass(cls)) {
21615 return presenceOf(trace.buckets, t->buckets);
21617 if (auto const b = folly::get_ptr(untracked.classes, cls)) {
21618 return presenceOf(trace.buckets, *b);
21621 assertx(trace.buckets.process);
21622 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21625 AnalysisScheduler::Presence
21626 AnalysisScheduler::presenceOfClassOrTypeAlias(const TraceState& trace,
21627 SString cls) const {
21628 if (is_closure_name(cls)) {
21629 if (auto const n = folly::get_default(index.m_data->closureToClass, cls)) {
21630 return presenceOfClass(trace, n);
21632 if (auto const n = folly::get_default(index.m_data->closureToFunc, cls)) {
21633 return presenceOfFunc(trace, n);
21637 if (auto const t = traceForClassOrTypeAlias(cls)) {
21638 return presenceOf(trace.buckets, t->buckets);
21640 if (auto const u = folly::get_default(index.m_data->typeAliasToUnit, cls)) {
21641 if (auto const b = folly::get_ptr(untracked.units, u)) {
21642 return presenceOf(trace.buckets, *b);
21644 } else if (auto const b = folly::get_ptr(untracked.classes, cls)) {
21645 return presenceOf(trace.buckets, *b);
21648 assertx(trace.buckets.process);
21649 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21652 AnalysisScheduler::Presence
21653 AnalysisScheduler::presenceOfFunc(const TraceState& trace,
21654 SString func) const {
21655 if (auto const t = traceForFunc(func)) {
21656 return presenceOf(trace.buckets, t->buckets);
21658 if (auto const b = folly::get_ptr(untracked.funcs, func)) {
21659 return presenceOf(trace.buckets, *b);
21661 assertx(trace.buckets.process);
21662 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21665 AnalysisScheduler::Presence
21666 AnalysisScheduler::presenceOfConstant(const TraceState& trace,
21667 SString cns) const {
21668 if (auto const trace2 = traceForConstant(cns)) {
21669 return presenceOf(trace.buckets, trace2->buckets);
21671 if (auto const b = folly::get_ptr(untracked.badConstants, cns)) {
21672 return presenceOf(trace.buckets, *b);
21674 assertx(trace.buckets.process);
21675 return trace.buckets.process->empty() ? Presence::Full : Presence::None;
21678 // Calculate any classes or functions which should be scheduled to be
21679 // analyzed in the next round.
21680 void AnalysisScheduler::findToSchedule() {
21681 // Check if the given entity (class or function) needs to run again
21682 // due to one of its dependencies changing (or if it previously
21683 // registered a new dependency).
21684 auto const check = [&] (SString name, DepState& d) {
21685 // The algorithm for these are all similar: Compare the old
21686 // dependencies with the new dependencies. If the dependency is
21687 // new, or if it's not the same as the old, check the
21688 // ClassGroup. If they're in the same ClassGroup, ignore it (this
21689 // entity already incorporated the change inside the analysis
21690 // job). Otherwise schedule this class or func to run.
21692 if (d.kind == DepState::Class && is_closure_name(name)) {
21693 assertx(d.deps.empty());
21694 return false;
21697 auto const trace = traceForDepState(d);
21698 always_assert(trace);
21700 for (auto const cls : d.deps.classes) {
21701 if (presenceOfClassOrTypeAlias(*trace, cls) == Presence::None) {
21702 FTRACE(
21703 4, "AnalysisScheduler: {} new class/type-alias dependency on {},"
21704 " scheduling\n",
21705 name, cls
21707 return true;
21711 for (auto const cls : d.deps.typeCnsClasses) {
21712 if (presenceOfClassOrTypeAlias(*trace, cls) == Presence::None) {
21713 FTRACE(
21714 4, "AnalysisScheduler: {} new class/type-alias dependency "
21715 "(in type-cns) on {}, scheduling\n",
21716 name, cls
21718 return true;
21722 for (auto const cls : d.deps.anyClsConstants) {
21723 auto const schedule = [&] {
21724 switch (presenceOfClassOrTypeAlias(*trace, cls)) {
21725 case Presence::None:
21726 return true;
21727 case Presence::Dep:
21728 case Presence::DepWithBytecode: {
21729 auto const state = folly::get_ptr(classState, cls);
21730 return state && state->cnsChanges.any();
21732 case Presence::Full:
21733 return false;
21735 }();
21737 if (schedule) {
21738 FTRACE(
21739 4, "AnalysisScheduler: {} new/changed dependency on "
21740 "any class constant for {}, scheduling\n",
21741 name, cls
21743 return true;
21747 for (auto const cls : d.deps.typeCnsAnyClsConstants) {
21748 if (presenceOfClassOrTypeAlias(*trace, cls) == Presence::None) {
21749 FTRACE(
21750 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21751 "any class constant for {}, scheduling\n",
21752 name, cls
21754 return true;
21758 for (auto const cns : d.deps.typeCnsClsConstants) {
21759 if (presenceOfClassOrTypeAlias(*trace, cns.cls) == Presence::None) {
21760 FTRACE(
21761 4, "AnalysisScheduler: {} new/changed dependency (in type-cns) on "
21762 "class constant {}, scheduling\n",
21763 name, show(cns)
21765 return true;
21769 for (auto const [meth, newT] : d.deps.methods) {
21770 if (newT == Type::None) continue;
21772 auto const schedule = [&, meth=meth, newT=newT] {
21773 switch (presenceOfClass(*trace, meth.cls)) {
21774 case Presence::None:
21775 return true;
21776 case Presence::Dep:
21777 if (newT & Type::Bytecode) return true;
21778 [[fallthrough]];
21779 case Presence::DepWithBytecode: {
21780 auto const state = folly::get_ptr(classState, meth.cls);
21781 if (!state) return false;
21782 auto const changed =
21783 state->methodChanges.get_default(meth.idx, Type::None);
21784 return (bool)(changed & newT);
21786 case Presence::Full:
21787 return false;
21789 }();
21791 if (schedule) {
21792 FTRACE(
21793 4, "AnalysisScheduler: {} new/changed dependency on method {},"
21794 " scheduling\n",
21795 name, show(meth)
21797 return true;
21801 for (auto const [func, newT] : d.deps.funcs) {
21802 if (newT == Type::None) continue;
21804 auto const schedule = [&, func=func, newT=newT] {
21805 switch (presenceOfFunc(*trace, func)) {
21806 case Presence::None:
21807 return true;
21808 case Presence::Dep:
21809 if (newT & Type::Bytecode) return true;
21810 [[fallthrough]];
21811 case Presence::DepWithBytecode: {
21812 auto const state = folly::get_ptr(funcState, func);
21813 if (!state) return false;
21814 return (bool)(state->changed & newT);
21816 case Presence::Full:
21817 return false;
21819 }();
21821 if (schedule) {
21822 FTRACE(
21823 4, "AnalysisScheduler: {} new/changed dependency on func {}, "
21824 "scheduling\n",
21825 name, func
21827 return true;
21831 for (auto const cns : d.deps.clsConstants) {
21832 auto const schedule = [&] {
21833 switch (presenceOfClass(*trace, cns.cls)) {
21834 case Presence::None:
21835 return true;
21836 case Presence::Dep:
21837 case Presence::DepWithBytecode: {
21838 auto const state = folly::get_ptr(classState, cns.cls);
21839 if (!state) return false;
21840 return
21841 cns.idx < state->cnsChanges.size() && state->cnsChanges[cns.idx];
21843 case Presence::Full:
21844 return false;
21846 }();
21848 if (schedule) {
21849 FTRACE(
21850 4, "AnalysisScheduler: {} new/changed dependency on "
21851 "class constant {}, scheduling\n",
21852 name, show(cns)
21854 return true;
21858 for (auto const cns : d.deps.constants) {
21859 auto const schedule = [&] {
21860 switch (presenceOfConstant(*trace, cns)) {
21861 case Presence::None:
21862 return true;
21863 case Presence::Dep:
21864 case Presence::DepWithBytecode: {
21865 auto const changed = folly::get_ptr(cnsChanged, cns);
21866 return changed && changed->load(std::memory_order_acquire);
21868 case Presence::Full:
21869 return false;
21871 }();
21873 if (schedule) {
21874 FTRACE(
21875 4, "AnalysisScheduler: {} new/changed dependency on "
21876 "constant {}, scheduling\n",
21877 name, cns
21879 return true;
21883 return false;
21886 parallel::for_each(
21887 classNames,
21888 [&] (SString name) {
21889 assertx(!is_closure_name(name));
21890 auto& state = classState.at(name).depState;
21891 state.toSchedule = check(name, state);
21892 if (state.toSchedule) ++totalWorkItems;
21895 parallel::for_each(
21896 funcNames,
21897 [&] (SString name) {
21898 auto& state = funcState.at(name).depState;
21899 state.toSchedule = check(name, state);
21900 if (state.toSchedule) ++totalWorkItems;
21903 parallel::for_each(
21904 unitNames,
21905 [&] (SString name) {
21906 auto& state = unitState.at(name).depState;
21907 state.toSchedule = check(name, state);
21908 if (state.toSchedule) ++totalWorkItems;
21913 // Reset any recorded changes from analysis jobs, in preparation for
21914 // another round.
21915 void AnalysisScheduler::resetChanges() {
21916 auto const resetClass = [&] (SString name) {
21917 auto& state = classState.at(name);
21918 std::fill(
21919 begin(state.methodChanges),
21920 end(state.methodChanges),
21921 Type::None
21923 state.cnsChanges.reset();
21926 parallel::for_each(
21927 classNames,
21928 [&] (SString name) {
21929 resetClass(name);
21930 auto const& closures =
21931 folly::get_default(index.m_data->classToClosures, name);
21932 for (auto const c : closures) resetClass(c);
21935 parallel::for_each(
21936 funcNames,
21937 [&] (SString name) {
21938 funcState.at(name).changed = Type::None;
21939 auto const& closures =
21940 folly::get_default(index.m_data->funcToClosures, name);
21941 for (auto const c : closures) resetClass(c);
21942 if (auto const cns = Constant::nameFromFuncName(name)) {
21943 cnsChanged.at(cns).store(false, std::memory_order_release);
21949 // Called when all analysis jobs are finished. "Finalize" the changes
21950 // and determine what should run in the next analysis round.
21951 void AnalysisScheduler::recordingDone() {
21952 findToSchedule();
21953 removeFuncs();
21954 resetChanges();
21957 void AnalysisScheduler::addClassToInput(SString name,
21958 AnalysisInput& input) const {
21959 FTRACE(4, "AnalysisScheduler: adding class {} to input\n", name);
21961 using K = AnalysisInput::Kind;
21962 auto k = K::Rep | K::Bytecode;
21963 if (index.m_data->classInfoRefs.count(name)) {
21964 k |= K::Info;
21965 } else if (index.m_data->uninstantiableClsMethRefs.count(name)) {
21966 k |= K::MInfo;
21967 } else {
21968 input.meta.badClasses.emplace(name);
21970 input.classes[name] |= k;
21972 if (!input.m_key) input.m_key = name;
21975 void AnalysisScheduler::addFuncToInput(SString name,
21976 AnalysisInput& input) const {
21977 FTRACE(4, "AnalysisScheduler: adding func {} to input\n", name);
21979 using K = AnalysisInput::Kind;
21980 input.funcs[name] |= (K::Rep | K::Bytecode | K::Info);
21981 if (!input.m_key) input.m_key = name;
21983 auto const& closures = folly::get_default(index.m_data->funcToClosures, name);
21984 for (auto const clo : closures) addClassToInput(clo, input);
21987 void AnalysisScheduler::addUnitToInput(SString name,
21988 AnalysisInput& input) const {
21989 FTRACE(4, "AnalysisScheduler: adding unit {} to input\n", name);
21990 using K = AnalysisInput::Kind;
21991 input.units[name] |= K::Rep;
21992 if (!input.m_key) input.m_key = name;
21995 void AnalysisScheduler::addDepClassToInput(SString cls,
21996 SString depSrc,
21997 bool addBytecode,
21998 AnalysisInput& input,
21999 bool fromClosureCtx) const {
22000 using K = AnalysisInput::Kind;
22002 if (auto const unit =
22003 folly::get_default(index.m_data->typeAliasToUnit, cls)) {
22004 addDepUnitToInput(unit, depSrc, input);
22005 return;
22008 auto const old = folly::get_default(input.classes, cls);
22009 if (any(old & K::Rep)) return;
22011 auto const badClass = [&] {
22012 if (input.meta.badClasses.emplace(cls).second) {
22013 FTRACE(4, "AnalysisScheduler: adding bad class {} to {} dep inputs\n",
22014 cls, depSrc);
22018 auto const clsRef = folly::get_ptr(index.m_data->classRefs, cls);
22019 if (!clsRef) {
22020 if (!is_closure_name(cls)) return badClass();
22021 assertx(!index.m_data->closureToFunc.count(cls));
22022 auto const ctx = folly::get_default(index.m_data->closureToClass, cls);
22023 if (!ctx) return badClass();
22024 if (fromClosureCtx) return;
22025 return addDepClassToInput(ctx, depSrc, addBytecode, input);
22027 assertx(!index.m_data->closureToClass.count(cls));
22029 if (any(old & K::Dep)) {
22030 if (!addBytecode || any(old & K::Bytecode)) return;
22031 } else {
22032 FTRACE(
22033 4, "AnalysisScheduler: adding class {} to {} dep inputs\n",
22034 cls, depSrc
22036 input.classes[cls] |= K::Dep;
22039 if (auto const r = folly::get_ptr(index.m_data->classInfoRefs, cls)) {
22040 input.classes[cls] |= K::Info;
22041 } else if (auto const r =
22042 folly::get_ptr(index.m_data->uninstantiableClsMethRefs, cls)) {
22043 input.classes[cls] |= K::MInfo;
22044 } else {
22045 badClass();
22048 if (addBytecode && !any(old & K::Bytecode)) {
22049 FTRACE(
22050 4, "AnalysisScheduler: adding class {} bytecode to {} dep inputs\n",
22051 cls, depSrc
22053 input.classes[cls] |= K::Bytecode;
22056 addDepUnitToInput(index.m_data->classToUnit.at(cls), depSrc, input);
22058 if (is_closure_name(cls)) {
22059 auto const f = folly::get_default(index.m_data->closureToFunc, cls);
22060 always_assert(f);
22061 if (fromClosureCtx) return;
22062 return addDepFuncToInput(
22064 depSrc,
22065 addBytecode ? Type::Bytecode : Type::Meta,
22066 input
22071 void AnalysisScheduler::addDepFuncToInput(SString func,
22072 SString depSrc,
22073 Type type,
22074 AnalysisInput& input) const {
22075 assertx(type != Type::None);
22077 using K = AnalysisInput::Kind;
22079 auto const old = folly::get_default(input.funcs, func);
22080 if (any(old & K::Rep)) return;
22082 auto const funcRef = folly::get_ptr(index.m_data->funcRefs, func);
22083 if (!funcRef) {
22084 if (input.meta.badFuncs.emplace(func).second) {
22085 FTRACE(4, "AnalysisScheduler: adding bad func {} to {} dep inputs\n",
22086 func, depSrc);
22088 return;
22091 if (any(old & K::Dep)) {
22092 if (!(type & Type::Bytecode) || any(old & K::Bytecode)) return;
22093 } else {
22094 FTRACE(
22095 4, "AnalysisScheduler: adding func {} to {} dep inputs\n",
22096 func, depSrc
22098 input.funcs[func] |= K::Dep;
22101 input.funcs[func] |= K::Info;
22102 if ((type & Type::Bytecode) && !any(old & K::Bytecode)) {
22103 FTRACE(
22104 4, "AnalysisScheduler: adding func {} bytecode to {} dep inputs\n",
22105 func, depSrc
22107 input.funcs[func] |= K::Bytecode;
22110 addDepUnitToInput(index.m_data->funcToUnit.at(func), depSrc, input);
22112 auto const& closures = folly::get_default(index.m_data->funcToClosures, func);
22113 for (auto const clo : closures) {
22114 addDepClassToInput(clo, depSrc, type & Type::Bytecode, input, true);
22118 void AnalysisScheduler::addDepConstantToInput(SString cns,
22119 SString depSrc,
22120 AnalysisInput& input) const {
22121 auto const unit = folly::get_ptr(index.m_data->constantToUnit, cns);
22122 if (!unit) {
22123 if (input.meta.badConstants.emplace(cns).second) {
22124 FTRACE(4, "AnalysisScheduler: adding bad constant {} to {} dep inputs\n",
22125 cns, depSrc);
22127 return;
22130 addDepUnitToInput(unit->first, depSrc, input);
22131 if (!unit->second || is_native_unit(unit->first)) return;
22133 auto const initName = Constant::funcNameFromName(cns);
22134 always_assert_flog(
22135 index.m_data->funcRefs.count(initName),
22136 "Constant {} is missing expected initialization function {}",
22137 cns, initName
22140 addDepFuncToInput(initName, depSrc, Type::Meta, input);
22143 void AnalysisScheduler::addDepUnitToInput(SString unit,
22144 SString depSrc,
22145 AnalysisInput& input) const {
22146 using K = AnalysisInput::Kind;
22147 auto const old = folly::get_default(input.units, unit);
22148 if (any(old & (K::Rep | K::Dep))) return;
22149 FTRACE(4, "AnalysisScheduler: adding unit {} to {} dep inputs\n",
22150 unit, depSrc);
22151 input.units[unit] |= K::Dep;
22154 void AnalysisScheduler::addTraceDepToInput(const DepState& d,
22155 AnalysisInput& input) const {
22156 switch (d.kind) {
22157 case DepState::Func:
22158 addDepFuncToInput(d.name, d.name, Type::Bytecode, input);
22159 break;
22160 case DepState::Class:
22161 addDepClassToInput(d.name, d.name, true, input);
22162 break;
22163 case DepState::Unit:
22164 addDepUnitToInput(d.name, d.name, input);
22165 break;
22167 addDepsToInput(d, input);
22170 void AnalysisScheduler::addDepsToInput(const DepState& deps,
22171 AnalysisInput& input) const {
22172 auto const& i = *index.m_data;
22174 switch (deps.kind) {
22175 case DepState::Func:
22176 addDepUnitToInput(
22177 i.funcToUnit.at(deps.name),
22178 deps.name,
22179 input
22181 break;
22182 case DepState::Class:
22183 addDepUnitToInput(
22184 i.classToUnit.at(deps.name),
22185 deps.name,
22186 input
22188 if (auto const bases = folly::get_ptr(i.classToCnsBases, deps.name)) {
22189 for (auto const b : *bases) {
22190 addDepClassToInput(b, deps.name, false, input);
22193 break;
22194 case DepState::Unit:
22195 break;
22198 stateForClassOrTypeAlias(deps.name).match(
22199 [&] (const ClassState* s) {
22200 if (!s) return;
22201 for (auto const t : s->typeCnsNames) {
22202 addDepClassToInput(t, deps.name, false, input);
22205 [&] (const UnitState* s) {
22206 if (!s) return;
22207 for (auto const t : s->typeCnsNames) {
22208 addDepClassToInput(t, deps.name, false, input);
22213 if (deps.deps.empty()) return;
22214 assertx(IMPLIES(deps.kind == DepState::Class, !is_closure_name(deps.name)));
22215 auto const& d = deps.deps;
22217 for (auto const cls : d.classes) {
22218 addDepClassToInput(cls, deps.name, false, input);
22220 for (auto const cls : d.typeCnsClasses) {
22221 addDepClassToInput(cls, deps.name, false, input);
22223 for (auto const [meth, type] : d.methods) {
22224 if (type != Type::None) {
22225 addDepClassToInput(
22226 meth.cls,
22227 deps.name,
22228 type & Type::Bytecode,
22229 input
22233 for (auto const [func, type] : d.funcs) {
22234 if (type != Type::None) addDepFuncToInput(func, deps.name, type, input);
22236 for (auto const cns : d.clsConstants) {
22237 addDepClassToInput(cns.cls, deps.name, false, input);
22239 for (auto const cns : d.typeCnsClsConstants) {
22240 addDepClassToInput(cns.cls, deps.name, false, input);
22242 for (auto const cls : d.anyClsConstants) {
22243 addDepClassToInput(cls, deps.name, false, input);
22244 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22245 for (auto const b : *bases) addDepClassToInput(b, deps.name, false, input);
22248 for (auto const cls : d.typeCnsAnyClsConstants) {
22249 addDepClassToInput(cls, deps.name, false, input);
22250 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22251 for (auto const b : *bases) addDepClassToInput(b, deps.name, false, input);
22254 for (auto const cns : d.constants) {
22255 addDepConstantToInput(cns, deps.name, input);
22259 // For every input in the AnalysisInput, add any associated
22260 // dependencies for those inputs.
22261 void AnalysisScheduler::addAllDepsToInput(AnalysisInput& input) const {
22262 using K = AnalysisInput::Kind;
22264 auto const names = [] (auto const& m) {
22265 using namespace folly::gen;
22266 return from(m)
22267 | map([] (auto const& p) { return p.first; })
22268 | as<std::vector>();
22271 // We can't just iterate over the containers because addDepsToInput
22272 // may add elements to them.
22273 for (auto const name : names(input.classes)) {
22274 auto const k = input.classes.at(name);
22275 if (!any(k & K::Rep)) continue;
22276 addDepsToInput(classState.at(name).depState, input);
22278 for (auto const name : names(input.funcs)) {
22279 auto const k = input.funcs.at(name);
22280 if (!any(k & K::Rep)) continue;
22281 addDepsToInput(funcState.at(name).depState, input);
22283 for (auto const name : names(input.units)) {
22284 auto const k = input.units.at(name);
22285 if (!any(k & K::Rep)) continue;
22286 addDepsToInput(unitState.at(name).depState, input);
22289 // These are automatically included everywhere:
22290 for (auto const f : special_builtins()) {
22291 addDepFuncToInput(f, f, Type::Meta, input);
22293 // Wait handle information must always be available.
22294 addDepClassToInput(s_Awaitable.get(), s_Awaitable.get(), false, input);
22295 addDepClassToInput(s_Traversable.get(), s_Traversable.get(), false, input);
22298 void AnalysisScheduler::addDepsMeta(const DepState& deps,
22299 AnalysisInput& input) const {
22300 auto& d = [&] () -> AnalysisDeps& {
22301 switch (deps.kind) {
22302 case DepState::Func: return input.meta.funcDeps[deps.name];
22303 case DepState::Class: return input.meta.classDeps[deps.name];
22304 case DepState::Unit: return input.meta.unitDeps[deps.name];
22306 }();
22307 d |= deps.deps;
22310 // Call F for any dependency which should be included
22311 // transitively. This usually means the dependency has information
22312 // that can change between rounds. This is a subset of a DepState's
22313 // total dependencies.
22314 template <typename F>
22315 void AnalysisScheduler::onTransitiveDep(SString name,
22316 const DepState& state,
22317 const F& f) const {
22318 if (state.deps.empty()) return;
22319 auto const& d = state.deps;
22320 auto const& i = *index.m_data;
22322 auto const filter = [&] (SString n) {
22323 if (!n->tsame(name)) f(n);
22326 auto const onFunc = [&] (SString n) {
22327 if (!Constant::nameFromFuncName(n)) return filter(n);
22328 auto const u = folly::get_default(i.funcToUnit, n);
22329 if (u) filter(u);
22332 auto const onCls = [&] (SString n) {
22333 if (auto const c = folly::get_default(i.closureToClass, n)) {
22334 return filter(c);
22336 if (auto const f = folly::get_default(i.closureToFunc, n)) {
22337 return onFunc(f);
22339 filter(n);
22342 auto const onClsOrTypeAlias = [&] (SString n) {
22343 if (auto const u = folly::get_default(i.typeAliasToUnit, n)) {
22344 filter(u);
22345 } else {
22346 onCls(n);
22350 stateForClassOrTypeAlias(state.name).match(
22351 [&] (const ClassState* s) {
22352 if (!s) return;
22353 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22355 [&] (const UnitState* s) {
22356 if (!s) return;
22357 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22360 if (auto const bases = folly::get_ptr(i.classToCnsBases, state.name)) {
22361 for (auto const b : *bases) onCls(b);
22364 for (auto const [meth, type] : d.methods) {
22365 if (type == Type::None) continue;
22366 if (!(type & AnalysisDeps::kValidForChanges)) continue;
22367 onCls(meth.cls);
22369 for (auto const [func, type] : d.funcs) {
22370 if (!(type & AnalysisDeps::kValidForChanges)) continue;
22371 onFunc(func);
22373 for (auto const cls : d.anyClsConstants) {
22374 stateForClassOrTypeAlias(cls).match(
22375 [&] (const ClassState* s) {
22376 if (s && !s->allCnsFixed) onCls(cls);
22378 [&] (const UnitState* s) {
22379 if (s && !s->fixed) filter(s->depState.name);
22383 for (auto const cls : d.typeCnsAnyClsConstants) onClsOrTypeAlias(cls);
22385 for (auto const cns : d.clsConstants) {
22386 stateForClassOrTypeAlias(cns.cls).match(
22387 [&] (const ClassState* s) {
22388 if (!s || s->allCnsFixed) return;
22389 if (cns.idx >= s->cnsFixed.size() || !s->cnsFixed[cns.idx]) {
22390 onCls(cns.cls);
22393 [&] (const UnitState* s) {
22394 if (s && !s->fixed) filter(s->depState.name);
22398 for (auto const cns : d.typeCnsClsConstants) onClsOrTypeAlias(cns.cls);
22400 for (auto const cns : d.constants) onFunc(Constant::funcNameFromName(cns));
22402 for (auto const ta : d.classes) {
22403 auto const unit = folly::get_default(i.typeAliasToUnit, ta);
22404 if (!unit) continue;
22405 auto const p = folly::get_ptr(unitState, unit);
22406 if (!p || p->fixed) continue;
22407 filter(unit);
22409 for (auto const c : d.typeCnsClasses) onClsOrTypeAlias(c);
22412 void AnalysisScheduler::addAllDeps(TSStringSet& out,
22413 const DepState& state) const {
22414 if (state.deps.empty()) return;
22415 auto const& d = state.deps;
22416 auto const& i = *index.m_data;
22418 auto const add = [&] (SString n) { out.emplace(n); };
22420 auto const onFunc = [&] (SString n) {
22421 if (!Constant::nameFromFuncName(n)) return add(n);
22422 auto const u = folly::get_default(i.funcToUnit, n);
22423 if (u) add(u);
22426 auto const onCls = [&] (SString n) {
22427 if (auto const c = folly::get_default(i.closureToClass, n)) {
22428 return add(c);
22430 if (auto const f = folly::get_default(i.closureToFunc, n)) {
22431 return onFunc(f);
22433 add(n);
22436 auto const onClsOrTypeAlias = [&] (SString n) {
22437 if (auto const u = folly::get_default(i.typeAliasToUnit, n)) {
22438 add(u);
22439 } else {
22440 onCls(n);
22444 stateForClassOrTypeAlias(state.name).match(
22445 [&] (const ClassState* s) {
22446 if (!s) return;
22447 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22449 [&] (const UnitState* s) {
22450 if (!s) return;
22451 for (auto const t : s->typeCnsNames) onClsOrTypeAlias(t);
22454 if (auto const bases = folly::get_ptr(i.classToCnsBases, state.name)) {
22455 for (auto const b : *bases) onCls(b);
22458 for (auto const cls : d.classes) onClsOrTypeAlias(cls);
22459 for (auto const cls : d.typeCnsClasses) onClsOrTypeAlias(cls);
22461 for (auto const [meth, type] : d.methods) {
22462 if (type != Type::None) onCls(meth.cls);
22464 for (auto const [func, type] : d.funcs) {
22465 if (type != Type::None) onFunc(func);
22468 for (auto const cls : d.anyClsConstants) {
22469 onClsOrTypeAlias(cls);
22470 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22471 for (auto const b : *bases) onCls(b);
22474 for (auto const cls : d.typeCnsAnyClsConstants) {
22475 onClsOrTypeAlias(cls);
22476 if (auto const bases = folly::get_ptr(i.classToCnsBases, cls)) {
22477 for (auto const b : *bases) onCls(b);
22480 for (auto const cns : d.clsConstants) onClsOrTypeAlias(cns.cls);
22481 for (auto const cns : d.typeCnsClsConstants) onClsOrTypeAlias(cns.cls);
22483 for (auto const cns : d.constants) onFunc(Constant::funcNameFromName(cns));
22486 // Dump dependencies of any trace classes or functions.
22487 void AnalysisScheduler::maybeDumpTraces() const {
22488 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace, 1)) return;
22490 TSStringSet allRoots;
22491 TSStringSet allTraces;
22492 TSStringSet allDeps;
22493 TSStringToOneT<TSStringSet> allTraceEdges;
22494 TSStringToOneT<TSStringSet> allDepEdges;
22496 parallel::for_each(
22497 traceNames,
22498 [&] (SString name) {
22499 Trace::BumpRelease bumper{
22500 Trace::hhbbc_dump_trace,
22501 [&] {
22502 auto& state = traceState.at(name);
22503 int bump = std::numeric_limits<int>::max();
22504 for (auto const d : state.depStates) {
22505 SString cls = nullptr;
22506 SString func = nullptr;
22507 SString unit = nullptr;
22508 switch (d->kind) {
22509 case DepState::Func:
22510 func = d->name;
22511 unit = folly::get_default(index.m_data->funcToUnit, d->name);
22512 break;
22513 case DepState::Class:
22514 cls = d->name;
22515 unit = folly::get_default(index.m_data->classToUnit, d->name);
22516 break;
22517 case DepState::Unit:
22518 unit = d->name;
22519 break;
22521 bump = std::min(bump, trace_bump_for(cls, func, unit));
22523 assertx(bump < std::numeric_limits<int>::max());
22524 return bump;
22527 if (!Trace::moduleEnabledRelease(Trace::hhbbc_dump_trace, 2)) return;
22529 TSStringSet traces;
22530 TSStringSet deps;
22531 TSStringToOneT<TSStringSet> traceEdges;
22532 TSStringToOneT<TSStringSet> depEdges;
22534 std::vector<SString> worklist;
22535 worklist.emplace_back(name);
22536 traces.emplace(name);
22538 while (!worklist.empty()) {
22539 auto const from = worklist.back();
22540 worklist.pop_back();
22541 auto const s = folly::get_ptr(traceState, from);
22542 if (!s) continue;
22543 for (auto const d : s->depStates) {
22544 onTransitiveDep(
22545 from, *d,
22546 [&] (SString to) {
22547 if (from->tsame(to)) return;
22548 if (!traceState.count(to)) return;
22549 traceEdges[from].emplace(to);
22550 if (!traces.emplace(to).second) return;
22551 worklist.emplace_back(to);
22557 for (auto const n : traces) {
22558 auto const s = folly::get_ptr(traceState, n);
22559 if (!s) continue;
22560 for (auto const d : s->depStates) {
22561 TSStringSet newDeps;
22562 addAllDeps(newDeps, *d);
22563 for (auto const n2 : newDeps) {
22564 if (n->tsame(n2)) continue;
22565 depEdges[n].emplace(n2);
22567 deps.insert(begin(newDeps), end(newDeps));
22571 std::lock_guard<std::mutex> _{*lock};
22572 allRoots.emplace(name);
22573 allTraces.insert(begin(traces), end(traces));
22574 allDeps.insert(begin(deps), end(deps));
22575 for (auto const& [n, e] : traceEdges) {
22576 allTraceEdges[n].insert(begin(e), end(e));
22578 for (auto const& [n, e] : depEdges) {
22579 allDepEdges[n].insert(begin(e), end(e));
22584 if (allRoots.empty() && allTraces.empty() && allDeps.empty()) return;
22586 size_t nextId = 0;
22587 TSStringToOneT<size_t> ids;
22588 auto const id = [&] (SString n) {
22589 if (auto const i = folly::get_ptr(ids, n)) return *i;
22590 ids.emplace(n, nextId);
22591 return nextId++;
22594 using namespace folly::gen;
22596 auto const nodes = [&] (const TSStringSet& ns,
22597 const char* color,
22598 const TSStringSet* f1 = nullptr,
22599 const TSStringSet* f2 = nullptr) {
22600 return from(ns)
22601 | filter([&] (SString n) { return !f1 || !f1->count(n); })
22602 | filter([&] (SString n) { return !f2 || !f2->count(n); })
22603 | orderBy(folly::identity, string_data_lt_type{})
22604 | map([&] (SString n) {
22605 return folly::sformat(
22606 " {} [label=\"{}\" color={}];",
22607 id(n), folly::cEscape<std::string>(n->slice()), color
22610 | unsplit<std::string>("\n");
22613 auto const edges = [&] (const TSStringToOneT<TSStringSet>& es,
22614 const char* color,
22615 const TSStringToOneT<TSStringSet>* f1 = nullptr) {
22616 std::vector<SString> fromE;
22617 for (auto const& [n, _] : es) fromE.emplace_back(n);
22618 std::sort(begin(fromE), end(fromE), string_data_lt_type{});
22619 return from(fromE)
22620 | map([&] (SString n) {
22621 return folly::sformat(
22622 " {} -> {{{}}} [color={}];",
22623 id(n),
22624 from(es.at(n))
22625 | filter([&] (SString n2) {
22626 if (!f1) return true;
22627 auto const t = folly::get_ptr(*f1, n);
22628 return !t || !t->count(n2);
22630 | orderBy(folly::identity, string_data_lt_type{})
22631 | map([&] (SString n2) { return folly::sformat("{}", id(n2)); })
22632 | unsplit<std::string>(" "),
22633 color
22636 | unsplit<std::string>("\n");
22639 namespace fs = std::filesystem;
22641 auto const path = [] {
22642 auto const base = [] {
22643 if (auto const e = getenv("HHBBC_DUMP_DIR")) return fs::path(e);
22644 return fs::temp_directory_path();
22645 }();
22646 return fs::weakly_canonical(
22647 base / boost::filesystem::unique_path("hhbbc-%%%%%%%%").native()
22649 }();
22651 std::ofstream out{path};
22652 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
22653 out << folly::format(
22654 "digraph \"Round #{}\" {{\n"
22655 "{}\n"
22656 "{}\n"
22657 "{}\n"
22658 "{}\n"
22659 "{}\n"
22660 "}}\n",
22661 round,
22662 nodes(allRoots, "red"),
22663 nodes(allTraces, "blue", &allRoots),
22664 nodes(allDeps, "gray", &allRoots, &allTraces),
22665 edges(allTraceEdges, "blue"),
22666 edges(allDepEdges, "gray", &allTraceEdges)
22669 std::cout << "Trace dump for round #" << round
22670 << " written to " << path
22671 << std::endl;
22674 // First pass: initialize all data in TraceStates.
22675 void AnalysisScheduler::tracePass1() {
22676 untracked = Untracked{};
22678 std::atomic<size_t> idx{0};
22679 parallel::for_each(
22680 traceNames,
22681 [&] (SString n) {
22682 auto& state = traceState.at(n);
22683 state.eligible = false;
22684 state.leaf.store(true);
22685 state.covered.store(false);
22686 state.trace.clear();
22687 state.deps.clear();
22688 state.buckets.present = nullptr;
22689 state.buckets.withBC = nullptr;
22690 state.buckets.process = nullptr;
22691 state.idx = idx++;
22696 // Second pass: Build traces for each entity and mark eligible.
22697 void AnalysisScheduler::tracePass2() {
22698 parallel::for_each(
22699 traceNames,
22700 [&] (SString name) {
22701 auto& state = traceState.at(name);
22703 // Build the trace up by using the transitive dependencies.
22704 std::vector<SString> worklist;
22705 auto const add = [&] (SString n) {
22706 if (!traceState.count(n)) return;
22707 if (!state.trace.emplace(n).second) return;
22708 if (n->tsame(name)) return;
22709 worklist.emplace_back(n);
22712 worklist.emplace_back(name);
22713 while (!worklist.empty()) {
22714 auto const n = worklist.back();
22715 worklist.pop_back();
22716 auto const s = folly::get_ptr(traceState, n);
22717 if (!s) continue;
22718 for (auto const d : s->depStates) onTransitiveDep(n, *d, add);
22721 // An entity is eligible if any of its components changed, or if
22722 // anything in the trace is eligible.
22723 auto const eligible = [&] (const TraceState& t) {
22724 return std::any_of(
22725 t.depStates.begin(), t.depStates.end(),
22726 [] (const DepState* d) { return d->toSchedule; }
22730 state.eligible =
22731 eligible(state) ||
22732 std::any_of(
22733 begin(state.trace), end(state.trace),
22734 [&] (SString n) {
22735 auto const s = folly::get_ptr(traceState, n);
22736 return s && eligible(*s);
22743 // Third pass: "Expand" every TraceState with it's total dependencies,
22744 // which is a superset of those in the trace.
22745 void AnalysisScheduler::tracePass3() {
22746 parallel::for_each(
22747 traceNames,
22748 [&] (SString name) {
22749 auto& state = traceState.at(name);
22751 if (!state.eligible) return;
22753 folly::erase_if(
22754 state.trace,
22755 [&] (SString n) {
22756 auto const s = folly::get_ptr(traceState, n);
22757 return !s || !s->eligible;
22761 auto const expand = [&] (SString n) {
22762 auto const s = folly::get_ptr(traceState, n);
22763 if (!s) return;
22764 for (auto const d : s->depStates) addAllDeps(state.deps, *d);
22767 state.deps = state.trace;
22768 expand(name);
22769 for (auto const n : state.trace) expand(n);
22774 // Fourth pass: Determine leafs for the purpose of scheduling.
22775 std::vector<SString> AnalysisScheduler::tracePass4() {
22776 // A leaf here means that nothing else depends on it.
22778 // Mark any TraceState which is on another TraceState's trace as not
22779 // being a leaf.
22780 parallel::for_each(
22781 traceNames,
22782 [&] (SString name) {
22783 auto& state = traceState.at(name);
22784 if (!state.eligible) return;
22785 for (auto const n : state.trace) {
22786 auto const s = folly::get_ptr(traceState, n);
22787 if (s) s->leaf.store(false);
22792 // Mark "covered" TraceStates. A covered TraceState is processed and
22793 // shouldn't be considered anymore in this function.
22794 std::vector<SString> traceLeafs;
22795 parallel::for_each(
22796 traceNames,
22797 [&] (SString name) {
22798 auto& state = traceState.at(name);
22799 if (!state.eligible || !state.leaf) return;
22801 // Right now a TraceState is covered if it's a leaf, along with
22802 // anything on this TraceState's trace.
22803 state.covered.store(true);
22804 for (auto const n : state.trace) {
22805 auto const s = folly::get_ptr(traceState, n);
22806 if (s) s->covered.store(true);
22809 std::lock_guard<std::mutex> _{*lock};
22810 traceLeafs.emplace_back(name);
22814 // Any uncovered TraceState's must now be part of a cycle (or depend
22815 // on a TraceState in a cycle).
22816 std::vector<SString> traceInCycle;
22817 parallel::for_each(
22818 traceNames,
22819 [&] (SString n) {
22820 auto const& state = traceState.at(n);
22821 if (!state.eligible || state.covered) return;
22822 assertx(!state.leaf);
22823 std::lock_guard<std::mutex> _{*lock};
22824 traceInCycle.emplace_back(n);
22828 // Sort all TraceStates in a cycle. This isn't strictly necessary,
22829 // but leads to better results.
22830 std::sort(
22831 begin(traceInCycle),
22832 end(traceInCycle),
22833 [&] (SString n1, SString n2) {
22834 auto const& state1 = traceState.at(n1);
22835 auto const& state2 = traceState.at(n2);
22836 assertx(state1.eligible);
22837 assertx(!state1.covered);
22838 assertx(!state1.leaf);
22839 assertx(state2.eligible);
22840 assertx(!state2.covered);
22841 assertx(!state2.leaf);
22842 if (state1.trace.size() > state2.trace.size()) return true;
22843 if (state1.trace.size() < state2.trace.size()) return false;
22844 return string_data_lt_type{}(n1, n2);
22848 // Pick an arbitrary TraceState from the cycle set, declare it a
22849 // leaf by fiat, then mark it and any trace dependencies as being
22850 // covered. This process repeats until all TraceStates are covered.
22851 for (auto const name : traceInCycle) {
22852 auto& s = traceState.at(name);
22853 if (s.covered) continue;
22854 assertx(!s.leaf);
22855 s.leaf.store(true);
22856 s.covered.store(true);
22857 for (auto const n : s.trace) {
22858 auto const s2 = folly::get_ptr(traceState, n);
22859 if (!s2 || s2->covered) continue;
22860 assertx(!s2->leaf);
22861 s2->covered.store(true);
22863 traceLeafs.emplace_back(name);
22866 if (debug) {
22867 // Every TraceState at this point should be covered (or not
22868 // eligible).
22869 parallel::for_each(
22870 traceNames,
22871 [&] (SString n) {
22872 auto const s = folly::get_ptr(traceState, n);
22873 always_assert(s);
22874 always_assert_flog(
22875 s->covered || !s->eligible,
22876 "{} is not covered", n
22882 return traceLeafs;
22885 // Fifth pass: Assign leafs and their dependencies to
22886 // buckets. Non-leafs (which must have some TraceState depending on
22887 // them) will be assigned to one of the buckets of the TraceState that
22888 // depends on it.
22889 std::vector<AnalysisScheduler::Bucket>
22890 AnalysisScheduler::tracePass5(size_t bucketSize,
22891 size_t maxBucketSize,
22892 std::vector<SString> leafs) {
22893 using namespace folly::gen;
22895 auto w = assign_hierarchical_work(
22896 leafs,
22897 traceState.size(),
22898 bucketSize,
22899 maxBucketSize,
22900 [&] (SString n) {
22901 return std::make_pair(&traceState.at(n).deps, true);
22903 [&] (const TSStringSet& roots,
22904 size_t bucketIdx,
22905 SString n) -> Optional<size_t> {
22906 // To determine whether we want to promote this TraceState or
22907 // not, we need to check if it's present in any of the bucket's
22908 // traces. This can be expensive to calculate, so utilize
22909 // caching. Only recalculate the set if we're referring to a
22910 // different bucket than last call.
22911 thread_local Optional<size_t> last;
22912 thread_local TSStringSet inTrace;
22913 if (!last || *last != bucketIdx) {
22914 inTrace.clear();
22915 for (auto const root : roots) {
22916 auto const& state = traceState.at(root);
22917 inTrace.insert(begin(state.trace), end(state.trace));
22919 last = bucketIdx;
22922 // If the TraceState is a leaf, or isn't in any of the traces
22923 // for this bucket, we don't want to promote it.
22924 auto const s = folly::get_ptr(traceState, n);
22925 if (!s || !s->eligible || s->leaf.load() || !inTrace.count(n)) {
22926 return std::nullopt;
22928 return s->idx;
22932 if (debug) {
22933 // Sanity check that every eligible TraceState belongs to at least
22934 // one bucket.
22935 TSStringSet inputs;
22936 for (auto const& b : w) {
22937 inputs.insert(begin(b.classes), end(b.classes));
22940 parallel::for_each(
22941 traceNames,
22942 [&] (SString n) {
22943 auto const s = folly::get_ptr(traceState, n);
22944 always_assert(s);
22945 if (!s->eligible) return;
22946 always_assert_flog(
22947 inputs.count(n),
22948 "{} should be present in at least one bucket's inputs, but is not",
22955 using H = HierarchicalWorkBucket;
22956 return from(w)
22957 | move
22958 | map([] (H&& b) { return Bucket{std::move(b)}; })
22959 | as<std::vector>();
22962 // Sixth pass: Make AnalysisInputs for each bucket.
22963 AnalysisScheduler::InputsAndUntracked
22964 AnalysisScheduler::tracePass6(const std::vector<Bucket>& buckets) {
22965 TSStringToOneConcurrentT<std::nullptr_t> seenClasses;
22966 FSStringToOneConcurrentT<std::nullptr_t> seenFuncs;
22967 SStringToOneConcurrentT<std::nullptr_t> seenUnits;
22968 SStringToOneConcurrentT<std::nullptr_t> seenConstants;
22970 auto outputs = parallel::gen(
22971 buckets.size(),
22972 [&] (size_t idx) {
22973 auto const& b = buckets[idx].b;
22974 assertx(b.uninstantiable.empty());
22976 TSStringSet inTrace;
22978 // Make an AnalysisInput by adding all the appropriate inputs
22979 // for each TraceState and dependencies.
22980 AnalysisInput input;
22981 input.index = index.m_data.get();
22982 input.meta.bucketIdx = idx;
22984 for (auto const item : b.classes) {
22985 auto const& state = traceState.at(item);
22986 assertx(!state.depStates.empty());
22987 for (auto const d : state.depStates) {
22988 switch (d->kind) {
22989 case DepState::Func:
22990 addFuncToInput(d->name, input);
22991 break;
22992 case DepState::Class:
22993 addClassToInput(d->name, input);
22994 break;
22995 case DepState::Unit:
22996 addUnitToInput(d->name, input);
22997 break;
23000 if (!d->toSchedule) addDepsMeta(*d, input);
23003 inTrace.insert(begin(state.trace), end(state.trace));
23006 for (auto const item : b.deps) {
23007 auto const s = folly::get_ptr(traceState, item);
23008 if (!s || !s->eligible || !inTrace.count(item)) continue;
23009 assertx(!s->depStates.empty());
23011 for (auto const d : s->depStates) {
23012 addTraceDepToInput(*d, input);
23014 if (!d->toSchedule) {
23015 addDepsMeta(*d, input);
23016 continue;
23019 switch (d->kind) {
23020 case DepState::Func:
23021 input.meta.processDepFunc.emplace(d->name);
23022 break;
23023 case DepState::Class:
23024 input.meta.processDepCls.emplace(d->name);
23025 break;
23026 case DepState::Unit:
23027 input.meta.processDepUnit.emplace(d->name);
23028 break;
23033 addAllDepsToInput(input);
23035 InputsAndUntracked out;
23037 using K = AnalysisInput::Kind;
23039 // Record untracked items. These are inputs to this bucket which
23040 // do not have TraceState. They must be dealt with differently
23041 // later on.
23042 auto const untracked = [&] (auto const& i1,
23043 auto const& i2,
23044 auto& s,
23045 auto t) {
23046 using D1 = std::decay_t<decltype(i1)>;
23047 using D2 = std::decay_t<decltype(i2)>;
23049 std::vector<SString> out;
23051 if constexpr (!std::is_same_v<D1, std::nullptr_t>) {
23052 for (auto const& [n, k] : i1) {
23053 if (!any(k & K::Dep)) continue;
23054 if (std::invoke(t, this, n)) continue;
23055 if (!s.try_emplace(n).second) continue;
23056 out.emplace_back(n);
23059 if constexpr (!std::is_same_v<D2, std::nullptr_t>) {
23060 for (auto const n : i2) {
23061 if (std::invoke(t, this, n)) continue;
23062 if (!s.try_emplace(n).second) continue;
23063 out.emplace_back(n);
23066 return out;
23069 using A = AnalysisScheduler;
23070 out.untrackedClasses = untracked(
23071 input.classes,
23072 input.meta.badClasses,
23073 seenClasses,
23074 &A::traceForClass
23076 out.untrackedFuncs = untracked(
23077 input.funcs,
23078 input.meta.badFuncs,
23079 seenFuncs,
23080 &A::traceForFunc
23082 out.untrackedUnits = untracked(
23083 input.units,
23084 nullptr,
23085 seenUnits,
23086 &A::traceForUnit
23088 out.badConstants = untracked(
23089 nullptr,
23090 input.meta.badConstants,
23091 seenConstants,
23092 &A::traceForConstant
23095 out.inputs.emplace_back(std::move(input));
23096 return out;
23100 // We created untracked data for each bucket. We must now combine
23101 // that into one unified set.
23103 auto const combine = [&] (auto f, auto& m) {
23104 std::vector<SString> out;
23105 for (auto const& output : outputs) {
23106 auto& in = output.*f;
23107 out.insert(end(out), begin(in), end(in));
23109 for (auto const n : out) m.try_emplace(n);
23110 return out;
23113 using I = InputsAndUntracked;
23114 I combined;
23115 parallel::parallel(
23116 [&] {
23117 combined.inputs.reserve(outputs.size());
23118 for (auto& output : outputs) {
23119 assertx(output.inputs.size() == 1);
23120 combined.inputs.emplace_back(std::move(output.inputs[0]));
23123 [&] {
23124 combined.untrackedClasses =
23125 combine(&I::untrackedClasses, untracked.classes);
23127 [&] {
23128 combined.untrackedFuncs =
23129 combine(&I::untrackedFuncs, untracked.funcs);
23131 [&] {
23132 combined.untrackedUnits =
23133 combine(&I::untrackedUnits, untracked.units);
23135 [&] {
23136 combined.badConstants =
23137 combine(&I::badConstants, untracked.badConstants);
23141 combined.inputs.shrink_to_fit();
23142 combined.untrackedClasses.shrink_to_fit();
23143 combined.untrackedFuncs.shrink_to_fit();
23144 combined.untrackedUnits.shrink_to_fit();
23145 combined.badConstants.shrink_to_fit();
23146 return combined;
23149 // Seventh pass: Calculate BucketSets for each TraceSet. This will
23150 // determine how different items can utilize information from another.
23151 void AnalysisScheduler::tracePass7(InputsAndUntracked& inputs) {
23152 using A = AnalysisInput;
23153 using K = A::Kind;
23155 auto const onClass = [&] (SString name,
23156 A::BucketSet& present,
23157 A::BucketSet& withBC,
23158 A::BucketSet& process) {
23159 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23160 auto const& input = inputs.inputs[i];
23161 auto const k = folly::get_default(input.classes, name);
23162 if (any(k & K::Rep)) {
23163 assertx(any(k & K::Bytecode));
23164 present.add(i);
23165 process.add(i);
23167 if (any(k & K::Dep)) {
23168 present.add(i);
23169 if (input.meta.classDeps.count(name) ||
23170 input.meta.processDepCls.count(name)) {
23171 assertx(any(k & K::Bytecode));
23172 process.add(i);
23175 if (any(k & K::Bytecode)) withBC.add(i);
23176 if (input.meta.badClasses.count(name)) present.add(i);
23180 auto const onFunc = [&] (SString name,
23181 A::BucketSet& present,
23182 A::BucketSet& withBC,
23183 A::BucketSet& process) {
23184 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23185 auto const& input = inputs.inputs[i];
23186 auto const k = folly::get_default(input.funcs, name);
23187 if (any(k & K::Rep)) {
23188 assertx(any(k & K::Bytecode));
23189 present.add(i);
23190 process.add(i);
23192 if (any(k & K::Dep)) {
23193 present.add(i);
23194 if (input.meta.funcDeps.count(name) ||
23195 input.meta.processDepFunc.count(name)) {
23196 assertx(any(k & K::Bytecode));
23197 process.add(i);
23200 if (any(k & K::Bytecode)) withBC.add(i);
23201 if (input.meta.badFuncs.count(name)) present.add(i);
23205 auto const onUnit = [&] (SString name,
23206 A::BucketSet& present,
23207 A::BucketSet&,
23208 A::BucketSet& process) {
23209 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23210 auto const& input = inputs.inputs[i];
23211 auto const k = folly::get_default(input.units, name);
23212 if (any(k & K::Rep)) {
23213 present.add(i);
23214 process.add(i);
23216 if (any(k & K::Dep)) {
23217 present.add(i);
23218 if (input.meta.unitDeps.count(name) ||
23219 input.meta.processDepUnit.count(name)) {
23220 process.add(i);
23226 parallel::for_each(
23227 traceNames,
23228 [&] (SString name) {
23229 auto& state = traceState.at(name);
23230 state.trace.clear();
23231 state.deps.clear();
23233 A::BucketSet present;
23234 A::BucketSet withBC;
23235 A::BucketSet process;
23237 auto first = true;
23238 A::BucketSet temp1, temp2, temp3;
23239 for (auto const d : state.depStates) {
23240 temp1.clear();
23241 temp2.clear();
23242 temp3.clear();
23243 auto& s1 = first ? present : temp1;
23244 auto& s2 = first ? withBC : temp2;
23245 auto& s3 = first ? process : temp3;
23246 switch (d->kind) {
23247 case DepState::Func:
23248 onFunc(d->name, s1, s2, s3);
23249 break;
23250 case DepState::Class:
23251 onClass(d->name, s1, s2, s3);
23252 break;
23253 case DepState::Unit:
23254 onUnit(d->name, s1, s2, s3);
23255 break;
23257 if (!first) {
23258 present |= temp1;
23259 withBC |= temp2;
23260 process |= temp3;
23261 } else {
23262 first = false;
23266 assertx(!state.buckets.present);
23267 assertx(!state.buckets.withBC);
23268 assertx(!state.buckets.process);
23270 state.buckets.present = A::BucketSet::intern(std::move(present));
23271 state.buckets.withBC = A::BucketSet::intern(std::move(withBC));
23272 state.buckets.process = A::BucketSet::intern(std::move(process));
23276 // Do the same for untracked items:
23278 auto const addUntracked = [&] (const std::vector<SString>& v,
23279 auto& m,
23280 auto const& o) {
23281 parallel::for_each(
23283 [&] (SString name) {
23284 auto& state = m.at(name);
23286 A::BucketSet present;
23287 A::BucketSet withBC;
23288 A::BucketSet process;
23289 o(name, present, withBC, process);
23291 assertx(!present.empty());
23292 assertx(process.empty());
23294 assertx(!state.present);
23295 assertx(!state.withBC);
23296 assertx(!state.process);
23298 state.present = A::BucketSet::intern(std::move(present));
23299 state.withBC = A::BucketSet::intern(std::move(withBC));
23300 state.process = A::BucketSet::intern(std::move(process));
23304 addUntracked(inputs.untrackedClasses, untracked.classes, onClass);
23305 addUntracked(inputs.untrackedFuncs, untracked.funcs, onFunc);
23306 addUntracked(inputs.untrackedUnits, untracked.units, onUnit);
23307 addUntracked(
23308 inputs.badConstants,
23309 untracked.badConstants,
23310 [&] (SString name,
23311 A::BucketSet& present,
23312 A::BucketSet&,
23313 A::BucketSet&) {
23314 for (size_t i = 0, size = inputs.inputs.size(); i < size; ++i) {
23315 auto const& input = inputs.inputs[i];
23316 if (input.meta.badConstants.count(name)) {
23317 present.add(i);
23323 decltype(inputs.untrackedClasses){}.swap(inputs.untrackedClasses);
23324 decltype(inputs.untrackedFuncs){}.swap(inputs.untrackedFuncs);
23325 decltype(inputs.untrackedUnits){}.swap(inputs.untrackedUnits);
23326 decltype(inputs.badConstants){}.swap(inputs.badConstants);
23329 // Eighth pass: Store BucketSets in each AnalysisInput's metadata.
23330 void AnalysisScheduler::tracePass8(std::vector<Bucket> buckets,
23331 std::vector<AnalysisInput>& inputs) {
23332 parallel::gen(
23333 inputs.size(),
23334 [&] (size_t i) {
23335 auto& input = inputs[i];
23336 assertx(i < buckets.size());
23337 auto& bucket = buckets[i].b;
23339 TSStringSet seenClasses;
23340 FSStringSet seenFuncs;
23341 SStringSet seenUnits;
23342 SStringSet seenBadConstants;
23344 for (auto const item : bucket.classes) {
23345 auto& state = traceState.at(item);
23346 assertx(!state.depStates.empty());
23347 assertx(state.buckets.present->contains(i));
23348 assertx(state.buckets.process->contains(i));
23350 for (auto const d : state.depStates) {
23351 switch (d->kind) {
23352 case DepState::Func:
23353 always_assert(seenFuncs.emplace(d->name).second);
23354 input.meta.funcBuckets.emplace_back(d->name, state.buckets);
23355 break;
23356 case DepState::Class:
23357 always_assert(seenClasses.emplace(d->name).second);
23358 input.meta.classBuckets.emplace_back(d->name, state.buckets);
23359 break;
23360 case DepState::Unit:
23361 always_assert(seenUnits.emplace(d->name).second);
23362 input.meta.unitBuckets.emplace_back(d->name, state.buckets);
23363 break;
23368 for (auto const item : bucket.deps) {
23369 auto const s = folly::get_ptr(traceState, item);
23370 if (!s) continue;
23371 assertx(!s->depStates.empty());
23372 if (!s->buckets.present->contains(i)) {
23373 // If this trace state isn't in the bucket, an untracked
23374 // item (with the same name) should be.
23375 auto const b = [&] () -> const AnalysisInput::BucketPresence* {
23376 auto const& u = untracked;
23377 if (auto const b = folly::get_ptr(u.classes, item)) return b;
23378 if (auto const b = folly::get_ptr(u.funcs, item)) return b;
23379 if (auto const b = folly::get_ptr(u.units, item)) return b;
23380 if (auto const b = folly::get_ptr(u.badConstants, item)) return b;
23381 return nullptr;
23382 }();
23383 always_assert(b);
23384 always_assert(b->present->contains(i));
23385 continue;
23388 for (auto const d : s->depStates) {
23389 switch (d->kind) {
23390 case DepState::Func:
23391 always_assert(seenFuncs.emplace(d->name).second);
23392 input.meta.funcBuckets.emplace_back(d->name, s->buckets);
23393 break;
23394 case DepState::Class:
23395 always_assert(seenClasses.emplace(d->name).second);
23396 input.meta.classBuckets.emplace_back(d->name, s->buckets);
23397 break;
23398 case DepState::Unit:
23399 always_assert(seenUnits.emplace(d->name).second);
23400 input.meta.unitBuckets.emplace_back(d->name, s->buckets);
23401 break;
23406 using K = AnalysisInput::Kind;
23408 for (auto const& [name, k] : input.classes) {
23409 if (!any(k & K::Dep)) continue;
23410 if (auto const b = folly::get_ptr(untracked.classes, name)) {
23411 assertx(!traceForClass(name));
23412 assertx(b->present->contains(i));
23413 always_assert(seenClasses.emplace(name).second);
23414 input.meta.classBuckets.emplace_back(name, *b);
23415 } else if (!seenClasses.count(name)) {
23416 auto const t = traceForClass(name);
23417 always_assert_flog(
23418 t, "{} is on input dep classes, but has no tracking state",
23419 name
23421 input.meta.classBuckets.emplace_back(name, t->buckets);
23422 assertx(t->buckets.present->contains(i));
23425 for (auto const& [name, k] : input.funcs) {
23426 if (!any(k & K::Dep)) continue;
23427 if (auto const b = folly::get_ptr(untracked.funcs, name)) {
23428 assertx(!traceForFunc(name));
23429 assertx(b->present->contains(i));
23430 always_assert(seenFuncs.emplace(name).second);
23431 input.meta.funcBuckets.emplace_back(name, *b);
23432 } else if (!seenFuncs.count(name)) {
23433 auto const t = traceForFunc(name);
23434 always_assert_flog(
23435 t, "{} is on input dep funcs, but has no tracking state",
23436 name
23438 input.meta.funcBuckets.emplace_back(name, t->buckets);
23439 assertx(t->buckets.present->contains(i));
23442 for (auto const& [name, k] : input.units) {
23443 if (!any(k & K::Dep)) continue;
23444 if (auto const b = folly::get_ptr(untracked.units, name)) {
23445 assertx(!traceForUnit(name));
23446 assertx(b->present->contains(i));
23447 always_assert(seenUnits.emplace(name).second);
23448 input.meta.unitBuckets.emplace_back(name, *b);
23449 } else if (!seenUnits.count(name)) {
23450 auto const t = traceForUnit(name);
23451 always_assert_flog(
23452 t, "{} is on input dep units, but has no tracking state",
23453 name
23455 input.meta.unitBuckets.emplace_back(name, t->buckets);
23456 assertx(t->buckets.present->contains(i));
23460 for (auto const name : input.meta.badClasses) {
23461 if (seenClasses.count(name)) continue;
23462 if (auto const b = folly::get_ptr(untracked.classes, name)) {
23463 assertx(!traceForClass(name));
23464 assertx(b->present->contains(i));
23465 always_assert(seenClasses.emplace(name).second);
23466 input.meta.classBuckets.emplace_back(name, *b);
23467 } else {
23468 auto const t = traceForClass(name);
23469 always_assert_flog(
23470 t, "{} is on input bad classes, but has no tracking state",
23471 name
23473 input.meta.classBuckets.emplace_back(name, t->buckets);
23474 assertx(t->buckets.present->contains(i));
23477 for (auto const name : input.meta.badFuncs) {
23478 if (auto const b = folly::get_ptr(untracked.funcs, name)) {
23479 assertx(!traceForFunc(name));
23480 assertx(b->present->contains(i));
23481 always_assert(seenFuncs.emplace(name).second);
23482 input.meta.funcBuckets.emplace_back(name, *b);
23483 } else if (!seenFuncs.count(name)) {
23484 auto const t = traceForFunc(name);
23485 always_assert_flog(
23486 t, "{} is on input bad funcs, but has no tracking state",
23487 name
23489 input.meta.funcBuckets.emplace_back(name, t->buckets);
23492 for (auto const name : input.meta.badConstants) {
23493 if (auto const b = folly::get_ptr(untracked.badConstants, name)) {
23494 assertx(!traceForConstant(name));
23495 assertx(b->present->contains(i));
23496 always_assert(seenBadConstants.emplace(name).second);
23497 input.meta.badConstantBuckets.emplace_back(name, *b);
23498 } else {
23499 auto const t = traceForConstant(name);
23500 always_assert_flog(
23501 t, "{} is on input bad constants, but has no tracking state",
23502 name
23504 always_assert(seenBadConstants.emplace(name).second);
23505 input.meta.badConstantBuckets.emplace_back(name, t->buckets);
23506 assertx(t->buckets.present->contains(i));
23510 std::sort(
23511 begin(input.meta.classBuckets),
23512 end(input.meta.classBuckets),
23513 [] (auto const& p1, auto const& p2) {
23514 return string_data_lt_type{}(p1.first, p2.first);
23517 std::sort(
23518 begin(input.meta.funcBuckets),
23519 end(input.meta.funcBuckets),
23520 [] (auto const& p1, auto const& p2) {
23521 return string_data_lt_func{}(p1.first, p2.first);
23524 std::sort(
23525 begin(input.meta.unitBuckets),
23526 end(input.meta.unitBuckets),
23527 [] (auto const& p1, auto const& p2) {
23528 return string_data_lt{}(p1.first, p2.first);
23531 std::sort(
23532 begin(input.meta.badConstantBuckets),
23533 end(input.meta.badConstantBuckets),
23534 [] (auto const& p1, auto const& p2) {
23535 return string_data_lt{}(p1.first, p2.first);
23539 input.meta.classBuckets.shrink_to_fit();
23540 input.meta.funcBuckets.shrink_to_fit();
23541 input.meta.unitBuckets.shrink_to_fit();
23542 input.meta.badConstantBuckets.shrink_to_fit();
23543 decltype(bucket.classes){}.swap(bucket.classes);
23544 decltype(bucket.deps){}.swap(bucket.deps);
23546 return nullptr;
23551 // Ninth pass: Strictly debug. Check various invariants.
23552 void AnalysisScheduler::tracePass9(const std::vector<AnalysisInput>& inputs) {
23553 if (!debug) return;
23555 TSStringToOneT<size_t> classes;
23556 FSStringToOneT<size_t> funcs;
23557 SStringToOneT<size_t> units;
23559 using K = AnalysisInput::Kind;
23561 // Every class/func/unit on an AnalysisInput should only be one
23562 // AnalysisInput.
23563 for (auto const& i : inputs) {
23564 for (auto const& [n, k] : i.classes) {
23565 if (!any(k & K::Rep)) continue;
23566 auto const [it, e] = classes.try_emplace(n, i.meta.bucketIdx);
23567 always_assert_flog(
23569 "Class {} is in both bucket {} and {}!\n",
23570 n, it->second, i.meta.bucketIdx
23573 for (auto const& [n, k] : i.funcs) {
23574 if (!any(k & K::Rep)) continue;
23575 auto const [it, e] = funcs.try_emplace(n, i.meta.bucketIdx);
23576 if (e) continue;
23577 always_assert_flog(
23579 "Func {} is in both bucket {} and {}!\n",
23580 n, it->second, i.meta.bucketIdx
23583 for (auto const& [n, k] : i.units) {
23584 if (!any(k & K::Rep)) continue;
23585 auto const [it, e] = units.try_emplace(n, i.meta.bucketIdx);
23586 if (e) continue;
23587 always_assert_flog(
23589 "Unit {} is in both bucket {} and {}!\n",
23590 n, it->second, i.meta.bucketIdx
23594 // Dep class/funcs/units can be in multiple AnalysisInputs, but
23595 // can't appear more than once in the same AnalysisInput.
23596 for (auto const& [n, k] : i.classes) {
23597 if (!any(k & K::Dep)) continue;
23598 always_assert_flog(
23599 !any(folly::get_default(i.classes, n) & K::Rep),
23600 "Class {} is in both classes and depClasses in bucket {}\n",
23601 n, i.meta.bucketIdx
23604 for (auto const& [n, k] : i.funcs) {
23605 if (!any(k & K::Dep)) continue;
23606 always_assert_flog(
23607 !any(folly::get_default(i.funcs, n) & K::Rep),
23608 "Func {} is in both funcs and depFuncs in bucket {}\n",
23609 n, i.meta.bucketIdx
23612 for (auto const& [n, k] : i.units) {
23613 if (!any(k & K::Dep)) continue;
23614 always_assert_flog(
23615 !any(folly::get_default(i.units, n) & K::Rep),
23616 "Unit {} is in both units and depUnits in bucket {}\n",
23617 n, i.meta.bucketIdx
23621 // Ditto for "bad" classes or funcs.
23622 for (auto const n : i.meta.badClasses) {
23623 always_assert_flog(
23624 !any(folly::get_default(i.classes, n) & K::Rep),
23625 "Class {} is in both classes and badClasses in bucket {}\n",
23626 n, i.meta.bucketIdx
23628 always_assert_flog(
23629 !any(folly::get_default(i.classes, n) & K::Dep),
23630 "Class {} is in both depClasses and badClasses in bucket {}\n",
23631 n, i.meta.bucketIdx
23634 for (auto const n : i.meta.badFuncs) {
23635 always_assert_flog(
23636 !any(folly::get_default(i.funcs, n) & K::Rep),
23637 "Func {} is in both funcs and badFuncs in bucket {}\n",
23638 n, i.meta.bucketIdx
23640 always_assert_flog(
23641 !any(folly::get_default(i.funcs, n) & K::Dep),
23642 "Func {} is in both depFuncs and badFuncs in bucket {}\n",
23643 n, i.meta.bucketIdx
23649 // Group the work that needs to run into buckets of the given size.
23650 std::vector<AnalysisInput> AnalysisScheduler::schedule(size_t bucketSize,
23651 size_t maxBucketSize) {
23652 FTRACE(2, "AnalysisScheduler: scheduling {} items into buckets of "
23653 "size {} (max {})\n",
23654 workItems(), bucketSize, maxBucketSize);
23656 tracePass1();
23657 maybeDumpTraces();
23658 tracePass2();
23659 tracePass3();
23660 auto leafs = tracePass4();
23661 auto buckets = tracePass5(bucketSize, maxBucketSize, std::move(leafs));
23662 auto inputs = tracePass6(buckets);
23663 tracePass7(inputs);
23664 tracePass8(std::move(buckets), inputs.inputs);
23665 tracePass9(inputs.inputs);
23667 totalWorkItems.store(0);
23668 FTRACE(2, "AnalysisScheduler: scheduled {} buckets\n", inputs.inputs.size());
23669 ++round;
23670 return std::move(inputs.inputs);
23673 //////////////////////////////////////////////////////////////////////
23675 namespace {
23677 //////////////////////////////////////////////////////////////////////
23679 // If we optimized a top-level constant's value to a scalar, we no
23680 // longer need the associated 86cinit function. This fixes up the
23681 // metadata to remove it.
23682 FSStringSet strip_unneeded_constant_inits(AnalysisIndex::IndexData& index) {
23683 FSStringSet stripped;
23685 for (auto const name : index.outFuncNames) {
23686 auto const cnsName = Constant::nameFromFuncName(name);
23687 if (!cnsName) continue;
23688 auto const it = index.constants.find(cnsName);
23689 if (it == end(index.constants)) continue;
23690 auto const& cns = *it->second.first;
23691 if (type(cns.val) == KindOfUninit) continue;
23692 stripped.emplace(name);
23694 if (stripped.empty()) return stripped;
23696 for (auto const name : stripped) {
23697 index.deps->getChanges().remove(*index.funcs.at(name));
23698 index.funcs.erase(name);
23699 index.finfos.erase(name);
23702 index.outFuncNames.erase(
23703 std::remove_if(
23704 begin(index.outFuncNames), end(index.outFuncNames),
23705 [&] (SString f) { return stripped.count(f); }
23707 end(index.outFuncNames)
23710 for (auto& [_, unit] : index.units) {
23711 unit->funcs.erase(
23712 std::remove_if(
23713 begin(unit->funcs), end(unit->funcs),
23714 [&, unit=unit] (SString f) {
23715 if (!stripped.count(f)) return false;
23716 assertx(
23717 index.deps->bucketFor(unit).process->contains(index.bucketIdx)
23719 return true;
23722 end(unit->funcs)
23726 return stripped;
23729 // Record the new set of classes which this class has inherited
23730 // constants from. This set can change (always shrink) due to
23731 // optimizations rewriting constants.
23732 TSStringSet record_cns_bases(const php::Class& cls,
23733 const AnalysisIndex::IndexData& index) {
23734 TSStringSet out;
23735 if (!cls.cinfo) return out;
23736 for (auto const& [_, idx] : cls.cinfo->clsConstants) {
23737 if (!cls.name->tsame(idx.idx.cls)) out.emplace(idx.idx.cls);
23738 if (auto const cnsCls = folly::get_default(index.classes, idx.idx.cls)) {
23739 assertx(idx.idx.idx < cnsCls->constants.size());
23740 auto const& cns = cnsCls->constants[idx.idx.idx];
23741 if (!cls.name->tsame(cns.cls)) out.emplace(cns.cls);
23744 return out;
23747 // Mark all "fixed" class constants. A class constant is fixed if it's
23748 // value can't change going forward. This transforms any dependency on
23749 // that constant from a transitive one (that gets put on the trace) to
23750 // a strict dependency (which only goes on the dep list).
23751 void mark_fixed_class_constants(const php::Class& cls,
23752 AnalysisIndex::IndexData& index) {
23753 auto& changes = index.deps->getChanges();
23755 auto all = true;
23756 for (size_t i = 0, size = cls.constants.size(); i < size; ++i) {
23757 auto const& cns = cls.constants[i];
23758 auto const fixed = [&] {
23759 if (!cns.val) return true;
23760 if (cns.kind == ConstModifiers::Kind::Type) {
23761 // A type-constant is fixed if it's been resolved.
23762 return cns.resolvedTypeStructure && cns.contextInsensitive;
23763 } else if (cns.kind == ConstModifiers::Kind::Value) {
23764 // A normal constant is fixed if it's a scalar.
23765 return type(*cns.val) != KindOfUninit;
23766 } else {
23767 // Anything else never changes.
23768 return true;
23770 }();
23771 if (fixed) {
23772 changes.fixed(ConstIndex { cls.name, (unsigned int)i });
23773 } else {
23774 all = false;
23777 if (all) changes.fixed(cls);
23779 // While we're at it, record all the names present in this class'
23780 // type-constants. This will be used in forming dependencies.
23781 for (auto const& [_, idx] :
23782 index.index.lookup_flattened_class_type_constants(cls)) {
23783 auto const cinfo = folly::get_default(index.cinfos, idx.cls);
23784 if (!cinfo) continue;
23785 assertx(idx.idx < cinfo->cls->constants.size());
23786 auto const& cns = cinfo->cls->constants[idx.idx];
23787 if (cns.kind != ConstModifiers::Kind::Type) continue;
23788 if (!cns.val.has_value()) continue;
23789 auto const ts = [&] () -> SArray {
23790 if (cns.resolvedTypeStructure &&
23791 (cns.contextInsensitive || cls.name->tsame(cns.cls))) {
23792 return cns.resolvedTypeStructure;
23794 assertx(tvIsDict(*cns.val));
23795 return val(*cns.val).parr;
23796 }();
23797 auto const name = type_structure_name(ts);
23798 if (!name) continue;
23799 changes.typeCnsName(cls, AnalysisChangeSet::Class { name });
23803 // Mark whether this unit is "fixed". An unit is fixed if all the
23804 // type-aliases in it are resolved.
23805 void mark_fixed_unit(const php::Unit& unit,
23806 AnalysisChangeSet& changes) {
23807 auto all = true;
23808 for (auto const& ta : unit.typeAliases) {
23809 if (!ta->resolvedTypeStructure) all = false;
23810 auto const ts = [&] {
23811 if (ta->resolvedTypeStructure) return ta->resolvedTypeStructure;
23812 return ta->typeStructure;
23813 }();
23814 auto const name = type_structure_name(ts);
23815 if (!name) continue;
23816 changes.typeCnsName(unit, AnalysisChangeSet::Class { name });
23818 if (all) changes.fixed(unit);
23821 //////////////////////////////////////////////////////////////////////
23825 //////////////////////////////////////////////////////////////////////
23827 AnalysisIndex::AnalysisIndex(
23828 AnalysisWorklist& worklist,
23829 VU<php::Class> classes,
23830 VU<php::Func> funcs,
23831 VU<php::Unit> units,
23832 VU<php::ClassBytecode> clsBC,
23833 VU<php::FuncBytecode> funcBC,
23834 V<AnalysisIndexCInfo> cinfos,
23835 V<AnalysisIndexFInfo> finfos,
23836 V<AnalysisIndexMInfo> minfos,
23837 VU<php::Class> depClasses,
23838 VU<php::Func> depFuncs,
23839 VU<php::Unit> depUnits,
23840 AnalysisInput::Meta meta,
23841 Mode mode
23842 ) : m_data{std::make_unique<IndexData>(*this, worklist, mode)}
23844 m_data->bucketIdx = meta.bucketIdx;
23846 m_data->badClasses = std::move(meta.badClasses);
23847 m_data->badFuncs = std::move(meta.badFuncs);
23848 m_data->badConstants = std::move(meta.badConstants);
23850 std::vector<SString> depClassNames;
23852 m_data->outClassNames.reserve(classes.size());
23853 m_data->allClasses.reserve(classes.size() + depClasses.size());
23854 depClassNames.reserve(depClasses.size());
23855 for (auto& cls : classes) {
23856 for (auto& clo : cls->closures) {
23857 always_assert(
23858 m_data->classes.emplace(clo->name, clo.get()).second
23861 auto const name = cls->name;
23862 always_assert(m_data->classes.emplace(name, cls.get()).second);
23863 m_data->outClassNames.emplace_back(name);
23864 m_data->allClasses.emplace(name, std::move(cls));
23866 for (auto& cls : depClasses) {
23867 for (auto& clo : cls->closures) {
23868 always_assert(
23869 m_data->classes.emplace(clo->name, clo.get()).second
23872 auto const name = cls->name;
23873 always_assert(m_data->classes.emplace(name, cls.get()).second);
23874 depClassNames.emplace_back(name);
23875 m_data->allClasses.emplace(name, std::move(cls));
23878 std::vector<SString> depFuncNames;
23880 m_data->outFuncNames.reserve(funcs.size());
23881 m_data->allFuncs.reserve(funcs.size() + depFuncs.size());
23882 m_data->funcs.reserve(funcs.size() + depFuncs.size());
23883 depFuncNames.reserve(depFuncs.size());
23884 for (auto& func : funcs) {
23885 auto const name = func->name;
23886 always_assert(m_data->funcs.emplace(name, func.get()).second);
23887 m_data->outFuncNames.emplace_back(name);
23888 m_data->allFuncs.emplace(name, std::move(func));
23890 for (auto& func : depFuncs) {
23891 auto const name = func->name;
23892 always_assert(m_data->funcs.emplace(name, func.get()).second);
23893 depFuncNames.emplace_back(name);
23894 m_data->allFuncs.emplace(name, std::move(func));
23897 auto const assignFInfoIdx = [&] (php::Func& func, FuncInfo2& finfo) {
23898 always_assert(func.idx == std::numeric_limits<uint32_t>::max());
23899 always_assert(!finfo.func);
23900 finfo.func = &func;
23901 func.idx = m_data->finfosByIdx.size();
23902 m_data->finfosByIdx.emplace_back(&finfo);
23905 auto const addCInfo = [&] (ClassInfo2* cinfo) {
23906 auto cls = folly::get_default(m_data->classes, cinfo->name);
23907 always_assert(cls);
23908 always_assert(!cls->cinfo);
23909 always_assert(!cinfo->cls);
23910 cls->cinfo = cinfo;
23911 cinfo->cls = cls;
23913 auto const numMethods = cls->methods.size();
23914 always_assert(cinfo->funcInfos.size() == numMethods);
23915 for (size_t i = 0; i < numMethods; ++i) {
23916 assignFInfoIdx(*cls->methods[i], *cinfo->funcInfos[i]);
23918 always_assert(m_data->cinfos.emplace(cinfo->name, cinfo).second);
23921 m_data->allCInfos.reserve(cinfos.size());
23922 for (auto& wrapper : cinfos) {
23923 for (auto& clo : wrapper.ptr->closures) addCInfo(clo.get());
23924 addCInfo(wrapper.ptr.get());
23925 auto const name = wrapper.ptr->name;
23926 m_data->allCInfos.emplace(name, wrapper.ptr.release());
23929 m_data->finfos.reserve(finfos.size());
23930 m_data->allFInfos.reserve(finfos.size());
23931 for (auto& wrapper : finfos) {
23932 auto func = folly::get_default(m_data->funcs, wrapper.ptr->name);
23933 always_assert(func);
23934 assignFInfoIdx(*func, *wrapper.ptr);
23935 auto const name = wrapper.ptr->name;
23936 always_assert(m_data->finfos.emplace(name, wrapper.ptr.get()).second);
23937 m_data->allFInfos.emplace(name, wrapper.ptr.release());
23940 m_data->minfos.reserve(minfos.size());
23941 m_data->allMInfos.reserve(minfos.size());
23942 for (auto& wrapper : minfos) {
23943 auto const minfo = wrapper.ptr.get();
23944 auto cls = folly::get_default(m_data->classes, minfo->cls);
23945 always_assert(cls);
23946 always_assert(!cls->cinfo);
23948 auto const numMethods = cls->methods.size();
23949 always_assert(minfo->finfos.size() == numMethods);
23950 for (size_t i = 0; i < numMethods; ++i) {
23951 assignFInfoIdx(*cls->methods[i], *minfo->finfos[i]);
23953 auto const numClosures = cls->closures.size();
23954 always_assert(minfo->closureInvokes.size() == numClosures);
23955 for (size_t i = 0; i < numClosures; ++i) {
23956 auto& clo = cls->closures[i];
23957 auto& finfo = minfo->closureInvokes[i];
23958 assertx(clo->methods.size() == 1);
23959 assignFInfoIdx(*clo->methods[0], *finfo);
23962 auto const name = minfo->cls;
23963 always_assert(m_data->minfos.emplace(name, minfo).second);
23964 m_data->allMInfos.emplace(name, wrapper.ptr.release());
23967 for (auto& bc : clsBC) {
23968 auto cls = folly::get_default(m_data->classes, bc->cls);
23969 always_assert(cls);
23971 size_t idx = 0;
23972 for (auto& meth : cls->methods) {
23973 assertx(idx < bc->methodBCs.size());
23974 auto& methBC = bc->methodBCs[idx++];
23975 always_assert(methBC.name == meth->name);
23976 meth->rawBlocks = std::move(methBC.bc);
23978 for (auto& clo : cls->closures) {
23979 assertx(idx < bc->methodBCs.size());
23980 auto& methBC = bc->methodBCs[idx++];
23981 assertx(clo->methods.size() == 1);
23982 always_assert(methBC.name == clo->methods[0]->name);
23983 clo->methods[0]->rawBlocks = std::move(methBC.bc);
23985 assertx(idx == bc->methodBCs.size());
23987 for (auto& bc : funcBC) {
23988 auto func = folly::get_default(m_data->funcs, bc->name);
23989 always_assert(func);
23990 func->rawBlocks = std::move(bc->bc);
23993 std::vector<SString> depUnitNames;
23995 m_data->outUnitNames.reserve(units.size());
23996 m_data->units.reserve(units.size() + depUnits.size());
23997 m_data->allUnits.reserve(units.size() + depUnits.size());
23998 depUnitNames.reserve(depUnits.size());
24000 auto const addUnit = [&] (php::Unit* unit) {
24001 auto const isNative = is_native_unit(*unit);
24002 for (auto& cns : unit->constants) {
24003 always_assert(
24004 m_data->constants.try_emplace(cns->name, cns.get(), unit).second
24006 assertx(!m_data->badConstants.count(cns->name));
24007 if (isNative && type(cns->val) == KindOfUninit) {
24008 m_data->dynamicConstants.emplace(cns->name);
24011 for (auto& typeAlias : unit->typeAliases) {
24012 always_assert(
24013 m_data->typeAliases.try_emplace(
24014 typeAlias->name,
24015 typeAlias.get(),
24016 unit
24017 ).second
24019 assertx(!m_data->badClasses.count(typeAlias->name));
24021 always_assert(m_data->units.emplace(unit->filename, unit).second);
24023 for (auto& unit : units) {
24024 addUnit(unit.get());
24025 auto const name = unit->filename;
24026 m_data->outUnitNames.emplace_back(name);
24027 m_data->allUnits.emplace(name, std::move(unit));
24029 for (auto& unit : depUnits) {
24030 addUnit(unit.get());
24031 auto const name = unit->filename;
24032 m_data->allUnits.emplace(name, std::move(unit));
24033 depUnitNames.emplace_back(name);
24036 for (auto const& [_, cls] : m_data->allClasses) {
24037 if (!cls->cinfo) continue;
24038 always_assert(cls.get() == cls->cinfo->cls);
24040 for (auto const& [_, cinfo]: m_data->allCInfos) {
24041 always_assert(cinfo->cls);
24042 always_assert(cinfo.get() == cinfo->cls->cinfo);
24044 for (size_t i = 0, size = m_data->finfosByIdx.size(); i < size; ++i) {
24045 auto finfo = m_data->finfosByIdx[i];
24046 always_assert(finfo->func);
24047 always_assert(finfo->func->idx == i);
24050 ClassGraph::setAnalysisIndex(*m_data);
24052 // Use the BucketSet information in the input metadata to set up the
24053 // permissions.
24054 for (auto& [n, b] : meta.classBuckets) {
24055 if (auto const c = folly::get_default(m_data->classes, n)) {
24056 m_data->deps->restrict(c, std::move(b));
24057 } else if (m_data->badClasses.count(n)) {
24058 m_data->deps->restrict(DepTracker::Class { n }, std::move(b));
24061 for (auto& [n, b] : meta.funcBuckets) {
24062 if (auto const f = folly::get_default(m_data->funcs, n)) {
24063 m_data->deps->restrict(f, std::move(b));
24064 } else if (m_data->badFuncs.count(n)) {
24065 m_data->deps->restrict(DepTracker::Func { n }, std::move(b));
24068 for (auto& [n, b] : meta.unitBuckets) {
24069 if (auto const u = folly::get_default(m_data->units, n)) {
24070 m_data->deps->restrict(u, std::move(b));
24073 for (auto& [n, b] : meta.badConstantBuckets) {
24074 assertx(m_data->badConstants.count(n));
24075 m_data->deps->restrict(DepTracker::Constant { n }, std::move(b));
24078 initialize_worklist(
24079 meta,
24080 std::move(depClassNames),
24081 std::move(depFuncNames),
24082 std::move(depUnitNames)
24086 AnalysisIndex::~AnalysisIndex() {
24087 ClassGraph::clearAnalysisIndex();
24090 // Initialize the worklist with the items we know we must
24091 // process. Also add dependency information for the items which *may*
24092 // run.
24093 void AnalysisIndex::initialize_worklist(const AnalysisInput::Meta& meta,
24094 std::vector<SString> depClasses,
24095 std::vector<SString> depFuncs,
24096 std::vector<SString> depUnits) {
24097 auto const add = [&] (FuncClsUnit src, const AnalysisDeps& deps) {
24098 for (auto const [name, t] : deps.funcs) {
24099 m_data->deps->preadd(src, DepTracker::Func { name }, t);
24101 for (auto const [meth, t] : deps.methods) {
24102 m_data->deps->preadd(src, meth, t);
24104 for (auto const cns : deps.clsConstants) {
24105 m_data->deps->preadd(src, cns);
24107 for (auto const cls : deps.anyClsConstants) {
24108 m_data->deps->preadd(src, DepTracker::AnyClassConstant { cls });
24110 for (auto const cns : deps.constants) {
24111 m_data->deps->preadd(src, DepTracker::Constant { cns });
24115 for (auto const name : m_data->outClassNames) {
24116 auto const cls = m_data->classes.at(name);
24117 if (is_closure(*cls)) {
24118 assertx(!cls->closureContextCls);
24119 assertx(cls->closureDeclFunc);
24120 assertx(!meta.classDeps.count(name));
24121 continue;
24123 assertx(m_data->deps->bucketFor(cls).process->contains(m_data->bucketIdx));
24124 if (auto const deps = folly::get_ptr(meta.classDeps, name)) {
24125 add(cls, *deps);
24126 } else {
24127 m_data->worklist.schedule(cls);
24131 for (auto const name : depClasses) {
24132 if (auto const deps = folly::get_ptr(meta.classDeps, name)) {
24133 assertx(
24134 m_data->deps->bucketFor(m_data->classes.at(name))
24135 .process->contains(m_data->bucketIdx)
24137 add(m_data->classes.at(name), *deps);
24138 } else if (meta.processDepCls.count(name)) {
24139 assertx(
24140 m_data->deps->bucketFor(m_data->classes.at(name))
24141 .process->contains(m_data->bucketIdx)
24143 m_data->worklist.schedule(m_data->classes.at(name));
24147 for (auto const name : m_data->outFuncNames) {
24148 auto const func = m_data->funcs.at(name);
24149 assertx(m_data->deps->bucketFor(func).process->contains(m_data->bucketIdx));
24150 if (auto const deps = folly::get_ptr(meta.funcDeps, name)) {
24151 add(func, *deps);
24152 } else {
24153 m_data->worklist.schedule(func);
24157 for (auto const name : depFuncs) {
24158 if (auto const deps = folly::get_ptr(meta.funcDeps, name)) {
24159 assertx(
24160 m_data->deps->bucketFor(m_data->funcs.at(name))
24161 .process->contains(m_data->bucketIdx)
24163 add(m_data->funcs.at(name), *deps);
24164 } else if (meta.processDepFunc.count(name)) {
24165 assertx(
24166 m_data->deps->bucketFor(m_data->funcs.at(name))
24167 .process->contains(m_data->bucketIdx)
24169 m_data->worklist.schedule(m_data->funcs.at(name));
24173 for (auto const name : m_data->outUnitNames) {
24174 auto const unit = m_data->units.at(name);
24175 assertx(
24176 m_data->deps->bucketFor(unit).process->contains(m_data->bucketIdx)
24178 if (auto const deps = folly::get_ptr(meta.unitDeps, name)) {
24179 add(unit, *deps);
24180 } else {
24181 m_data->worklist.schedule(unit);
24185 for (auto const name : depUnits) {
24186 if (auto const deps = folly::get_ptr(meta.unitDeps, name)) {
24187 assertx(
24188 m_data->deps->bucketFor(m_data->units.at(name))
24189 .process->contains(m_data->bucketIdx)
24191 add(m_data->units.at(name), *deps);
24192 } else if (meta.processDepUnit.count(name)) {
24193 assertx(
24194 m_data->deps->bucketFor(m_data->units.at(name))
24195 .process->contains(m_data->bucketIdx)
24197 m_data->worklist.schedule(m_data->units.at(name));
24202 void AnalysisIndex::start() { ClassGraph::init(); }
24203 void AnalysisIndex::stop() { ClassGraph::destroy(); }
24205 void AnalysisIndex::push_context(const Context& ctx) {
24206 m_data->contexts.emplace_back(ctx);
24209 void AnalysisIndex::pop_context() {
24210 assertx(!m_data->contexts.empty());
24211 m_data->contexts.pop_back();
24214 bool AnalysisIndex::set_in_type_cns(bool b) {
24215 auto const was = m_data->inTypeCns;
24216 m_data->inTypeCns = b;
24217 return was;
24220 void AnalysisIndex::freeze() {
24221 FTRACE(2, "Freezing index...\n");
24222 assertx(!m_data->frozen);
24223 m_data->frozen = true;
24225 // We're going to do a final set of analysis to record dependencies,
24226 // so we must re-add the original set of items to the worklist.
24227 assertx(!m_data->worklist.next());
24228 for (auto const name : m_data->outClassNames) {
24229 auto const cls = m_data->classes.at(name);
24230 if (is_closure(*cls)) continue;
24231 m_data->deps->reset(cls);
24232 m_data->worklist.schedule(cls);
24234 for (auto const name : m_data->outFuncNames) {
24235 auto const func = m_data->funcs.at(name);
24236 m_data->deps->reset(func);
24237 m_data->worklist.schedule(func);
24239 for (auto const name : m_data->outUnitNames) {
24240 auto const unit = m_data->units.at(name);
24241 m_data->deps->reset(unit);
24242 m_data->worklist.schedule(unit);
24246 bool AnalysisIndex::frozen() const { return m_data->frozen; }
24248 const php::Unit& AnalysisIndex::lookup_func_unit(const php::Func& f) const {
24249 auto const it = m_data->units.find(f.unit);
24250 always_assert_flog(
24251 it != end(m_data->units),
24252 "Attempting to access missing unit {} for func {}",
24253 f.unit, func_fullname(f)
24255 return *it->second;
24258 const php::Unit& AnalysisIndex::lookup_class_unit(const php::Class& c) const {
24259 auto const it = m_data->units.find(c.unit);
24260 always_assert_flog(
24261 it != end(m_data->units),
24262 "Attempting to access missing unit {} for class {}",
24263 c.unit, c.name
24265 return *it->second;
24268 const php::Unit& AnalysisIndex::lookup_unit(SString n) const {
24269 auto const it = m_data->units.find(n);
24270 always_assert_flog(
24271 it != end(m_data->units),
24272 "Attempting to access missing unit {}",
24275 return *it->second;
24278 const php::Class*
24279 AnalysisIndex::lookup_const_class(const php::Const& cns) const {
24280 if (!m_data->deps->add(AnalysisDeps::Class { cns.cls })) return nullptr;
24281 return folly::get_default(m_data->classes, cns.cls);
24284 const php::Class* AnalysisIndex::lookup_class(SString name) const {
24285 return folly::get_default(m_data->classes, name);
24288 const php::Class&
24289 AnalysisIndex::lookup_closure_context(const php::Class& cls) const {
24290 always_assert_flog(
24291 !cls.closureContextCls,
24292 "AnalysisIndex does not yet support closure contexts (for {})",
24293 cls.name
24295 return cls;
24298 res::Func AnalysisIndex::resolve_func(SString n) const {
24299 n = normalizeNS(n);
24300 if (m_data->mode == Mode::Constants) {
24301 return res::Func { res::Func::FuncName { n } };
24304 if (m_data->deps->add(AnalysisDeps::Func { n })) {
24305 if (auto const finfo = folly::get_default(m_data->finfos, n)) {
24306 return res::Func { res::Func::Fun2 { finfo } };
24308 if (m_data->badFuncs.count(n)) {
24309 return res::Func { res::Func::MissingFunc { n } };
24312 return res::Func { res::Func::FuncName { n } };
24315 Optional<res::Class> AnalysisIndex::resolve_class(SString n) const {
24316 n = normalizeNS(n);
24317 if (!m_data->deps->add(AnalysisDeps::Class { n })) {
24318 // If we can't use information about the class, there's no point
24319 // in checking, and just use the unknown case.
24320 return res::Class::getOrCreate(n);
24322 if (auto const cinfo = folly::get_default(m_data->cinfos, n)) {
24323 return res::Class::get(*cinfo);
24325 if (m_data->typeAliases.count(n)) return std::nullopt;
24326 // A php::Class should always be accompanied by it's ClassInfo,
24327 // unless if it's uninstantiable. So, if we have a php::Class here,
24328 // we know it's uninstantiable.
24329 if (m_data->badClasses.count(n) || m_data->classes.count(n)) {
24330 return std::nullopt;
24332 return res::Class::getOrCreate(n);
24335 Optional<res::Class> AnalysisIndex::resolve_class(const php::Class& cls) const {
24336 if (!m_data->deps->add(AnalysisDeps::Class { cls.name })) {
24337 return res::Class::getOrCreate(cls.name);
24339 if (cls.cinfo) return res::Class::get(*cls.cinfo);
24340 return std::nullopt;
24343 res::Func AnalysisIndex::resolve_func_or_method(const php::Func& f) const {
24344 if (!m_data->deps->add(f)) {
24345 if (!f.cls) {
24346 return res::Func { res::Func::FuncName { f.name } };
24348 return res::Func { res::Func::MethodName { f.cls->name, f.name } };
24350 if (!f.cls) return res::Func { res::Func::Fun2 { &func_info(*m_data, f) } };
24351 return res::Func { res::Func::Method2 { &func_info(*m_data, f) } };
24354 Type AnalysisIndex::lookup_constant(SString name) const {
24355 if (!m_data->deps->add(AnalysisDeps::Constant { name })) return TInitCell;
24357 if (auto const p = folly::get_ptr(m_data->constants, name)) {
24358 auto const cns = p->first;
24359 if (type(cns->val) != KindOfUninit) return from_cell(cns->val);
24360 if (m_data->dynamicConstants.count(name)) return TInitCell;
24361 auto const fname = Constant::funcNameFromName(name);
24362 auto const fit = m_data->finfos.find(fname);
24363 // We might have the unit present by chance, but without an
24364 // explicit dependence on the constant, we might not have the init
24365 // func present.
24366 if (fit == end(m_data->finfos)) return TInitCell;
24367 return unctx(unserialize_type(fit->second->returnTy));
24369 return m_data->badConstants.count(name) ? TBottom : TInitCell;
24372 std::vector<std::pair<SString, ConstIndex>>
24373 AnalysisIndex::lookup_flattened_class_type_constants(
24374 const php::Class& cls
24375 ) const {
24376 std::vector<std::pair<SString, ConstIndex>> out;
24378 auto const cinfo = cls.cinfo;
24379 if (!cinfo) return out;
24381 out.reserve(cinfo->clsConstants.size());
24382 for (auto const& [name, idx] : cinfo->clsConstants) {
24383 if (idx.kind != ConstModifiers::Kind::Type) continue;
24384 out.emplace_back(name, idx.idx);
24386 std::sort(
24387 begin(out), end(out),
24388 [] (auto const& p1, auto const& p2) {
24389 return string_data_lt{}(p1.first, p2.first);
24392 return out;
24395 std::vector<std::pair<SString, ClsConstInfo>>
24396 AnalysisIndex::lookup_class_constants(const php::Class& cls) const {
24397 std::vector<std::pair<SString, ClsConstInfo>> out;
24398 out.reserve(cls.constants.size());
24399 for (auto const& cns : cls.constants) {
24400 if (cns.kind != ConstModifiers::Kind::Value) continue;
24401 if (!cns.val) continue;
24402 if (cns.val->m_type != KindOfUninit) {
24403 out.emplace_back(cns.name, ClsConstInfo{ from_cell(*cns.val), 0 });
24404 } else if (!cls.cinfo) {
24405 out.emplace_back(cns.name, ClsConstInfo{ TInitCell, 0 });
24406 } else {
24407 auto info = folly::get_default(
24408 cls.cinfo->clsConstantInfo,
24409 cns.name,
24410 ClsConstInfo{ TInitCell, 0 }
24412 info.type = unserialize_type(std::move(info.type));
24413 out.emplace_back(cns.name, std::move(info));
24416 return out;
24419 ClsConstLookupResult
24420 AnalysisIndex::lookup_class_constant(const Type& cls,
24421 const Type& name) const {
24422 ITRACE(4, "lookup_class_constant: ({}) {}::{}\n",
24423 show(current_context(*m_data)), show(cls), show(name));
24424 Trace::Indent _;
24426 AnalysisIndexAdaptor adaptor{*this};
24427 InTypeCns _2{adaptor, false};
24429 using R = ClsConstLookupResult;
24431 auto const conservative = [] {
24432 ITRACE(4, "conservative\n");
24433 return R{ TInitCell, TriBool::Maybe, true };
24436 auto const notFound = [] {
24437 ITRACE(4, "not found\n");
24438 return R{ TBottom, TriBool::No, false };
24441 if (!is_specialized_cls(cls)) return conservative();
24443 // We could easily support the case where we don't know the constant
24444 // name, but know the class (like we do for properties), by unioning
24445 // together all possible constants. However it very rarely happens,
24446 // but when it does, the set of constants to union together can be
24447 // huge and it becomes very expensive.
24448 if (!is_specialized_string(name)) return conservative();
24449 auto const sname = sval_of(name);
24451 auto const& dcls = dcls_of(cls);
24452 if (dcls.isExact()) {
24453 auto const rcls = dcls.cls();
24454 auto const cinfo = rcls.cinfo2();
24455 if (!cinfo || !m_data->deps->add(AnalysisDeps::Class { rcls.name() })) {
24456 (void)m_data->deps->add(AnalysisDeps::AnyClassConstant { rcls.name() });
24457 return conservative();
24460 ITRACE(4, "{}:\n", cinfo->name);
24461 Trace::Indent _;
24463 auto const idxIt = cinfo->clsConstants.find(sname);
24464 if (idxIt == end(cinfo->clsConstants)) return notFound();
24465 auto const& idx = idxIt->second;
24467 assertx(!m_data->badClasses.count(idx.idx.cls));
24468 if (idx.kind != ConstModifiers::Kind::Value) return notFound();
24470 if (!m_data->deps->add(idx.idx)) return conservative();
24472 auto const cnsClsIt = m_data->classes.find(idx.idx.cls);
24473 if (cnsClsIt == end(m_data->classes)) return conservative();
24474 auto const& cnsCls = cnsClsIt->second;
24476 assertx(idx.idx.idx < cnsCls->constants.size());
24477 auto const& cns = cnsCls->constants[idx.idx.idx];
24478 assertx(cns.kind == ConstModifiers::Kind::Value);
24480 if (!cns.val.has_value()) return notFound();
24482 auto const r = [&] {
24483 if (type(*cns.val) != KindOfUninit) {
24484 // Fully resolved constant with a known value. We don't need
24485 // to register a dependency on the constant because the value
24486 // will never change.
24487 auto mightThrow = bool(cinfo->cls->attrs & AttrInternal);
24488 if (!mightThrow) {
24489 auto const& unit = lookup_class_unit(*cinfo->cls);
24490 auto const moduleName = unit.moduleName;
24491 auto const packageInfo = unit.packageInfo;
24492 if (auto const activeDeployment = packageInfo.getActiveDeployment()) {
24493 if (!packageInfo.moduleInDeployment(
24494 moduleName, *activeDeployment, DeployKind::Hard)) {
24495 mightThrow = true;
24499 return R{ from_cell(*cns.val), TriBool::Yes, mightThrow };
24502 ITRACE(4, "(dynamic)\n");
24503 if (!cnsCls->cinfo) return conservative();
24504 auto const info =
24505 folly::get_ptr(cnsCls->cinfo->clsConstantInfo, cns.name);
24506 return R{
24507 info ? unserialize_type(info->type) : TInitCell,
24508 TriBool::Yes,
24509 true
24511 }();
24512 ITRACE(4, "-> {}\n", show(r));
24513 return r;
24516 // Subclasses not yet implemented
24517 return conservative();
24520 ClsTypeConstLookupResult
24521 AnalysisIndex::lookup_class_type_constant(
24522 const Type& cls,
24523 const Type& name,
24524 const Index::ClsTypeConstLookupResolver& resolver
24525 ) const {
24526 ITRACE(4, "lookup_class_type_constant: ({}) {}::{}\n",
24527 show(current_context(*m_data)), show(cls), show(name));
24528 Trace::Indent _;
24530 using R = ClsTypeConstLookupResult;
24532 auto const conservative = [&] (SString n = nullptr) {
24533 ITRACE(4, "conservative\n");
24534 return R{
24535 TypeStructureResolution { TSDictN, true },
24536 TriBool::Maybe,
24537 TriBool::Maybe
24541 auto const notFound = [] {
24542 ITRACE(4, "not found\n");
24543 return R {
24544 TypeStructureResolution { TBottom, false },
24545 TriBool::No,
24546 TriBool::No
24550 // Unlike lookup_class_constant, we distinguish abstract from
24551 // not-found, as the runtime sometimes treats them differently.
24552 auto const abstract = [] {
24553 ITRACE(4, "abstract\n");
24554 return R {
24555 TypeStructureResolution { TBottom, false },
24556 TriBool::No,
24557 TriBool::Yes
24561 if (!is_specialized_cls(cls)) return conservative();
24563 // As in lookup_class_constant, we could handle this, but it's not
24564 // worth it.
24565 if (!is_specialized_string(name)) return conservative();
24566 auto const sname = sval_of(name);
24568 auto const& dcls = dcls_of(cls);
24569 if (dcls.isExact()) {
24570 auto const rcls = dcls.cls();
24571 auto const cinfo = rcls.cinfo2();
24572 if (!cinfo || !m_data->deps->add(AnalysisDeps::Class { rcls.name() })) {
24573 (void)m_data->deps->add(AnalysisDeps::AnyClassConstant { rcls.name() });
24574 return conservative(rcls.name());
24577 ITRACE(4, "{}:\n", cinfo->name);
24578 Trace::Indent _;
24580 auto const idxIt = cinfo->clsConstants.find(sname);
24581 if (idxIt == end(cinfo->clsConstants)) return notFound();
24582 auto const& idx = idxIt->second;
24584 assertx(!m_data->badClasses.count(idx.idx.cls));
24585 if (idx.kind != ConstModifiers::Kind::Type) return notFound();
24587 if (!m_data->deps->add(idx.idx)) return conservative(rcls.name());
24589 auto const cnsClsIt = m_data->classes.find(idx.idx.cls);
24590 if (cnsClsIt == end(m_data->classes)) return conservative(rcls.name());
24591 auto const& cnsCls = cnsClsIt->second;
24593 assertx(idx.idx.idx < cnsCls->constants.size());
24594 auto const& cns = cnsCls->constants[idx.idx.idx];
24595 assertx(cns.kind == ConstModifiers::Kind::Type);
24596 if (!cns.val.has_value()) return abstract();
24597 assertx(tvIsDict(*cns.val));
24599 ITRACE(4, "({}) {}\n", cns.cls, show(dict_val(val(*cns.val).parr)));
24601 // If we've been given a resolver, use it. Otherwise resolve it in
24602 // the normal way.
24603 auto resolved = resolver
24604 ? resolver(cns, *cinfo->cls)
24605 : resolve_type_structure(
24606 AnalysisIndexAdaptor { *this }, cns, *cinfo->cls
24609 // The result of resolve_type_structure isn't, in general,
24610 // static. However a type-constant will always be, so force that
24611 // here.
24612 assertx(resolved.type.is(BBottom) || resolved.type.couldBe(BUnc));
24613 resolved.type &= TUnc;
24614 auto const r = R{
24615 std::move(resolved),
24616 TriBool::Yes,
24617 TriBool::No
24619 ITRACE(4, "-> {}\n", show(r));
24620 return r;
24623 // Subclasses not yet implemented
24624 return conservative();
24627 ClsTypeConstLookupResult
24628 AnalysisIndex::lookup_class_type_constant(const php::Class& ctx,
24629 SString name,
24630 ConstIndex idx) const {
24631 ITRACE(4, "lookup_class_type_constant: ({}) {}::{} (from {})\n",
24632 show(current_context(*m_data)),
24633 ctx.name, name,
24634 show(idx, AnalysisIndexAdaptor{ *this }));
24635 Trace::Indent _;
24637 assertx(!m_data->badClasses.count(idx.cls));
24639 using R = ClsTypeConstLookupResult;
24641 auto const conservative = [] {
24642 ITRACE(4, "conservative\n");
24643 return R {
24644 TypeStructureResolution { TSDictN, true },
24645 TriBool::Maybe,
24646 TriBool::Maybe
24650 auto const notFound = [] {
24651 ITRACE(4, "not found\n");
24652 return R {
24653 TypeStructureResolution { TBottom, false },
24654 TriBool::No,
24655 TriBool::No
24659 // Unlike lookup_class_constant, we distinguish abstract from
24660 // not-found, as the runtime sometimes treats them differently.
24661 auto const abstract = [] {
24662 ITRACE(4, "abstract\n");
24663 return R {
24664 TypeStructureResolution { TBottom, false },
24665 TriBool::No,
24666 TriBool::Yes
24670 if (!m_data->deps->add(idx)) return conservative();
24671 auto const cinfo = folly::get_default(m_data->cinfos, idx.cls);
24672 if (!cinfo) return conservative();
24674 assertx(idx.idx < cinfo->cls->constants.size());
24675 auto const& cns = cinfo->cls->constants[idx.idx];
24676 if (cns.kind != ConstModifiers::Kind::Type) return notFound();
24677 if (!cns.val.has_value()) return abstract();
24679 assertx(tvIsDict(*cns.val));
24681 ITRACE(4, "({}) {}\n", cns.cls, show(dict_val(val(*cns.val).parr)));
24683 auto resolved = resolve_type_structure(
24684 AnalysisIndexAdaptor { *this }, cns, ctx
24687 // The result of resolve_type_structure isn't, in general,
24688 // static. However a type-constant will always be, so force that
24689 // here.
24690 assertx(resolved.type.is(BBottom) || resolved.type.couldBe(BUnc));
24691 resolved.type &= TUnc;
24692 auto const r = R{
24693 std::move(resolved),
24694 TriBool::Yes,
24695 TriBool::No
24697 ITRACE(4, "-> {}\n", show(r));
24698 return r;
24701 PropState AnalysisIndex::lookup_private_props(const php::Class& cls) const {
24702 // Private property tracking not yet implemented, so be
24703 // conservative.
24704 return make_unknown_propstate(
24705 AnalysisIndexAdaptor { *this },
24706 cls,
24707 [&] (const php::Prop& prop) {
24708 return (prop.attrs & AttrPrivate) && !(prop.attrs & AttrStatic);
24713 PropState AnalysisIndex::lookup_private_statics(const php::Class& cls) const {
24714 // Private static property tracking not yet implemented, so be
24715 // conservative.
24716 return make_unknown_propstate(
24717 AnalysisIndexAdaptor { *this },
24718 cls,
24719 [&] (const php::Prop& prop) {
24720 return (prop.attrs & AttrPrivate) && (prop.attrs & AttrStatic);
24725 Index::ReturnType AnalysisIndex::lookup_return_type(MethodsInfo* methods,
24726 res::Func rfunc) const {
24727 using R = Index::ReturnType;
24729 auto const meth = [&] (const FuncInfo2& finfo) {
24730 if (methods) {
24731 if (auto ret = methods->lookupReturnType(*finfo.func)) {
24732 return R{ unctx(std::move(ret->t)), ret->effectFree };
24735 if (!m_data->deps->add(*finfo.func, AnalysisDeps::Type::RetType)) {
24736 return R{ TInitCell, false };
24738 return R{
24739 unctx(unserialize_type(finfo.returnTy)),
24740 finfo.effectFree
24744 return match<R>(
24745 rfunc.val,
24746 [&] (res::Func::FuncName f) {
24747 (void)m_data->deps->add(
24748 AnalysisDeps::Func { f.name },
24749 AnalysisDeps::Type::RetType
24751 return R{ TInitCell, false };
24753 [&] (res::Func::MethodName m) {
24754 // If we know the name of the class, we can register a
24755 // dependency on it. If not, nothing we can do.
24756 if (m.cls) (void)m_data->deps->add(AnalysisDeps::Class { m.cls });
24757 return R{ TInitCell, false };
24759 [&] (res::Func::Fun) -> R { always_assert(false); },
24760 [&] (res::Func::Method) -> R { always_assert(false); },
24761 [&] (res::Func::MethodFamily) -> R { always_assert(false); },
24762 [&] (res::Func::MethodOrMissing) -> R { always_assert(false); },
24763 [&] (res::Func::MissingFunc) { return R{ TBottom, false }; },
24764 [&] (res::Func::MissingMethod) { return R{ TBottom, false }; },
24765 [&] (const res::Func::Isect&) -> R { always_assert(false); },
24766 [&] (res::Func::Fun2 f) {
24767 if (!m_data->deps->add(*f.finfo->func, AnalysisDeps::Type::RetType)) {
24768 return R{ TInitCell, false };
24770 return R{
24771 unctx(unserialize_type(f.finfo->returnTy)),
24772 f.finfo->effectFree
24775 [&] (res::Func::Method2 m) { return meth(*m.finfo); },
24776 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
24777 [&] (res::Func::MethodOrMissing2 m) { return meth(*m.finfo); },
24778 [&] (const res::Func::Isect2&) -> R { always_assert(false); }
24782 Index::ReturnType
24783 AnalysisIndex::lookup_return_type(MethodsInfo* methods,
24784 const CompactVector<Type>& args,
24785 const Type& context,
24786 res::Func rfunc) const {
24787 using R = Index::ReturnType;
24789 auto ty = lookup_return_type(methods, rfunc);
24791 auto const contextual = [&] (const FuncInfo2& finfo) {
24792 return context_sensitive_return_type(
24793 *m_data,
24794 { finfo.func, args, context },
24795 std::move(ty)
24799 return match<R>(
24800 rfunc.val,
24801 [&] (res::Func::FuncName) { return ty; },
24802 [&] (res::Func::MethodName) { return ty; },
24803 [&] (res::Func::Fun) -> R { always_assert(false); },
24804 [&] (res::Func::Method) -> R { always_assert(false); },
24805 [&] (res::Func::MethodFamily) -> R { always_assert(false); },
24806 [&] (res::Func::MethodOrMissing) -> R { always_assert(false); },
24807 [&] (res::Func::MissingFunc) { return R{ TBottom, false }; },
24808 [&] (res::Func::MissingMethod) { return R{ TBottom, false }; },
24809 [&] (const res::Func::Isect&) -> R { always_assert(false); },
24810 [&] (res::Func::Fun2 f) { return contextual(*f.finfo); },
24811 [&] (res::Func::Method2 m) { return contextual(*m.finfo); },
24812 [&] (res::Func::MethodFamily2) -> R { always_assert(false); },
24813 [&] (res::Func::MethodOrMissing2 m) { return contextual(*m.finfo); },
24814 [&] (const res::Func::Isect2&) -> R { always_assert(false); }
24818 Index::ReturnType
24819 AnalysisIndex::lookup_foldable_return_type(const CallContext& calleeCtx) const {
24820 constexpr size_t maxNestingLevel = 2;
24822 using R = Index::ReturnType;
24824 auto const& func = *calleeCtx.callee;
24826 if (m_data->mode == Mode::Constants) {
24827 ITRACE_MOD(
24828 Trace::hhbbc, 4,
24829 "Skipping inline interp of {} because analyzing constants\n",
24830 func_fullname(func)
24832 return R{ TInitCell, false };
24835 auto const ctxType =
24836 adjust_closure_context(AnalysisIndexAdaptor { *this }, calleeCtx);
24838 // Don't fold functions when staticness mismatches
24839 if (!func.isClosureBody) {
24840 if ((func.attrs & AttrStatic) && ctxType.couldBe(TObj)) {
24841 return R{ TInitCell, false };
24843 if (!(func.attrs & AttrStatic) && ctxType.couldBe(TCls)) {
24844 return R{ TInitCell, false };
24848 auto const& finfo = func_info(*m_data, func);
24849 // No need to call unserialize_type here. If it's a scalar, there's
24850 // nothing to unserialize anyways.
24851 if (finfo.effectFree && is_scalar(finfo.returnTy)) {
24852 return R{ finfo.returnTy, finfo.effectFree };
24855 auto const& caller = *context_for_deps(*m_data).func;
24857 if (!m_data->deps->add(
24858 func,
24859 AnalysisDeps::Type::ScalarRetType |
24860 AnalysisDeps::Type::Bytecode
24861 )) {
24862 return R{ TInitCell, false };
24864 if (m_data->foldableInterpNestingLevel > maxNestingLevel) {
24865 return R{ TInitCell, false };
24868 if (!func.rawBlocks) {
24869 ITRACE_MOD(
24870 Trace::hhbbc, 4,
24871 "Skipping inline interp of {} because bytecode not present\n",
24872 func_fullname(func)
24874 return R{ TInitCell, false };
24877 auto const contextualRet = [&] () -> Optional<Type> {
24878 ++m_data->foldableInterpNestingLevel;
24879 SCOPE_EXIT { --m_data->foldableInterpNestingLevel; };
24881 auto const wf = php::WideFunc::cns(&func);
24882 auto const fa = analyze_func_inline(
24883 AnalysisIndexAdaptor { *this },
24884 AnalysisContext {
24885 func.unit,
24887 func.cls,
24888 &context_for_deps(*m_data)
24890 ctxType,
24891 calleeCtx.args,
24892 nullptr,
24893 CollectionOpts::EffectFreeOnly
24895 if (!fa.effectFree) return std::nullopt;
24896 return fa.inferredReturn;
24897 }();
24899 if (!contextualRet) {
24900 ITRACE_MOD(
24901 Trace::hhbbc, 4,
24902 "Foldable inline analysis failed due to possible side-effects\n"
24904 return R{ TInitCell, false };
24907 ITRACE_MOD(
24908 Trace::hhbbc, 4,
24909 "Foldable return type: {}\n",
24910 show(*contextualRet)
24913 auto const error_context = [&] {
24914 using namespace folly::gen;
24915 return folly::sformat(
24916 "{} calling {} (context: {}, args: {})",
24917 func_fullname(caller),
24918 func_fullname(func),
24919 show(calleeCtx.context),
24920 from(calleeCtx.args)
24921 | map([] (const Type& t) { return show(t); })
24922 | unsplit<std::string>(",")
24926 auto const insensitive = unserialize_type(finfo.returnTy);
24927 always_assert_flog(
24928 contextualRet->subtypeOf(insensitive),
24929 "Context sensitive return type for {} is {} "
24930 "which not at least as refined as context insensitive "
24931 "return type {}\n",
24932 error_context(),
24933 show(*contextualRet),
24934 show(insensitive)
24936 if (!is_scalar(*contextualRet)) return R{ TInitCell, false };
24938 return R{ *contextualRet, true };
24941 std::pair<Index::ReturnType, size_t>
24942 AnalysisIndex::lookup_return_type_raw(const php::Func& f) const {
24943 auto const& finfo = func_info(*m_data, f);
24944 return std::make_pair(
24945 Index::ReturnType{
24946 unserialize_type(finfo.returnTy),
24947 finfo.effectFree
24949 finfo.returnRefinements
24953 bool AnalysisIndex::func_depends_on_arg(const php::Func& func,
24954 size_t arg) const {
24955 if (!m_data->deps->add(func, AnalysisDeps::Type::UnusedParams)) return true;
24956 auto const& finfo = func_info(*m_data, func);
24957 return arg >= finfo.unusedParams.size() || !finfo.unusedParams.test(arg);
24961 * Given a DCls, return the most specific res::Func for that DCls. For
24962 * intersections, this will call process/general on every component of
24963 * the intersection and combine the results. process is called to
24964 * obtain a res::Func from a ClassInfo. If a ClassInfo isn't
24965 * available, general will be called instead.
24967 template <typename P, typename G>
24968 res::Func AnalysisIndex::rfunc_from_dcls(const DCls& dcls,
24969 SString name,
24970 const P& process,
24971 const G& general) const {
24972 using Func = res::Func;
24973 using Class = res::Class;
24976 * Combine together multiple res::Funcs together. Since the DCls
24977 * represents a class which is a subtype of every ClassInfo in the
24978 * list, every res::Func we get is true.
24980 * The relevant res::Func types in order from most general to more
24981 * specific are:
24983 * MethodName -> FuncFamily -> MethodOrMissing -> Method -> Missing
24985 * Since every res::Func in the intersection is true, we take the
24986 * res::Func which is most specific. Two different res::Funcs cannot
24987 * be contradict. For example, we shouldn't get a Method and a
24988 * Missing since one implies there's no func and the other implies
24989 * one specific func. Or two different res::Funcs shouldn't resolve
24990 * to two different methods.
24992 auto missing = TriBool::Maybe;
24993 Func::Isect2 isect;
24994 const php::Func* singleMethod = nullptr;
24996 auto const onFunc = [&] (Func func) {
24997 match<void>(
24998 func.val,
24999 [&] (Func::MethodName) {},
25000 [&] (Func::Method) { always_assert(false); },
25001 [&] (Func::MethodFamily) { always_assert(false); },
25002 [&] (Func::MethodOrMissing) { always_assert(false); },
25003 [&] (Func::MissingMethod) {
25004 assertx(missing != TriBool::No);
25005 singleMethod = nullptr;
25006 isect.families.clear();
25007 missing = TriBool::Yes;
25009 [&] (Func::FuncName) { always_assert(false); },
25010 [&] (Func::Fun) { always_assert(false); },
25011 [&] (Func::Fun2) { always_assert(false); },
25012 [&] (Func::Method2 m) {
25013 assertx(IMPLIES(singleMethod, singleMethod == m.finfo->func));
25014 assertx(IMPLIES(singleMethod, isect.families.empty()));
25015 assertx(missing != TriBool::Yes);
25016 if (!singleMethod) {
25017 singleMethod = m.finfo->func;
25018 isect.families.clear();
25020 missing = TriBool::No;
25022 [&] (Func::MethodFamily2) { always_assert(false); },
25023 [&] (Func::MethodOrMissing2 m) {
25024 assertx(IMPLIES(singleMethod, singleMethod == m.finfo->func));
25025 assertx(IMPLIES(singleMethod, isect.families.empty()));
25026 if (missing == TriBool::Yes) {
25027 assertx(!singleMethod);
25028 assertx(isect.families.empty());
25029 return;
25031 if (!singleMethod) {
25032 singleMethod = m.finfo->func;
25033 isect.families.clear();
25036 [&] (Func::MissingFunc) { always_assert(false); },
25037 [&] (const Func::Isect&) { always_assert(false); },
25038 [&] (const Func::Isect2&) { always_assert(false); }
25042 auto const onClass = [&] (Class cls, bool isExact) {
25043 auto const g = cls.graph();
25044 if (!g.ensureCInfo()) {
25045 onFunc(general(dcls.containsNonRegular()));
25046 return;
25049 if (auto const cinfo = g.cinfo2()) {
25050 onFunc(process(cinfo, isExact, dcls.containsNonRegular()));
25051 } else {
25052 // The class doesn't have a ClassInfo present, so we cannot call
25053 // process. We can, however, look at any parents that do have a
25054 // ClassInfo. This won't result in as good results, but it
25055 // preserves monotonicity.
25056 onFunc(general(dcls.containsNonRegular()));
25057 if (g.isMissing()) return;
25059 if (!dcls.containsNonRegular() &&
25060 !g.mightBeRegular() &&
25061 g.hasCompleteChildren()) {
25062 if (isExact) {
25063 onFunc(
25064 Func { Func::MissingMethod { dcls.smallestCls().name(), name } }
25066 return;
25069 hphp_fast_set<ClassGraph, ClassGraphHasher> commonParents;
25070 auto first = true;
25071 for (auto const c : g.children()) {
25072 assertx(!c.isMissing());
25073 if (!c.mightBeRegular()) continue;
25075 hphp_fast_set<ClassGraph, ClassGraphHasher> newCommon;
25076 c.walkParents(
25077 [&] (ClassGraph p) {
25078 if (first || commonParents.count(p)) {
25079 newCommon.emplace(p);
25081 return true;
25084 first = false;
25085 commonParents = std::move(newCommon);
25088 if (first) {
25089 onFunc(
25090 Func { Func::MissingMethod { dcls.smallestCls().name(), name } }
25092 return;
25095 assertx(!commonParents.empty());
25096 for (auto const p : commonParents) {
25097 if (!p.ensureCInfo()) continue;
25098 if (auto const cinfo = p.cinfo2()) {
25099 onFunc(process(cinfo, false, false));
25102 return;
25105 g.walkParents(
25106 [&] (ClassGraph p) {
25107 if (!p.ensureCInfo()) return true;
25108 if (auto const cinfo = p.cinfo2()) {
25109 onFunc(process(cinfo, false, dcls.containsNonRegular()));
25110 return false;
25112 return true;
25118 if (dcls.isExact() || dcls.isSub()) {
25119 // If this isn't an intersection, there's only one class to
25120 // process and we're done.
25121 onClass(dcls.cls(), dcls.isExact());
25122 } else if (dcls.isIsect()) {
25123 for (auto const c : dcls.isect()) onClass(c, false);
25124 } else {
25125 assertx(dcls.isIsectAndExact());
25126 auto const [e, i] = dcls.isectAndExact();
25127 onClass(e, true);
25128 for (auto const c : *i) onClass(c, false);
25131 // If we got a method, that always wins. Again, every res::Func is
25132 // true, and method is more specific than a FuncFamily, so it is
25133 // preferred.
25134 if (singleMethod) {
25135 assertx(missing != TriBool::Yes);
25136 // If missing is Maybe, then *every* resolution was to a
25137 // MethodName or MethodOrMissing, so include that fact here by
25138 // using MethodOrMissing.
25139 if (missing == TriBool::Maybe) {
25140 return Func {
25141 Func::MethodOrMissing2 { &func_info(*m_data, *singleMethod) }
25144 return Func { Func::Method2 { &func_info(*m_data, *singleMethod) } };
25146 // We only got unresolved classes. If missing is TriBool::Yes, the
25147 // function doesn't exist. Otherwise be pessimistic.
25148 if (isect.families.empty()) {
25149 if (missing == TriBool::Yes) {
25150 return Func { Func::MissingMethod { dcls.smallestCls().name(), name } };
25152 assertx(missing == TriBool::Maybe);
25153 return general(dcls.containsNonRegular());
25155 // Isect case. Isects always might contain missing funcs.
25156 assertx(missing == TriBool::Maybe);
25158 // We could add a FuncFamily multiple times, so remove duplicates.
25159 std::sort(begin(isect.families), end(isect.families));
25160 isect.families.erase(
25161 std::unique(begin(isect.families), end(isect.families)),
25162 end(isect.families)
25164 // If everything simplifies down to a single FuncFamily, just use
25165 // that.
25166 if (isect.families.size() == 1) {
25167 return Func {
25168 Func::MethodFamily2 { isect.families[0], isect.regularOnly }
25171 return Func { std::move(isect) };
25174 res::Func AnalysisIndex::resolve_method(const Type& thisType,
25175 SString name) const {
25176 assertx(thisType.subtypeOf(BCls) || thisType.subtypeOf(BObj));
25178 using Func = res::Func;
25180 auto const general = [&] (SString maybeCls, bool) {
25181 assertx(name != s_construct.get());
25182 return Func { Func::MethodName { maybeCls, name } };
25185 if (m_data->mode == Mode::Constants) return general(nullptr, true);
25187 auto const process = [&] (ClassInfo2* cinfo,
25188 bool isExact,
25189 bool includeNonRegular) {
25190 assertx(name != s_construct.get());
25192 auto const meth = folly::get_ptr(cinfo->methods, name);
25193 if (!meth) {
25194 // We don't store metadata for special methods, so be pessimistic
25195 // (the lack of a method entry does not mean the call might fail
25196 // at runtme).
25197 if (is_special_method_name(name)) {
25198 return Func { Func::MethodName { cinfo->name, name } };
25200 // We're only considering this class, not it's subclasses. Since
25201 // it doesn't exist here, the resolution will always fail.
25202 if (isExact) {
25203 return Func { Func::MissingMethod { cinfo->name, name } };
25205 // The method isn't present on this class, but it might be in the
25206 // subclasses. In most cases try a general lookup to get a
25207 // slightly better type than nothing.
25208 return general(cinfo->name, includeNonRegular);
25211 if (!m_data->deps->add(meth->meth())) {
25212 return general(cinfo->name, includeNonRegular);
25214 auto const func = func_from_meth_ref(*m_data, meth->meth());
25215 if (!func) return general(cinfo->name, includeNonRegular);
25217 // We don't store method family information about special methods
25218 // and they have special inheritance semantics.
25219 if (is_special_method_name(name)) {
25220 // If we know the class exactly, we can use ftarget.
25221 if (isExact) {
25222 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25224 // The method isn't overwritten, but they don't inherit, so it
25225 // could be missing.
25226 if (meth->attrs & AttrNoOverride) {
25227 return Func { Func::MethodOrMissing2 { &func_info(*m_data, *func) } };
25229 // Otherwise be pessimistic.
25230 return Func { Func::MethodName { cinfo->name, name } };
25233 // Private method handling: Private methods have special lookup
25234 // rules. If we're in the context of a particular class, and that
25235 // class defines a private method, an instance of the class will
25236 // always call that private method (even if overridden) in that
25237 // context.
25238 assertx(cinfo->cls);
25239 auto const& ctx = current_context(*m_data);
25240 if (ctx.cls == cinfo->cls) {
25241 // The context matches the current class. If we've looked up a
25242 // private method (defined on this class), then that's what
25243 // we'll call.
25244 if ((meth->attrs & AttrPrivate) && meth->topLevel()) {
25245 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25247 } else if ((meth->attrs & AttrPrivate) || meth->hasPrivateAncestor()) {
25248 // Otherwise the context doesn't match the current class. If the
25249 // looked up method is private, or has a private ancestor,
25250 // there's a chance we'll call that method (or
25251 // ancestor). Otherwise there's no private method in the
25252 // inheritance tree we'll call.
25253 auto conservative = false;
25254 auto const ancestor = [&] () -> const php::Func* {
25255 if (!ctx.cls) return nullptr;
25256 if (!m_data->deps->add(AnalysisDeps::Class { ctx.cls->name })) {
25257 conservative = true;
25258 return nullptr;
25260 // Look up the ClassInfo corresponding to the context.
25261 auto const ctxCInfo = ctx.cls->cinfo;
25262 if (!ctxCInfo) return nullptr;
25263 // Is this context a parent of our class?
25264 if (!cinfo->classGraph.isChildOf(ctxCInfo->classGraph)) {
25265 return nullptr;
25267 // It is. See if it defines a private method.
25268 auto const ctxMeth = folly::get_ptr(ctxCInfo->methods, name);
25269 if (!ctxMeth) return nullptr;
25270 // If it defines a private method, use it.
25271 if ((ctxMeth->attrs & AttrPrivate) && ctxMeth->topLevel()) {
25272 if (!m_data->deps->add(ctxMeth->meth())) {
25273 conservative = true;
25274 return nullptr;
25276 auto const ctxFunc = func_from_meth_ref(*m_data, ctxMeth->meth());
25277 if (!ctxFunc) conservative = true;
25278 return ctxFunc;
25280 // Otherwise do normal lookup.
25281 return nullptr;
25282 }();
25283 if (ancestor) {
25284 return Func { Func::Method2 { &func_info(*m_data, *ancestor) } };
25285 } else if (conservative) {
25286 return Func { Func::MethodName { cinfo->name, name } };
25290 // If we're only including regular subclasses, and this class
25291 // itself isn't regular, the result may not necessarily include
25292 // func.
25293 if (!includeNonRegular && !is_regular_class(*cinfo->cls)) {
25294 // We're not including this base class. If we're exactly this
25295 // class, there's no method at all. It will always be missing.
25296 if (isExact) {
25297 return Func { Func::MissingMethod { cinfo->name, name } };
25299 if (meth->noOverrideRegular()) {
25300 // The method isn't overridden in a subclass, but we can't use
25301 // the base class either. This leaves two cases. Either the
25302 // method isn't overridden because there are no regular
25303 // subclasses (in which case there's no resolution at all), or
25304 // because there's regular subclasses, but they use the same
25305 // method (in which case the result is just func).
25306 if (!cinfo->classGraph.mightHaveRegularSubclass()) {
25307 return Func { Func::MissingMethod { cinfo->name, name } };
25309 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25311 } else if (isExact ||
25312 meth->attrs & AttrNoOverride ||
25313 (!includeNonRegular && meth->noOverrideRegular())) {
25314 // Either we want all classes, or the base class is regular. If
25315 // the method isn't overridden we know it must be just func (the
25316 // override bits include it being missing in a subclass, so we
25317 // know it cannot be missing either).
25318 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25321 // Be pessimistic for the rest of cases
25322 return general(cinfo->name, includeNonRegular);
25325 auto const isClass = thisType.subtypeOf(BCls);
25326 if (name == s_construct.get()) {
25327 if (isClass) {
25328 return Func { Func::MethodName { nullptr, s_construct.get() } };
25330 return resolve_ctor(thisType);
25333 if (isClass) {
25334 if (!is_specialized_cls(thisType)) return general(nullptr, true);
25335 } else if (!is_specialized_obj(thisType)) {
25336 return general(nullptr, false);
25339 auto const& dcls = isClass ? dcls_of(thisType) : dobj_of(thisType);
25340 return rfunc_from_dcls(
25341 dcls,
25342 name,
25343 process,
25344 [&] (bool i) { return general(dcls.smallestCls().name(), i); }
25348 res::Func AnalysisIndex::resolve_ctor(const Type& obj) const {
25349 assertx(obj.subtypeOf(BObj));
25351 using Func = res::Func;
25353 if (m_data->mode == Mode::Constants) {
25354 return Func { Func::MethodName { nullptr, s_construct.get() } };
25357 // Can't say anything useful if we don't know the object type.
25358 if (!is_specialized_obj(obj)) {
25359 return Func { Func::MethodName { nullptr, s_construct.get() } };
25362 auto const& dcls = dobj_of(obj);
25363 return rfunc_from_dcls(
25364 dcls,
25365 s_construct.get(),
25366 [&] (ClassInfo2* cinfo, bool isExact, bool includeNonRegular) {
25367 // We're dealing with an object here, which never uses
25368 // non-regular classes.
25369 assertx(!includeNonRegular);
25371 // See if this class has a ctor.
25372 auto const meth = folly::get_ptr(cinfo->methods, s_construct.get());
25373 if (!meth) {
25374 // There's no ctor on this class. This doesn't mean the ctor
25375 // won't exist at runtime, it might get the default ctor, so
25376 // we have to be conservative.
25377 return Func { Func::MethodName { cinfo->name, s_construct.get() } };
25380 if (!m_data->deps->add(meth->meth())) {
25381 return Func {
25382 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
25386 // We have a ctor, but it might be overridden in a subclass.
25387 assertx(!(meth->attrs & AttrStatic));
25388 auto const func = func_from_meth_ref(*m_data, meth->meth());
25389 if (!func) {
25390 // Relevant function doesn't exist on the AnalysisIndex. Be
25391 // conservative.
25392 return Func { Func::MethodName { cinfo->name, s_construct.get() } };
25394 assertx(!(func->attrs & AttrStatic));
25396 // If this class is known exactly, or we know nothing overrides
25397 // this ctor, we know this ctor is precisely it.
25398 if (isExact || meth->noOverrideRegular()) {
25399 // If this class isn't regular, and doesn't have any regular
25400 // subclasses (or if it's exact), this resolution will always
25401 // fail.
25402 if (!is_regular_class(*cinfo->cls) &&
25403 (isExact || !cinfo->classGraph.mightHaveRegularSubclass())) {
25404 return Func {
25405 Func::MissingMethod { cinfo->name, s_construct.get() }
25408 return Func { Func::Method2 { &func_info(*m_data, *func) } };
25411 // Be pessimistic for the rest of cases
25412 return Func {
25413 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
25416 [&] (bool includeNonRegular) {
25417 assertx(!includeNonRegular);
25418 return Func {
25419 Func::MethodName { dcls.smallestCls().name(), s_construct.get() }
25425 std::pair<const php::TypeAlias*, bool>
25426 AnalysisIndex::lookup_type_alias(SString name) const {
25427 if (!m_data->deps->add(AnalysisDeps::Class { name })) {
25428 return std::make_pair(nullptr, true);
25430 if (m_data->classes.count(name)) return std::make_pair(nullptr, false);
25431 if (auto const ta = folly::get_ptr(m_data->typeAliases, name)) {
25432 return std::make_pair(ta->first, true);
25434 return std::make_pair(nullptr, !m_data->badClasses.count(name));
25437 Index::ClassOrTypeAlias
25438 AnalysisIndex::lookup_class_or_type_alias(SString n) const {
25439 n = normalizeNS(n);
25440 if (!m_data->deps->add(AnalysisDeps::Class { n })) {
25441 return Index::ClassOrTypeAlias{nullptr, nullptr, true};
25443 if (auto const cls = folly::get_default(m_data->classes, n)) {
25444 return Index::ClassOrTypeAlias{cls, nullptr, true};
25446 if (auto const ta = folly::get_ptr(m_data->typeAliases, n)) {
25447 return Index::ClassOrTypeAlias{nullptr, ta->first, true};
25449 return Index::ClassOrTypeAlias{
25450 nullptr,
25451 nullptr,
25452 !m_data->badClasses.count(n)
25456 PropMergeResult AnalysisIndex::merge_static_type(
25457 PublicSPropMutations& publicMutations,
25458 PropertiesInfo& privateProps,
25459 const Type& cls,
25460 const Type& name,
25461 const Type& val,
25462 bool checkUB,
25463 bool ignoreConst,
25464 bool mustBeReadOnly) const {
25465 // Not yet implemented
25466 return PropMergeResult{ TInitCell, TriBool::Maybe };
25469 void AnalysisIndex::refine_constants(const FuncAnalysisResult& fa) {
25470 auto const& func = *fa.ctx.func;
25471 if (func.cls) return;
25473 auto const name = Constant::nameFromFuncName(func.name);
25474 if (!name) return;
25476 auto const cnsPtr = folly::get_ptr(m_data->constants, name);
25477 always_assert_flog(
25478 cnsPtr,
25479 "Attempting to refine constant {} "
25480 "which we don't have meta-data for",
25481 name
25483 auto const cns = cnsPtr->first;
25484 auto const val = tv(fa.inferredReturn);
25485 if (!val) {
25486 always_assert_flog(
25487 type(cns->val) == KindOfUninit,
25488 "Constant value invariant violated in {}.\n"
25489 " Value went from {} to {}",
25490 name,
25491 show(from_cell(cns->val)),
25492 show(fa.inferredReturn)
25494 return;
25497 if (type(cns->val) != KindOfUninit) {
25498 always_assert_flog(
25499 from_cell(cns->val) == fa.inferredReturn,
25500 "Constant value invariant violated in {}.\n"
25501 " Value went from {} to {}",
25502 name,
25503 show(from_cell(cns->val)),
25504 show(fa.inferredReturn)
25506 } else {
25507 always_assert_flog(
25508 !m_data->frozen,
25509 "Attempting to refine constant {} to {} when index is frozen",
25510 name,
25511 show(fa.inferredReturn)
25513 cns->val = *val;
25514 m_data->deps->update(*cns);
25518 void AnalysisIndex::refine_class_constants(const FuncAnalysisResult& fa) {
25519 auto const resolved = fa.resolvedInitializers.left();
25520 if (!resolved || resolved->empty()) return;
25522 assertx(fa.ctx.func->cls);
25523 auto& constants = fa.ctx.func->cls->constants;
25525 for (auto const& c : *resolved) {
25526 assertx(c.first < constants.size());
25527 auto& cns = constants[c.first];
25528 assertx(cns.kind == ConstModifiers::Kind::Value);
25529 always_assert(cns.val.has_value());
25530 always_assert(type(*cns.val) == KindOfUninit);
25532 auto cinfo = fa.ctx.func->cls->cinfo;
25533 if (auto const val = tv(c.second.type)) {
25534 assertx(type(*val) != KindOfUninit);
25535 always_assert_flog(
25536 !m_data->frozen,
25537 "Attempting to refine class constant {}::{} to {} "
25538 "when index is frozen",
25539 fa.ctx.func->cls->name,
25540 cns.name,
25541 show(c.second.type)
25543 cns.val = *val;
25544 if (cinfo) cinfo->clsConstantInfo.erase(cns.name);
25545 m_data->deps->update(
25546 cns,
25547 ConstIndex { fa.ctx.func->cls->name, c.first }
25549 } else if (cinfo) {
25550 auto old = folly::get_default(
25551 cinfo->clsConstantInfo,
25552 cns.name,
25553 ClsConstInfo{ TInitCell, 0 }
25555 old.type = unserialize_type(std::move(old.type));
25557 if (c.second.type.strictlyMoreRefined(old.type)) {
25558 always_assert(c.second.refinements > old.refinements);
25559 always_assert_flog(
25560 !m_data->frozen,
25561 "Attempting to refine class constant {}::{} to {} "
25562 "when index is frozen",
25563 cinfo->name,
25564 cns.name,
25565 show(c.second.type)
25567 cinfo->clsConstantInfo.insert_or_assign(cns.name, c.second);
25568 m_data->deps->update(cns, ConstIndex { cinfo->name, c.first });
25569 } else {
25570 always_assert_flog(
25571 c.second.type.moreRefined(old.type),
25572 "Class constant type invariant violated for {}::{}\n"
25573 " {} is not at least as refined as {}\n",
25574 fa.ctx.func->cls->name,
25575 cns.name,
25576 show(c.second.type),
25577 show(old.type)
25584 void AnalysisIndex::refine_return_info(const FuncAnalysisResult& fa) {
25585 auto const& func = *fa.ctx.func;
25586 auto& finfo = func_info(*m_data, func);
25588 auto const error_loc = [&] {
25589 return folly::sformat("{} {}", func.unit, func_fullname(func));
25592 auto changes = AnalysisDeps::Type::None;
25594 if (finfo.retParam == NoLocalId) {
25595 // This is just a heuristic; it doesn't mean that the value passed
25596 // in was returned, but that the value of the parameter at the
25597 // point of the RetC was returned. We use it to make (heuristic)
25598 // decisions about whether to do inline interps, so we only allow
25599 // it to change once. (otherwise later passes might not do the
25600 // inline interp, and get worse results, which breaks
25601 // monotonicity).
25602 if (fa.retParam != NoLocalId) {
25603 finfo.retParam = fa.retParam;
25604 changes |= AnalysisDeps::Type::RetParam;
25606 } else {
25607 always_assert_flog(
25608 finfo.retParam == fa.retParam,
25609 "Index return param invariant violated in {}.\n"
25610 " Went from {} to {}\n",
25611 finfo.retParam,
25612 fa.retParam,
25613 error_loc()
25617 auto const unusedParams = ~fa.usedParams;
25618 if (finfo.unusedParams != unusedParams) {
25619 always_assert_flog(
25620 (finfo.unusedParams | unusedParams) == unusedParams,
25621 "Index unused params decreased in {}.\n",
25622 error_loc()
25624 finfo.unusedParams = unusedParams;
25625 changes |= AnalysisDeps::Type::UnusedParams;
25628 auto const oldReturnTy = unserialize_type(finfo.returnTy);
25629 if (fa.inferredReturn.strictlyMoreRefined(oldReturnTy)) {
25630 if (finfo.returnRefinements < options.returnTypeRefineLimit) {
25631 finfo.returnTy = fa.inferredReturn;
25632 finfo.returnRefinements += fa.localReturnRefinements + 1;
25633 if (finfo.returnRefinements > options.returnTypeRefineLimit) {
25634 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25636 changes |= AnalysisDeps::Type::RetType;
25637 if (is_scalar(finfo.returnTy)) {
25638 changes |= AnalysisDeps::Type::ScalarRetType;
25640 } else {
25641 FTRACE(1, "maxed out return type refinements at {}\n", error_loc());
25643 } else {
25644 always_assert_flog(
25645 fa.inferredReturn.moreRefined(oldReturnTy),
25646 "Index return type invariant violated in {}.\n"
25647 " {} is not at least as refined as {}\n",
25648 error_loc(),
25649 show(fa.inferredReturn),
25650 show(oldReturnTy)
25654 always_assert_flog(
25655 !finfo.effectFree || fa.effectFree,
25656 "Index effect-free invariant violated in {}.\n"
25657 " Went from true to false\n",
25658 error_loc()
25661 if (finfo.effectFree != fa.effectFree) {
25662 finfo.effectFree = fa.effectFree;
25663 changes |= AnalysisDeps::Type::RetType;
25666 always_assert_flog(
25667 !m_data->frozen || changes == AnalysisDeps::Type::None,
25668 "Attempting to refine return info for {} ({}) "
25669 "when index is frozen",
25670 error_loc(),
25671 show(changes)
25674 if (changes & AnalysisDeps::Type::RetType) {
25675 if (auto const name = Constant::nameFromFuncName(func.name)) {
25676 auto const cns = folly::get_ptr(m_data->constants, name);
25677 always_assert_flog(
25678 cns,
25679 "Attempting to update constant {} type, but constant is not present!",
25680 name
25682 m_data->deps->update(*cns->first);
25685 m_data->deps->update(func, changes);
25688 void AnalysisIndex::update_prop_initial_values(const FuncAnalysisResult& fa) {
25689 auto const resolved = fa.resolvedInitializers.right();
25690 if (!resolved || resolved->empty()) return;
25692 assertx(fa.ctx.cls);
25693 auto& props = const_cast<php::Class*>(fa.ctx.cls)->properties;
25695 auto changed = false;
25696 for (auto const& [idx, info] : *resolved) {
25697 assertx(idx < props.size());
25698 auto& prop = props[idx];
25700 if (info.satisfies) {
25701 if (!(prop.attrs & AttrInitialSatisfiesTC)) {
25702 always_assert_flog(
25703 !m_data->frozen,
25704 "Attempting to update AttrInitialSatisfiesTC for {}::{} "
25705 "when index is frozen",
25706 fa.ctx.cls->name,
25707 prop.name
25709 attribute_setter(prop.attrs, true, AttrInitialSatisfiesTC);
25710 changed = true;
25712 } else {
25713 always_assert_flog(
25714 !(prop.attrs & AttrInitialSatisfiesTC),
25715 "AttrInitialSatisfiesTC invariant violated for {}::{}\n"
25716 " Went from true to false",
25717 fa.ctx.cls->name, prop.name
25721 always_assert_flog(
25722 IMPLIES(!(prop.attrs & AttrDeepInit), !info.deepInit),
25723 "AttrDeepInit invariant violated for {}::{}\n"
25724 " Went from false to true",
25725 fa.ctx.cls->name, prop.name
25727 if (bool(prop.attrs & AttrDeepInit) != info.deepInit) {
25728 always_assert_flog(
25729 !m_data->frozen,
25730 "Attempting to update AttrDeepInit for {}::{} "
25731 "when index is frozen",
25732 fa.ctx.cls->name,
25733 prop.name
25735 attribute_setter(prop.attrs, info.deepInit, AttrDeepInit);
25738 if (type(info.val) != KindOfUninit) {
25739 always_assert_flog(
25740 !m_data->frozen,
25741 "Attempting to update property initial value for {}::{} "
25742 "to {} when index is frozen",
25743 fa.ctx.cls->name,
25744 prop.name,
25745 show(from_cell(info.val))
25747 always_assert_flog(
25748 type(prop.val) == KindOfUninit ||
25749 from_cell(prop.val) == from_cell(info.val),
25750 "Property initial value invariant violated for {}::{}\n"
25751 " Value went from {} to {}",
25752 fa.ctx.cls->name, prop.name,
25753 show(from_cell(prop.val)), show(from_cell(info.val))
25755 prop.val = info.val;
25756 } else {
25757 always_assert_flog(
25758 type(prop.val) == KindOfUninit,
25759 "Property initial value invariant violated for {}::{}\n"
25760 " Value went from {} to not set",
25761 fa.ctx.cls->name, prop.name,
25762 show(from_cell(prop.val))
25766 if (!changed) return;
25768 auto const cinfo = fa.ctx.cls->cinfo;
25769 if (!cinfo) return;
25771 assertx(cinfo->hasBadInitialPropValues);
25772 auto const noBad = std::all_of(
25773 begin(props), end(props),
25774 [] (const php::Prop& prop) {
25775 return bool(prop.attrs & AttrInitialSatisfiesTC);
25779 if (noBad) {
25780 cinfo->hasBadInitialPropValues = false;
25784 void AnalysisIndex::update_type_consts(const ClassAnalysis& analysis) {
25785 if (analysis.resolvedTypeConsts.empty()) return;
25787 always_assert_flog(
25788 !m_data->frozen,
25789 "Attempting to update type constants for {} when index is frozen",
25790 analysis.ctx.cls->name
25793 auto const cls = const_cast<php::Class*>(analysis.ctx.cls);
25794 auto const cinfo = cls->cinfo;
25795 if (!cinfo) return;
25797 for (auto const& update : analysis.resolvedTypeConsts) {
25798 auto const srcCls = folly::get_default(m_data->classes, update.from.cls);
25799 assertx(srcCls);
25800 assertx(update.from.idx < srcCls->constants.size());
25802 auto& newCns = [&] () -> php::Const& {
25803 auto& srcCns = srcCls->constants[update.from.idx];
25804 if (srcCls == cls) {
25805 assertx(!srcCns.resolvedTypeStructure);
25806 return srcCns;
25808 cinfo->clsConstants[srcCns.name] =
25809 ClassInfo2::ConstIndexAndKind {
25810 ConstIndex { cinfo->name, (ConstIndex::Idx)cls->constants.size() },
25811 srcCns.kind
25813 cls->constants.emplace_back(srcCns);
25814 return cls->constants.back();
25815 }();
25817 newCns.resolvedTypeStructure = update.resolved;
25818 newCns.contextInsensitive = update.contextInsensitive;
25819 newCns.resolvedLocally = true;
25823 void AnalysisIndex::update_bytecode(FuncAnalysisResult& fa) {
25824 auto func = php::WideFunc::mut(const_cast<php::Func*>(fa.ctx.func));
25825 auto const update = HHBBC::update_bytecode(func, std::move(fa.blockUpdates));
25826 if (update == UpdateBCResult::None) return;
25828 always_assert_flog(
25829 !m_data->frozen,
25830 "Attempting to update bytecode for {} when index is frozen",
25831 func_fullname(*fa.ctx.func)
25834 if (update == UpdateBCResult::ChangedAnalyze ||
25835 fa.ctx.func->name == s_86cinit.get()) {
25836 ITRACE(2, "Updated bytecode for {} in a way that requires re-analysis\n",
25837 func_fullname(*fa.ctx.func));
25838 m_data->worklist.schedule(fc_from_context(fa.ctx, *m_data));
25841 m_data->deps->update(*fa.ctx.func, AnalysisDeps::Type::Bytecode);
25844 void AnalysisIndex::update_type_aliases(const UnitAnalysis& ua) {
25845 assertx(ua.ctx.unit);
25846 if (ua.resolvedTypeAliases.empty()) return;
25848 always_assert_flog(
25849 !m_data->frozen,
25850 "Attempting to update type-aliases for unit {} when index is frozen\n",
25851 ua.ctx.unit
25854 auto const& unit = lookup_unit(ua.ctx.unit);
25855 for (auto const& update : ua.resolvedTypeAliases) {
25856 assertx(update.idx < unit.typeAliases.size());
25857 auto& ta = *unit.typeAliases[update.idx];
25858 ta.resolvedTypeStructure = update.resolved;
25859 ta.resolvedLocally = true;
25863 // Finish using the AnalysisIndex and calculate the output to be
25864 // returned back from the job.
25865 AnalysisIndex::Output AnalysisIndex::finish() {
25866 Variadic<std::unique_ptr<php::Class>> classes;
25867 Variadic<std::unique_ptr<php::Func>> funcs;
25868 Variadic<std::unique_ptr<php::Unit>> units;
25869 Variadic<std::unique_ptr<php::ClassBytecode>> clsBC;
25870 Variadic<std::unique_ptr<php::FuncBytecode>> funcBC;
25871 Variadic<AnalysisIndexCInfo> cinfos;
25872 Variadic<AnalysisIndexFInfo> finfos;
25873 Variadic<AnalysisIndexMInfo> minfos;
25874 AnalysisOutput::Meta meta;
25876 assertx(m_data->frozen);
25878 // Remove any 86cinits that are now unneeded.
25879 meta.removedFuncs = strip_unneeded_constant_inits(*m_data);
25881 for (auto const name : m_data->outClassNames) {
25882 auto const& cls = m_data->allClasses.at(name);
25883 mark_fixed_class_constants(*cls, *m_data);
25884 meta.cnsBases[cls->name] = record_cns_bases(*cls, *m_data);
25885 for (auto const& clo : cls->closures) {
25886 mark_fixed_class_constants(*clo, *m_data);
25890 for (auto const name : m_data->outUnitNames) {
25891 auto const& unit = m_data->allUnits.at(name);
25892 mark_fixed_unit(*unit, m_data->deps->getChanges());
25895 auto const moveNewAuxs = [&] (AuxClassGraphs& auxs) {
25896 auxs.noChildren = std::move(auxs.newNoChildren);
25897 auxs.withChildren = std::move(auxs.newWithChildren);
25900 TSStringSet outClasses;
25902 classes.vals.reserve(m_data->outClassNames.size());
25903 clsBC.vals.reserve(m_data->outClassNames.size());
25904 cinfos.vals.reserve(m_data->outClassNames.size());
25905 meta.classDeps.reserve(m_data->outClassNames.size());
25906 for (auto const name : m_data->outClassNames) {
25907 auto& cls = m_data->allClasses.at(name);
25909 outClasses.emplace(name);
25911 meta.classDeps.emplace_back(m_data->deps->take(cls.get()));
25912 always_assert(IMPLIES(is_closure(*cls), meta.classDeps.back().empty()));
25913 for (auto const& clo : cls->closures) {
25914 assertx(m_data->deps->take(clo.get()).empty());
25915 outClasses.emplace(clo->name);
25918 clsBC.vals.emplace_back(
25919 std::make_unique<php::ClassBytecode>(cls->name)
25921 auto& bc = *clsBC.vals.back();
25922 for (auto& meth : cls->methods) {
25923 bc.methodBCs.emplace_back(meth->name, std::move(meth->rawBlocks));
25925 for (auto& clo : cls->closures) {
25926 assertx(clo->methods.size() == 1);
25927 auto& meth = clo->methods[0];
25928 bc.methodBCs.emplace_back(meth->name, std::move(meth->rawBlocks));
25931 if (auto cinfo = folly::get_ptr(m_data->allCInfos, name)) {
25932 moveNewAuxs(cinfo->get()->auxClassGraphs);
25934 AnalysisIndexCInfo acinfo;
25935 acinfo.ptr = decltype(acinfo.ptr){cinfo->release()};
25936 cinfos.vals.emplace_back(std::move(acinfo));
25937 } else if (auto minfo = folly::get_ptr(m_data->allMInfos, name)) {
25938 AnalysisIndexMInfo aminfo;
25939 aminfo.ptr = decltype(aminfo.ptr){minfo->release()};
25940 minfos.vals.emplace_back(std::move(aminfo));
25943 classes.vals.emplace_back(std::move(cls));
25946 FSStringSet outFuncs;
25947 outFuncs.reserve(m_data->outFuncNames.size());
25949 funcs.vals.reserve(m_data->outFuncNames.size());
25950 funcBC.vals.reserve(m_data->outFuncNames.size());
25951 finfos.vals.reserve(m_data->outFuncNames.size());
25952 meta.funcDeps.reserve(m_data->outFuncNames.size());
25953 for (auto const name : m_data->outFuncNames) {
25954 assertx(!meta.removedFuncs.count(name));
25956 auto& func = m_data->allFuncs.at(name);
25957 auto& finfo = m_data->allFInfos.at(name);
25959 outFuncs.emplace(name);
25960 meta.funcDeps.emplace_back(m_data->deps->take(func.get()));
25962 funcBC.vals.emplace_back(
25963 std::make_unique<php::FuncBytecode>(name, std::move(func->rawBlocks))
25965 funcs.vals.emplace_back(std::move(func));
25967 if (finfo->auxClassGraphs) moveNewAuxs(*finfo->auxClassGraphs);
25969 AnalysisIndexFInfo afinfo;
25970 afinfo.ptr = decltype(afinfo.ptr){finfo.release()};
25971 finfos.vals.emplace_back(std::move(afinfo));
25974 hphp_fast_set<php::Unit*> outUnits;
25975 outUnits.reserve(m_data->outUnitNames.size());
25977 units.vals.reserve(m_data->outUnitNames.size());
25978 meta.unitDeps.reserve(m_data->outUnitNames.size());
25979 for (auto const name : m_data->outUnitNames) {
25980 auto& unit = m_data->allUnits.at(name);
25981 outUnits.emplace(unit.get());
25982 meta.unitDeps.emplace_back(m_data->deps->take(unit.get()));
25983 units.vals.emplace_back(std::move(unit));
25986 SStringSet outConstants;
25987 for (auto const& [_, p] : m_data->constants) {
25988 if (!outUnits.count(p.second)) continue;
25989 outConstants.emplace(p.first->name);
25992 const SStringSet outUnitNames{
25993 begin(m_data->outUnitNames),
25994 end(m_data->outUnitNames)
25997 meta.changed = std::move(m_data->deps->getChanges());
25998 meta.changed.filter(outClasses, outFuncs, outUnitNames, outConstants);
26000 return std::make_tuple(
26001 std::move(classes),
26002 std::move(funcs),
26003 std::move(units),
26004 std::move(clsBC),
26005 std::move(funcBC),
26006 std::move(cinfos),
26007 std::move(finfos),
26008 std::move(minfos),
26009 std::move(meta)
26013 //////////////////////////////////////////////////////////////////////
26015 PublicSPropMutations::PublicSPropMutations(bool enabled) : m_enabled{enabled} {}
26017 PublicSPropMutations::Data& PublicSPropMutations::get() {
26018 if (!m_data) m_data = std::make_unique<Data>();
26019 return *m_data;
26022 void PublicSPropMutations::mergeKnown(const ClassInfo* ci,
26023 const php::Prop& prop,
26024 const Type& val) {
26025 if (!m_enabled) return;
26026 ITRACE(4, "PublicSPropMutations::mergeKnown: {} {} {}\n",
26027 ci->cls->name->data(), prop.name, show(val));
26029 auto const res = get().m_known.emplace(
26030 KnownKey { const_cast<ClassInfo*>(ci), prop.name }, val
26032 if (!res.second) res.first->second |= val;
26035 void PublicSPropMutations::mergeUnknownClass(SString prop, const Type& val) {
26036 if (!m_enabled) return;
26037 ITRACE(4, "PublicSPropMutations::mergeUnknownClass: {} {}\n",
26038 prop, show(val));
26040 auto const res = get().m_unknown.emplace(prop, val);
26041 if (!res.second) res.first->second |= val;
26044 void PublicSPropMutations::mergeUnknown(Context ctx) {
26045 if (!m_enabled) return;
26046 ITRACE(4, "PublicSPropMutations::mergeUnknown\n");
26049 * We have a case here where we know neither the class nor the static
26050 * property name. This means we have to pessimize public static property
26051 * types for the entire program.
26053 * We could limit it to pessimizing them by merging the `val' type, but
26054 * instead we just throw everything away---this optimization is not
26055 * expected to be particularly useful on programs that contain any
26056 * instances of this situation.
26058 std::fprintf(
26059 stderr,
26060 "NOTE: had to mark everything unknown for public static "
26061 "property types due to dynamic code. -fanalyze-public-statics "
26062 "will not help for this program.\n"
26063 "NOTE: The offending code occured in this context: %s\n",
26064 show(ctx).c_str()
26066 get().m_nothing_known = true;
26069 //////////////////////////////////////////////////////////////////////
26071 #define UNIMPLEMENTED always_assert_flog(false, "{} not implemented for AnalysisIndex", __func__)
26073 bool AnalysisIndexAdaptor::frozen() const {
26074 return index.frozen();
26077 void AnalysisIndexAdaptor::push_context(const Context& ctx) const {
26078 index.push_context(ctx);
26081 void AnalysisIndexAdaptor::pop_context() const {
26082 index.pop_context();
26085 bool AnalysisIndexAdaptor::set_in_type_cns(bool b) const {
26086 return index.set_in_type_cns(b);
26089 const php::Unit* AnalysisIndexAdaptor::lookup_func_unit(const php::Func& func) const {
26090 return &index.lookup_func_unit(func);
26092 const php::Unit* AnalysisIndexAdaptor::lookup_class_unit(const php::Class& cls) const {
26093 return &index.lookup_class_unit(cls);
26095 const php::Class* AnalysisIndexAdaptor::lookup_const_class(const php::Const& cns) const {
26096 return index.lookup_const_class(cns);
26098 const php::Class* AnalysisIndexAdaptor::lookup_closure_context(const php::Class& cls) const {
26099 return &index.lookup_closure_context(cls);
26101 const php::Class* AnalysisIndexAdaptor::lookup_class(SString c) const {
26102 return index.lookup_class(c);
26105 const CompactVector<const php::Class*>*
26106 AnalysisIndexAdaptor::lookup_closures(const php::Class*) const {
26107 UNIMPLEMENTED;
26110 const hphp_fast_set<const php::Func*>*
26111 AnalysisIndexAdaptor::lookup_extra_methods(const php::Class*) const {
26112 UNIMPLEMENTED;
26115 Optional<res::Class> AnalysisIndexAdaptor::resolve_class(SString n) const {
26116 return index.resolve_class(n);
26118 Optional<res::Class>
26119 AnalysisIndexAdaptor::resolve_class(const php::Class& c) const {
26120 return index.resolve_class(c);
26122 std::pair<const php::TypeAlias*, bool>
26123 AnalysisIndexAdaptor::lookup_type_alias(SString n) const {
26124 return index.lookup_type_alias(n);
26126 Index::ClassOrTypeAlias
26127 AnalysisIndexAdaptor::lookup_class_or_type_alias(SString n) const {
26128 return index.lookup_class_or_type_alias(n);
26131 res::Func AnalysisIndexAdaptor::resolve_func_or_method(const php::Func& f) const {
26132 return index.resolve_func_or_method(f);
26134 res::Func AnalysisIndexAdaptor::resolve_func(SString f) const {
26135 return index.resolve_func(f);
26137 res::Func AnalysisIndexAdaptor::resolve_method(Context,
26138 const Type& t,
26139 SString n) const {
26140 return index.resolve_method(t, n);
26142 res::Func AnalysisIndexAdaptor::resolve_ctor(const Type& obj) const {
26143 return index.resolve_ctor(obj);
26146 std::vector<std::pair<SString, ClsConstInfo>>
26147 AnalysisIndexAdaptor::lookup_class_constants(const php::Class& cls) const {
26148 return index.lookup_class_constants(cls);
26151 ClsConstLookupResult
26152 AnalysisIndexAdaptor::lookup_class_constant(Context,
26153 const Type& cls,
26154 const Type& name) const {
26155 return index.lookup_class_constant(cls, name);
26158 ClsTypeConstLookupResult
26159 AnalysisIndexAdaptor::lookup_class_type_constant(
26160 const Type& cls,
26161 const Type& name,
26162 const Index::ClsTypeConstLookupResolver& resolver
26163 ) const {
26164 return index.lookup_class_type_constant(cls, name, resolver);
26167 ClsTypeConstLookupResult
26168 AnalysisIndexAdaptor::lookup_class_type_constant(const php::Class& ctx,
26169 SString n,
26170 ConstIndex idx) const {
26171 return index.lookup_class_type_constant(ctx, n, idx);
26174 std::vector<std::pair<SString, ConstIndex>>
26175 AnalysisIndexAdaptor::lookup_flattened_class_type_constants(
26176 const php::Class& cls
26177 ) const {
26178 return index.lookup_flattened_class_type_constants(cls);
26181 Type AnalysisIndexAdaptor::lookup_constant(Context, SString n) const {
26182 return index.lookup_constant(n);
26184 bool AnalysisIndexAdaptor::func_depends_on_arg(const php::Func* f,
26185 size_t arg) const {
26186 return index.func_depends_on_arg(*f, arg);
26188 Index::ReturnType
26189 AnalysisIndexAdaptor::lookup_foldable_return_type(Context,
26190 const CallContext& callee) const {
26191 return index.lookup_foldable_return_type(callee);
26193 Index::ReturnType AnalysisIndexAdaptor::lookup_return_type(Context,
26194 MethodsInfo* methods,
26195 res::Func func,
26196 Dep) const {
26197 return index.lookup_return_type(methods, func);
26199 Index::ReturnType AnalysisIndexAdaptor::lookup_return_type(Context,
26200 MethodsInfo* methods,
26201 const CompactVector<Type>& args,
26202 const Type& context,
26203 res::Func func,
26204 Dep) const {
26205 return index.lookup_return_type(methods, args, context, func);
26208 std::pair<Index::ReturnType, size_t>
26209 AnalysisIndexAdaptor::lookup_return_type_raw(const php::Func* f) const {
26210 return index.lookup_return_type_raw(*f);
26212 CompactVector<Type>
26213 AnalysisIndexAdaptor::lookup_closure_use_vars(const php::Func*, bool) const {
26214 UNIMPLEMENTED;
26217 PropState AnalysisIndexAdaptor::lookup_private_props(const php::Class* cls,
26218 bool) const {
26219 return index.lookup_private_props(*cls);
26221 PropState AnalysisIndexAdaptor::lookup_private_statics(const php::Class* cls,
26222 bool) const {
26223 return index.lookup_private_statics(*cls);
26226 PropLookupResult AnalysisIndexAdaptor::lookup_static(Context,
26227 const PropertiesInfo&,
26228 const Type&,
26229 const Type& name) const {
26230 // Not implemented yet, be conservative.
26232 auto const sname = [&] () -> SString {
26233 // Treat non-string names conservatively, but the caller should be
26234 // checking this.
26235 if (!is_specialized_string(name)) return nullptr;
26236 return sval_of(name);
26237 }();
26239 return PropLookupResult{
26240 TInitCell,
26241 sname,
26242 TriBool::Maybe,
26243 TriBool::Maybe,
26244 TriBool::Maybe,
26245 TriBool::Maybe,
26246 TriBool::Maybe,
26247 true
26251 Type AnalysisIndexAdaptor::lookup_public_prop(const Type&, const Type&) const {
26252 return TInitCell;
26255 PropMergeResult
26256 AnalysisIndexAdaptor::merge_static_type(Context,
26257 PublicSPropMutations& publicMutations,
26258 PropertiesInfo& privateProps,
26259 const Type& cls,
26260 const Type& name,
26261 const Type& val,
26262 bool checkUB,
26263 bool ignoreConst,
26264 bool mustBeReadOnly) const {
26265 return index.merge_static_type(
26266 publicMutations,
26267 privateProps,
26268 cls,
26269 name,
26270 val,
26271 checkUB,
26272 ignoreConst,
26273 mustBeReadOnly
26277 bool AnalysisIndexAdaptor::using_class_dependencies() const {
26278 return false;
26281 #undef UNIMPLEMENTED
26283 //////////////////////////////////////////////////////////////////////
26285 template <typename T>
26286 void AnalysisIndexParam<T>::Deleter::operator()(T* t) const {
26287 delete t;
26290 template <typename T>
26291 void AnalysisIndexParam<T>::serde(BlobEncoder& sd) const {
26292 sd(ptr);
26295 template <typename T>
26296 void AnalysisIndexParam<T>::serde(BlobDecoder& sd) {
26297 sd(ptr);
26300 template struct AnalysisIndexParam<ClassInfo2>;
26301 template struct AnalysisIndexParam<FuncInfo2>;
26302 template struct AnalysisIndexParam<MethodsWithoutCInfo>;
26304 //////////////////////////////////////////////////////////////////////
26308 //////////////////////////////////////////////////////////////////////
26310 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::ClassInfo2);
26311 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncInfo2);
26312 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::FuncFamily2);
26313 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::MethodsWithoutCInfo);
26314 MAKE_UNIQUE_PTR_BLOB_SERDE_HELPER(HHBBC::BuildSubclassListJob::Split);
26316 //////////////////////////////////////////////////////////////////////