Arquivos
hhvm/hphp/runtime/base/array/array_data.h
T
andrewparoski dec99505a6 Refactor iterator logic
This diff refactors some of the VM's logic for iterators (with a focus on
mutable iteration), delivering several improvements:
  1) MIterCtx was renamed to MArrayIter, and the m_key and m_val fields
     were eliminated.
  2) Eliminated the need for MArrayIter to dynamically allocate a
     MutableArrayIter object, and removed other layers of indirection as
     well.
  3) Reduced the size of HPHP::VM::Iter from 64 bytes down to 32 bytes.
  4) Removed the "if (siPastEnd())" check when adding a new element to an
     HphpArray or a ZendArray.
  5) Moved all of the iterator logic into a single .cpp file.

This diff reworks FullPos's to point to current element instead of pointing
to the next element. It also splits up the IterFree instruction into two
instructions (IterFree and MIterFree). These changes allowed various logic
to be simplified and data structures to be reduced in size. There is
definitely more opportunity for refactoring, but I know the JIT helpers for
iteration have been carefully tuned and so I'll leave further refactoring
for future diffs.

Finally, I spent a little time cleaning up the bytecode spec a bit, mostly
with respect to iteration.
2013-02-11 06:36:51 -08:00

515 linhas
17 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. |
+----------------------------------------------------------------------+
*/
#ifndef __HPHP_ARRAY_DATA_H__
#define __HPHP_ARRAY_DATA_H__
#include <runtime/base/util/countable.h>
#include <runtime/base/types.h>
#include <runtime/base/macros.h>
#include <climits>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
class SharedVariant;
struct TypedValue;
/**
* Base class/interface for all types of specialized array data.
*/
class ArrayData : public Countable {
public:
enum ArrayOp {
Plus,
Merge,
};
// enum of possible array types, so we can guard nonvirtual
// fast paths in runtime code.
enum ArrayKind {
kArrayData,
kHphpArray
};
protected:
// used in subclasses but declared here.
enum AllocMode { kInline, kSmart, kMalloc };
public:
static const ssize_t invalid_index = -1;
ArrayData(ArrayKind kind = kArrayData, bool nonsmart = false) :
m_size(-1), m_pos(0), m_strongIterators(0), m_kind(kind),
m_nonsmart(nonsmart) {
}
ArrayData(const ArrayData *src, ArrayKind kind = kArrayData,
bool nonsmart = false) :
m_pos(src->m_pos), m_strongIterators(0), m_kind(kind),
m_nonsmart(nonsmart) {
}
virtual ~ArrayData();
/**
* Create a new ArrayData with specified array element(s).
*/
static ArrayData *Create();
static ArrayData *Create(CVarRef value);
static ArrayData *Create(CVarRef name, CVarRef value);
static ArrayData *CreateRef(CVarRef value);
static ArrayData *CreateRef(CVarRef name, CVarRef value);
/**
* Type conversion functions. All other types are handled inside Array class.
*/
Object toObject() const;
/**
* Array interface functions.
*
* 1. For functions that return ArrayData pointers, these are the ones that
* can potentially escalate into a different ArrayData type. Return NULL
* if no escalation is needed.
*
* 2. All functions with a "key" parameter are type-specialized.
*/
/**
* For SmartAllocator.
*/
virtual void release() = 0;
/**
* Whether this array has any element.
*/
bool empty() const {
return size() == 0;
}
/**
* return the array kind for fast typechecks
*/
ArrayKind kind() const {
return (ArrayKind)m_kind;
}
/**
* Number of elements this array has.
*/
ssize_t size() const {
if (UNLIKELY((int)m_size) < 0) return vsize();
return m_size;
}
/**
* Number of elements this array has.
*/
virtual ssize_t vsize() const = 0;
/**
* For ArrayIter to work. Get key or value at position "pos".
*/
virtual Variant getKey(ssize_t pos) const = 0;
virtual Variant getValue(ssize_t pos) const = 0;
/**
* getValueRef() gets a reference to value at position "pos".
*/
virtual CVarRef getValueRef(ssize_t pos) const = 0;
/*
* Return true for array types that don't have COW semantics.
*/
virtual bool noCopyOnWrite() const { return false; }
/*
* Specific derived class type querying operators.
*/
virtual bool isVectorArray() const { return false; }
virtual bool isSharedMap() const { return false; }
/*
* Returns whether or not this array contains "vector-like" data.
* I.e. all the keys are contiguous increasing integers.
*/
virtual bool isVectorData() const;
virtual SharedVariant *getSharedVariant() const { return NULL; }
/**
* Whether or not this array has a referenced Variant or Object appearing
* twice. This is mainly for APC to decide whether to serialize an array.
* Also used for detecting whether there is serializable object in the tree.
*/
bool hasInternalReference(PointerSet &seen,
bool detectSerializable = false) const;
/**
* Position-based iterations.
*/
virtual Variant reset();
virtual Variant prev();
virtual Variant current() const;
virtual Variant next();
virtual Variant end();
virtual Variant key() const;
virtual Variant value(ssize_t &pos) const;
virtual Variant each();
bool isHead() const { return m_pos == iter_begin(); }
bool isTail() const { return m_pos == iter_end(); }
virtual bool isInvalid() const { return m_pos == invalid_index; }
/**
* Testing whether a key exists.
*/
virtual bool exists(int64 k) const = 0;
virtual bool exists(const StringData* k) const = 0;
/**
* Getting value at specified key.
*/
virtual CVarRef get(int64 k, bool error = false) const = 0;
virtual CVarRef get(const StringData* k, bool error = false) const = 0;
/**
* Interface for VM helpers. ArrayData implements generic versions
* using the other ArrayData api; subclasses may do better.
*/
virtual TypedValue* nvGet(int64 k) const;
virtual TypedValue* nvGet(const StringData* k) const;
virtual void nvGetKey(TypedValue* out, ssize_t pos);
virtual TypedValue* nvGetValueRef(ssize_t pos);
virtual TypedValue* nvGetCell(int64 ki) const;
virtual TypedValue* nvGetCell(const StringData* k) const;
/**
* Get the numeric index for a key. Only these need to be
* in ArrayData.
*/
virtual ssize_t getIndex(int64 k) const = 0;
virtual ssize_t getIndex(const StringData* k) const = 0;
/**
* Getting l-value (that Variant pointer) at specified key. Return NULL if
* escalation is not needed, or an escalated array data.
*/
virtual ArrayData *lval(int64 k, Variant *&ret, bool copy,
bool checkExist = false) = 0;
virtual ArrayData *lval(StringData* k, Variant *&ret, bool copy,
bool checkExist = false) = 0;
/**
* Getting l-value (that Variant pointer) of a new element with the next
* available integer key. Return NULL if escalation is not needed, or an
* escalated array data. Note that adding a new element with the next
* available integer key may fail, in which case ret is set to point to
* the lval blackhole (see Variant::lvalBlackHole() for details).
*/
virtual ArrayData *lvalNew(Variant *&ret, bool copy) = 0;
/**
* Helper functions used for getting a reference to elements of
* the dynamic property array in ObjectData or the local cache array
* in ShardMap.
*/
virtual ArrayData *lvalPtr(int64 k, Variant *&ret, bool copy, bool create);
virtual ArrayData *lvalPtr(StringData* k, Variant *&ret, bool copy,
bool create);
/**
* Setting a value at specified key. If "copy" is true, make a copy first
* then set the value. Return NULL if escalation is not needed, or an
* escalated array data.
*/
virtual ArrayData *set(int64 k, CVarRef v, bool copy) = 0;
virtual ArrayData *set(StringData* k, CVarRef v, bool copy) = 0;
virtual ArrayData *setRef(int64 k, CVarRef v, bool copy) = 0;
virtual ArrayData *setRef(StringData* k, CVarRef v, bool copy) = 0;
/**
* The same as set(), but with the precondition that the key does
* not already exist in this array. (This is to allow more
* efficient implementation of this case in some derived classes.)
*/
virtual ArrayData *add(int64 k, CVarRef v, bool copy);
virtual ArrayData *add(StringData* k, CVarRef v, bool copy);
/*
* Same semantics as lval(), except with the precondition that the
* key doesn't already exist in the array.
*/
virtual ArrayData *addLval(int64 k, Variant *&ret, bool copy);
virtual ArrayData *addLval(StringData* k, Variant *&ret, bool copy);
/**
* Remove a value at specified key. If "copy" is true, make a copy first
* then remove the value. Return NULL if escalation is not needed, or an
* escalated array data.
*/
virtual ArrayData *remove(int64 k, bool copy) = 0;
virtual ArrayData *remove(const StringData* k, bool copy) = 0;
/**
* Inline accessors that convert keys to StringData* before delegating to
* the virtual method.
*/
bool exists(litstr k) const;
bool exists(CStrRef k) const;
bool exists(CVarRef k) const;
CVarRef get(litstr k, bool error = false) const;
CVarRef get(CStrRef k, bool error = false) const;
CVarRef get(CVarRef k, bool error = false) const;
ssize_t getIndex(litstr k) const;
ssize_t getIndex(CStrRef k) const;
ssize_t getIndex(CVarRef k) const;
ArrayData *lval(litstr k, Variant *&ret, bool copy, bool checkExist=false);
ArrayData *lval(CStrRef k, Variant *&ret, bool copy, bool checkExist=false);
ArrayData *lval(CVarRef k, Variant *&ret, bool copy, bool checkExist=false);
ArrayData *lvalPtr(CStrRef k, Variant *&ret, bool copy, bool create);
ArrayData *set(litstr k, CVarRef v, bool copy);
ArrayData *set(CStrRef k, CVarRef v, bool copy);
ArrayData *set(CVarRef k, CVarRef v, bool copy);
ArrayData *setRef(litstr k, CVarRef v, bool copy);
ArrayData *setRef(CStrRef k, CVarRef v, bool copy);
ArrayData *setRef(CVarRef k, CVarRef v, bool copy);
ArrayData *add(CStrRef k, CVarRef v, bool copy);
ArrayData *add(CVarRef k, CVarRef v, bool copy);
ArrayData *addLval(CStrRef k, Variant *&ret, bool copy);
ArrayData *addLval(CVarRef k, Variant *&ret, bool copy);
ArrayData *remove(litstr k, bool copy);
ArrayData *remove(CStrRef k, bool copy);
ArrayData *remove(CVarRef k, bool copy);
/*
* Inline wrappers that just use tvAsCVarRef on the value
*/
ArrayData* nvSet(int64 ki, const TypedValue* v, bool copy);
ArrayData* nvSet(StringData* k, const TypedValue* v, bool copy);
virtual ssize_t iter_begin() const;
virtual ssize_t iter_end() const;
virtual ssize_t iter_advance(ssize_t prev) const;
virtual ssize_t iter_rewind(ssize_t prev) const;
/**
* Mutable iteration APIs
*
* The following six methods are used for mutable iteration. For all methods
* except newFullPos(), it is the caller's responsibility to ensure that the
* specified FullPos 'fp' is registered with this array and hasn't already
* been freed.
*/
/**
* Create a new mutable iterator and register it with this array (the mutable
* iterator will be stored in 'fp'). The new iterator will point to whatever
* element the array's internal cursor currently points to. Note that the
* array keeps track of all mutable iterators that have registered with it.
*
* A mutable iterator remains live until one of the following happens:
* (1) The mutable iterator is freed by calling the freeFullPos() method.
* (2) The array's refcount drops to 0 and the array frees all mutable
* iterators that were registered with it.
* (3) Some other kind of "invalidation" event happens to the array that
* causes it to free all mutable iterators that were registered with
* it (ex. array_shift() is called on the array).
*/
void newFullPos(FullPos &fp);
/**
* Frees a mutable iterator that was registered with this array.
*/
void freeFullPos(FullPos &fp);
/**
* Checks if a mutable iterator points to a valid element within this array.
* This will return false if the iterator points past the last element, or
* if the iterator points before the first element.
*/
virtual bool validFullPos(const FullPos& fp) const;
/**
* Sets the specified mutable iterator to point to whatever element the
* array's internal cursor currently points to.
*/
virtual void getFullPos(FullPos &fp);
/**
* Sets the array's internal cursor to point to whetever element the
* specified mutable iterator to currently points to. It is the caller's
* responsibility to ensure that fp.getResetFlag() is false.
*/
virtual bool setFullPos(const FullPos &fp);
/**
* TODO Task #2091628: nextForFullPos() is a temporary hack to keep
* GlobalArrayWrapper working. Once GlobalArrayWrapper is gone we can
* remove this hack.
*/
virtual void nextForFullPos();
virtual CVarRef currentRef();
virtual CVarRef endRef();
virtual ArrayData* escalateForSort();
virtual void ksort(int sort_flags, bool ascending);
virtual void sort(int sort_flags, bool ascending);
virtual void asort(int sort_flags, bool ascending);
virtual void uksort(CVarRef cmp_function);
virtual void usort(CVarRef cmp_function);
virtual void uasort(CVarRef cmp_function);
/**
* Make a copy of myself.
*
* The nonSmartCopy() version means not to use the smart allocator.
* Is only implemented for array types that need to be able to go
* into the static array list.
*/
virtual ArrayData *copy() const = 0;
virtual ArrayData *copyWithStrongIterators() const;
virtual ArrayData *nonSmartCopy() const;
/**
* Append a value to the array. If "copy" is true, make a copy first
* then append the value. Return NULL if escalation is not needed, or an
* escalated array data.
*/
virtual ArrayData *append(CVarRef v, bool copy) = 0;
virtual ArrayData *appendRef(CVarRef v, bool copy) = 0;
/**
* Similar to append(v, copy), with reference in v preserved.
*/
virtual ArrayData *appendWithRef(CVarRef v, bool copy) = 0;
/**
* Implementing array appending and merging. If "copy" is true, make a copy
* first then append/merge arrays. Return NULL if escalation is not needed,
* or an escalated array data.
*/
virtual ArrayData *append(const ArrayData *elems, ArrayOp op, bool copy) = 0;
/**
* Stack function: pop the last item and return it.
*/
virtual ArrayData *pop(Variant &value);
/**
* Queue function: remove the 1st item and return it.
*/
virtual ArrayData *dequeue(Variant &value);
/**
* Array function: prepend a new item.
*/
virtual ArrayData *prepend(CVarRef v, bool copy) = 0;
/**
* Only map classes need this. Re-index all numeric keys to start from 0.
*/
virtual void renumber() {}
virtual void onSetEvalScalar() { assert(false);}
/**
* Serialize this array. We could have made this virtual function to ask
* sub-classes to implement it specifically, but since this is not a critical
* function to optimize, we implement it in a generic way in this base class.
* Then all the sudden we find out all Zend HashTable functions are similar
* to implementing array functions in this base class than utilizing a type
* specialized implementation, which is normally more optimized.
*/
void serialize(VariableSerializer *serializer,
bool skipNestCheck = false) const;
virtual void dump();
virtual void dump(std::string &out);
virtual void dump(std::ostream &os);
/**
* Comparisons. Similar to serialize(), we implemented it here generically.
*/
int compare(const ArrayData *v2) const;
bool equal(const ArrayData *v2, bool strict) const;
void setPosition(ssize_t p) { m_pos = p; }
virtual ArrayData *escalate(bool mutableIteration = false) const {
return const_cast<ArrayData *>(this);
}
static ArrayData *GetScalarArray(ArrayData *arr,
const StringData *key = NULL);
private:
void serializeImpl(VariableSerializer *serializer) const;
static void compileTimeAssertions() {
static_assert(offsetof(ArrayData, _count) == FAST_REFCOUNT_OFFSET,
"Offset of _count in ArrayData must be FAST_REFCOUNT_OFFSET");
}
protected:
void freeStrongIterators();
static void moveStrongIterators(ArrayData* dest, ArrayData* src);
FullPos* strongIterators() const {
return m_strongIterators;
}
void setStrongIterators(FullPos* p) {
m_strongIterators = p;
}
// error-handling helpers
static CVarRef getNotFound(int64 k);
static CVarRef getNotFound(litstr k);
static CVarRef getNotFound(const StringData* k);
static CVarRef getNotFound(CStrRef k);
static CVarRef getNotFound(CVarRef k);
static TypedValue* nvGetNotFound(int64 k);
static TypedValue* nvGetNotFound(const StringData* k);
static bool IsValidKey(litstr k);
static bool IsValidKey(CStrRef k);
static bool IsValidKey(CVarRef k);
static bool IsValidKey(const StringData* k);
protected:
uint m_size;
ssize_t m_pos;
private:
FullPos* m_strongIterators; // head of linked list
protected:
const uint8_t m_kind; // enum ArrayKind
const bool m_nonsmart; // never use smartalloc to allocate Elms
uint8_t m_allocMode; // enum AllocMode
/* The 4 bytes of padding here are available to subclasses if their
* first field is also <= 4 bytes. */
public: // for the JIT
static uint32_t getKindOff() {
return (uintptr_t)&((ArrayData*)0)->m_kind;
}
};
ALWAYS_INLINE inline
void decRefArr(ArrayData* arr) {
if (arr->decRefCount() == 0) arr->release();
}
///////////////////////////////////////////////////////////////////////////////
}
#endif // __HPHP_ARRAY_DATA_H__