Arquivos
hhvm/hphp/runtime/base/array/array_iterator.cpp
T
mwilliams 78ca8ecbaa Add bytecodes to decode a function, and push an actrec for a decoded function
array_filter and friends only need to evaluate the callback once,
so add bytecodes to support that.

Depends on D817883
2013-05-30 17:33:05 -07:00

1149 linhas
33 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/base/array/array_iterator.h"
#include "hphp/runtime/base/array/array_data.h"
#include "hphp/runtime/base/array/hphp_array.h"
#include "hphp/runtime/base/complex_types.h"
#include "hphp/runtime/base/object_data.h"
#include "hphp/runtime/ext/ext_collections.h"
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
// Static strings.
static const Trace::Module TRACEMOD = Trace::runtime;
static StaticString s_rewind("rewind");
static StaticString s_valid("valid");
static StaticString s_next("next");
static StaticString s_key("key");
static StaticString s_current("current");
///////////////////////////////////////////////////////////////////////////////
// ArrayIter
ArrayIter::ArrayIter() : m_pos(ArrayData::invalid_index) {
m_data = nullptr;
}
HOT_FUNC
ArrayIter::ArrayIter(const ArrayData *data) {
setArrayData(data);
if (data) {
data->incRefCount();
m_pos = data->iter_begin();
} else {
m_pos = ArrayData::invalid_index;
}
}
HOT_FUNC
ArrayIter::ArrayIter(CArrRef array) : m_pos(0) {
const ArrayData* ad = array.get();
setArrayData(ad);
if (ad) {
ad->incRefCount();
m_pos = ad->iter_begin();
} else {
m_pos = ArrayData::invalid_index;
}
}
void ArrayIter::reset() {
if (hasArrayData()) {
const ArrayData* ad = getArrayData();
m_data = nullptr;
if (ad) decRefArr(const_cast<ArrayData*>(ad));
return;
}
ObjectData* obj = getObject();
m_data = nullptr;
assert(obj);
decRefObj(obj);
}
template <bool incRef>
void ArrayIter::objInit(ObjectData *obj) {
assert(obj);
setObject(obj);
if (incRef) {
obj->incRefCount();
}
switch (getCollectionType()) {
case Collection::VectorType: {
m_version = getVector()->getVersion();
m_pos = 0;
break;
}
case Collection::MapType: {
c_Map* mp = getMap();
m_version = mp->getVersion();
m_pos = mp->iter_begin();
break;
}
case Collection::StableMapType: {
c_StableMap* smp = getStableMap();
m_version = smp->getVersion();
m_pos = smp->iter_begin();
break;
}
case Collection::SetType: {
c_Set* st = getSet();
m_version = st->getVersion();
m_pos = st->iter_begin();
break;
}
case Collection::PairType: {
m_pos = 0;
break;
}
default: {
assert(obj->instanceof(SystemLib::s_IteratorClass));
obj->o_invoke_few_args(s_rewind, 0);
break;
}
}
}
ArrayIter::ArrayIter(ObjectData *obj)
: m_pos(ArrayData::invalid_index) {
objInit<true>(obj);
}
ArrayIter::ArrayIter(Object &obj, TransferOwner)
: m_pos(ArrayData::invalid_index) {
objInit<false>(obj.get());
(void) obj.detach();
}
// Special constructor used by the VM. This constructor does not increment the
// refcount of the specified object.
ArrayIter::ArrayIter(ObjectData *obj, NoInc)
: m_pos(ArrayData::invalid_index) {
objInit<false>(obj);
}
HOT_FUNC
ArrayIter::~ArrayIter() {
if (hasArrayData()) {
const ArrayData* ad = getArrayData();
if (ad) decRefArr(const_cast<ArrayData*>(ad));
} else {
ObjectData* obj = getObject();
assert(obj);
decRefObj(obj);
}
if (debug) {
m_itype = TypeUndefined;
}
}
bool ArrayIter::endHelper() {
switch (getCollectionType()) {
case Collection::VectorType: {
return m_pos >= getVector()->t_count();
}
case Collection::MapType: {
return m_pos == 0;
}
case Collection::StableMapType: {
return m_pos == 0;
}
case Collection::SetType: {
return m_pos == 0;
}
case Collection::PairType: {
return m_pos >= getPair()->t_count();
}
default: {
ObjectData* obj = getIteratorObj();
return !obj->o_invoke_few_args(s_valid, 0);
}
}
}
void ArrayIter::nextHelper() {
switch (getCollectionType()) {
case Collection::VectorType: {
m_pos++;
return;
}
case Collection::MapType: {
assert(m_pos != 0);
c_Map* mp = getMap();
if (UNLIKELY(m_version != mp->getVersion())) {
throw_collection_modified();
}
m_pos = mp->iter_next(m_pos);
return;
}
case Collection::StableMapType: {
assert(m_pos != 0);
c_StableMap* smp = getStableMap();
if (UNLIKELY(m_version != smp->getVersion())) {
throw_collection_modified();
}
m_pos = smp->iter_next(m_pos);
return;
}
case Collection::SetType: {
assert(m_pos != 0);
c_Set* st = getSet();
if (UNLIKELY(m_version != st->getVersion())) {
throw_collection_modified();
}
m_pos = st->iter_next(m_pos);
return;
}
case Collection::PairType: {
m_pos++;
return;
}
default:
ObjectData* obj = getIteratorObj();
obj->o_invoke_few_args(s_next, 0);
}
}
Variant ArrayIter::firstHelper() {
switch (getCollectionType()) {
case Collection::VectorType: {
return m_pos;
}
case Collection::MapType: {
assert(m_pos != 0);
c_Map* mp = getMap();
if (UNLIKELY(m_version != mp->getVersion())) {
throw_collection_modified();
}
return mp->iter_key(m_pos);
}
case Collection::StableMapType: {
assert(m_pos != 0);
c_StableMap* smp = getStableMap();
if (UNLIKELY(m_version != smp->getVersion())) {
throw_collection_modified();
}
return smp->iter_key(m_pos);
}
case Collection::SetType: {
return uninit_null();
}
case Collection::PairType: {
return m_pos;
}
default: {
ObjectData* obj = getIteratorObj();
return obj->o_invoke_few_args(s_key, 0);
}
}
}
HOT_FUNC
Variant ArrayIter::second() {
if (hasArrayData()) {
assert(m_pos != ArrayData::invalid_index);
const ArrayData* ad = getArrayData();
assert(ad);
return ad->getValue(m_pos);
}
switch (getCollectionType()) {
case Collection::VectorType: {
c_Vector* vec = getVector();
if (UNLIKELY(m_version != vec->getVersion())) {
throw_collection_modified();
}
return tvAsCVarRef(vec->at(m_pos));
}
case Collection::MapType: {
c_Map* mp = getMap();
if (UNLIKELY(m_version != mp->getVersion())) {
throw_collection_modified();
}
return mp->iter_value(m_pos);
}
case Collection::StableMapType: {
c_StableMap* smp = getStableMap();
if (UNLIKELY(m_version != smp->getVersion())) {
throw_collection_modified();
}
return smp->iter_value(m_pos);
}
case Collection::SetType: {
c_Set* st = getSet();
if (UNLIKELY(m_version != st->getVersion())) {
throw_collection_modified();
}
return st->iter_value(m_pos);
}
case Collection::PairType: {
return tvAsCVarRef(getPair()->at(m_pos));
}
default: {
ObjectData* obj = getIteratorObj();
return obj->o_invoke_few_args(s_current, 0);
}
}
}
void ArrayIter::secondHelper(Variant& v) {
switch (getCollectionType()) {
case Collection::VectorType: {
c_Vector* vec = getVector();
if (UNLIKELY(m_version != vec->getVersion())) {
throw_collection_modified();
}
v = tvAsCVarRef(vec->at(m_pos));
break;
}
case Collection::MapType: {
c_Map* mp = getMap();
if (UNLIKELY(m_version != mp->getVersion())) {
throw_collection_modified();
}
v = mp->iter_value(m_pos);
break;
}
case Collection::StableMapType: {
c_StableMap* smp = getStableMap();
if (UNLIKELY(m_version != smp->getVersion())) {
throw_collection_modified();
}
v = smp->iter_value(m_pos);
break;
}
case Collection::SetType: {
c_Set* st = getSet();
if (UNLIKELY(m_version != st->getVersion())) {
throw_collection_modified();
}
v = st->iter_value(m_pos);
break;
}
case Collection::PairType: {
v = tvAsCVarRef(getPair()->at(m_pos));
break;
}
default: {
ObjectData* obj = getIteratorObj();
v = obj->o_invoke_few_args(s_current, 0);
break;
}
}
}
HOT_FUNC
CVarRef ArrayIter::secondRef() {
if (!hasArrayData()) {
throw FatalErrorException("taking reference on iterator objects");
}
assert(hasArrayData());
assert(m_pos != ArrayData::invalid_index);
const ArrayData* ad = getArrayData();
assert(ad);
return ad->getValueRef(m_pos);
}
///////////////////////////////////////////////////////////////////////////////
// FullPos
bool FullPos::end() const {
return !const_cast<FullPos*>(this)->prepare();
}
bool FullPos::advance() {
ArrayData* data = getArray();
ArrayData* container = getContainer();
if (!data) {
if (container) {
container->freeFullPos(*this);
}
setResetFlag(false);
return false;
}
if (container == data) {
return cowCheck()->advanceFullPos(*this);
}
data = reregister();
assert(data && data == getContainer());
assert(!getResetFlag());
if (!data->validFullPos(*this)) return false;
// To conform to PHP behavior, we need to set the internal
// cursor to point to the next element.
data->next();
return true;
}
bool FullPos::prepare() {
ArrayData* data = getArray();
ArrayData* container = getContainer();
if (!data) {
if (container) {
container->freeFullPos(*this);
}
setResetFlag(false);
return false;
}
if (container != data) {
data = reregister();
}
return data->validFullPos(*this);
}
void FullPos::escalateCheck() {
ArrayData* data;
if (hasVar()) {
data = getData();
if (!data) return;
ArrayData* esc = data->escalate();
if (data != esc) {
*const_cast<Variant*>(getVar()) = esc;
}
} else {
assert(hasAd());
data = getAd();
ArrayData* esc = data->escalate();
if (data != esc) {
esc->incRefCount();
decRefArr(data);
setAd(esc);
}
}
}
ArrayData* FullPos::cowCheck() {
ArrayData* data;
if (hasVar()) {
data = getData();
if (!data) return nullptr;
if (data->getCount() > 1 && !data->noCopyOnWrite()) {
*const_cast<Variant*>(getVar()) = data = data->copyWithStrongIterators();
}
} else {
assert(hasAd());
data = getAd();
if (data->getCount() > 1 && !data->noCopyOnWrite()) {
ArrayData* copied = data->copyWithStrongIterators();
copied->incRefCount();
decRefArr(data);
setAd(data = copied);
}
}
return data;
}
ArrayData* FullPos::reregister() {
ArrayData* container = getContainer();
assert(getArray() != nullptr && container != getArray());
if (container != nullptr) {
container->freeFullPos(*this);
}
setResetFlag(false);
assert(getContainer() == nullptr);
escalateCheck();
ArrayData* data = cowCheck();
data->newFullPos(*this);
return data;
}
///////////////////////////////////////////////////////////////////////////////
// MutableArrayIter
MutableArrayIter::MutableArrayIter(const Variant *var, Variant *key,
Variant &val) {
m_var = nullptr;
m_key = key;
m_valp = &val;
setVar(var);
assert(getVar());
escalateCheck();
ArrayData* data = cowCheck();
if (!data) return;
data->reset();
data->newFullPos(*this);
setResetFlag(true);
data->next();
assert(getContainer() == data);
}
MutableArrayIter::MutableArrayIter(ArrayData *data, Variant *key,
Variant &val) {
m_var = nullptr;
m_key = key;
m_valp = &val;
if (!data) return;
setAd(data);
escalateCheck();
data = cowCheck();
data->reset();
data->newFullPos(*this);
setResetFlag(true);
data->next();
assert(getContainer() == data);
}
MutableArrayIter::~MutableArrayIter() {
// free the iterator
ArrayData* container = getContainer();
if (container) {
container->freeFullPos(*this);
assert(getContainer() == nullptr);
}
// unprotect the data
if (hasAd()) decRefArr(getAd());
}
bool MutableArrayIter::advance() {
if (!this->FullPos::advance()) return false;
ArrayData* data = getArray();
assert(data);
assert(!getResetFlag());
assert(getContainer() == data);
assert(data->validFullPos(*this));
m_valp->assignRef(data->getValueRef(m_pos));
if (m_key) m_key->assignVal(data->getKey(m_pos));
return true;
}
///////////////////////////////////////////////////////////////////////////////
// MArrayIter
MArrayIter::MArrayIter(const RefData* ref) {
m_var = nullptr;
ref->incRefCount();
setVar(ref->var());
assert(hasVar());
escalateCheck();
ArrayData* data = cowCheck();
if (!data) return;
data->reset();
data->newFullPos(*this);
setResetFlag(true);
data->next();
assert(getContainer() == data);
}
MArrayIter::MArrayIter(ArrayData *data) {
m_var = nullptr;
if (!data) return;
assert(!data->isStatic());
setAd(data);
escalateCheck();
data = cowCheck();
data->reset();
data->newFullPos(*this);
setResetFlag(true);
data->next();
assert(getContainer() == data);
}
MArrayIter::~MArrayIter() {
// free the iterator
ArrayData* container = getContainer();
if (container) {
container->freeFullPos(*this);
assert(getContainer() == nullptr);
}
// unprotect the data
if (hasVar()) {
RefData* ref = RefData::refDataFromVariantIfYouDare(getVar());
decRefRef(ref);
} else if (hasAd()) {
decRefArr(getAd());
}
}
CufIter::~CufIter() {
if (m_ctx && !(uintptr_t(m_ctx) & 1)) {
decRefObj((ObjectData*)m_ctx);
}
if (m_name) decRefStr(m_name);
}
bool Iter::init(TypedValue* c1) {
assert(c1->m_type != KindOfRef);
bool hasElems = true;
if (c1->m_type == KindOfArray) {
if (!c1->m_data.parr->empty()) {
(void) new (&arr()) ArrayIter(c1->m_data.parr);
arr().setIterType(ArrayIter::TypeArray);
} else {
hasElems = false;
}
} else if (c1->m_type == KindOfObject) {
bool isIterator;
if (c1->m_data.pobj->isCollection()) {
isIterator = true;
(void) new (&arr()) ArrayIter(c1->m_data.pobj);
} else {
Object obj = c1->m_data.pobj->iterableObject(isIterator);
if (isIterator) {
(void) new (&arr()) ArrayIter(obj, ArrayIter::transferOwner);
} else {
Class* ctx = arGetContextClass(g_vmContext->getFP());
CStrRef ctxStr = ctx ? ctx->nameRef() : null_string;
Array iterArray(obj->o_toIterArray(ctxStr));
ArrayData* ad = iterArray.getArrayData();
(void) new (&arr()) ArrayIter(ad);
}
}
try {
if (arr().end()) {
// Iterator was empty; call the destructor on the iterator we
// just constructed and branch to done case
arr().~ArrayIter();
hasElems = false;
} else {
arr().setIterType(
isIterator ? ArrayIter::TypeIterator : ArrayIter::TypeArray);
}
} catch (...) {
arr().~ArrayIter();
throw;
}
} else {
raise_warning("Invalid argument supplied for foreach()");
hasElems = false;
}
return hasElems;
}
bool Iter::minit(TypedValue* v1) {
assert(v1->m_type == KindOfRef);
bool hasElems = true;
TypedValue* rtv = v1->m_data.pref->tv();
if (rtv->m_type == KindOfArray) {
ArrayData* ad = rtv->m_data.parr;
if (!ad->empty()) {
MArrayIter& mi = marr();
(void) new (&mi) MArrayIter(v1->m_data.pref);
mi.advance();
} else {
hasElems = false;
}
} else if (rtv->m_type == KindOfObject) {
if (rtv->m_data.pobj->isCollection()) {
raise_error("Collection elements cannot be taken by reference");
}
bool isIterator;
Object obj = rtv->m_data.pobj->iterableObject(isIterator);
if (isIterator) {
raise_error("An iterator cannot be used with foreach by reference");
}
Class* ctx = arGetContextClass(g_vmContext->getFP());
CStrRef ctxStr = ctx ? ctx->nameRef() : null_string;
Array iterArray = obj->o_toIterArray(ctxStr, true);
if (iterArray->empty()) {
hasElems = false;
} else {
ArrayData* ad = iterArray.detach();
MArrayIter& mi = marr();
(void) new (&mi) MArrayIter(ad);
mi.advance();
}
} else {
if (!hphpiCompat) {
raise_warning("Invalid argument supplied for foreach()");
}
hasElems = false;
}
return hasElems;
}
bool Iter::next() {
assert(arr().getIterType() == ArrayIter::TypeArray ||
arr().getIterType() == ArrayIter::TypeIterator);
// The emitter should never generate bytecode where the iterator
// is at the end before IterNext is executed. However, even if
// the iterator is at the end, it is safe to call next().
ArrayIter* ai = &arr();
ai->next();
if (ai->end()) {
// If after advancing the iterator we have reached the end, free
// the iterator and fall through to the next instruction.
// The ArrayIter destructor will decRef the array.
ai->~ArrayIter();
return false;
}
// If after advancing the iterator we have not reached the end,
// jump to the location specified by the second immediate argument.
return true;
}
bool Iter::mnext() {
MArrayIter &mi = marr();
if (!mi.advance()) {
// If after advancing the iterator we have reached the end, free
// the iterator and fall through to the next instruction.
mi.~MArrayIter();
return false;
} else {
// If after advancing the iterator we have not reached the end,
// jump to the location specified by the second immediate argument.
return true;
}
}
void Iter::free() {
assert(arr().getIterType() == ArrayIter::TypeArray ||
arr().getIterType() == ArrayIter::TypeIterator);
arr().~ArrayIter();
}
void Iter::mfree() {
marr().~MArrayIter();
}
void Iter::cfree() {
cuf().~CufIter();
}
/*
* iter_value_cell* will store a copy of the current value at the address
* given by 'out'. iter_value_cell* will increment the refcount of the current
* value if appropriate.
*
* This function has been split into hot and cold parts. The hot part has
* been carefully crafted so that it's a leaf function (after all functions
* it calls have been trivially inlined) that then tail calls a cold
* version of itself (new_value_cell_cold). The hot part should cover the
* common case, which occurs when the array parameter is an HphpArray.
* If you make any changes to this function, please keep the hot/cold
* splitting in mind, and disasemble the optimized version of the binary
* to make sure the hot part is a good-looking leaf function; otherwise,
* you're likely to get a performance regression.
*/
template <bool typeArray, bool withRef>
static inline void iter_value_cell_local_impl(Iter* iter, TypedValue* out) {
DataType oldType = out->m_type;
assert(withRef || oldType != KindOfRef);
uint64_t oldDatum = out->m_data.num;
TRACE(2, "%s: typeArray: %s, I %p, out %p\n",
__func__, typeArray ? "true" : "false", iter, out);
assert((typeArray && iter->arr().getIterType() == ArrayIter::TypeArray) ||
(!typeArray && iter->arr().getIterType() == ArrayIter::TypeIterator));
ArrayIter& arrIter = iter->arr();
if (typeArray) {
TypedValue* cur = arrIter.nvSecond();
if (cur->m_type == KindOfRef) {
if (!withRef || cur->m_data.pref->getCount() == 1) {
cur = cur->m_data.pref->tv();
}
}
tvDup(cur, out);
} else {
Variant val = arrIter.second();
assert(val.getRawType() != KindOfRef);
tvDupCell((TypedValue*)&val, out);
}
tvRefcountedDecRefHelper(oldType, oldDatum);
}
template <bool typeArray, bool withRef>
static inline void iter_key_cell_local_impl(Iter* iter, TypedValue* out) {
DataType oldType = out->m_type;
assert(withRef || oldType != KindOfRef);
uint64_t oldDatum = out->m_data.num;
TRACE(2, "%s: I %p, out %p\n", __func__, iter, out);
assert((typeArray && iter->arr().getIterType() == ArrayIter::TypeArray) ||
(!typeArray && iter->arr().getIterType() == ArrayIter::TypeIterator));
ArrayIter& arr = iter->arr();
if (typeArray) {
arr.nvFirst(out);
} else {
Variant key = arr.first();
tvDupCell((TypedValue*)&key, out);
}
tvRefcountedDecRefHelper(oldType, oldDatum);
}
/**
* new_iter_array creates an iterator for the specified array iff the array is
* not empty. If new_iter_array creates an iterator, it does not increment the
* refcount of the specified array. If new_iter_array does not create an
* iterator, it decRefs the array.
*/
template <bool withRef>
NEVER_INLINE
int64_t new_iter_array_cold(Iter* dest, ArrayData* arr, TypedValue* valOut,
TypedValue* keyOut) {
TRACE(2, "%s: I %p, arr %p\n", __func__, dest, arr);
if (!arr->empty()) {
// We are transferring ownership of the array to the iterator, therefore
// we do not need to adjust the refcount.
(void) new (&dest->arr()) ArrayIter(arr, ArrayIter::noInc);
dest->arr().setIterType(ArrayIter::TypeArray);
iter_value_cell_local_impl<true, withRef>(dest, valOut);
if (keyOut) {
iter_key_cell_local_impl<true, withRef>(dest, keyOut);
}
return 1LL;
}
// We did not transfer ownership of the array to an iterator, so we need
// to decRef the array.
decRefArr(arr);
return 0LL;
}
template <bool withRef>
inline ALWAYS_INLINE
void getHphpArrayElm(HphpArray::Elm* elm, TypedValue* valOut,
TypedValue* keyOut) {
if (withRef) {
tvAsVariant(valOut) = withRefBind(tvAsVariant(&elm->data));
if (LIKELY(keyOut != nullptr)) {
DataType t = keyOut->m_type;
uint64_t d = keyOut->m_data.num;
HphpArray::getElmKey(elm, keyOut);
tvRefcountedDecRefHelper(t, d);
}
} else {
TypedValue* cur = tvToCell(&elm->data);
tvDupCell(cur, valOut);
if (keyOut) {
HphpArray::getElmKey(elm, keyOut);
}
}
}
HOT_FUNC
int64_t new_iter_array(Iter* dest, ArrayData* ad, TypedValue* valOut) {
TRACE(2, "%s: I %p, ad %p\n", __func__, dest, ad);
valOut = tvToCell(valOut);
if (UNLIKELY(!ad->isHphpArray())) {
goto cold;
}
{
HphpArray* arr = (HphpArray*)ad;
if (LIKELY(arr->getSize() != 0)) {
if (UNLIKELY(tvWillBeReleased(valOut))) {
goto cold;
}
tvDecRefOnly(valOut);
// We are transferring ownership of the array to the iterator, therefore
// we do not need to adjust the refcount.
(void) new (&dest->arr()) ArrayIter(arr, ArrayIter::noIncNonNull);
dest->arr().setIterType(ArrayIter::TypeArray);
HphpArray::Elm* elm = arr->getElm(dest->arr().m_pos);
getHphpArrayElm<false>(elm, valOut, nullptr);
return 1LL;
}
// We did not transfer ownership of the array to an iterator, so we need
// to decRef the array.
if (UNLIKELY(arr->getCount() == 1)) {
goto cold;
}
arr->decRefCount();
return 0LL;
}
cold:
return new_iter_array_cold<false>(dest, ad, valOut, nullptr);
}
template <bool withRef>
HOT_FUNC
int64_t new_iter_array_key(Iter* dest, ArrayData* ad,
TypedValue* valOut, TypedValue* keyOut) {
TRACE(2, "%s: I %p, ad %p\n", __func__, dest, ad);
if (!withRef) {
valOut = tvToCell(valOut);
keyOut = tvToCell(keyOut);
}
if (UNLIKELY(!ad->isHphpArray())) {
goto cold;
}
{
HphpArray* arr = (HphpArray*)ad;
if (LIKELY(arr->getSize() != 0)) {
if (!withRef) {
if (UNLIKELY(tvWillBeReleased(valOut)) ||
UNLIKELY(tvWillBeReleased(keyOut))) {
goto cold;
}
tvDecRefOnly(valOut);
tvDecRefOnly(keyOut);
}
// We are transferring ownership of the array to the iterator, therefore
// we do not need to adjust the refcount.
(void) new (&dest->arr()) ArrayIter(arr, ArrayIter::noIncNonNull);
dest->arr().setIterType(ArrayIter::TypeArray);
HphpArray::Elm* elm = arr->getElm(dest->arr().m_pos);
getHphpArrayElm<withRef>(elm, valOut, keyOut);
return 1LL;
}
// We did not transfer ownership of the array to an iterator, so we need
// to decRef the array.
if (UNLIKELY(arr->getCount() == 1)) {
goto cold;
}
arr->decRefCount();
return 0LL;
}
cold:
return new_iter_array_cold<withRef>(dest, ad, valOut, keyOut);
}
template int64_t new_iter_array_key<false>(Iter* dest, ArrayData* ad,
TypedValue* valOut,
TypedValue* keyOut);
template int64_t new_iter_array_key<true>(Iter* dest, ArrayData* ad,
TypedValue* valOut,
TypedValue* keyOut);
class FreeObj {
public:
FreeObj() : m_obj(0) {}
void operator=(ObjectData* obj) { m_obj = obj; }
~FreeObj() { if (UNLIKELY(m_obj != nullptr)) decRefObj(m_obj); }
private:
ObjectData* m_obj;
};
/**
* new_iter_object creates an iterator for the specified object if the object
* is iterable and it is non-empty (has properties). If new_iter_object creates
* an iterator, it does not increment the refcount of the specified object. If
* new_iter_object does not create an iterator, it decRefs the object.
*
* If exceptions are thrown, new_iter_object takes care of decRefing the object.
*/
HOT_FUNC
int64_t new_iter_object(Iter* dest, ObjectData* obj, Class* ctx,
TypedValue* valOut, TypedValue* keyOut) {
valOut = tvToCell(valOut);
if (keyOut) {
keyOut = tvToCell(keyOut);
}
ArrayIter::Type itType;
{
FreeObj fo;
if (obj->isCollection() || obj->implementsIterator()) {
TRACE(2, "%s: I %p, obj %p, ctx %p, collection or Iterator\n",
__func__, dest, obj, ctx);
try {
(void) new (&dest->arr()) ArrayIter(obj, ArrayIter::noInc);
} catch (...) {
decRefObj(obj);
throw;
}
itType = ArrayIter::TypeIterator;
} else {
bool isIteratorAggregate;
/*
* We are not going to transfer ownership of obj to the iterator,
* so arrange to decRef it later. The actual decRef has to happen
* after the call to arr().end() below, because both can have visible side
* effects (calls to __destruct() and valid()). Similarly it has to
* happen before the iter_*_cell_local_impl calls below, because they call
* current() and key() (hence the explicit scope around FreeObj fo;)
*/
fo = obj;
Object itObj = obj->iterableObject(isIteratorAggregate, false);
if (isIteratorAggregate) {
TRACE(2, "%s: I %p, obj %p, ctx %p, IteratorAggregate\n",
__func__, dest, obj, ctx);
(void) new (&dest->arr()) ArrayIter(itObj, ArrayIter::transferOwner);
itType = ArrayIter::TypeIterator;
} else {
TRACE(2, "%s: I %p, obj %p, ctx %p, iterate as array\n",
__func__, dest, obj, ctx);
CStrRef ctxStr = ctx ? ctx->nameRef() : null_string;
Array iterArray(itObj->o_toIterArray(ctxStr));
ArrayData* ad = iterArray.getArrayData();
(void) new (&dest->arr()) ArrayIter(ad);
itType = ArrayIter::TypeArray;
}
}
try {
if (dest->arr().end()) {
// Iterator was empty; call the destructor on the iterator we just
// constructed.
dest->arr().~ArrayIter();
return 0LL;
}
} catch (...) {
dest->arr().~ArrayIter();
throw;
}
}
dest->arr().setIterType(itType);
if (itType == ArrayIter::TypeIterator) {
iter_value_cell_local_impl<false, false>(dest, valOut);
if (keyOut) {
iter_key_cell_local_impl<false, false>(dest, keyOut);
}
} else {
iter_value_cell_local_impl<true, false>(dest, valOut);
if (keyOut) {
iter_key_cell_local_impl<true, false>(dest, keyOut);
}
}
return 1LL;
}
/**
* iter_next will advance the iterator to point to the next element.
* If the iterator reaches the end, iter_next will free the iterator
* and will decRef the array.
* This function has been split into hot and cold parts. The hot part has
* been carefully crafted so that it's a leaf function (after all functions
* it calls have been trivially inlined) that then tail calls a cold
* version of itself (iter_next_array_cold). The hot part should cover the
* common case, which occurs when the array parameter is an HphpArray.
* If you make any changes to this function, please keep the hot/cold
* splitting in mind, and disasemble the optimized version of the binary
* to make sure the hot part is a good-looking leaf function; otherwise,
* you're likely to get a performance regression.
*/
template <bool withRef>
NEVER_INLINE
int64_t iter_next_cold(Iter* iter, TypedValue* valOut, TypedValue* keyOut) {
TRACE(2, "iter_next_cold: I %p\n", iter);
assert(iter->arr().getIterType() == ArrayIter::TypeArray ||
iter->arr().getIterType() == ArrayIter::TypeIterator);
ArrayIter* ai = &iter->arr();
ai->next();
if (ai->end()) {
// The ArrayIter destructor will decRef the array
ai->~ArrayIter();
return 0;
}
if (iter->arr().getIterType() == ArrayIter::TypeArray) {
iter_value_cell_local_impl<true, withRef>(iter, valOut);
if (keyOut) {
iter_key_cell_local_impl<true, withRef>(iter, keyOut);
}
} else {
iter_value_cell_local_impl<false, withRef>(iter, valOut);
if (keyOut) {
iter_key_cell_local_impl<false, withRef>(iter, keyOut);
}
}
return 1;
}
HOT_FUNC
int64_t iter_next(Iter* iter, TypedValue* valOut) {
TRACE(2, "iter_next: I %p\n", iter);
assert(iter->arr().getIterType() == ArrayIter::TypeArray ||
iter->arr().getIterType() == ArrayIter::TypeIterator);
ArrayIter* arrIter = &iter->arr();
valOut = tvToCell(valOut);
if (UNLIKELY(!arrIter->hasArrayData())) {
goto cold;
}
{
const ArrayData* ad = arrIter->getArrayData();
if (UNLIKELY(!ad->isHphpArray())) {
goto cold;
}
const HphpArray* arr = (HphpArray*)ad;
ssize_t pos = arrIter->getPos();
HphpArray::Elm* elm;
do {
if (size_t(pos) >= size_t(arr->getLastE())) {
if (UNLIKELY(arr->getCount() == 1)) {
goto cold;
}
arr->decRefCount();
if (debug) {
iter->arr().setIterType(ArrayIter::TypeUndefined);
}
return 0;
}
pos = pos + 1;
elm = arr->getElm(pos);
} while (elm->data.m_type >= HphpArray::KindOfTombstone);
if (UNLIKELY(tvWillBeReleased(valOut))) {
goto cold;
}
tvDecRefOnly(valOut);
arrIter->setPos(pos);
getHphpArrayElm<false>(elm, valOut, nullptr);
return 1;
}
cold:
return iter_next_cold<false>(iter, valOut, nullptr);
}
template <bool withRef>
HOT_FUNC
int64_t iter_next_key(Iter* iter, TypedValue* valOut, TypedValue* keyOut) {
TRACE(2, "iter_next_key: I %p\n", iter);
assert(iter->arr().getIterType() == ArrayIter::TypeArray ||
iter->arr().getIterType() == ArrayIter::TypeIterator);
ArrayIter* arrIter = &iter->arr();
if (!withRef) {
valOut = tvToCell(valOut);
keyOut = tvToCell(keyOut);
}
if (UNLIKELY(!arrIter->hasArrayData())) {
goto cold;
}
{
const ArrayData* ad = arrIter->getArrayData();
if (UNLIKELY(!ad->isHphpArray())) {
goto cold;
}
const HphpArray* arr = (HphpArray*)ad;
ssize_t pos = arrIter->getPos();
HphpArray::Elm* elm;
do {
if (size_t(pos) >= size_t(arr->getLastE())) {
if (UNLIKELY(arr->getCount() == 1)) {
goto cold;
}
arr->decRefCount();
if (debug) {
iter->arr().setIterType(ArrayIter::TypeUndefined);
}
return 0;
}
pos = pos + 1;
elm = arr->getElm(pos);
} while (elm->data.m_type >= HphpArray::KindOfTombstone);
if (!withRef) {
if (UNLIKELY(tvWillBeReleased(valOut))) {
goto cold;
}
if (UNLIKELY(tvWillBeReleased(keyOut))) {
goto cold;
}
tvDecRefOnly(valOut);
tvDecRefOnly(keyOut);
}
arrIter->setPos(pos);
getHphpArrayElm<withRef>(elm, valOut, keyOut);
return 1;
}
cold:
return iter_next_cold<withRef>(iter, valOut, keyOut);
}
template int64_t iter_next_key<false>(Iter* dest,
TypedValue* valOut,
TypedValue* keyOut);
template int64_t iter_next_key<true>(Iter* dest,
TypedValue* valOut,
TypedValue* keyOut);
///////////////////////////////////////////////////////////////////////////////
}