2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2016 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 #ifndef incl_HPHP_HPHP_ARRAY_H_
18 #define incl_HPHP_HPHP_ARRAY_H_
20 #include "hphp/runtime/base/array-data.h"
21 #include "hphp/runtime/base/array-common.h"
22 #include "hphp/runtime/base/string-data.h"
23 #include "hphp/runtime/base/typed-value.h"
27 //////////////////////////////////////////////////////////////////////
34 //////////////////////////////////////////////////////////////////////
36 struct MixedArray
: private ArrayData
{
37 // Load factor scaler. If S is the # of elements, C is the
38 // power-of-2 capacity, and L=LoadScale, we grow when S > C-C/L.
39 // So 2 gives 0.5 load factor, 4 gives 0.75 load factor, 8 gives
40 // 0.875 load factor. Use powers of 2 to enable shift-divide.
41 static constexpr uint32_t LoadScale
= 4;
43 constexpr static uint32_t HashSize(uint32_t scale
) { return 4 * scale
; }
44 constexpr static uint32_t Capacity(uint32_t scale
) { return 3 * scale
; }
45 constexpr static uint32_t Mask(uint32_t scale
) { return 4 * scale
- 1; }
49 * Iterator helper for kPackedKind and kMixedKind. You can use this
50 * to look at the values in the array, but not the keys unless you
51 * know it is kMixedKind.
53 * This can be used as an optimization vs. ArrayIter, which uses
54 * indirect calls in the loop.
63 // We store values here, but also some information local to this array:
64 // data.m_aux.u_hash contains either a negative number (for an int key) or
65 // a string hashcode (31-bit and thus non-negative); the high bit is the
66 // int/string key descriminator. data.m_type == kInvalidDataType if this is
67 // an empty slot in the array (e.g. after a key is deleted). It is
68 // critical that when we return &data to clients, that they not read or
69 // write the m_aux field!
72 bool hasStrKey() const {
73 // Currently string hash is 31-bit, thus it saves us some instructions to
74 // encode int keys as a negative hash, so that we don't have to care about
75 // the MSB when working with strhash_t.
76 return data
.hash() >= 0;
79 bool hasIntKey() const {
80 return data
.hash() < 0;
83 int32_t hash() const {
87 int32_t probe() const {
91 void setStaticKey(StringData
* k
, strhash_t h
) {
92 assert(k
->isStatic());
97 void setStrKey(StringData
* k
, strhash_t h
) {
103 void setIntKey(int64_t k
) {
105 data
.hash() = k
| STRHASH_MSB
;
107 static_assert(STRHASH_MSB
< 0, "using strhash_t = int32_t");
110 bool isTombstone() const {
111 return MixedArray::isTombstone(data
.m_type
);
114 template<class F
> void scan(F
& mark
) const {
115 if (!isTombstone()) {
116 if (hasStrKey()) mark(skey
);
121 static constexpr size_t dataOff() {
122 return offsetof(Elm
, data
);
126 static constexpr size_t dataOff() {
127 return sizeof(MixedArray
);
131 * Initialize an empty small mixed array with given field. This should be
134 static void InitSmall(MixedArray
* a
, RefCount count
, uint32_t size
,
138 * Allocate a new, empty, request-local array in mixed mode, with
139 * enough space reserved for `capacity' members.
141 * The returned array is already incref'd.
143 static ArrayData
* MakeReserveMixed(uint32_t capacity
);
146 * Allocate a new, empty, request-local array with the same mode as
147 * `other' and with enough space reserved for `capacity' members, or
148 * if `capacity' is zero, with the same capacity as `other'.
150 * The returned array is already incref'd.
152 static ArrayData
* MakeReserveLike(const ArrayData
* other
, uint32_t capacity
);
155 * Like MakePacked, but given static strings, make a struct-like array.
156 * Also requires size > 0.
158 static MixedArray
* MakeStruct(uint32_t size
, StringData
** keys
,
159 const TypedValue
* values
);
160 static StructArray
* MakeStructArray(uint32_t size
, const TypedValue
* values
,
164 * Allocate an uncounted MixedArray and copy the values from the
165 * input 'array' into the uncounted one. All values copied are made
166 * uncounted as well. An uncounted array can only contain uncounted
167 * values (primitive values, uncounted or static strings and
168 * uncounted or static arrays). The Packed version does the same
169 * when the array has a kPackedKind.
171 static ArrayData
* MakeUncounted(ArrayData
* array
);
173 // This behaves the same as iter_begin except that it assumes
174 // this array is not empty and its not virtual.
176 ssize_t
getIterBegin() const {
178 if (LIKELY(!data()[0].isTombstone())) {
181 return nextElm(data(), 0);
184 using ArrayData::decRefCount
;
185 using ArrayData::hasMultipleRefs
;
186 using ArrayData::hasExactlyOneRef
;
187 using ArrayData::decWillRelease
;
188 using ArrayData::incRefCount
;
191 * MixedArray is convertible to ArrayData*, but not implicitly.
192 * This is to avoid accidentally using virtual dispatch when you
193 * already know something is Mixed.
195 * I.e., instead of doing things like mixed->nvGet(...) you want to
196 * do MixedArray::NvGetInt(adYouKnowIsMixed, ...). This means using
197 * MixedArray*'s directly shouldn't really happen very often.
199 ArrayData
* asArrayData() { return this; }
200 const ArrayData
* asArrayData() const { return this; }
202 // These using directives ensure the full set of overloaded functions
203 // are visible in this class, to avoid triggering implicit conversions
204 // from a const Variant& key to int64.
206 using ArrayData::exists
;
207 using ArrayData::lval
;
208 using ArrayData::lvalNew
;
209 using ArrayData::set
;
210 using ArrayData::setRef
;
211 using ArrayData::add
;
212 using ArrayData::remove
;
213 using ArrayData::nvGet
;
214 using ArrayData::release
;
216 static Variant
CreateVarForUncountedArray(const Variant
& source
);
218 static size_t Vsize(const ArrayData
*);
219 static const Variant
& GetValueRef(const ArrayData
*, ssize_t pos
);
220 static bool IsVectorData(const ArrayData
*);
221 static const TypedValue
* NvGetInt(const ArrayData
*, int64_t ki
);
222 static const TypedValue
* NvGetStr(const ArrayData
*, const StringData
* k
);
223 static void NvGetKey(const ArrayData
*, TypedValue
* out
, ssize_t pos
);
224 static ssize_t
IterBegin(const ArrayData
*);
225 static ssize_t
IterLast(const ArrayData
*);
226 static ssize_t
IterEnd(const ArrayData
*);
227 static ssize_t
IterAdvance(const ArrayData
*, ssize_t pos
);
228 static ssize_t
IterRewind(const ArrayData
*, ssize_t pos
);
229 static bool ExistsInt(const ArrayData
*, int64_t k
);
230 static bool ExistsStr(const ArrayData
*, const StringData
* k
);
231 static ArrayData
* LvalInt(ArrayData
* ad
, int64_t k
, Variant
*& ret
,
233 static ArrayData
* LvalStr(ArrayData
* ad
, StringData
* k
, Variant
*& ret
,
235 static ArrayData
* LvalNew(ArrayData
*, Variant
*& ret
, bool copy
);
236 static ArrayData
* SetInt(ArrayData
*, int64_t k
, Cell v
, bool copy
);
237 static ArrayData
* SetStr(ArrayData
*, StringData
* k
, Cell v
, bool copy
);
238 // TODO(t4466630) Do we want to raise warnings in zend compatibility mode?
239 static ArrayData
* ZSetInt(ArrayData
*, int64_t k
, RefData
* v
);
240 static ArrayData
* ZSetStr(ArrayData
*, StringData
* k
, RefData
* v
);
241 static ArrayData
* ZAppend(ArrayData
* ad
, RefData
* v
, int64_t* key_ptr
);
242 static ArrayData
* SetRefInt(ArrayData
* ad
, int64_t k
, Variant
& v
, bool copy
);
243 static ArrayData
* SetRefStr(ArrayData
* ad
, StringData
* k
, Variant
& v
,
245 static ArrayData
* AddInt(ArrayData
*, int64_t k
, Cell v
, bool copy
);
246 static ArrayData
* AddStr(ArrayData
*, StringData
* k
, Cell v
, bool copy
);
247 static ArrayData
* RemoveInt(ArrayData
*, int64_t k
, bool copy
);
248 static ArrayData
* RemoveStr(ArrayData
*, const StringData
* k
, bool copy
);
249 static ArrayData
* Copy(const ArrayData
*);
250 static ArrayData
* CopyWithStrongIterators(const ArrayData
*);
251 static ArrayData
* CopyStatic(const ArrayData
*);
252 static ArrayData
* Append(ArrayData
*, const Variant
& v
, bool copy
);
253 static ArrayData
* AppendRef(ArrayData
*, Variant
& v
, bool copy
);
254 static ArrayData
* AppendWithRef(ArrayData
*, const Variant
& v
, bool copy
);
255 static ArrayData
* PlusEq(ArrayData
*, const ArrayData
* elems
);
256 static ArrayData
* Merge(ArrayData
*, const ArrayData
* elems
);
257 static ArrayData
* Pop(ArrayData
*, Variant
& value
);
258 static ArrayData
* Dequeue(ArrayData
*, Variant
& value
);
259 static ArrayData
* Prepend(ArrayData
*, const Variant
& v
, bool copy
);
260 static void Renumber(ArrayData
*);
261 static void OnSetEvalScalar(ArrayData
*);
262 static void Release(ArrayData
*);
263 static void ReleaseUncounted(ArrayData
*);
264 static constexpr auto ValidMArrayIter
= &ArrayCommon::ValidMArrayIter
;
265 static bool AdvanceMArrayIter(ArrayData
*, MArrayIter
& fp
);
266 static ArrayData
* Escalate(const ArrayData
* ad
) {
267 return const_cast<ArrayData
*>(ad
);
270 static ArrayData
* EscalateForSort(ArrayData
* ad
, SortFunction sf
);
271 static void Ksort(ArrayData
*, int sort_flags
, bool ascending
);
272 static void Sort(ArrayData
*, int sort_flags
, bool ascending
);
273 static void Asort(ArrayData
*, int sort_flags
, bool ascending
);
274 static bool Uksort(ArrayData
*, const Variant
& cmp_function
);
275 static bool Usort(ArrayData
*, const Variant
& cmp_function
);
276 static bool Uasort(ArrayData
*, const Variant
& cmp_function
);
279 MixedArray
* copyMixed() const;
280 MixedArray
* copyMixedAndResizeIfNeeded() const;
281 MixedArray
* copyMixedAndResizeIfNeededSlow() const;
284 // Elm's data.m_type == kInvalidDataType for deleted slots.
285 static bool isTombstone(DataType t
) {
286 assert(isRealType(t
) || t
== kInvalidDataType
);
287 return t
< KindOfUninit
;
288 static_assert(KindOfUninit
== 0 && kInvalidDataType
< 0, "");
291 // Element index, with special values < 0 used for hash tables.
292 static constexpr int32_t Empty
= -1;
293 static constexpr int32_t Tombstone
= -2;
295 // Use a minimum of an 4-element hash table. Valid range: [2..32]
296 static constexpr uint32_t LgSmallScale
= 0;
297 static constexpr uint32_t SmallScale
= 1 << LgSmallScale
;
298 static constexpr uint32_t SmallHashSize
= SmallScale
* 4;
299 static constexpr uint32_t SmallMask
= SmallHashSize
- 1; // 3
300 static constexpr uint32_t SmallSize
= SmallScale
* 3;
302 static constexpr uint64_t MaxHashSize
= uint64_t(1) << 32;
303 static constexpr uint32_t MaxMask
= MaxHashSize
- 1;
304 static constexpr uint32_t MaxSize
= MaxMask
- MaxMask
/ LoadScale
;
305 static constexpr uint32_t MaxMakeSize
= 4 * SmallSize
;
307 uint32_t iterLimit() const { return m_used
; }
309 // Fetch a value and optional key (if keyPos != nullptr), given an
310 // iterator pos. If withref is true, copy the value with "withRef"
311 // semantics, and decref the previous key before copying the key.
312 // Otherwise get the value cell (unboxing), and initialize keyOut.
313 void getArrayElm(ssize_t pos
, TypedValue
* out
, TypedValue
* keyOut
) const;
314 void getArrayElm(ssize_t pos
, TypedValue
* out
) const;
315 void dupArrayElmWithRef(ssize_t pos
, TypedValue
* valOut
,
316 TypedValue
* keyOut
) const;
318 bool isTombstone(ssize_t pos
) const;
320 size_t hashSize() const;
321 size_t heapSize() const;
322 static constexpr size_t computeMaxElms(uint32_t tableMask
);
323 static size_t computeAllocBytesFromMaxElms(uint32_t maxElms
);
326 friend struct ArrayInit
;
327 friend struct MemoryProfile
;
328 friend struct EmptyArray
;
329 friend struct PackedArray
;
330 friend struct StructArray
;
331 friend struct HashCollection
;
332 friend struct BaseMap
;
334 friend struct c_ImmMap
;
335 friend struct BaseSet
;
337 friend struct c_ImmSet
;
338 friend class c_AwaitAllWaitHandle
;
339 enum class ClonePacked
{};
340 enum class CloneMixed
{};
342 friend size_t getMemSize(const ArrayData
*);
345 // Safe downcast helpers
346 static MixedArray
* asMixed(ArrayData
* ad
);
347 static const MixedArray
* asMixed(const ArrayData
* ad
);
350 static void getElmKey(const Elm
& e
, TypedValue
* out
);
353 enum class AllocMode
: bool { Request
, Static
};
355 static MixedArray
* CopyMixed(const MixedArray
& other
, AllocMode
);
356 static MixedArray
* CopyReserve(const MixedArray
* src
, size_t expectedSize
);
358 MixedArray() = delete;
359 MixedArray(const MixedArray
&) = delete;
360 MixedArray
& operator=(const MixedArray
&) = delete;
361 ~MixedArray() = delete;
364 static void initHash(int32_t* table
, uint32_t scale
);
365 static void copyHash(int32_t* to
, const int32_t* from
, uint32_t scale
);
366 // Copy elements as well as `m_nextKI' from one MixedArray to another.
367 // Warning: it could copy up to 24 bytes beyond the array and thus overwrite
368 // the hashtable, but it never reads/writes beyond the end of the hash
369 // table. If you use this function, make sure you copy/write the correct
370 // data on the hash table afterwards.
371 static void copyElmsNextUnsafe(MixedArray
* to
, const MixedArray
* from
,
374 template <typename AccessorT
>
375 SortFlavor
preSort(const AccessorT
& acc
, bool checkTypes
);
376 void postSort(bool resetKeys
);
377 static ArrayData
* ArrayPlusEqGeneric(ArrayData
*,
378 MixedArray
*, const ArrayData
*, size_t);
379 static ArrayData
* ArrayMergeGeneric(MixedArray
*, const ArrayData
*);
381 // convert in-place from kPackedKind to kMixedKind: fill in keys & hashtable
382 MixedArray
* packedToMixed();
384 ssize_t
nextElm(Elm
* elms
, ssize_t ei
) const {
386 while (size_t(++ei
) < m_used
) {
387 if (!elms
[ei
].isTombstone()) {
391 assert(ei
== m_used
);
395 ssize_t
prevElm(Elm
* elms
, ssize_t ei
) const;
397 // Assert a bunch of invariants about this array then return true.
398 // usage: assert(checkInvariants());
399 bool checkInvariants() const;
402 ssize_t
findImpl(size_t h0
, Hit
) const;
403 ssize_t
find(int64_t ki
) const;
404 ssize_t
find(const StringData
* s
, strhash_t h
) const;
406 // The array should already be sized for the new insertion before
407 // calling these methods.
409 int32_t* findForInsertImpl(size_t h0
, Hit
) const;
410 int32_t* findForInsert(int64_t ki
) const;
411 int32_t* findForInsert(const StringData
* k
, strhash_t h
) const;
414 InsertPos(bool found
, TypedValue
& tv
) : found(found
), tv(tv
) {}
418 InsertPos
insert(int64_t k
);
419 InsertPos
insert(StringData
* k
);
421 template <class Hit
, class Remove
>
422 ssize_t
findForRemoveImpl(size_t h0
, Hit
, Remove
) const;
423 ssize_t
findForRemove(int64_t ki
, bool updateNext
);
424 ssize_t
findForRemove(const StringData
* k
, strhash_t h
);
426 ssize_t
iter_advance_helper(ssize_t prev
) const;
429 * findForNewInsert() CANNOT be used unless the caller can guarantee that
430 * the relevant key is not already present in the array. Otherwise this can
431 * put the array into a bad state; use with caution. The *CheckUnbalanced
432 * version checks for the array becoming too unbalanced because of hash
433 * collisions, and is only called when an array Grow()s.
435 int32_t* findForNewInsert(size_t h0
) const;
436 int32_t* findForNewInsert(int32_t* table
, size_t mask
, size_t h0
) const;
437 int32_t* findForNewInsertCheckUnbalanced(int32_t* table
,
438 size_t mask
, size_t h0
);
440 bool nextInsert(const Variant
& data
);
441 ArrayData
* nextInsertRef(Variant
& data
);
442 ArrayData
* nextInsertWithRef(const Variant
& data
);
443 ArrayData
* addVal(int64_t ki
, Cell data
);
444 ArrayData
* addVal(StringData
* key
, Cell data
);
445 ArrayData
* addValNoAsserts(StringData
* key
, Cell data
);
447 Elm
& addKeyAndGetElem(StringData
* key
);
449 template <class K
> ArrayData
* addLvalImpl(K k
, Variant
*& ret
);
450 template <class K
> ArrayData
* update(K k
, Cell data
);
451 template <class K
> ArrayData
* updateRef(K k
, Variant
& data
);
453 template <class K
> ArrayData
* zSetImpl(K k
, RefData
* data
);
454 ArrayData
* zAppendImpl(RefData
* data
, int64_t* key_ptr
);
456 void adjustMArrayIter(ssize_t pos
);
457 void eraseNoCompact(ssize_t pos
);
458 void erase(ssize_t pos
) {
460 if (m_size
< m_used
/ 2) {
461 // Compact in order to keep elms from being overly sparse.
466 MixedArray
* copyImpl(MixedArray
* target
) const;
469 bool isFullPacked() const;
471 // Pre: !isFullPacked()
472 TypedValue
& allocNextElm(uint32_t i
);
474 Elm
& allocElm(int32_t* ei
);
476 MixedArray
* getLval(TypedValue
& tv
, Variant
*& ret
);
477 MixedArray
* initRef(TypedValue
& tv
, Variant
& v
);
478 MixedArray
* initLval(TypedValue
& tv
, Variant
*& ret
);
479 MixedArray
* initWithRef(TypedValue
& tv
, const Variant
& v
);
480 MixedArray
* moveVal(TypedValue
& tv
, TypedValue v
);
482 ArrayData
* zInitVal(TypedValue
& tv
, RefData
* v
);
483 ArrayData
* zSetVal(TypedValue
& tv
, RefData
* v
);
486 * Helper routine for inserting elements into a new array
487 * when Grow()ing the array, that also checks for potentially
488 * unbalanced entries because of hash collision.
490 static MixedArray
* InsertCheckUnbalanced(MixedArray
* ad
, int32_t* table
,
492 Elm
* iter
, Elm
* stop
);
494 * grow() increases the hash table size and the number of slots for
495 * elements by a factor of 2. grow() rebuilds the hash table, but it
496 * does not compact the elements.
498 static MixedArray
* Grow(MixedArray
* old
, uint32_t newScale
);
501 * compact() does not change the hash table size or the number of slots
502 * for elements. compact() rebuilds the hash table and compacts the
503 * elements into the slots with lower addresses.
505 void compact(bool renumber
);
508 * resize() and resizeIfNeeded() will grow or compact the array as
509 * necessary to ensure that there is room for a new element and a
512 * resize() assumes isFull(). resizeIfNeeded() will first check if
513 * there is room for a new element and hash entry before growing or
514 * compacting the array.
516 * Both functions return the new MixedArray* to use (or the old one
517 * if they didn't need to grow). The old MixedArray is left in a
518 * zombie state where the only legal action is to decref and then
521 MixedArray
* resize();
522 MixedArray
* resizeIfNeeded();
525 return const_cast<Elm
*>(reinterpret_cast<Elm
const*>(this + 1));
528 int32_t* hashTab() const {
529 return const_cast<int32_t*>(
530 reinterpret_cast<int32_t const*>(
531 data() + static_cast<size_t>(m_scale
) * 3
536 uint32_t capacity() const { return Capacity(m_scale
); }
537 uint32_t mask() const { return Mask(m_scale
); }
538 uint32_t scale() const { return m_scale
; }
540 bool isZombie() const { return m_used
+ 1 == 0; }
541 void setZombie() { m_used
= -uint32_t{1}; }
544 template<class F
> void scan(F
&) const; // in mixed-array-defs.h
547 // Some of these are packed into qword-sized unions so we can
548 // combine stores during initialization. (gcc won't do it on its own.)
551 uint32_t m_scale
; // size-class equal to 1/4 table size
552 uint32_t m_used
; // Number of used elements (values or tombstones)
554 uint64_t m_scale_used
;
556 int64_t m_nextKI
; // Next integer key to use for append.
559 inline constexpr size_t MixedArray::computeMaxElms(uint32_t mask
) {
560 return size_t(mask
) - size_t(mask
) / LoadScale
;
563 ALWAYS_INLINE
constexpr size_t computeAllocBytes(uint32_t scale
) {
564 return sizeof(MixedArray
) +
565 MixedArray::HashSize(scale
) * sizeof(int32_t) +
566 MixedArray::Capacity(scale
) * sizeof(MixedArray::Elm
);
569 //////////////////////////////////////////////////////////////////////
573 #endif // incl_HPHP_HPHP_ARRAY_H_