1 #include "hphp/runtime/ext/collections/hash-collection.h"
2 #include "hphp/runtime/base/array-init.h"
3 #include "hphp/runtime/base/vanilla-dict.h"
4 #include "hphp/runtime/ext/collections/ext_collections-map.h"
5 #include "hphp/runtime/ext/collections/ext_collections-set.h"
6 #include "hphp/runtime/vm/vm-regs.h"
7 #include "hphp/util/text-util.h"
10 /////////////////////////////////////////////////////////////////////////////
13 HashCollection::HashCollection(Class
* cls
, HeaderKind kind
, uint32_t cap
)
14 : c_Collection(cls
, kind
)
18 ? VanillaDict::as(VanillaDict::MakeReserveDict(cap
))
19 : CreateDictAsMixed());
23 void HashCollection::throwTooLarge() {
24 assertx(getClassName().size() == 6);
25 auto clsName
= getClassName().get()->slice();
26 String
msg(130, ReserveString
);
27 auto buf
= msg
.bufferSlice();
28 auto sz
= snprintf(buf
.data(), buf
.size() + 1,
29 "%s object has reached its maximum capacity of %u element "
30 "slots and does not have room to add a new element",
31 clsName
.data() + 3, // strip "HH\" prefix
34 msg
.setSize(std::min
<int>(sz
, buf
.size()));
35 SystemLib::throwInvalidOperationExceptionObject(msg
);
39 void HashCollection::throwReserveTooLarge() {
40 assertx(getClassName().size() == 6);
41 auto clsName
= getClassName().get()->slice();
42 String
msg(80, ReserveString
);
43 auto buf
= msg
.bufferSlice();
44 auto sz
= snprintf(buf
.data(), buf
.size() + 1,
45 "%s does not support reserving room for more than %u elements",
46 clsName
.data() + 3, // strip "HH\" prefix
49 msg
.setSize(std::min
<int>(sz
, buf
.size()));
50 SystemLib::throwInvalidOperationExceptionObject(msg
);
54 int32_t* HashCollection::warnUnbalanced(size_t n
, int32_t* ei
) const {
55 if (n
> size_t(Cfg::Server::MaxArrayChain
)) {
56 raise_error("%s is too unbalanced (%lu)",
57 getClassName().data() + 3, // strip "HH\" prefix
63 Array
HashCollection::toKeysArray() {
65 auto* eLimit
= elmLimit();
66 for (auto* e
= firstElm(); e
!= eLimit
; e
= nextElm(e
, eLimit
)) {
68 ai
.append(int64_t{e
->ikey
});
70 assertx(e
->hasStrKey());
71 ai
.append(VarNR(e
->skey
).tv());
77 Array
HashCollection::toValuesArray() {
78 if (!m_size
) return empty_vec_array();
79 return Array
{arrayData()}.toVec();
82 void HashCollection::remove(int64_t key
) {
83 auto p
= findForRemove(key
, hash_int64(key
));
90 void HashCollection::remove(StringData
* key
) {
91 auto p
= findForRemove(key
, key
->hash());
98 void HashCollection::eraseNoCompact(VanillaDict::RemovePos pos
) {
99 assertx(canMutateBuffer());
100 assertx(pos
.valid() && !isTombstone(pos
.elmIdx
));
102 arrayData()->eraseNoCompact(pos
);
107 void HashCollection::makeRoom() {
109 assertx(!isDensityTooLow());
110 if (UNLIKELY(cap() == MaxSize
)) throwTooLarge();
111 assertx(scale() > 0);
113 assertx(canMutateBuffer());
118 void HashCollection::reserve(int64_t sz
) {
119 assertx(m_size
<= posLimit() && posLimit() <= cap());
120 auto cap
= static_cast<int64_t>(this->cap());
121 if (LIKELY(sz
> cap
)) {
122 if (UNLIKELY(sz
> int64_t(MaxReserveSize
))) {
123 throwReserveTooLarge();
125 // Fast path: The requested capacity is greater than the current capacity.
126 // Grow to the smallest allowed capacity that is sufficient.
127 grow(VanillaDict::computeScaleFromSize(sz
));
128 assertx(canMutateBuffer());
131 if (LIKELY(!hasTombstones())) {
132 // Fast path: There are no tombstones and the requested capacity is less
133 // than or equal to the current capacity.
137 if (sz
+ int64_t(posLimit() - m_size
) <= cap
|| isDensityTooLow()) {
138 // If we reach this case, then either (1) density is too low (this is
139 // possible because of methods like retain()), in which case we compact
140 // to make room and return, OR (2) density is not too low and either
141 // sz < m_size or there's enough room to add sz-m_size elements, in
142 // which case we do nothing and return.
143 compactOrShrinkIfDensityTooLow();
144 assertx(sz
+ int64_t(posLimit() - m_size
) <= cap
);
148 // If we reach this case, then density is not too low and sz > m_size and
149 // there is not enough room to add sz-m_size elements. While would could
150 // compact to make room, it's better for Hysteresis if we grow capacity
152 assertx(!isDensityTooLow());
153 assertx(sz
+ int64_t(posLimit() - m_size
) > cap
);
154 assertx(cap
< MaxSize
&& tableMask() != 0);
155 auto newScale
= scale() * 2;
156 assertx(sz
> 0 && VanillaDict::Capacity(newScale
) >= sz
);
158 assertx(canMutateBuffer());
162 void HashCollection::resizeHelper(uint32_t newCap
) {
163 assertx(newCap
>= m_size
);
164 assertx(m_immCopy
.isNull());
165 // Allocate a new ArrayData with the specified capacity and dup
166 // all the elements (without copying over tombstones).
167 auto ad
= arrayData()->isStatic() && arrayData()->empty() ?
168 VanillaDict::as(VanillaDict::MakeReserveDict(newCap
)) :
169 VanillaDict::CopyReserve(arrayData(), newCap
);
170 decRefArr(arrayData());
172 assertx(canMutateBuffer());
175 void HashCollection::grow(uint32_t newScale
) {
176 auto newCap
= VanillaDict::Capacity(newScale
);
177 assertx(m_size
<= posLimit() && posLimit() <= cap() && cap() <= newCap
);
178 assertx(SmallSize
<= newCap
&& newCap
<= MaxSize
);
179 assertx(m_size
<= newCap
);
180 auto oldAd
= arrayData();
182 if (m_size
> 0 && !oldAd
->cowCheck()) {
183 setArrayData(VanillaDict::Grow(oldAd
, newScale
, false));
186 // For cases where m_size is zero or the buffer's refcount is
187 // greater than 1, call resizeHelper().
188 resizeHelper(newCap
);
190 assertx(canMutateBuffer());
193 void HashCollection::compact() {
194 assertx(isDensityTooLow());
196 if (!arrayData()->cowCheck()) {
197 // VanillaDict::compact can only handle cases where the buffer's
199 arrayData()->compact();
201 // For cases where the buffer's refcount is greater than 1, call
205 assertx(canMutateBuffer());
206 assertx(!isDensityTooLow());
209 void HashCollection::shrink(uint32_t oldCap
/* = 0 */) {
210 assertx(isCapacityTooHigh() && (oldCap
== 0 || oldCap
< cap()));
211 assertx(m_size
<= posLimit() && posLimit() <= cap());
215 // If an old capacity was specified, use that
217 // .. unless the old capacity is too small, in which case we use the
218 // smallest capacity that is large enough to hold the current number
220 for (; newCap
< m_size
; newCap
<<= 1) {}
221 assertx(newCap
== computeMaxElms(folly::nextPowTwo
<uint64_t>(newCap
) - 1));
223 // If no old capacity was provided, we compute the largest capacity
224 // where m_size/cap() is less than or equal to 0.5 for good hysteresis
225 size_t doubleSz
= size_t(m_size
) * 2;
226 uint32_t capThreshold
= (doubleSz
< size_t(MaxSize
)) ? doubleSz
: MaxSize
;
227 for (newCap
= SmallSize
* 2; newCap
< capThreshold
; newCap
<<= 1) {}
229 assertx(SmallSize
<= newCap
&& newCap
<= MaxSize
);
230 assertx(m_size
<= newCap
);
231 auto* oldAd
= arrayData();
232 if (!oldAd
->cowCheck()) {
233 // If the buffer's refcount is 1, we can teleport the elements
235 auto oldBuf
= data();
236 auto oldUsed
= posLimit();
237 auto oldNextKI
= nextKI();
238 auto const arr
= VanillaDict::as(VanillaDict::MakeReserveDict(newCap
));
240 arr
->m_size
= m_size
;
241 arr
->mutableKeyTypes()->copyFrom(oldAd
->keyTypes(), /*compact=*/true);
242 auto data
= this->data();
243 auto table
= hashTab();
244 auto table_mask
= tableMask();
246 setNextKI(oldNextKI
);
247 for (uint32_t frPos
= 0, toPos
= 0; toPos
< m_size
; ++toPos
, ++frPos
) {
248 frPos
= skipTombstonesNoBoundsCheck(frPos
, oldUsed
, oldBuf
);
249 copyElm(oldBuf
[frPos
], data
[toPos
]);
250 *findForNewInsert(table
, table_mask
, data
[toPos
].probe()) = toPos
;
255 // For cases where the buffer's refcount is greater than 1, call
257 resizeHelper(newCap
);
259 assertx(canMutateBuffer());
260 assertx(!isCapacityTooHigh() || newCap
== oldCap
);
263 HashCollection::Elm
& HashCollection::allocElmFront(VanillaDict::Inserter ei
) {
264 assertx(VanillaDict::isValidIns(ei
) && !VanillaDict::isValidPos(*ei
));
265 assertx(m_size
<= posLimit() && posLimit() < cap());
266 // Move the existing elements to make element slot 0 available.
267 memmove(data() + 1, data(), posLimit() * sizeof(Elm
));
269 // Update the hashtable to reflect the fact that everything was
270 // moved over one position
271 auto* hash
= hashTab();
272 auto* hashEnd
= hash
+ arrayData()->hashSize();
273 for (; hash
!= hashEnd
; ++hash
) {
274 if (validPos(*hash
)) {
278 // Set the hash entry we found to point to element slot 0.
280 // Adjust size to reflect that we're adding a new element.
282 // Store the value into element slot 0.
286 void HashCollection::mutateImpl() {
287 assertx(arrayData()->hasMultipleRefs());
289 if (canMutateBuffer()) {
292 auto* oldAd
= arrayData();
293 setArrayData(VanillaDict::as(VanillaDict::Copy(oldAd
)));
294 assertx(oldAd
->hasMultipleRefs());
295 oldAd
->decRefCount();
298 /////////////////////////////////////////////////////////////////////////////