hphp/runtime/base/array-data.cpp (545 lines of code) (raw):

/* +----------------------------------------------------------------------+ | 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. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/base/array-data.h" #include <vector> #include <array> #include <tbb/concurrent_unordered_set.h> #include "hphp/runtime/base/array-common.h" #include "hphp/runtime/base/array-init.h" #include "hphp/runtime/base/array-iterator.h" #include "hphp/runtime/base/bespoke-array.h" #include "hphp/runtime/base/builtin-functions.h" #include "hphp/runtime/base/comparisons.h" #include "hphp/runtime/base/memory-manager-defs.h" #include "hphp/runtime/base/memory-manager.h" #include "hphp/runtime/base/request-info.h" #include "hphp/runtime/base/runtime-option.h" #include "hphp/runtime/base/vanilla-dict.h" #include "hphp/runtime/base/vanilla-keyset.h" #include "hphp/runtime/base/vanilla-vec.h" #include "hphp/runtime/base/variable-serializer.h" #include "hphp/runtime/server/memory-stats.h" #include "hphp/runtime/vm/interp-helpers.h" #include "hphp/runtime/vm/reverse-data-map.h" #include "hphp/util/exception.h" #include "hphp/zend/zend-string.h" namespace HPHP { /////////////////////////////////////////////////////////////////////////////// const StaticString s_InvalidKeysetOperationMsg{"Invalid operation on keyset"}, s_VarrayUnsetMsg{"varrays do not support unsetting non-end elements"}, s_VecUnsetMsg{"Vecs do not support unsetting non-end elements"}; /////////////////////////////////////////////////////////////////////////////// __thread std::pair<const ArrayData*, size_t> s_cachedHash; namespace { static_assert( sizeof(ArrayData) == 16, "Performance is sensitive to sizeof(ArrayData)." " Make sure you changed it with good reason and then update this assert."); size_t hashArrayPortion(const ArrayData* arr) { assertx(arr->isVanilla()); auto hash = folly::hash::hash_128_to_64(arr->kind(), arr->isLegacyArray()); IterateKV( arr, [&](TypedValue k, TypedValue v) { assertx(tvIsString(k) || tvIsInt(k)); assertx(IMPLIES(tvIsString(k), val(k).pstr->isStatic())); hash = folly::hash::hash_combine( hash, dt_with_persistence(k.m_type), k.m_data.num, v.m_type ); switch (v.m_type) { case KindOfNull: break; case KindOfBoolean: case KindOfInt64: case KindOfDouble: case KindOfPersistentString: case KindOfPersistentVec: case KindOfPersistentDict: case KindOfPersistentKeyset: hash = folly::hash::hash_combine(hash, v.m_data.num); break; case KindOfLazyClass: assertx(v.m_data.plazyclass.name()->isStatic()); hash = folly::hash::hash_combine( hash, (uintptr_t)v.m_data.plazyclass.name() ); break; case KindOfClass: assertx(v.m_data.pclass->isPersistent()); hash = folly::hash::hash_combine(hash, (uintptr_t)v.m_data.pclass); break; case KindOfFunc: assertx(v.m_data.pfunc->isPersistent()); hash = folly::hash::hash_combine(hash, (uintptr_t)v.m_data.pfunc); break; case KindOfClsMeth: assertx(v.m_data.pclsmeth->isPersistent()); hash = folly::hash::hash_combine( hash, (uintptr_t)v.m_data.pclsmeth->getCls()); hash = folly::hash::hash_combine( hash, (uintptr_t)v.m_data.pclsmeth->getFunc()); break; case KindOfUninit: case KindOfString: case KindOfVec: case KindOfDict: case KindOfKeyset: case KindOfObject: case KindOfResource: case KindOfRFunc: case KindOfRClsMeth: always_assert(false); } } ); return hash; } bool compareArrayPortion(const ArrayData* ad1, const ArrayData* ad2) { assertx(ad1->isVanilla()); assertx(ad2->isVanilla()); if (ad1 == ad2) return true; if (ad1->kind() != ad2->kind()) return false; if (ad1->size() != ad2->size()) return false; if (ad1->isLegacyArray() != ad2->isLegacyArray()) return false; auto const check = [] (const TypedValue& tv1, const TypedValue& tv2) { if (tv1.m_type != tv2.m_type) { // String keys from arrays might be KindOfString, even when // the StringData is static. if (!isStringType(tv1.m_type) || !isStringType(tv2.m_type)) { return false; } assertx(tv1.m_data.pstr->isStatic()); assertx(tv2.m_data.pstr->isStatic()); } if (isNullType(tv1.m_type)) return true; return tv1.m_data.num == tv2.m_data.num; }; auto equal = true; ArrayIter iter2{ad2}; IterateKV( ad1, [&](TypedValue k, TypedValue v) { if (!check(k, iter2.nvFirst()) || !check(v, iter2.secondVal())) { equal = false; return true; } ++iter2; return false; } ); return equal; } struct ScalarHash { size_t operator()(ArrayData* arr) const { return s_cachedHash.first == arr ? s_cachedHash.second : hashArrayPortion(arr); } bool operator()(ArrayData* arr1, ArrayData* arr2) const { return compareArrayPortion(arr1, arr2); } }; using ArrayDataMap = tbb::concurrent_unordered_set<ArrayData*, ScalarHash, ScalarHash>; ArrayDataMap s_arrayDataMap; } size_t loadedStaticArrayCount() { return s_arrayDataMap.size(); } /////////////////////////////////////////////////////////////////////////////// void ArrayData::GetScalarArray(ArrayData** parr) { auto const base = *parr; if (base->isStatic()) return; auto const arr = [&]{ if (base->isVanilla()) return base; *parr = BespokeArray::ToVanilla(base, "ArrayData::GetScalarArray"); decRefArr(base); return *parr; }(); auto const replace = [&] (ArrayData* rep) { *parr = rep; decRefArr(arr); s_cachedHash.first = nullptr; }; if (arr->empty()) { return replace([&]{ auto const legacy = arr->isLegacyArray(); switch (arr->toDataType()) { case KindOfVec: return ArrayData::CreateVec(legacy); case KindOfDict: return ArrayData::CreateDict(legacy); case KindOfKeyset: return ArrayData::CreateKeyset(); default: always_assert(false); } }()); } arr->onSetEvalScalar(); s_cachedHash.first = arr; s_cachedHash.second = hashArrayPortion(arr); auto const lookup = [&] (ArrayData* a) -> ArrayData* { if (auto const it = s_arrayDataMap.find(a); it != s_arrayDataMap.end()) { return *it; } return nullptr; }; if (auto const a = lookup(arr)) return replace(a); static std::array<std::mutex, 128> s_mutexes; std::lock_guard<std::mutex> g{ s_mutexes[s_cachedHash.second % s_mutexes.size()] }; if (auto const a = lookup(arr)) return replace(a); // We should clear the sampled bit in the new static array regardless of // whether the input array was sampled, because specializing the input is // not sufficient to specialize this new static array. auto const ad = arr->copyStatic(); ad->m_aux16 &= ~kSampledArray; assertx(ad->isStatic()); // TODO(T68458896): allocSize rounds up to size class, which we shouldn't do. MemoryStats::LogAlloc(AllocKind::StaticArray, allocSize(ad)); if (RuntimeOption::EvalEnableReverseDataMap) { data_map::register_start(ad); } s_cachedHash.first = ad; assertx(hashArrayPortion(ad) == s_cachedHash.second); auto const DEBUG_ONLY inserted = s_arrayDataMap.insert(ad).second; assertx(inserted); return replace(ad); } ArrayData* ArrayData::GetScalarArray(Array&& arr) { auto a = arr.detach(); GetScalarArray(&a); return a; } ArrayData* ArrayData::GetScalarArray(Variant&& arr) { assertx(arr.isArray()); auto a = arr.detach().m_data.parr; GetScalarArray(&a); return a; } ////////////////////////////////////////////////////////////////////// static_assert(ArrayFunctions::NK == ArrayData::ArrayKind::kNumKinds, "add new kinds here"); #define DISPATCH(entry) \ { VanillaVec::entry, /* vanilla vec */ \ BespokeArray::entry, /* bespoke vec */ \ VanillaDict::entry, /* vanilla dict */ \ BespokeArray::entry, /* bespoke dict */ \ VanillaKeyset::entry, /* vanilla keyset */ \ BespokeArray::entry, /* bespoke keyset */ \ }, /* * "Copy/grow" semantics: * * Many of the functions that mutate arrays return an ArrayData* and * take a boolean parameter called 'copy'. The semantics of these * functions is the following: * * If the `copy' argument is false, a COW is not required for * language semantics. The array may mutate itself in place and * return the same pointer, or the array may allocate a new array * and return that. This is called "growing", since it may be used * if an array is out of capacity for new elements---but it is also * what happens if an array needs to change kinds and can't do that * in-place. If an array grows, the old array pointer may not be * used for any more array operations except to eventually call * Release---these grown-from arrays are sometimes described as * being in "zombie" state. * * If the `copy' argument is true, the returned array must be * indistinguishable from an array that did a COW, in terms of the * contents of the array. Whether it is the same pointer value or * not is not specified. Note, for example, that this means an * array doesn't have to copy if it was asked to remove an element * that doesn't exist. * * When a function with these semantics returns a new array, the new array is * already incref'd. In a few cases, an existing array (different than the * source array) may be returned. In this case, the array will already be * incref'd. */ const ArrayFunctions g_array_funcs = { /* * void Release(ArrayData*) * * Free the memory associated with a refcounted array. Generally called * when the reference count on the array drops to zero. */ DISPATCH(Release) /* * void ReleaseUncounted(ArrayData*) * * Free the memory associated with an uncounted array. Generally called * when the reference count on the array drops to "uncounted zero". */ DISPATCH(ReleaseUncounted) /* * TypedValue NvGetInt(const ArrayData*, int64_t key) * TypedValue NvGetStr(const ArrayData*, const StringData*) * * Lookup a value in an array using an int or string key. Returns Uninit if * the key is not in the array. This method does not inc-ref the result. */ DISPATCH(NvGetInt) DISPATCH(NvGetStr) /* * TypedValue GetPosKey(const ArrayData*, ssize_t pos) * TypedValue GetPosVal(const ArrayData*, ssize_t pos) * * Look up the key or value at a valid iterator position in this array. * Both of these methods return the result without inc-ref-ing it. */ DISPATCH(GetPosKey) DISPATCH(GetPosVal) /* * bool PosIsValid(const ArrayData*, ssize_t pos) * * Return true if the given iterator position is valid in this * array. */ DISPATCH(PosIsValid) /* * ArrayData* SetIntMove(ArrayData*, int64_t key, TypedValue v) * * Set a value in the array for an integer key. If copy / escalation is * necessary, this operation will dec-ref the array and return a new one; * else, it'll return the original array. It does not inc-ref the value. */ DISPATCH(SetIntMove) /* * ArrayData* SetStrMove(ArrayData*, StringData*, TypedValue v) * * Set a value in the array for a string key. If copy / escalation is * necessary, this operation will dec-ref the array and return a new one; * else, it'll return the original array. It does not inc-ref the value. * * SetStrMove *does* inc-ref the key if it's newly added to the array. */ DISPATCH(SetStrMove) /* * bool IsVectorData(const ArrayData*) * * Return true if this array is empty, or if it has only contiguous integer * keys and the first key is zero. Determining this may be an O(N) * operation. */ DISPATCH(IsVectorData) /* * bool ExistsInt(const ArrayData*, int64_t key) * * Return true iff this array contains an element with the supplied integer * key. */ DISPATCH(ExistsInt) /* * bool ExistsStr(const ArrayData*, const StringData*) * * Return true iff this array contains an element with the supplied string * key. The string will not undergo intish-key cast. */ DISPATCH(ExistsStr) /* * arr_lval LvalInt(ArrayData*, int64_t k) * arr_lval LvalStr(ArrayData*, StringData* key) * * Look up a value in the array by the supplied key, throwing if it doesn't * exist, and return a reference to it. This function has copy/grow * semantics. */ DISPATCH(LvalInt) DISPATCH(LvalStr) /* * ArrayData* RemoveIntMove(ArrayData*, int64_t key) * * Remove an array element with an integer key, copying or escalating * as necessary. If there was no entry for that element, this function * will not remove it, but it may still copy the array in that case. If * a copy or escalation was required, there will be a dec-ref of the old * array. */ DISPATCH(RemoveIntMove) /* * ArrayData* RemoveStr(ArrayData*, const StringData*) * * Remove an array element with a string key, copying or escalating * as necessary. If there was no entry for that element, this function * will not remove it, but it may still copy the array in that case. If * a copy or escalation was required, there will be a dec-ref of the old * array. */ DISPATCH(RemoveStrMove) /* * ssize_t IterEnd(const ArrayData*) * * Returns the canonical invalid position for this array. Note * that if elements are added or removed from the array, the value * of the array's canonical invalid position may change. * * ssize_t IterBegin(const ArrayData*) * * Returns the position of the first element, or the canonical * invalid position if this array is empty. * * ssize_t IterLast(const ArrayData*) * * Returns the position of the last element, or the canonical * invalid position if this array is empty. */ DISPATCH(IterBegin) DISPATCH(IterLast) DISPATCH(IterEnd) /* * ssize_t IterAdvance(const ArrayData*, size_t pos) * * Returns the position of the element that comes after pos, or the * canonical invalid position if there are no more elements after pos. * If pos is the canonical invalid position, this method will return * the canonical invalid position. */ DISPATCH(IterAdvance) /* * ssize_t IterRewind(const ArrayData*, size_t pos) * * Returns the position of the element that comes before pos, or the * canonical invalid position if there are no elements before pos. If * pos is the canonical invalid position, no guarantees are made about * what this method returns. */ DISPATCH(IterRewind) /* * ArrayData* EscalateForSort(ArrayData*, SortFunction) * * Must be called before calling any of the sort routines on an * array. This gives arrays a chance to change to a kind that * supports sorting. */ DISPATCH(EscalateForSort) /* * void Ksort(ArrayData*, int sort_flags, bool ascending) * * Sort an array by its keys, keeping the values associated with * their respective keys. */ DISPATCH(Ksort) /* * void Sort(ArrayData*, int sort_flags, bool ascending) * * Sort an array, by values, and then assign new keys to the * elements in the resulting array. */ DISPATCH(Sort) /* * void Asort(ArrayData*, int sort_flags, bool ascending) * * Sort an array and maintain index association. This means sort * the array by values, but keep the keys associated with the * values they used to be associated with. */ DISPATCH(Asort) /* * bool Uksort(ArrayData*, const Variant&) * * Sort on keys with a user-defined compare function (in the * variant argument). Returns false if the user comparison * function modifies the array we are sorting. */ DISPATCH(Uksort) /* * bool Usort(ArrayData*, const Variant&) * * Sort the array by values with a user-defined comparison * function (in the variant). Returns false if the user-defined * comparison function modifies the array we are sorting. */ DISPATCH(Usort) /* * bool Uasort(ArrayData*, const Variant&) * * Sort array by values with a user-defined comparison function * (in the variant arg), keeping the original indexes associated * with the values. Returns false if the user-defined comparison * function modifies the array we are sorting. */ DISPATCH(Uasort) /* * ArrayData* CopyStatic(const ArrayData*) * * Copy an array, allocating the new array with malloc() instead * of from the request local allocator. This function does * guarantee the returned array is a new copy, but it may throw a * fatal error if this cannot be accomplished. */ DISPATCH(CopyStatic) /* * ArrayData* Append(ArrayData*, TypedValue v); * * Append a new value to the array, with the next available integer key. * If copy / escalation is necessary, this operation will dec-ref the * array and return a new one; else, it'll return the original array. * This operation does not inc-ref the value. */ DISPATCH(AppendMove) /* * ArrayData* PopMove(ArrayData*, Variant& value); * * Remove the last element from the array and assign it to `value'. * If copy / escalation is necessary, this operation will dec-ref the * array and return a new one; else, it'll return the original array. */ DISPATCH(PopMove) /* * void OnSetEvalScalar(ArrayData*) * * Go through an array and call Variant::setEvalScalar on each * value, and make all string keys into static strings. */ DISPATCH(OnSetEvalScalar) /* * void MakeUncounted(ArrayData*, DataWalker::PointerMap*, bool hasApcTv) * * Return a copy of the array where all its contents are converted to * uncounted values. The caller must confirm that its contents can all be * converted, e.g. by using DataWalker. If hasApcTv is true, this method * will colocate an ApcTypedValue right before the array. */ DISPATCH(MakeUncounted) /* * ArrayData* Copy(ArrayData*) * * Return a counted copy of the array. A copy is made regardless of * the array's ref-count. */ DISPATCH(Copy) }; #undef DISPATCH /////////////////////////////////////////////////////////////////////////////// namespace { DEBUG_ONLY void assertForCreate(TypedValue name) { always_assert(isIntType(name.m_type) || isStringType(name.m_type)); } } // In general, arrays can contain int-valued-strings, even though plain array // access converts them to integers. non-int-string assertions should go // upstream of the ArrayData api. bool ArrayData::IsValidKey(TypedValue k) { return isIntType(k.m_type) || (isStringType(k.m_type) && IsValidKey(k.m_data.pstr)); } bool ArrayData::IsValidKey(const Variant& k) { return IsValidKey(*k.asTypedValue()); } bool ArrayData::IsValidKey(const String& k) { return IsValidKey(k.get()); } /////////////////////////////////////////////////////////////////////////////// // reads ALWAYS_INLINE bool ArrayData::EqualHelper(const ArrayData* ad1, const ArrayData* ad2, bool strict) { if (ad1 == ad2) return true; if (ad1->size() != ad2->size()) return false; // Prevent circular referenced objects/arrays or deep ones. check_recursion_error(); if (strict) { for (ArrayIter iter1{ad1}, iter2{ad2}; iter1; ++iter1, ++iter2) { assertx(iter2); if (!HPHP::same(iter1.first(), iter2.first()) || !tvSame(iter1.secondVal(), iter2.secondVal())) { return false; } } return true; } else { bool equal = true; IterateKV( ad1, [&](TypedValue k, TypedValue v) { auto const v2 = ad2->get(k); if (!v2.is_init() || !tvEqual(v, v2)) { equal = false; return true; } return false; } ); return equal; } } ALWAYS_INLINE int64_t ArrayData::CompareHelper(const ArrayData* ad1, const ArrayData* ad2) { auto const size1 = ad1->size(); auto const size2 = ad2->size(); if (size1 < size2) return -1; if (size1 > size2) return 1; // Prevent circular referenced objects/arrays or deep ones. check_recursion_error(); int result = 0; IterateKV( ad1, [&](TypedValue k, TypedValue v) { auto const v2 = ad2->get(k); if (!v2.is_init()) { result = 1; return true; } auto const cmp = tvCompare(v, v2); if (cmp != 0) { result = cmp; return true; } return false; } ); return result; } bool ArrayData::Equal(const ArrayData* ad1, const ArrayData* ad2) { return EqualHelper(ad1, ad2, false); } bool ArrayData::NotEqual(const ArrayData* ad1, const ArrayData* ad2) { return !EqualHelper(ad1, ad2, false); } bool ArrayData::Same(const ArrayData* ad1, const ArrayData* ad2) { return EqualHelper(ad1, ad2, true); } bool ArrayData::NotSame(const ArrayData* ad1, const ArrayData* ad2) { return !EqualHelper(ad1, ad2, true); } bool ArrayData::Lt(const ArrayData* ad1, const ArrayData* ad2) { return CompareHelper(ad1, ad2) < 0; } bool ArrayData::Lte(const ArrayData* ad1, const ArrayData* ad2) { return CompareHelper(ad1, ad2) <= 0; } bool ArrayData::Gt(const ArrayData* ad1, const ArrayData* ad2) { return CompareHelper(ad1, ad2) > 0; } bool ArrayData::Gte(const ArrayData* ad1, const ArrayData* ad2) { return CompareHelper(ad1, ad2) >= 0; } int64_t ArrayData::Compare(const ArrayData* ad1, const ArrayData* ad2) { return CompareHelper(ad1, ad2); } bool ArrayData::same(const ArrayData* v2) const { assertx(v2); if (toDataType() != v2->toDataType()) return false; if (!bothVanilla(this, v2)) return Same(this, v2); if (isVanillaVec()) return VanillaVec::VecSame(this, v2); if (isVanillaDict()) return VanillaDict::DictSame(this, v2); return VanillaKeyset::Same(this, v2); } /////////////////////////////////////////////////////////////////////////////// // helpers void ArrayData::getNotFound(int64_t k) const { throwOOBArrayKeyException(k, this); } void ArrayData::getNotFound(const StringData* k) const { if (isVecType()) throwInvalidArrayKeyException(k, this); throwOOBArrayKeyException(k, this); } const char* ArrayData::kindToString(ArrayKind kind) { std::array<const char*, 6> names = {{ "VecKind", "BespokeVecKind", "DictKind", "BespokeDictKind", "KeysetKind", "BespokeKeysetKind", }}; static_assert(names.size() == kNumKinds, "add new kinds here"); return names[kind]; } namespace { //////////////////////////////////////////////////////////////////////////////// std::string describeKeyValue(TypedValue tv) { switch (tv.m_type) { case KindOfPersistentString: case KindOfString: return folly::sformat("\"{}\"", tv.m_data.pstr->data()); case KindOfInt64: return folly::to<std::string>(tv.m_data.num); case KindOfUninit: case KindOfNull: case KindOfBoolean: case KindOfDouble: case KindOfPersistentVec: case KindOfVec: case KindOfPersistentDict: case KindOfDict: case KindOfPersistentKeyset: case KindOfKeyset: case KindOfResource: case KindOfObject: case KindOfRFunc: case KindOfFunc: case KindOfClass: case KindOfLazyClass: case KindOfClsMeth: case KindOfRClsMeth: return "<invalid key type>"; } not_reached(); } //////////////////////////////////////////////////////////////////////////////// } void throwInvalidArrayKeyException(const TypedValue* key, const ArrayData* ad) { std::pair<const char*, const char*> kind_type = [&]{ if (ad->isVecType()) return std::make_pair("vec", "int"); if (ad->isDictType()) return std::make_pair("dict", "int or string"); assertx(ad->isKeysetType()); return std::make_pair("keyset", "int or string"); }(); SystemLib::throwInvalidArgumentExceptionObject( folly::sformat( "Invalid {} key: expected a key of type {}, {} given", kind_type.first, kind_type.second, describe_actual_type(key) ) ); } void throwInvalidArrayKeyException(const StringData* key, const ArrayData* ad) { auto const tv = make_tv<KindOfString>(const_cast<StringData*>(key)); throwInvalidArrayKeyException(&tv, ad); } void throwFalseyPromoteException(const char* type) { SystemLib::throwOutOfBoundsExceptionObject( folly::sformat("Promoting {} to array", type) ); } void throwOOBArrayKeyException(TypedValue key, const ArrayData* ad) { SystemLib::throwOutOfBoundsExceptionObject( folly::sformat( "Out of bounds {} access: invalid index {}", getDataTypeString(ad->toDataType(), ad->isLegacyArray()).data(), describeKeyValue(key) ) ); } void throwOOBArrayKeyException(int64_t key, const ArrayData* ad) { throwOOBArrayKeyException(make_tv<KindOfInt64>(key), ad); } void throwOOBArrayKeyException(const StringData* key, const ArrayData* ad) { throwOOBArrayKeyException( make_tv<KindOfString>(const_cast<StringData*>(key)), ad ); } void throwInvalidKeysetOperation() { SystemLib::throwInvalidOperationExceptionObject(s_InvalidKeysetOperationMsg); } void throwVarrayUnsetException() { SystemLib::throwInvalidOperationExceptionObject(s_VarrayUnsetMsg); } void throwVecUnsetException() { SystemLib::throwInvalidOperationExceptionObject(s_VecUnsetMsg); } /////////////////////////////////////////////////////////////////////////////// ArrayData* ArrayData::toDictIntishCast(bool copy) { auto const base = toDict(copy); if (isVecType()) return base; // Check if we need to intish-cast any string keys. int64_t i; auto intish = false; IterateKV(base, [&](TypedValue k, TypedValue) { return intish |= tvIsString(k) && val(k).pstr->isStrictlyInteger(i); }); if (!intish) return base; // Create a new dict with the casted keys. auto result = DictInit(base->size()); IterateKV(base, [&](TypedValue k, TypedValue v) { result.setUnknownKey<IntishCast::Cast>(k, tvAsCVarRef(&v)); }); if (base != this) decRefArr(base); auto ad = result.create(); if (!isKeysetType()) return ad; // When keysets are intish-casted, we cast the values, too. Do so in place. // This case is rare, so we keep the extra checks out of the loop above. assertx(ad->hasExactlyOneRef()); auto elm = VanillaDict::as(ad)->data(); for (auto const end = elm + ad->size(); elm != end; elm++) { if (tvIsString(elm->data) && elm->hasIntKey()) { decRefStr(val(elm->data).pstr); tvCopy(make_tv<KindOfInt64>(elm->intKey()), elm->data); } } return ad; } bool ArrayData::IntishCastKey(const StringData* key, int64_t& i) { return key->isStrictlyInteger(i); } ArrayData* ArrayData::setLegacyArray(bool copy, bool legacy) { assertx(IMPLIES(cowCheck(), copy)); assertx(isDictType() || isVecType()); if (legacy == isLegacyArray()) return this; if (!isVanilla()) return BespokeArray::SetLegacyArray(this, copy, legacy); auto const ad = [&]{ if (!copy) return this; return isVanillaVec() ? VanillaVec::Copy(this) : VanillaDict::Copy(this); }(); ad->setLegacyArrayInPlace(legacy); return ad; } void ArrayData::setLegacyArrayInPlace(bool legacy) { assertx(hasExactlyOneRef()); if (legacy) { m_aux16 |= kLegacyArray; } else { m_aux16 &= ~kLegacyArray; } } size_t ArrayData::heapSize() const { return kSizeIndex2Size[sizeIndex()]; } /////////////////////////////////////////////////////////////////////////////// ArrayData* ArrayData::toVec(bool copy) { assertx(IMPLIES(cowCheck(), copy)); if (isVecType()) return this; if (empty()) return ArrayData::CreateVec(); VecInit init{size()}; IterateV(this, [&](auto v) { init.append(v); }); return init.create(); } ArrayData* ArrayData::toDict(bool copy) { assertx(IMPLIES(cowCheck(), copy)); if (isDictType()) return this; if (empty()) return ArrayData::CreateDict(); DictInit init{size()}; IterateKV(this, [&](auto k, auto v) { init.setValidKey(k, v); }); return init.create(); } ArrayData* ArrayData::toKeyset(bool copy) { assertx(IMPLIES(cowCheck(), copy)); if (isKeysetType()) return this; if (empty()) return ArrayData::CreateKeyset(); KeysetInit init{size()}; IterateV(this, [&](auto v) { init.add(v); }); return init.create(); } /////////////////////////////////////////////////////////////////////////////// }