Automatic forward-decl fixup
[hiphop-php.git] / hphp / runtime / base / mixed-array.h
blob02cc9936307526490105548685daebf111e88700
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
25 namespace HPHP {
27 //////////////////////////////////////////////////////////////////////
29 struct ArrayInit;
30 struct MemoryProfile;
31 struct Shape;
32 struct StructArray;
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; }
47 public:
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.
56 struct ValIter;
58 struct Elm {
59 union {
60 int64_t ikey;
61 StringData* skey;
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!
70 TypedValueAux data;
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 {
84 return data.hash();
87 int32_t probe() const {
88 return hash();
91 void setStaticKey(StringData* k, strhash_t h) {
92 assert(k->isStatic());
93 skey = k;
94 data.hash() = h;
97 void setStrKey(StringData* k, strhash_t h) {
98 skey = k;
99 data.hash() = h;
100 k->incRefCount();
103 void setIntKey(int64_t k) {
104 ikey = k;
105 data.hash() = k | STRHASH_MSB;
106 assert(hasIntKey());
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);
117 mark(data);
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
132 * inlined.
134 static void InitSmall(MixedArray* a, RefCount count, uint32_t size,
135 int64_t nextIntKey);
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,
161 Shape*);
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.
175 ALWAYS_INLINE
176 ssize_t getIterBegin() const {
177 assert(!empty());
178 if (LIKELY(!data()[0].isTombstone())) {
179 return 0;
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.
205 private:
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;
215 public:
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,
232 bool copy);
233 static ArrayData* LvalStr(ArrayData* ad, StringData* k, Variant*& ret,
234 bool copy);
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,
244 bool copy);
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);
278 private:
279 MixedArray* copyMixed() const;
280 MixedArray* copyMixedAndResizeIfNeeded() const;
281 MixedArray* copyMixedAndResizeIfNeededSlow() const;
283 public:
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);
325 private:
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;
333 friend struct c_Map;
334 friend struct c_ImmMap;
335 friend struct BaseSet;
336 friend struct c_Set;
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*);
344 public:
345 // Safe downcast helpers
346 static MixedArray* asMixed(ArrayData* ad);
347 static const MixedArray* asMixed(const ArrayData* ad);
349 private:
350 static void getElmKey(const Elm& e, TypedValue* out);
352 private:
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;
363 private:
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,
372 uint32_t nElems);
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 {
385 assert(ei >= -1);
386 while (size_t(++ei) < m_used) {
387 if (!elms[ei].isTombstone()) {
388 return ei;
391 assert(ei == m_used);
392 return ei;
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;
401 template <class Hit>
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.
408 template <class Hit>
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;
413 struct InsertPos {
414 InsertPos(bool found, TypedValue& tv) : found(found), tv(tv) {}
415 bool found;
416 TypedValue& 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) {
459 eraseNoCompact(pos);
460 if (m_size < m_used / 2) {
461 // Compact in order to keep elms from being overly sparse.
462 compact(false);
466 MixedArray* copyImpl(MixedArray* target) const;
468 bool isFull() 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,
491 uint32_t mask,
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
510 * new hash entry.
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
519 * throw it away.
521 MixedArray* resize();
522 MixedArray* resizeIfNeeded();
524 Elm* data() const {
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}; }
543 public:
544 template<class F> void scan(F&) const; // in mixed-array-defs.h
546 private:
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.)
549 union {
550 struct {
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_