HEX
Server: Apache
System: Linux wp02.tdr-lab.com 3.10.0-1160.42.2.el7.x86_64 #1 SMP Tue Sep 7 14:49:57 UTC 2021 x86_64
User: kusanagi (1001)
PHP: 7.4.23
Disabled: NONE
Upload Files
File: //proc/self/root/usr/include/hphp/runtime/base/mixed-array.h
/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/

#ifndef incl_HPHP_HPHP_ARRAY_H_
#define incl_HPHP_HPHP_ARRAY_H_

#include "hphp/runtime/base/array-data.h"
#include "hphp/runtime/base/array-common.h"
#include "hphp/runtime/base/string-data.h"
#include "hphp/runtime/base/typed-value.h"

#include <folly/portability/Constexpr.h>

namespace HPHP {

//////////////////////////////////////////////////////////////////////

struct APCArray;
struct ArrayInit;
struct MemoryProfile;

//////////////////////////////////////////////////////////////////////

struct MixedArray final : ArrayData,
                          type_scan::MarkCountable<MixedArray> {
  // Load factor scaler. If S is the # of elements, C is the
  // power-of-2 capacity, and L=LoadScale, we grow when S > C-C/L.
  // So 2 gives 0.5 load factor, 4 gives 0.75 load factor, 8 gives
  // 0.875 load factor. Use powers of 2 to enable shift-divide.
  static constexpr uint32_t LoadScale = 4;

  constexpr static uint32_t HashSize(uint32_t scale) { return 4 * scale; }
  constexpr static uint32_t Capacity(uint32_t scale) { return 3 * scale; }
  constexpr static uint32_t Mask(uint32_t scale) { return 4 * scale - 1; }

  using hash_t = strhash_t;

  /*
   * Iterator helper for kPackedKind and kMixedKind.  You can use this
   * to look at the values in the array, but not the keys unless you
   * know it is kMixedKind.
   *
   * This can be used as an optimization vs. ArrayIter, which uses
   * indirect calls in the loop.
   */
  struct ValIter;

  struct Elm {
    union {
      int64_t ikey;
      StringData* skey;
    };
    // We store values here, but also some information local to this array:
    // data.m_aux.u_hash contains either a negative number (for an int key) or
    // a string hashcode (31-bit and thus non-negative); the high bit is the
    // int/string key descriminator. data.m_type == kInvalidDataType if this is
    // an empty slot in the array (e.g. after a key is deleted).  It is
    // critical that when we return &data to clients, that they not read or
    // write the m_aux field!
    TypedValueAux data;

    bool hasStrKey() const {
      // Currently string hash is 31-bit, thus it saves us some instructions to
      // encode int keys as a negative hash, so that we don't have to care about
      // the MSB when working with strhash_t.
      return data.hash() >= 0;
    }

    bool hasIntKey() const {
      return data.hash() < 0;
    }

    hash_t hash() const {
      return data.hash();
    }

    hash_t probe() const {
      return hash();
    }

    void setStaticKey(StringData* k, strhash_t h) {
      assert(k->isStatic());
      skey = k;
      data.hash() = h;
    }

    void setStrKey(StringData* k, strhash_t h) {
      skey = k;
      data.hash() = h;
      k->incRefCount();
    }

    void setIntKey(int64_t k, inthash_t h) {
      ikey = k;
      data.hash() = static_cast<int32_t>(h) | STRHASH_MSB;
      assert(hasIntKey());
      static_assert(static_cast<int32_t>(STRHASH_MSB) < 0,
                    "high bit indicates int key");
    }

    bool isTombstone() const {
      return MixedArray::isTombstone(data.m_type);
    }

    TYPE_SCAN_CUSTOM() {
      // if data is a Tombstone, the TypedValue scanner will ignore it
      static_assert(!isRefcountedType(kInvalidDataType), "");
      if (hasStrKey()) scanner.scan(skey);
      scanner.scan(data);
    }

    static constexpr ptrdiff_t keyOff() {
      return offsetof(Elm, ikey);
    }
    static constexpr ptrdiff_t dataOff() {
      return offsetof(Elm, data);
    }
    static constexpr ptrdiff_t hashOff() {
      return offsetof(Elm, data) + offsetof(TypedValue, m_aux);
    }
  };

  static constexpr ptrdiff_t dataOff() {
    return sizeof(MixedArray);
  }
  static constexpr ptrdiff_t usedOff() {
    return offsetof(MixedArray, m_used);
  }
  static constexpr ptrdiff_t scaleOff() {
    return offsetof(MixedArray, m_scale);
  }

  static constexpr ptrdiff_t elmOff(uint32_t pos) {
    return dataOff() + pos * sizeof(Elm);
  }

  struct ElmKey {
    ElmKey() {}
    ElmKey(strhash_t hash, StringData* key)
        : skey(key), hash(hash)
      {}
    union {
      StringData* skey;
      int64_t ikey;
    };
    int32_t hash;

    TYPE_SCAN_CUSTOM_FIELD(skey) {
      if (hash < 0) scanner.scan(skey);
    }
  };

  /*
   * Initialize an empty small mixed array with given field. This should be
   * inlined.
   */
  static void InitSmall(MixedArray* a, RefCount count, uint32_t size,
                        int64_t nextIntKey);

  /*
   * Allocate a new, empty, request-local array in (mixed|dict) mode, with
   * enough space reserved for `capacity' members.
   *
   * The returned array is already incref'd.
   */
  static ArrayData* MakeReserveMixed(uint32_t size);
  static ArrayData* MakeReserveDict(uint32_t size);

  /*
   * Convert mixed-layout array to dict in-place. This function doesn't check
   * whether the input array contains references or not, so only use this when
   * you already know that they do not.
   */
  static MixedArray* ToDictInPlace(ArrayData*);

  /*
   * MakeReserveSame allocates a new, empty, request-local array with the same
   * mode as `other' and with enough space reserved for `capacity' members, or
   * if `capacity' is zero, with the same capacity as `other'.
   *
   * MakeReserveLike will return a PHP array with a memory representation
   * similar to the one used by `other'.
   *
   * The returned array is already incref'd.
   */
  static ArrayData* MakeReserveSame(const ArrayData* other, uint32_t capacity);
  static ArrayData* MakeReserveLike(const ArrayData* other, uint32_t capacity);

  /*
   * Allocates a new request-local array with given key,value,key,value,... in
   * natural order. Returns nullptr if there are duplicate keys. Does not check
   * for integer-like keys. Takes ownership of keys and values iff successful.
   */
  static MixedArray* MakeMixed(uint32_t size, const TypedValue* keysAndValues);

  /*
   * Like MakePacked, but given static strings, make a struct-like array.
   * Also requires size > 0.
   */
  static MixedArray* MakeStruct(uint32_t size, const StringData* const* keys,
                               const TypedValue* values);

  /*
   * Allocate an uncounted MixedArray and copy the values from the
   * input 'array' into the uncounted one. All values copied are made
   * uncounted as well.  An uncounted array can only contain uncounted
   * values (primitive values, uncounted or static strings and
   * uncounted or static arrays).  The Packed version does the same
   * when the array has a kPackedKind.
   *
   * 'extra' bytes may be allocated in front of the returned pointer,
   * must be a multiple of 16, and later be passed to ReleaseUncounted.
   * (This is used to co-allocate a TypedValue with its array data.)
   */
  static ArrayData* MakeUncounted(ArrayData* array, size_t extra = 0);

  static ArrayData* MakeDictFromAPC(const APCArray* apc);

  // This behaves the same as iter_begin except that it assumes
  // this array is not empty and its not virtual.
  ALWAYS_INLINE
  ssize_t getIterBegin() const {
    assert(!empty());
    if (LIKELY(!data()[0].isTombstone())) {
      return 0;
    }
    return nextElm(data(), 0);
  }

  static bool DictEqual(const ArrayData*, const ArrayData*);
  static bool DictNotEqual(const ArrayData*, const ArrayData*);
  static bool DictSame(const ArrayData*, const ArrayData*);
  static bool DictNotSame(const ArrayData*, const ArrayData*);

  /*
   * Memoization interface.
   *
   * Both functions take a current base (which should either be a memoization
   * cache, or a RefData pointing to one), a pointer to a contiguous range of
   * keys to use for the lookup, and the length of the range. The length should
   * always be at least one.
   *
   * MemoGet will return the stored value corresponding to the keys, or
   * KindOfUninit if not found.
   *
   * MemoSet will store the given Cell at the location corresponding to the
   * keys, updating the base if the underlying dicts are mutated.
   */
  static Cell MemoGet(const TypedValue*, const Cell*, uint32_t);
  static void MemoSet(TypedValue*, const Cell*, uint32_t, Cell);

  using ArrayData::decRefCount;
  using ArrayData::hasMultipleRefs;
  using ArrayData::hasExactlyOneRef;
  using ArrayData::decWillRelease;
  using ArrayData::incRefCount;

  /*
   * MixedArray is convertible to ArrayData*, but not implicitly.
   * This is to avoid accidentally using virtual dispatch when you
   * already know something is Mixed.
   *
   * I.e., instead of doing things like mixed->nvGet(...) you want to
   * do MixedArray::NvGetInt(adYouKnowIsMixed, ...).  This means using
   * MixedArray*'s directly shouldn't really happen very often.
   */
  ArrayData* asArrayData() { return this; }
  const ArrayData* asArrayData() const { return this; }

  // These using directives ensure the full set of overloaded functions
  // are visible in this class, to avoid triggering implicit conversions
  // from a const Variant& key to int64.
private:
  using ArrayData::exists;
  using ArrayData::lval;
  using ArrayData::lvalNew;
  using ArrayData::set;
  using ArrayData::setRef;
  using ArrayData::add;
  using ArrayData::remove;
  using ArrayData::nvGet;
  using ArrayData::release;

public:
  static Variant CreateVarForUncountedArray(const Variant& source);

  static size_t Vsize(const ArrayData*);
  static const Variant& GetValueRef(const ArrayData*, ssize_t pos);
  static bool IsVectorData(const ArrayData*);
  static const TypedValue* NvGetInt(const ArrayData*, int64_t ki);
  static constexpr auto NvTryGetInt = &NvGetInt;
  static const TypedValue* NvGetStr(const ArrayData*, const StringData* k);
  static constexpr auto NvTryGetStr = &NvGetStr;
  static Cell NvGetKey(const ArrayData*, ssize_t pos);
  static ssize_t IterBegin(const ArrayData*);
  static ssize_t IterLast(const ArrayData*);
  static ssize_t IterEnd(const ArrayData*);
  static ssize_t IterAdvance(const ArrayData*, ssize_t pos);
  static ssize_t IterRewind(const ArrayData*, ssize_t pos);
  static bool ExistsInt(const ArrayData*, int64_t k);
  static bool ExistsStr(const ArrayData*, const StringData* k);
  static ArrayLval LvalInt(ArrayData* ad, int64_t k, bool copy);
  static ArrayLval LvalIntRef(ArrayData* ad, int64_t k, bool copy);
  static ArrayLval LvalStr(ArrayData* ad, StringData* k, bool copy);
  static ArrayLval LvalStrRef(ArrayData* ad, StringData* k, bool copy);
  static ArrayLval LvalNew(ArrayData*, bool copy);
  static ArrayLval LvalNewRef(ArrayData*, bool copy);
  static ArrayData* SetInt(ArrayData*, int64_t k, Cell v, bool copy);
  static ArrayData* SetStr(ArrayData*, StringData* k, Cell v, bool copy);
  // TODO(t4466630) Do we want to raise warnings in zend compatibility mode?
  static ArrayData* ZSetInt(ArrayData*, int64_t k, RefData* v);
  static ArrayData* ZSetStr(ArrayData*, StringData* k, RefData* v);
  static ArrayData* ZAppend(ArrayData* ad, RefData* v, int64_t* key_ptr);
  static ArrayData* SetRefInt(ArrayData* ad, int64_t k, Variant& v, bool copy);
  static ArrayData* SetRefStr(ArrayData* ad, StringData* k, Variant& v,
                              bool copy);
  static ArrayData* AddInt(ArrayData*, int64_t k, Cell v, bool copy);
  static ArrayData* AddStr(ArrayData*, StringData* k, Cell v, bool copy);
  static ArrayData* RemoveInt(ArrayData*, int64_t k, bool copy);
  static ArrayData* RemoveStr(ArrayData*, const StringData* k, bool copy);
  static ArrayData* Copy(const ArrayData*);
  static ArrayData* CopyWithStrongIterators(const ArrayData*);
  static ArrayData* CopyStatic(const ArrayData*);
  static ArrayData* Append(ArrayData*, Cell v, bool copy);
  static ArrayData* AppendRef(ArrayData*, Variant& v, bool copy);
  static ArrayData* AppendWithRef(ArrayData*, const Variant& v, bool copy);
  static ArrayData* PlusEq(ArrayData*, const ArrayData* elems);
  static ArrayData* Merge(ArrayData*, const ArrayData* elems);
  static ArrayData* Pop(ArrayData*, Variant& value);
  static ArrayData* Dequeue(ArrayData*, Variant& value);
  static ArrayData* Prepend(ArrayData*, Cell v, bool copy);
  static ArrayData* ToPHPArray(ArrayData*, bool);
  static ArrayData* ToDict(ArrayData*, bool);
  static constexpr auto ToVec = &ArrayCommon::ToVec;
  static constexpr auto ToKeyset = &ArrayCommon::ToKeyset;

  static void Renumber(ArrayData*);
  static void OnSetEvalScalar(ArrayData*);
  static void Release(ArrayData*);
  static void ReleaseUncounted(ArrayData*, size_t extra = 0);
  static constexpr auto ValidMArrayIter = &ArrayCommon::ValidMArrayIter;
  static bool AdvanceMArrayIter(ArrayData*, MArrayIter& fp);
  static ArrayData* Escalate(const ArrayData* ad) {
    return const_cast<ArrayData*>(ad);
  }

  static ArrayData* EscalateForSort(ArrayData* ad, SortFunction sf);
  static void Ksort(ArrayData*, int sort_flags, bool ascending);
  static void Sort(ArrayData*, int sort_flags, bool ascending);
  static void Asort(ArrayData*, int sort_flags, bool ascending);
  static bool Uksort(ArrayData*, const Variant& cmp_function);
  static bool Usort(ArrayData*, const Variant& cmp_function);
  static bool Uasort(ArrayData*, const Variant& cmp_function);

  static const TypedValue* NvTryGetIntDict(const ArrayData*, int64_t);
  static constexpr auto NvGetIntDict = &NvGetInt;
  static const TypedValue* NvTryGetStrDict(const ArrayData*, const StringData*);
  static constexpr auto NvGetStrDict = &NvGetStr;
  static constexpr auto ReleaseDict = &Release;
  static constexpr auto NvGetKeyDict = &NvGetKey;
  static constexpr auto SetIntDict = &SetInt;
  static constexpr auto SetStrDict = &SetStr;
  static constexpr auto AddIntDict = &AddInt;
  static constexpr auto AddStrDict = &AddStr;
  static constexpr auto VsizeDict = &Vsize;
  static constexpr auto GetValueRefDict = &GetValueRef;
  static constexpr auto IsVectorDataDict = &IsVectorData;
  static constexpr auto ExistsIntDict = &ExistsInt;
  static constexpr auto ExistsStrDict = &ExistsStr;
  static constexpr auto LvalIntDict = &LvalInt;
  static constexpr auto LvalStrDict = &LvalStr;
  static constexpr auto LvalNewDict = &LvalNew;
  static constexpr auto RemoveIntDict = &RemoveInt;
  static constexpr auto RemoveStrDict = &RemoveStr;
  static constexpr auto IterBeginDict = &IterBegin;
  static constexpr auto IterLastDict = &IterLast;
  static constexpr auto IterEndDict = &IterEnd;
  static constexpr auto IterAdvanceDict = &IterAdvance;
  static constexpr auto IterRewindDict = &IterRewind;
  static constexpr auto ValidMArrayIterDict = ValidMArrayIter;
  static constexpr auto AdvanceMArrayIterDict = &AdvanceMArrayIter;
  static constexpr auto EscalateForSortDict = &EscalateForSort;
  static constexpr auto KsortDict = &Ksort;
  static constexpr auto SortDict = &Sort;
  static constexpr auto AsortDict = &Asort;
  static constexpr auto UksortDict = &Uksort;
  static constexpr auto UsortDict = &Usort;
  static constexpr auto UasortDict = &Uasort;
  static constexpr auto CopyDict = &Copy;
  static constexpr auto CopyWithStrongIteratorsDict = &CopyWithStrongIterators;
  static constexpr auto CopyStaticDict = &CopyStatic;
  static constexpr auto AppendDict = &Append;
  static ArrayLval LvalIntRefDict(ArrayData*, int64_t, bool);
  static ArrayLval LvalStrRefDict(ArrayData*, StringData*, bool);
  static ArrayLval LvalNewRefDict(ArrayData*, bool);
  static ArrayData* SetRefIntDict(ArrayData*, int64_t, Variant&, bool);
  static ArrayData* SetRefStrDict(ArrayData*, StringData*, Variant&, bool);
  static ArrayData* AppendRefDict(ArrayData*, Variant&, bool);
  static ArrayData* AppendWithRefDict(ArrayData*, const Variant&, bool);
  static constexpr auto PlusEqDict = &PlusEq;
  static constexpr auto MergeDict = &Merge;
  static constexpr auto PopDict = &Pop;
  static constexpr auto DequeueDict = &Dequeue;
  static constexpr auto PrependDict = &Prepend;
  static constexpr auto RenumberDict = &Renumber;
  static constexpr auto OnSetEvalScalarDict = &OnSetEvalScalar;
  static constexpr auto EscalateDict = &Escalate;
  static ArrayData* ToPHPArrayDict(ArrayData*, bool);
  static ArrayData* ToDictDict(ArrayData*, bool);
  static constexpr auto ToVecDict = &ArrayCommon::ToVec;
  static constexpr auto ToKeysetDict = &ArrayCommon::ToKeyset;

  //////////////////////////////////////////////////////////////////////

  // Like Lval[Int,Str], but silently does nothing if the element does not
  // exist. Not part of the ArrayData interface, but used for member operations.
  static ArrayLval LvalSilentInt(ArrayData*, int64_t, bool);
  static ArrayLval LvalSilentStr(ArrayData*, const StringData*, bool);

  static constexpr auto LvalSilentIntDict = &LvalSilentInt;
  static constexpr auto LvalSilentStrDict = &LvalSilentStr;

  //////////////////////////////////////////////////////////////////////

private:
  MixedArray* copyMixed() const;
  MixedArray* copyMixedAndResizeIfNeeded() const;
  MixedArray* copyMixedAndResizeIfNeededSlow() const;
  static ArrayData* MakeReserveImpl(uint32_t capacity, HeaderKind hk);

  static bool DictEqualHelper(const ArrayData*, const ArrayData*, bool);

public:
  // Elm's data.m_type == kInvalidDataType for deleted slots.
  static bool isTombstone(DataType t) {
    assert(isRealType(t) || t == kInvalidDataType);
    return t < KindOfUninit;
    static_assert(KindOfUninit == 0 && kInvalidDataType < 0, "");
  }

  // Element index, with special values < 0 used for hash tables.
  static constexpr int32_t Empty      = -1;
  static constexpr int32_t Tombstone  = -2;

  // Use a minimum of an 4-element hash table.  Valid range: [2..32]
  static constexpr uint32_t LgSmallScale = 0;
  static constexpr uint32_t SmallScale = 1 << LgSmallScale;
  static constexpr uint32_t SmallHashSize = SmallScale * 4;
  static constexpr uint32_t SmallMask = SmallHashSize - 1; // 3
  static constexpr uint32_t SmallSize = SmallScale * 3;

  static constexpr uint64_t MaxHashSize = uint64_t(1) << 32;
  static constexpr uint32_t MaxMask = MaxHashSize - 1;
  static constexpr uint32_t MaxSize = MaxMask - MaxMask / LoadScale;
  static constexpr uint32_t MaxMakeSize = 4 * SmallSize;
  static constexpr uint32_t MaxStructMakeSize = 64;

  uint32_t iterLimit() const { return m_used; }

  // Fetch a value and optional key (if keyPos != nullptr), given an
  // iterator pos.  If withref is true, copy the value with "withRef"
  // semantics, and decref the previous key before copying the key.
  // Otherwise get the value cell (unboxing), and initialize keyOut.
  void getArrayElm(ssize_t pos, TypedValue* out, TypedValue* keyOut) const;
  void getArrayElm(ssize_t pos, TypedValue* out) const;
  void dupArrayElmWithRef(ssize_t pos, TypedValue* valOut,
    TypedValue* keyOut) const;

  const TypedValue* getArrayElmPtr(ssize_t pos) const;
  TypedValue getArrayElmKey(ssize_t pos) const;

  bool isTombstone(ssize_t pos) const;

  size_t hashSize() const;
  size_t heapSize() const;
  static constexpr size_t computeMaxElms(uint32_t tableMask);
  static size_t computeAllocBytesFromMaxElms(uint32_t maxElms);

private:
  friend struct ArrayInit;
  friend struct MemoryProfile;
  friend struct EmptyArray;
  friend struct PackedArray;
  friend struct HashCollection;
  friend struct BaseMap;
  friend struct c_Map;
  friend struct c_ImmMap;
  friend struct BaseSet;
  friend struct c_Set;
  friend struct c_ImmSet;
  friend struct c_AwaitAllWaitHandle;
  enum class ClonePacked {};
  enum class CloneMixed {};

  friend size_t getMemSize(const ArrayData*);
  template <typename AccessorT, class ArrayT>
  friend SortFlavor genericPreSort(ArrayT&, const AccessorT&, bool);

public:
  // Safe downcast helpers
  static MixedArray* asMixed(ArrayData* ad) {
    assert(ad->hasMixedLayout());
    auto a = static_cast<MixedArray*>(ad);
    assert(a->checkInvariants());
    return a;
  }
  static const MixedArray* asMixed(const ArrayData* ad) {
    assert(ad->hasMixedLayout());
    auto a = static_cast<const MixedArray*>(ad);
    assert(a->checkInvariants());
    return a;
  }

  // Fast iteration
  template <class F, bool inc = true>
  static void IterateV(const MixedArray* arr, F fn) {
    assert(arr->hasMixedLayout());
    auto elm = arr->data();
    if (inc) arr->incRefCount();
    SCOPE_EXIT { if (inc) decRefArr(const_cast<MixedArray*>(arr)); };
    for (auto i = arr->m_used; i--; elm++) {
      if (LIKELY(!elm->isTombstone())) {
        if (ArrayData::call_helper(fn, &elm->data)) break;
      }
    }
  }
  template <class F, bool inc = true>
  static void IterateKV(const MixedArray* arr, F fn) {
    assert(arr->hasMixedLayout());
    auto elm = arr->data();
    if (inc) arr->incRefCount();
    SCOPE_EXIT { if (inc) decRefArr(const_cast<MixedArray*>(arr)); };
    for (auto i = arr->m_used; i--; elm++) {
      if (LIKELY(!elm->isTombstone())) {
        TypedValue key;
        key.m_data.num = elm->ikey;
        key.m_type = elm->hasIntKey() ? KindOfInt64 : KindOfString;
        if (ArrayData::call_helper(fn, &key, &elm->data)) break;
      }
    }
  }

private:
  static Cell getElmKey(const Elm& e);

private:
  enum class AllocMode : bool { Request, Static };

  static MixedArray* CopyMixed(const MixedArray& other, AllocMode, HeaderKind);
  static MixedArray* CopyReserve(const MixedArray* src, size_t expectedSize);

  MixedArray() = delete;
  MixedArray(const MixedArray&) = delete;
  MixedArray& operator=(const MixedArray&) = delete;
  ~MixedArray() = delete;

private:
  static void initHash(int32_t* table, uint32_t scale);
  static void copyHash(int32_t* to, const int32_t* from, uint32_t scale);
  // Copy elements as well as `m_nextKI' from one MixedArray to another.
  // Warning: it could copy up to 24 bytes beyond the array and thus overwrite
  // the hashtable, but it never reads/writes beyond the end of the hash
  // table.  If you use this function, make sure you copy/write the correct
  // data on the hash table afterwards.
  static void copyElmsNextUnsafe(MixedArray* to, const MixedArray* from,
                                 uint32_t nElems);

  template <typename AccessorT>
  SortFlavor preSort(const AccessorT& acc, bool checkTypes);
  void postSort(bool resetKeys);

  static ArrayData* ArrayPlusEqGeneric(ArrayData*, MixedArray*,
                                       const ArrayData*, size_t);
  static ArrayData* ArrayMergeGeneric(MixedArray*, const ArrayData*);

  ssize_t nextElm(Elm* elms, ssize_t ei) const {
    assert(ei >= -1);
    while (size_t(++ei) < m_used) {
      if (!elms[ei].isTombstone()) {
        return ei;
      }
    }
    assert(ei == m_used);
    return ei;
  }

  ssize_t prevElm(Elm* elms, ssize_t ei) const;

  // Assert a bunch of invariants about this array then return true.
  // usage:  assert(checkInvariants());
  bool checkInvariants() const;

  template <class Hit>
  ssize_t findImpl(hash_t h0, Hit) const;

public:
  ssize_t find(int64_t ki, inthash_t h) const;
  ssize_t find(const StringData* s, strhash_t h) const;

private:
  // The array should already be sized for the new insertion before
  // calling these methods.
  template <class Hit>
  int32_t* findForInsertImpl(hash_t h0, Hit) const;
  int32_t* findForInsert(int64_t ki, inthash_t h) const;
  int32_t* findForInsert(const StringData* k, strhash_t h) const;

  struct InsertPos {
    InsertPos(bool found, TypedValue& tv) : found(found), tv(tv) {}
    bool found;
    TypedValue& tv;
  };
  InsertPos insert(int64_t k);
  InsertPos insert(StringData* k);

  template <class Hit, class Remove>
  ssize_t findForRemoveImpl(hash_t h0, Hit, Remove) const;
  ssize_t findForRemove(int64_t ki, inthash_t h, bool updateNext);
  ssize_t findForRemove(const StringData* k, strhash_t h);

  ssize_t iter_advance_helper(ssize_t prev) const;

  /**
   * findForNewInsert() CANNOT be used unless the caller can guarantee that
   * the relevant key is not already present in the array. Otherwise this can
   * put the array into a bad state; use with caution. The *CheckUnbalanced
   * version checks for the array becoming too unbalanced because of hash
   * collisions, and is only called when an array Grow()s.
   */
  int32_t* findForNewInsert(hash_t h0) const;
  int32_t* findForNewInsert(int32_t* table, size_t mask, hash_t h0) const;
  int32_t* findForNewInsertCheckUnbalanced(int32_t* table,
                                           size_t mask, hash_t h0);

  bool nextInsert(Cell);
  ArrayData* nextInsertRef(Variant& data);
  ArrayData* nextInsertWithRef(const Variant& data);
  ArrayData* addVal(int64_t ki, Cell data);
  ArrayData* addVal(StringData* key, Cell data);
  ArrayData* addValNoAsserts(StringData* key, Cell data);

  Elm& addKeyAndGetElem(StringData* key);

  template <class K> ArrayLval addLvalImpl(K k);
  template <class K> ArrayData* update(K k, Cell data);
  template <class K> ArrayData* updateRef(K k, Variant& data);

  template <class K> ArrayData* zSetImpl(K k, RefData* data);
  ArrayData* zAppendImpl(RefData* data, int64_t* key_ptr);

  void adjustMArrayIter(ssize_t pos);
  void eraseNoCompact(ssize_t pos);
  void erase(ssize_t pos) {
    eraseNoCompact(pos);
    if (m_size <= m_used / 2) {
      // Compact in order to keep elms from being overly sparse.
      compact(false);
    }
  }

  MixedArray* copyImpl(MixedArray* target) const;

  bool isFull() const;
  Elm& allocElm(int32_t* ei);

  MixedArray* initRef(TypedValue& tv, Variant& v);
  MixedArray* initWithRef(TypedValue& tv, const Variant& v);
  MixedArray* moveVal(TypedValue& tv, TypedValue v);

  ArrayData* zInitVal(TypedValue& tv, RefData* v);
  ArrayData* zSetVal(TypedValue& tv, RefData* v);

  /*
   * Helper routine for inserting elements into a new array
   * when Grow()ing the array, that also checks for potentially
   * unbalanced entries because of hash collision.
   */
  static MixedArray* InsertCheckUnbalanced(MixedArray* ad, int32_t* table,
                                           uint32_t mask,
                                           Elm* iter, Elm* stop);
  /*
   * grow() increases the hash table size and the number of slots for
   * elements by a factor of 2. grow() rebuilds the hash table, but it
   * does not compact the elements.
   */
  static MixedArray* Grow(MixedArray* old, uint32_t newScale);

  /**
   * compact() does not change the hash table size or the number of slots
   * for elements. compact() rebuilds the hash table and compacts the
   * elements into the slots with lower addresses.
   */
  void compact(bool renumber);

  /*
   * resize() and resizeIfNeeded() will grow or compact the array as
   * necessary to ensure that there is room for a new element and a
   * new hash entry.
   *
   * resize() assumes isFull().  resizeIfNeeded() will first check if
   * there is room for a new element and hash entry before growing or
   * compacting the array.
   *
   * Both functions return the new MixedArray* to use (or the old one
   * if they didn't need to grow).  The old MixedArray is left in a
   * zombie state where the only legal action is to decref and then
   * throw it away.
   */
  MixedArray* resize();
  MixedArray* resizeIfNeeded();

  Elm* data() const {
    return const_cast<Elm*>(reinterpret_cast<Elm const*>(this + 1));
  }

  int32_t* hashTab() const {
    return const_cast<int32_t*>(
      reinterpret_cast<int32_t const*>(
        data() + static_cast<size_t>(m_scale) * 3
      )
    );
  }

  uint32_t capacity() const { return Capacity(m_scale); }
  uint32_t mask() const { return Mask(m_scale); }
  uint32_t scale() const { return m_scale; }

  bool isZombie() const { return m_used + 1 == 0; }
  void setZombie() { m_used = -uint32_t{1}; }

public:
  void scan(type_scan::Scanner&) const; // in mixed-array-defs.h

private:
  struct Initializer;
  static Initializer s_initializer;

  // Some of these are packed into qword-sized unions so we can
  // combine stores during initialization. (gcc won't do it on its own.)
  union {
    struct {
      uint32_t m_scale;     // size-class equal to 1/4 table size
      uint32_t m_used;      // Number of used elements (values or tombstones)
    };
    uint64_t m_scale_used;
  };
  int64_t  m_nextKI;        // Next integer key to use for append.
};

ALWAYS_INLINE constexpr size_t MixedArray::computeMaxElms(uint32_t mask) {
  return mask - mask / LoadScale;
}

ALWAYS_INLINE constexpr size_t computeAllocBytes(uint32_t scale) {
  return sizeof(MixedArray) +
         MixedArray::HashSize(scale) * sizeof(int32_t) +
         MixedArray::Capacity(scale) * sizeof(MixedArray::Elm);
}

ALWAYS_INLINE Array empty_dict_array() {
  return Array::attach(staticEmptyDictArray());
}
//////////////////////////////////////////////////////////////////////

}

#endif // incl_HPHP_HPHP_ARRAY_H_