PolicyArray initial review

PolicyArray splits the ArrayData implementation in two parts. ArrayShell implements the baroque ArrayData implementation in terms of a much smaller statically-bound core that concerns itself exclusively with the storage strategy for the array. This way the two aspects can be worked on separately, and different stores can be easily plugged into the given ArrayShell.

To give a better perspective, the featured SimpleArrayStore has about 340 lines all told (including solid documentation), whereas ArrayShell has some 1100 lines. The shell can be reused with other stores of unbounded sophistication. The store needs to implement only 22 primitives, some of which are trivial. Basically a new store implementation saves 1100 of difficult-to-get-right lines of code right off the bat, and only needs to focus on implementing 22 well-defined primitives.

Things to watch for when reviewing:

- Is the store API small/expressive enough? What should be added/removed?

- Are various forms of duplication present? If so, how could code be factored better?

- Any subtle change in semantics from HphpArray and friends that should be minded?

Known issues:

* Currently ArrayShell is defined as:

  class ArrayShell : public ArrayData, private SimpleArrayStore { ... };

when in fact it should be:

  template <class StorePolicy>
  class ArrayShell : public ArrayData, private StorePolicy { ... };

Extenuating circumstances related to allocator design prevent use of templates at this point. I'll work on these in parallel with the review.

* growNoResize is almost always prefaced by a test-and-grow. This sequence should be encoded in a function.

* The growth policy is spread all over the place in a subtle form of duplication.

