/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 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. | +----------------------------------------------------------------------+ */ #define INLINE_VARIANT_HELPER 1 #include "hphp/runtime/base/hphp-array.h" #include "hphp/runtime/base/array-init.h" #include "hphp/runtime/base/array-iterator.h" #include "hphp/runtime/base/complex-types.h" #include "hphp/runtime/base/runtime-option.h" #include "hphp/runtime/base/runtime-error.h" #include "hphp/runtime/base/variable-serializer.h" #include "hphp/runtime/base/shared-map.h" #include "hphp/util/hash.h" #include "hphp/util/lock.h" #include "hphp/util/alloc.h" #include "hphp/util/trace.h" #include "hphp/util/util.h" #include "hphp/runtime/base/execution-context.h" #include "hphp/runtime/vm/member-operations.h" #include "hphp/runtime/base/stats.h" // inline methods of HphpArray #include "hphp/runtime/base/hphp-array-defs.h" namespace HPHP { static_assert( sizeof(HphpArray) == 152, "Performance is sensitive to sizeof(HphpArray)." " Make sure you changed it with good reason and then update this assert."); TRACE_SET_MOD(runtime); /////////////////////////////////////////////////////////////////////////////// /* * Allocation of HphpArray buffers works like this: the smallest buffer * size is allocated inline in HphpArray. Larger buffer sizes are smart * allocated or malloc-allocated depending on whether the array itself * was smart-allocated or not. (nonSmartCopy() is used to create static * arrays). HphpArray::m_allocMode tracks the state as it progresses: * * kInline -> kSmart, or * -> kMalloc * * Hashtables never shrink, so the allocMode Never goes backwards. * If an array is pre-sized, we might skip directly to kSmart or kMalloc. * If an array is created via nonSmartCopy(), we skip kSmart. * Since kMalloc is only used for static arrays, and static arrays are * never swept, we don't need any sweep method. * * For kInline, we use space in HphpArray defined as InlineSlots, which * has enough room for slots and the hashtable. The next few larger array * sizes use the inline space for just the hashtable, with slots allocated * separately. Even larger tables allocate the hashtable and slots * contiguously. */ void *HphpArray::SmaAllocatorInitSetup = SmartAllocatorInitSetup(); //============================================================================= // Static members. HphpArray HphpArray::s_theEmptyArray(StaticEmptyArray); //============================================================================= // Helpers. static inline uint32_t computeMaskFromNumElms(const uint32_t n) { assert(n <= 0x7fffffffU); auto lgSize = HphpArray::MinLgTableSize; auto maxElms = HphpArray::SmallSize; assert(lgSize >= 2); while (maxElms < n) { ++lgSize; maxElms <<= 1; } assert(lgSize <= 32); // return 2^lgSize - 1 return ((size_t(1U)) << lgSize) - 1; static_assert(HphpArray::MinLgTableSize >= 2, "lower limit for 0.75 load factor"); } //============================================================================= // Construction/destruction. HphpArray::HphpArray(uint capacity) : ArrayData(kVectorKind, AllocationMode::smart, 0) , m_used(0) { assert(m_size == 0); const auto mask = computeMaskFromNumElms(capacity); m_tableMask = mask; allocData(computeMaxElms(mask), computeTableSize(mask)); assert(checkInvariants()); } HphpArray::HphpArray(uint size, const TypedValue* values) : ArrayData(kVectorKind, AllocationMode::smart, size) , m_used(size) { const auto mask = computeMaskFromNumElms(size); m_tableMask = mask; allocData(computeMaxElms(mask), computeTableSize(mask)); // append values by moving -- Caller assumes we update refcount. Values // are in reverse order since they come from the stack, which grows down. Elm* data = m_data; for (uint32_t i = 0; i < size; i++) { const auto& tv = values[size - i - 1]; data[i].data.m_data = tv.m_data; data[i].data.m_type = tv.m_type; } assert(size == 0 || m_pos == 0); assert(checkInvariants()); } HphpArray::HphpArray(EmptyMode) : ArrayData(kVectorKind, AllocationMode::smart, 0) , m_used(0) , m_tableMask(SmallHashSize - 1) { allocData(SmallSize, SmallHashSize); setStatic(); assert(checkInvariants()); } // for internal use by nonSmartCopy() and copyVec() inline ALWAYS_INLINE HphpArray::HphpArray(const HphpArray& other, AllocationMode mode, CopyVector) : ArrayData(other.m_kind, mode, other.m_size) , m_used(other.m_used) , m_tableMask(other.m_tableMask) { assert(other.isVector()); m_pos = other.m_pos; allocData(other.m_cap, computeTableSize(m_tableMask)); // Copy the elements and bump up refcounts as needed. Elm* elms = other.m_data; Elm* targetElms = m_data; for (uint32_t i = 0, limit = m_used; i < limit; ++i) { tvDupFlattenVars(&elms[i].data, &targetElms[i].data, &other); } assert(checkInvariants()); } // For internal use by nonSmartCopy() and copyGeneric() inline ALWAYS_INLINE HphpArray::HphpArray(const HphpArray& other, AllocationMode mode, CopyGeneric) : ArrayData(other.m_kind, mode, other.m_size) , m_used(other.m_used) , m_tableMask(other.m_tableMask) , m_hLoad(other.m_hLoad) , m_nextKI(other.m_nextKI) { assert(!other.isVector()); m_pos = other.m_pos; auto maxElms = other.m_cap; auto tableSize = computeTableSize(m_tableMask); m_hash = allocData(maxElms, tableSize); // Copy the hash. memcpy(m_hash, other.m_hash, tableSize * sizeof(ElmInd)); // Copy the elements and bump up refcounts as needed. auto elms = other.m_data; auto targetElms = m_data; for (uint32_t i = 0, limit = m_used; i < limit; ++i) { const auto e = &elms[i]; auto te = &targetElms[i]; if (!isTombstone(e->data.m_type)) { te->key = e->key; te->data.hash() = e->data.hash(); if (te->hasStrKey()) te->key->incRefCount(); tvDupFlattenVars(&e->data, &te->data, &other); assert(te->hash() == e->hash()); // ensure not clobbered. } else { // Tombstone. te->data.m_type = KindOfInvalid; } } // If the element density dropped below 50% due to indirect elements // being converted into tombstones, we should do a compaction if (m_size < m_used / 2) { compact(false); } assert(checkInvariants()); } inline void HphpArray::destroyVec() { auto const elms = m_data; for (uint32_t i = 0, n = m_used; i < n; ++i) { tvRefcountedDecRef(&elms[i].data); } if (elms != m_inline_data.slots) modeFree(elms); } inline void HphpArray::destroy() { auto const elms = m_data; for (uint32_t i = 0, n = m_used; i < n; ++i) { auto& e = elms[i]; if (isTombstone(e.data.m_type)) continue; if (e.hasStrKey()) decRefStr(e.key); tvRefcountedDecRef(&e.data); } if (elms != m_inline_data.slots) modeFree(elms); } HphpArray::~HphpArray() { assert(checkInvariants()); if (isVector()) destroyVec(); else destroy(); } HOT_FUNC_VM void HphpArray::ReleaseVec(ArrayData* ad) { auto a = asVector(ad); a->destroyVec(); a->ArrayData::destroy(); HphpArray::AllocatorType::getNoCheck()->dealloc(a); } HOT_FUNC_VM void HphpArray::Release(ArrayData* ad) { auto a = asGeneric(ad); a->destroy(); a->ArrayData::destroy(); HphpArray::AllocatorType::getNoCheck()->dealloc(a); } NEVER_INLINE HOT_FUNC_VM HphpArray* HphpArray::vectorToGeneric() { assert(isVector()); if (m_data == m_inline_data.slots) { m_hash = m_inline_data.hash; } else { auto dataSize = m_cap * sizeof(*m_data); auto hashSize = computeTableSize(m_tableMask) * sizeof(*m_hash); m_hash = hashSize <= sizeof(m_inline_hash) ? m_inline_hash : (ElmInd*)(uintptr_t(m_data) + dataSize); } m_kind = kMixedKind; uint32_t i = 0; auto size = m_size; for (; i < size; ++i) { m_data[i].setIntKey(i); m_hash[i] = i; } for (; i <= m_tableMask; ++i) { m_hash[i] = ElmIndEmpty; } m_hLoad = size; m_nextKI = size; assert(checkInvariants()); return this; } // Invariants: // // m_size <= m_used; m_used <= m_cap // last element cannot be a tombstone // m_pos and all external iterators can't be on a tombstone // m_tableMask is 2^k - 1 (required for quadratic probe) // m_tableMask == nextPower2(m_cap) - 1; // m_cap == computeMaxElms(m_tableMask); // // HphpArray: // m_nextKI >= highest actual int key // Elm.data.m_type maybe KindOfInvalid (tombstone) // hash[] maybe ElmIndTombstone // m_hLoad >= m_size, == number of non-ElmIndEmpty hash entries // // Vector: // m_size == m_used // m_nextKI = uninitialized // m_hLoad = uninitialized // m_hash = uninitialized // Elm.key uninitialized // Elm.hash uninitialized // no KindOfInvalid tombstones // bool HphpArray::checkInvariants() const { assert(m_size <= m_used); assert(m_used <= m_cap); assert(m_tableMask > 0 && ((m_tableMask+1) & m_tableMask) == 0); assert(m_tableMask == Util::nextPower2(m_cap) - 1); assert(m_cap == computeMaxElms(m_tableMask)); if (m_pos != invalid_index) { assert(size_t(m_pos) < m_used); assert(!isTombstone(m_data[m_pos].data.m_type)); } if (m_used > 0) { // can't have a tombstone at the end; m_used should have been trimmed. assert(!isTombstone(m_data[m_used - 1].data.m_type)); } switch (m_kind) { case kVectorKind: assert(m_size == m_used); break; case kMixedKind: { assert(m_hash); assert(m_hLoad >= m_size); size_t load = 0; return true; // The following loop is for debugging arrays only; it slows // things down too much for general use for (size_t i = 0; i <= m_tableMask; i++) { load += m_hash[i] != ElmIndEmpty; } assert(m_hLoad == load); break; } default: assert(false); break; } if (this == &s_theEmptyArray) { assert(m_size == 0); assert(m_used == 0); assert(isVector()); assert(m_pos == invalid_index); } return true; } //============================================================================= // Iteration. inline ssize_t HphpArray::prevElm(Elm* elms, ssize_t ei) const { assert(ei <= ssize_t(m_used)); while (ei > 0) { --ei; if (!isTombstone(elms[ei].data.m_type)) { return ei; } } return (ssize_t)ElmIndEmpty; } ssize_t HphpArray::IterBegin(const ArrayData* ad) { auto a = asHphpArray(ad); return a->nextElm(a->m_data, ElmIndEmpty); } ssize_t HphpArray::IterEnd(const ArrayData* ad) { auto a = asHphpArray(ad); return a->prevElm(a->m_data, a->m_used); } ssize_t HphpArray::IterAdvance(const ArrayData* ad, ssize_t pos) { auto a = asHphpArray(ad); // Since m_used is always less than 2^32 and invalid_index == -1, // we can save a check by doing an unsigned comparison instead // of a signed comparison. if (size_t(++pos) < a->m_used && !isTombstone(a->m_data[pos].data.m_type)) { return pos; } return a->iter_advance_helper(pos); static_assert(invalid_index == -1, ""); } // caller has already incremented pos but encountered a tombstone ssize_t HphpArray::iter_advance_helper(ssize_t next_pos) const { Elm* elms = m_data; // Since m_used is always less than 2^32 and invalid_index == -1, // we can save a check by doing an unsigned comparison instead of // a signed comparison. for (auto limit = m_used; size_t(next_pos) < limit; ++next_pos) { if (!isTombstone(elms[next_pos].data.m_type)) { return next_pos; } } return invalid_index; } ssize_t HphpArray::IterRewind(const ArrayData* ad, ssize_t pos) { if (pos == invalid_index) return invalid_index; auto a = asHphpArray(ad); return a->prevElm(a->m_data, pos); } CVarRef HphpArray::GetValueRef(const ArrayData* ad, ssize_t pos) { auto a = asHphpArray(ad); assert(a->checkInvariants()); assert(pos != ArrayData::invalid_index); Elm* e = &a->m_data[pos]; assert(!isTombstone(e->data.m_type)); return tvAsCVarRef(&e->data); } bool HphpArray::IsVectorDataVec(const ArrayData*) { return true; } bool HphpArray::IsVectorData(const ArrayData* ad) { auto a = asGeneric(ad); if (a->m_size == 0) { // any 0-length array is "vector-like" for the sake of this // function, even if m_kind != kVector. return true; } auto const elms = a->m_data; int64_t i = 0; for (uint32_t pos = 0, limit = a->m_used; pos < limit; ++pos) { auto const& e = elms[pos]; if (isTombstone(e.data.m_type)) { continue; } if (e.hasStrKey() || e.ikey != i) { return false; } ++i; } return true; } //============================================================================= // Lookup. #define STRING_HASH(x) (int32_t(x) | 0x80000000) static bool hitStringKey(const HphpArray::Elm& e, const StringData* s, int32_t hash) { // hitStringKey() should only be called on an Elm that is referenced by a // hash table entry. HphpArray guarantees that when it adds a hash table // entry that it always sets it to refer to a valid element. Likewise when // it removes an element it always removes the corresponding hash entry. // Therefore the assertion below must hold. assert(!HphpArray::isTombstone(e.data.m_type)); return hash == e.hash() && (s == e.key || s->same(e.key)); } static bool hitIntKey(const HphpArray::Elm& e, int64_t ki) { // hitIntKey() should only be called on an Elm that is referenced by a // hash table entry. HphpArray guarantees that when it adds a hash table // entry that it always sets it to refer to a valid element. Likewise when // it removes an element it always removes the corresponding hash entry. // Therefore the assertion below must hold. assert(!HphpArray::isTombstone(e.data.m_type)); return e.ikey == ki && e.hasIntKey(); } // Quadratic probe is: // // h(k, i) = (k + c1*i + c2*(i^2)) % tableSize // // Use 1/2 for c1 and c2. In combination with a table size that is a power of // 2, this guarantees a probe sequence of length tableSize that probes all // table elements exactly once. template inline ALWAYS_INLINE HphpArray::ElmInd HphpArray::findBody(size_t h0, Hit hit) const { // tableMask, probeIndex, and pos are explicitly 64-bit, because performance // regressed when they were 32-bit types via auto. Test carefully. size_t tableMask = m_tableMask; size_t probeIndex = h0 & tableMask; auto* elms = m_data; auto* hashtable = m_hash; ssize_t pos = hashtable[probeIndex]; if ((validElmInd(pos) && hit(elms[pos])) || pos == ElmIndEmpty) { return pos; } for (size_t i = 1;; ++i) { assert(i <= tableMask); probeIndex = (probeIndex + i) & tableMask; assert(probeIndex == ((h0 + (i + i*i) / 2) & tableMask)); pos = hashtable[probeIndex]; if ((validElmInd(pos) && hit(elms[pos])) || pos == ElmIndEmpty) { return pos; } } } NEVER_INLINE ssize_t HphpArray::find(int64_t ki) const { // all vector methods should work w/out touching the hashtable assert(!isVector()); if (size_t(ki) < m_used) { // Try to get at it without dirtying a data cache line. auto& e = m_data[ki]; if (!isTombstone(e.data.m_type) && hitIntKey(e, ki)) { Stats::inc(Stats::HA_FindIntFast); // Our results had better match the other path assert(ki == findBody(ki, [ki] (const Elm& e) { return hitIntKey(e, ki); })); return ki; } } Stats::inc(Stats::HA_FindIntSlow); return findBody(ki, [ki] (const Elm& e) { return hitIntKey(e, ki); }); } NEVER_INLINE ssize_t HphpArray::find(const StringData* s, strhash_t prehash) const { // all vector methods should work w/out touching the hashtable assert(!isVector()); int32_t h = STRING_HASH(prehash); return findBody(prehash, [s, h] (const Elm& e) { return hitStringKey(e, s, h); }); } NEVER_INLINE HphpArray::ElmInd* warnUnbalanced(size_t n, HphpArray::ElmInd* ei) { raise_error("Array is too unbalanced (%lu)", n); return ei; } template inline ALWAYS_INLINE HphpArray::ElmInd* HphpArray::findForInsertBody(size_t h0, Hit hit) const { // tableMask, probeIndex, and pos are explicitly 64-bit, because performance // regressed when they were 32-bit types via auto. Test carefully. assert(m_hLoad <= computeMaxElms(m_tableMask)); size_t tableMask = m_tableMask; auto* elms = m_data; auto* hashtable = m_hash; ElmInd* ret = nullptr; size_t probeIndex = h0 & tableMask; auto* ei = &hashtable[probeIndex]; ssize_t pos = *ei; if ((validElmInd(pos) && hit(elms[pos])) || pos == ElmIndEmpty) { return ei; } if (!validElmInd(pos)) ret = ei; for (size_t i = 1;; ++i) { assert(i <= tableMask); probeIndex = (probeIndex + i) & tableMask; assert(probeIndex == ((h0 + (i + i*i) / 2) & tableMask)); ei = &hashtable[probeIndex]; pos = *ei; if (validElmInd(pos)) { if (hit(elms[pos])) { return ei; } } else { if (!ret) ret = ei; if (pos == ElmIndEmpty) { return LIKELY(i <= 100) || LIKELY(i <= size_t(RuntimeOption::MaxArrayChain)) ? ret : warnUnbalanced(i, ret); } } } } NEVER_INLINE HphpArray::ElmInd* HphpArray::findForInsert(int64_t ki) const { // all vector methods should work w/out touching the hashtable assert(!isVector()); return findForInsertBody(ki, [ki] (const Elm& e) { return hitIntKey(e, ki); }); } NEVER_INLINE HphpArray::ElmInd* HphpArray::findForInsert(const StringData* s, strhash_t prehash) const { // all vector methods should work w/out touching the hashtable assert(!isVector()); int32_t h = STRING_HASH(prehash); return findForInsertBody(prehash, [s, h] (const Elm& e) { return hitStringKey(e, s, h); }); } NEVER_INLINE HphpArray::ElmInd* HphpArray::findForNewInsertLoop(size_t tableMask, size_t h0) const { /* Quadratic probe. */ size_t probeIndex = h0 & tableMask; for (size_t i = 1;; ++i) { assert(i <= tableMask); probeIndex = (probeIndex + i) & tableMask; assert(((h0 + ((i + i * i) >> 1)) & tableMask) == probeIndex); ElmInd* ei = &m_hash[probeIndex]; ssize_t pos = ssize_t(*ei); if (!validElmInd(pos)) { return ei; } } } bool HphpArray::ExistsIntVec(const ArrayData* ad, int64_t k) { auto a = asVector(ad); return size_t(k) < a->m_size; } bool HphpArray::ExistsInt(const ArrayData* ad, int64_t k) { auto a = asGeneric(ad); return a->find(k) != ElmIndEmpty; } bool HphpArray::ExistsStrVec(const ArrayData* ad, const StringData* k) { assert(asVector(ad)); return false; } bool HphpArray::ExistsStr(const ArrayData* ad, const StringData* k) { auto a = asGeneric(ad); return a->find(k, k->hash()) != ElmIndEmpty; } //============================================================================= // Append/insert/update. inline ALWAYS_INLINE bool HphpArray::isFull() const { assert(!isVector()); assert(m_used <= m_cap); assert(m_hLoad <= m_cap); return m_used == m_cap || m_hLoad == m_cap; } inline ALWAYS_INLINE HphpArray::Elm* HphpArray::allocElm(ElmInd* ei) { assert(!validElmInd(*ei) && !isFull()); assert(m_size != 0 || m_used == 0); ++m_size; m_hLoad += (*ei == ElmIndEmpty); size_t i = m_used; (*ei) = i; m_used = i + 1; if (m_pos == invalid_index) m_pos = i; return &m_data[i]; } inline ALWAYS_INLINE TypedValue& HphpArray::allocNextElm(uint32_t i) { assert(isVector() && i == m_size); if (i == m_cap) growVec(); auto next = i + 1; if (m_pos == invalid_index) m_pos = i; m_used = m_size = next; return m_data[i].data; } inline ALWAYS_INLINE HphpArray::Elm* HphpArray::newElm(ElmInd* ei, size_t h0) { if (isFull()) return newElmGrow(h0); return allocElm(ei); } NEVER_INLINE HphpArray::Elm* HphpArray::newElmGrow(size_t h0) { resize(); return allocElm(findForNewInsert(h0)); } inline ALWAYS_INLINE HphpArray* HphpArray::initVal(TypedValue& tv, CVarRef v) { tvAsUninitializedVariant(&tv).constructValHelper(v); return this; } inline ALWAYS_INLINE HphpArray* HphpArray::initRef(TypedValue& tv, CVarRef v) { tvAsUninitializedVariant(&tv).constructRefHelper(v); return this; } inline ALWAYS_INLINE HphpArray* HphpArray::getLval(TypedValue& tv, Variant*& ret) { ret = &tvAsVariant(&tv); return this; } inline ALWAYS_INLINE HphpArray* HphpArray::initLval(TypedValue& tv, Variant*& ret) { tvWriteNull(&tv); ret = &tvAsVariant(&tv); return this; } inline ALWAYS_INLINE HphpArray* HphpArray::initWithRef(TypedValue& tv, CVarRef v) { tvWriteNull(&tv); tvAsVariant(&tv).setWithRef(v); return this; } inline ALWAYS_INLINE HphpArray* HphpArray::setVal(TypedValue& tv, CVarRef v) { tvAsVariant(&tv).assignValHelper(v); return this; } inline ALWAYS_INLINE HphpArray* HphpArray::setRef(TypedValue& tv, CVarRef v) { tvAsVariant(&tv).assignRefHelper(v); return this; } /* * This is a streamlined copy of Variant.constructValHelper() * with no incref+decref because we're moving v to this array. */ inline ALWAYS_INLINE HphpArray* HphpArray::moveVal(TypedValue& tv, TypedValue v) { tv.m_type = typeInitNull(v.m_type); tv.m_data.num = v.m_data.num; return this; } HphpArray::ElmInd* HphpArray::allocData(size_t maxElms, size_t tableSize) { m_cap = maxElms; if (maxElms <= SmallSize) { m_data = m_inline_data.slots; return m_inline_data.hash; } size_t hashSize = tableSize * sizeof(ElmInd); size_t dataSize = maxElms * sizeof(Elm); size_t allocSize = hashSize <= sizeof(m_inline_hash) ? dataSize : dataSize + hashSize; m_data = (Elm*) modeAlloc(allocSize); return hashSize <= sizeof(m_inline_hash) ? m_inline_hash : (ElmInd*)(uintptr_t(m_data) + dataSize); } HphpArray::ElmInd* HphpArray::reallocData(size_t maxElms, size_t tableSize) { assert(m_data && m_cap > 0 && maxElms > SmallSize); size_t hashSize = tableSize * sizeof(ElmInd); size_t dataSize = maxElms * sizeof(Elm); size_t allocSize = hashSize <= sizeof(m_inline_hash) ? dataSize : dataSize + hashSize; size_t oldDataSize = m_cap * sizeof(Elm); // slots only. if (m_data == m_inline_data.slots) { m_data = (Elm*) modeAlloc(allocSize); memcpy(m_data, m_inline_data.slots, oldDataSize); } else { m_data = (Elm*) modeRealloc(m_data, allocSize); } m_cap = maxElms; return hashSize <= sizeof(m_inline_hash) ? m_inline_hash : (ElmInd*)(uintptr_t(m_data) + dataSize); } inline ALWAYS_INLINE void HphpArray::resizeIfNeeded() { if (isFull()) resize(); } NEVER_INLINE void HphpArray::resize() { uint32_t maxElms = computeMaxElms(m_tableMask); assert(m_used <= maxElms); assert(m_hLoad <= maxElms); // At a minimum, compaction is required. If the load factor would be >0.5 // even after compaction, grow instead, in order to avoid the possibility // of repeated compaction if the load factor were to hover at nearly 0.75. if (m_size > maxElms / 2) { grow(); } else { compact(false); } } void HphpArray::grow() { assert(!isVector()); assert(m_tableMask <= 0x7fffffffU); m_tableMask = 2 * m_tableMask + 1; auto tableSize = computeTableSize(m_tableMask); auto maxElms = computeMaxElms(m_tableMask); m_hash = reallocData(maxElms, tableSize); // All the elements have been copied and their offsets from the base are // still the same, so we just need to build the new hash table. initHash(tableSize); Elm* elms = m_data; for (uint32_t i = 0, limit = m_used; i < limit; ++i) { auto& e = elms[i]; if (isTombstone(e.data.m_type)) continue; auto* ei = findForNewInsert(e.hasIntKey() ? e.ikey : e.hash()); *ei = i; } m_hLoad = m_size; } NEVER_INLINE void HphpArray::growVec() { assert(isVector()); auto maxElms = m_cap * 2; auto mask = m_tableMask * 2 + 1; m_tableMask = mask; reallocData(maxElms, computeTableSize(mask)); } void HphpArray::compact(bool renumber /* = false */) { assert(!isVector()); ElmKey mPos; if (m_pos != ArrayData::invalid_index) { // Cache key for element associated with m_pos in order to update m_pos // below. assert(size_t(m_pos) < m_used); Elm* e = &(m_data[(ElmInd)m_pos]); mPos.hash = e->hasIntKey() ? 0 : e->hash(); mPos.key = e->key; } else { // Silence compiler warnings. mPos.hash = 0; mPos.key = nullptr; } TinyVector siKeys; for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) { ElmInd ei = r.front()->m_pos; if (ei != ElmIndEmpty) { Elm* e = &m_data[ei]; siKeys.push_back(ElmKey(e->hash(), e->key)); } } if (renumber) { m_nextKI = 0; } Elm* elms = m_data; size_t tableSize = computeTableSize(m_tableMask); initHash(tableSize); for (uint32_t frPos = 0, toPos = 0; toPos < m_size; ++toPos, ++frPos) { while (isTombstone(elms[frPos].data.m_type)) { assert(frPos + 1 < m_used); ++frPos; } Elm& toE = elms[toPos]; if (toPos != frPos) { toE = elms[frPos]; } if (renumber && !toE.hasStrKey()) { toE.ikey = m_nextKI++; } ElmInd* ie = findForNewInsert(toE.hasIntKey() ? toE.ikey : toE.hash()); *ie = toPos; } m_used = m_size; m_hLoad = m_size; if (m_pos != ArrayData::invalid_index) { // Update m_pos, now that compaction is complete. if (mPos.hash) { m_pos = ssize_t(find(mPos.key, mPos.hash)); } else { m_pos = ssize_t(find(mPos.ikey)); } } // Update strong iterators, now that compaction is complete. int key = 0; for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) { FullPos* fp = r.front(); if (fp->m_pos != ArrayData::invalid_index) { ElmKey &k = siKeys[key]; key++; if (k.hash) { // string key fp->m_pos = ssize_t(find(k.key, k.hash)); } else { // int key fp->m_pos = ssize_t(find(k.ikey)); } } } } bool HphpArray::nextInsert(CVarRef data) { if (UNLIKELY(m_nextKI < 0)) { raise_warning("Cannot add element to the array as the next element is " "already occupied"); return false; } resizeIfNeeded(); int64_t ki = m_nextKI; // The check above enforces an invariant that allows us to always // know that m_nextKI is not present in the array, so it is safe // to use findForNewInsert() ElmInd* ei = findForNewInsert(ki); assert(!validElmInd(*ei)); // Allocate and initialize a new element. auto* e = allocElm(ei); e->setIntKey(ki); m_nextKI = ki + 1; // Update next free element. initVal(e->data, data); return true; } ArrayData* HphpArray::nextInsertRef(CVarRef data) { if (UNLIKELY(m_nextKI < 0)) { raise_warning("Cannot add element to the array as the next element is " "already occupied"); return this; } resizeIfNeeded(); int64_t ki = m_nextKI; // The check above enforces an invariant that allows us to always // know that m_nextKI is not present in the array, so it is safe // to use findForNewInsert() ElmInd* ei = findForNewInsert(ki); auto e = allocElm(ei); e->setIntKey(ki); m_nextKI = ki + 1; // Update next free element. return initRef(e->data, data); } ArrayData* HphpArray::nextInsertWithRef(CVarRef data) { resizeIfNeeded(); int64_t ki = m_nextKI; ElmInd* ei = findForInsert(ki); assert(!validElmInd(*ei)); // Allocate a new element. Elm* e = allocElm(ei); e->setIntKey(ki); m_nextKI = ki + 1; // Update next free element. return initWithRef(e->data, data); } ArrayData* HphpArray::addLvalImpl(int64_t ki, Variant*& ret) { assert(!isVector()); ElmInd* ei = findForInsert(ki); if (validElmInd(*ei)) { return getLval(m_data[*ei].data, ret); } Elm* e = newElm(ei, ki); e->setIntKey(ki); if (ki >= m_nextKI && m_nextKI >= 0) m_nextKI = ki + 1; return initLval(e->data, ret); } ArrayData* HphpArray::addLvalImpl(StringData* key, strhash_t h, Variant*& ret) { assert(key && !isVector()); ElmInd* ei = findForInsert(key, h); if (validElmInd(*ei)) { return getLval(m_data[*ei].data, ret); } Elm* e = newElm(ei, h); e->setStrKey(key, h); return initLval(e->data, ret); } inline ArrayData* HphpArray::addVal(int64_t ki, CVarRef data) { assert(!isVector()); resizeIfNeeded(); ElmInd* ei = findForNewInsert(ki); Elm* e = allocElm(ei); e->setIntKey(ki); if (ki >= m_nextKI && m_nextKI >= 0) m_nextKI = ki + 1; return initVal(e->data, data); } inline ArrayData* HphpArray::addVal(StringData* key, CVarRef data) { assert(!exists(key) && !isVector()); resizeIfNeeded(); strhash_t h = key->hash(); ElmInd* ei = findForNewInsert(h); Elm *e = allocElm(ei); e->setStrKey(key, h); return initVal(e->data, data); } inline ArrayData* HphpArray::addValWithRef(int64_t ki, CVarRef data) { resizeIfNeeded(); ElmInd* ei = findForInsert(ki); if (!validElmInd(*ei)) { Elm* e = allocElm(ei); e->setIntKey(ki); if (ki >= m_nextKI) m_nextKI = ki + 1; initWithRef(e->data, data); } return this; } inline ArrayData* HphpArray::addValWithRef(StringData* key, CVarRef data) { resizeIfNeeded(); strhash_t h = key->hash(); ElmInd* ei = findForInsert(key, h); if (!validElmInd(*ei)) { Elm* e = allocElm(ei); e->setStrKey(key, h); initWithRef(e->data, data); } return this; } inline INLINE_SINGLE_CALLER ArrayData* HphpArray::update(int64_t ki, CVarRef data) { ElmInd* ei = findForInsert(ki); if (validElmInd(*ei)) { return setVal(m_data[*ei].data, data); } auto e = newElm(ei, ki); e->setIntKey(ki); if (ki >= m_nextKI && m_nextKI >= 0) m_nextKI = ki + 1; return initVal(e->data, data); } inline INLINE_SINGLE_CALLER ArrayData* HphpArray::update(StringData* key, CVarRef data) { strhash_t h = key->hash(); ElmInd* ei = findForInsert(key, h); if (validElmInd(*ei)) { return setVal(m_data[*ei].data, data); } auto e = newElm(ei, h); e->setStrKey(key, h); return initVal(e->data, data); } ArrayData* HphpArray::updateRef(int64_t ki, CVarRef data) { assert(!isVector()); ElmInd* ei = findForInsert(ki); if (validElmInd(*ei)) { return setRef(m_data[*ei].data, data); } auto e = newElm(ei, ki); e->setIntKey(ki); if (ki >= m_nextKI && m_nextKI >= 0) m_nextKI = ki + 1; return initRef(e->data, data); } ArrayData* HphpArray::updateRef(StringData* key, CVarRef data) { assert(!isVector()); strhash_t h = key->hash(); ElmInd* ei = findForInsert(key, h); if (validElmInd(*ei)) { return setRef(m_data[*ei].data, data); } auto e = newElm(ei, h); e->setStrKey(key, h); return initRef(e->data, data); } // return true if Elm contains a Reference that won't be flattened // by a copy, or an object. static inline bool isContainer(const TypedValue& tv) { auto& v = tvAsCVarRef(&tv); return v.isReferenced() || v.isObject(); } ArrayData* HphpArray::LvalIntVec(ArrayData* ad, int64_t k, Variant*& ret, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); if (size_t(k) < a->m_size) { return a->getLval(a->m_data[k].data, ret); } if (size_t(k) == a->m_size) { auto& tv = a->allocNextElm(k); return a->initLval(tv, ret); } // todo t2606310: we know key is new. use add/findForNewInsert return a->vectorToGeneric()->addLvalImpl(k, ret); } ArrayData* HphpArray::LvalInt(ArrayData* ad, int64_t k, Variant*& ret, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->addLvalImpl(k, ret); } ArrayData* HphpArray::LvalStrVec(ArrayData* ad, StringData* key, Variant*& ret, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); return a->vectorToGeneric()->addLvalImpl(key, key->hash(), ret); } ArrayData* HphpArray::LvalStr(ArrayData* ad, StringData* key, Variant*& ret, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->addLvalImpl(key, key->hash(), ret); } ArrayData* HphpArray::LvalNewVec(ArrayData* ad, Variant*& ret, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); auto& tv = a->allocNextElm(a->m_size); tvWriteUninit(&tv); ret = &tvAsVariant(&tv); return a; } ArrayData* HphpArray::LvalNew(ArrayData* ad, Variant*& ret, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); if (UNLIKELY(!a->nextInsert(uninit_null()))) { ret = &Variant::lvalBlackHole(); return a; } ret = &tvAsVariant(&a->m_data[a->m_used - 1].data); return a; } ArrayData* HphpArray::SetIntVec(ArrayData* ad, int64_t k, CVarRef v, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); if (size_t(k) < a->m_size) { return a->setVal(a->m_data[k].data, v); } if (size_t(k) == a->m_size) { auto& tv = a->allocNextElm(k); return a->initVal(tv, v); } // must escalate, but call addVal() since key doesn't exist. return a->vectorToGeneric()->addVal(k, v); } ArrayData* HphpArray::SetInt(ArrayData* ad, int64_t k, CVarRef v, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->update(k, v); } ArrayData* HphpArray::SetStrVec(ArrayData* ad, StringData* k, CVarRef v, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); // must escalate, but call addVal() since key doesn't exist. return a->vectorToGeneric()->addVal(k, v); } ArrayData* HphpArray::SetStr(ArrayData* ad, StringData* k, CVarRef v, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->update(k, v); } ArrayData* HphpArray::SetRefIntVec(ArrayData* ad, int64_t k, CVarRef v, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); if (size_t(k) < a->m_size) { return a->setRef(a->m_data[k].data, v); } if (size_t(k) == a->m_size) { auto& tv = a->allocNextElm(k); return a->initRef(tv, v); } // todo t2606310: key can't exist. use add/findForNewInsert return a->vectorToGeneric()->updateRef(k, v); } ArrayData* HphpArray::SetRefInt(ArrayData* ad, int64_t k, CVarRef v, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->updateRef(k, v); } ArrayData* HphpArray::SetRefStrVec(ArrayData* ad, StringData* k, CVarRef v, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); // todo t2606310: key can't exist. use add/findForNewInsert return a->vectorToGeneric()->updateRef(k, v); } ArrayData* HphpArray::SetRefStr(ArrayData* ad, StringData* k, CVarRef v, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->updateRef(k, v); } ArrayData* HphpArray::AddIntVec(ArrayData* ad, int64_t k, CVarRef v, bool copy) { assert(!ad->exists(k)); auto a = asVector(ad); if (copy) a = a->copyVec(); if (size_t(k) == a->m_size) { auto& tv = a->allocNextElm(k); return a->initVal(tv, v); } return a->vectorToGeneric()->addVal(k, v); } ArrayData* HphpArray::AddInt(ArrayData* ad, int64_t k, CVarRef v, bool copy) { assert(!ad->exists(k)); auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->addVal(k, v); } ArrayData* HphpArray::AddStr(ArrayData* ad, StringData* k, CVarRef v, bool copy) { assert(!ad->exists(k)); auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->addVal(k, v); } ArrayData* HphpArray::AddLvalIntVec(ArrayData* ad, int64_t k, Variant*& ret, bool copy) { assert(!ad->exists(k)); auto a = asVector(ad); if (copy) a = a->copyVec(); if (size_t(k) < a->m_size) { ret = &tvAsVariant(&a->m_data[k].data); return a; } if (size_t(k) == a->m_size) { auto& tv = a->allocNextElm(k); return a->initLval(tv, ret); } return a->vectorToGeneric()->addLvalImpl(k, ret); } ArrayData* HphpArray::AddLvalInt(ArrayData* ad, int64_t k, Variant*& ret, bool copy) { assert(!ad->exists(k)); auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->addLvalImpl(k, ret); } ArrayData* HphpArray::AddLvalStr(ArrayData* ad, StringData* k, Variant*& ret, bool copy) { assert(!ad->exists(k)); auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->addLvalImpl(k, k->hash(), ret); } //============================================================================= // Delete. NEVER_INLINE void HphpArray::adjustFullPos(ElmInd pos) { ElmInd eIPrev = ElmIndTombstone; for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) { FullPos* fp = r.front(); if (fp->m_pos == ssize_t(pos)) { if (eIPrev == ElmIndTombstone) { // eIPrev will actually be used, so properly initialize it with the // previous element before pos, or ElmIndEmpty if pos is the first // element. eIPrev = prevElm(m_data, pos); } if (eIPrev == ElmIndEmpty) { fp->setResetFlag(true); } fp->m_pos = ssize_t(eIPrev); } } } ArrayData* HphpArray::erase(ElmInd* ei, bool updateNext) { ElmInd pos = *ei; if (!validElmInd(pos)) { return this; } // move strong iterators to the previous element if (strongIterators()) adjustFullPos(pos); // If the internal pointer points to this element, advance it. Elm* elms = m_data; if (m_pos == ssize_t(pos)) { ElmInd eINext = nextElm(elms, pos); m_pos = ssize_t(eINext); } Elm* e = &elms[pos]; // Mark the value as a tombstone. TypedValue* tv = &e->data; DataType oldType = tv->m_type; uint64_t oldDatum = tv->m_data.num; tv->m_type = KindOfInvalid; // Free the key if necessary, and clear the h and key fields in order to // increase the chances that subsequent searches will quickly/safely fail // when encountering tombstones, even though checking for KindOfInvalid is // the last validation step during search. if (e->hasStrKey()) { decRefStr(e->key); e->setIntKey(0); } else { // Match PHP 5.3.1 semantics // Hacky: don't removed the unsigned cast, else g++ can optimize away // the check for == 0x7fff..., since there is no signed int k // for which k-1 == 0x7fff... if ((uint64_t)e->ikey == (uint64_t)m_nextKI-1 && (e->ikey == 0x7fffffffffffffffLL || updateNext)) { --m_nextKI; } } --m_size; // If this element was last, adjust m_used. if (size_t(pos + 1) == m_used) { do { --m_used; } while (m_used > 0 && isTombstone(elms[m_used - 1].data.m_type)); } // Mark the hash entry as "deleted". *ei = ElmIndTombstone; assert(m_used <= m_cap); assert(m_hLoad <= m_cap); // Finally, decref the old value tvRefcountedDecRefHelper(oldType, oldDatum); if (m_size < m_used / 2) { // Compact in order to keep elms from being overly sparse. compact(false); } return this; } ArrayData* HphpArray::RemoveIntVec(ArrayData* ad, int64_t k, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); // todo t2606310: what is probability of (k == size-1) if (size_t(k) < a->m_size) { a->vectorToGeneric(); return a->erase(a->findForInsert(k), false); } return a; // key didn't exist, so we're still vector } ArrayData* HphpArray::RemoveInt(ArrayData* ad, int64_t k, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->erase(a->findForInsert(k), false); } ArrayData* HphpArray::RemoveStrVec(ArrayData* ad, const StringData* key, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); return a; } ArrayData* HphpArray::RemoveStr(ArrayData* ad, const StringData* key, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->erase(a->findForInsert(key, key->hash()), false); } ArrayData* HphpArray::CopyVec(const ArrayData* ad) { return asVector(ad)->copyVec(); } ArrayData* HphpArray::Copy(const ArrayData* ad) { return asGeneric(ad)->copyGeneric(); } ArrayData* HphpArray::CopyWithStrongIterators(const ArrayData* ad) { auto a = asHphpArray(ad); auto copied = a->copyImpl(); moveStrongIterators(copied, const_cast(a)); return copied; } //============================================================================= // non-variant interface TypedValue* HphpArray::NvGetIntVec(const ArrayData* ad, int64_t ki) { auto a = asVector(ad); return LIKELY(size_t(ki) < a->m_size) ? &a->m_data[ki].data : nullptr; } TypedValue* HphpArray::NvGetInt(const ArrayData* ad, int64_t ki) { auto a = asGeneric(ad); auto i = a->find(ki); return LIKELY(i != ElmIndEmpty) ? &a->m_data[i].data : nullptr; } TypedValue* HphpArray::NvGetStrVec(const ArrayData* ad, const StringData* k) { assert(asVector(ad)); return nullptr; } TypedValue* HphpArray::NvGetStr(const ArrayData* ad, const StringData* k) { auto a = asGeneric(ad); auto i = a->find(k, k->hash()); if (LIKELY(i != ElmIndEmpty)) { return &a->m_data[i].data; } return nullptr; } // nvGetKey does not touch out->_count, so can be used // for inner or outer cells. void HphpArray::NvGetKeyVec(const ArrayData* ad, TypedValue* out, ssize_t pos) { DEBUG_ONLY auto a = asVector(ad); assert(pos != ArrayData::invalid_index); assert(!isTombstone(a->m_data[pos].data.m_type)); out->m_data.num = pos; out->m_type = KindOfInt64; } void HphpArray::NvGetKey(const ArrayData* ad, TypedValue* out, ssize_t pos) { auto a = asGeneric(ad); assert(pos != ArrayData::invalid_index); assert(!isTombstone(a->m_data[pos].data.m_type)); getElmKey(a->m_data[pos], out); } /* * Insert a new element with index k in to the array, * doing nothing and returning false if the element * already exists. */ bool HphpArray::nvInsert(StringData *k, TypedValue *data) { assert(checkInvariants()); if (isVector()) { vectorToGeneric(); // todo t2606310: we know key doesn't exist. } strhash_t h = k->hash(); ElmInd* ei = findForInsert(k, h); if (validElmInd(*ei)) { return false; } auto e = newElm(ei, h); e->setStrKey(k, h); initVal(e->data, tvAsVariant(data)); return true; } HphpArray* HphpArray::nextInsertVec(CVarRef v) { assert(isVector()); auto& tv = allocNextElm(m_size); return initVal(tv, v); } ArrayData* HphpArray::AppendVec(ArrayData* ad, CVarRef v, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); return a->nextInsertVec(v); } ArrayData* HphpArray::Append(ArrayData* ad, CVarRef v, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); a->nextInsert(v); return a; } /* * Cold path helper for AddNewElemC delegates to the ArrayData::append * virtual method. */ static NEVER_INLINE ArrayData* genericAddNewElemC(ArrayData* a, TypedValue value) { ArrayData* r = a->append(tvAsCVarRef(&value), a->getCount() != 1); if (UNLIKELY(r != a)) { r->incRefCount(); decRefArr(a); } tvRefcountedDecRef(value); return r; } /* * The pass-by-value and move semantics of this helper are slightly different * than other array helpers, but tuned for the opcode. See doc comment in * hphp_array.h. */ ArrayData* HphpArray::AddNewElemC(ArrayData* ad, TypedValue value) { assert(value.m_type != KindOfRef); HphpArray* a; int64_t k; if (LIKELY(ad->isVector()) && ((a = asVector(ad)), LIKELY(a->m_pos >= 0)) && LIKELY(a->getCount() <= 1) && ((k = a->m_size), LIKELY(size_t(k) < a->m_cap))) { assert(a->checkInvariants()); auto& tv = a->allocNextElm(k); return a->moveVal(tv, value); } return genericAddNewElemC(ad, value); } ArrayData* HphpArray::AppendRefVec(ArrayData* ad, CVarRef v, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); auto &tv = a->allocNextElm(a->m_size); return a->initRef(tv, v); } ArrayData* HphpArray::AppendRef(ArrayData* ad, CVarRef v, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->nextInsertRef(v); } ArrayData *HphpArray::AppendWithRefVec(ArrayData* ad, CVarRef v, bool copy) { auto a = asVector(ad); if (copy) a = a->copyVec(); auto& tv = a->allocNextElm(a->m_size); return a->initWithRef(tv, v); } ArrayData *HphpArray::AppendWithRef(ArrayData* ad, CVarRef v, bool copy) { auto a = asGeneric(ad); if (copy) a = a->copyGeneric(); return a->nextInsertWithRef(v); } ArrayData* HphpArray::Plus(ArrayData* ad, const ArrayData* elems, bool copy) { auto a = asHphpArray(ad); if (copy) a = a->copyImpl(); if (a->isVector()) { // todo t2606310: is there a fast path if elems is also a vector? a->vectorToGeneric(); } for (ArrayIter it(elems); !it.end(); it.next()) { Variant key = it.first(); CVarRef value = it.secondRef(); if (key.asTypedValue()->m_type == KindOfInt64) { a->addValWithRef(key.toInt64(), value); } else { a->addValWithRef(key.getStringData(), value); } } return a; } ArrayData* HphpArray::Merge(ArrayData* ad, const ArrayData* elems, bool copy) { auto a = asHphpArray(ad); if (copy) a = a->copyImpl(); if (a->isVector()) { // todo t2606310: is there a fast path if elems is also a vector? a->vectorToGeneric(); } for (ArrayIter it(elems); !it.end(); it.next()) { Variant key = it.first(); CVarRef value = it.secondRef(); if (key.asTypedValue()->m_type == KindOfInt64) { a->nextInsertWithRef(value); } else { Variant *p; StringData *sd = key.getStringData(); a->addLvalImpl(sd, sd->hash(), p); p->setWithRef(value); } } return a; } ArrayData* HphpArray::PopVec(ArrayData* ad, Variant& value) { auto a = asVector(ad); if (a->getCount() > 1) a = a->copyVec(); if (a->m_size > 0) { auto i = a->m_size - 1; auto& tv = a->m_data[i].data; value = tvAsCVarRef(&tv); if (a->strongIterators()) a->adjustFullPos(i); auto oldType = tv.m_type; auto oldDatum = tv.m_data.num; a->m_size = a->m_used = i; a->m_pos = a->m_size > 0 ? 0 : invalid_index; // reset internal iterator tvRefcountedDecRefHelper(oldType, oldDatum); return a; } value = uninit_null(); a->m_pos = invalid_index; // reset internal iterator return a; } ArrayData* HphpArray::Pop(ArrayData* ad, Variant& value) { auto a = asGeneric(ad); if (a->getCount() > 1) a = a->copyGeneric(); Elm* elms = a->m_data; ElmInd pos = IterEnd(a); if (validElmInd(pos)) { Elm* e = &elms[pos]; assert(!isTombstone(e->data.m_type)); value = tvAsCVarRef(&e->data); ElmInd* ei = e->hasStrKey() ? a->findForInsert(e->key, e->hash()) : a->findForInsert(e->ikey); a->erase(ei, true); } else { value = uninit_null(); } // To conform to PHP behavior, the pop operation resets the array's // internal iterator. a->m_pos = a->nextElm(elms, ElmIndEmpty); return a; } ArrayData* HphpArray::Dequeue(ArrayData* ad, Variant& value) { auto a = asHphpArray(ad); if (a->getCount() > 1) a = a->copyImpl(); // To conform to PHP behavior, we invalidate all strong iterators when an // element is removed from the beginning of the array. a->freeStrongIterators(); Elm* elms = a->m_data; ElmInd pos = a->nextElm(elms, ElmIndEmpty); if (validElmInd(pos)) { Elm* e = &elms[pos]; value = tvAsCVarRef(&e->data); if (a->isVector()) { if (a->m_size == 1) { assert(pos == 0); a->m_size = a->m_used = 0; a->m_pos = invalid_index; tvRefcountedDecRef(&e->data); return a; } a->vectorToGeneric(); } ElmInd* ei = e->hasStrKey() ? a->findForInsert(e->key, e->hash()) : a->findForInsert(e->ikey); a->erase(ei, false); a->compact(true); } else { value = uninit_null(); } // To conform to PHP behavior, the dequeue operation resets the array's // internal iterator a->m_pos = ssize_t(a->nextElm(elms, ElmIndEmpty)); return a; } ArrayData* HphpArray::Prepend(ArrayData* ad, CVarRef v, bool copy) { auto a = asHphpArray(ad); if (a->getCount() > 1) a = a->copyImpl(); if (a->isVector()) { // todo t2606310: fast path - same as add for empty vectors a->vectorToGeneric(); } // To conform to PHP behavior, we invalidate all strong iterators when an // element is added to the beginning of the array. a->freeStrongIterators(); Elm* elms = a->m_data; if (a->m_used == 0 || !isTombstone(elms[0].data.m_type)) { // Make sure there is room to insert an element. a->resizeIfNeeded(); // Reload elms, in case resizeIfNeeded() had side effects. elms = a->m_data; // Move the existing elements to make element 0 available. memmove(&elms[1], &elms[0], a->m_used * sizeof(Elm)); ++a->m_used; } // Prepend. ++a->m_size; Elm* e = &elms[0]; e->setIntKey(0); a->initVal(e->data, v); // Renumber. a->compact(true); // To conform to PHP behavior, the prepend operation resets the array's // internal iterator a->m_pos = ssize_t(a->nextElm(elms, ElmIndEmpty)); return a; } void HphpArray::RenumberVec(ArrayData* ad) { assert(asVector(ad)); // for the checkInvariants() call // renumber has no effect on Vector and doesn't move internal pos } void HphpArray::Renumber(ArrayData* ad) { asGeneric(ad)->compact(true); } void HphpArray::OnSetEvalScalarVec(ArrayData* ad) { auto a = asVector(ad); Elm* elms = a->m_data; for (uint32_t i = 0, limit = a->m_size; i < limit; ++i) { tvAsVariant(&elms[i].data).setEvalScalar(); } } void HphpArray::OnSetEvalScalar(ArrayData* ad) { auto a = asGeneric(ad); Elm* elms = a->m_data; for (uint32_t i = 0, limit = a->m_used; i < limit; ++i) { Elm* e = &elms[i]; if (!isTombstone(e->data.m_type)) { StringData *key = e->key; if (e->hasStrKey() && !key->isStatic()) { e->key = StringData::GetStaticString(key); decRefStr(key); } tvAsVariant(&e->data).setEvalScalar(); } } } bool HphpArray::ValidFullPos(const ArrayData* ad, const FullPos &fp) { assert(fp.getContainer() == asHphpArray(ad)); if (fp.getResetFlag()) return false; return (fp.m_pos != ssize_t(ElmIndEmpty)); } bool HphpArray::AdvanceFullPos(ArrayData* ad, FullPos& fp) { auto a = asHphpArray(ad); Elm* elms = a->m_data; if (fp.getResetFlag()) { fp.setResetFlag(false); fp.m_pos = ElmIndEmpty; } else if (fp.m_pos == ssize_t(ElmIndEmpty)) { return false; } fp.m_pos = a->nextElm(elms, fp.m_pos); if (fp.m_pos == ssize_t(ElmIndEmpty)) { return false; } // To conform to PHP behavior, we need to set the internal // cursor to point to the next element. a->m_pos = a->nextElm(elms, fp.m_pos); return true; } //============================================================================= NEVER_INLINE ArrayData* HphpArray::NonSmartCopy(const ArrayData* ad) { auto a = asHphpArray(ad); return a->isVector() ? new HphpArray(*a, AllocationMode::nonSmart, CopyVector()) : new HphpArray(*a, AllocationMode::nonSmart, CopyGeneric()); } NEVER_INLINE HphpArray* HphpArray::copyVec() const { assert(checkInvariants()); return new (HphpArray::AllocatorType::getNoCheck()->alloc(sizeof(*this))) HphpArray(*this, AllocationMode::smart, CopyVector()); } NEVER_INLINE HphpArray* HphpArray::copyGeneric() const { assert(checkInvariants()); return new (HphpArray::AllocatorType::getNoCheck()->alloc(sizeof(*this))) HphpArray(*this, AllocationMode::smart, CopyGeneric()); } //============================================================================= HOT_FUNC_VM HphpArray* ArrayData::MakeTuple(uint size, const TypedValue* data) { auto a = NEW(HphpArray)(size, data); a->setRefCount(1); TRACE(2, "MakeTuple: size %d\n", size); return a; } HOT_FUNC_VM HphpArray* ArrayData::MakeReserve(uint capacity) { auto a = NEW(HphpArray)(capacity); a->setRefCount(1); TRACE(2, "MakeReserve: capacity %d\n", capacity); return a; } /////////////////////////////////////////////////////////////////////////////// }