2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #ifndef incl_HPHP_ARRAY_ITERATOR_H_
18 #define incl_HPHP_ARRAY_ITERATOR_H_
23 #include "hphp/runtime/base/array-data-defs.h"
24 #include "hphp/runtime/base/collections.h"
25 #include "hphp/runtime/base/packed-array.h"
26 #include "hphp/runtime/base/packed-array-defs.h"
27 #include "hphp/runtime/base/mixed-array.h"
28 #include "hphp/runtime/base/member-val.h"
29 #include "hphp/runtime/base/set-array.h"
30 #include "hphp/runtime/base/req-containers.h"
31 #include "hphp/runtime/base/req-ptr.h"
32 #include "hphp/runtime/base/type-variant.h"
33 #include "hphp/util/tls-pod-bag.h"
34 #include "hphp/util/type-scan.h"
37 ///////////////////////////////////////////////////////////////////////////////
43 enum class IterNextIndex
: uint16_t {
51 * An iteration normally looks like this:
53 * for (ArrayIter iter(data); iter; ++iter) {
59 * Iterator for an immutable array.
62 enum Type
: uint16_t {
65 TypeIterator
// for objects that implement Iterator or
69 enum NoInc
{ noInc
= 0 };
72 * Constructors. Note that sometimes ArrayIter objects are created
73 * without running their C++ constructor. (See new_iter_array.)
78 explicit ArrayIter(const ArrayData
* data
);
79 ArrayIter(const ArrayData
* data
, NoInc
) {
82 m_pos
= data
->iter_begin();
85 explicit ArrayIter(const MixedArray
*) = delete;
86 explicit ArrayIter(const Array
& array
);
87 explicit ArrayIter(ObjectData
* obj
);
88 ArrayIter(ObjectData
* obj
, NoInc
);
89 explicit ArrayIter(const Object
& obj
);
90 explicit ArrayIter(Cell
);
91 explicit ArrayIter(const Variant
& v
);
94 ArrayIter(const ArrayIter
& iter
);
97 ArrayIter(ArrayIter
&& iter
) noexcept
{
100 m_itype
= iter
.m_itype
;
101 m_nextHelperIdx
= iter
.m_nextHelperIdx
;
102 iter
.m_data
= nullptr;
106 ArrayIter
& operator=(const ArrayIter
& iter
);
109 ArrayIter
& operator=(ArrayIter
&& iter
);
121 explicit operator bool() { return !end(); }
122 void operator++() { next(); }
124 if (LIKELY(hasArrayData())) {
125 auto* ad
= getArrayData();
126 return !ad
|| m_pos
== ad
->iter_end();
130 bool endHelper() const;
133 if (LIKELY(hasArrayData())) {
134 const ArrayData
* ad
= getArrayData();
136 assert(m_pos
!= ad
->iter_end());
137 m_pos
= ad
->iter_advance(m_pos
);
145 if (LIKELY(hasArrayData())) {
146 const ArrayData
* ad
= getArrayData();
148 assert(m_pos
!= ad
->iter_end());
149 return ad
->getKey(m_pos
);
151 return firstHelper();
153 Variant
firstHelper();
155 TypedValue
nvFirst() {
156 auto const ad
= getArrayData();
157 assert(ad
&& m_pos
!= ad
->iter_end());
158 return ad
->nvGetKey(m_pos
);
162 * Retrieve the value at the current position.
167 * Get a member_rval for the current iterator position.
169 * The difference between secondRval and secondRvalPlus is that, if called
170 * when iterating an Iterable object the former will fatal and the latter
171 * will throw (whereas second will invoke the current() method on the
172 * Iterable object). Why this is has been lost in the mists of time.
174 member_rval
secondRval() const;
175 member_rval
secondRvalPlus();
177 TypedValue
secondVal() const { return secondRval().tv(); }
178 TypedValue
secondValPlus() { return secondRvalPlus().tv(); }
180 const Variant
& secondRef() const {
181 return tvAsCVarRef(secondRval().tv_ptr());
184 // Inline version of secondRef. Only for use in iterator helpers.
185 member_rval
nvSecond() const {
186 auto const ad
= getArrayData();
187 assert(ad
&& m_pos
!= ad
->iter_end());
188 return ad
->rvalPos(m_pos
);
191 bool hasArrayData() const {
192 return !((intptr_t)m_data
& 1);
195 const ArrayData
* getArrayData() const {
196 assert(hasArrayData());
202 void setPos(ssize_t newPos
) {
205 void advance(ssize_t count
) {
206 while (!end() && count
--) {
211 Type
getIterType() const {
214 void setIterType(Type iterType
) {
218 IterNextIndex
getHelperIndex() {
219 return m_nextHelperIdx
;
222 ObjectData
* getObject() const {
223 assert(!hasArrayData());
224 return (ObjectData
*)((intptr_t)m_obj
& ~1);
228 friend int64_t new_iter_array(Iter
*, ArrayData
*, TypedValue
*);
229 template<bool withRef
>
230 friend int64_t new_iter_array_key(Iter
*, ArrayData
*, TypedValue
*,
233 void arrInit(const ArrayData
* arr
);
235 template <bool incRef
>
236 void objInit(ObjectData
* obj
);
242 void setArrayData(const ArrayData
* ad
) {
243 assert((intptr_t(ad
) & 1) == 0);
245 m_nextHelperIdx
= IterNextIndex::ArrayMixed
;
247 if (ad
->hasPackedLayout()) {
248 m_nextHelperIdx
= IterNextIndex::ArrayPacked
;
249 } else if (!ad
->hasMixedLayout()) {
250 m_nextHelperIdx
= IterNextIndex::Array
;
255 void setObject(ObjectData
* obj
) {
256 assert((intptr_t(obj
) & 1) == 0);
257 m_obj
= (ObjectData
*)((intptr_t)obj
| 1);
258 m_nextHelperIdx
= IterNextIndex::Object
;
262 const ArrayData
* m_data
;
266 // m_pos is used by the array implementation to track the current position
267 // in the array. Beware that when m_data is null, m_pos is uninitialized.
270 // we don't use this pointer, but it gives ArrayIter the same layout
271 // as MArrayIter and CufIter, allowing Iter to be scanned without a union
273 MaybeCountable
* m_unused
;
274 UNUSED
int m_alsoUnused
;
275 // This is unioned so new_iter_array can initialize it more
280 IterNextIndex m_nextHelperIdx
;
282 uint32_t m_itypeAndNextHelperIdx
;
288 ///////////////////////////////////////////////////////////////////////////////
291 * MArrayIter provides the necessary functionality for supporting
292 * "foreach by reference" (also called "strong foreach").
294 * In the common case, a MArrayIter is bound to a RefData when it is
295 * initialized. When iterating objects with foreach by reference, a
296 * MArrayIter may instead be bound directly to an array which m_data
297 * points to. (This is because the array is created as a temporary.)
299 * Foreach by reference is a pain. Iteration needs to be robust in
300 * the face of two challenges: (1) the case where an element is unset
301 * during iteration, and (2) the case where user code modifies the
302 * RefData to be a different array or a non-array value. In such
303 * cases, we should never crash and ideally when an element is unset
304 * we should be able to keep track of where we are in the array.
306 * MArrayIter works by "registering" itself with the array being
307 * iterated over, in a way that any array can find out all active
308 * MArrayIters associated with it (if any). See MIterTable below.
310 * Using this association, when an array mutation occurs, if there are
311 * active MArrayIters the array will update them to ensure they behave
312 * coherently. For example, if an element is unset, the MArrayIter's
313 * that were pointing to that element are moved to point to the
314 * element before the element being unset.
316 * Note that it is possible for an iterator to point to the position
317 * before the first element (this is what the "reset" flag is for).
319 * MArrayIter has also has a m_container field to keep track of which
320 * array it has "registered" itself with. By comparing the array
321 * pointed to through m_ref with the array pointed to by m_container,
322 * MArrayIter can detect if user code has modified the inner cell to
323 * be a different array or a non-array value. When this happens, the
324 * MArrayIter unregisters itself with the old array (pointed to by
325 * m_container) and registers itself with the new array (pointed to by
326 * m_ref->tv().m_data.parr) and resumes iteration at the position
327 * pointed to by the new array's internal cursor (ArrayData::m_pos).
328 * If m_ref points to a non-array value, iteration terminates.
331 explicit MArrayIter(RefData
* ref
);
332 explicit MArrayIter(ArrayData
* data
);
335 MArrayIter(const MArrayIter
&) = delete;
336 MArrayIter
& operator=(const MArrayIter
&) = delete;
339 * It is only safe to call key() and val() if all of the following
340 * conditions are met:
341 * 1) The calls to key() and/or val() are immediately preceded by
342 * a call to advance(), prepare(), or end().
343 * 2) The iterator points to a valid position in the array.
346 ArrayData
* data
= getArray();
347 assert(data
&& data
== getContainer());
348 assert(!getResetFlag() && data
->validMArrayIter(*this));
349 return data
->getKey(m_pos
);
353 ArrayData
* data
= getArray();
354 assert(data
&& data
== getContainer());
355 assert(!data
->cowCheck() || data
->noCopyOnWrite());
356 assert(!getResetFlag());
357 assert(data
->validMArrayIter(*this));
358 // Normally it's not ok to modify the return value of rvalPos,
359 // but the whole point of mutable array iteration is that this is
360 // allowed, so this const_cast is not actually evil.
361 // TODO(#9077255): Use member_lval for this somehow.
362 return tvAsVariant(const_cast<TypedValue
*>(
363 data
->rvalPos(m_pos
).tv_ptr()
367 void release() { delete this; }
369 // Returns true if the iterator points past the last element (or if
370 // it points before the first element)
373 // Move the iterator forward one element
376 // Returns true if the iterator points to a valid element
379 ArrayData
* getArray() const {
380 return hasRef() ? getData() : getAd();
383 bool hasRef() const {
384 return m_ref
&& !(intptr_t(m_ref
) & 1LL);
387 return bool(intptr_t(m_data
) & 1LL);
389 RefData
* getRef() const {
393 ArrayData
* getAd() const {
395 return (ArrayData
*)(intptr_t(m_data
) & ~1LL);
397 void setRef(RefData
* ref
) {
400 void setAd(ArrayData
* val
) {
401 m_data
= (ArrayData
*)(intptr_t(val
) | 1LL);
403 ArrayData
* getContainer() const {
406 void setContainer(ArrayData
* arr
) {
410 bool getResetFlag() const { return m_resetFlag
; }
411 void setResetFlag(bool reset
) { m_resetFlag
= reset
; }
414 ArrayData
* getData() const {
416 return isArrayType(m_ref
->tv()->m_type
)
417 ? m_ref
->tv()->m_data
.parr
421 ArrayData
* cowCheck();
422 void escalateCheck();
423 ArrayData
* reregister();
427 * m_ref/m_data are used to keep track of the array that we're
428 * supposed to be iterating over. The low bit is used to indicate
429 * whether we are using m_ref or m_data.
431 * Mutable array iteration usually iterates over m_ref---the m_data
432 * case here occurs is when we've converted an object to an array
433 * before iterating it (and this MArrayIter object actually owns a
441 // m_pos is used by the array implementation to track the current position
445 // m_container keeps track of which array we're "registered" with. Normally
446 // getArray() and m_container refer to the same array. However, the two may
447 // differ in cases where user code has modified the inner cell to be a
448 // different array or non-array value.
449 ArrayData
* m_container
;
450 // The m_resetFlag is used to indicate a mutable array iterator is
451 // "before the first" position in the array.
452 UNUSED
uint32_t m_unused
;
453 uint32_t m_resetFlag
;
457 //////////////////////////////////////////////////////////////////////
460 * Active mutable iterators are associated with their corresponding
461 * arrays using a table in thread local storage. The iterators
462 * themselves can find their registered container using their
463 * m_container pointer, but arrays must linearly search this table to
464 * go the other direction.
466 * This scheme is optimized for the overwhelmingly common case that
467 * there are no active mutable array iterations in the whole request.
468 * When there are active mutable iterators, it is also overwhelmingly
469 * the case that there is only one, and on real applications exceeding
470 * 4 or 5 simultaneously is apparently rare.
472 * This table has the following semantics:
474 * o If there are any 'active' MArrayIters (i.e. ones that are
475 * actually associated with arrays), one of them will be present
476 * in the first Ent slot in this table. This is so that any array
477 * can check that there are no active MArrayIters just by
478 * comparing the first slot of this table with null. (See
479 * strong_iterators_exist().)
481 * o Secondly we expect that we essentially never exceed a small
482 * number iterators (outside of code specifically designed to
483 * stress mutable array iteration). We've chosen 7 preallocated
484 * slots because it fills out two cache lines, and we've observed
485 * 4 or 5 occasionally in some real programs. If there are
486 * actually more live than 7, we allocate additional space and
487 * point to it with 'extras'.
489 * o The entries in this table (including 'extras') are not
490 * guaranteed to be contiguous. Empty entries may be present in
491 * the middle, and there is no ordering.
493 * o If an entry has a non-null array pointer, it must have a
494 * non-null iter pointer. Checking either one for null are both
495 * valid ways to check if a slot is empty.
505 static constexpr int ents_size
= 7;
506 std::array
<Ent
, ents_size
> ents
;
507 // Slow path: we expect this `extras' list to rarely be allocated.
508 TlsPodBag
<Ent
,req::Allocator
<Ent
>> extras
;
510 static_assert(sizeof(MIterTable
) == 2*64, "want multiple of cache line size");
511 extern THREAD_LOCAL_FLAT(MIterTable
, tl_miter_table
);
513 void free_strong_iterators(ArrayData
*);
514 void move_strong_iterators(ArrayData
* dest
, ArrayData
* src
);
515 bool has_strong_iterator(ArrayData
*);
516 void reset_strong_iterators(ArrayData
* ad
);
518 //////////////////////////////////////////////////////////////////////
521 CufIter() : m_obj_or_cls(nullptr), m_func(nullptr),
522 m_name(nullptr), m_dynamic
{false} {}
524 const Func
* func() const { return m_func
; }
525 void* ctx() const { return m_obj_or_cls
; }
526 StringData
* name() const { return m_name
; }
527 bool dynamic() const { return m_dynamic
; }
529 void setFunc(const Func
* f
) { m_func
= f
; }
530 void setCtx(ObjectData
* obj
) { m_obj_or_cls
= obj
; }
531 void setCtx(const Class
* cls
) {
532 m_obj_or_cls
= !cls
? nullptr :
533 reinterpret_cast<ObjectData
*>((char*)cls
+ 1);
535 void setName(StringData
* name
) { m_name
= name
; }
536 void setDynamic(bool dynamic
) { m_dynamic
= dynamic
; }
538 static constexpr uint32_t funcOff() { return offsetof(CufIter
, m_func
); }
539 static constexpr uint32_t ctxOff() { return offsetof(CufIter
, m_obj_or_cls
); }
540 static constexpr uint32_t nameOff() { return offsetof(CufIter
, m_name
); }
541 static constexpr uint32_t dynamicOff() {
542 return offsetof(CufIter
, m_dynamic
);
546 ObjectData
* m_obj_or_cls
; // maybe a Class* if lsb set.
553 struct alignas(16) Iter
{
554 const ArrayIter
& arr() const { return m_u
.aiter
; }
555 const MArrayIter
& marr() const { return m_u
.maiter
; }
556 const CufIter
& cuf() const { return m_u
.cufiter
; }
557 ArrayIter
& arr() { return m_u
.aiter
; }
558 MArrayIter
& marr() { return m_u
.maiter
; }
559 CufIter
& cuf() { return m_u
.cufiter
; }
561 bool init(TypedValue
* c1
);
568 // ArrayIter, MArrayIter, and CufIter all declare pointers at the
569 // same offsets, allowing gen-type-scanners to generate a scanner
570 // automatically, for the union. If the layouts become incompatible,
571 // gen-type-scanners will report a build-time error.
580 //////////////////////////////////////////////////////////////////////
581 // Template based iteration, bypassing ArrayIter where possible
584 * Iterate the values of the iterable 'it'.
586 * If it is a collection, preCollFn will be called first, with the ObjectData
587 * as a parameter. If it returns true, no further iteration will be performed.
588 * This allows for certain optimizations - see eg BaseSet::addAll. Otherwise...
590 * If its an array or a collection, the ArrayData is passed to preArrFn, which
591 * can do any necessary setup, and as with preCollFn can return true to bypass
592 * any further work. Otherwise...
594 * The array is iterated efficiently (without ArrayIter for MixedArray,
595 * PackedArray, and SetArray), and ArrFn is called for each element.
598 * If its an iterable object, the object is iterated using ArrayIter, and
599 * objFn is called on each element. Otherwise...
601 * If none of the above apply, the function returns false.
603 * During iteration, if objFn or arrFn returns true, iteration stops.
605 * There are also two supported shortcuts:
606 * If ObjFn is a bool, and 'it' is not an array, and not a collection,
607 * IterateV will do nothing, and return the value of objFn.
609 * If PreCollFn is a bool, and 'it' is not an array, IterateV will do nothing,
610 * and return the value of preCollFn.
612 * There are overloads that take 4 and 3 arguments respectively, that pass
613 * false for the trailing arguments as a convenience.
616 // Overload for the case where we already know we have an array
617 template <typename ArrFn
, bool IncRef
= true>
618 bool IterateV(const ArrayData
* adata
, ArrFn arrFn
) {
619 if (adata
->empty()) return true;
620 if (adata
->hasPackedLayout()) {
621 PackedArray::IterateV
<ArrFn
, IncRef
>(adata
, arrFn
);
622 } else if (adata
->hasMixedLayout()) {
623 MixedArray::IterateV
<ArrFn
, IncRef
>(MixedArray::asMixed(adata
), arrFn
);
624 } else if (adata
->isKeyset()) {
625 SetArray::Iterate
<ArrFn
, IncRef
>(SetArray::asSet(adata
), arrFn
);
627 for (ArrayIter
iter(adata
); iter
; ++iter
) {
628 if (ArrayData::call_helper(arrFn
, iter
.secondVal())) {
636 template <typename PreArrFn
, typename ArrFn
, typename PreCollFn
, typename ObjFn
>
637 bool IterateV(const TypedValue
& it
,
642 assert(it
.m_type
!= KindOfRef
);
644 if (LIKELY(isArrayLikeType(it
.m_type
))) {
645 adata
= it
.m_data
.parr
;
647 adata
->incRefCount();
648 SCOPE_EXIT
{ decRefArr(adata
); };
649 if (ArrayData::call_helper(preArrFn
, adata
)) return true;
650 return IterateV
<ArrFn
, false>(adata
, arrFn
);
652 if (std::is_same
<PreCollFn
, bool>::value
) {
653 return ArrayData::call_helper(preCollFn
, nullptr);
655 if (it
.m_type
!= KindOfObject
) return false;
656 auto odata
= it
.m_data
.pobj
;
657 if (odata
->isCollection()) {
658 if (ArrayData::call_helper(preCollFn
, odata
)) return true;
659 adata
= collections::asArray(odata
);
660 if (adata
) goto do_array
;
661 assert(odata
->collectionType() == CollectionType::Pair
);
662 auto tv
= make_tv
<KindOfInt64
>(0);
663 if (!ArrayData::call_helper(arrFn
, *collections::at(odata
, &tv
))) {
665 ArrayData::call_helper(arrFn
, *collections::at(odata
, &tv
));
669 if (std::is_same
<ObjFn
, bool>::value
) {
670 return ArrayData::call_helper(objFn
, nullptr);
673 Object iterable
= odata
->iterableObject(isIterable
);
674 if (!isIterable
) return false;
675 for (ArrayIter
iter(iterable
.detach(), ArrayIter::noInc
); iter
; ++iter
) {
676 if (ArrayData::call_helper(objFn
, iter
.second().asTypedValue())) break;
681 template <typename PreArrFn
, typename ArrFn
, typename PreCollFn
>
682 bool IterateV(const TypedValue
& it
,
685 PreCollFn preCollFn
) {
686 return IterateV(it
, preArrFn
, arrFn
, preCollFn
, false);
689 template <typename PreArrFn
, typename ArrFn
>
690 bool IterateV(const TypedValue
& it
,
693 return IterateV(it
, preArrFn
, arrFn
, false);
697 * Iterate the keys and values of the iterable 'it'.
699 * The behavior is identical to that of IterateV, except the ArrFn and ObjFn
700 * callbacks are called with both a key and a value.
703 // Overload for the case where we already know we have an array
704 template <typename ArrFn
, bool IncRef
= true>
705 bool IterateKV(const ArrayData
* adata
, ArrFn arrFn
) {
706 if (adata
->empty()) return true;
707 if (adata
->hasMixedLayout()) {
708 MixedArray::IterateKV
<ArrFn
, IncRef
>(MixedArray::asMixed(adata
), arrFn
);
709 } else if (adata
->hasPackedLayout()) {
710 PackedArray::IterateKV
<ArrFn
, IncRef
>(adata
, arrFn
);
711 } else if (adata
->isKeyset()) {
712 auto fun
= [&](TypedValue v
) { return arrFn(v
, v
); };
713 SetArray::Iterate
<decltype(fun
), IncRef
>(SetArray::asSet(adata
), fun
);
715 for (ArrayIter
iter(adata
); iter
; ++iter
) {
716 if (ArrayData::call_helper(arrFn
, iter
.nvFirst(), iter
.secondVal())) {
724 template <typename PreArrFn
, typename ArrFn
, typename PreCollFn
, typename ObjFn
>
725 bool IterateKV(const TypedValue
& it
,
730 assert(it
.m_type
!= KindOfRef
);
732 if (LIKELY(isArrayLikeType(it
.m_type
))) {
733 adata
= it
.m_data
.parr
;
735 adata
->incRefCount();
736 SCOPE_EXIT
{ decRefArr(adata
); };
737 if (preArrFn(adata
)) return true;
738 return IterateKV
<ArrFn
, false>(adata
, arrFn
);
740 if (std::is_same
<PreCollFn
, bool>::value
) {
741 return ArrayData::call_helper(preCollFn
, nullptr);
743 if (it
.m_type
!= KindOfObject
) return false;
744 auto odata
= it
.m_data
.pobj
;
745 if (odata
->isCollection()) {
746 if (ArrayData::call_helper(preCollFn
, odata
)) return true;
747 adata
= collections::asArray(odata
);
748 if (adata
) goto do_array
;
749 assert(odata
->collectionType() == CollectionType::Pair
);
750 auto tv
= make_tv
<KindOfInt64
>(0);
751 if (!ArrayData::call_helper(arrFn
, tv
, *collections::at(odata
, &tv
))) {
753 ArrayData::call_helper(arrFn
, tv
, *collections::at(odata
, &tv
));
757 if (std::is_same
<ObjFn
, bool>::value
) {
758 return ArrayData::call_helper(objFn
, nullptr, nullptr);
761 Object iterable
= odata
->iterableObject(isIterable
);
762 if (!isIterable
) return false;
763 for (ArrayIter
iter(iterable
.detach(), ArrayIter::noInc
); iter
; ++iter
) {
764 if (ArrayData::call_helper(objFn
,
765 iter
.first().asTypedValue(),
766 iter
.second().asTypedValue())) {
773 template <typename PreArrFn
, typename ArrFn
, typename PreCollFn
>
774 bool IterateKV(const TypedValue
& it
,
777 PreCollFn preCollFn
) {
778 return IterateKV(it
, preArrFn
, arrFn
, preCollFn
, false);
781 template <typename PreArrFn
, typename ArrFn
>
782 bool IterateKV(const TypedValue
& it
,
785 return IterateKV(it
, preArrFn
, arrFn
, false);
788 //////////////////////////////////////////////////////////////////////
790 int64_t new_iter_array(Iter
* dest
, ArrayData
* arr
, TypedValue
* val
);
791 template <bool withRef
>
792 int64_t new_iter_array_key(Iter
* dest
, ArrayData
* arr
, TypedValue
* val
,
794 int64_t new_iter_object(Iter
* dest
, ObjectData
* obj
, Class
* ctx
,
795 TypedValue
* val
, TypedValue
* key
);
796 int64_t witer_next_key(Iter
* dest
, TypedValue
* val
, TypedValue
* key
);
799 int64_t new_miter_array_key(Iter
* dest
, RefData
* arr
, TypedValue
* val
,
801 int64_t new_miter_object(Iter
* dest
, RefData
* obj
, Class
* ctx
,
802 TypedValue
* val
, TypedValue
* key
);
803 int64_t new_miter_other(Iter
* dest
, RefData
* data
);
804 int64_t miter_next_key(Iter
* dest
, TypedValue
* val
, TypedValue
* key
);
806 int64_t iter_next_ind(Iter
* iter
, TypedValue
* valOut
);
807 int64_t iter_next_key_ind(Iter
* iter
, TypedValue
* valOut
, TypedValue
* keyOut
);
809 //////////////////////////////////////////////////////////////////////
813 #endif // incl_HPHP_ARRAY_ITERATOR_H_