Arquivos
hhvm/hphp/runtime/base/hphp_array.h
T
Edwin Smith a9479c7059 Eliminate CreateLvalPtr and GetLvalPtr
HphpArray::CreateLvalPtr does the same thing as LvalStr().  SharedMap
and NameValueTableWrapper::CreateLvalPtr just fatal, and aren't called
anyway.  GetLvalPtr is only called from one place which suppresses COW
and thus can also be done with nvGet, leaving getLvalPtr dead.
2013-07-22 11:34:12 -07:00

533 linhas
20 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1998-2010 Zend Technologies Ltd. (http://www.zend.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_HPHP_ARRAY_H_
#define incl_HPHP_HPHP_ARRAY_H_
#include "hphp/runtime/base/types.h"
#include "hphp/runtime/base/array_data.h"
#include "hphp/runtime/base/smart_allocator.h"
#include "hphp/runtime/base/complex_types.h"
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
class ArrayInit;
class HphpArray : public ArrayData {
enum SortFlavor { IntegerSort, StringSort, GenericSort };
public:
friend class ArrayInit;
// Load factor scaler. If S is the # of elements, C is the
// power-of-2 capacity, and L=LoadScale, we grow when S > C-C/L.
// So 2 gives 0.5 load factor, 4 gives 0.75 load factor, 8 gives
// 0.125 load factor. Use powers of 2 to enable shift-divide.
static const uint LoadScale = 4;
public:
static HphpArray* GetStaticEmptyArray() {
return &s_theEmptyArray;
}
private:
enum class CopyVector {};
HphpArray(const HphpArray& other, AllocationMode, CopyVector);
enum class CopyGeneric {};
HphpArray(const HphpArray& other, AllocationMode, CopyGeneric);
// convert in-place from kVector to kHphpArray: fill in keys & hashtable
HphpArray* vectorToGeneric();
// Safe downcast helpers
static HphpArray* asVector(ArrayData* ad);
static const HphpArray* asVector(const ArrayData* ad);
static HphpArray* asGeneric(ArrayData* ad);
static const HphpArray* asGeneric(const ArrayData* ad);
static HphpArray* asHphpArray(ArrayData* ad);
static const HphpArray* asHphpArray(const ArrayData* ad);
public:
// Create an empty array with enough capacity for nSize elements.
explicit HphpArray(uint nSize);
// Create and initialize an array with size elements, populated by
// moving (without refcounting) and reversing vals.
HphpArray(uint size, const TypedValue* vals); // make tuple
~HphpArray();
void destroyVec();
void destroy();
// This behaves the same as iter_begin except that it assumes
// this array is not empty and its not virtual.
ssize_t getIterBegin() const {
assert(!empty());
if (LIKELY(!isTombstone(m_data[0].data.m_type))) {
return 0;
}
return nextElm(m_data, 0);
}
// override/implement ArrayData api's
// these using directives ensure the full set of overloaded functions
// are visible in this class, to avoid triggering implicit conversions
// from a CVarRef key to int64.
using ArrayData::exists;
using ArrayData::lval;
using ArrayData::lvalNew;
using ArrayData::set;
using ArrayData::setRef;
using ArrayData::add;
using ArrayData::addLval;
using ArrayData::remove;
using ArrayData::nvGet;
// implements ArrayData
static CVarRef GetValueRef(const ArrayData*, ssize_t pos);
// overrides ArrayData
static bool IsVectorData(const ArrayData*);
static bool IsVectorDataVec(const ArrayData*);
static ssize_t IterBegin(const ArrayData*);
static ssize_t IterEnd(const ArrayData*);
static ssize_t IterAdvance(const ArrayData*, ssize_t pos);
static ssize_t IterRewind(const ArrayData*, ssize_t pos);
// implements ArrayData
static bool ExistsInt(const ArrayData*, int64_t k);
static bool ExistsStr(const ArrayData*, const StringData* k);
static bool ExistsIntVec(const ArrayData*, int64_t k);
static bool ExistsStrVec(const ArrayData*, const StringData* k);
// implements ArrayData
static ArrayData* LvalInt(ArrayData* ad, int64_t k, Variant*& ret,
bool copy);
static ArrayData* LvalStr(ArrayData* ad, StringData* k, Variant*& ret,
bool copy);
static ArrayData* LvalIntVec(ArrayData* ad, int64_t k, Variant*& ret,
bool copy);
static ArrayData* LvalStrVec(ArrayData* ad, StringData* k, Variant*& ret,
bool copy);
static ArrayData* LvalNew(ArrayData*, Variant*& ret, bool copy);
static ArrayData* LvalNewVec(ArrayData*, Variant*& ret, bool copy);
// implements ArrayData
static ArrayData* SetIntVec(ArrayData*, int64_t k, CVarRef v, bool copy);
static ArrayData* SetStrVec(ArrayData*, StringData* k, CVarRef v, bool copy);
static ArrayData* SetInt(ArrayData*, int64_t k, CVarRef v, bool copy);
static ArrayData* SetStr(ArrayData*, StringData* k, CVarRef v, bool copy);
// implements ArrayData
static ArrayData* SetRefInt(ArrayData* ad, int64_t k, CVarRef v,
bool copy);
static ArrayData* SetRefStr(ArrayData* ad, StringData* k, CVarRef v,
bool copy);
static ArrayData* SetRefIntVec(ArrayData* ad, int64_t k, CVarRef v,
bool copy);
static ArrayData* SetRefStrVec(ArrayData* ad, StringData* k, CVarRef v,
bool copy);
// overrides ArrayData
static ArrayData* AddInt(ArrayData*, int64_t k, CVarRef v, bool copy);
static ArrayData* AddStr(ArrayData*, StringData* k, CVarRef v, bool copy);
static ArrayData* AddIntVec(ArrayData*, int64_t k, CVarRef v, bool copy);
static ArrayData* AddStrVec(ArrayData*, StringData* k, CVarRef v, bool copy);
static ArrayData* AddLvalInt(ArrayData*, int64_t k, Variant*& ret,
bool copy);
static ArrayData* AddLvalStr(ArrayData*, StringData* k, Variant*& ret,
bool copy);
static ArrayData* AddLvalIntVec(ArrayData*, int64_t k, Variant*& ret,
bool copy);
static ArrayData* AddLvalStrVec(ArrayData*, StringData* k, Variant*& ret,
bool copy);
// implements ArrayData
static ArrayData* RemoveInt(ArrayData*, int64_t k, bool copy);
static ArrayData* RemoveStr(ArrayData*, const StringData* k, bool copy);
static ArrayData* RemoveIntVec(ArrayData*, int64_t k, bool copy);
static ArrayData* RemoveStrVec(ArrayData*, const StringData* k, bool copy);
// overrides ArrayData
static ArrayData* Copy(const ArrayData*);
static ArrayData* CopyVec(const ArrayData*);
static ArrayData* CopyWithStrongIterators(const ArrayData*);
static ArrayData* NonSmartCopy(const ArrayData*);
HphpArray* copyImpl() const;
HphpArray* copyVec() const;
HphpArray* copyGeneric() const;
static ArrayData* AppendVec(ArrayData*, CVarRef v, bool copy);
static ArrayData* Append(ArrayData*, CVarRef v, bool copy);
static ArrayData* AppendRef(ArrayData*, CVarRef v, bool copy);
static ArrayData* AppendRefVec(ArrayData*, CVarRef v, bool copy);
static ArrayData* AppendWithRef(ArrayData*, CVarRef v, bool copy);
static ArrayData* AppendWithRefVec(ArrayData*, CVarRef v, bool copy);
static ArrayData* Plus(ArrayData*, const ArrayData* elems, bool copy);
static ArrayData* Merge(ArrayData*, const ArrayData* elems, bool copy);
static ArrayData* Pop(ArrayData*, Variant& value);
static ArrayData* PopVec(ArrayData*, Variant& value);
static ArrayData* Dequeue(ArrayData*, Variant& value);
static ArrayData* Prepend(ArrayData*, CVarRef v, bool copy);
static void Renumber(ArrayData*);
static void RenumberVec(ArrayData*);
static void OnSetEvalScalar(ArrayData*);
static void OnSetEvalScalarVec(ArrayData*);
// overrides ArrayData
static bool ValidFullPos(const ArrayData*, const FullPos &fp);
static bool AdvanceFullPos(ArrayData*, FullPos& fp);
// END overide/implements section
// nvGet and friends.
// "nv" stands for non-variant. If we know the types of keys and values
// through runtime and compile-time chicanery, we can directly call these
// methods.
// nvGet returns a pointer to the value if the specified key is in the
// array, NULL otherwise.
static TypedValue* NvGetIntVec(const ArrayData*, int64_t ki);
static TypedValue* NvGetInt(const ArrayData*, int64_t ki);
static TypedValue* NvGetStrVec(const ArrayData*, const StringData* k);
static TypedValue* NvGetStr(const ArrayData*, const StringData* k);
void nvBind(int64_t k, const TypedValue* v) {
ArrayData::setRef(k, tvAsCVarRef(v), false);
}
void nvBind(StringData* k, const TypedValue* v) {
ArrayData::setRef(k, tvAsCVarRef(v), false);
}
void nvAppend(const TypedValue* v) {
nextInsertVec(tvAsCVarRef(v));
}
static void NvGetKeyVec(const ArrayData*, TypedValue* out, ssize_t pos);
static void NvGetKey(const ArrayData*, TypedValue* out, ssize_t pos);
bool nvInsert(StringData* k, TypedValue *v);
/**
* Main helper for AddNewElemC. The semantics are slightly different from
* other helpers, but tuned for the opcode. The value to set is passed by
* value; the caller has incref'd it if necessary, and this call *moves* it
* to its location in the array (caller must not decref). If the value cannot
* be stored in the array, this helper decref's it.
*/
static ArrayData* AddNewElemC(ArrayData* a, TypedValue value);
private:
template <typename AccessorT>
SortFlavor preSort(const AccessorT& acc, bool checkTypes);
void postSort(bool resetKeys);
public:
static ArrayData* EscalateForSort(ArrayData* ad);
static void Ksort(ArrayData*, int sort_flags, bool ascending);
static void Sort(ArrayData*, int sort_flags, bool ascending);
static void Asort(ArrayData*, int sort_flags, bool ascending);
static void Uksort(ArrayData*, CVarRef cmp_function);
static void Usort(ArrayData*, CVarRef cmp_function);
static void Uasort(ArrayData*, CVarRef cmp_function);
// Elm's data.m_type == KindOfInvalid for deleted slots.
static bool isTombstone(DataType t) {
return t < KindOfUninit;
static_assert(KindOfUninit == 0 && KindOfInvalid < 0, "");
}
// Array element.
struct Elm {
/* The key is either a string pointer or an int value, and the _count
* field in data is used to discriminate the key type. _count = 0 means
* int, nonzero values contain 32 bits of a string's hashcode.
* It is critical that when we return &data to clients, that they not
* read or write the _count field! */
union {
int64_t ikey;
StringData* key;
};
// We store values here, but also some information local to this array:
// data.m_aux.u_hash contains either 0 (for an int key) or a string
// hashcode; the high bit is the int/string key descriminator.
// data.m_type == KindOfInvalid if this is an empty slot in the
// array (e.g. after a key is deleted).
TypedValueAux data;
bool hasStrKey() const {
return data.hash() != 0;
}
bool hasIntKey() const {
return data.hash() == 0;
}
int32_t hash() const {
return data.hash();
}
void setStrKey(StringData* k, strhash_t h) {
key = k;
data.hash() = int32_t(h) | 0x80000000;
}
void setIntKey(int64_t k) {
ikey = k;
data.hash() = 0;
}
};
struct ElmKey {
ElmKey() {}
ElmKey(int32_t hash, StringData* key) {
this->hash = hash;
this->key = key;
}
int32_t hash;
union {
StringData* key;
int64_t ikey;
};
};
// Element index, with special values < 0 used for hash tables.
// NOTE: Unfortunately, g++ on x64 tends to generate worse machine code for
// 32-bit ints than it does for 64-bit ints. As such, we have deliberately
// chosen to use ssize_t in some places where ideally we *should* have used
// ElmInd.
typedef int32_t ElmInd;
static const ElmInd ElmIndEmpty = -1; // == ArrayData::invalid_index
static const ElmInd ElmIndTombstone = -2;
// Use a minimum of an 4-element hash table. Valid range: [2..32]
static const uint32_t MinLgTableSize = 2;
static const uint32_t SmallHashSize = 1 << MinLgTableSize;
static const uint32_t SmallMask = SmallHashSize - 1;
static const uint32_t SmallSize = SmallHashSize - SmallHashSize / LoadScale;
uint32_t iterLimit() const { return m_used; }
// Fetch a value and optional key (if keyPos != nullptr), given an
// iterator pos. If withref is true, copy the value with "withRef"
// semantics, and decref the previous key before copying the key.
// Otherwise get the value cell (unboxing), and initialize keyOut.
template <bool withRef>
void getArrayElm(ssize_t pos, TypedValue* out, TypedValue* keyOut) const;
bool isTombstone(ssize_t pos) const;
private:
// Small: Array elements and the hash table are allocated inline.
//
// +--------------------+
// this --> | HphpArray fields |
// +--------------------+
// m_data --> | slot 0 ... | SmallSize slots for elements.
// | slot SmallSize-1 |
// +--------------------+
// m_hash --> | | 2^MinLgTableSize hash table entries.
// +--------------------+
//
// Medium: Just the hash table is allocated inline, array elements
// are allocated from malloc.
//
// +--------------------+
// this --> | HphpArray fields |
// +--------------------+
// m_hash --> | | 2^K hash table entries
// +--------------------+
//
// +--------------------+
// m_data --> | slot 0 | 0.75 * 2^K slots for elements.
// | slot 1 |
// | ... |
// +--------------------+
//
// Big: Array elements and the hash table are contiguously allocated, and
// elements are pointer aligned.
//
// +--------------------+
// m_data --> | slot 0 | 0.75 * 2^K slots for elements.
// | slot 1 |
// | ... |
// +--------------------+
// m_hash --> | | 2^K hash table entries.
// +--------------------+
uint32_t m_used; // Number of used elements (values or tombstones)
uint32_t m_cap; // Number of Elms we can use before having to grow.
uint32_t m_tableMask; // Bitmask used when indexing into the hash table.
uint32_t m_hLoad; // Hash table load (# of non-empty slots).
int64_t m_nextKI; // Next integer key to use for append.
Elm* m_data; // Contains elements and hash table.
ElmInd* m_hash; // Hash table.
union {
struct {
Elm slots[SmallSize];
ElmInd hash[SmallHashSize];
} m_inline_data;
ElmInd m_inline_hash[sizeof(m_inline_data) / sizeof(ElmInd)];
};
ssize_t nextElm(Elm* elms, ssize_t ei) const {
assert(ei >= -1);
while (size_t(++ei) < m_used) {
if (!isTombstone(elms[ei].data.m_type)) {
return ei;
}
}
return (ssize_t)ElmIndEmpty;
}
ssize_t prevElm(Elm* elms, ssize_t ei) const;
// Assert a bunch of invariants about this array then return true.
// usage: assert(checkInvariants());
bool checkInvariants() const;
static void getElmKey(const Elm& e, TypedValue* out);
ssize_t find(int64_t ki) const;
ssize_t find(const StringData* s, strhash_t prehash) const;
ElmInd* findForInsert(int64_t ki) const;
ElmInd* findForInsert(const StringData* k, strhash_t prehash) const;
ssize_t iter_advance_helper(ssize_t prev) const ATTRIBUTE_COLD;
/**
* findForNewInsert() CANNOT be used unless the caller can guarantee that
* the relevant key is not already present in the array. Otherwise this can
* put the array into a bad state; use with caution.
*/
ElmInd* findForNewInsert(size_t h0) const;
ElmInd* findForNewInsertLoop(size_t tableMask, size_t h0) const;
bool nextInsert(CVarRef data);
HphpArray* nextInsertVec(CVarRef data);
ArrayData* nextInsertRef(CVarRef data);
ArrayData* nextInsertWithRef(CVarRef data);
ArrayData* addLvalImpl(int64_t ki, Variant** pDest);
ArrayData* addLvalImpl(StringData* key, strhash_t h, Variant** pDest);
ArrayData* addVal(int64_t ki, CVarRef data);
ArrayData* addVal(StringData* key, CVarRef data);
ArrayData* addValWithRef(int64_t ki, CVarRef data);
ArrayData* addValWithRef(StringData* key, CVarRef data);
ArrayData* update(int64_t ki, CVarRef data);
ArrayData* update(StringData* key, CVarRef data);
ArrayData* updateRef(int64_t ki, CVarRef data);
ArrayData* updateRef(StringData* key, CVarRef data);
ArrayData* erase(ElmInd* ei, bool updateNext = false);
HphpArray* copyImpl(HphpArray* target) const;
bool isFull() const;
Elm* newElm(ElmInd* e, size_t h0);
Elm* newElmGrow(size_t h0);
Elm* allocElm(ElmInd* ei);
Elm* allocElmFast(ElmInd* ei);
TypedValue& allocNextElm(uint32_t i);
void initElmInt(Elm* e, int64_t ki, CVarRef data, bool byRef=false);
void initElmStr(Elm* e, strhash_t h, StringData* key, CVarRef data,
bool byRef=false);
void newElmInt(ElmInd* ei, int64_t ki, CVarRef data,
bool byRef=false);
void newElmStr(ElmInd* ei, strhash_t h, StringData* key, CVarRef data,
bool byRef=false);
ElmInd* allocData(size_t maxElms, size_t tableSize);
ElmInd* reallocData(size_t maxElms, size_t tableSize);
/**
* grow() increases the hash table size and the number of slots for
* elements by a factor of 2. grow() rebuilds the hash table, but it
* does not compact the elements.
*/
void grow() ATTRIBUTE_COLD;
void growVec() ATTRIBUTE_COLD;
/**
* compact() does not change the hash table size or the number of slots
* for elements. compact() rebuilds the hash table and compacts the
* elements into the slots with lower addresses.
*/
void compact(bool renumber=false) ATTRIBUTE_COLD;
/**
* resize() and resizeIfNeeded() will grow or compact the array as
* necessary to ensure that there is room for a new element and a
* new hash entry.
*
* resize() assumes that the array does not have room for a new element
* or a new hash entry. resizeIfNeeded() will first check if there is room
* for a new element and hash entry before growing or compacting the array.
*/
void resize();
void resizeIfNeeded();
// Memory allocator methods.
DECLARE_SMART_ALLOCATION(HphpArray);
static void ReleaseVec(ArrayData*);
static void Release(ArrayData*);
private:
enum EmptyMode { StaticEmptyArray };
explicit HphpArray(EmptyMode);
// static singleton empty array. Not a subclass because we want a fast
// isHphpArray implementation; HphpArray should be effectively final.
static HphpArray s_theEmptyArray;
void initHash(size_t tableSize);
void initNonEmpty(const HphpArray& other);
public:
static bool validElmInd(ssize_t /*HphpArray::ElmInd*/ ei) {
return (ei > ssize_t(HphpArray::ElmIndEmpty));
}
static size_t computeTableSize(uint32_t tableMask) {
return size_t(tableMask) + size_t(1U);
}
static size_t computeMaxElms(uint32_t tableMask) {
return size_t(tableMask) - size_t(tableMask) / HphpArray::LoadScale;
}
static size_t computeDataSize(uint32_t tableMask) {
return computeTableSize(tableMask) * sizeof(HphpArray::ElmInd) +
computeMaxElms(tableMask) * sizeof(HphpArray::Elm);
}
};
//=============================================================================
// inline for performance reasons
inline HphpArray* ArrayData::Make(uint capacity) {
return NEW(HphpArray)(capacity);
}
inline HphpArray* ArrayData::Make(uint size, const TypedValue* data) {
return NEW(HphpArray)(size, data);
}
// HphpArray has more than one kind, so reuse ArrayData's virtual dispatch.
inline void HphpArray::release() {
ArrayData::release();
}
///////////////////////////////////////////////////////////////////////////////
}
#endif // incl_HPHP_HPHP_ARRAY_H_