void HashCollection::mutateImpl() { assert(arrayData()->hasMultipleRefs()); dropImmCopy(); if (canMutateBuffer()) { return; } auto* oldAd = arrayData(); m_arr = MixedArray::asMixed(MixedArray::Copy(oldAd)); assert(oldAd->hasMultipleRefs()); oldAd->decRefCount(); }
// This function will create a immutable copy of this Set (if it doesn't // already exist) and then return it Object c_Set::getImmutableCopy() { if (m_immCopy.isNull()) { auto set = req::make<c_ImmSet>(); set->m_size = m_size; set->m_version = m_version; set->m_arr = m_arr; set->setIntLikeStrKeys(intLikeStrKeys()); m_immCopy = std::move(set); arrayData()->incRefCount(); } assert(!m_immCopy.isNull()); assert(data() == static_cast<c_ImmSet*>(m_immCopy.get())->data()); assert(arrayData()->hasMultipleRefs()); return m_immCopy; }
void HashCollection::eraseNoCompact(ssize_t pos) { assert(canMutateBuffer()); assert(validPos(pos) && !isTombstone(pos)); assert(m_size > 0); arrayData()->eraseNoCompact(pos); --m_size; }
void HashCollection::compact() { assert(isDensityTooLow()); dropImmCopy(); if (!arrayData()->cowCheck()) { // MixedArray::compact can only handle cases where the buffer's // refcount is 1. arrayData()->compact(false); } else { // For cases where the buffer's refcount is greater than 1, call // resizeHelper(). resizeHelper(cap()); } assert(canMutateBuffer()); assert(m_immCopy.isNull()); assert(!isDensityTooLow()); }
void c_Set::clear() { ++m_version; dropImmCopy(); decRefArr(arrayData()); m_arr = staticEmptyMixedArray(); m_size = 0; setIntLikeStrKeys(false); }
void HashCollection::shrink(uint32_t oldCap /* = 0 */) { assert(isCapacityTooHigh() && (oldCap == 0 || oldCap < cap())); assert(m_size <= posLimit() && posLimit() <= cap()); dropImmCopy(); uint32_t newCap; if (oldCap != 0) { // If an old capacity was specified, use that newCap = oldCap; // .. unless the old capacity is too small, in which case we use the // smallest capacity that is large enough to hold the current number // of elements. for (; newCap < m_size; newCap <<= 1) {} assert(newCap == computeMaxElms(folly::nextPowTwo<uint64_t>(newCap) - 1)); } else { if (m_size == 0 && nextKI() == 0) { decRefArr(m_arr); m_arr = staticEmptyDictArrayAsMixed(); return; } // If no old capacity was provided, we compute the largest capacity // where m_size/cap() is less than or equal to 0.5 for good hysteresis size_t doubleSz = size_t(m_size) * 2; uint32_t capThreshold = (doubleSz < size_t(MaxSize)) ? doubleSz : MaxSize; for (newCap = SmallSize * 2; newCap < capThreshold; newCap <<= 1) {} } assert(SmallSize <= newCap && newCap <= MaxSize); assert(m_size <= newCap); auto* oldAd = arrayData(); if (!oldAd->cowCheck()) { // If the buffer's refcount is 1, we can teleport the elements // to a new buffer auto oldBuf = data(); auto oldUsed = posLimit(); auto oldNextKI = nextKI(); auto arr = MixedArray::asMixed(MixedArray::MakeReserveDict(newCap)); auto data = mixedData(arr); m_arr = arr; auto table = (int32_t*)(data + size_t(newCap)); auto table_mask = tableMask(); arr->m_size = m_size; setPosLimit(m_size); setNextKI(oldNextKI); for (uint32_t frPos = 0, toPos = 0; toPos < m_size; ++toPos, ++frPos) { frPos = skipTombstonesNoBoundsCheck(frPos, oldUsed, oldBuf); copyElm(oldBuf[frPos], data[toPos]); *findForNewInsert(table, table_mask, data[toPos].probe()) = toPos; } oldAd->setZombie(); decRefArr(oldAd); } else { // For cases where the buffer's refcount is greater than 1, call // resizeHelper() resizeHelper(newCap); } assert(canMutateBuffer()); assert(m_immCopy.isNull()); assert(!isCapacityTooHigh() || newCap == oldCap); }
Array HashCollection::toArray() { if (!m_size) { return empty_array(); } auto ad = arrayData()->toPHPArray(true); if (UNLIKELY(ad->size() < m_size)) warnOnStrIntDup(); assert(m_size); assert(ad->m_pos == 0); return Array::attach(ad); }
void BaseMap::addAllImpl(const Variant& iterable) { if (iterable.isNull()) return; VMRegGuard _; decltype(cap()) oldCap = 0; bool ok = IterateKV( *iterable.asTypedValue(), [&](ArrayData* adata) { auto sz = adata->size(); if (!sz) return true; if (!m_size) { if (adata->isMixed()) { replaceArray(adata); updateIntLikeStrKeys(); return true; } } else { oldCap = cap(); // assume minimal collisions } reserve(m_size + sz); mutateAndBump(); return false; }, [this](const TypedValue* key, const TypedValue* value) { setRaw(tvAsCVarRef(key), tvAsCVarRef(value)); }, [this](ObjectData* coll) { switch (coll->collectionType()) { case CollectionType::Map: case CollectionType::Set: { if (m_size) break; auto hc = static_cast<HashCollection*>(coll); replaceArray(hc->arrayData()); setIntLikeStrKeys(BaseMap::intLikeStrKeys(hc)); return true; } case CollectionType::Pair: mutateAndBump(); break; default: break; } return false; }, [this](const TypedValue* key, const TypedValue* value) { set(tvAsCVarRef(key), tvAsCVarRef(value)); }); if (UNLIKELY(!ok)) { throw_invalid_collection_parameter(); } // ... and shrink back if that was incorrect if (oldCap) shrinkIfCapacityTooHigh(oldCap); }
SortFlavor HashCollection::preSort(const AccessorT& acc, bool checkTypes) { assert(m_size > 0); if (!checkTypes && !hasTombstones()) { // No need to loop over the elements, we're done return GenericSort; } auto* start = data(); auto* end = data() + posLimit(); bool allInts UNUSED = true; bool allStrs UNUSED = true; for (;;) { if (checkTypes) { while (!isTombstone(start)) { allInts = (allInts && acc.isInt(*start)); allStrs = (allStrs && acc.isStr(*start)); ++start; if (start == end) { goto done; } } } else { while (!isTombstone(start)) { ++start; if (start == end) { goto done; } } } --end; if (start == end) { goto done; } while (isTombstone(end)) { --end; if (start == end) { goto done; } } copyElm(*end, *start); } done: setPosLimit(start - data()); // The logic above possibly moved elements and tombstones around // within the buffer, so we make sure m_pos is not pointing at // garbage by resetting it. The logic above ensures that the first // slot is not a tombstone, so it's safe to set m_pos to 0. arrayData()->m_pos = 0; assert(!hasTombstones()); if (checkTypes) { return allStrs ? StringSort : allInts ? IntegerSort : GenericSort; } else { return GenericSort; } }
ALWAYS_INLINE void HashCollection::resizeHelper(uint32_t newCap) { assert(newCap >= m_size); assert(m_immCopy.isNull()); // Allocate a new ArrayData with the specified capacity and dup // all the elements (without copying over tombstones). auto ad = arrayData() == staticEmptyDictArrayAsMixed() ? MixedArray::asMixed(MixedArray::MakeReserveDict(newCap)) : MixedArray::CopyReserve(m_arr, newCap); decRefArr(m_arr); m_arr = ad; assert(canMutateBuffer()); }
typename std::enable_if< std::is_base_of<BaseMap, TMap>::value, TMap*>::type BaseMap::Clone(ObjectData* obj) { auto thiz = static_cast<TMap*>(obj); auto target = static_cast<TMap*>(obj->cloneImpl()); if (!thiz->m_size) { return target; } thiz->arrayData()->incRefCount(); target->m_size = thiz->m_size; target->m_arr = thiz->m_arr; target->setIntLikeStrKeys(thiz->intLikeStrKeys()); return target; }
typename std::enable_if< std::is_base_of<BaseSet, TSet>::value, TSet*>::type BaseSet::Clone(ObjectData* obj) { auto thiz = static_cast<TSet*>(obj); auto target = static_cast<TSet*>(TSet::instanceCtor(TSet::classof())); if (!thiz->m_size) { return target; } thiz->arrayData()->incRefCount(); target->m_size = thiz->m_size; target->m_arr = thiz->m_arr; target->setIntLikeStrKeys(thiz->intLikeStrKeys()); return target; }
void HashCollection::grow(uint32_t newScale) { auto newCap = MixedArray::Capacity(newScale); assert(m_size <= posLimit() && posLimit() <= cap() && cap() <= newCap); assert(SmallSize <= newCap && newCap <= MaxSize); assert(m_size <= newCap); auto oldAd = arrayData(); dropImmCopy(); if (m_size > 0 && !oldAd->cowCheck()) { // MixedArray::Grow can only handle non-empty cases where the // buffer's refcount is 1. m_arr = MixedArray::Grow(oldAd, newScale); decRefArr(oldAd); } else { // For cases where m_size is zero or the buffer's refcount is // greater than 1, call resizeHelper(). resizeHelper(newCap); } assert(canMutateBuffer()); assert(m_immCopy.isNull()); }
void BaseSet::addAll(const Variant& t) { if (t.isNull()) { return; } // nothing to do decltype(cap()) oldCap = 0; bool ok = IterateV( *t.asTypedValue(), [&](ArrayData* adata) { auto sz = adata->size(); if (!sz) return true; if (m_size) { oldCap = cap(); // assume minimal collisions } reserve(m_size + sz); mutateAndBump(); return false; }, [this](const TypedValue* value) { addRaw(tvAsCVarRef(value)); }, [this](ObjectData* coll) { if (!m_size && coll->collectionType() == CollectionType::Set) { auto hc = static_cast<HashCollection*>(coll); replaceArray(hc->arrayData()); setIntLikeStrKeys(BaseSet::intLikeStrKeys(hc)); return true; } if (coll->collectionType() == CollectionType::Pair) { mutateAndBump(); } return false; }, [this](const TypedValue* value) { add(tvAsCVarRef(value)); }); if (UNLIKELY(!ok)) { throw_invalid_collection_parameter(); } // ... and shrink back if that was incorrect if (oldCap) shrinkIfCapacityTooHigh(oldCap); }
typename std::enable_if< std::is_base_of<BaseSet, TSet>::value, Object>::type BaseSet::php_map(const Variant& callback) const { VMRegGuard _; CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto set = req::make<TSet>(); if (!m_size) return Object{std::move(set)}; assert(posLimit() != 0); assert(hashSize() > 0); assert(set->arrayData() == staticEmptyMixedArray()); auto oldCap = set->cap(); set->reserve(posLimit()); // presume minimum collisions ... assert(set->canMutateBuffer()); constexpr int64_t argc = useKey ? 2 : 1; TypedValue argv[argc]; for (ssize_t pos = iter_begin(); iter_valid(pos); pos = iter_next(pos)) { auto e = iter_elm(pos); TypedValue tvCbRet; int32_t pVer = m_version; if (useKey) { argv[0] = e->data; } argv[argc-1] = e->data; g_context->invokeFuncFew(&tvCbRet, ctx, argc, argv); // Now that tvCbRet is live, make sure to decref even if we throw. SCOPE_EXIT { tvRefcountedDecRef(&tvCbRet); }; if (UNLIKELY(m_version != pVer)) throw_collection_modified(); set->addRaw(&tvCbRet); } // ... and shrink back if that was incorrect set->shrinkIfCapacityTooHigh(oldCap); return Object{std::move(set)}; }
HashCollection::Elm& HashCollection::allocElmFront(MixedArray::Inserter ei) { assert(MixedArray::isValidIns(ei) && !MixedArray::isValidPos(*ei)); assert(m_size <= posLimit() && posLimit() < cap()); // Move the existing elements to make element slot 0 available. memmove(data() + 1, data(), posLimit() * sizeof(Elm)); incPosLimit(); // Update the hashtable to reflect the fact that everything was // moved over one position auto* hash = hashTab(); auto* hashEnd = hash + hashSize(); for (; hash != hashEnd; ++hash) { if (validPos(*hash)) { ++(*hash); } } // Set the hash entry we found to point to element slot 0. (*ei) = 0; // Adjust m_pos so that is points at this new first element. arrayData()->m_pos = 0; // Adjust size to reflect that we're adding a new element. incSize(); // Store the value into element slot 0. return data()[0]; }
/** * postSort() runs after the sort has been performed. For c_Map, * postSort() handles rebuilding the hash. */ void HashCollection::postSort() { // Must provide the nothrow guarantee arrayData()->postSort(false); }
BaseSet::~BaseSet() { auto const mixed = MixedArray::asMixed(arrayData()); // Avoid indirect call, as we know it is a MixedArray if (mixed->decReleaseCheck()) MixedArray::Release(mixed); }
ALWAYS_INLINE typename std::enable_if< std::is_base_of<BaseMap, TMap>::value, Object>::type BaseMap::php_map(const Variant& callback) const { VMRegGuard _; CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto map = req::make<TMap>(); if (!m_size) return Object{std::move(map)}; assert(posLimit() != 0); assert(hashSize() > 0); assert(map->arrayData() == staticEmptyMixedArray()); map->m_arr = MixedArray::asMixed(MixedArray::MakeReserveMixed(cap())); map->setIntLikeStrKeys(intLikeStrKeys()); wordcpy(map->hashTab(), hashTab(), hashSize()); { uint32_t used = posLimit(); int32_t version = m_version; uint32_t i = 0; // When the loop below finishes or when an exception is thrown, // make sure that posLimit() get set to the correct value and // that m_pos gets set to point to the first element. SCOPE_EXIT { map->setPosLimit(i); map->arrayData()->m_pos = map->nthElmPos(0); }; constexpr int64_t argc = useKey ? 2 : 1; TypedValue argv[argc]; for (; i < used; ++i) { const Elm& e = data()[i]; Elm& ne = map->data()[i]; if (isTombstone(i)) { ne.data.m_type = e.data.m_type; continue; } TypedValue* tv = &ne.data; if (useKey) { if (e.hasIntKey()) { argv[0].m_type = KindOfInt64; argv[0].m_data.num = e.ikey; } else { argv[0].m_type = KindOfString; argv[0].m_data.pstr = e.skey; } } argv[argc-1] = e.data; g_context->invokeFuncFew(tv, ctx, argc, argv); if (UNLIKELY(version != m_version)) { tvRefcountedDecRef(tv); throw_collection_modified(); } if (e.hasStrKey()) { e.skey->incRefCount(); } ne.ikey = e.ikey; ne.data.hash() = e.data.hash(); map->incSize(); // Needed so that the new elements are accounted for when GC scanning. map->incPosLimit(); } } return Object{std::move(map)}; }