* The current store design makes almost no effort to be particularly efficient.
Esse commit está contido em:
aalexandre
2013-04-17 20:47:25 -07:00
commit de Sara Golemon
commit 9559dcf879
12 arquivos alterados com 1744 adições e 86 exclusões
+3 -4
Ver Arquivo
@@ -6766,7 +6766,7 @@ void EmitterVisitor::initScalar(TypedValue& tvVal, ExpressionPtr val) {
case Expression::KindOfUnaryOpExpression: {
UnaryOpExpressionPtr u(static_pointer_cast<UnaryOpExpression>(val));
if (u->getOp() == T_ARRAY) {
HphpArray* a = NEW(HphpArray)(0);
auto a = NEW(HphpArray)(0);
a->incRefCount();
m_staticArrays.push_back(a);
@@ -6774,10 +6774,9 @@ void EmitterVisitor::initScalar(TypedValue& tvVal, ExpressionPtr val) {
HphpArray* va = m_staticArrays.back();
m_staticArrays.pop_back();
assert(ArrayData::GetScalarArray(va)->isHphpArray());
va = static_cast<HphpArray*>(ArrayData::GetScalarArray(va));
tvVal.m_data.parr = va;
auto sa = ArrayData::GetScalarArray(va);
tvVal.m_data.parr = sa;
tvVal.m_type = KindOfArray;
decRefArr(a);
+6
Ver Arquivo
@@ -26,6 +26,7 @@
#include <runtime/base/shared/shared_map.h>
#include <util/exception.h>
#include <tbb/concurrent_hash_map.h>
#include <runtime/base/array/policy_array.h>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
@@ -115,6 +116,11 @@ void ArrayData::release() {
that->release();
return;
}
if (isArrayShell()) {
auto that = static_cast<ArrayShell*>(this);
that->release();
return;
}
assert(m_kind == kNameValueTableWrapper);
// NameValueTableWrapper: nop.
}
+3 -1
Ver Arquivo
@@ -44,6 +44,7 @@ class ArrayData : public Countable {
kHphpArray,
kSharedMap,
kNameValueTableWrapper,
kArrayShell,
};
protected:
@@ -53,7 +54,7 @@ class ArrayData : public Countable {
public:
static const ssize_t invalid_index = -1;
ArrayData(ArrayKind kind, bool nonsmart = false) :
explicit ArrayData(ArrayKind kind, bool nonsmart = false) :
m_size(-1), m_pos(0), m_strongIterators(0), m_kind(kind),
m_nonsmart(nonsmart) {
}
@@ -146,6 +147,7 @@ class ArrayData : public Countable {
/*
* Specific derived class type querying operators.
*/
bool isArrayShell() const { return m_kind == kArrayShell; }
bool isHphpArray() const { return m_kind == kHphpArray; }
bool isSharedMap() const { return m_kind == kSharedMap; }
bool isNameValueTableWrapper() const {
+9 -1
Ver Arquivo
@@ -14,6 +14,7 @@
+----------------------------------------------------------------------+
*/
#include <runtime/base/array/array_init.h>
#include <runtime/base/array/policy_array.h>
#include <runtime/base/array/hphp_array.h>
#include <runtime/base/runtime_option.h>
@@ -23,7 +24,14 @@ namespace HPHP {
HOT_FUNC
ArrayInit::ArrayInit(ssize_t n) {
m_data = n == 0 ? HphpArray::GetStaticEmptyArray() : NEW(HphpArray)(n);
if (!n) {
m_data = HphpArray::GetStaticEmptyArray();
} else if (false) {
// Force compilation of ArrayShell
m_data = NEW(ArrayShell)(n);
} else {
m_data = NEW(HphpArray)(n);
}
}
HOT_FUNC
+3 -2
Ver Arquivo
@@ -99,7 +99,7 @@ class ArrayIter {
~ArrayIter();
operator bool() { return !end(); }
explicit operator bool() { return !end(); }
void operator++() { next(); }
bool end() {
@@ -436,7 +436,8 @@ class MArrayIter : public FullPos {
ArrayData* data = getArray();
assert(data && data == getContainer());
assert(data->getCount() <= 1 || data->noCopyOnWrite());
assert(!getResetFlag() && data->validFullPos(*this));
assert(!getResetFlag());
assert(data->validFullPos(*this));
return data->getValueRef(m_pos);
}
+32 -32
Ver Arquivo
@@ -526,38 +526,38 @@ HphpArray::ElmInd* warnUnbalanced(size_t n, HphpArray::ElmInd* ei) {
return ei;
}
#define FIND_FOR_INSERT_BODY(h0, hit) \
ElmInd* ret = nullptr; \
size_t tableMask = m_tableMask; \
size_t probeIndex = size_t(h0) & tableMask; \
Elm* elms = m_data; \
ElmInd* ei = &m_hash[probeIndex]; \
ssize_t /*ElmInd*/ pos = *ei; \
if ((validElmInd(pos) && hit) || pos == ssize_t(ElmIndEmpty)) { \
return ei; \
} \
if (!validElmInd(pos)) ret = ei; \
/* Quadratic probe. */ \
for (size_t i = 1;; ++i) { \
assert(i <= tableMask); \
probeIndex = (probeIndex + i) & tableMask; \
assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \
ei = &m_hash[probeIndex]; \
pos = ssize_t(*ei); \
if (validElmInd(pos)) { \
if (hit) { \
assert(m_hLoad <= computeMaxElms(tableMask)); \
return ei; \
} \
} else { \
if (!ret) ret = ei; \
if (pos == ElmIndEmpty) { \
assert(m_hLoad <= computeMaxElms(tableMask)); \
return LIKELY(i <= 100) || \
LIKELY(i <= size_t(RuntimeOption::MaxArrayChain)) ? \
ret : warnUnbalanced(i, ret);\
} \
} \
#define FIND_FOR_INSERT_BODY(h0, hit) \
ElmInd* ret = nullptr; \
size_t tableMask = m_tableMask; \
size_t probeIndex = size_t(h0) & tableMask; \
Elm* elms = m_data; \
ElmInd* ei = &m_hash[probeIndex]; \
ssize_t /*ElmInd*/ pos = *ei; \
if ((validElmInd(pos) && hit) || pos == ssize_t(ElmIndEmpty)) { \
return ei; \
} \
if (!validElmInd(pos)) ret = ei; \
/* Quadratic probe. */ \
for (size_t i = 1;; ++i) { \
assert(i <= tableMask); \
probeIndex = (probeIndex + i) & tableMask; \
assert(((size_t(h0)+((i + i*i) >> 1)) & tableMask) == probeIndex); \
ei = &m_hash[probeIndex]; \
pos = ssize_t(*ei); \
if (validElmInd(pos)) { \
if (hit) { \
assert(m_hLoad <= computeMaxElms(tableMask)); \
return ei; \
} \
} else { \
if (!ret) ret = ei; \
if (pos == ElmIndEmpty) { \
assert(m_hLoad <= computeMaxElms(tableMask)); \
return LIKELY(i <= 100) || \
LIKELY(i <= size_t(RuntimeOption::MaxArrayChain)) ? \
ret : warnUnbalanced(i, ret); \
} \
} \
}
NEVER_INLINE
+940
Ver Arquivo
@@ -0,0 +1,940 @@
/*
+----------------------------------------------------------------------+
| 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 <runtime/base/array/policy_array.h>
#include <runtime/base/array/array_init.h>
#include <runtime/base/array/array_iterator.h>
#include <runtime/base/array/hphp_array.h>
#include <runtime/base/array/sort_helpers.h>
#include "folly/Foreach.h"
TRACE_SET_MOD(runtime);
#define MYLOG if (true) {} else LOG(INFO)
#define APILOG MYLOG << "{" << this << ":m_size=" << this->m_size \
<< ";cap=" << this->capacity() << ";m_pos=" << this->m_pos << "}->" \
<< __FUNCTION__
namespace HPHP {
static string keystr(const StringData* key) {
return "s:" + string(key->data(), key->size());
}
static string keystr(int64_t key) {
return "i:" + std::to_string(key);
}
static string valstr(const Variant& v) {
try {
auto result = v.toString();
return string(result.data(), result.size());
} catch (...) {
return "<messedup>";
}
}
SimpleArrayStore::SimpleArrayStore(const SimpleArrayStore& rhs,
uint length, uint capacity,
bool nonSmart, const ArrayData* owner)
: m_capacity(std::max<uint>(startingCapacity, capacity))
, m_nextKey(rhs.m_nextKey) {
assert(length <= capacity && this != &rhs);
allocate(m_keys, m_vals, m_capacity, nonSmart);
// Copy data with flattening
FOR_EACH_RANGE (i, 0, length) {
tvDupFlattenVars(rhs.m_vals + i, m_vals + i, owner);
if (rhs.hasStrKey(toPos(i))) {
setKey(toPos(i), rhs.m_keys[i].s);
} else {
setKey(toPos(i), rhs.m_keys[i].i);
}
}
}
void SimpleArrayStore::grow(uint length, uint minCap, uint idealCap,
bool nonSmart) {
assert(idealCap >= minCap);
if (m_capacity >= minCap) return;
MYLOG << (void*)this << "->grow(" << length << ", " << minCap << ", "
<< idealCap << ", " << nonSmart << "); m_capacity=" << m_capacity;
idealCap = std::max<uint>(startingCapacity, idealCap);
Key* newKeys;
TypedValueAux* newVals;
allocate(newKeys, newVals, idealCap, nonSmart);
// Move data
memcpy(newKeys, m_keys, length * sizeof(*m_keys));
memcpy(newVals, m_vals, length * sizeof(*m_vals));
deallocate(m_keys, m_vals, nonSmart);
// Change state
m_capacity = idealCap;
m_keys = newKeys;
m_vals = newVals;
}
void SimpleArrayStore::destroy(uint length, bool nonSmart) {
FOR_EACH_RANGE (i, 0, length) {
if (hasStrKey(toPos(i))) {
auto k = m_keys[i].s;
assert(k);
if (!k->decRefCount()) DELETE(StringData)(k);
}
lval(toPos(i)).~Variant();
}
deallocate(m_keys, m_vals, nonSmart);
#ifndef NDEBUG
m_keys = nullptr;
m_vals = nullptr;
#endif
}
PosType SimpleArrayStore::find(int64_t key, uint length) const {
assert(m_keys && length <= m_capacity);
// glorious linear find
for (uint i = 0; i < length; ++i) {
if (key == m_keys[i].i && !hasStrKey(toPos(i))) {
return toPos(i);
}
}
return PosType::invalid;
}
PosType SimpleArrayStore::find(const StringData* key, uint length) const {
// glorious linear find
assert(key && m_keys && length <= m_capacity);
auto const d0 = key->data();
auto const sz = key->size();
for (uint i = 0; i < length; ++i) {
if (!hasStrKey(toPos(i))) continue;
auto const k = m_keys[i].s;
if (key == k) return toPos(i);
assert(k);
if (sz != k->size()) continue;
auto const data = k->data();
if (d0 == data) return toPos(i);
assert(d0 && data);
if (memcmp(d0, data, sz) == 0) return toPos(i);
}
return PosType::invalid;
}
template <class K>
bool SimpleArrayStore::update(K key, const Variant& val, uint length,
bool nonSmart) {
assert(length <= m_capacity && m_vals);
auto const pos = find(key, length);
if (pos != PosType::invalid) {
// found, overwrite
assert(tvIsPlausible(m_vals + toUint32(pos)));
lval(pos) = val;
return false;
}
// not found, insert
assert(length <= m_capacity);
if (length == m_capacity) {
grow(length, length + 1, length * 2 + 1, nonSmart);
}
assert(m_keys && m_vals && length < m_capacity);
new(&lval(toPos(length))) Variant(val);
setKey(toPos(length), key);
return true;
}
void SimpleArrayStore::erase(PosType pos, uint length) {
auto const ipos = toUint32(pos);
assert(ipos < length && length <= capacity());
// Destroy data at pos
if (hasStrKey(pos)) {
auto const k = m_keys[ipos].s;
assert(k);
if (!k->decRefCount()) DELETE(StringData)(k);
}
lval(pos).~Variant();
// Shift over memory
auto const itemsToMove = length - ipos - 1;
memmove(m_keys + ipos, m_keys + ipos + 1, itemsToMove * sizeof(*m_keys));
memmove(m_vals + ipos, m_vals + ipos + 1, itemsToMove * sizeof(*m_vals));
}
void SimpleArrayStore::prepend(const Variant& v, uint length, bool nonSmart) {
if (length == capacity()) {
grow(length, length + 1, length * 2 + 1, nonSmart);
}
assert(length < capacity());
// Shift stuff over
memmove(m_keys + 1, m_keys, length * sizeof(*m_keys));
memmove(m_vals + 1, m_vals, length * sizeof(*m_vals));
// Construct the new value
new(m_vals) Variant(v);
}
////////////////////////////////////////////////////////////////////////////////
IMPLEMENT_SMART_ALLOCATION(ArrayShell)
ArrayShell::ArrayShell(uint capacity)
: ArrayData(kArrayShell)
, Store(m_nonsmart, capacity) {
m_size = 0;
m_pos = invalid_index;
// Log at the end of the ctor so as to show the properly initialized
// members.
APILOG << "(" << capacity << ");";
}
ArrayShell::ArrayShell(const ArrayShell& rhs, uint capacity,
bool nonSmart)
: ArrayData(kArrayShell, nonSmart)
, Store(rhs, rhs.m_size, capacity, nonSmart, &rhs) {
m_size = rhs.m_size;
m_pos = rhs.m_pos;
// Log at the end of the ctor so as to show the properly initialized
// members.
APILOG << "(" << &rhs << ", " << capacity << ", " << nonSmart << ");";
}
ArrayShell::~ArrayShell() {
APILOG << "()";
destroy(m_size, m_nonsmart);
}
Variant ArrayShell::getKey(ssize_t pos) const {
APILOG << "(" << pos << ")";
assert(size_t(pos) < m_size);
return key(toPos(pos));
}
Variant ArrayShell::getValue(ssize_t pos) const {
APILOG << "(" << pos << ")";
assert(size_t(pos) < m_size);
return getValueRef(pos);
}
const Variant& ArrayShell::getValueRef(ssize_t pos) const {
APILOG << "(" << pos << ")";
assert(size_t(pos) < m_size);
return val(toPos(pos));
}
bool ArrayShell::isVectorData() const {
APILOG << "()";
return ArrayData::isVectorData();
}
Variant ArrayShell::reset() {
APILOG << "()";
if (m_size) {
auto const first = firstIndex(m_size);
m_pos = toUint32(first);
return val(first);
}
m_pos = invalid_index;
return false;
}
Variant ArrayShell::prev() {
NOT_IMPLEMENTED();
}
Variant ArrayShell::current() const {
APILOG << "()";
if (m_pos == invalid_index) {
return false;
}
assert(size_t(m_pos) < m_size);
return val(toPos(m_pos));
}
Variant ArrayShell::next() {
APILOG << "()";
if (m_pos == invalid_index
|| (m_pos = iter_advance(m_pos)) == invalid_index) {
return false;
}
assert(size_t(m_pos) < m_size);
return val(toPos(m_pos));
}
Variant ArrayShell::end() {
if (m_size) {
auto const last = lastIndex(m_size);;
m_pos = toUint32(last);
return val(last);
}
m_pos = invalid_index;
return false;
}
Variant ArrayShell::key() const {
APILOG << ")";
if (m_pos == invalid_index) {
return uninit_null();
}
assert(size_t(m_pos) < m_size);
return key(toPos(m_pos));
}
Variant ArrayShell::value(ssize_t &pos) const {
NOT_IMPLEMENTED();
}
static const StaticString s_value("value");
static const StaticString s_key("key");
static_assert(ArrayData::invalid_index == size_t(-1), "ehm");
Variant ArrayShell::each() {
APILOG << "()";
if (m_pos == invalid_index) return false;
assert(m_size);
ArrayInit init(4);
assert(size_t(m_pos) < m_size);
Variant key = Store::key(toPos(m_pos));
Variant value = val(toPos(m_pos));
init.set(int64_t(1), value);
init.set(s_value, value, true);
init.set(int64_t(0), key);
init.set(s_key, key, true);
m_pos = toInt64(nextIndex(toPos(m_pos), m_size));
return Array(init.create());
}
template <class K>
TypedValue* ArrayShell::nvGetImpl(K k) const {
APILOG << "(" << keystr(k) << ")";
auto const pos = find(k, m_size);
return LIKELY(pos != PosType::invalid)
? reinterpret_cast<TypedValue*>(&lval(pos))
: nullptr;
}
void ArrayShell::nvGetKey(TypedValue* out, ssize_t pos) {
APILOG << "(" << out << ", " << pos << ")";
assert(size_t(pos) < m_size);
new(out) Variant(key(toPos(pos)));
}
TypedValue* ArrayShell::nvGetValueRef(ssize_t pos) {
APILOG << "(" << pos << ")";
assert(size_t(pos) < m_size);
return reinterpret_cast<TypedValue*>(&lval(toPos(pos)));
}
template <class K>
TypedValue* ArrayShell::nvGetCellImpl(K k) const {
APILOG << "(" << keystr(k) << ")";
auto const pos = find(k, m_size);
return LIKELY(pos != PosType::invalid)
? tvToCell(reinterpret_cast<TypedValue*>(&lval(pos)))
: nvGetNotFound(k);
}
template <class K>
ssize_t ArrayShell::getIndexImpl(K k) const {
APILOG << "(" << keystr(k) << ")";
return toInt64(find(k, m_size));
}
template <class K>
const Variant& ArrayShell::getImpl(K k, bool error) const {
APILOG << "(" << keystr(k) << ", " << error << ")";
auto const pos = find(k, m_size);
if (pos != PosType::invalid) {
return val(pos);
}
return error ? getNotFound(k) : null_variant;
}
template <class K>
ArrayData *ArrayShell::lvalImpl(K k, Variant*& ret,
bool copy, bool checkExist) {
APILOG << "(" << keystr(k) << ", " << ret << ", "
<< copy << ", " << checkExist << ")";
if (copy) {
return ArrayShell::copy()->lvalImpl(k, ret, false, checkExist);
}
PosType pos = PosType::invalid;
if (checkExist && (pos = find(k, m_size)) != PosType::invalid) {
assert(toUint32(pos) < m_size);
auto& e = lval(pos);
if (e.isReferenced() || e.isObject()) {
MYLOG << (void*)this << "->lval:" << "found1";
ret = &e;
return this;
}
}
// Make sure the search is done. TODO: this may actually search
// twice sometimes.
if (pos == PosType::invalid) {
pos = find(k, m_size);
}
if (pos != PosType::invalid) {
// found, don't overwrite anything
assert(toUint32(pos) <= m_size);
ret = &lval(pos);
MYLOG << (void*)this << "->lvalImpl:" << "found at " << toInt64(pos)
<< ", value=" << valstr(*ret) << ", size=" << m_size;
} else {
// not found, initialize
if (m_size == capacity()) {
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
assert(m_size < capacity());
ret = appendNoGrow(k, Variant::nullInit);
}
return this;
}
ArrayData *ArrayShell::lvalNew(Variant *&ret, bool copy) {
if (copy) {
return ArrayShell::copy()->lvalNew(ret, false);
}
// Andrei: TODO - append() currently never fails, probably it
// should.
auto oldSize = m_size;
append(uninit_null(), false);
assert(m_size == oldSize + 1);
if (UNLIKELY(oldSize == m_size)) {
ret = &Variant::lvalBlackHole();
} else {
assert(lastIndex(m_size) != PosType::invalid);
ret = &lval(lastIndex(m_size));
}
return this;
}
ArrayData *ArrayShell::lvalPtr(StringData* k, Variant *&ret, bool copy,
bool create) {
NOT_IMPLEMENTED();
}
template <class K>
ArrayShell* ArrayShell::setImpl(K k, const Variant& v, bool copy) {
APILOG << "(" << keystr(k) << ", " << valstr(v) << ", " << copy
<< ")";
ArrayShell* result = this;
if (copy) result = ArrayShell::copy();
if (result->update(k, v, result->m_size, result->m_nonsmart)) {
// Added a new element, must update size and possibly m_pos
if (m_pos == invalid_index) m_pos = result->m_size;
result->m_size++;
}
return result;
}
template <class K>
ArrayData *ArrayShell::setRefImpl(K k, CVarRef v, bool copy) {
APILOG << "(" << keystr(k) << ", " << valstr(v) << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->setRef(k, v, false);
}
auto const pos = find(k, m_size);
assert(m_size <= capacity());
if (pos != PosType::invalid) {
// found, update
lval(pos).assignRef(v);
} else {
// not found, create new element
MYLOG << "setRef: not found, appending at " << m_size;
if (m_size == capacity()) {
MYLOG << "grow";
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
appendNoGrow(k, Variant::noInit)->constructRefHelper(v);
}
return this;
}
template <class K>
ArrayData *ArrayShell::addImpl(K k, const Variant& v, bool copy) {
APILOG << "(" << keystr(k) << ", " << valstr(v) << ", " << copy << ");";
if (copy) {
auto result = ArrayShell::copy(m_size * 2 + 1);
result->add(k, v, false);
return result;
}
assert(!exists(k));
// Make sure there's enough capacity
if (m_size == capacity()) {
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
appendNoGrow(k, v);
return this;
}
ArrayShell *ArrayShell::addLval(int64_t k, Variant*& ret, bool copy) {
APILOG << "(" << k << ", " << ret << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->addLval(k, ret, false);
}
assert(!exists(k) && m_size <= capacity());
if (m_size == capacity()) {
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
ret = appendNoGrow(k, Variant::nullInit);
MYLOG << (void*)this << "->lval:" << "added";
return this;
}
ArrayShell *ArrayShell::addLval(StringData* k, Variant *&ret, bool copy) {
NOT_IMPLEMENTED();
}
template <class K>
ArrayData *ArrayShell::removeImpl(K k, bool copy) {
APILOG << "(" << keystr(k) << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->remove(k, false);
}
auto const pos = find(k, m_size);
if (pos == PosType::invalid) {
// Not found, nothing to delete
MYLOG << "not found, nothing to delete: " << keystr(k);
return this;
}
for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) {
FullPos& fp = *r.front();
if (ssize_t(pos) <= fp.m_pos) {
// We are removing something before or at the current position,
// back off position to account for the shifting.
if (!fp.m_pos) fp.setResetFlag(true);
else --fp.m_pos;
}
}
Store::erase(pos, m_size);
--m_size;
if (!Store::before(m_pos, pos)) {
// We removed something before or at the current position, back
// off position to account for the shifting.
m_pos = ssize_t(prevIndex(toPos(m_pos), m_size));
}
assert(size_t(m_pos) < m_size || m_pos == invalid_index);
return this;
}
ssize_t ArrayShell::iter_begin() const {
APILOG << "()";
return m_size ? toInt64(firstIndex(m_size)) : invalid_index;
}
ssize_t ArrayShell::iter_end() const {
APILOG << "()";
return ssize_t(lastIndex(m_size));
}
ssize_t ArrayShell::iter_advance(ssize_t prev) const {
APILOG << "(" << prev << ")";
auto const result = toInt64(nextIndex(toPos(prev), m_size));
MYLOG << "returning " << result;
return result;
}
ssize_t ArrayShell::iter_rewind(ssize_t prev) const {
APILOG << "(" << prev << ")";
return toInt64(prevIndex(toPos(prev), m_size));
}
bool ArrayShell::validFullPos(const FullPos& fp) const {
APILOG << "(" << fp.m_pos << ";" << fp.getResetFlag() << ")";
assert(fp.getContainer() == this);
return fp.m_pos != invalid_index;
}
bool ArrayShell::advanceFullPos(FullPos &fp) {
APILOG << "(" << fp.m_pos << ";" << fp.getResetFlag() << ")";
assert(fp.getContainer() == this);
if (fp.getResetFlag()) {
fp.setResetFlag(false);
fp.m_pos = invalid_index;
} else if (fp.m_pos == invalid_index) {
return false;
}
fp.m_pos = toInt64(nextIndex(toPos(fp.m_pos), m_size));
if (fp.m_pos == invalid_index) {
return false;
}
// To conform to PHP behavior, we need to set the internal
// cursor to point to the next element.
m_pos = toInt64(nextIndex(toPos(fp.m_pos), m_size));
return true;
}
CVarRef ArrayShell::currentRef() {
NOT_IMPLEMENTED();
}
CVarRef ArrayShell::endRef() {
NOT_IMPLEMENTED();
}
HphpArray* ArrayShell::toHphpArray() const {
auto result = NEW(HphpArray)(m_size);
FOR_EACH_RANGE (i, 0, m_size) {
if (hasStrKey(toPos(i))) {
result->add(key(toPos(i)).getStringData(), val(toPos(i)), false);
} else {
result->add(key(toPos(i)).getInt64(), val(toPos(i)), false);
}
}
return result;
}
ArrayData* ArrayShell::escalateForSort() {
APILOG << "()";
return toHphpArray();
}
void ArrayShell::ksort(int sort_flags, bool ascending) {
NOT_IMPLEMENTED();
}
void ArrayShell::sort(int sort_flags, bool ascending) {
NOT_IMPLEMENTED();
}
void ArrayShell::asort(int sort_flags, bool ascending) {
NOT_IMPLEMENTED();
}
void ArrayShell::uksort(CVarRef cmp_function) {
NOT_IMPLEMENTED();
}
void ArrayShell::usort(CVarRef cmp_function) {
NOT_IMPLEMENTED();
}
void ArrayShell::uasort(CVarRef cmp_function) {
NOT_IMPLEMENTED();
}
ArrayShell *ArrayShell::copy() const {
APILOG << "()";
auto result = NEW(ArrayShell)(
*this,
capacity() + (m_size == capacity()),
m_nonsmart);
assert(result->getCount() == 0);
return result;
}
ArrayShell* ArrayShell::copy(uint capacity) {
APILOG << "(" << capacity << ")";
return NEW(ArrayShell)(*this, capacity, m_nonsmart);
}
ArrayShell *ArrayShell::copyWithStrongIterators() const {
APILOG << "()";
auto result = ArrayShell::copy();
moveStrongIterators(result, const_cast<ArrayShell*>(this));
assert(result->getCount() == 0);
return result;
}
ArrayData *ArrayShell::nonSmartCopy() const {
APILOG << "()";
//return NEW(ArrayShell)(*this, capacity(), true);
return toHphpArray()->nonSmartCopy();
}
ArrayShell *ArrayShell::append(const Variant& v, bool copy) {
APILOG << "(" << valstr(v) << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->append(v, false);
}
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
appendNoGrow(nextKeyBump(), v);
return this;
}
ArrayShell *ArrayShell::appendRef(const Variant& v, bool copy) {
APILOG << "(" << valstr(v) << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->appendRef(v, false);
}
//addValWithRef(nextKeyBump(), v);
auto const k = nextKeyBump();
if (m_size == capacity()) {
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
assert(m_size < capacity());
appendNoGrow(k, Variant::noInit)->constructRefHelper(v);
return this;
}
/**
* Similar to append(v, copy), with reference in v preserved.
*/
ArrayData *ArrayShell::appendWithRef(CVarRef v, bool copy) {
APILOG << "(" << valstr(v) << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->appendWithRef(v, false);
}
if (m_size == capacity()) {
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
assert(m_size < capacity());
appendNoGrow(nextKeyBump(), Variant::nullInit)->setWithRef(v);
return this;
}
template <class K>
void ArrayShell::addValWithRef(K k, const Variant& v) {
MYLOG << (void*)this << "->addValWithRef("
<< keystr(k) << ", " << valstr(v)
<< "); size=" << m_size;
auto pos = find(k, m_size);
if (pos != PosType::invalid) {
return;
}
if (m_size == capacity()) {
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
assert(m_size < capacity());
appendNoGrow(k, Variant::nullInit)->setWithRef(v);
}
void ArrayShell::nextInsertWithRef(const Variant& v) {
MYLOG << (void*)this << "->nextInsertWithRef("
<< valstr(v)
<< "); size=" << m_size;
// We need to define k here (before the if/grow) because otherwise
// the overzealous gcc issues a spurious warning as such:
//
// hphp/runtime/base/array/policy_array.h: In member function 'void
// HPHP::ArrayShell::nextInsertWithRef(const HPHP::Variant&)':
// hphp/runtime/base/array/policy_array.h:114:5: error: assuming
// signed overflow does not occur when assuming that (X + c) < X is
// always false [-Werror=strict-overflow]
auto const k = nextKeyBump();
if (m_size == capacity()) {
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
}
assert(m_size < capacity());
appendNoGrow(k, Variant::nullInit)->setWithRef(v);
}
ArrayData *ArrayShell::append(const ArrayData *elems, ArrayOp op, bool copy) {
APILOG << "(" << elems << ", " << op << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->append(elems, op, false);
}
assert(elems);
assert(op == Plus || op == Merge);
grow(m_size, m_size + 1, m_size * 2 + 1, m_nonsmart);
for (ArrayIter it(elems); !it.end(); it.next()) {
Variant key = it.first();
const Variant& value = it.secondRef();
if (op == Plus) {
if (key.isNumeric()) {
addValWithRef(key.toInt64(), value);
} else {
addValWithRef(key.getStringData(), value);
}
} else {
if (key.isNumeric()) {
nextInsertWithRef(value);
} else {
StringData *s = key.getStringData();
Variant *p;
// Andrei TODO: make sure this is the right semantics
lval(s, p, false, true);
p->setWithRef(value);
}
}
}
return this;
}
/**
* Stack function: pop the last item and return it.
*/
ArrayData* ArrayShell::pop(Variant &value) {
if (getCount() > 1) {
return ArrayShell::copy()->pop(value);
}
if (!m_size) {
value = uninit_null();
return this;
}
auto pos = lastIndex(m_size);
assert(size_t(pos) < m_size);
value = val(pos);
erase(pos, m_size);
--m_size;
// To match PHP-like semantics, the pop operation resets the array's
// internal iterator.
m_pos = m_size ? toInt64(firstIndex(m_size)) : invalid_index;
return this;
}
ArrayData *ArrayShell::dequeue(Variant &value) {
APILOG << "(" << &value << ")";
if (getCount() > 1) {
return ArrayShell::copy()->dequeue(value);
}
// To match PHP-like semantics, we invalidate all strong iterators when an
// element is removed from the beginning of the array.
freeStrongIterators();
if (!m_size) {
value = uninit_null();
return this;
}
auto& front = lval(firstIndex(m_size));
value = std::move(front);
new(&front) Variant;
erase(firstIndex(m_size), m_size);
--m_size;
renumber();
// To match PHP-like semantics, the dequeue operation resets the array's
// internal iterator
m_pos = m_size ? toInt64(firstIndex(m_size)) : invalid_index;
return this;
}
ArrayData* ArrayShell::prepend(CVarRef v, bool copy) {
APILOG << "(" << valstr(v) << ", " << copy << ")";
if (copy) {
return ArrayShell::copy()->prepend(v, false);
}
// To match PHP-like semantics, we invalidate all strong iterators when an
// element is added to the beginning of the array.
freeStrongIterators();
Store::prepend(v, m_size, m_nonsmart);
++m_size;
auto first = firstIndex(m_size);
setKey(first, int64_t(0));
// Renumber.
renumber();
// To match PHP-like semantics, the prepend operation resets the array's
// internal iterator
m_pos = toInt64(first);
return this;
}
void ArrayShell::renumber() {
APILOG << "()";
if (!m_size) {
return;
}
Variant currentPosKey;
if (m_pos != invalid_index) {
// Cache key for element associated with m_pos in order to update m_pos
// below.
assert(size_t(m_pos) < m_size);
currentPosKey = key(toPos(m_pos));
}
vector<Variant> siKeys;
for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) {
auto const pos = toPos(r.front()->m_pos);
if (pos != PosType::invalid) {
siKeys.push_back(key(pos));
}
}
nextKeyReset();
FOR_EACH_RANGE (i, 0, m_size) {
if (!hasStrKey(toPos(i))) {
setKey(toPos(i), nextKeyBump());
}
}
if (m_pos != invalid_index) {
// Update m_pos, now that compaction is complete.
if (currentPosKey.isString()) {
m_pos = toInt64(find(currentPosKey.getStringData(), m_size));
} else if (currentPosKey.is(KindOfInt64)) {
m_pos = toInt64(find(currentPosKey.getInt64(), m_size));
} else {
assert(false);
}
}
// Update strong iterators, now that compaction is complete.
auto i = siKeys.cbegin();
for (FullPosRange r(strongIterators()); !r.empty(); r.popFront()) {
FullPos* fp = r.front();
if (fp->m_pos == invalid_index) {
continue;
}
auto& k = *i++;
if (k.isString()) {
fp->m_pos = toInt64(find(k.getStringData(), m_size));
} else {
assert(k.is(KindOfInt64));
fp->m_pos = toInt64(find(k.getInt64(), m_size));
}
}
assert(i == siKeys.cend());
}
void ArrayShell::onSetEvalScalar() {
APILOG << "()";
//FOR_EACH_RANGE (pos, 0, m_size) {
for (auto pos = firstIndex(m_size); pos != PosType::invalid;
pos = nextIndex(pos, m_size)) {
if (hasStrKey(pos)) {
auto k = key(pos).getStringData();
if (!k->isStatic()) {
auto sk = StringData::GetStaticString(k);
if (k->decRefCount() == 0) {
DELETE(StringData)(k);
}
// Andrei TODO: inefficient, does one incref and then decref
setKey(pos, sk);
sk->decRefCount();
}
}
lval(pos).setEvalScalar();
}
}
void ArrayShell::dump() {
NOT_IMPLEMENTED();
}
void ArrayShell::dump(std::string &out) {
NOT_IMPLEMENTED();
}
void ArrayShell::dump(std::ostream &os) {
NOT_IMPLEMENTED();
}
ArrayData *ArrayShell::escalate() const {
APILOG << "()";
return ArrayData::escalate();
}
} // namespace HPHP
+699
Ver Arquivo
@@ -0,0 +1,699 @@
/*
+----------------------------------------------------------------------+
| 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_POLICY_ARRAY_H_
#define HPHP_POLICY_ARRAY_H_
#include <runtime/base/types.h>
#include <runtime/base/array/array_data.h>
#include <runtime/base/memory/smart_allocator.h>
#include <runtime/base/complex_types.h>
#include <util/trace.h>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
/**
PosType is a int-like type that denotes a position in an array. All
traffic in ArrayStore implementation uses PosType consistently. The
advantage is that there's never a possibility to confuse a position
with e.g. an integral key. The disadvantage is that explicit casts
must be inserted whenever we have an integral and need a position, or
vice versa.
*/
enum class PosType : size_t { invalid = size_t(-1) };
static_assert(ssize_t(PosType::invalid) == ArrayData::invalid_index,
"Bad design: can't have PosType::invalid and"
" ArrayData::invalid_index mean distinct things.");
// Change of type, no effect on representation
static int64_t toInt64(PosType pos) {
static_assert(sizeof(int64_t) == sizeof(PosType), "can't");
return static_cast<int64_t>(pos);
}
// Assumes a valid position, convert to an uint32_t. Useful for array
// indices because they can't be larger than 32 bits anyway.
static uint32_t toUint32(PosType pos) {
static_assert(uint32_t(PosType::invalid) == uint32_t(-1), "");
assert(pos != PosType::invalid);
return static_cast<uint32_t>(pos);
}
// Signed to pos, no change in representation.
static PosType toPos(int64_t n) {
static_assert(sizeof(int64_t) == sizeof(PosType), "can't");
return static_cast<PosType>(n);
}
// Unsigned to pos.
static PosType toPos(uint32_t n) {
// If this fails it's weird - uint max doesn't become
// PosType::invalid; instead, it'll be 4 billion something, which is
// unhelpful.
assert(n != uint32_t(-1));
return static_cast<PosType>(n);
}
/**
SimpleArrayStore implements a basic storage strategy for ArrayShell. It
uses two contiguous arrays, one for keys and the other for
values. There is no indexing, just linear search, so don't use for
large arrays.
Storage policies don't store the array size internally because it's
already a member in ArrayData; keeping it here again only invites bugs
and inconsistencies. So the API requires passing the size around
whenever necessary. The name used herein is 'length' and it's a uint
because array lengths cannot be larger than that.
*/
class SimpleArrayStore {
union Key {
int64_t i;
StringData* s;
};
// Nope
SimpleArrayStore() = delete;
SimpleArrayStore(const SimpleArrayStore&) = delete;
SimpleArrayStore& operator=(const SimpleArrayStore&) = delete;
/**
Allocate keys and values at a given capacity and with a given
allocation strategy.
*/
static void allocate(Key*& ks, TypedValueAux*& vs, uint cap, bool nonSmart) {
/* To save time, only one allocation is done and keys are stored
right next to the values. */
auto const totSize = (sizeof(*ks) + sizeof(*vs)) * cap;
auto const raw = nonSmart
? Util::safe_malloc(totSize)
: smart_malloc(totSize);
vs = static_cast<TypedValueAux*>(raw);
ks = reinterpret_cast<Key*>(vs + cap);
}
/** Inverse of allocate */
static void deallocate(Key* ks, TypedValueAux* vs, bool nonSmart) {
assert(ks && vs);
if (nonSmart) {
Util::safe_free(vs);
} else {
smart_free(vs);
}
}
protected:
/**
This is the smallest non-zero capacity allocated.
*/
enum : unsigned int { startingCapacity = 16 };
/**
Returns true iff pos1 comes before pos2 in the array's normal order.
*/
static bool before(ssize_t pos1, PosType pos2) {
return pos1 < ssize_t(pos2);
}
/**
Pseudo-copy constructor taking the source, the needed length and
capacity, allocation strategy, and the owner ArrayData (weird;
required by tvDupFlattenVars).
*/
SimpleArrayStore(const SimpleArrayStore& rhs, uint length, uint capacity,
bool nonSmart, const ArrayData* owner);
~SimpleArrayStore() {
/* If this fails, it means someone didn't call destroy. */
assert(!m_keys && !m_vals);
}
/**
Destroys and deallocates all data, assumed to be of size
length. This is effectively the destructor, implemented as a
separate function in order to obtain length and nonSmart
information.
*/
void destroy(uint length, bool nonSmart);
/**
The following four methods implement bidirectional iteration for
indexes. Client code should not assume that these indices are
contiguous. (This particular policy implementation does use
contiguous indices.)
*/
PosType firstIndex(uint length) const {
assert(length);
return PosType(0);
}
PosType lastIndex(uint length) const {
assert(length);
return toPos(length - 1);
}
PosType nextIndex(PosType current, uint length) const {
auto result = toPos(toInt64(current) + 1);
return toUint32(result) < length ? result : PosType::invalid;
}
PosType prevIndex(PosType current, uint length) const {
if (current == PosType(0) || current == PosType::invalid) {
return PosType::invalid;
}
return toPos(toUint32(current) - 1);
}
/**
Primitives for the next available integral key.
*/
void nextKeyReset() { m_nextKey = 0; }
int64_t nextKeyBump() { return m_nextKey++; }
/**
Prepend v to the array. An uninitialized hole is left in the first
key slot; you MUST use setKey subsequently to initialize it.
*/
void prepend(const Variant& v, uint length, bool nonSmart);
/**
Erase at position pos from the array. Cleans up data and
everything. Caller must update its own length etc.
*/
void erase(PosType pos, uint length);
/**
Construct an array store with the given allocation strategy and
capacity.
*/
SimpleArrayStore(bool nonSmart, uint capacity)
: m_capacity(std::max<uint>(startingCapacity, capacity))
, m_nextKey(0) {
allocate(m_keys, m_vals, m_capacity, nonSmart);
}
/**
Returns the maximum number of elements this store can hold without a
reallocation.
*/
uint capacity() const { return m_capacity; }
/**
Grow the store starting from size up to at least minSize, ideally to
idealSize, using the given allocation strategy. Caller ensures size
<= minSize && minSize = idealSize. No actual objects are allocated
in the grown store.
*/
void grow(uint size, uint minSize, uint idealSize, bool nonSmart);
/**
Returns true iff the key at pos is a string.
*/
bool hasStrKey(PosType pos) const {
assert(m_vals && toUint32(pos) < m_capacity);
return m_vals[toUint32(pos)].hash() != 0;
}
/**
Returns the key at position pos. The result is either KindOfInt64 or
KindOfString.
*/
Variant key(PosType pos) const {
assert(m_keys && toUint32(pos) < m_capacity);
return hasStrKey(pos)
? Variant(m_keys[toUint32(pos)].s)
: Variant(m_keys[toUint32(pos)].i);
}
/**
Sets key at pos to integer k. Does NOT destroy the previous key,
user must ensure that the previous key is released appropriately (if
it was a string).
*/
void setKey(PosType pos, int64_t k) {
assert(m_keys && m_vals && toUint32(pos) < m_capacity);
m_keys[toUint32(pos)].i = k;
m_vals[toUint32(pos)].hash() = 0;
if (m_nextKey <= k) m_nextKey = k + 1;
}
/**
Sets key at pos to StringData* k. Does NOT destroy the previous key,
user must ensure that the previous key is released appropriately (if
it was a string). However, it DOES increment the reference count of
the StringData.
*/
void setKey(PosType pos, StringData* k) {
assert(k && m_keys && m_vals && toUint32(pos) < m_capacity);
m_keys[toUint32(pos)].s = k;
m_vals[toUint32(pos)].hash() = 1;
k->incRefCount();
}
/**
Finds the given key and returns its position. If key not found,
returns PosType::invalid.
*/
PosType find(int64_t key, uint length) const;
PosType find(const StringData* key, uint length) const;
/**
Updates the slot at position key with value. If slot did not exist,
insert it. If slot did not exist and key is a StringData, increments
its refcount.
*/
template <class K>
bool update(K key, const Variant& val, uint length, bool nonSmart);
/**
Returns a const reference to the Variant held at position
pos. Caller must ensure that pos is valid (i.e. corresponds to an
existing element). Other implementations should be able to return an
rvalue Variant, meaning they can use alternate storage strategies
for the data.
*/
const Variant& val(PosType pos) const {
assert(m_vals && toUint32(pos) < m_capacity);
return tvAsCVarRef(m_vals + toUint32(pos));
}
/**
Returns a modifiable lvalue at position pos. Using the API forces
storing values as Variant/TypedValue. Calling lval for a
not-yet-initialized position is fine and may be used by the caller
to initialize the value.
*/
Variant& lval(PosType pos) const {
assert(m_vals && toUint32(pos) < m_capacity);
// Don't use tvAsVariant here (which may assert on you), the
// reference returned may refer to a not-yet-initialized object.
return tvAsUninitializedVariant(m_vals + toUint32(pos));
}
private:
/*****************************************************************************
State begins here
*****************************************************************************/
TypedValueAux* m_vals;
Key* m_keys;
uint m_capacity;
int64_t m_nextKey;
/*****************************************************************************
State ends here
*****************************************************************************/
};
////////////////////////////////////////////////////////////////////////////////
// ArrayShell
////////////////////////////////////////////////////////////////////////////////
class ArrayShell : public ArrayData, private SimpleArrayStore {
typedef SimpleArrayStore Store;
using Store::key;
ArrayShell(const ArrayShell& rhs) = delete;
ArrayShell& operator=(const ArrayShell& rhs) = delete;
ArrayShell(const ArrayShell& rhs, uint capacity, bool nonSmart);
template <class K>
void addValWithRef(K k, const Variant& value);
void nextInsertWithRef(const Variant& value);
// Copy to a brand new HphpArray
HphpArray* toHphpArray() const;
public:
// Memory allocator methods.
DECLARE_SMART_ALLOCATION(ArrayShell);
explicit ArrayShell(uint size);
virtual ~ArrayShell() FOLLY_OVERRIDE;
/**
* Number of elements this array has.
*/
virtual ssize_t vsize() const FOLLY_OVERRIDE {
// vsize() called, but m_size should never be -1 in ArrayShell
assert(false);
return m_size;
}
/**
* For ArrayIter to work. Get key or value at position "pos".
*/
virtual Variant getKey(ssize_t pos) const FOLLY_OVERRIDE;
virtual Variant getValue(ssize_t pos) const FOLLY_OVERRIDE;
/**
* getValueRef() gets a reference to value at position "pos".
*/
virtual CVarRef getValueRef(ssize_t pos) const FOLLY_OVERRIDE;
/*
* Return true for array types that don't have COW semantics.
*/
virtual bool noCopyOnWrite() const FOLLY_OVERRIDE { 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 FOLLY_OVERRIDE;
virtual SharedVariant *getSharedVariant() const FOLLY_OVERRIDE {
return nullptr;
}
/**
* Position-based iterations.
*/
virtual Variant reset() FOLLY_OVERRIDE;
virtual Variant prev() FOLLY_OVERRIDE;
virtual Variant current() const FOLLY_OVERRIDE;
virtual Variant next() FOLLY_OVERRIDE;
virtual Variant end() FOLLY_OVERRIDE;
virtual Variant key() const FOLLY_OVERRIDE;
virtual Variant value(ssize_t &pos) const FOLLY_OVERRIDE;
virtual Variant each() FOLLY_OVERRIDE;
virtual bool isInvalid() const FOLLY_OVERRIDE {
return m_pos == invalid_index;
}
/**
* Testing whether a key exists.
*/
virtual bool exists(int64_t k) const FOLLY_OVERRIDE {
return Store::find(k, m_size) < toPos(m_size);
}
virtual bool exists(const StringData* k) const FOLLY_OVERRIDE {
return Store::find(k, m_size) < toPos(m_size);
}
/**
* Getting value at specified key.
*/
private:
template <class K>
const Variant& getImpl(K k, bool error) const;
public:
// Andrei: this API forces storage to store variants in the
// TypedValue/Variant layout, thus disallowing e.g. implementations
// that store type tags and values separately.
virtual const Variant&
get(int64_t k, bool error = false) const FOLLY_OVERRIDE {
return getImpl(k, error);
}
virtual const Variant&
get(const StringData* k, bool error = false) const FOLLY_OVERRIDE {
return getImpl(k, error);
}
private:
template <class K> TypedValue* nvGetImpl(K k) const;
public:
/**
* Interface for VM helpers. ArrayData implements generic versions
* using the other ArrayData api; subclasses may do better.
*/
virtual TypedValue* nvGet(int64_t k) const FOLLY_OVERRIDE {
return nvGetImpl(k);
}
virtual TypedValue* nvGet(const StringData* k) const FOLLY_OVERRIDE {
return nvGetImpl(k);
}
virtual void nvGetKey(TypedValue* out, ssize_t pos) FOLLY_OVERRIDE;
virtual TypedValue* nvGetValueRef(ssize_t pos) FOLLY_OVERRIDE;
private:
template <class K> TypedValue* nvGetCellImpl(K k) const;
public:
virtual TypedValue* nvGetCell(int64_t k) const FOLLY_OVERRIDE {
return nvGetCellImpl(k);
}
virtual TypedValue* nvGetCell(const StringData* k) const FOLLY_OVERRIDE {
return nvGetCellImpl(k);
}
/**
* Get the numeric index for a key. Only these need to be
* in ArrayData.
*/
private:
template <class K> ssize_t getIndexImpl(K k) const;
public:
virtual ssize_t getIndex(int64_t k) const FOLLY_OVERRIDE {
return getIndexImpl(k);
}
virtual ssize_t getIndex(const StringData* k) const FOLLY_OVERRIDE {
return getIndexImpl(k);
}
private:
template <class K>
ArrayData *lvalImpl(K k, Variant *&ret, bool copy, bool checkExist);
using Store::lval;
public:
/**
* 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_t k,
Variant *&ret,
bool copy,
bool checkExist = false) FOLLY_OVERRIDE {
return lvalImpl(k, ret, copy, checkExist);
}
virtual ArrayData *lval(StringData* k,
Variant*& ret,
bool copy,
bool checkExist = false) FOLLY_OVERRIDE {
return lvalImpl(k, ret, copy, checkExist);
}
/**
* 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) FOLLY_OVERRIDE;
/**
* 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(StringData* k, Variant *&ret,
bool copy,
bool create) FOLLY_OVERRIDE;
/**
* 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.
*/
private:
template <class K>
ArrayShell* setImpl(K k, CVarRef v, bool copy);
public:
virtual ArrayData* set(int64_t k, CVarRef v, bool copy) FOLLY_OVERRIDE {
return setImpl(k, v, copy);
}
virtual ArrayData* set(StringData* k, CVarRef v, bool copy) FOLLY_OVERRIDE {
return setImpl(k, v, copy);
}
private:
template <class K>
ArrayData *setRefImpl(K k, CVarRef v, bool copy);
public:
virtual ArrayData *setRef(int64_t k, CVarRef v, bool copy) FOLLY_OVERRIDE {
return setRefImpl(k, v, copy);
}
virtual ArrayData *setRef(StringData* k, CVarRef v, bool cpy) FOLLY_OVERRIDE {
return setRefImpl(k, v, cpy);
}
/**
* 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.)
*/
private:
template <class K>
ArrayData *addImpl(K k, CVarRef v, bool copy);
public:
virtual ArrayData *add(int64_t k, CVarRef v, bool copy) FOLLY_OVERRIDE {
return addImpl(k, v, copy);
}
virtual ArrayData *add(StringData* k, CVarRef v, bool copy) FOLLY_OVERRIDE {
return addImpl(k, v, copy);
}
/*
* Same semantics as lval(), except with the precondition that the
* key doesn't already exist in the array.
*/
virtual ArrayShell *addLval(int64_t k, Variant *&ret, bool copy)
FOLLY_OVERRIDE;
virtual ArrayShell *addLval(StringData* k, Variant *&ret, bool copy)
FOLLY_OVERRIDE;
/**
* 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.
*/
private:
template <class K> ArrayData *removeImpl(K k, bool copy);
public:
virtual ArrayData *remove(int64_t k, bool copy) FOLLY_OVERRIDE {
return removeImpl(k, copy);
}
virtual ArrayData *remove(const StringData* k, bool copy) FOLLY_OVERRIDE {
return removeImpl(k, copy);
}
virtual ssize_t iter_begin() const FOLLY_OVERRIDE;
virtual ssize_t iter_end() const FOLLY_OVERRIDE;
virtual ssize_t iter_advance(ssize_t prev) const FOLLY_OVERRIDE;
virtual ssize_t iter_rewind(ssize_t prev) const FOLLY_OVERRIDE;
/**
* 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.
*/
/**
* 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 FOLLY_OVERRIDE;
/**
* Advances the mutable iterator to the next element in the array. Returns
* false if the iterator has moved past the last element, otherwise returns
* true.
*/
virtual bool advanceFullPos(FullPos& fp) FOLLY_OVERRIDE;
virtual CVarRef currentRef() FOLLY_OVERRIDE;
virtual CVarRef endRef() FOLLY_OVERRIDE;
virtual ArrayData* escalateForSort() FOLLY_OVERRIDE;
virtual void ksort(int sort_flags, bool ascending) FOLLY_OVERRIDE;
virtual void sort(int sort_flags, bool ascending) FOLLY_OVERRIDE;
virtual void asort(int sort_flags, bool ascending) FOLLY_OVERRIDE;
virtual void uksort(CVarRef cmp_function) FOLLY_OVERRIDE;
virtual void usort(CVarRef cmp_function) FOLLY_OVERRIDE;
virtual void uasort(CVarRef cmp_function) FOLLY_OVERRIDE;
/**
* 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.
*/
ArrayShell* copy(uint minCapacity);
virtual ArrayShell *copy() const FOLLY_OVERRIDE;
virtual ArrayShell *copyWithStrongIterators() const FOLLY_OVERRIDE;
virtual ArrayData *nonSmartCopy() const FOLLY_OVERRIDE;
private:
template <class K, class V>
Variant* appendNoGrow(K k, const V& v) {
assert(m_size < Store::capacity());
auto result = &lval(toPos(m_size));
// WARNING: sequencing the following two lines is important.
new(result) Variant(v);
setKey(toPos(m_size), k);
// If position is at end, make it point to the element just added.
if (size_t(m_pos) > m_size) m_pos = m_size;
++m_size;
return result;
}
public:
/**
* 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 ArrayShell *append(CVarRef v, bool copy) FOLLY_OVERRIDE;
virtual ArrayShell *appendRef(CVarRef v, bool copy) FOLLY_OVERRIDE;
/**
* Similar to append(v, copy), with reference in v preserved.
*/
virtual ArrayData *appendWithRef(CVarRef v, bool copy) FOLLY_OVERRIDE;
/**
* 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)
FOLLY_OVERRIDE;
/**
* Stack function: pop the last item and return it.
*/
virtual ArrayData *pop(Variant &value) FOLLY_OVERRIDE;
/**
* Queue function: remove the 1st item and return it.
*/
virtual ArrayData *dequeue(Variant &value) FOLLY_OVERRIDE;
/**
* Array function: prepend a new item.
*/
virtual ArrayData *prepend(CVarRef v, bool copy) FOLLY_OVERRIDE;
/**
* Only map classes need this. Re-index all numeric keys to start from 0.
*/
virtual void renumber() FOLLY_OVERRIDE;
virtual void onSetEvalScalar() FOLLY_OVERRIDE;
virtual void dump() FOLLY_OVERRIDE;
virtual void dump(std::string &out) FOLLY_OVERRIDE;
virtual void dump(std::ostream &os) FOLLY_OVERRIDE;
virtual ArrayData *escalate() const FOLLY_OVERRIDE;
};
///////////////////////////////////////////////////////////////////////////////
}
#endif // HPHP_POLICY_ARRAY_H_
+45 -43
Ver Arquivo
@@ -91,9 +91,9 @@ class Variant : private TypedValue {
}
enum NullInit { nullInit };
Variant(NullInit) { m_type = KindOfNull; }
explicit Variant(NullInit) { m_type = KindOfNull; }
enum NoInit { noInit };
Variant(NoInit) {}
explicit Variant(NoInit) {}
void destruct();
static void destructData(RefData* num, DataType t);
@@ -111,35 +111,35 @@ class Variant : private TypedValue {
* Variant being able to take many other external types, messing up those
* operator overloads.
*/
Variant(bool v) { m_type = KindOfBoolean; m_data.num = v; }
Variant(int v) { m_type = KindOfInt64; m_data.num = v; }
/* implicit */ Variant(bool v) { m_type = KindOfBoolean; m_data.num = v; }
/* implicit */ Variant(int v) { m_type = KindOfInt64; m_data.num = v; }
// The following two overloads will accept int64_t whether it's
// implemented as long or long long.
Variant(long v) { m_type = KindOfInt64; m_data.num = v; }
Variant(long long v) { m_type = KindOfInt64; m_data.num = v; }
Variant(uint64_t v) { m_type = KindOfInt64; m_data.num = v; }
/* implicit */ Variant(long v) { m_type = KindOfInt64; m_data.num = v; }
/* implicit */ Variant(long long v) { m_type = KindOfInt64; m_data.num = v; }
/* implicit */ Variant(uint64_t v) { m_type = KindOfInt64; m_data.num = v; }
Variant(double v) { m_type = KindOfDouble; m_data.dbl = v; }
/* implicit */ Variant(double v) { m_type = KindOfDouble; m_data.dbl = v; }
Variant(litstr v);
Variant(const std::string &v);
Variant(const StaticString &v) {
/* implicit */ Variant(litstr v);
/* implicit */ Variant(const std::string &v);
/* implicit */ Variant(const StaticString &v) {
m_type = KindOfStaticString;
StringData *s = v.get();
assert(s);
m_data.pstr = s;
}
Variant(CStrRef v);
Variant(CArrRef v);
Variant(CObjRef v);
Variant(StringData *v);
Variant(ArrayData *v);
Variant(ObjectData *v);
Variant(RefData *r);
/* implicit */ Variant(CStrRef v);
/* implicit */ Variant(CArrRef v);
/* implicit */ Variant(CObjRef v);
/* implicit */ Variant(StringData *v);
/* implicit */ Variant(ArrayData *v);
/* implicit */ Variant(ObjectData *v);
/* implicit */ Variant(RefData *r);
// Move ctor for strings
Variant(String&& v) {
/* implicit */ Variant(String&& v) {
StringData *s = v.get();
if (LIKELY(s != nullptr)) {
m_data.pstr = s;
@@ -153,7 +153,7 @@ class Variant : private TypedValue {
}
// Move ctor for arrays
Variant(Array&& v) {
/* implicit */ Variant(Array&& v) {
m_type = KindOfArray;
ArrayData *a = v.get();
if (LIKELY(a != nullptr)) {
@@ -165,7 +165,7 @@ class Variant : private TypedValue {
}
// Move ctor for objects
Variant(Object&& v) {
/* implicit */ Variant(Object&& v) {
m_type = KindOfObject;
ObjectData *pobj = v.get();
if (pobj) {
@@ -179,14 +179,14 @@ class Variant : private TypedValue {
// These are prohibited, but declared just to prevent accidentally
// calling the bool constructor just because we had a pointer to
// const.
Variant(const StringData *v) = delete;
Variant(const ArrayData *v) = delete;
Variant(const ObjectData *v) = delete;
Variant(const RefData *v) = delete;
Variant(const TypedValue *v) = delete;
Variant(TypedValue *v) = delete;
Variant(const Variant *v) = delete;
Variant(Variant *v) = delete;
/* implicit */ Variant(const StringData *v) = delete;
/* implicit */ Variant(const ArrayData *v) = delete;
/* implicit */ Variant(const ObjectData *v) = delete;
/* implicit */ Variant(const RefData *v) = delete;
/* implicit */ Variant(const TypedValue *v) = delete;
/* implicit */ Variant(TypedValue *v) = delete;
/* implicit */ Variant(const /* implicit */ Variant *v) = delete;
/* implicit */ Variant(/* implicit */ Variant *v) = delete;
/*
* Creation constructor from ArrayInit that avoids a null check.
@@ -199,17 +199,18 @@ class Variant : private TypedValue {
}
#ifdef INLINE_VARIANT_HELPER
inline ALWAYS_INLINE Variant(CVarRef v) { constructValHelper(v); }
inline ALWAYS_INLINE
Variant(CVarStrongBind v) { constructRefHelper(variant(v)); }
/* implicit */ Variant(CVarRef v) { constructValHelper(v); }
inline ALWAYS_INLINE
Variant(CVarWithRefBind v) {
/* implicit */ Variant(CVarStrongBind v) { constructRefHelper(variant(v)); }
inline ALWAYS_INLINE
/* implicit */ Variant(CVarWithRefBind v) {
constructWithRefHelper(variant(v), 0);
}
#else
Variant(CVarRef v);
Variant(CVarStrongBind v);
Variant(CVarWithRefBind v);
/* implicit */ Variant(CVarRef v);
/* implicit */ Variant(CVarStrongBind v);
/* implicit */ Variant(CVarWithRefBind v);
#endif
/*
@@ -627,7 +628,7 @@ class Variant : private TypedValue {
* inference coding simpler (Expression::m_expectedType handling).
*/
operator bool () const { return toBoolean();}
/* implicit */ operator bool () const { return toBoolean();}
operator char () const { return toByte();}
operator short () const { return toInt16();}
operator int () const { return toInt32();}
@@ -1204,6 +1205,7 @@ public:
if (IS_REFCOUNTED_TYPE(t)) destructData(d, t);
}
public:
inline ALWAYS_INLINE void constructRefHelper(CVarRef v) {
PromoteToRef(v);
v.m_data.pref->incRefCount();
@@ -1319,10 +1321,10 @@ private:
class VRefParamValue {
public:
template <class T> VRefParamValue(const T &v) : m_var(v) {}
template <class T> /* implicit */ VRefParamValue(const T &v) : m_var(v) {}
VRefParamValue() : m_var(Variant::nullInit) {}
VRefParamValue(RefResult v) : m_var(strongBind(v)) {}
/* implicit */ VRefParamValue() : m_var(Variant::nullInit) {}
/* implicit */ VRefParamValue(RefResult v) : m_var(strongBind(v)) {}
template <typename T>
Variant &operator=(const T &v) const {
m_var = v;
@@ -1336,7 +1338,7 @@ public:
Variant *operator&() const { return &m_var; }
Variant *operator->() const { return &m_var; }
operator bool () const { return m_var.toBoolean();}
explicit operator bool () const { return m_var.toBoolean();}
operator int () const { return m_var.toInt32();}
operator int64_t () const { return m_var.toInt64();}
operator double () const { return m_var.toDouble();}
@@ -1387,7 +1389,7 @@ private:
};
inline VRefParamValue vref(CVarRef v) {
return strongBind(v);
return VRefParamValue(strongBind(v));
}
inline VRefParam directRef(CVarRef v) {
@@ -1447,8 +1449,8 @@ public:
return asVariant()->isNull();
}
private:
VarNR(litstr v); // not implemented
VarNR(const std::string & v); // not implemented
/* implicit */ VarNR(litstr v); // not implemented
/* implicit */ VarNR(const std::string & v); // not implemented
void init(DataType dt) {
m_type = dt;
+1 -1
Ver Arquivo
@@ -1671,7 +1671,7 @@ void VMExecutionContext::shuffleMagicArgs(ActRec* ar) {
assert(!ar->hasVarEnv() && !ar->hasInvName());
// We need to make an array containing all the arguments passed by the
// caller and put it where the second argument is
HphpArray* argArray = pack_args_into_array(ar, nargs);
ArrayData* argArray = pack_args_into_array(ar, nargs);
argArray->incRefCount();
// Remove the arguments from the stack
for (int i = 0; i < nargs; ++i) {
+1
Ver Arquivo
@@ -703,6 +703,7 @@ public:
}
inline void ALWAYS_INLINE pushArray(ArrayData* a) {
assert(a);
pushArrayNoRc(a);
a->incRefCount();
}
+2 -2
Ver Arquivo
@@ -2117,8 +2117,8 @@ void checkFrame(ActRec* fp, Cell* sp, bool checkLocals) {
}
// TODO: validate this pointer from actrec
int numLocals = func->numLocals();
DEBUG_ONLY Cell* firstSp = ((Cell*)fp) - func->numSlotsInFrame();
assert(sp <= firstSp || func->isGenerator());
assert(sp <= (Cell*)fp - func->numSlotsInFrame()
|| func->isGenerator());
if (checkLocals) {
int numParams = func->numParams();
for (int i=0; i < numLocals; i++) {