HphpArray::SortFlavor HphpArray::preSort(const AccessorT& acc, bool checkTypes) { assert(m_size > 0); if (isPacked()) { // todo t2607563: this is pessimistic. packedToMixed(); } if (!checkTypes && m_size == m_used) { // No need to loop over the elements, we're done return GenericSort; } Elm* start = data(); Elm* end = data() + m_used; bool allInts UNUSED = true; bool allStrs UNUSED = true; for (;;) { if (checkTypes) { while (!isTombstone(start->data.m_type)) { allInts = (allInts && acc.isInt(*start)); allStrs = (allStrs && acc.isStr(*start)); ++start; if (start == end) { goto done; } } } else { while (!isTombstone(start->data.m_type)) { ++start; if (start == end) { goto done; } } } --end; if (start == end) { goto done; } while (isTombstone(end->data.m_type)) { --end; if (start == end) { goto done; } } memcpy(start, end, sizeof(Elm)); } done: m_used = start - data(); assert(m_size == m_used); if (checkTypes) { return allStrs ? StringSort : allInts ? IntegerSort : GenericSort; } else { return GenericSort; } }
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; } }
void HashCollection::eraseNoCompact(ssize_t pos) { assert(canMutateBuffer()); assert(validPos(pos) && !isTombstone(pos)); assert(m_size > 0); arrayData()->eraseNoCompact(pos); --m_size; }
ALWAYS_INLINE typename std::enable_if< std::is_base_of<BaseMap, TMap>::value, Object>::type BaseMap::php_zip(const Variant& iterable) const { size_t sz; ArrayIter iter = getArrayIterHelper(iterable, sz); auto map = req::make<TMap>(); if (!m_size) { return Object{std::move(map)}; } map->reserve(std::min(sz, size_t(m_size))); uint32_t used = posLimit(); for (uint32_t i = 0; i < used && iter; ++i) { if (isTombstone(i)) continue; const Elm& e = data()[i]; Variant v = iter.second(); auto pair = req::make<c_Pair>(c_Pair::NoInit{}); pair->initAdd(&e.data); pair->initAdd(v); TypedValue tv; tv.m_data.pobj = pair.detach(); tv.m_type = KindOfObject; if (e.hasIntKey()) { map->setRaw(e.ikey, &tv); } else { assert(e.hasStrKey()); map->setRaw(e.skey, &tv); } ++iter; } return Object{std::move(map)}; }
typename std::enable_if< std::is_base_of<BaseSet, TSet>::value, Object>::type BaseSet::php_skipWhile(const Variant& fn) { CallCtx ctx; vm_decode_function(fn, 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)); // we don't reserve(), because we don't know how selective fn will be set->mutate(); int32_t version UNUSED; if (std::is_same<c_Set, TSet>::value) { version = m_version; } uint32_t used = posLimit(); uint32_t i = 0; for (; i < used; ++i) { if (isTombstone(i)) continue; Elm& e = data()[i]; bool b = invokeAndCastToBool(ctx, 1, &e.data); if (std::is_same<c_Set, TSet>::value) { if (UNLIKELY(version != m_version)) { throw_collection_modified(); } } if (!b) break; } for (; i < used; ++i) { if (isTombstone(i)) continue; Elm& e = data()[i]; if (e.hasIntKey()) { set->addRaw(e.data.m_data.num); } else { assert(e.hasStrKey()); set->addRaw(e.data.m_data.pstr); } } return Object(std::move(set)); }
Variant BaseSet::lastValue() { if (!m_size) return init_null(); // TODO Task# 4281431: If nthElmPos(n) is optimized to // walk backward from the end when n > m_size/2, then // we could use that here instead of having to use a // manual while loop. uint32_t pos = posLimit() - 1; while (isTombstone(pos)) { assert(pos > 0); --pos; } return tvAsCVarRef(&data()[pos].data); }
Variant BaseSet::popFront() { if (UNLIKELY(m_size == 0)) { SystemLib::throwInvalidOperationExceptionObject("Cannot pop empty Set"); } mutateAndBump(); auto e = data(); for (;; ++e) { assert(e != elmLimit()); if (!isTombstone(e)) break; } Variant ret = tvAsCVarRef(&e->data); auto h = e->hash(); auto ei = e->hasIntKey() ? findForRemove(e->ikey, h) : findForRemove(e->skey, h); erase(ei); return ret; }
Variant BaseMap::lastKey() { if (!m_size) return null_variant; // TODO Task# 4281431: If nthElmPos(n) is optimized to // walk backward from the end when n > m_size/2, then // we could use that here instead of having to use a // manual while loop. uint32_t pos = posLimit() - 1; while (isTombstone(pos)) { assert(pos > 0); --pos; } if (data()[pos].hasIntKey()) { return data()[pos].ikey; } assert(data()[pos].hasStrKey()); return Variant{data()[pos].skey}; }
typename std::enable_if< std::is_base_of<BaseSet, TSet>::value, Object>::type BaseSet::php_skip(const Variant& n) { if (!n.isInteger()) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter n must be an integer"); } int64_t len = n.toInt64(); if (len <= 0) { // We know the resulting Set will simply be a copy of this Set, // so we can just call Clone() and return early here. return Object::attach(TSet::Clone(this)); } auto set = req::make<TSet>(); if (len >= m_size) { // We know the resulting Set will be empty, so we can return // early here. return Object{std::move(set)}; } size_t sz = size_t(m_size) - size_t(len); assert(sz); set->reserve(sz); set->setSize(sz); set->setPosLimit(sz); uint32_t frPos = nthElmPos(len); auto table = set->hashTab(); auto mask = set->tableMask(); for (uint32_t toPos = 0; toPos < sz; ++toPos, ++frPos) { while (isTombstone(frPos)) { assert(frPos + 1 < posLimit()); ++frPos; } auto& toE = set->data()[toPos]; dupElm(data()[frPos], toE); *findForNewInsert(table, mask, toE.probe()) = toPos; if (toE.hasIntKey()) { set->updateNextKI(toE.ikey); } else { assert(toE.hasStrKey()); set->updateIntLikeStrKeys(toE.skey); } } return Object{std::move(set)}; }
Variant BaseMap::popFront() { if (m_size) { mutateAndBump(); auto* e = data(); for (;; ++e) { assert(e != elmLimit()); if (!isTombstone(e)) break; } Variant ret = tvAsCVarRef(&e->data); ssize_t ei; if (e->hasIntKey()) { ei = findForRemove(e->ikey); } else { assert(e->hasStrKey()); ei = findForRemove(e->skey, e->skey->hash()); } erase(ei); return ret; } else { SystemLib::throwInvalidOperationExceptionObject( "Cannot pop empty Map"); } }
typename std::enable_if< std::is_base_of<BaseVector, TVector>::value, Object>::type BaseSet::php_concat(const Variant& iterable) { size_t itSize; ArrayIter iter = getArrayIterHelper(iterable, itSize); auto vec = req::make<TVector>(); uint32_t sz = m_size; vec->reserve((size_t)sz + itSize); assert(vec->canMutateBuffer()); vec->setSize(sz); uint32_t used = posLimit(); for (uint32_t i = 0, j = 0; i < used; ++i) { if (isTombstone(i)) { continue; } cellDup(data()[i].data, vec->data()[j]); ++j; } for (; iter; ++iter) { vec->addRaw(iter.second()); } return Object{std::move(vec)}; }
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)}; }
bool BaseMap::Equals(EqualityFlavor eq, const ObjectData* obj1, const ObjectData* obj2) { auto map1 = static_cast<const BaseMap*>(obj1); auto map2 = static_cast<const BaseMap*>(obj2); auto size = map1->size(); if (size != map2->size()) { return false; } if (size == 0) { return true; } switch (eq) { case EqualityFlavor::OrderIrrelevant: { // obj1 and obj2 must have the exact same set of keys, and the values // for each key must compare equal (==). This equality behavior // matches that of == on two PHP (associative) arrays. for (uint32_t i = 0; i < map1->posLimit(); ++i) { if (map1->isTombstone(i)) continue; const HashCollection::Elm& e = map1->data()[i]; TypedValue* tv2; if (e.hasIntKey()) { tv2 = map2->get(e.ikey); } else { assert(e.hasStrKey()); tv2 = map2->get(e.skey); } if (!tv2) return false; if (!HPHP::equal(tvAsCVarRef(&e.data), tvAsCVarRef(tv2))) return false; } return true; } case EqualityFlavor::OrderMatters: { // obj1 and obj2 must compare equal according to OrderIrrelevant; // additionally, the (identical) keys of obj1 and obj2 must be in the // same iteration order. uint32_t compared = 0; for (uint32_t ix1 = 0, ix2 = 0; ix1 < map1->posLimit() && ix2 < map2->posLimit() ; ) { auto tomb1 = map1->isTombstone(ix1); auto tomb2 = map2->isTombstone(ix2); if (tomb1 || tomb2) { if (tomb1) { ++ix1; } if (tomb2) { ++ix2; } continue; } const HashCollection::Elm& e1 = map1->data()[ix1]; const HashCollection::Elm& e2 = map2->data()[ix2]; if (e1.hasIntKey()) { if (!e2.hasIntKey() || e1.ikey != e2.ikey) { return false; } } else { assert(e1.hasStrKey()); if (!e2.hasStrKey() || !HPHP::equal(e1.skey, e2.skey)) { return false; } } if (!HPHP::equal(tvAsCVarRef(&e1.data), tvAsCVarRef(&e2.data))) { return false; } ++ix1; ++ix2; ++compared; } return (compared == size); } } not_reached(); }