2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/bespoke/layout.h"
19 #include "hphp/runtime/base/bespoke/logging-array.h"
20 #include "hphp/runtime/base/bespoke/logging-profile.h"
21 #include "hphp/runtime/base/bespoke/monotype-dict.h"
22 #include "hphp/runtime/base/bespoke/monotype-vec.h"
23 #include "hphp/runtime/base/packed-array-defs.h"
24 #include "hphp/runtime/vm/jit/irgen.h"
25 #include "hphp/runtime/vm/jit/irgen-internal.h"
26 #include "hphp/runtime/vm/jit/mcgen-translate.h"
27 #include "hphp/runtime/vm/jit/type.h"
28 #include "hphp/runtime/vm/jit/punt.h"
29 #include "hphp/util/trace.h"
33 #include <folly/lang/Bits.h>
34 #include <folly/Optional.h>
38 namespace HPHP
{ namespace bespoke
{
40 //////////////////////////////////////////////////////////////////////////////
46 std::atomic
<size_t> s_topoIndex
;
47 std::atomic
<size_t> s_layoutTableIndex
;
48 std::array
<Layout
*, Layout::kMaxIndex
.raw
+ 1> s_layoutTable
;
49 std::atomic
<bool> s_hierarchyFinal
= false;
50 std::mutex s_layoutCreationMutex
;
52 LayoutFunctions s_emptyVtable
;
54 constexpr LayoutIndex kBespokeTopIndex
= {0};
56 bool isSingletonLayout(LayoutIndex index
) {
57 return index
== EmptyMonotypeVecLayout::Index() ||
58 index
== EmptyMonotypeDictLayout::Index();
61 using Split
= std::pair
<uint16_t, std::vector
<uint16_t>>;
63 folly::Optional
<MaskAndCompare
> computeSimpleMaskAndCompare(
64 const std::vector
<uint16_t>& liveVec
,
65 const std::vector
<uint16_t>& deadVec
) {
66 uint16_t liveAgree
= 0xffff;
67 for (auto const live
: liveVec
) {
68 auto const agree
= ~(live
^ liveVec
[0]);
72 const uint16_t base
= liveVec
[0] & liveAgree
;
73 for (auto const dead
: deadVec
) {
74 if ((dead
& liveAgree
) == base
) {
79 return MaskAndCompare
{base
, liveAgree
, 0};
82 folly::Optional
<MaskAndCompare
> computeXORMaskAndCompare(
83 const std::vector
<uint16_t>& liveVec
,
84 const std::vector
<uint16_t>& deadVec
) {
85 std::vector
<uint16_t> remainingLive(liveVec
);
86 std::vector
<uint16_t> remainingDead(deadVec
);
88 // We process each bit from most significant to least significant,
89 // determining the proper assignment for that bit in the XOR and CMP
92 // For each bit, there three relevant scenarios:
94 // 1. All live layouts agree on this bit having value x. We should
95 // XOR with x and CMP with 0.
97 // 2. Live layouts do not agree on the bit, but dead layouts agree on
98 // it having value y. We should XOR with ~y and CMP with 1.
100 // 3. Live layouts do not agree on the bit, and neither do dead
101 // layouts. We must exclude this bit in the mask.
103 // After each action, we remove all dead layouts that are guaranteed
104 // the fail our test and all live layouts that are guaranteed to pass
106 auto const recomputeLive
= [&] () -> std::pair
<uint16_t, uint16_t> {
107 uint16_t liveAgree
= 0xffff;
108 for (auto const live
: remainingLive
) {
109 auto const agree
= ~(live
^ remainingLive
[0]);
112 uint16_t deadAgree
= 0xffff;
113 for (auto const dead
: remainingDead
) {
114 auto const agree
= ~(dead
^ remainingDead
[0]);
117 return {liveAgree
, deadAgree
};
122 uint16_t andVal
= 0xffff;
123 auto [liveAgree
, deadAgree
] = recomputeLive();
124 for (int i
= 0; i
< 16; i
++) {
125 // The bit currently being examined.
126 const uint16_t mask
= (1 << (15 - i
));
128 if (remainingDead
.empty() && remainingLive
.empty()) break;
130 if (!remainingLive
.empty() && (liveAgree
& mask
)) {
131 xorVal
|= remainingLive
[0] & mask
;
132 } else if (!remainingDead
.empty() && (deadAgree
& mask
)) {
133 xorVal
|= (~remainingDead
[0]) & mask
;
135 } else if (remainingDead
.empty()) {
137 } else if (!remainingLive
.empty()) {
139 // Nothing will be filtered out as we have not constrained our mask, so
140 // reuse the agreement sets from the prior round.
144 // The set of bits that have already been fixed and will not be masked
145 // away. This is a prefix of the set of bits that will be fixed and is used
146 // for determining which live and dead layouts must pass or fail the test,
147 // regardless of the decisions made for future (less significant) bits.
148 const uint16_t cmpMask
= andVal
& ~(mask
- 1);
151 std::remove_if(remainingDead
.begin(), remainingDead
.end(),
152 [&](auto v
) { return ((v
^ xorVal
) & cmpMask
) > cmpVal
; }),
153 remainingDead
.end());
155 std::remove_if(remainingLive
.begin(), remainingLive
.end(),
156 [&](auto v
) { return ((v
^ xorVal
) & cmpMask
) < cmpVal
; }),
157 remainingLive
.end());
159 auto const [liveAgreeNew
, deadAgreeNew
] = recomputeLive();
160 liveAgree
= liveAgreeNew
;
161 deadAgree
= deadAgreeNew
;
164 if (!remainingDead
.empty() && !remainingLive
.empty()) {
168 // If it came down to the last bit, adjust the comparison accordingly.
169 if (!remainingDead
.empty()) cmpVal
--;
171 return MaskAndCompare
{xorVal
, andVal
, cmpVal
};
176 Layout::Layout(LayoutIndex index
, std::string description
, LayoutSet parents
,
177 const LayoutFunctions
* vtable
)
179 , m_description(std::move(description
))
180 , m_parents(std::move(parents
))
181 , m_vtable(vtable
? vtable
: &s_emptyVtable
)
183 std::lock_guard
<std::mutex
> lock(s_layoutCreationMutex
);
184 assertx(!s_hierarchyFinal
.load(std::memory_order_acquire
));
185 assertx(s_layoutTable
[m_index
.raw
] == nullptr);
186 s_layoutTable
[m_index
.raw
] = this;
187 m_topoIndex
= s_topoIndex
++;
188 assertx(checkInvariants());
192 * A BFS implementation used to traverse the bespoke type lattice.
194 struct Layout::BFSWalker
{
195 BFSWalker(bool upward
, LayoutIndex initIdx
)
200 template <typename C
>
201 BFSWalker(bool upward
, C
&& col
)
202 : m_workQ(col
.cbegin(), col
.cend())
207 * Return the next new node in the graph discovered by BFS, or folly::none if
208 * the BFS has terminated.
210 folly::Optional
<LayoutIndex
> next() {
211 while (!m_workQ
.empty()) {
212 auto const index
= m_workQ
.front();
214 if (m_processed
.find(index
) != m_processed
.end()) continue;
215 m_processed
.insert(index
);
216 auto const layout
= FromIndex(index
);
217 auto const nextSet
= m_upward
? layout
->m_parents
: layout
->m_children
;
218 for (auto const next
: nextSet
) {
219 m_workQ
.push_back(next
);
227 * Checks if the BFS has encountered a given node.
229 bool hasSeen(LayoutIndex index
) const {
230 return m_processed
.find(index
) != m_processed
.end();
234 * Returns the full set of nodes seen during the BFS.
236 LayoutSet
allSeen() const { return m_processed
; }
239 LayoutSet m_processed
;
240 std::deque
<LayoutIndex
> m_workQ
;
245 * Computes whether the layout is a descendant of another layout. To do so, we
246 * simply perform an upward BFS from the current layout until we encounter the
247 * other layout or the BFS terminates.
249 bool Layout::isDescendantOfDebug(const Layout
* other
) const {
250 BFSWalker
walker(true, index());
251 while (auto const idx
= walker
.next()) {
252 if (idx
== other
->index()) return true;
258 * Compute the set of ancestors by running upward DFS until termination.
260 Layout::LayoutSet
Layout::computeAncestors() const {
261 BFSWalker
walker(true, index());
262 while (walker
.next()) {}
263 return walker
.allSeen();
267 * Compute the set of descendants by running downward DFS until termination.
269 Layout::LayoutSet
Layout::computeDescendants() const {
270 BFSWalker
walker(false, index());
271 while (walker
.next()) {}
272 return walker
.allSeen();
275 bool Layout::checkInvariants() const {
276 if (!allowBespokeArrayLikes()) return true;
278 for (auto const DEBUG_ONLY parent
: m_parents
) {
279 auto const DEBUG_ONLY layout
= FromIndex(parent
);
281 assertx(layout
->m_topoIndex
< m_topoIndex
);
287 struct Layout::DescendantOrdering
{
288 bool operator()(const Layout
* a
, const Layout
* b
) const {
289 return a
->m_topoIndex
< b
->m_topoIndex
;
293 struct Layout::AncestorOrdering
{
294 bool operator()(const Layout
* a
, const Layout
* b
) const {
295 return a
->m_topoIndex
> b
->m_topoIndex
;
299 void Layout::FinalizeHierarchy() {
300 assertx(allowBespokeArrayLikes());
301 assertx(!s_hierarchyFinal
.load(std::memory_order_acquire
));
302 std::vector
<Layout
*> allLayouts
;
303 for (size_t i
= 0; i
< s_layoutTableIndex
; i
++) {
304 auto const layout
= s_layoutTable
[i
];
305 if (!layout
) continue;
306 allLayouts
.push_back(layout
);
307 assertx(layout
->checkInvariants());
308 for (auto const pIdx
: layout
->m_parents
) {
309 auto const parent
= s_layoutTable
[pIdx
.raw
];
311 parent
->m_children
.insert(layout
->index());
315 // TODO(mcolavita): implement these by merging in topological order instead
317 for (size_t i
= 0; i
< s_layoutTableIndex
; i
++) {
318 auto layout
= s_layoutTable
[i
];
319 if (!layout
) continue;
320 auto const descendants
= layout
->computeDescendants();
322 descendants
.begin(), descendants
.end(),
323 std::back_inserter(layout
->m_descendants
),
324 [&] (LayoutIndex i
) { return s_layoutTable
[i
.raw
]; }
326 auto const ancestors
= layout
->computeAncestors();
328 ancestors
.begin(), ancestors
.end(),
329 std::back_inserter(layout
->m_ancestors
),
330 [&] (LayoutIndex i
) { return s_layoutTable
[i
.raw
]; }
333 std::sort(layout
->m_descendants
.begin(), layout
->m_descendants
.end(),
334 DescendantOrdering
{});
335 std::sort(layout
->m_ancestors
.begin(), layout
->m_ancestors
.end(),
339 // Compute mask and compare sets in topological order so that they can use
340 // their children's masks if necessary.
341 std::sort(allLayouts
.begin(), allLayouts
.end(), AncestorOrdering
{});
342 for (auto const layout
: allLayouts
) {
343 layout
->m_maskAndCompare
= layout
->computeMaskAndCompare();
346 s_hierarchyFinal
.store(true, std::memory_order_release
);
349 bool Layout::operator<=(const Layout
& other
) const {
350 assertx(s_hierarchyFinal
.load(std::memory_order_acquire
));
351 auto const res
= std::binary_search(m_ancestors
.begin(), m_ancestors
.end(),
352 &other
, AncestorOrdering
{});
353 assertx(isDescendantOfDebug(&other
) == res
);
357 const Layout
* Layout::operator|(const Layout
& other
) const {
358 assertx(s_hierarchyFinal
.load(std::memory_order_acquire
));
359 auto lIter
= m_ancestors
.cbegin();
360 auto rIter
= other
.m_ancestors
.cbegin();
361 auto const lt
= AncestorOrdering
{};
362 while (lIter
!= m_ancestors
.cend() && rIter
!= other
.m_ancestors
.cend()) {
363 if (*lIter
== *rIter
) {
366 if (lt(*lIter
, *rIter
)) {
375 const Layout
* Layout::operator&(const Layout
& other
) const {
376 assertx(s_hierarchyFinal
.load(std::memory_order_acquire
));
377 auto lIter
= m_descendants
.cbegin();
378 auto rIter
= other
.m_descendants
.cbegin();
379 auto const lt
= DescendantOrdering
{};
380 while (lIter
!= m_descendants
.cend() && rIter
!= other
.m_descendants
.cend()) {
381 if (*lIter
== *rIter
) {
384 if (lt(*lIter
, *rIter
)) {
393 LayoutIndex
Layout::ReserveIndices(size_t count
) {
394 assertx(folly::isPowTwo(count
));
395 auto const padded_count
= 2 * count
- 1;
396 auto const base
= s_layoutTableIndex
.fetch_add(padded_count
);
397 always_assert(base
+ padded_count
<= kMaxIndex
.raw
+ 1);
398 for (auto i
= 0; i
< padded_count
; i
++) {
399 s_layoutTable
[base
+ i
] = nullptr;
401 auto const result
= (base
+ count
- 1) & ~(count
- 1);
402 assertx(result
% count
== 0);
403 return {safe_cast
<uint16_t>(result
)};
406 const Layout
* Layout::FromIndex(LayoutIndex index
) {
407 auto const layout
= s_layoutTable
[index
.raw
];
408 assertx(layout
!= nullptr);
409 assertx(layout
->index() == index
);
413 std::string
Layout::dumpAllLayouts() {
414 std::ostringstream ss
;
416 for (size_t i
= 0; i
< s_layoutTableIndex
; i
++) {
417 auto const layout
= s_layoutTable
[i
];
418 if (!layout
) continue;
420 ss
<< layout
->dumpInformation();
426 std::string
Layout::dumpInformation() const {
427 assertx(s_hierarchyFinal
.load(std::memory_order_acquire
));
429 auto const concreteDesc
= [&](const Layout
* layout
) {
430 return layout
->isConcrete() ? "Concrete" : "Abstract";
433 std::ostringstream ss
;
434 ss
<< folly::format("{:04x}: {} [{}]\n", m_index
.raw
, describe(),
437 ss
<< folly::format(" Type test:\n");
438 ss
<< folly::format(" XOR {:04x}\n", m_maskAndCompare
.xorVal
);
439 ss
<< folly::format(" AND {:04x}\n", m_maskAndCompare
.andVal
);
440 ss
<< folly::format(" CMP {:04x}\n", m_maskAndCompare
.cmpVal
);
442 ss
<< folly::format(" Ancestors:\n");
443 for (auto const ancestor
: m_ancestors
) {
444 ss
<< folly::format(" - {:04x}: {} [{}]\n",
445 ancestor
->m_index
.raw
, ancestor
->describe(),
446 concreteDesc(ancestor
));
449 ss
<< folly::format(" Descendants:\n");
450 for (auto const descendant
: m_descendants
) {
451 ss
<< folly::format(" - {:04x}: {} [{}]\n",
452 descendant
->m_index
.raw
, descendant
->describe(),
453 concreteDesc(descendant
));
461 MaskAndCompare
Layout::computeMaskAndCompare() const {
462 FTRACE_MOD(Trace::bespoke
, 1, "Try: {}\n", describe());
464 // The set of all concrete layouts that descend from the layout.
465 std::vector
<uint16_t> liveVec
;
466 for(auto const layout
: m_descendants
) {
467 if (!layout
->isConcrete()) continue;
468 liveVec
.push_back(layout
->m_index
.raw
);
471 assertx(!liveVec
.empty());
472 if (liveVec
.size() == 1) return {MaskAndCompare::fullCompare(liveVec
[0])};
473 std::sort(liveVec
.begin(), liveVec
.end());
475 // The set of all possible concrete layouts.
476 std::vector
<uint16_t> allConcrete
;
477 for (size_t i
= 0; i
< s_layoutTableIndex
; i
++) {
478 auto const layout
= s_layoutTable
[i
];
479 if (!layout
) continue;
480 if (!layout
->isConcrete()) continue;
481 allConcrete
.push_back(layout
->m_index
.raw
);
484 // The set of all concrete layouts that do *not* descend from this layout.
485 // This set includes the vanilla index, so that our test will exclude it.
486 std::vector
<uint16_t> deadVec
;
488 allConcrete
.cbegin(), allConcrete
.cend(),
489 liveVec
.cbegin(), liveVec
.cend(),
490 std::back_inserter(deadVec
)
492 static_assert(ArrayData::kDefaultVanillaArrayExtra
== uint32_t(-1));
493 auto constexpr kVanillaLayoutIndex
= uint16_t(-1);
494 deadVec
.push_back(kVanillaLayoutIndex
);
496 auto const check
= [&] () -> MaskAndCompare
{
497 // 1. Attempt to find a trivial mask to cover.
498 if (auto const result
= computeSimpleMaskAndCompare(liveVec
, deadVec
)) {
502 // 2. Attempt to find a single mask to cover.
503 if (auto const result
= computeXORMaskAndCompare(liveVec
, deadVec
)) {
508 SCOPE_ASSERT_DETAIL("bespoke::Layout::computeMaskAndCompare") {
509 std::string ret
= folly::sformat("{:04x}: {}\n", m_index
.raw
, describe());
510 ret
+= folly::sformat(" Live:\n");
511 for (auto const live
: liveVec
) {
512 auto const layout
= s_layoutTable
[live
];
513 ret
+= folly::sformat(" - {:04x}: {}\n", live
, layout
->describe());
515 ret
+= folly::sformat(" Dead:\n");
516 for (auto const dead
: deadVec
) {
517 auto const layout
= s_layoutTable
[dead
];
518 ret
+= folly::sformat(" - {:04x}: {}\n", dead
, layout
->describe());
522 always_assert(false);
526 // The check should pass on live values and fail on dead values.
527 for (auto const live
: liveVec
) always_assert(check
.accepts(live
));
528 for (auto const dead
: deadVec
) always_assert(!check
.accepts(dead
));
534 MaskAndCompare
Layout::maskAndCompare() const {
535 assertx(s_hierarchyFinal
.load(std::memory_order_acquire
));
536 return m_maskAndCompare
;
539 struct Layout::Initializer
{
541 AbstractLayout::InitializeLayouts();
542 LoggingArray::InitializeLayouts();
543 MonotypeVec::InitializeLayouts();
544 EmptyMonotypeDict::InitializeLayouts();
547 Layout::Initializer
Layout::s_initializer
;
549 //////////////////////////////////////////////////////////////////////////////
551 ArrayLayout
Layout::appendType(Type val
) const {
552 return ArrayLayout::Top();
555 ArrayLayout
Layout::removeType(Type key
) const {
556 return ArrayLayout::Top();
559 ArrayLayout
Layout::setType(Type key
, Type val
) const {
560 return ArrayLayout::Top();
563 std::pair
<Type
, bool> Layout::elemType(Type key
) const {
564 return {TInitCell
, false};
567 std::pair
<Type
, bool> Layout::firstLastType(bool isFirst
, bool isKey
) const {
568 return {isKey
? (TInt
| TStr
) : TInitCell
, false};
571 Type
Layout::iterPosType(Type pos
, bool isKey
) const {
572 return isKey
? (TInt
| TStr
) : TInitCell
;
575 //////////////////////////////////////////////////////////////////////////////
577 AbstractLayout::AbstractLayout(LayoutIndex index
,
578 std::string description
,
580 const LayoutFunctions
* vtable
/*=nullptr*/)
581 : Layout(index
, std::move(description
), std::move(parents
), vtable
)
584 void AbstractLayout::InitializeLayouts() {
585 auto const index
= Layout::ReserveIndices(1);
586 always_assert(index
== kBespokeTopIndex
);
587 new AbstractLayout(index
, "BespokeTop", {});
590 LayoutIndex
AbstractLayout::GetBespokeTopIndex() {
591 return kBespokeTopIndex
;
594 ConcreteLayout::ConcreteLayout(LayoutIndex index
,
595 std::string description
,
597 const LayoutFunctions
* vtable
)
598 : Layout(index
, std::move(description
), std::move(parents
), vtable
)
601 #define X(Return, Name, Args...) \
602 assertx(vtable->fn##Name);
603 BESPOKE_LAYOUT_FUNCTIONS(ArrayData
)
607 const ConcreteLayout
* ConcreteLayout::FromConcreteIndex(LayoutIndex index
) {
608 auto const layout
= s_layoutTable
[index
.raw
];
609 assertx(layout
!= nullptr);
610 assertx(layout
->index() == index
);
611 assertx(layout
->isConcrete());
612 return reinterpret_cast<ConcreteLayout
*>(layout
);
615 //////////////////////////////////////////////////////////////////////////////
618 ArrayData
* maybeMonoifyForTesting(ArrayData
* ad
, LoggingProfile
* profile
) {
619 assertx(profile
->data
);
620 auto& profileMonotype
= profile
->data
->staticMonotypeArray
;
621 auto const mad
= profileMonotype
.load(std::memory_order_relaxed
);
624 auto const result
= maybeMonoify(ad
);
625 if (!result
) return ad
;
626 ad
->decRefAndRelease();
628 // We should cache a staticMonotypeArray iff this profile is for a static
629 // array constructor or static prop - i.e. iff staticLoggingArray is set.
630 if (!profile
->data
->staticLoggingArray
) return result
;
632 ArrayData
* current
= nullptr;
633 if (profileMonotype
.compare_exchange_strong(current
, result
)) {
636 RO::EvalLowStaticArrays
? low_free(result
) : uncounted_free(result
);
641 void logBespokeDispatch(const ArrayData
* ad
, const char* fn
) {
642 DEBUG_ONLY
auto const sk
= getSrcKey();
643 DEBUG_ONLY
auto const index
= BespokeArray::asBespoke(ad
)->layoutIndex();
644 DEBUG_ONLY
auto const layout
= Layout::FromIndex(index
);
645 TRACE_MOD(Trace::bespoke
, 6, "Bespoke dispatch: %s: %s::%s\n",
646 sk
.valid() ? sk
.getSymbol().data() : "(unknown)",
647 layout
->describe().data(), fn
);
650 BespokeArray
* maybeMonoify(ArrayData
* ad
) {
651 if (!ad
->isVanilla() || ad
->isKeysetType()) return nullptr;
653 auto const et
= EntryTypes::ForArray(ad
);
654 if (!et
.isMonotypeState()) return nullptr;
656 auto const legacy
= ad
->isLegacyArray();
658 if (et
.valueTypes
== ValueTypes::Empty
) {
659 switch (ad
->toDataType()) {
660 case KindOfVArray
: return EmptyMonotypeVec::GetVArray(legacy
);
661 case KindOfVec
: return EmptyMonotypeVec::GetVec(legacy
);
662 case KindOfDArray
: return EmptyMonotypeDict::GetDArray(legacy
);
663 case KindOfDict
: return EmptyMonotypeDict::GetDict(legacy
);
664 default: always_assert(false);
668 auto const dt
= et
.valueDatatype
;
669 return ad
->isDArray() || ad
->isDictType()
670 ? MakeMonotypeDictFromVanilla(ad
, dt
, et
.keyTypes
)
671 : MonotypeVec::MakeFromVanilla(ad
, dt
);
674 ArrayData
* makeBespokeForTesting(ArrayData
* ad
, LoggingProfile
* profile
) {
675 if (!jit::mcgen::retranslateAllEnabled()) {
676 return bespoke::maybeMakeLoggingArray(ad
, profile
);
678 auto const mod
= requestCount() % 3;
679 if (mod
== 1) return bespoke::maybeMakeLoggingArray(ad
, profile
);
680 if (mod
== 2) return bespoke::maybeMonoifyForTesting(ad
, profile
);
684 //////////////////////////////////////////////////////////////////////////////