2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/ext_collections.h"
19 #include "hphp/runtime/base/variable_serializer.h"
20 #include "hphp/runtime/base/array/sort_helpers.h"
21 #include "hphp/runtime/ext/ext_array.h"
22 #include "hphp/runtime/ext/ext_math.h"
23 #include "hphp/runtime/ext/ext_intl.h"
24 #include "hphp/runtime/vm/translator/translator-inline.h"
25 #include "hphp/system/lib/systemlib.h"
28 ///////////////////////////////////////////////////////////////////////////////
30 static void throwIntOOB(int64_t key
, bool isVector
= false) ATTRIBUTE_COLD
33 void throwIntOOB(int64_t key
, bool isVector
/* = false */) {
34 static const size_t reserveSize
= 50;
35 String
msg(reserveSize
, ReserveString
);
36 char* buf
= msg
.mutableSlice().ptr
;
37 int sz
= sprintf(buf
, "Integer key %" PRId64
" is %s", key
,
38 isVector
? "out of bounds" : "not defined");
39 assert(sz
<= reserveSize
);
41 Object
e(SystemLib::AllocOutOfBoundsExceptionObject(msg
));
45 static void throwStrOOB(StringData
* key
) ATTRIBUTE_COLD ATTRIBUTE_NORETURN
;
47 void throwStrOOB(StringData
* key
) {
48 const size_t maxDisplaySize
= 20;
49 const char* dots
= "...";
50 size_t dotsSize
= strlen(dots
);
51 int keySize
= key
->size();
52 bool keyIsLarge
= (keySize
> maxDisplaySize
);
53 size_t displaySize
= keyIsLarge
? (maxDisplaySize
- dotsSize
) : keySize
;
54 const char* part1
= "String key \"";
55 size_t part1Size
= strlen(part1
);
56 size_t part2Size
= keyIsLarge
? maxDisplaySize
: keySize
;
57 const char* part3
= "\" is not defined";
58 size_t part3Size
= strlen(part3
);
59 // Do some math ahead of time so we know exactly how large
60 // the String needs to be
61 String
msg(part1Size
+ part2Size
+ part3Size
, ReserveString
);
62 msg
+= StringSlice(part1
, part1Size
);
63 msg
+= StringSlice(key
->data(), displaySize
);
64 if (keyIsLarge
) msg
+= StringSlice(dots
, dotsSize
);
65 msg
+= StringSlice(part3
, part3Size
);
66 Object
e(SystemLib::AllocOutOfBoundsExceptionObject(msg
));
70 static inline ArrayIter
getArrayIterHelper(CVarRef v
, size_t& sz
) {
72 ArrayData
* ad
= v
.getArrayData();
77 ObjectData
* obj
= v
.getObjectData();
78 if (obj
->isCollection()) {
79 sz
= collectionSize(obj
);
80 return ArrayIter(obj
);
83 Object iterable
= obj
->iterableObject(isIterable
);
86 return ArrayIter(iterable
, ArrayIter::transferOwner
);
89 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
90 "Parameter must be an array or an instance of Traversable"));
94 ///////////////////////////////////////////////////////////////////////////////
96 c_Vector::c_Vector(Class
* cb
) :
97 ExtObjectDataFlags
<ObjectData::VectorAttrInit
|
100 ObjectData::UseIsset
|
101 ObjectData::UseUnset
>(cb
),
102 m_data(nullptr), m_size(0), m_capacity(0), m_version(0) {
105 c_Vector::~c_Vector() {
107 for (uint i
= 0; i
< sz
; ++i
) {
108 tvRefcountedDecRef(&m_data
[i
]);
110 c_Vector::freeData();
113 void c_Vector::freeData() {
120 void c_Vector::t___construct(CVarRef iterable
/* = null_variant */) {
121 if (!iterable
.isInitialized()) {
125 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
129 for (; iter
; ++iter
) {
130 Variant v
= iter
.second();
131 TypedValue
* tv
= cvarToCell(&v
);
136 void c_Vector::grow() {
138 m_capacity
+= m_capacity
;
142 m_data
= (TypedValue
*)smart_realloc(m_data
, m_capacity
* sizeof(TypedValue
));
145 void c_Vector::resize(int64_t sz
, TypedValue
* val
) {
146 assert(val
&& val
->m_type
!= KindOfRef
);
149 uint requestedSize
= (uint
)sz
;
150 if (m_capacity
< requestedSize
) {
151 m_capacity
= requestedSize
;
153 (TypedValue
*)smart_realloc(m_data
, m_capacity
* sizeof(TypedValue
));
155 if (m_size
> requestedSize
) {
158 tvRefcountedDecRef(&m_data
[m_size
]);
159 } while (m_size
> requestedSize
);
161 for (; m_size
< requestedSize
; ++m_size
) {
162 tvDup(val
, &m_data
[m_size
]);
167 void c_Vector::reserve(int64_t sz
) {
169 if (m_capacity
< sz
) {
173 (TypedValue
*)smart_realloc(m_data
, m_capacity
* sizeof(TypedValue
));
177 Array
c_Vector::toArrayImpl() const {
178 ArrayInit
ai(m_size
, ArrayInit::vectorInit
);
180 for (uint i
= 0; i
< sz
; ++i
) {
181 ai
.set(tvAsCVarRef(&m_data
[i
]));
186 Array
c_Vector::o_toArray() const {
187 check_collection_cast_to_array();
188 return toArrayImpl();
191 ObjectData
* c_Vector::clone() {
192 ObjectData
* obj
= ObjectData::clone();
193 auto target
= static_cast<c_Vector
*>(obj
);
196 target
->m_capacity
= target
->m_size
= sz
;
197 target
->m_data
= data
= (TypedValue
*)smart_malloc(sz
* sizeof(TypedValue
));
198 for (int i
= 0; i
< sz
; ++i
) {
199 tvDup(&m_data
[i
], &data
[i
]);
204 Object
c_Vector::t_add(CVarRef val
) {
205 TypedValue
* tv
= cvarToCell(&val
);
210 Object
c_Vector::t_addall(CVarRef iterable
) {
212 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
214 reserve(m_size
+ sz
);
216 for (; iter
; ++iter
) {
217 Variant v
= iter
.second();
218 TypedValue
* tv
= cvarToCell(&v
);
224 Object
c_Vector::t_append(CVarRef val
) {
225 TypedValue
* tv
= cvarToCell(&val
);
230 Variant
c_Vector::t_pop() {
234 Variant ret
= tvAsCVarRef(&m_data
[m_size
]);
235 tvRefcountedDecRef(&m_data
[m_size
]);
238 Object
e(SystemLib::AllocRuntimeExceptionObject(
239 "Cannot pop empty Vector"));
244 void c_Vector::t_resize(CVarRef sz
, CVarRef value
) {
245 if (!sz
.isInteger()) {
246 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
247 "Parameter sz must be a non-negative integer"));
250 int64_t intSz
= sz
.toInt64();
252 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
253 "Parameter sz must be a non-negative integer"));
256 TypedValue
* val
= cvarToCell(&value
);
260 Object
c_Vector::t_clear() {
263 for (int i
= 0; i
< sz
; ++i
) {
264 tvRefcountedDecRef(&m_data
[i
]);
273 bool c_Vector::t_isempty() {
274 return (m_size
== 0);
277 int64_t c_Vector::t_count() {
281 Object
c_Vector::t_items() {
282 return SystemLib::AllocIterableViewObject(this);
285 Object
c_Vector::t_keys() {
287 Object obj
= vec
= NEWOBJ(c_Vector
)();
288 vec
->reserve(m_size
);
289 vec
->m_size
= m_size
;
290 for (uint i
= 0; i
< m_size
; ++i
) {
291 vec
->m_data
[i
].m_data
.num
= i
;
292 vec
->m_data
[i
].m_type
= KindOfInt64
;
297 Object
c_Vector::t_view() {
298 return SystemLib::AllocKeyedIterableViewObject(this);
301 Object
c_Vector::t_kvzip() {
303 Object obj
= vec
= NEWOBJ(c_Vector
)();
304 vec
->reserve(m_size
);
305 for (uint i
= 0; i
< m_size
; ++i
) {
306 c_Pair
* pair
= NEWOBJ(c_Pair
)();
308 pair
->elm0
.m_type
= KindOfInt64
;
309 pair
->elm0
.m_data
.num
= i
;
311 pair
->add(&m_data
[i
]);
312 vec
->m_data
[i
].m_data
.pobj
= pair
;
313 vec
->m_data
[i
].m_type
= KindOfObject
;
319 Variant
c_Vector::t_at(CVarRef key
) {
320 if (key
.isInteger()) {
321 return tvAsCVarRef(at(key
.toInt64()));
324 return uninit_null();
327 Variant
c_Vector::t_get(CVarRef key
) {
328 if (key
.isInteger()) {
329 TypedValue
* tv
= get(key
.toInt64());
331 return tvAsCVarRef(tv
);
333 return uninit_null();
337 return uninit_null();
340 bool c_Vector::t_contains(CVarRef key
) {
341 return t_containskey(key
);
344 bool c_Vector::t_containskey(CVarRef key
) {
345 if (key
.isInteger()) {
346 return contains(key
.toInt64());
352 Object
c_Vector::t_removekey(CVarRef key
) {
353 if (!key
.isInteger()) {
356 int64_t k
= key
.toInt64();
360 uint64_t datum
= m_data
[k
].m_data
.num
;
361 DataType t
= m_data
[k
].m_type
;
363 memmove(&m_data
[k
], &m_data
[k
+1],
364 (m_size
-(k
+1)) * sizeof(TypedValue
));
367 tvRefcountedDecRefHelper(t
, datum
);
371 Array
c_Vector::t_toarray() {
372 return toArrayImpl();
375 void c_Vector::t_sort(CVarRef col
/* = null */) {
376 raise_warning("Vector::sort() is deprecated, please use the builtin sort() "
377 "function or usort() function instead");
378 // Terribly inefficient, but produces correct results for now
379 Variant arr
= t_toarray();
383 if (!col
.isObject()) {
384 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
385 "Expected col to be an instance of Collator"));
388 ObjectData
* obj
= col
.getObjectData();
389 if (!obj
->instanceof(c_Collator::s_cls
)) {
390 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
391 "Expected col to be an instance of Collator"));
394 auto collator
= static_cast<c_Collator
*>(obj
);
395 // TODO Task #1429976: What do we do if the Collator encountered errors
396 // while sorting? How is this reported to the user?
397 collator
->t_sort(ref(arr
));
399 if (!arr
.isArray()) {
403 ArrayData
* ad
= arr
.getArrayData();
405 ssize_t pos
= ad
->iter_begin();
406 for (int i
= 0; i
< sz
; ++i
, pos
= ad
->iter_advance(pos
)) {
407 assert(pos
!= ArrayData::invalid_index
);
408 tvAsVariant(&m_data
[i
]) = ad
->getValue(pos
);
412 void c_Vector::t_reverse() {
413 if (m_size
<= 1) return;
414 TypedValue
* start
= m_data
;
415 TypedValue
* end
= m_data
+ m_size
- 1;
416 for (; start
< end
; ++start
, --end
) {
417 std::swap(start
->m_data
.num
, end
->m_data
.num
);
418 std::swap(start
->m_type
, end
->m_type
);
422 void c_Vector::t_splice(CVarRef offset
, CVarRef len
/* = null */,
423 CVarRef replacement
/* = null */) {
424 if (!offset
.isInteger()) {
425 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
426 "Parameter offset must be an integer"));
429 if (!len
.isNull() && !len
.isInteger()) {
430 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
431 "Parameter len must be null or an integer"));
434 if (!replacement
.isNull()) {
435 Object
e(SystemLib::AllocRuntimeExceptionObject(
436 "Vector::splice does not support replacement parameter"));
440 int64_t startPos
= offset
.toInt64();
441 if (UNLIKELY(uint64_t(startPos
) >= uint64_t(sz
))) {
451 if (len
.isInteger()) {
452 int64_t intLen
= len
.toInt64();
453 if (LIKELY(intLen
> 0)) {
454 endPos
= startPos
+ intLen
;
462 endPos
= sz
+ intLen
;
463 if (endPos
<= startPos
) {
470 // Null out each element before decreffing it. We need to do this in case
471 // a __destruct method reenters and accesses this Vector object.
472 for (int64_t i
= startPos
; i
< endPos
; ++i
) {
473 uint64_t datum
= m_data
[i
].m_data
.num
;
474 DataType t
= m_data
[i
].m_type
;
475 tvWriteNull(&m_data
[i
]);
476 tvRefcountedDecRefHelper(t
, datum
);
478 // Move elements that came after the deleted elements (if there are any)
480 memmove(&m_data
[startPos
], &m_data
[endPos
],
481 (sz
- endPos
) * sizeof(TypedValue
));
483 m_size
-= (endPos
- startPos
);
486 int64_t c_Vector::t_linearsearch(CVarRef search_value
) {
488 for (uint i
= 0; i
< sz
; ++i
) {
489 if (search_value
.same(tvAsCVarRef(&m_data
[i
]))) {
496 void c_Vector::t_shuffle() {
497 for (uint i
= 1; i
< m_size
; ++i
) {
498 uint j
= f_mt_rand(0, i
);
499 std::swap(m_data
[i
], m_data
[j
]);
503 Object
c_Vector::t_getiterator() {
504 c_VectorIterator
* it
= NEWOBJ(c_VectorIterator
)();
507 it
->m_version
= getVersion();
511 Object
c_Vector::t_map(CVarRef callback
) {
513 vm_decode_function(callback
, nullptr, false, ctx
);
515 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
516 "Parameter must be a valid callback"));
520 Object obj
= vec
= NEWOBJ(c_Vector
)();
521 vec
->reserve(m_size
);
522 for (uint i
= 0; i
< m_size
; ++i
) {
523 TypedValue
* tv
= &vec
->m_data
[i
];
524 int32_t version
= m_version
;
526 tvDup(&m_data
[i
], args
+ 0);
527 g_vmContext
->invokeFuncFew(tv
, ctx
, 1, args
);
528 if (UNLIKELY(version
!= m_version
)) {
529 tvRefcountedDecRef(tv
);
530 throw_collection_modified();
537 Object
c_Vector::t_filter(CVarRef callback
) {
539 vm_decode_function(callback
, nullptr, false, ctx
);
541 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
542 "Parameter must be a valid callback"));
546 Object obj
= vec
= NEWOBJ(c_Vector
)();
547 for (uint i
= 0; i
< m_size
; ++i
) {
549 int32_t version
= m_version
;
551 tvDup(&m_data
[i
], args
+ 0);
552 g_vmContext
->invokeFuncFew(ret
.asTypedValue(), ctx
, 1, args
);
553 if (UNLIKELY(version
!= m_version
)) {
554 throw_collection_modified();
556 if (ret
.toBoolean()) {
557 vec
->add(&m_data
[i
]);
563 Object
c_Vector::t_zip(CVarRef iterable
) {
565 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
567 Object obj
= vec
= NEWOBJ(c_Vector
)();
568 vec
->reserve(std::min(sz
, size_t(m_size
)));
569 for (uint i
= 0; i
< m_size
&& iter
; ++i
, ++iter
) {
570 Variant v
= iter
.second();
571 if (vec
->m_capacity
<= vec
->m_size
) {
574 c_Pair
* pair
= NEWOBJ(c_Pair
)();
576 pair
->add(&m_data
[i
]);
577 pair
->add(cvarToCell(&v
));
578 vec
->m_data
[i
].m_data
.pobj
= pair
;
579 vec
->m_data
[i
].m_type
= KindOfObject
;
585 Object
c_Vector::t_set(CVarRef key
, CVarRef value
) {
586 if (key
.isInteger()) {
587 TypedValue
* tv
= cvarToCell(&value
);
588 set(key
.toInt64(), tv
);
595 Object
c_Vector::t_setall(CVarRef iterable
) {
597 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
598 for (; iter
; ++iter
) {
599 Variant k
= iter
.first();
600 Variant v
= iter
.second();
601 TypedValue
* tvKey
= cvarToCell(&k
);
602 TypedValue
* tvVal
= cvarToCell(&v
);
603 if (tvKey
->m_type
!= KindOfInt64
) {
606 set(tvKey
->m_data
.num
, tvVal
);
611 Object
c_Vector::t_put(CVarRef key
, CVarRef value
) {
612 return t_set(key
, value
);
615 Object
c_Vector::ti_fromitems(CVarRef iterable
) {
617 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
619 Object ret
= target
= NEWOBJ(c_Vector
)();
620 for (uint i
= 0; iter
; ++i
, ++iter
) {
621 Variant v
= iter
.second();
622 TypedValue
* tv
= tvToCell(v
.asTypedValue());
628 Object
c_Vector::ti_fromarray(CVarRef arr
) {
629 if (!arr
.isArray()) {
630 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
631 "Parameter arr must be an array"));
635 Object ret
= target
= NEWOBJ(c_Vector
)();
636 ArrayData
* ad
= arr
.getArrayData();
637 uint sz
= ad
->size();
638 target
->m_capacity
= target
->m_size
= sz
;
640 target
->m_data
= data
= (TypedValue
*)smart_malloc(sz
* sizeof(TypedValue
));
641 ssize_t pos
= ad
->iter_begin();
642 for (uint i
= 0; i
< sz
; ++i
, pos
= ad
->iter_advance(pos
)) {
643 assert(pos
!= ArrayData::invalid_index
);
644 TypedValue
* tv
= cvarToCell(&ad
->getValueRef(pos
));
645 tvRefcountedIncRef(tv
);
646 data
[i
].m_data
.num
= tv
->m_data
.num
;
647 data
[i
].m_type
= tv
->m_type
;
652 Object
c_Vector::ti_fromvector(CVarRef vec
) {
653 if (!vec
.isObject()) {
654 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
655 "vec must be an instance of Vector"));
658 ObjectData
* obj
= vec
.getObjectData();
659 if (!obj
->instanceof(c_Vector::s_cls
)) {
660 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
661 "vec must be an instance of Vector"));
664 auto v
= static_cast<c_Vector
*>(obj
);
666 Object ret
= target
= NEWOBJ(c_Vector
)();
669 target
->m_capacity
= target
->m_size
= sz
;
670 target
->m_data
= data
= (TypedValue
*)smart_malloc(sz
* sizeof(TypedValue
));
671 for (uint i
= 0; i
< sz
; ++i
) {
672 tvDup(&v
->m_data
[i
], &data
[i
]);
677 Variant
c_Vector::ti_slice(CVarRef vec
, CVarRef offset
,
678 CVarRef len
/* = null */) {
679 if (!vec
.isObject()) {
680 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
681 "vec must be an instance of Vector"));
684 ObjectData
* obj
= vec
.getObjectData();
685 if (!obj
->instanceof(c_Vector::s_cls
)) {
686 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
687 "vec must be an instance of Vector"));
690 if (!offset
.isInteger()) {
691 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
692 "Parameter offset must be an integer"));
695 if (!len
.isNull() && !len
.isInteger()) {
696 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
697 "Parameter len must be null or an integer"));
701 Object ret
= target
= NEWOBJ(c_Vector
)();
702 auto v
= static_cast<c_Vector
*>(obj
);
703 int64_t sz
= v
->m_size
;
704 int64_t startPos
= offset
.toInt64();
705 if (UNLIKELY(uint64_t(startPos
) >= uint64_t(sz
))) {
715 if (len
.isInteger()) {
716 int64_t intLen
= len
.toInt64();
717 if (LIKELY(intLen
> 0)) {
718 endPos
= startPos
+ intLen
;
726 endPos
= sz
+ intLen
;
727 if (endPos
<= startPos
) {
734 assert(startPos
< endPos
);
735 uint targetSize
= endPos
- startPos
;
737 target
->m_capacity
= target
->m_size
= targetSize
;
738 target
->m_data
= data
=
739 (TypedValue
*)smart_malloc(targetSize
* sizeof(TypedValue
));
740 for (uint i
= 0; i
< targetSize
; ++i
, ++startPos
) {
741 tvDup(&v
->m_data
[startPos
], &data
[i
]);
746 void c_Vector::throwOOB(int64_t key
) {
747 throwIntOOB(key
, true);
750 struct VectorValAccessor
{
751 typedef const TypedValue
& ElmT
;
752 bool isInt(ElmT elm
) const { return elm
.m_type
== KindOfInt64
; }
753 bool isStr(ElmT elm
) const { return IS_STRING_TYPE(elm
.m_type
); }
754 int64_t getInt(ElmT elm
) const { return elm
.m_data
.num
; }
755 StringData
* getStr(ElmT elm
) const { return elm
.m_data
.pstr
; }
756 Variant
getValue(ElmT elm
) const { return tvAsCVarRef(&elm
); }
760 * preSort() does an initial pass over the array to do some preparatory work
761 * before the sort algorithm runs. For sorts that use builtin comparators, the
762 * types of values are also observed during this first pass. By observing the
763 * types during this initial pass, we can often use a specialized comparator
764 * and avoid performing type checks during the actual sort.
766 template <typename AccessorT
>
767 c_Vector::SortFlavor
c_Vector::preSort(const AccessorT
& acc
) {
772 for (uint i
= 0; i
< sz
; ++i
) {
773 allInts
= (allInts
&& acc
.isInt(m_data
[i
]));
774 allStrs
= (allStrs
&& acc
.isStr(m_data
[i
]));
776 return allStrs
? StringSort
: allInts
? IntegerSort
: GenericSort
;
779 #define SORT_CASE(flag, cmp_type, acc_type) \
782 cmp_type##Compare<acc_type, flag, true> comp; \
783 HPHP::Sort::sort(m_data, m_data + m_size, comp); \
785 cmp_type##Compare<acc_type, flag, false> comp; \
786 HPHP::Sort::sort(m_data, m_data + m_size, comp); \
790 #define SORT_CASE_BLOCK(cmp_type, acc_type) \
791 switch (sort_flags) { \
792 default: /* fall through to SORT_REGULAR case */ \
793 SORT_CASE(SORT_REGULAR, cmp_type, acc_type) \
794 SORT_CASE(SORT_NUMERIC, cmp_type, acc_type) \
795 SORT_CASE(SORT_STRING, cmp_type, acc_type) \
796 SORT_CASE(SORT_LOCALE_STRING, cmp_type, acc_type) \
797 SORT_CASE(SORT_NATURAL, cmp_type, acc_type) \
798 SORT_CASE(SORT_NATURAL_CASE, cmp_type, acc_type) \
800 #define CALL_SORT(acc_type) \
801 if (flav == StringSort) { \
802 SORT_CASE_BLOCK(StrElm, acc_type) \
803 } else if (flav == IntegerSort) { \
804 SORT_CASE_BLOCK(IntElm, acc_type) \
806 SORT_CASE_BLOCK(Elm, acc_type) \
809 void c_Vector::sort(int sort_flags
, bool ascending
) {
813 SortFlavor flav
= preSort
<VectorValAccessor
>(VectorValAccessor());
814 CALL_SORT(VectorValAccessor
);
818 #undef SORT_CASE_BLOCK
821 void c_Vector::usort(CVarRef cmp_function
) {
825 ElmUCompare
<VectorValAccessor
> comp
;
827 Transl::CallerFrame cf
;
828 vm_decode_function(cmp_function
, cf(), false, ctx
);
830 HPHP::Sort::sort(m_data
, m_data
+ m_size
, comp
);
833 void c_Vector::throwBadKeyType() {
834 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
835 "Only integer keys may be used with Vectors"));
839 TypedValue
* c_Vector::OffsetGet(ObjectData
* obj
, TypedValue
* key
) {
840 assert(key
->m_type
!= KindOfRef
);
841 auto vec
= static_cast<c_Vector
*>(obj
);
842 if (key
->m_type
== KindOfInt64
) {
843 return vec
->at(key
->m_data
.num
);
849 void c_Vector::OffsetSet(ObjectData
* obj
, TypedValue
* key
, TypedValue
* val
) {
850 assert(key
->m_type
!= KindOfRef
);
851 assert(val
->m_type
!= KindOfRef
);
852 auto vec
= static_cast<c_Vector
*>(obj
);
853 if (key
->m_type
== KindOfInt64
) {
854 vec
->set(key
->m_data
.num
, val
);
860 bool c_Vector::OffsetIsset(ObjectData
* obj
, TypedValue
* key
) {
861 assert(key
->m_type
!= KindOfRef
);
862 auto vec
= static_cast<c_Vector
*>(obj
);
864 if (key
->m_type
== KindOfInt64
) {
865 result
= vec
->get(key
->m_data
.num
);
870 return result
? isset(tvAsCVarRef(result
)) : false;
873 bool c_Vector::OffsetEmpty(ObjectData
* obj
, TypedValue
* key
) {
874 assert(key
->m_type
!= KindOfRef
);
875 auto vec
= static_cast<c_Vector
*>(obj
);
877 if (key
->m_type
== KindOfInt64
) {
878 result
= vec
->get(key
->m_data
.num
);
883 return result
? empty(tvAsCVarRef(result
)) : true;
886 bool c_Vector::OffsetContains(ObjectData
* obj
, TypedValue
* key
) {
887 assert(key
->m_type
!= KindOfRef
);
888 auto vec
= static_cast<c_Vector
*>(obj
);
889 if (key
->m_type
== KindOfInt64
) {
890 return vec
->contains(key
->m_data
.num
);
897 void c_Vector::OffsetAppend(ObjectData
* obj
, TypedValue
* val
) {
898 assert(val
->m_type
!= KindOfRef
);
899 auto vec
= static_cast<c_Vector
*>(obj
);
903 void c_Vector::OffsetUnset(ObjectData
* obj
, TypedValue
* key
) {
904 Object
e(SystemLib::AllocRuntimeExceptionObject(
905 "Cannot unset element of a Vector"));
909 bool c_Vector::Equals(ObjectData
* obj1
, ObjectData
* obj2
) {
910 auto vec1
= static_cast<c_Vector
*>(obj1
);
911 auto vec2
= static_cast<c_Vector
*>(obj2
);
912 uint sz
= vec1
->m_size
;
913 if (sz
!= vec2
->m_size
) {
916 for (uint i
= 0; i
< sz
; ++i
) {
917 if (!equal(tvAsCVarRef(&vec1
->m_data
[i
]),
918 tvAsCVarRef(&vec2
->m_data
[i
]))) {
925 void c_Vector::Unserialize(ObjectData
* obj
,
926 VariableUnserializer
* uns
,
930 throw Exception("Vector does not support the '%c' serialization "
933 auto vec
= static_cast<c_Vector
*>(obj
);
935 for (int64_t i
= 0; i
< sz
; ++i
) {
936 auto tv
= &vec
->m_data
[vec
->m_size
];
937 tv
->m_type
= KindOfNull
;
939 tvAsVariant(tv
).unserialize(uns
, Uns::ColValueMode
);
943 c_VectorIterator::c_VectorIterator(Class
* cb
) :
947 c_VectorIterator::~c_VectorIterator() {
950 void c_VectorIterator::t___construct() {
953 Variant
c_VectorIterator::t_current() {
954 c_Vector
* vec
= m_obj
.get();
955 if (UNLIKELY(m_version
!= vec
->getVersion())) {
956 throw_collection_modified();
958 if (!vec
->contains(m_pos
)) {
959 throw_iterator_not_valid();
961 return tvAsCVarRef(&vec
->m_data
[m_pos
]);
964 Variant
c_VectorIterator::t_key() {
965 c_Vector
* vec
= m_obj
.get();
966 if (!vec
->contains(m_pos
)) {
967 throw_iterator_not_valid();
972 bool c_VectorIterator::t_valid() {
974 c_Vector
* vec
= m_obj
.get();
975 return vec
&& (m_pos
< (ssize_t
)vec
->m_size
);
978 void c_VectorIterator::t_next() {
982 void c_VectorIterator::t_rewind() {
986 ///////////////////////////////////////////////////////////////////////////////
988 static const char emptyMapSlot
[sizeof(c_Map::Bucket
)] = { 0 };
990 c_Map::c_Map(Class
* cb
) :
991 ExtObjectDataFlags
<ObjectData::MapAttrInit
|
994 ObjectData::UseIsset
|
995 ObjectData::UseUnset
>(cb
),
996 m_size(0), m_load(0), m_nLastSlot(0), m_version(0) {
997 m_data
= (Bucket
*)emptyMapSlot
;
1005 void c_Map::freeData() {
1006 if (m_data
!= (Bucket
*)emptyMapSlot
) {
1009 m_data
= (Bucket
*)emptyMapSlot
;
1012 void c_Map::deleteBuckets() {
1013 if (!m_size
) return;
1014 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1015 Bucket
& p
= m_data
[i
];
1016 if (p
.validValue()) {
1017 tvRefcountedDecRef(&p
.data
);
1018 if (p
.hasStrKey() && p
.skey
->decRefCount() == 0) {
1019 DELETE(StringData
)(p
.skey
);
1025 void c_Map::t___construct(CVarRef iterable
/* = null_variant */) {
1026 if (!iterable
.isInitialized()) {
1030 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
1034 for (; iter
; ++iter
) {
1035 Variant k
= iter
.first();
1036 Variant v
= iter
.second();
1037 TypedValue
* tv
= tvToCell(v
.asTypedValue());
1038 if (k
.isInteger()) {
1039 update(k
.toInt64(), tv
);
1040 } else if (k
.isString()) {
1041 update(k
.getStringData(), tv
);
1048 Array
c_Map::toArrayImpl() const {
1049 ArrayInit
ai(m_size
);
1050 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1051 Bucket
& p
= m_data
[i
];
1052 if (p
.validValue()) {
1053 if (p
.hasIntKey()) {
1054 ai
.set((int64_t)p
.ikey
, tvAsCVarRef(&p
.data
));
1056 ai
.set(*(const String
*)(&p
.skey
), tvAsCVarRef(&p
.data
));
1063 Array
c_Map::o_toArray() const {
1064 check_collection_cast_to_array();
1065 return toArrayImpl();
1068 ObjectData
* c_Map::clone() {
1069 ObjectData
* obj
= ObjectData::clone();
1070 auto target
= static_cast<c_Map
*>(obj
);
1072 if (!m_size
) return obj
;
1074 assert(m_nLastSlot
!= 0);
1075 target
->m_size
= m_size
;
1076 target
->m_load
= m_load
;
1077 target
->m_nLastSlot
= m_nLastSlot
;
1078 target
->m_data
= (Bucket
*)smart_malloc(numSlots() * sizeof(Bucket
));
1079 memcpy(target
->m_data
, m_data
, numSlots() * sizeof(Bucket
));
1081 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1082 Bucket
& p
= m_data
[i
];
1083 if (p
.validValue()) {
1084 tvRefcountedIncRef(&p
.data
);
1085 if (p
.hasStrKey()) {
1086 p
.skey
->incRefCount();
1094 Object
c_Map::t_add(CVarRef val
) {
1095 TypedValue
* tv
= cvarToCell(&val
);
1100 Object
c_Map::t_addall(CVarRef iterable
) {
1102 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
1103 reserve(std::max(sz
, size_t(m_size
)));
1104 for (; iter
; ++iter
) {
1105 Variant v
= iter
.second();
1106 TypedValue
* tv
= cvarToCell(&v
);
1112 Object
c_Map::t_clear() {
1118 m_data
= (Bucket
*)emptyMapSlot
;
1122 bool c_Map::t_isempty() {
1123 return (m_size
== 0);
1126 int64_t c_Map::t_count() {
1130 Object
c_Map::t_items() {
1131 return SystemLib::AllocKVZippedIterableObject(this);
1134 Object
c_Map::t_keys() {
1136 Object obj
= vec
= NEWOBJ(c_Vector
)();
1137 vec
->reserve(m_size
);
1138 for (uint i
= 0, j
= 0; i
<= m_nLastSlot
; ++i
) {
1139 Bucket
& p
= m_data
[i
];
1140 if (!p
.validValue()) continue;
1141 if (p
.hasIntKey()) {
1142 vec
->m_data
[j
].m_data
.num
= p
.ikey
;
1143 vec
->m_data
[j
].m_type
= KindOfInt64
;
1145 p
.skey
->incRefCount();
1146 vec
->m_data
[j
].m_data
.pstr
= p
.skey
;
1147 vec
->m_data
[j
].m_type
= KindOfString
;
1155 Object
c_Map::t_view() {
1156 return SystemLib::AllocKeyedIterableViewObject(this);
1159 Object
c_Map::t_kvzip() {
1161 Object obj
= vec
= NEWOBJ(c_Vector
)();
1162 vec
->reserve(m_size
);
1163 for (uint i
= 0, j
= 0; i
<= m_nLastSlot
; ++i
) {
1164 Bucket
& p
= m_data
[i
];
1165 if (!p
.validValue()) continue;
1166 c_Pair
* pair
= NEWOBJ(c_Pair
)();
1167 pair
->incRefCount();
1168 if (p
.hasIntKey()) {
1169 pair
->elm0
.m_data
.num
= p
.ikey
;
1170 pair
->elm0
.m_type
= KindOfInt64
;
1172 p
.skey
->incRefCount();
1173 pair
->elm0
.m_data
.pstr
= p
.skey
;
1174 pair
->elm0
.m_type
= KindOfString
;
1178 vec
->m_data
[j
].m_data
.pobj
= pair
;
1179 vec
->m_data
[j
].m_type
= KindOfObject
;
1186 Variant
c_Map::t_at(CVarRef key
) {
1187 if (key
.isInteger()) {
1188 return tvAsCVarRef(at(key
.toInt64()));
1189 } else if (key
.isString()) {
1190 return tvAsCVarRef(at(key
.getStringData()));
1193 return uninit_null();
1196 Variant
c_Map::t_get(CVarRef key
) {
1197 if (key
.isInteger()) {
1198 TypedValue
* tv
= get(key
.toInt64());
1200 return tvAsCVarRef(tv
);
1202 return uninit_null();
1204 } else if (key
.isString()) {
1205 TypedValue
* tv
= get(key
.getStringData());
1207 return tvAsCVarRef(tv
);
1209 return uninit_null();
1213 return uninit_null();
1216 Object
c_Map::t_set(CVarRef key
, CVarRef value
) {
1217 TypedValue
* val
= cvarToCell(&value
);
1218 if (key
.isInteger()) {
1219 update(key
.toInt64(), val
);
1220 } else if (key
.isString()) {
1221 update(key
.getStringData(), val
);
1228 Object
c_Map::t_setall(CVarRef iterable
) {
1230 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
1231 for (; iter
; ++iter
) {
1232 Variant k
= iter
.first();
1233 Variant v
= iter
.second();
1234 TypedValue
* tvKey
= cvarToCell(&k
);
1235 TypedValue
* tvVal
= cvarToCell(&v
);
1236 if (tvKey
->m_type
== KindOfInt64
) {
1237 set(tvKey
->m_data
.num
, tvVal
);
1238 } else if (IS_STRING_TYPE(tvKey
->m_type
)) {
1239 set(tvKey
->m_data
.pstr
, tvVal
);
1247 Object
c_Map::t_put(CVarRef key
, CVarRef value
) {
1248 return t_set(key
, value
);
1251 bool c_Map::t_contains(CVarRef key
) {
1252 DataType t
= key
.getType();
1253 if (t
== KindOfInt64
) {
1254 return contains(key
.toInt64());
1256 if (IS_STRING_TYPE(t
)) {
1257 return contains(key
.getStringData());
1263 bool c_Map::t_containskey(CVarRef key
) {
1264 return t_contains(key
);
1267 Object
c_Map::t_remove(CVarRef key
) {
1268 DataType t
= key
.getType();
1269 if (t
== KindOfInt64
) {
1270 remove(key
.toInt64());
1271 } else if (IS_STRING_TYPE(t
)) {
1272 remove(key
.getStringData());
1279 Object
c_Map::t_removekey(CVarRef key
) {
1280 return t_remove(key
);
1283 Object
c_Map::t_discard(CVarRef key
) {
1284 return t_remove(key
);
1287 Array
c_Map::t_toarray() {
1288 return toArrayImpl();
1291 Array
c_Map::t_copyasarray() {
1292 return toArrayImpl();
1295 Array
c_Map::t_tokeysarray() {
1296 ArrayInit
ai(m_size
, ArrayInit::vectorInit
);
1297 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1298 Bucket
& p
= m_data
[i
];
1299 if (!p
.validValue()) continue;
1300 if (p
.hasIntKey()) {
1301 ai
.set((int64_t)p
.ikey
);
1303 ai
.set(*(const String
*)(&p
.skey
));
1309 Object
c_Map::t_values() {
1311 Object ret
= target
= NEWOBJ(c_Vector
)();
1312 int64_t sz
= m_size
;
1317 target
->m_capacity
= target
->m_size
= m_size
;
1318 target
->m_data
= data
= (TypedValue
*)smart_malloc(sz
* sizeof(TypedValue
));
1321 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1322 Bucket
& p
= m_data
[i
];
1323 if (!p
.validValue()) continue;
1324 TypedValue
* tv
= &p
.data
;
1325 tvRefcountedIncRef(tv
);
1326 data
[j
].m_data
.num
= tv
->m_data
.num
;
1327 data
[j
].m_type
= tv
->m_type
;
1333 Array
c_Map::t_tovaluesarray() {
1334 ArrayInit
ai(m_size
, ArrayInit::vectorInit
);
1335 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1336 Bucket
& p
= m_data
[i
];
1337 if (!p
.validValue()) continue;
1338 ai
.set(tvAsCVarRef(&p
.data
));
1343 Object
c_Map::t_updatefromarray(CVarRef arr
) {
1344 if (!arr
.isArray()) {
1345 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1346 "Expected arr to be an array"));
1349 ArrayData
* ad
= arr
.getArrayData();
1350 for (ssize_t pos
= ad
->iter_begin(); pos
!= ArrayData::invalid_index
;
1351 pos
= ad
->iter_advance(pos
)) {
1352 Variant k
= ad
->getKey(pos
);
1353 TypedValue
* tv
= cvarToCell(&ad
->getValueRef(pos
));
1354 if (k
.isInteger()) {
1355 update(k
.toInt64(), tv
);
1357 assert(k
.isString());
1358 update(k
.getStringData(), tv
);
1364 Object
c_Map::t_updatefromiterable(CVarRef it
) {
1365 if (!it
.isObject()) {
1366 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1367 "Parameter it must be an instance of Iterable"));
1370 ObjectData
* obj
= it
.getObjectData();
1371 if (obj
->getCollectionType() == Collection::MapType
) {
1372 auto mp
= static_cast<c_Map
*>(obj
);
1373 for (uint i
= 0; i
<= mp
->m_nLastSlot
; ++i
) {
1374 c_Map::Bucket
& p
= mp
->m_data
[i
];
1375 if (!p
.validValue()) continue;
1376 if (p
.hasIntKey()) {
1377 update((int64_t)p
.ikey
, &p
.data
);
1379 update(p
.skey
, &p
.data
);
1384 for (ArrayIter iter
= obj
->begin(); iter
; ++iter
) {
1385 Variant k
= iter
.first();
1386 Variant v
= iter
.second();
1387 TypedValue
* tv
= tvToCell(v
.asTypedValue());
1388 if (k
.isInteger()) {
1389 update(k
.toInt64(), tv
);
1391 assert(k
.isString());
1392 update(k
.getStringData(), tv
);
1398 Object
c_Map::t_differencebykey(CVarRef it
) {
1399 if (!it
.isObject()) {
1400 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1401 "Parameter it must be an instance of Iterable"));
1404 ObjectData
* obj
= it
.getObjectData();
1406 Object ret
= target
= static_cast<c_Map
*>(clone());
1407 if (obj
->getCollectionType() == Collection::MapType
) {
1408 auto mp
= static_cast<c_Map
*>(obj
);
1409 for (uint i
= 0; i
<= mp
->m_nLastSlot
; ++i
) {
1410 c_Map::Bucket
& p
= mp
->m_data
[i
];
1411 if (!p
.validValue()) continue;
1412 if (p
.hasIntKey()) {
1413 target
->remove((int64_t)p
.ikey
);
1415 target
->remove(p
.skey
);
1420 for (ArrayIter iter
= obj
->begin(); iter
; ++iter
) {
1421 Variant k
= iter
.first();
1422 if (k
.isInteger()) {
1423 target
->remove(k
.toInt64());
1425 assert(k
.isString());
1426 target
->remove(k
.getStringData());
1432 Object
c_Map::t_getiterator() {
1433 c_MapIterator
* it
= NEWOBJ(c_MapIterator
)();
1435 it
->m_pos
= iter_begin();
1436 it
->m_version
= getVersion();
1440 Object
c_Map::t_map(CVarRef callback
) {
1442 vm_decode_function(callback
, nullptr, false, ctx
);
1444 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1445 "Parameter must be a valid callback"));
1449 Object obj
= mp
= NEWOBJ(c_Map
)();
1450 if (!m_size
) return obj
;
1451 assert(m_nLastSlot
!= 0);
1452 mp
->m_size
= m_size
;
1453 mp
->m_load
= m_load
;
1454 mp
->m_nLastSlot
= 0;
1455 mp
->m_data
= (Bucket
*)smart_malloc(numSlots() * sizeof(Bucket
));
1456 // We need to zero out the first slot in case an exception
1457 // is thrown during the first iteration, because ~c_Map()
1458 // will decRef all slots up to (and including) m_nLastSlot.
1459 mp
->m_data
[0].data
.m_type
= (DataType
)0;
1460 for (uint i
= 0; i
<= m_nLastSlot
; mp
->m_nLastSlot
= i
++) {
1461 Bucket
& p
= m_data
[i
];
1462 Bucket
& np
= mp
->m_data
[i
];
1463 if (!p
.validValue()) {
1464 np
.data
.m_type
= p
.data
.m_type
;
1467 TypedValue
* tv
= &np
.data
;
1468 int32_t version
= m_version
;
1470 tvDup(&p
.data
, args
+ 0);
1471 g_vmContext
->invokeFuncFew(tv
, ctx
, 1, args
);
1472 if (UNLIKELY(version
!= m_version
)) {
1473 tvRefcountedDecRef(tv
);
1474 throw_collection_modified();
1476 if (p
.hasStrKey()) {
1477 p
.skey
->incRefCount();
1480 np
.data
.hash() = p
.data
.hash();
1485 Object
c_Map::t_filter(CVarRef callback
) {
1487 vm_decode_function(callback
, nullptr, false, ctx
);
1489 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1490 "Parameter must be a valid callback"));
1494 Object obj
= mp
= NEWOBJ(c_Map
)();
1495 if (!m_size
) return obj
;
1496 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1497 Bucket
& p
= m_data
[i
];
1498 if (!p
.validValue()) continue;
1500 int32_t version
= m_version
;
1502 tvDup(&p
.data
, args
+ 0);
1503 g_vmContext
->invokeFuncFew(ret
.asTypedValue(), ctx
, 1, args
);
1504 if (UNLIKELY(version
!= m_version
)) {
1505 throw_collection_modified();
1507 if (!ret
.toBoolean()) continue;
1508 if (p
.hasIntKey()) {
1509 mp
->update(p
.ikey
, &p
.data
);
1511 mp
->update(p
.skey
, &p
.data
);
1517 Object
c_Map::t_zip(CVarRef iterable
) {
1519 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
1521 Object obj
= mp
= NEWOBJ(c_Map
)();
1522 mp
->reserve(std::min(sz
, size_t(m_size
)));
1523 for (uint i
= 0; i
<= m_nLastSlot
&& iter
; ++i
) {
1524 Bucket
& p
= m_data
[i
];
1525 if (!p
.validValue()) continue;
1526 Variant v
= iter
.second();
1528 Object pairObj
= pair
= NEWOBJ(c_Pair
)();
1530 pair
->add(cvarToCell(&v
));
1532 tv
.m_data
.pobj
= pair
;
1533 tv
.m_type
= KindOfObject
;
1534 if (p
.hasIntKey()) {
1535 mp
->update(p
.ikey
, &tv
);
1537 mp
->update(p
.skey
, &tv
);
1544 Object
c_Map::ti_fromitems(CVarRef iterable
) {
1546 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
1548 Object ret
= target
= NEWOBJ(c_Map
)();
1550 target
->reserve(sz
);
1552 for (; iter
; ++iter
) {
1553 Variant v
= iter
.second();
1554 TypedValue
* tv
= tvToCell(v
.asTypedValue());
1555 if (UNLIKELY(tv
->m_type
!= KindOfObject
||
1556 !tv
->m_data
.pobj
->instanceof(c_Pair::s_cls
))) {
1557 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1558 "Parameter must be an instance of Iterable<Pair>"));
1561 auto pair
= static_cast<c_Pair
*>(tv
->m_data
.pobj
);
1562 TypedValue
* tvKey
= &pair
->elm0
;
1563 TypedValue
* tvValue
= &pair
->elm1
;
1564 assert(tvKey
->m_type
!= KindOfRef
);
1565 assert(tvValue
->m_type
!= KindOfRef
);
1566 if (tvKey
->m_type
== KindOfInt64
) {
1567 target
->update(tvKey
->m_data
.num
, tvValue
);
1568 } else if (IS_STRING_TYPE(tvKey
->m_type
)) {
1569 target
->update(tvKey
->m_data
.pstr
, tvValue
);
1577 Object
c_Map::ti_fromarray(CVarRef arr
) {
1578 if (!arr
.isArray()) {
1579 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1580 "Parameter arr must be an array"));
1584 Object ret
= mp
= NEWOBJ(c_Map
)();
1585 ArrayData
* ad
= arr
.getArrayData();
1586 for (ssize_t pos
= ad
->iter_begin(); pos
!= ArrayData::invalid_index
;
1587 pos
= ad
->iter_advance(pos
)) {
1588 Variant k
= ad
->getKey(pos
);
1589 TypedValue
* tv
= cvarToCell(&ad
->getValueRef(pos
));
1590 if (k
.isInteger()) {
1591 mp
->update(k
.toInt64(), tv
);
1593 assert(k
.isString());
1594 mp
->update(k
.getStringData(), tv
);
1600 Object
c_Map::ti_fromiterable(CVarRef it
) {
1601 if (!it
.isObject()) {
1602 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1603 "Parameter it must be an instance of Iterable"));
1606 ObjectData
* obj
= it
.getObjectData();
1608 if (obj
->getCollectionType() == Collection::MapType
) {
1613 ret
= target
= NEWOBJ(c_Map
)();
1614 for (ArrayIter iter
= obj
->begin(); iter
; ++iter
) {
1615 Variant k
= iter
.first();
1616 Variant v
= iter
.second();
1617 TypedValue
* tv
= cvarToCell(&v
);
1618 if (k
.isInteger()) {
1619 target
->update(k
.toInt64(), tv
);
1621 assert(k
.isString());
1622 target
->update(k
.getStringData(), tv
);
1628 void c_Map::throwOOB(int64_t key
) {
1632 void c_Map::throwOOB(StringData
* key
) {
1636 void c_Map::add(TypedValue
* val
) {
1637 if (UNLIKELY(val
->m_type
!= KindOfObject
||
1638 !val
->m_data
.pobj
->instanceof(c_Pair::s_cls
))) {
1639 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1640 "Parameter must be an instance of Pair"));
1643 auto pair
= static_cast<c_Pair
*>(val
->m_data
.pobj
);
1644 TypedValue
* tvKey
= &pair
->elm0
;
1645 TypedValue
* tvValue
= &pair
->elm1
;
1646 assert(tvKey
->m_type
!= KindOfRef
);
1647 assert(tvValue
->m_type
!= KindOfRef
);
1648 if (tvKey
->m_type
== KindOfInt64
) {
1649 update(tvKey
->m_data
.num
, tvValue
);
1650 } else if (IS_STRING_TYPE(tvKey
->m_type
)) {
1651 update(tvKey
->m_data
.pstr
, tvValue
);
1657 #define STRING_HASH(x) (int32_t(x) | 0x80000000)
1659 bool inline hitStringKey(const c_Map::Bucket
* p
, const char* k
,
1660 int len
, int32_t hash
) ALWAYS_INLINE
;
1661 bool inline hitStringKey(const c_Map::Bucket
* p
, const char* k
,
1662 int len
, int32_t hash
) {
1663 assert(p
->validValue());
1664 if (p
->hasIntKey()) return false;
1665 const char* data
= p
->skey
->data();
1666 return data
== k
|| (p
->hash() == hash
&&
1667 p
->skey
->size() == len
&&
1668 memcmp(data
, k
, len
) == 0);
1671 bool inline hitIntKey(const c_Map::Bucket
* p
, int64_t ki
) ALWAYS_INLINE
;
1672 bool inline hitIntKey(const c_Map::Bucket
* p
, int64_t ki
) {
1673 assert(p
->validValue());
1674 return p
->ikey
== ki
&& p
->hasIntKey();
1677 #define FIND_BODY(h0, hit) \
1678 size_t tableMask = m_nLastSlot; \
1679 size_t probeIndex = size_t(h0) & tableMask; \
1680 Bucket* p = fetchBucket(probeIndex); \
1681 if (LIKELY(p->validValue() && (hit))) { \
1684 if (LIKELY(p->empty())) { \
1687 for (size_t i = 1;; ++i) { \
1688 assert(i <= tableMask); \
1689 probeIndex = (probeIndex + i) & tableMask; \
1690 assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \
1691 p = fetchBucket(probeIndex); \
1692 if (p->validValue() && (hit)) { \
1700 #define FIND_FOR_INSERT_BODY(h0, hit) \
1701 size_t tableMask = m_nLastSlot; \
1702 size_t probeIndex = size_t(h0) & tableMask; \
1703 Bucket* p = fetchBucket(h0 & tableMask); \
1704 if (LIKELY((p->validValue() && (hit)) || \
1708 Bucket* ts = nullptr; \
1709 for (size_t i = 1;; ++i) { \
1710 if (UNLIKELY(p->tombstone() && !ts)) { \
1713 assert(i <= tableMask); \
1714 probeIndex = (probeIndex + i) & tableMask; \
1715 assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \
1716 p = fetchBucket(probeIndex); \
1717 if (LIKELY(p->validValue() && (hit))) { \
1720 if (LIKELY(p->empty())) { \
1721 if (LIKELY(!ts)) { \
1728 c_Map::Bucket
* c_Map::find(int64_t h
) const {
1729 FIND_BODY(h
, hitIntKey(p
, h
));
1732 c_Map::Bucket
* c_Map::find(const char* k
, int len
, strhash_t prehash
) const {
1733 FIND_BODY(prehash
, hitStringKey(p
, k
, len
, STRING_HASH(prehash
)));
1736 c_Map::Bucket
* c_Map::findForInsert(int64_t h
) const {
1737 FIND_FOR_INSERT_BODY(h
, hitIntKey(p
, h
));
1740 c_Map::Bucket
* c_Map::findForInsert(const char* k
, int len
,
1741 strhash_t prehash
) const {
1742 FIND_FOR_INSERT_BODY(prehash
, hitStringKey(p
, k
, len
, STRING_HASH(prehash
)));
1745 // findForNewInsert() is only safe to use if you know for sure that the
1746 // key is not already present in the Map.
1747 inline ALWAYS_INLINE
1748 c_Map::Bucket
* c_Map::findForNewInsert(size_t h0
) const {
1749 size_t tableMask
= m_nLastSlot
;
1750 size_t probeIndex
= h0
& tableMask
;
1751 Bucket
* p
= fetchBucket(probeIndex
);
1752 if (LIKELY(p
->empty())) {
1755 for (size_t i
= 1;; ++i
) {
1756 assert(i
<= tableMask
);
1757 probeIndex
= (probeIndex
+ i
) & tableMask
;
1758 assert(((size_t(h0
)+((i
+ i
*i
) >> 1)) & tableMask
) == probeIndex
);
1759 p
= fetchBucket(probeIndex
);
1760 if (LIKELY(p
->empty())) {
1768 #undef FIND_FOR_INSERT_BODY
1770 bool c_Map::update(int64_t h
, TypedValue
* data
) {
1771 assert(data
->m_type
!= KindOfRef
);
1772 Bucket
* p
= findForInsert(h
);
1774 if (p
->validValue()) {
1775 tvRefcountedIncRef(data
);
1776 tvRefcountedDecRef(&p
->data
);
1777 p
->data
.m_data
.num
= data
->m_data
.num
;
1778 p
->data
.m_type
= data
->m_type
;
1783 if (!p
->tombstone()) {
1784 if (UNLIKELY(++m_load
>= computeMaxLoad())) {
1786 p
= findForInsert(h
);
1790 tvRefcountedIncRef(data
);
1791 p
->data
.m_data
.num
= data
->m_data
.num
;
1792 p
->data
.m_type
= data
->m_type
;
1797 bool c_Map::update(StringData
*key
, TypedValue
* data
) {
1798 strhash_t h
= key
->hash();
1799 Bucket
* p
= findForInsert(key
->data(), key
->size(), h
);
1801 if (p
->validValue()) {
1802 tvRefcountedIncRef(data
);
1803 tvRefcountedDecRef(&p
->data
);
1804 p
->data
.m_data
.num
= data
->m_data
.num
;
1805 p
->data
.m_type
= data
->m_type
;
1810 if (!p
->tombstone()) {
1811 if (UNLIKELY(++m_load
>= computeMaxLoad())) {
1813 p
= findForInsert(key
->data(), key
->size(), h
);
1817 tvRefcountedIncRef(data
);
1818 p
->data
.m_data
.num
= data
->m_data
.num
;
1819 p
->data
.m_type
= data
->m_type
;
1820 p
->setStrKey(key
, h
);
1824 void c_Map::erase(Bucket
* p
) {
1828 if (p
->validValue()) {
1830 tvRefcountedDecRef(&p
->data
);
1831 if (p
->hasStrKey() && p
->skey
->decRefCount() == 0) {
1832 DELETE(StringData
)(p
->skey
);
1834 p
->data
.m_type
= (DataType
)KindOfTombstone
;
1835 if (m_size
< computeMinElements() && m_size
) {
1841 void c_Map::adjustCapacityImpl(int64_t sz
) {
1844 if (sz
<= 0) return;
1847 if (m_nLastSlot
== 0) {
1848 assert(m_data
== (Bucket
*)emptyMapSlot
);
1849 m_nLastSlot
= Util::roundUpToPowerOfTwo(sz
<< 1) - 1;
1850 m_data
= (Bucket
*)smart_calloc(numSlots(), sizeof(Bucket
));
1853 size_t oldNumSlots
= numSlots();
1854 m_nLastSlot
= Util::roundUpToPowerOfTwo(sz
<< 1) - 1;
1856 Bucket
* oldBuckets
= m_data
;
1857 m_data
= (Bucket
*)smart_calloc(numSlots(), sizeof(Bucket
));
1858 for (uint i
= 0; i
< oldNumSlots
; ++i
) {
1859 Bucket
* p
= &oldBuckets
[i
];
1860 if (p
->validValue()) {
1861 Bucket
* np
= findForNewInsert(p
->hasIntKey() ? p
->ikey
: p
->hash());
1862 memcpy(np
, p
, sizeof(Bucket
));
1865 smart_free(oldBuckets
);
1868 ssize_t
c_Map::iter_begin() const {
1869 if (!m_size
) return 0;
1870 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
1871 Bucket
* p
= fetchBucket(i
);
1872 if (p
->validValue()) {
1873 return reinterpret_cast<ssize_t
>(p
);
1879 ssize_t
c_Map::iter_next(ssize_t pos
) const {
1883 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
1884 Bucket
* pLast
= fetchBucket(m_nLastSlot
);
1886 while (p
<= pLast
) {
1887 if (p
->validValue()) {
1888 return reinterpret_cast<ssize_t
>(p
);
1895 ssize_t
c_Map::iter_prev(ssize_t pos
) const {
1899 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
1900 Bucket
* pStart
= m_data
;
1902 while (p
>= pStart
) {
1903 if (p
->validValue()) {
1904 return reinterpret_cast<ssize_t
>(p
);
1911 Variant
c_Map::iter_key(ssize_t pos
) const {
1913 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
1914 if (p
->hasStrKey()) {
1917 return (int64_t)p
->ikey
;
1920 Variant
c_Map::iter_value(ssize_t pos
) const {
1922 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
1923 return tvAsCVarRef(&p
->data
);
1926 void c_Map::throwBadKeyType() {
1927 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
1928 "Only integer keys and string keys may be used with Maps"));
1932 void c_Map::Bucket::dump() {
1933 if (!validValue()) {
1934 printf("c_Map::Bucket: %s\n", (empty() ? "empty" : "tombstone"));
1937 printf("c_Map::Bucket: %" PRIx64
"\n", hashKey());
1941 tvAsCVarRef(&data
).dump();
1944 TypedValue
* c_Map::OffsetGet(ObjectData
* obj
, TypedValue
* key
) {
1945 assert(key
->m_type
!= KindOfRef
);
1946 auto mp
= static_cast<c_Map
*>(obj
);
1947 if (key
->m_type
== KindOfInt64
) {
1948 return mp
->at(key
->m_data
.num
);
1950 if (IS_STRING_TYPE(key
->m_type
)) {
1951 return mp
->at(key
->m_data
.pstr
);
1957 void c_Map::OffsetSet(ObjectData
* obj
, TypedValue
* key
, TypedValue
* val
) {
1958 assert(key
->m_type
!= KindOfRef
);
1959 assert(val
->m_type
!= KindOfRef
);
1960 auto mp
= static_cast<c_Map
*>(obj
);
1961 if (key
->m_type
== KindOfInt64
) {
1962 mp
->set(key
->m_data
.num
, val
);
1965 if (IS_STRING_TYPE(key
->m_type
)) {
1966 mp
->set(key
->m_data
.pstr
, val
);
1972 bool c_Map::OffsetIsset(ObjectData
* obj
, TypedValue
* key
) {
1973 assert(key
->m_type
!= KindOfRef
);
1974 auto mp
= static_cast<c_Map
*>(obj
);
1976 if (key
->m_type
== KindOfInt64
) {
1977 result
= mp
->get(key
->m_data
.num
);
1978 } else if (IS_STRING_TYPE(key
->m_type
)) {
1979 result
= mp
->get(key
->m_data
.pstr
);
1984 return result
? isset(tvAsCVarRef(result
)) : false;
1987 bool c_Map::OffsetEmpty(ObjectData
* obj
, TypedValue
* key
) {
1988 assert(key
->m_type
!= KindOfRef
);
1989 auto mp
= static_cast<c_Map
*>(obj
);
1991 if (key
->m_type
== KindOfInt64
) {
1992 result
= mp
->get(key
->m_data
.num
);
1993 } else if (IS_STRING_TYPE(key
->m_type
)) {
1994 result
= mp
->get(key
->m_data
.pstr
);
1999 return result
? empty(tvAsCVarRef(result
)) : true;
2002 bool c_Map::OffsetContains(ObjectData
* obj
, TypedValue
* key
) {
2003 assert(key
->m_type
!= KindOfRef
);
2004 auto mp
= static_cast<c_Map
*>(obj
);
2005 if (key
->m_type
== KindOfInt64
) {
2006 return mp
->contains(key
->m_data
.num
);
2007 } else if (IS_STRING_TYPE(key
->m_type
)) {
2008 return mp
->contains(key
->m_data
.pstr
);
2015 void c_Map::OffsetAppend(ObjectData
* obj
, TypedValue
* val
) {
2016 assert(val
->m_type
!= KindOfRef
);
2017 auto mp
= static_cast<c_Map
*>(obj
);
2021 void c_Map::OffsetUnset(ObjectData
* obj
, TypedValue
* key
) {
2022 assert(key
->m_type
!= KindOfRef
);
2023 auto mp
= static_cast<c_Map
*>(obj
);
2024 if (key
->m_type
== KindOfInt64
) {
2025 mp
->remove(key
->m_data
.num
);
2028 if (IS_STRING_TYPE(key
->m_type
)) {
2029 mp
->remove(key
->m_data
.pstr
);
2035 bool c_Map::Equals(ObjectData
* obj1
, ObjectData
* obj2
) {
2036 auto mp1
= static_cast<c_Map
*>(obj1
);
2037 auto mp2
= static_cast<c_Map
*>(obj2
);
2038 if (mp1
->m_size
!= mp2
->m_size
) return false;
2039 for (uint i
= 0; i
<= mp1
->m_nLastSlot
; ++i
) {
2040 c_Map::Bucket
& p
= mp1
->m_data
[i
];
2041 if (p
.validValue()) {
2043 if (p
.hasIntKey()) {
2044 tv2
= mp2
->get(p
.ikey
);
2046 assert(p
.hasStrKey());
2047 tv2
= mp2
->get(p
.skey
);
2049 if (!tv2
) return false;
2050 if (!equal(tvAsCVarRef(&p
.data
), tvAsCVarRef(tv2
))) return false;
2056 void c_Map::Unserialize(ObjectData
* obj
,
2057 VariableUnserializer
* uns
,
2061 throw Exception("Map does not support the '%c' serialization "
2064 auto mp
= static_cast<c_Map
*>(obj
);
2066 for (int64_t i
= 0; i
< sz
; ++i
) {
2068 k
.unserialize(uns
, Uns::ColKeyMode
);
2070 if (k
.isInteger()) {
2071 auto h
= k
.toInt64();
2072 p
= mp
->findForInsert(h
);
2073 if (UNLIKELY(p
->validValue())) goto do_unserialize
;
2075 } else if (k
.isString()) {
2076 auto key
= k
.getStringData();
2077 auto h
= key
->hash();
2078 p
= mp
->findForInsert(key
->data(), key
->size(), h
);
2079 if (UNLIKELY(p
->validValue())) goto do_unserialize
;
2080 p
->setStrKey(key
, h
);
2082 throw Exception("Invalid key");
2086 p
->data
.m_type
= KindOfNull
;
2088 tvAsVariant(&p
->data
).unserialize(uns
, Uns::ColValueMode
);
2092 c_MapIterator::c_MapIterator(Class
* cb
) :
2096 c_MapIterator::~c_MapIterator() {
2099 void c_MapIterator::t___construct() {
2102 Variant
c_MapIterator::t_current() {
2103 c_Map
* mp
= m_obj
.get();
2104 if (UNLIKELY(m_version
!= mp
->getVersion())) {
2105 throw_collection_modified();
2108 throw_iterator_not_valid();
2110 return mp
->iter_value(m_pos
);
2113 Variant
c_MapIterator::t_key() {
2114 c_Map
* mp
= m_obj
.get();
2115 if (UNLIKELY(m_version
!= mp
->getVersion())) {
2116 throw_collection_modified();
2119 throw_iterator_not_valid();
2121 return mp
->iter_key(m_pos
);
2124 bool c_MapIterator::t_valid() {
2128 void c_MapIterator::t_next() {
2129 c_Map
* mp
= m_obj
.get();
2130 if (UNLIKELY(m_version
!= mp
->getVersion())) {
2131 throw_collection_modified();
2133 m_pos
= mp
->iter_next(m_pos
);
2136 void c_MapIterator::t_rewind() {
2137 c_Map
* mp
= m_obj
.get();
2138 if (UNLIKELY(m_version
!= mp
->getVersion())) {
2139 throw_collection_modified();
2141 m_pos
= mp
->iter_begin();
2144 ///////////////////////////////////////////////////////////////////////////////
2146 IMPLEMENT_SMART_ALLOCATION_CLS(c_StableMap
, Bucket
);
2148 #define CONNECT_TO_GLOBAL_DLLIST(mapobj, element) \
2150 (element)->pListLast = (mapobj)->m_pListTail; \
2151 (mapobj)->m_pListTail = (element); \
2152 (element)->pListNext = nullptr; \
2153 if ((element)->pListLast != nullptr) { \
2154 (element)->pListLast->pListNext = (element); \
2156 if (!(mapobj)->m_pListHead) { \
2157 (mapobj)->m_pListHead = (element); \
2161 static const char emptyStableMapSlot
[sizeof(c_StableMap::Bucket
*)] = { 0 };
2163 c_StableMap::c_StableMap(Class
* cb
) :
2164 ExtObjectDataFlags
<ObjectData::StableMapAttrInit
|
2167 ObjectData::UseIsset
|
2168 ObjectData::UseUnset
>(cb
),
2169 m_version(0), m_pListHead(nullptr), m_pListTail(nullptr) {
2173 m_arBuckets
= (Bucket
**)emptyStableMapSlot
;
2176 c_StableMap::~c_StableMap() {
2181 void c_StableMap::freeData() {
2182 if (m_arBuckets
!= (Bucket
**)emptyStableMapSlot
) {
2183 smart_free(m_arBuckets
);
2185 m_arBuckets
= (Bucket
**)emptyStableMapSlot
;
2188 void c_StableMap::deleteBuckets() {
2189 Bucket
* p
= m_pListHead
;
2197 void c_StableMap::t___construct(CVarRef iterable
/* = null_variant */) {
2198 if (!iterable
.isInitialized()) {
2202 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
2206 for (; iter
; ++iter
) {
2207 Variant k
= iter
.first();
2208 Variant v
= iter
.second();
2209 TypedValue
* tv
= cvarToCell(&v
);
2210 if (k
.isInteger()) {
2211 update(k
.toInt64(), tv
);
2212 } else if (k
.isString()) {
2213 update(k
.getStringData(), tv
);
2220 Array
c_StableMap::toArrayImpl() const {
2221 ArrayInit
ai(m_size
);
2222 Bucket
* p
= m_pListHead
;
2224 if (p
->hasIntKey()) {
2225 ai
.set((int64_t)p
->ikey
, tvAsCVarRef(&p
->data
));
2227 ai
.set(*(const String
*)(&p
->skey
), tvAsCVarRef(&p
->data
));
2234 Array
c_StableMap::o_toArray() const {
2235 check_collection_cast_to_array();
2236 return toArrayImpl();
2239 ObjectData
* c_StableMap::clone() {
2240 ObjectData
* obj
= ObjectData::clone();
2241 auto target
= static_cast<c_StableMap
*>(obj
);
2243 if (!m_size
) return obj
;
2245 target
->m_size
= m_size
;
2246 target
->m_nTableSize
= m_nTableSize
;
2247 target
->m_nTableMask
= m_nTableMask
;
2248 target
->m_arBuckets
= (Bucket
**)smart_calloc(m_nTableSize
, sizeof(Bucket
*));
2250 Bucket
*last
= nullptr;
2251 for (Bucket
* p
= m_pListHead
; p
; p
= p
->pListNext
) {
2252 Bucket
*np
= NEW(Bucket
)();
2253 tvDup(&p
->data
, &np
->data
);
2255 if (p
->hasIntKey()) {
2256 np
->setIntKey(p
->ikey
);
2257 nIndex
= p
->ikey
& target
->m_nTableMask
;
2259 np
->setStrKey(p
->skey
, p
->hash());
2260 nIndex
= p
->hash() & target
->m_nTableMask
;
2263 np
->pNext
= target
->m_arBuckets
[nIndex
];
2264 target
->m_arBuckets
[nIndex
] = np
;
2267 last
->pListNext
= np
;
2268 np
->pListLast
= last
;
2270 target
->m_pListHead
= np
;
2274 target
->m_pListTail
= last
;
2279 Object
c_StableMap::t_add(CVarRef val
) {
2280 TypedValue
* tv
= cvarToCell(&val
);
2285 Object
c_StableMap::t_addall(CVarRef iterable
) {
2287 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
2288 reserve(std::max(sz
, size_t(m_size
)));
2289 for (; iter
; ++iter
) {
2290 Variant v
= iter
.second();
2291 TypedValue
* tv
= cvarToCell(&v
);
2297 Object
c_StableMap::t_clear() {
2300 m_pListHead
= nullptr;
2301 m_pListTail
= nullptr;
2305 m_arBuckets
= (Bucket
**)emptyStableMapSlot
;
2309 bool c_StableMap::t_isempty() {
2310 return (m_size
== 0);
2313 int64_t c_StableMap::t_count() {
2317 Object
c_StableMap::t_items() {
2318 return SystemLib::AllocKVZippedIterableObject(this);
2321 Object
c_StableMap::t_keys() {
2323 Object obj
= vec
= NEWOBJ(c_Vector
)();
2324 vec
->reserve(m_size
);
2326 for (Bucket
* p
= m_pListHead
; p
; p
= p
->pListNext
) {
2327 if (p
->hasIntKey()) {
2328 vec
->m_data
[j
].m_data
.num
= p
->ikey
;
2329 vec
->m_data
[j
].m_type
= KindOfInt64
;
2331 p
->skey
->incRefCount();
2332 vec
->m_data
[j
].m_data
.pstr
= p
->skey
;
2333 vec
->m_data
[j
].m_type
= KindOfString
;
2341 Object
c_StableMap::t_view() {
2342 return SystemLib::AllocKeyedIterableViewObject(this);
2345 Object
c_StableMap::t_kvzip() {
2347 Object obj
= vec
= NEWOBJ(c_Vector
)();
2348 vec
->reserve(m_size
);
2350 for (Bucket
* p
= m_pListHead
; p
; p
= p
->pListNext
) {
2351 c_Pair
* pair
= NEWOBJ(c_Pair
)();
2352 pair
->incRefCount();
2353 if (p
->hasIntKey()) {
2354 pair
->elm0
.m_data
.num
= p
->ikey
;
2355 pair
->elm0
.m_type
= KindOfInt64
;
2357 p
->skey
->incRefCount();
2358 pair
->elm0
.m_data
.pstr
= p
->skey
;
2359 pair
->elm0
.m_type
= KindOfString
;
2362 pair
->add(&p
->data
);
2363 vec
->m_data
[j
].m_data
.pobj
= pair
;
2364 vec
->m_data
[j
].m_type
= KindOfObject
;
2371 Variant
c_StableMap::t_at(CVarRef key
) {
2372 if (key
.isInteger()) {
2373 return tvAsCVarRef(at(key
.toInt64()));
2374 } else if (key
.isString()) {
2375 return tvAsCVarRef(at(key
.getStringData()));
2378 return uninit_null();
2381 Variant
c_StableMap::t_get(CVarRef key
) {
2382 if (key
.isInteger()) {
2383 TypedValue
* tv
= get(key
.toInt64());
2385 return tvAsCVarRef(tv
);
2387 return uninit_null();
2389 } else if (key
.isString()) {
2390 TypedValue
* tv
= get(key
.getStringData());
2392 return tvAsCVarRef(tv
);
2394 return uninit_null();
2398 return uninit_null();
2401 Object
c_StableMap::t_set(CVarRef key
, CVarRef value
) {
2402 TypedValue
* val
= cvarToCell(&value
);
2403 if (key
.isInteger()) {
2404 update(key
.toInt64(), val
);
2405 } else if (key
.isString()) {
2406 update(key
.getStringData(), val
);
2413 Object
c_StableMap::t_setall(CVarRef iterable
) {
2415 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
2416 for (; iter
; ++iter
) {
2417 Variant k
= iter
.first();
2418 Variant v
= iter
.second();
2419 TypedValue
* tvKey
= cvarToCell(&k
);
2420 TypedValue
* tvVal
= cvarToCell(&v
);
2421 if (tvKey
->m_type
== KindOfInt64
) {
2422 set(tvKey
->m_data
.num
, tvVal
);
2423 } else if (IS_STRING_TYPE(tvKey
->m_type
)) {
2424 set(tvKey
->m_data
.pstr
, tvVal
);
2432 Object
c_StableMap::t_put(CVarRef key
, CVarRef value
) {
2433 return t_set(key
, value
);
2436 bool c_StableMap::t_contains(CVarRef key
) {
2437 DataType t
= key
.getType();
2438 if (t
== KindOfInt64
) {
2439 return contains(key
.toInt64());
2441 if (IS_STRING_TYPE(t
)) {
2442 return contains(key
.getStringData());
2448 bool c_StableMap::t_containskey(CVarRef key
) {
2449 return t_contains(key
);
2452 Object
c_StableMap::t_remove(CVarRef key
) {
2453 DataType t
= key
.getType();
2454 if (t
== KindOfInt64
) {
2455 remove(key
.toInt64());
2456 } else if (IS_STRING_TYPE(t
)) {
2457 remove(key
.getStringData());
2464 Object
c_StableMap::t_removekey(CVarRef key
) {
2465 return t_remove(key
);
2468 Object
c_StableMap::t_discard(CVarRef key
) {
2469 return t_remove(key
);
2472 Array
c_StableMap::t_toarray() {
2473 return toArrayImpl();
2476 Array
c_StableMap::t_copyasarray() {
2477 return toArrayImpl();
2480 Array
c_StableMap::t_tokeysarray() {
2481 ArrayInit
ai(m_size
, ArrayInit::vectorInit
);
2482 Bucket
* p
= m_pListHead
;
2484 if (p
->hasIntKey()) {
2485 ai
.set((int64_t)p
->ikey
);
2487 ai
.set(*(const String
*)(&p
->skey
));
2494 Object
c_StableMap::t_values() {
2496 Object ret
= target
= NEWOBJ(c_Vector
)();
2497 int64_t sz
= m_size
;
2502 target
->m_capacity
= target
->m_size
= m_size
;
2503 target
->m_data
= data
= (TypedValue
*)smart_malloc(sz
* sizeof(TypedValue
));
2504 Bucket
* p
= m_pListHead
;
2505 for (int64_t i
= 0; i
< sz
; ++i
) {
2507 tvDup(&p
->data
, &data
[i
]);
2513 Array
c_StableMap::t_tovaluesarray() {
2514 ArrayInit
ai(m_size
, ArrayInit::vectorInit
);
2515 Bucket
* p
= m_pListHead
;
2517 ai
.set(tvAsCVarRef(&p
->data
));
2523 Object
c_StableMap::t_updatefromarray(CVarRef arr
) {
2524 if (!arr
.isArray()) {
2525 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2526 "Parameter arr must be an array"));
2529 ArrayData
* ad
= arr
.getArrayData();
2530 for (ssize_t pos
= ad
->iter_begin(); pos
!= ArrayData::invalid_index
;
2531 pos
= ad
->iter_advance(pos
)) {
2532 Variant k
= ad
->getKey(pos
);
2533 TypedValue
* tv
= cvarToCell(&ad
->getValueRef(pos
));
2534 if (k
.isInteger()) {
2535 update(k
.toInt64(), tv
);
2537 assert(k
.isString());
2538 update(k
.getStringData(), tv
);
2544 Object
c_StableMap::t_updatefromiterable(CVarRef it
) {
2545 if (!it
.isObject()) {
2546 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2547 "Parameter it must be an instance of Iterable"));
2550 ObjectData
* obj
= it
.getObjectData();
2551 if (obj
->getCollectionType() == Collection::StableMapType
) {
2552 auto smp
= static_cast<c_StableMap
*>(obj
);
2553 c_StableMap::Bucket
* p
= smp
->m_pListHead
;
2555 if (p
->hasIntKey()) {
2556 update((int64_t)p
->ikey
, &p
->data
);
2558 update(p
->skey
, &p
->data
);
2564 for (ArrayIter iter
= obj
->begin(); iter
; ++iter
) {
2565 Variant k
= iter
.first();
2566 Variant v
= iter
.second();
2567 TypedValue
* tv
= cvarToCell(&v
);
2568 if (k
.isInteger()) {
2569 update(k
.toInt64(), tv
);
2571 assert(k
.isString());
2572 update(k
.getStringData(), tv
);
2578 Object
c_StableMap::t_differencebykey(CVarRef it
) {
2579 if (!it
.isObject()) {
2580 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2581 "Parameter it must be an instance of Iterable"));
2584 ObjectData
* obj
= it
.getObjectData();
2585 c_StableMap
* target
;
2586 Object ret
= target
= static_cast<c_StableMap
*>(clone());
2587 if (obj
->getCollectionType() == Collection::StableMapType
) {
2588 auto smp
= static_cast<c_StableMap
*>(obj
);
2589 c_StableMap::Bucket
* p
= smp
->m_pListHead
;
2591 if (p
->hasIntKey()) {
2592 target
->remove((int64_t)p
->ikey
);
2594 target
->remove(p
->skey
);
2599 for (ArrayIter iter
= obj
->begin(); iter
; ++iter
) {
2600 Variant k
= iter
.first();
2601 if (k
.isInteger()) {
2602 target
->remove(k
.toInt64());
2604 assert(k
.isString());
2605 target
->remove(k
.getStringData());
2611 Object
c_StableMap::t_getiterator() {
2612 c_StableMapIterator
* it
= NEWOBJ(c_StableMapIterator
)();
2614 it
->m_pos
= iter_begin();
2615 it
->m_version
= getVersion();
2619 Object
c_StableMap::t_map(CVarRef callback
) {
2621 vm_decode_function(callback
, nullptr, false, ctx
);
2623 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2624 "Parameter must be a valid callback"));
2628 Object obj
= smp
= NEWOBJ(c_StableMap
)();
2629 if (!m_size
) return obj
;
2630 smp
->m_size
= m_size
;
2631 smp
->m_nTableSize
= m_nTableSize
;
2632 smp
->m_nTableMask
= m_nTableMask
;
2633 smp
->m_arBuckets
= (Bucket
**)smart_calloc(m_nTableSize
, sizeof(Bucket
*));
2634 Bucket
*last
= nullptr;
2635 for (Bucket
* p
= m_pListHead
; p
; p
= p
->pListNext
) {
2637 int32_t version
= m_version
;
2639 tvDup(&p
->data
, args
+ 0);
2640 g_vmContext
->invokeFuncFew(ret
.asTypedValue(), ctx
, 1, args
);
2641 if (UNLIKELY(version
!= m_version
)) {
2642 throw_collection_modified();
2644 Bucket
*np
= NEW(Bucket
)(ret
.asTypedValue());
2646 if (p
->hasIntKey()) {
2647 np
->setIntKey(p
->ikey
);
2648 nIndex
= p
->ikey
& smp
->m_nTableMask
;
2650 np
->setStrKey(p
->skey
, p
->hash());
2651 nIndex
= p
->hash() & smp
->m_nTableMask
;
2653 np
->pNext
= smp
->m_arBuckets
[nIndex
];
2654 smp
->m_arBuckets
[nIndex
] = np
;
2656 last
->pListNext
= np
;
2657 np
->pListLast
= last
;
2659 smp
->m_pListHead
= np
;
2663 smp
->m_pListTail
= last
;
2667 Object
c_StableMap::t_filter(CVarRef callback
) {
2669 vm_decode_function(callback
, nullptr, false, ctx
);
2671 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2672 "Parameter must be a valid callback"));
2676 Object obj
= smp
= NEWOBJ(c_StableMap
)();
2677 if (!m_size
) return obj
;
2678 for (Bucket
* p
= m_pListHead
; p
; p
= p
->pListNext
) {
2680 int32_t version
= m_version
;
2682 tvDup(&p
->data
, args
+ 0);
2683 g_vmContext
->invokeFuncFew(ret
.asTypedValue(), ctx
, 1, args
);
2684 if (UNLIKELY(version
!= m_version
)) {
2685 throw_collection_modified();
2687 if (!ret
.toBoolean()) continue;
2688 if (p
->hasIntKey()) {
2689 smp
->update(p
->ikey
, &p
->data
);
2691 smp
->update(p
->skey
, &p
->data
);
2697 Object
c_StableMap::t_zip(CVarRef iterable
) {
2699 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
2701 Object obj
= smp
= NEWOBJ(c_StableMap
)();
2702 smp
->reserve(std::min(sz
, size_t(m_size
)));
2703 for (Bucket
* p
= m_pListHead
; p
&& iter
; p
= p
->pListNext
, ++iter
) {
2704 Variant v
= iter
.second();
2706 Object pairObj
= pair
= NEWOBJ(c_Pair
)();
2707 pair
->add(&p
->data
);
2708 pair
->add(cvarToCell(&v
));
2710 tv
.m_data
.pobj
= pair
;
2711 tv
.m_type
= KindOfObject
;
2712 if (p
->hasIntKey()) {
2713 smp
->update(p
->ikey
, &tv
);
2715 smp
->update(p
->skey
, &tv
);
2721 Object
c_StableMap::ti_fromitems(CVarRef iterable
) {
2723 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
2724 c_StableMap
* target
;
2725 Object ret
= target
= NEWOBJ(c_StableMap
)();
2727 target
->reserve(sz
);
2729 for (; iter
; ++iter
) {
2730 Variant v
= iter
.second();
2731 TypedValue
* tv
= cvarToCell(&v
);
2732 if (UNLIKELY(tv
->m_type
!= KindOfObject
||
2733 !tv
->m_data
.pobj
->instanceof(c_Pair::s_cls
))) {
2734 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2735 "Parameter must be an instance of Iterable<Pair>"));
2738 auto pair
= static_cast<c_Pair
*>(tv
->m_data
.pobj
);
2739 TypedValue
* tvKey
= &pair
->elm0
;
2740 TypedValue
* tvValue
= &pair
->elm1
;
2741 assert(tvKey
->m_type
!= KindOfRef
);
2742 assert(tvValue
->m_type
!= KindOfRef
);
2743 if (tvKey
->m_type
== KindOfInt64
) {
2744 target
->update(tvKey
->m_data
.num
, tvValue
);
2745 } else if (IS_STRING_TYPE(tvKey
->m_type
)) {
2746 target
->update(tvKey
->m_data
.pstr
, tvValue
);
2754 Object
c_StableMap::ti_fromarray(CVarRef arr
) {
2755 if (!arr
.isArray()) {
2756 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2757 "Parameter arr must be an array"));
2761 Object ret
= smp
= NEWOBJ(c_StableMap
)();
2762 ArrayData
* ad
= arr
.getArrayData();
2763 for (ssize_t pos
= ad
->iter_begin(); pos
!= ArrayData::invalid_index
;
2764 pos
= ad
->iter_advance(pos
)) {
2765 Variant k
= ad
->getKey(pos
);
2766 Variant v
= ad
->getValue(pos
);
2767 TypedValue
* tv
= cvarToCell(&v
);
2768 if (k
.isInteger()) {
2769 smp
->update(k
.toInt64(), tv
);
2771 assert(k
.isString());
2772 smp
->update(k
.getStringData(), tv
);
2778 Object
c_StableMap::ti_fromiterable(CVarRef it
) {
2779 if (!it
.isObject()) {
2780 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2781 "Parameter it must be an instance of Iterable"));
2784 ObjectData
* obj
= it
.getObjectData();
2786 if (obj
->getCollectionType() == Collection::StableMapType
) {
2790 c_StableMap
* target
;
2791 ret
= target
= NEWOBJ(c_StableMap
)();
2792 for (ArrayIter iter
= obj
->begin(); iter
; ++iter
) {
2793 Variant k
= iter
.first();
2794 Variant v
= iter
.second();
2795 TypedValue
* tv
= cvarToCell(&v
);
2796 if (k
.isInteger()) {
2797 target
->update(k
.toInt64(), tv
);
2799 assert(k
.isString());
2800 target
->update(k
.getStringData(), tv
);
2806 void c_StableMap::throwOOB(int64_t key
) {
2810 void c_StableMap::throwOOB(StringData
* key
) {
2814 void c_StableMap::add(TypedValue
* val
) {
2815 if (UNLIKELY(val
->m_type
!= KindOfObject
||
2816 !val
->m_data
.pobj
->instanceof(c_Pair::s_cls
))) {
2817 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
2818 "Parameter must be an instance of Pair"));
2821 auto pair
= static_cast<c_Pair
*>(val
->m_data
.pobj
);
2822 TypedValue
* tvKey
= &pair
->elm0
;
2823 TypedValue
* tvValue
= &pair
->elm1
;
2824 assert(tvKey
->m_type
!= KindOfRef
);
2825 assert(tvValue
->m_type
!= KindOfRef
);
2826 if (tvKey
->m_type
== KindOfInt64
) {
2827 update(tvKey
->m_data
.num
, tvValue
);
2828 } else if (IS_STRING_TYPE(tvKey
->m_type
)) {
2829 update(tvKey
->m_data
.pstr
, tvValue
);
2835 bool inline sm_hit_string_key(const c_StableMap::Bucket
* p
,
2836 const char* k
, int len
, int32_t hash
) ALWAYS_INLINE
;
2837 bool inline sm_hit_string_key(const c_StableMap::Bucket
* p
,
2838 const char* k
, int len
, int32_t hash
) {
2839 if (p
->hasIntKey()) return false;
2840 const char* data
= p
->skey
->data();
2841 return data
== k
|| (p
->hash() == hash
&&
2842 p
->skey
->size() == len
&&
2843 memcmp(data
, k
, len
) == 0);
2846 c_StableMap::Bucket
* c_StableMap::find(int64_t h
) const {
2847 for (Bucket
* p
= m_arBuckets
[h
& m_nTableMask
]; p
; p
= p
->pNext
) {
2848 if (p
->hasIntKey() && p
->ikey
== h
) {
2855 c_StableMap::Bucket
* c_StableMap::find(const char* k
, int len
,
2856 strhash_t prehash
) const {
2857 int32_t hash
= c_StableMap::Bucket::encodeHash(prehash
);
2858 for (Bucket
* p
= m_arBuckets
[prehash
& m_nTableMask
]; p
; p
= p
->pNext
) {
2859 if (sm_hit_string_key(p
, k
, len
, hash
)) return p
;
2864 c_StableMap::Bucket
** c_StableMap::findForErase(int64_t h
) const {
2865 Bucket
** ret
= &(m_arBuckets
[h
& m_nTableMask
]);
2868 if (p
->hasIntKey() && p
->ikey
== h
) {
2877 c_StableMap::Bucket
** c_StableMap::findForErase(const char* k
, int len
,
2878 strhash_t prehash
) const {
2879 Bucket
** ret
= &(m_arBuckets
[prehash
& m_nTableMask
]);
2881 int32_t hash
= c_StableMap::Bucket::encodeHash(prehash
);
2883 if (sm_hit_string_key(p
, k
, len
, hash
)) return ret
;
2890 bool c_StableMap::update(int64_t h
, TypedValue
* data
) {
2891 Bucket
* p
= find(h
);
2893 tvRefcountedIncRef(data
);
2894 tvRefcountedDecRef(&p
->data
);
2895 p
->data
.m_data
.num
= data
->m_data
.num
;
2896 p
->data
.m_type
= data
->m_type
;
2900 if (++m_size
> m_nTableSize
) {
2903 p
= NEW(Bucket
)(data
);
2905 uint nIndex
= (h
& m_nTableMask
);
2906 p
->pNext
= m_arBuckets
[nIndex
];
2907 m_arBuckets
[nIndex
] = p
;
2908 CONNECT_TO_GLOBAL_DLLIST(this, p
);
2912 bool c_StableMap::update(StringData
*key
, TypedValue
* data
) {
2913 strhash_t h
= key
->hash();
2914 Bucket
* p
= find(key
->data(), key
->size(), h
);
2916 tvRefcountedIncRef(data
);
2917 tvRefcountedDecRef(&p
->data
);
2918 p
->data
.m_data
.num
= data
->m_data
.num
;
2919 p
->data
.m_type
= data
->m_type
;
2923 if (++m_size
> m_nTableSize
) {
2926 p
= NEW(Bucket
)(data
);
2927 p
->setStrKey(key
, h
);
2928 uint nIndex
= (h
& m_nTableMask
);
2929 p
->pNext
= m_arBuckets
[nIndex
];
2930 m_arBuckets
[nIndex
] = p
;
2931 CONNECT_TO_GLOBAL_DLLIST(this, p
);
2935 void c_StableMap::erase(Bucket
** prev
) {
2943 p
->pListLast
->pListNext
= p
->pListNext
;
2945 /* Deleting the head of the list */
2946 assert(m_pListHead
== p
);
2947 m_pListHead
= p
->pListNext
;
2950 p
->pListNext
->pListLast
= p
->pListLast
;
2952 assert(m_pListTail
== p
);
2953 m_pListTail
= p
->pListLast
;
2960 void c_StableMap::adjustCapacityImpl(int64_t sz
) {
2963 if (sz
<= 0) return;
2966 sz
= Util::roundUpToPowerOfTwo(sz
);
2968 if (m_nTableSize
== 0) {
2970 m_nTableMask
= m_nTableSize
- 1;
2971 m_arBuckets
= (Bucket
**)smart_calloc(m_nTableSize
, sizeof(Bucket
*));
2975 m_nTableMask
= m_nTableSize
- 1;
2976 smart_free(m_arBuckets
);
2977 m_arBuckets
= (Bucket
**)smart_calloc(m_nTableSize
, sizeof(Bucket
*));
2978 for (Bucket
* p
= m_pListHead
; p
; p
= p
->pListNext
) {
2979 uint nIndex
= (p
->hashKey() & m_nTableMask
);
2980 p
->pNext
= m_arBuckets
[nIndex
];
2981 m_arBuckets
[nIndex
] = p
;
2985 ssize_t
c_StableMap::iter_begin() const {
2986 Bucket
* p
= m_pListHead
;
2987 return reinterpret_cast<ssize_t
>(p
);
2990 ssize_t
c_StableMap::iter_next(ssize_t pos
) const {
2994 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
2996 return reinterpret_cast<ssize_t
>(p
);
2999 ssize_t
c_StableMap::iter_prev(ssize_t pos
) const {
3003 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
3005 return reinterpret_cast<ssize_t
>(p
);
3008 Variant
c_StableMap::iter_key(ssize_t pos
) const {
3010 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
3011 if (p
->hasStrKey()) {
3014 return (int64_t)p
->ikey
;
3017 Variant
c_StableMap::iter_value(ssize_t pos
) const {
3019 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
3020 return tvAsCVarRef(&p
->data
);
3023 struct StableMapKeyAccessor
{
3024 typedef const c_StableMap::Bucket
* ElmT
;
3025 bool isInt(ElmT elm
) const { return elm
->hasIntKey(); }
3026 bool isStr(ElmT elm
) const { return elm
->hasStrKey(); }
3027 int64_t getInt(ElmT elm
) const { return elm
->ikey
; }
3028 StringData
* getStr(ElmT elm
) const { return elm
->skey
; }
3029 Variant
getValue(ElmT elm
) const {
3038 struct StableMapValAccessor
{
3039 typedef const c_StableMap::Bucket
* ElmT
;
3040 bool isInt(ElmT elm
) const { return elm
->data
.m_type
== KindOfInt64
; }
3041 bool isStr(ElmT elm
) const { return IS_STRING_TYPE(elm
->data
.m_type
); }
3042 int64_t getInt(ElmT elm
) const { return elm
->data
.m_data
.num
; }
3043 StringData
* getStr(ElmT elm
) const { return elm
->data
.m_data
.pstr
; }
3044 Variant
getValue(ElmT elm
) const { return tvAsCVarRef(&elm
->data
); }
3048 * preSort() does an initial pass over the array to do some preparatory work
3049 * before the sort algorithm runs. For sorts that use builtin comparators, the
3050 * types of values are also observed during this first pass. By observing the
3051 * types during this initial pass, we can often use a specialized comparator
3052 * and avoid performing type checks during the actual sort.
3054 template <typename AccessorT
>
3055 c_StableMap::SortFlavor
c_StableMap::preSort(Bucket
** buffer
,
3056 const AccessorT
& acc
,
3059 bool allInts UNUSED
= true;
3060 bool allStrs UNUSED
= true;
3062 // Build up an auxillary array of Bucket pointers. We will
3063 // sort this auxillary array, and then we will rebuild the
3064 // linked list based on the result.
3066 for (Bucket
* p
= m_pListHead
; p
; ++i
, p
= p
->pListNext
) {
3067 allInts
= (allInts
&& acc
.isInt(p
));
3068 allStrs
= (allStrs
&& acc
.isStr(p
));
3071 return allStrs
? StringSort
: allInts
? IntegerSort
: GenericSort
;
3073 for (Bucket
* p
= m_pListHead
; p
; ++i
, p
= p
->pListNext
) {
3081 * postSort() runs after sorting has been performed. For StableMap, postSort()
3082 * handles rewiring the linked list according to the results of the sort.
3084 void c_StableMap::postSort(Bucket
** buffer
) {
3085 uint last
= m_size
-1;
3086 Bucket
* b
= m_pListHead
= buffer
[0];
3087 b
->pListLast
= nullptr;
3088 for (uint i
= 0; i
< last
; ++i
) {
3089 Bucket
* bNext
= buffer
[i
+1];
3090 b
->pListNext
= bNext
;
3091 bNext
->pListLast
= b
;
3095 b
->pListNext
= nullptr;
3098 #define SORT_CASE(flag, cmp_type, acc_type) \
3101 cmp_type##Compare<acc_type, flag, true> comp; \
3102 HPHP::Sort::sort(buffer, buffer + m_size, comp); \
3104 cmp_type##Compare<acc_type, flag, false> comp; \
3105 HPHP::Sort::sort(buffer, buffer + m_size, comp); \
3109 #define SORT_CASE_BLOCK(cmp_type, acc_type) \
3110 switch (sort_flags) { \
3111 default: /* fall through to SORT_REGULAR case */ \
3112 SORT_CASE(SORT_REGULAR, cmp_type, acc_type) \
3113 SORT_CASE(SORT_NUMERIC, cmp_type, acc_type) \
3114 SORT_CASE(SORT_STRING, cmp_type, acc_type) \
3115 SORT_CASE(SORT_LOCALE_STRING, cmp_type, acc_type) \
3116 SORT_CASE(SORT_NATURAL, cmp_type, acc_type) \
3117 SORT_CASE(SORT_NATURAL_CASE, cmp_type, acc_type) \
3119 #define CALL_SORT(acc_type) \
3120 if (flav == StringSort) { \
3121 SORT_CASE_BLOCK(StrElm, acc_type) \
3122 } else if (flav == IntegerSort) { \
3123 SORT_CASE_BLOCK(IntElm, acc_type) \
3125 SORT_CASE_BLOCK(Elm, acc_type) \
3127 #define SORT_BODY(acc_type) \
3132 Bucket** buffer = (Bucket**)smart_malloc(m_size * sizeof(Bucket*)); \
3133 SortFlavor flav = preSort<acc_type>(buffer, acc_type(), true); \
3135 CALL_SORT(acc_type); \
3138 smart_free(buffer); \
3142 smart_free(buffer); \
3145 void c_StableMap::asort(int sort_flags
, bool ascending
) {
3146 SORT_BODY(StableMapValAccessor
);
3149 void c_StableMap::ksort(int sort_flags
, bool ascending
) {
3150 SORT_BODY(StableMapKeyAccessor
);
3154 #undef SORT_CASE_BLOCK
3158 #define USER_SORT_BODY(acc_type) \
3163 Bucket** buffer = (Bucket**)smart_malloc(m_size * sizeof(Bucket*)); \
3164 preSort<acc_type>(buffer, acc_type(), false); \
3165 ElmUCompare<acc_type> comp; \
3167 Transl::CallerFrame cf; \
3168 vm_decode_function(cmp_function, cf(), false, ctx); \
3171 HPHP::Sort::sort(buffer, buffer + m_size, comp); \
3174 smart_free(buffer); \
3178 smart_free(buffer); \
3181 void c_StableMap::uasort(CVarRef cmp_function
) {
3182 USER_SORT_BODY(StableMapValAccessor
);
3185 void c_StableMap::uksort(CVarRef cmp_function
) {
3186 USER_SORT_BODY(StableMapKeyAccessor
);
3189 #undef USER_SORT_BODY
3191 void c_StableMap::throwBadKeyType() {
3192 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3193 "Only integer keys and string keys may be used with StableMaps"));
3197 c_StableMap::Bucket::~Bucket() {
3198 if (hasStrKey() && skey
->decRefCount() == 0) {
3199 DELETE(StringData
)(skey
);
3201 tvRefcountedDecRef(&data
);
3204 void c_StableMap::Bucket::dump() {
3205 printf("c_StableMap::Bucket: %" PRIx64
", %p, %p, %p\n",
3206 hashKey(), pListNext
, pListLast
, pNext
);
3210 tvAsCVarRef(&data
).dump();
3213 TypedValue
* c_StableMap::OffsetGet(ObjectData
* obj
, TypedValue
* key
) {
3214 assert(key
->m_type
!= KindOfRef
);
3215 auto smp
= static_cast<c_StableMap
*>(obj
);
3216 if (key
->m_type
== KindOfInt64
) {
3217 return smp
->at(key
->m_data
.num
);
3219 if (IS_STRING_TYPE(key
->m_type
)) {
3220 return smp
->at(key
->m_data
.pstr
);
3226 void c_StableMap::OffsetSet(ObjectData
* obj
, TypedValue
* key
, TypedValue
* val
) {
3227 assert(key
->m_type
!= KindOfRef
);
3228 assert(val
->m_type
!= KindOfRef
);
3229 auto smp
= static_cast<c_StableMap
*>(obj
);
3230 if (key
->m_type
== KindOfInt64
) {
3231 smp
->set(key
->m_data
.num
, val
);
3234 if (IS_STRING_TYPE(key
->m_type
)) {
3235 smp
->set(key
->m_data
.pstr
, val
);
3241 bool c_StableMap::OffsetIsset(ObjectData
* obj
, TypedValue
* key
) {
3242 assert(key
->m_type
!= KindOfRef
);
3243 auto smp
= static_cast<c_StableMap
*>(obj
);
3245 if (key
->m_type
== KindOfInt64
) {
3246 result
= smp
->get(key
->m_data
.num
);
3247 } else if (IS_STRING_TYPE(key
->m_type
)) {
3248 result
= smp
->get(key
->m_data
.pstr
);
3253 return result
? isset(tvAsCVarRef(result
)) : false;
3256 bool c_StableMap::OffsetEmpty(ObjectData
* obj
, TypedValue
* key
) {
3257 assert(key
->m_type
!= KindOfRef
);
3258 auto smp
= static_cast<c_StableMap
*>(obj
);
3260 if (key
->m_type
== KindOfInt64
) {
3261 result
= smp
->get(key
->m_data
.num
);
3262 } else if (IS_STRING_TYPE(key
->m_type
)) {
3263 result
= smp
->get(key
->m_data
.pstr
);
3268 return result
? empty(tvAsCVarRef(result
)) : true;
3271 bool c_StableMap::OffsetContains(ObjectData
* obj
, TypedValue
* key
) {
3272 assert(key
->m_type
!= KindOfRef
);
3273 auto smp
= static_cast<c_StableMap
*>(obj
);
3274 if (key
->m_type
== KindOfInt64
) {
3275 return smp
->contains(key
->m_data
.num
);
3276 } else if (IS_STRING_TYPE(key
->m_type
)) {
3277 return smp
->contains(key
->m_data
.pstr
);
3284 void c_StableMap::OffsetAppend(ObjectData
* obj
, TypedValue
* val
) {
3285 assert(val
->m_type
!= KindOfRef
);
3286 auto smp
= static_cast<c_StableMap
*>(obj
);
3290 void c_StableMap::OffsetUnset(ObjectData
* obj
, TypedValue
* key
) {
3291 assert(key
->m_type
!= KindOfRef
);
3292 auto smp
= static_cast<c_StableMap
*>(obj
);
3293 if (key
->m_type
== KindOfInt64
) {
3294 smp
->remove(key
->m_data
.num
);
3297 if (IS_STRING_TYPE(key
->m_type
)) {
3298 smp
->remove(key
->m_data
.pstr
);
3304 bool c_StableMap::Equals(ObjectData
* obj1
, ObjectData
* obj2
) {
3305 auto smp1
= static_cast<c_StableMap
*>(obj1
);
3306 auto smp2
= static_cast<c_StableMap
*>(obj2
);
3307 if (smp1
->m_size
!= smp2
->m_size
) return false;
3308 auto p1
= smp1
->m_pListHead
;
3309 auto p2
= smp2
->m_pListHead
;
3310 for (; p1
; p1
= p1
->pListNext
, p2
= p2
->pListNext
) {
3312 // Check if the keys are identical (===)
3313 if (p1
->hasIntKey()) {
3314 if (!p2
->hasIntKey()) return false;
3315 if (p1
->ikey
!= p2
->ikey
) return false;
3317 assert(p1
->hasStrKey());
3318 if (!p2
->hasStrKey()) return false;
3319 if (!p1
->skey
->same(p2
->skey
)) return false;
3321 // Compare the values using equals (==)
3322 if (!equal(tvAsCVarRef(&p1
->data
), tvAsCVarRef(&p2
->data
))) {
3329 void c_StableMap::Unserialize(ObjectData
* obj
,
3330 VariableUnserializer
* uns
,
3334 throw Exception("StableMap does not support the '%c' serialization "
3337 auto smp
= static_cast<c_StableMap
*>(obj
);
3339 for (int64_t i
= 0; i
< sz
; ++i
) {
3341 k
.unserialize(uns
, Uns::ColKeyMode
);
3344 if (k
.isInteger()) {
3345 auto h
= k
.toInt64();
3347 if (UNLIKELY(p
!= nullptr)) goto do_unserialize
;
3350 nIndex
= (h
& smp
->m_nTableMask
);
3351 } else if (k
.isString()) {
3352 auto key
= k
.getStringData();
3353 auto h
= key
->hash();
3354 p
= smp
->find(key
->data(), key
->size(), h
);
3355 if (UNLIKELY(p
!= nullptr)) goto do_unserialize
;
3357 p
->setStrKey(key
, h
);
3358 nIndex
= (h
& smp
->m_nTableMask
);
3360 throw Exception("Invalid key");
3363 p
->data
.m_type
= KindOfNull
;
3364 p
->pNext
= smp
->m_arBuckets
[nIndex
];
3365 smp
->m_arBuckets
[nIndex
] = p
;
3366 CONNECT_TO_GLOBAL_DLLIST(smp
, p
);
3368 tvAsVariant(&p
->data
).unserialize(uns
, Uns::ColValueMode
);
3372 #undef CONNECT_TO_GLOBAL_DLLIST
3374 c_StableMapIterator::c_StableMapIterator(Class
* cb
) :
3378 c_StableMapIterator::~c_StableMapIterator() {
3381 void c_StableMapIterator::t___construct() {
3384 Variant
c_StableMapIterator::t_current() {
3385 c_StableMap
* smp
= m_obj
.get();
3386 if (UNLIKELY(m_version
!= smp
->getVersion())) {
3387 throw_collection_modified();
3390 throw_iterator_not_valid();
3392 return smp
->iter_value(m_pos
);
3395 Variant
c_StableMapIterator::t_key() {
3396 c_StableMap
* smp
= m_obj
.get();
3397 if (UNLIKELY(m_version
!= smp
->getVersion())) {
3398 throw_collection_modified();
3401 throw_iterator_not_valid();
3403 return smp
->iter_key(m_pos
);
3406 bool c_StableMapIterator::t_valid() {
3410 void c_StableMapIterator::t_next() {
3411 c_StableMap
* smp
= m_obj
.get();
3412 if (UNLIKELY(m_version
!= smp
->getVersion())) {
3413 throw_collection_modified();
3415 m_pos
= smp
->iter_next(m_pos
);
3418 void c_StableMapIterator::t_rewind() {
3419 c_StableMap
* smp
= m_obj
.get();
3420 if (UNLIKELY(m_version
!= smp
->getVersion())) {
3421 throw_collection_modified();
3423 m_pos
= smp
->iter_begin();
3426 ///////////////////////////////////////////////////////////////////////////////
3428 static const char emptySetSlot
[sizeof(c_Set::Bucket
)] = { 0 };
3430 c_Set::c_Set(Class
* cb
) :
3431 ExtObjectDataFlags
<ObjectData::SetAttrInit
|
3434 ObjectData::UseIsset
|
3435 ObjectData::UseUnset
>(cb
),
3436 m_size(0), m_load(0), m_nLastSlot(0), m_version(0) {
3437 m_data
= (Bucket
*)emptySetSlot
;
3445 void c_Set::freeData() {
3446 if (m_data
!= (Bucket
*)emptySetSlot
) {
3449 m_data
= (Bucket
*)emptySetSlot
;
3452 void c_Set::deleteBuckets() {
3453 if (!m_size
) return;
3454 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
3455 Bucket
& p
= m_data
[i
];
3456 if (p
.validValue()) {
3457 tvRefcountedDecRef(&p
.data
);
3462 void c_Set::t___construct(CVarRef iterable
/* = null_variant */) {
3463 if (!iterable
.isInitialized()) {
3467 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
3468 for (; iter
; ++iter
) {
3469 Variant v
= iter
.second();
3470 if (v
.isInteger()) {
3471 update(v
.toInt64());
3472 } else if (v
.isString()) {
3473 update(v
.getStringData());
3475 throwBadValueType();
3480 Array
c_Set::toArrayImpl() const {
3481 ArrayInit
ai(m_size
, ArrayInit::vectorInit
);
3482 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
3483 Bucket
& p
= m_data
[i
];
3484 if (p
.validValue()) {
3485 ai
.set(tvAsCVarRef(&p
.data
));
3491 Array
c_Set::o_toArray() const {
3492 check_collection_cast_to_array();
3493 return toArrayImpl();
3496 ObjectData
* c_Set::clone() {
3497 ObjectData
* obj
= ObjectData::clone();
3498 auto target
= static_cast<c_Set
*>(obj
);
3500 if (!m_size
) return obj
;
3502 assert(m_nLastSlot
!= 0);
3503 target
->m_size
= m_size
;
3504 target
->m_load
= m_load
;
3505 target
->m_nLastSlot
= m_nLastSlot
;
3506 target
->m_data
= (Bucket
*)smart_malloc(numSlots() * sizeof(Bucket
));
3507 memcpy(target
->m_data
, m_data
, numSlots() * sizeof(Bucket
));
3509 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
3510 Bucket
& p
= m_data
[i
];
3511 if (p
.validValue()) {
3512 tvRefcountedIncRef(&p
.data
);
3519 Object
c_Set::t_add(CVarRef val
) {
3520 TypedValue
* tv
= cvarToCell(&val
);
3525 Object
c_Set::t_addall(CVarRef iterable
) {
3527 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
3528 for (; iter
; ++iter
) {
3529 Variant v
= iter
.second();
3530 TypedValue
* tv
= cvarToCell(&v
);
3536 Object
c_Set::t_clear() {
3542 m_data
= (Bucket
*)emptySetSlot
;
3546 bool c_Set::t_isempty() {
3547 return (m_size
== 0);
3550 int64_t c_Set::t_count() {
3554 Object
c_Set::t_items() {
3555 return SystemLib::AllocIterableViewObject(this);
3558 Object
c_Set::t_view() {
3559 return SystemLib::AllocIterableViewObject(this);
3562 bool c_Set::t_contains(CVarRef key
) {
3563 DataType t
= key
.getType();
3564 if (t
== KindOfInt64
) {
3565 return contains(key
.toInt64());
3567 if (IS_STRING_TYPE(t
)) {
3568 return contains(key
.getStringData());
3570 throwBadValueType();
3574 Object
c_Set::t_remove(CVarRef key
) {
3575 DataType t
= key
.getType();
3576 if (t
== KindOfInt64
) {
3577 remove(key
.toInt64());
3578 } else if (IS_STRING_TYPE(t
)) {
3579 remove(key
.getStringData());
3581 throwBadValueType();
3586 Array
c_Set::t_toarray() {
3587 return toArrayImpl();
3590 Object
c_Set::t_getiterator() {
3591 c_SetIterator
* it
= NEWOBJ(c_SetIterator
)();
3593 it
->m_pos
= iter_begin();
3594 it
->m_version
= getVersion();
3598 Object
c_Set::t_map(CVarRef callback
) {
3600 vm_decode_function(callback
, nullptr, false, ctx
);
3602 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3603 "Parameter must be a valid callback"));
3607 Object obj
= st
= NEWOBJ(c_Set
)();
3608 if (!m_size
) return obj
;
3609 assert(m_nLastSlot
!= 0);
3610 st
->m_size
= m_size
;
3611 st
->m_load
= m_load
;
3612 st
->m_nLastSlot
= 0;
3613 st
->m_data
= (Bucket
*)smart_malloc(numSlots() * sizeof(Bucket
));
3614 // We need to zero out the first slot in case an exception
3615 // is thrown during the first iteration, because ~c_Set()
3616 // will decRef all slots up to (and including) m_nLastSlot.
3617 st
->m_data
[0].data
.m_type
= (DataType
)0;
3618 for (uint i
= 0; i
<= m_nLastSlot
; st
->m_nLastSlot
= i
++) {
3619 Bucket
& p
= m_data
[i
];
3620 Bucket
& np
= st
->m_data
[i
];
3621 if (!p
.validValue()) {
3622 np
.data
.m_type
= p
.data
.m_type
;
3625 TypedValue
* tv
= &np
.data
;
3626 int32_t version
= m_version
;
3628 tvDup(&p
.data
, args
+ 0);
3629 g_vmContext
->invokeFuncFew(tv
, ctx
, 1, args
);
3630 if (UNLIKELY(version
!= m_version
)) {
3631 tvRefcountedDecRef(tv
);
3632 throw_collection_modified();
3634 np
.data
.hash() = p
.data
.hash();
3639 Object
c_Set::t_filter(CVarRef callback
) {
3641 vm_decode_function(callback
, nullptr, false, ctx
);
3643 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3644 "Parameter must be a valid callback"));
3648 Object obj
= st
= NEWOBJ(c_Set
)();
3649 if (!m_size
) return obj
;
3650 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
3651 Bucket
& p
= m_data
[i
];
3652 if (!p
.validValue()) continue;
3654 int32_t version
= m_version
;
3656 tvDup(&p
.data
, args
+ 0);
3657 g_vmContext
->invokeFuncFew(ret
.asTypedValue(), ctx
, 1, args
);
3658 if (UNLIKELY(version
!= m_version
)) {
3659 throw_collection_modified();
3661 if (!ret
.toBoolean()) continue;
3663 st
->update(p
.data
.m_data
.num
);
3666 st
->update(p
.data
.m_data
.pstr
);
3672 Object
c_Set::t_zip(CVarRef iterable
) {
3674 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
3675 if (m_size
&& iter
) {
3676 // At present, Sets only support int values and string values,
3677 // so if this Set is non empty and the iterable is non empty
3678 // the zip operation will always fail
3679 throwBadValueType();
3681 Object obj
= NEWOBJ(c_Set
)();
3685 Object
c_Set::ti_fromitems(CVarRef iterable
) {
3687 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
3689 Object ret
= target
= NEWOBJ(c_Set
)();
3690 for (; iter
; ++iter
) {
3691 Variant v
= iter
.second();
3692 if (v
.isInteger()) {
3693 target
->update(v
.toInt64());
3694 } else if (v
.isString()) {
3695 target
->update(v
.getStringData());
3697 throwBadValueType();
3703 Object
c_Set::ti_fromarray(CVarRef arr
) {
3704 if (!arr
.isArray()) {
3705 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3706 "Parameter arr must be an array"));
3710 Object ret
= st
= NEWOBJ(c_Set
)();
3711 ArrayData
* ad
= arr
.getArrayData();
3712 for (ssize_t pos
= ad
->iter_begin(); pos
!= ArrayData::invalid_index
;
3713 pos
= ad
->iter_advance(pos
)) {
3714 CVarRef v
= ad
->getValueRef(pos
);
3715 if (v
.isInteger()) {
3716 st
->update(v
.toInt64());
3717 } else if (v
.isString()) {
3718 st
->update(v
.getStringData());
3720 throwBadValueType();
3726 Object
c_Set::t_discard(CVarRef key
) {
3727 return t_remove(key
);
3730 Object
c_Set::t_difference(CVarRef iterable
) {
3731 if (!iterable
.isObject()) {
3732 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3733 "Parameter iterable must be an instance of Iterable"));
3737 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
3738 for (; iter
; ++iter
) {
3739 t_remove(iter
.second());
3744 Object
c_Set::t_updatefromarrayvalues(CVarRef arr
) {
3745 if (!arr
.isArray()) {
3746 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3747 "Parameter arr must be an array"));
3751 ArrayIter iter
= getArrayIterHelper(arr
, sz
);
3752 for (; iter
; ++iter
) {
3753 t_add(iter
.second());
3758 Object
c_Set::t_updatefromiterablevalues(CVarRef iterable
) {
3759 if (!iterable
.isObject()) {
3760 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3761 "Parameter iterable must be an instance of Iterable"));
3765 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
3766 for (; iter
; ++iter
) {
3767 t_add(iter
.second());
3772 Object
c_Set::ti_fromarrays(int _argc
,
3773 CArrRef _argv
/* = null_array */) {
3775 Object ret
= st
= NEWOBJ(c_Set
)();
3776 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
3777 Variant arr
= iter
.second();
3778 if (!arr
.isArray()) {
3779 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3780 "Parameters must be arrays"));
3783 ArrayData
* ad
= arr
.getArrayData();
3784 for (ssize_t pos
= ad
->iter_begin(); pos
!= ArrayData::invalid_index
;
3785 pos
= ad
->iter_advance(pos
)) {
3786 st
->t_add(ad
->getValueRef(pos
));
3792 Object
c_Set::ti_fromiterablevalues(CVarRef iterable
) {
3793 if (!iterable
.isObject()) {
3794 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
3795 "Parameter iterable must be an instance of Iterable"));
3799 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
3801 Object ret
= st
= NEWOBJ(c_Set
)();
3802 for (; iter
; ++iter
) {
3803 st
->t_add(iter
.second());
3808 void c_Set::throwOOB(int64_t val
) {
3812 void c_Set::throwOOB(StringData
* val
) {
3816 void c_Set::throwNoIndexAccess() {
3817 Object
e(SystemLib::AllocRuntimeExceptionObject(
3818 "Cannot use object of type Set as array"));
3822 void c_Set::add(TypedValue
* val
) {
3823 if (val
->m_type
== KindOfInt64
) {
3824 update(val
->m_data
.num
);
3825 } else if (IS_STRING_TYPE(val
->m_type
)) {
3826 update(val
->m_data
.pstr
);
3828 throwBadValueType();
3832 #define STRING_HASH(x) (int32_t(x) | 0x80000000)
3834 bool inline hitString(const c_Set::Bucket
* p
, const char* k
,
3835 int len
, int32_t hash
) ALWAYS_INLINE
;
3836 bool inline hitString(const c_Set::Bucket
* p
, const char* k
,
3837 int len
, int32_t hash
) {
3838 assert(p
->validValue());
3839 if (p
->hasInt()) return false;
3840 const char* data
= p
->data
.m_data
.pstr
->data();
3841 return data
== k
|| (p
->hash() == hash
&&
3842 p
->data
.m_data
.pstr
->size() == len
&&
3843 memcmp(data
, k
, len
) == 0);
3846 bool inline hitInt(const c_Set::Bucket
* p
, int64_t ki
) ALWAYS_INLINE
;
3847 bool inline hitInt(const c_Set::Bucket
* p
, int64_t ki
) {
3848 assert(p
->validValue());
3849 return p
->hasInt() && p
->data
.m_data
.num
== ki
;
3852 #define FIND_BODY(h0, hit) \
3853 size_t tableMask = m_nLastSlot; \
3854 size_t probeIndex = size_t(h0) & tableMask; \
3855 Bucket* p = fetchBucket(probeIndex); \
3856 if (LIKELY(p->validValue() && (hit))) { \
3859 if (LIKELY(p->empty())) { \
3862 for (size_t i = 1;; ++i) { \
3863 assert(i <= tableMask); \
3864 probeIndex = (probeIndex + i) & tableMask; \
3865 assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \
3866 p = fetchBucket(probeIndex); \
3867 if (p->validValue() && (hit)) { \
3875 #define FIND_FOR_INSERT_BODY(h0, hit) \
3876 size_t tableMask = m_nLastSlot; \
3877 size_t probeIndex = size_t(h0) & tableMask; \
3878 Bucket* p = fetchBucket(h0 & tableMask); \
3879 if (LIKELY((p->validValue() && (hit)) || \
3883 Bucket* ts = nullptr; \
3884 for (size_t i = 1;; ++i) { \
3885 if (UNLIKELY(p->tombstone() && !ts)) { \
3888 assert(i <= tableMask); \
3889 probeIndex = (probeIndex + i) & tableMask; \
3890 assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \
3891 p = fetchBucket(probeIndex); \
3892 if (LIKELY(p->validValue() && (hit))) { \
3895 if (LIKELY(p->empty())) { \
3896 if (LIKELY(!ts)) { \
3903 c_Set::Bucket
* c_Set::find(int64_t h
) const {
3904 FIND_BODY(h
, hitInt(p
, h
));
3907 c_Set::Bucket
* c_Set::find(const char* k
, int len
, strhash_t prehash
) const {
3908 FIND_BODY(prehash
, hitString(p
, k
, len
, STRING_HASH(prehash
)));
3911 c_Set::Bucket
* c_Set::findForInsert(int64_t h
) const {
3912 FIND_FOR_INSERT_BODY(h
, hitInt(p
, h
));
3915 c_Set::Bucket
* c_Set::findForInsert(const char* k
, int len
,
3916 strhash_t prehash
) const {
3917 FIND_FOR_INSERT_BODY(prehash
, hitString(p
, k
, len
, STRING_HASH(prehash
)));
3920 // findForNewInsert() is only safe to use if you know for sure that the
3921 // value is not already present in the Set.
3922 inline ALWAYS_INLINE
3923 c_Set::Bucket
* c_Set::findForNewInsert(size_t h0
) const {
3924 size_t tableMask
= m_nLastSlot
;
3925 size_t probeIndex
= h0
& tableMask
;
3926 Bucket
* p
= fetchBucket(probeIndex
);
3927 if (LIKELY(p
->empty())) {
3930 for (size_t i
= 1;; ++i
) {
3931 assert(i
<= tableMask
);
3932 probeIndex
= (probeIndex
+ i
) & tableMask
;
3933 assert(((size_t(h0
)+((i
+ i
*i
) >> 1)) & tableMask
) == probeIndex
);
3934 p
= fetchBucket(probeIndex
);
3935 if (LIKELY(p
->empty())) {
3943 #undef FIND_FOR_INSERT_BODY
3945 void c_Set::update(int64_t h
) {
3946 Bucket
* p
= findForInsert(h
);
3948 if (p
->validValue()) {
3953 if (!p
->tombstone()) {
3954 if (UNLIKELY(++m_load
>= computeMaxLoad())) {
3956 p
= findForInsert(h
);
3963 void c_Set::update(StringData
*key
) {
3964 strhash_t h
= key
->hash();
3965 Bucket
* p
= findForInsert(key
->data(), key
->size(), h
);
3967 if (p
->validValue()) {
3972 if (!p
->tombstone()) {
3973 if (UNLIKELY(++m_load
>= computeMaxLoad())) {
3975 p
= findForInsert(key
->data(), key
->size(), h
);
3982 void c_Set::erase(Bucket
* p
) {
3986 if (p
->validValue()) {
3988 tvRefcountedDecRef(&p
->data
);
3989 p
->data
.m_type
= (DataType
)KindOfTombstone
;
3990 if (m_size
< computeMinElements() && m_size
) {
3996 void c_Set::adjustCapacityImpl(int64_t sz
) {
3999 if (sz
<= 0) return;
4002 if (m_nLastSlot
== 0) {
4003 assert(m_data
== (Bucket
*)emptySetSlot
);
4004 m_nLastSlot
= Util::roundUpToPowerOfTwo(sz
<< 1) - 1;
4005 m_data
= (Bucket
*)smart_calloc(numSlots(), sizeof(Bucket
));
4008 size_t oldNumSlots
= numSlots();
4009 m_nLastSlot
= Util::roundUpToPowerOfTwo(sz
<< 1) - 1;
4011 Bucket
* oldBuckets
= m_data
;
4012 m_data
= (Bucket
*)smart_calloc(numSlots(), sizeof(Bucket
));
4013 for (uint i
= 0; i
< oldNumSlots
; ++i
) {
4014 Bucket
* p
= &oldBuckets
[i
];
4015 if (p
->validValue()) {
4017 findForNewInsert(p
->hasInt() ? p
->data
.m_data
.num
: p
->hash());
4018 memcpy(np
, p
, sizeof(Bucket
));
4021 smart_free(oldBuckets
);
4024 ssize_t
c_Set::iter_begin() const {
4025 if (!m_size
) return 0;
4026 for (uint i
= 0; i
<= m_nLastSlot
; ++i
) {
4027 Bucket
* p
= fetchBucket(i
);
4028 if (p
->validValue()) {
4029 return reinterpret_cast<ssize_t
>(p
);
4035 ssize_t
c_Set::iter_next(ssize_t pos
) const {
4039 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
4040 Bucket
* pLast
= fetchBucket(m_nLastSlot
);
4042 while (p
<= pLast
) {
4043 if (p
->validValue()) {
4044 return reinterpret_cast<ssize_t
>(p
);
4051 ssize_t
c_Set::iter_prev(ssize_t pos
) const {
4055 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
4056 Bucket
* pStart
= m_data
;
4058 while (p
>= pStart
) {
4059 if (p
->validValue()) {
4060 return reinterpret_cast<ssize_t
>(p
);
4067 Variant
c_Set::iter_value(ssize_t pos
) const {
4069 Bucket
* p
= reinterpret_cast<Bucket
*>(pos
);
4070 return tvAsCVarRef(&p
->data
);
4073 void c_Set::throwBadValueType() {
4074 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
4075 "Only integer values and string values may be used with Sets"));
4079 void c_Set::Bucket::dump() {
4080 if (!validValue()) {
4081 printf("c_Set::Bucket: %s\n", (empty() ? "empty" : "tombstone"));
4084 printf("c_Set::Bucket: %d\n", hash());
4085 tvAsCVarRef(&data
).dump();
4088 TypedValue
* c_Set::OffsetGet(ObjectData
* obj
, TypedValue
* key
) {
4089 c_Set::throwNoIndexAccess();
4092 void c_Set::OffsetSet(ObjectData
* obj
, TypedValue
* key
, TypedValue
* val
) {
4093 c_Set::throwNoIndexAccess();
4096 bool c_Set::OffsetIsset(ObjectData
* obj
, TypedValue
* key
) {
4097 c_Set::throwNoIndexAccess();
4100 bool c_Set::OffsetEmpty(ObjectData
* obj
, TypedValue
* key
) {
4101 c_Set::throwNoIndexAccess();
4104 bool c_Set::OffsetContains(ObjectData
* obj
, TypedValue
* key
) {
4105 c_Set::throwNoIndexAccess();
4108 void c_Set::OffsetAppend(ObjectData
* obj
, TypedValue
* val
) {
4109 assert(val
->m_type
!= KindOfRef
);
4110 auto st
= static_cast<c_Set
*>(obj
);
4114 void c_Set::OffsetUnset(ObjectData
* obj
, TypedValue
* key
) {
4115 c_Set::throwNoIndexAccess();
4118 bool c_Set::Equals(ObjectData
* obj1
, ObjectData
* obj2
) {
4119 auto st1
= static_cast<c_Set
*>(obj1
);
4120 auto st2
= static_cast<c_Set
*>(obj2
);
4121 if (st1
->m_size
!= st2
->m_size
) return false;
4122 for (uint i
= 0; i
<= st1
->m_nLastSlot
; ++i
) {
4123 c_Set::Bucket
& p
= st1
->m_data
[i
];
4124 if (p
.validValue()) {
4126 int64_t key
= p
.data
.m_data
.num
;
4127 if (!st2
->find(key
)) return false;
4130 StringData
* key
= p
.data
.m_data
.pstr
;
4131 if (!st2
->find(key
->data(), key
->size(), key
->hash())) return false;
4138 void c_Set::Unserialize(ObjectData
* obj
,
4139 VariableUnserializer
* uns
,
4143 throw Exception("Set does not support the '%c' serialization "
4146 auto st
= static_cast<c_Set
*>(obj
);
4148 for (int64_t i
= 0; i
< sz
; ++i
) {
4150 // When unserializing an element of a Set, we use ColKeyMode for now.
4151 // This will make the unserializer to reserve an id for the element
4152 // but won't allow referencing the element via 'r' or 'R'.
4153 k
.unserialize(uns
, Uns::ColKeyMode
);
4155 if (k
.isInteger()) {
4156 auto h
= k
.toInt64();
4157 p
= st
->findForInsert(h
);
4158 if (UNLIKELY(p
->validValue())) continue;
4160 } else if (k
.isString()) {
4161 auto key
= k
.getStringData();
4162 auto h
= key
->hash();
4163 p
= st
->findForInsert(key
->data(), key
->size(), h
);
4164 if (UNLIKELY(p
->validValue())) continue;
4167 throw Exception("Set values must be integers or strings");
4174 c_SetIterator::c_SetIterator(Class
* cb
) :
4178 c_SetIterator::~c_SetIterator() {
4181 void c_SetIterator::t___construct() {
4184 Variant
c_SetIterator::t_current() {
4185 c_Set
* st
= m_obj
.get();
4186 if (UNLIKELY(m_version
!= st
->getVersion())) {
4187 throw_collection_modified();
4190 throw_iterator_not_valid();
4192 return st
->iter_value(m_pos
);
4195 Variant
c_SetIterator::t_key() {
4196 return uninit_null();
4199 bool c_SetIterator::t_valid() {
4203 void c_SetIterator::t_next() {
4204 c_Set
* st
= m_obj
.get();
4205 if (UNLIKELY(m_version
!= st
->getVersion())) {
4206 throw_collection_modified();
4208 m_pos
= st
->iter_next(m_pos
);
4211 void c_SetIterator::t_rewind() {
4212 c_Set
* st
= m_obj
.get();
4213 if (UNLIKELY(m_version
!= st
->getVersion())) {
4214 throw_collection_modified();
4216 m_pos
= st
->iter_begin();
4219 ///////////////////////////////////////////////////////////////////////////////
4221 c_Pair::c_Pair(Class
* cb
) :
4222 ExtObjectDataFlags
<ObjectData::PairAttrInit
|
4225 ObjectData::UseIsset
|
4226 ObjectData::UseUnset
>(cb
),
4231 if (LIKELY(m_size
== 2)) {
4232 tvRefcountedDecRef(&elm0
);
4233 tvRefcountedDecRef(&elm1
);
4237 tvRefcountedDecRef(&elm0
);
4241 void c_Pair::t___construct() {
4242 Object
e(SystemLib::AllocRuntimeExceptionObject(
4243 "Pairs cannot be created using the new operator"));
4247 Array
c_Pair::toArrayImpl() const {
4248 ArrayInit
ai(2, ArrayInit::vectorInit
);
4249 ai
.set(tvAsCVarRef(&elm0
));
4250 ai
.set(tvAsCVarRef(&elm1
));
4254 Array
c_Pair::o_toArray() const {
4255 check_collection_cast_to_array();
4256 return toArrayImpl();
4259 ObjectData
* c_Pair::clone() {
4260 auto pair
= NEWOBJ(c_Pair
)();
4261 pair
->incRefCount();
4263 tvDup(&elm0
, &pair
->elm0
);
4264 tvDup(&elm1
, &pair
->elm1
);
4268 bool c_Pair::t_isempty() {
4272 int64_t c_Pair::t_count() {
4276 Object
c_Pair::t_items() {
4277 return SystemLib::AllocIterableViewObject(this);
4280 Object
c_Pair::t_keys() {
4282 Object obj
= vec
= NEWOBJ(c_Vector
)();
4285 vec
->m_data
[0].m_data
.num
= 0;
4286 vec
->m_data
[0].m_type
= KindOfInt64
;
4287 vec
->m_data
[1].m_data
.num
= 1;
4288 vec
->m_data
[1].m_type
= KindOfInt64
;
4292 Object
c_Pair::t_view() {
4293 return SystemLib::AllocKeyedIterableViewObject(this);
4296 Object
c_Pair::t_kvzip() {
4298 Object obj
= vec
= NEWOBJ(c_Vector
)();
4300 for (uint i
= 0; i
< 2; ++i
) {
4301 c_Pair
* pair
= NEWOBJ(c_Pair
)();
4302 pair
->incRefCount();
4303 pair
->elm0
.m_type
= KindOfInt64
;
4304 pair
->elm0
.m_data
.num
= i
;
4306 pair
->add(&getElms()[i
]);
4307 vec
->m_data
[i
].m_data
.pobj
= pair
;
4308 vec
->m_data
[i
].m_type
= KindOfObject
;
4314 Variant
c_Pair::t_at(CVarRef key
) {
4315 if (key
.isInteger()) {
4316 return tvAsCVarRef(at(key
.toInt64()));
4319 return init_null_variant
;
4322 Variant
c_Pair::t_get(CVarRef key
) {
4323 if (key
.isInteger()) {
4324 TypedValue
* tv
= get(key
.toInt64());
4326 return tvAsCVarRef(tv
);
4328 return init_null_variant
;
4332 return init_null_variant
;
4335 bool c_Pair::t_containskey(CVarRef key
) {
4336 if (key
.isInteger()) {
4337 return contains(key
.toInt64());
4343 Array
c_Pair::t_toarray() {
4344 return toArrayImpl();
4347 Object
c_Pair::t_getiterator() {
4348 c_PairIterator
* it
= NEWOBJ(c_PairIterator
)();
4354 Object
c_Pair::t_map(CVarRef callback
) {
4356 vm_decode_function(callback
, nullptr, false, ctx
);
4358 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
4359 "Parameter must be a valid callback"));
4363 Object obj
= vec
= NEWOBJ(c_Vector
)();
4365 for (uint64_t i
= 0; i
< 2; ++i
) {
4367 tvDup(&getElms()[i
], args
+ 0);
4368 g_vmContext
->invokeFuncFew(&vec
->m_data
[i
], ctx
, 1, args
);
4374 Object
c_Pair::t_filter(CVarRef callback
) {
4376 vm_decode_function(callback
, nullptr, false, ctx
);
4378 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
4379 "Parameter must be a valid callback"));
4383 Object obj
= vec
= NEWOBJ(c_Vector
)();
4384 for (uint64_t i
= 0; i
< 2; ++i
) {
4387 tvDup(&getElms()[i
], args
+ 0);
4388 g_vmContext
->invokeFuncFew(ret
.asTypedValue(), ctx
, 1, args
);
4389 if (ret
.toBoolean()) {
4390 vec
->add(&getElms()[i
]);
4396 Object
c_Pair::t_zip(CVarRef iterable
) {
4398 ArrayIter iter
= getArrayIterHelper(iterable
, sz
);
4400 Object obj
= vec
= NEWOBJ(c_Vector
)();
4401 vec
->reserve(std::min(sz
, size_t(2)));
4402 for (uint64_t i
= 0; i
< 2 && iter
; ++i
, ++iter
) {
4403 Variant v
= iter
.second();
4404 if (vec
->m_capacity
<= vec
->m_size
) {
4407 c_Pair
* pair
= NEWOBJ(c_Pair
)();
4408 pair
->incRefCount();
4409 pair
->add(&getElms()[i
]);
4410 pair
->add(cvarToCell(&v
));
4411 vec
->m_data
[i
].m_data
.pobj
= pair
;
4412 vec
->m_data
[i
].m_type
= KindOfObject
;
4418 void c_Pair::throwOOB(int64_t key
) {
4419 throwIntOOB(key
, true);
4422 void c_Pair::throwBadKeyType() {
4423 Object
e(SystemLib::AllocInvalidArgumentExceptionObject(
4424 "Only integer keys may be used with Pairs"));
4428 TypedValue
* c_Pair::OffsetGet(ObjectData
* obj
, TypedValue
* key
) {
4429 assert(key
->m_type
!= KindOfRef
);
4430 auto pair
= static_cast<c_Pair
*>(obj
);
4431 if (key
->m_type
== KindOfInt64
) {
4432 return pair
->at(key
->m_data
.num
);
4438 void c_Pair::OffsetSet(ObjectData
* obj
, TypedValue
* key
, TypedValue
* val
) {
4439 Object
e(SystemLib::AllocRuntimeExceptionObject(
4440 "Cannot assign to an element of a Pair"));
4444 bool c_Pair::OffsetIsset(ObjectData
* obj
, TypedValue
* key
) {
4445 assert(key
->m_type
!= KindOfRef
);
4446 auto pair
= static_cast<c_Pair
*>(obj
);
4448 if (key
->m_type
== KindOfInt64
) {
4449 result
= pair
->get(key
->m_data
.num
);
4454 return result
? isset(tvAsCVarRef(result
)) : false;
4457 bool c_Pair::OffsetEmpty(ObjectData
* obj
, TypedValue
* key
) {
4458 assert(key
->m_type
!= KindOfRef
);
4459 auto pair
= static_cast<c_Pair
*>(obj
);
4461 if (key
->m_type
== KindOfInt64
) {
4462 result
= pair
->get(key
->m_data
.num
);
4467 return result
? empty(tvAsCVarRef(result
)) : true;
4470 bool c_Pair::OffsetContains(ObjectData
* obj
, TypedValue
* key
) {
4471 assert(key
->m_type
!= KindOfRef
);
4472 auto pair
= static_cast<c_Pair
*>(obj
);
4473 if (key
->m_type
== KindOfInt64
) {
4474 return pair
->contains(key
->m_data
.num
);
4481 void c_Pair::OffsetAppend(ObjectData
* obj
, TypedValue
* val
) {
4482 assert(val
->m_type
!= KindOfRef
);
4483 auto pair
= static_cast<c_Pair
*>(obj
);
4487 void c_Pair::OffsetUnset(ObjectData
* obj
, TypedValue
* key
) {
4488 Object
e(SystemLib::AllocRuntimeExceptionObject(
4489 "Cannot unset element of a Pair"));
4493 bool c_Pair::Equals(ObjectData
* obj1
, ObjectData
* obj2
) {
4494 auto pair1
= static_cast<c_Pair
*>(obj1
);
4495 auto pair2
= static_cast<c_Pair
*>(obj2
);
4496 return equal(tvAsCVarRef(&pair1
->elm0
), tvAsCVarRef(&pair2
->elm0
)) &&
4497 equal(tvAsCVarRef(&pair1
->elm1
), tvAsCVarRef(&pair2
->elm1
));
4500 void c_Pair::Unserialize(ObjectData
* obj
,
4501 VariableUnserializer
* uns
,
4506 throw Exception("Pair does not support the '%c' serialization "
4509 auto pair
= static_cast<c_Pair
*>(obj
);
4511 pair
->elm0
.m_type
= KindOfNull
;
4512 pair
->elm1
.m_type
= KindOfNull
;
4513 tvAsVariant(&pair
->elm0
).unserialize(uns
, Uns::ColValueMode
);
4514 tvAsVariant(&pair
->elm1
).unserialize(uns
, Uns::ColValueMode
);
4517 c_PairIterator::c_PairIterator(Class
* cb
) :
4521 c_PairIterator::~c_PairIterator() {
4524 void c_PairIterator::t___construct() {
4527 Variant
c_PairIterator::t_current() {
4528 c_Pair
* pair
= m_obj
.get();
4529 if (!pair
->contains(m_pos
)) {
4530 throw_iterator_not_valid();
4532 return tvAsCVarRef(&pair
->getElms()[m_pos
]);
4535 Variant
c_PairIterator::t_key() {
4536 c_Pair
* pair
= m_obj
.get();
4537 if (!pair
->contains(m_pos
)) {
4538 throw_iterator_not_valid();
4543 bool c_PairIterator::t_valid() {
4545 c_Pair
* pair
= m_obj
.get();
4546 return pair
&& (m_pos
< ssize_t(2));
4549 void c_PairIterator::t_next() {
4553 void c_PairIterator::t_rewind() {
4557 ///////////////////////////////////////////////////////////////////////////////
4559 #define COLLECTION_MAGIC_METHODS(cls) \
4560 String c_##cls::t___tostring() { \
4563 Variant c_##cls::t___get(Variant name) { \
4564 throw_collection_property_exception(); \
4566 Variant c_##cls::t___set(Variant name, Variant value) { \
4567 throw_collection_property_exception(); \
4569 bool c_##cls::t___isset(Variant name) { \
4570 throw_collection_property_exception(); \
4572 Variant c_##cls::t___unset(Variant name) { \
4573 throw_collection_property_exception(); \
4576 COLLECTION_MAGIC_METHODS(Vector
)
4577 COLLECTION_MAGIC_METHODS(Map
)
4578 COLLECTION_MAGIC_METHODS(StableMap
)
4579 COLLECTION_MAGIC_METHODS(Set
)
4580 COLLECTION_MAGIC_METHODS(Pair
)
4582 #undef COLLECTION_MAGIC_METHODS
4584 void collectionSerialize(ObjectData
* obj
, VariableSerializer
* serializer
) {
4585 assert(obj
->isCollection());
4586 int64_t sz
= collectionSize(obj
);
4587 if (obj
->getCollectionType() == Collection::VectorType
||
4588 obj
->getCollectionType() == Collection::SetType
||
4589 obj
->getCollectionType() == Collection::PairType
) {
4590 serializer
->setObjectInfo(obj
->o_getClassName(), obj
->o_getId(), 'V');
4591 serializer
->writeArrayHeader(sz
, true);
4592 if (serializer
->getType() == VariableSerializer::Serialize
||
4593 serializer
->getType() == VariableSerializer::APCSerialize
||
4594 serializer
->getType() == VariableSerializer::DebuggerSerialize
||
4595 serializer
->getType() == VariableSerializer::VarExport
||
4596 serializer
->getType() == VariableSerializer::PHPOutput
) {
4597 // For the 'V' serialization format, we don't print out keys
4598 // for Serialize, APCSerialize, DebuggerSerialize
4599 for (ArrayIter
iter(obj
); iter
; ++iter
) {
4600 serializer
->writeCollectionKeylessPrefix();
4601 serializer
->writeArrayValue(iter
.second());
4604 for (ArrayIter
iter(obj
); iter
; ++iter
) {
4605 if (obj
->getCollectionType() != Collection::SetType
) {
4606 serializer
->writeCollectionKey(iter
.first());
4608 serializer
->writeCollectionKeylessPrefix();
4610 serializer
->writeArrayValue(iter
.second());
4613 serializer
->writeArrayFooter();
4615 assert(obj
->getCollectionType() == Collection::MapType
||
4616 obj
->getCollectionType() == Collection::StableMapType
);
4617 serializer
->setObjectInfo(obj
->o_getClassName(), obj
->o_getId(), 'K');
4618 serializer
->writeArrayHeader(sz
, false);
4619 for (ArrayIter
iter(obj
); iter
; ++iter
) {
4620 serializer
->writeCollectionKey(iter
.first());
4621 serializer
->writeArrayValue(iter
.second());
4623 serializer
->writeArrayFooter();
4627 void collectionDeepCopyTV(TypedValue
* tv
) {
4628 switch (tv
->m_type
) {
4630 ArrayData
* arr
= collectionDeepCopyArray(tv
->m_data
.parr
);
4631 if (tv
->m_data
.parr
->decRefCount() == 0) {
4632 tv
->m_data
.parr
->release();
4634 tv
->m_data
.parr
= arr
;
4637 case KindOfObject
: {
4638 ObjectData
* obj
= tv
->m_data
.pobj
;
4639 if (!obj
->isCollection()) break;
4640 switch (obj
->getCollectionType()) {
4641 case Collection::VectorType
:
4642 obj
= collectionDeepCopyVector(static_cast<c_Vector
*>(obj
));
4644 case Collection::MapType
:
4645 obj
= collectionDeepCopyMap(static_cast<c_Map
*>(obj
));
4647 case Collection::StableMapType
:
4648 obj
= collectionDeepCopyStableMap(static_cast<c_StableMap
*>(obj
));
4650 case Collection::PairType
:
4651 obj
= collectionDeepCopyPair(static_cast<c_Pair
*>(obj
));
4658 if (tv
->m_data
.pobj
->decRefCount() == 0) {
4659 tv
->m_data
.pobj
->release();
4661 tv
->m_data
.pobj
= obj
;
4672 ArrayData
* collectionDeepCopyArray(ArrayData
* arr
) {
4673 Array a
= arr
= arr
->copy();
4674 for (ArrayIter
iter(arr
); iter
; ++iter
) {
4675 collectionDeepCopyTV((TypedValue
*)(&iter
.secondRef()));
4680 ObjectData
* collectionDeepCopyVector(c_Vector
* vec
) {
4681 Object o
= vec
= static_cast<c_Vector
*>(vec
->clone());
4682 size_t sz
= vec
->m_size
;
4683 for (size_t i
= 0; i
< sz
; ++i
) {
4684 collectionDeepCopyTV(&vec
->m_data
[i
]);
4689 ObjectData
* collectionDeepCopyMap(c_Map
* mp
) {
4690 Object o
= mp
= static_cast<c_Map
*>(mp
->clone());
4691 uint lastSlot
= mp
->m_nLastSlot
;
4692 for (uint i
= 0; i
<= lastSlot
; ++i
) {
4693 c_Map::Bucket
* p
= mp
->fetchBucket(i
);
4694 if (p
->validValue()) {
4695 collectionDeepCopyTV(&p
->data
);
4701 ObjectData
* collectionDeepCopyStableMap(c_StableMap
* smp
) {
4702 Object o
= smp
= static_cast<c_StableMap
*>(smp
->clone());
4703 for (c_StableMap::Bucket
* p
= smp
->m_pListHead
; p
; p
= p
->pListNext
) {
4704 collectionDeepCopyTV(&p
->data
);
4709 ObjectData
* collectionDeepCopySet(c_Set
* st
) {
4710 Object o
= st
= static_cast<c_Set
*>(st
->clone());
4714 ObjectData
* collectionDeepCopyPair(c_Pair
* pair
) {
4715 Object o
= pair
= static_cast<c_Pair
*>(pair
->clone());
4716 collectionDeepCopyTV(&pair
->elm0
);
4717 collectionDeepCopyTV(&pair
->elm1
);
4721 ///////////////////////////////////////////////////////////////////////////////