diff --git a/hphp/runtime/base/shared/concurrent_shared_store.cpp b/hphp/runtime/base/shared/concurrent_shared_store.cpp index 16355f1fd..51decd84d 100644 --- a/hphp/runtime/base/shared/concurrent_shared_store.cpp +++ b/hphp/runtime/base/shared/concurrent_shared_store.cpp @@ -252,7 +252,7 @@ SharedVariant* ConcurrentTableSharedStore::unserialize(CStrRef key, VariableUnserializer vu(sval->sAddr, sval->getSerializedSize(), sType); Variant v; v.unserialize(&vu); - sval->var = SharedVariant::Create(v, sval->isSerializedObj()); + sval->var = new SharedVariant(v, sval->isSerializedObj()); stats_on_add(key.get(), sval, 0, true, true); // delayed prime return sval->var; } catch (Exception &e) { @@ -518,7 +518,7 @@ bool ConcurrentTableSharedStore::constructPrime(CStrRef v, KeyValuePair& item, return false; } } - item.value = SharedVariant::Create(v, serialized); + item.value = new SharedVariant(v, serialized); return true; } @@ -535,7 +535,7 @@ bool ConcurrentTableSharedStore::constructPrime(CVarRef v, return false; } } - item.value = SharedVariant::Create(v, false); + item.value = new SharedVariant(v, false); return true; } diff --git a/hphp/runtime/base/shared/concurrent_shared_store.h b/hphp/runtime/base/shared/concurrent_shared_store.h index c7bc2f209..6972619f4 100644 --- a/hphp/runtime/base/shared/concurrent_shared_store.h +++ b/hphp/runtime/base/shared/concurrent_shared_store.h @@ -38,7 +38,7 @@ namespace HPHP { class ConcurrentTableSharedStore : public SharedStore { public: - ConcurrentTableSharedStore(int id) + explicit ConcurrentTableSharedStore(int id) : SharedStore(id), m_lockingFlag(false), m_purgeCounter(0) {} virtual int size() { @@ -62,7 +62,7 @@ public: protected: virtual SharedVariant* construct(CVarRef v) { - return SharedVariant::Create(v, false); + return new SharedVariant(v, false); } struct charHashCompare { diff --git a/hphp/runtime/base/shared/immutable_map.cpp b/hphp/runtime/base/shared/immutable_map.cpp index 5f63ef71c..fd363c1eb 100644 --- a/hphp/runtime/base/shared/immutable_map.cpp +++ b/hphp/runtime/base/shared/immutable_map.cpp @@ -38,11 +38,7 @@ ImmutableMap* ImmutableMap::Create(ArrayData* arr, try { for (ArrayIter it(arr); !it.end(); it.next()) { - SharedVariant* key = SharedVariant::Create(it.first(), false, true, - unserializeObj); - SharedVariant* val = SharedVariant::Create(it.secondRef(), false, true, - unserializeObj); - ret->add(ret->m.m_num, key, val); + ret->add(ret->m.m_num, it.first(), it.secondRef(), unserializeObj); ++ret->m.m_num; } } catch (...) { @@ -57,36 +53,62 @@ HOT_FUNC void ImmutableMap::Destroy(ImmutableMap* map) { Bucket* buckets = map->buckets(); for (int i = 0; i < map->m.m_num; i++) { - delete buckets[i].key; - delete buckets[i].val; + (*(SharedVariant*)&buckets[i].val).~SharedVariant(); } free(map); } HOT_FUNC -void ImmutableMap::add(int pos, SharedVariant *key, SharedVariant *val) { +void ImmutableMap::addVal(int pos, int hash_pos, + CVarRef val, bool unserializeObj) { // NOTE: no check on duplication because we assume the original array has no // duplication Bucket* bucket = buckets() + pos; - bucket->key = key; - bucket->val = val; - int hash_pos = - (key->is(KindOfInt64) ? - key->intData() : key->getStringData()->hash()) & m.m_capacity_mask; - + new (&bucket->val) SharedVariant(val, false, true, unserializeObj); int& hp = hash()[hash_pos]; bucket->next = hp; hp = pos; } +HOT_FUNC +void ImmutableMap::add(int pos, CVarRef key, CVarRef val, bool unserializeObj) { + int64_t ikey; + StringData* skey; + int32_t hash; + Bucket* b = buckets() + pos; + + switch (key.getType()) { + case KindOfInt64: { + hash = ikey = key.toInt64(); + b->setIntKey(ikey); + break; + } + case KindOfString: { + skey = StringData::GetStaticString(key.getStringData()); + goto static_case; + } + case KindOfStaticString: { + skey = key.getStringData(); +static_case: + hash = skey->hash(); + b->setStrKey(skey, hash); + break; + } + default: not_reached(); + } + addVal(pos, hash & m.m_capacity_mask, val, unserializeObj); +} + +#define STR_HASH(x) (int32_t(x) | 0x80000000) + HOT_FUNC int ImmutableMap::indexOf(const StringData* key) { - strhash_t h = key->hash(); + strhash_t h = STR_HASH(key->hash()); int bucket = hash()[h & m.m_capacity_mask]; Bucket* b = buckets(); while (bucket != -1) { - if (!b[bucket].key->is(KindOfInt64) && - key->same(b[bucket].key->getStringData())) { + Bucket* cand = &b[bucket]; + if (cand->hash() == h && (cand->skey == key || key->same(cand->skey))) { return bucket; } bucket = b[bucket].next; @@ -99,8 +121,7 @@ int ImmutableMap::indexOf(int64_t key) { int bucket = hash()[key & m.m_capacity_mask]; Bucket* b = buckets(); while (bucket != -1) { - if (b[bucket].key->is(KindOfInt64) && - key == b[bucket].key->intData()) { + if (b[bucket].hasIntKey() && key == b[bucket].ikey) { return bucket; } bucket = b[bucket].next; diff --git a/hphp/runtime/base/shared/immutable_map.h b/hphp/runtime/base/shared/immutable_map.h index 2ee3e62b0..66a493743 100644 --- a/hphp/runtime/base/shared/immutable_map.h +++ b/hphp/runtime/base/shared/immutable_map.h @@ -25,7 +25,6 @@ namespace HPHP { /////////////////////////////////////////////////////////////////////////////// -class SharedVariant; /** * an immutable map is a php-style array that can take strings and * ints as keys. the map also stores the order in which the elements @@ -37,14 +36,19 @@ public: int indexOf(const StringData* key); int indexOf(int64_t key); - SharedVariant* getKeyIndex(int index) { + Variant getKey(int index) { assert(index < size()); - return buckets()[index].key; + Bucket* b = &buckets()[index]; + if (b->hasIntKey()) { + return b->ikey; + } else { + return b->skey; + } } - SharedVariant* getValIndex(int index) { + SharedVariant* getValue(int index) { assert(index < size()); - return buckets()[index].val; + return (SharedVariant*)&buckets()[index].val; } unsigned size() const { @@ -65,18 +69,43 @@ public: static ImmutableMap* Create(ArrayData* arr, bool unserializeObj); static void Destroy(ImmutableMap* im); -private: - ImmutableMap() {} - ~ImmutableMap() {} - void add(int pos, SharedVariant *key, SharedVariant *val); struct Bucket { /** index of the next bucket, or -1 if the end of a chain */ int next; - /** the value of this bucket */ - SharedVariant *key; - SharedVariant *val; + /* similar to HphpArray::Elm */ + union { + int64_t ikey; + StringData* skey; + }; + // cannot declare SharedVariant here because of cyclic header + // includes + TypedValueAux val; + bool hasStrKey() const { + return val.hash() != 0; + } + bool hasIntKey() const { + return val.hash() == 0; + } + int32_t hash() const { + return val.hash(); + } + void setStrKey(StringData* k, strhash_t h) { + skey = k; + val.hash() = int32_t(h) | 0x80000000; + } + void setIntKey(int64_t k) { + ikey = k; + val.hash() = 0; + } }; + +private: + ImmutableMap() {} + ~ImmutableMap() {} + void addVal(int pos, int hash_pos, CVarRef val, bool unserializeObj); + void add(int pos, CVarRef key, CVarRef val, bool unserializeObj); + /** index of the beginning of each hash chain */ int *hash() const { return (int*)(this + 1); } /** buckets, stored in index order */ diff --git a/hphp/runtime/base/shared/shared_map.cpp b/hphp/runtime/base/shared/shared_map.cpp index ad902c793..2005c2220 100644 --- a/hphp/runtime/base/shared/shared_map.cpp +++ b/hphp/runtime/base/shared/shared_map.cpp @@ -27,17 +27,16 @@ IMPLEMENT_SMART_ALLOCATION_HOT(SharedMap); /////////////////////////////////////////////////////////////////////////////// HOT_FUNC CVarRef SharedMap::getValueRef(ssize_t pos) const { - SharedVariant *sv = m_arr->getValue(pos); + SharedVariant *sv = getValueImpl(pos); DataType t = sv->getType(); if (!IS_REFCOUNTED_TYPE(t)) return sv->asCVarRef(); if (LIKELY(m_localCache != nullptr)) { - assert(unsigned(pos) < m_arr->arrCap()); + assert(unsigned(pos) < size()); TypedValue* tv = &m_localCache[pos]; if (tv->m_type != KindOfUninit) return tvAsCVarRef(tv); } else { static_assert(KindOfUninit == 0, "must be 0 since we use smart_calloc"); - unsigned cap = m_arr->arrCap(); - m_localCache = (TypedValue*) smart_calloc(cap, sizeof(TypedValue)); + m_localCache = (TypedValue*) smart_calloc(size(), sizeof(TypedValue)); } TypedValue* tv = &m_localCache[pos]; tvAsVariant(tv) = sv->toLocal(); @@ -48,7 +47,7 @@ CVarRef SharedMap::getValueRef(ssize_t pos) const { HOT_FUNC SharedMap::~SharedMap() { if (m_localCache) { - for (TypedValue* tv = m_localCache, *end = tv + m_arr->arrCap(); + for (TypedValue* tv = m_localCache, *end = tv + size(); tv < end; ++tv) { tvRefcountedDecRef(tv); } @@ -56,24 +55,29 @@ SharedMap::~SharedMap() { } } -bool SharedMap::exists(const StringData* k) const { - return m_arr->getIndex(k) != -1; -} - -bool SharedMap::exists(int64_t k) const { - return m_arr->getIndex(k) != -1; +ssize_t SharedMap::getIndex(const StringData* k) const { + if (isVector()) return -1; + return m_map->indexOf(k); } ssize_t SharedMap::getIndex(int64_t k) const { - return m_arr->getIndex(k); + if (isVector()) { + if (k < 0 || (size_t)k >= m_vec->m_size) return -1; + return k; + } + return m_map->indexOf(k); } -ssize_t SharedMap::getIndex(const StringData* k) const { - return m_arr->getIndex(k); +bool SharedMap::exists(const StringData* k) const { + return getIndex(k) != -1; +} + +bool SharedMap::exists(int64_t k) const { + return getIndex(k) != -1; } CVarRef SharedMap::get(const StringData* k, bool error /* = false */) const { - int index = m_arr->getIndex(k); + int index = getIndex(k); if (index == -1) { return error ? getNotFound(k) : null_variant; } @@ -81,7 +85,7 @@ CVarRef SharedMap::get(const StringData* k, bool error /* = false */) const { } CVarRef SharedMap::get(int64_t k, bool error /* = false */) const { - int index = m_arr->getIndex(k); + int index = getIndex(k); if (index == -1) { return error ? getNotFound(k) : null_variant; } @@ -171,25 +175,25 @@ ArrayData *SharedMap::prepend(CVarRef v, bool copy) { } ArrayData *SharedMap::escalate() const { - ArrayData *ret = m_arr->loadElems(*this); + ArrayData *ret = loadElems(); assert(!ret->isStatic()); return ret; } TypedValue* SharedMap::nvGet(int64_t k) const { - int index = m_arr->getIndex(k); + int index = getIndex(k); if (index == -1) return nullptr; return (TypedValue*)&getValueRef(index); } TypedValue* SharedMap::nvGet(const StringData* key) const { - int index = m_arr->getIndex(key); + int index = getIndex(key); if (index == -1) return nullptr; return (TypedValue*)&getValueRef(index); } void SharedMap::nvGetKey(TypedValue* out, ssize_t pos) { - Variant k = m_arr->getKey(pos); + Variant k = getKey(pos); TypedValue* tv = k.asTypedValue(); // copy w/out clobbering out->_count. out->m_type = tv->m_type; @@ -202,22 +206,46 @@ TypedValue* SharedMap::nvGetValueRef(ssize_t pos) { } TypedValue* SharedMap::nvGetCell(int64_t k) const { - int index = m_arr->getIndex(k); + int index = getIndex(k); return index != -1 ? getValueRef(index).getTypedAccessor() : nvGetNotFound(k); } TypedValue* SharedMap::nvGetCell(const StringData* key) const { - int index = m_arr->getIndex(key); + int index = getIndex(key); return index != -1 ? getValueRef(index).getTypedAccessor() : nvGetNotFound(key); } ArrayData* SharedMap::escalateForSort() { - ArrayData *ret = m_arr->loadElems(*this, true /* mapInit */); + ArrayData *ret = loadElems(true /* mapInit */); assert(!ret->isStatic()); return ret; } +ArrayData* SharedMap::loadElems(bool mapInit /* = false */) const { + uint count = size(); + bool isVec = isVector(); + + auto ai = + mapInit ? ArrayInit(count, ArrayInit::mapInit) : + isVec ? ArrayInit(count, ArrayInit::vectorInit) : + ArrayInit(count); + + if (isVec) { + for (uint i = 0; i < count; i++) { + ai.set(getValueRef(i)); + } + } else { + for (uint i = 0; i < count; i++) { + ai.add(m_map->getKey(i), getValueRef(i), + true); + } + } + ArrayData* elems = ai.create(); + if (elems->isStatic()) elems = elems->copy(); + return elems; +} + /////////////////////////////////////////////////////////////////////////////// } diff --git a/hphp/runtime/base/shared/shared_map.h b/hphp/runtime/base/shared/shared_map.h index 7254f9f66..284c11e7a 100644 --- a/hphp/runtime/base/shared/shared_map.h +++ b/hphp/runtime/base/shared/shared_map.h @@ -31,10 +31,11 @@ namespace HPHP { */ class SharedMap : public ArrayData { public: - SharedMap(SharedVariant* source) + explicit SharedMap(SharedVariant* source) : ArrayData(kSharedMap) - , m_arr(source) , m_localCache(nullptr) { + m_map = source->getMap(); + m_isVector = source->getIsVector(); } ~SharedMap(); @@ -57,11 +58,19 @@ public: using ArrayData::remove; ssize_t vsize() const { - return m_arr->arrSize(); + return isVector() ? m_vec->m_size : m_map->size(); } Variant getKey(ssize_t pos) const { - return m_arr->getKey(pos); + if (isVector()) { + assert(pos < (ssize_t)m_vec->m_size); + return pos; + } + return m_map->getKey(pos); + } + + SharedVariant* getValueImpl(ssize_t pos) const { + return isVector() ? m_vec->getValue(pos) : m_map->getValue(pos); } Variant getValue(ssize_t pos) const { return getValueRef(pos); } @@ -119,9 +128,16 @@ public: virtual ArrayData *escalate() const; virtual ArrayData* escalateForSort(); + ArrayData* loadElems(bool mapInit = false) const; + private: - SharedVariant *m_arr; + bool m_isVector; + union { + ImmutableMap* m_map; + VectorData* m_vec; + }; mutable TypedValue* m_localCache; + bool isVector() const { return m_isVector; } }; /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/base/shared/shared_variant.cpp b/hphp/runtime/base/shared/shared_variant.cpp index d8b81af9c..f05d7e145 100644 --- a/hphp/runtime/base/shared/shared_variant.cpp +++ b/hphp/runtime/base/shared/shared_variant.cpp @@ -28,7 +28,6 @@ SharedVariant::SharedVariant(CVarRef source, bool serialized, bool unserializeObj /* = false */) : m_flags(0) { assert(!serialized || source.isString()); - m_count = 1; m_type = source.getType(); switch (m_type) { case KindOfBoolean: @@ -91,9 +90,7 @@ StringCase: setIsVector(); m_data.vec = new (arr->size()) VectorData(); for (ArrayIter it(arr); !it.end(); it.next()) { - SharedVariant* val = Create(it.secondRef(), false, true, - unserializeObj); - m_data.vec->vals()[m_data.vec->m_size++] = val; + m_data.vec->add(it.secondRef(), unserializeObj); } } else { m_data.map = ImmutableMap::Create(arr, unserializeObj); @@ -246,88 +243,6 @@ SharedVariant::~SharedVariant() { /////////////////////////////////////////////////////////////////////////////// -HOT_FUNC -int SharedVariant::getIndex(const StringData* key) { - assert(is(KindOfArray)); - if (getIsVector()) return -1; - return m_data.map->indexOf(key); -} - -int SharedVariant::getIndex(int64_t key) { - assert(is(KindOfArray)); - if (getIsVector()) { - if (key < 0 || (size_t) key >= m_data.vec->m_size) return -1; - return key; - } - return m_data.map->indexOf(key); -} - -Variant SharedVariant::getKey(ssize_t pos) const { - assert(is(KindOfArray)); - if (getIsVector()) { - assert(pos < (ssize_t) m_data.vec->m_size); - return pos; - } - return m_data.map->getKeyIndex(pos)->toLocal(); -} - -HOT_FUNC -SharedVariant* SharedVariant::getValue(ssize_t pos) const { - assert(is(KindOfArray)); - if (getIsVector()) { - assert(pos < (ssize_t) m_data.vec->m_size); - return m_data.vec->vals()[pos]; - } - return m_data.map->getValIndex(pos); -} - -ArrayData* SharedVariant::loadElems(const SharedMap &sharedMap, - bool mapInit /* = false */) { - assert(is(KindOfArray)); - uint count = arrSize(); - bool isVector = getIsVector(); - - auto ai = - mapInit ? ArrayInit(count, ArrayInit::mapInit) : - isVector ? ArrayInit(count, ArrayInit::vectorInit) : - ArrayInit(count); - - if (isVector) { - for (uint i = 0; i < count; i++) { - ai.set(sharedMap.getValueRef(i)); - } - } else { - for (uint i = 0; i < count; i++) { - ai.add(m_data.map->getKeyIndex(i)->toLocal(), sharedMap.getValueRef(i), - true); - } - } - ArrayData* elems = ai.create(); - if (elems->isStatic()) elems = elems->copy(); - return elems; -} - -int SharedVariant::countReachable() const { - int count = 1; - if (getType() == KindOfArray) { - int size = arrSize(); - if (!getIsVector()) { - count += size; // for keys - } - for (int i = 0; i < size; i++) { - SharedVariant* p = getValue(i); - count += p->countReachable(); // for values - } - } - return count; -} - -SharedVariant *SharedVariant::Create -(CVarRef source, bool serialized, bool inner /* = false */, - bool unserializeObj /* = false*/) { - return new SharedVariant(source, serialized, inner, unserializeObj); -} - SharedVariant* SharedVariant::convertObj(CVarRef var) { if (!var.is(KindOfObject) || getObjAttempted()) { return nullptr; @@ -367,14 +282,14 @@ int32_t SharedVariant::getSpaceUsage() const { size += sizeof(VectorData) + sizeof(SharedVariant*) * m_data.vec->m_size; for (size_t i = 0; i < m_data.vec->m_size; i++) { - size += m_data.vec->vals()[i]->getSpaceUsage(); + size += m_data.vec->getValue(i)->getSpaceUsage(); } } else { ImmutableMap *map = m_data.map; size += map->getStructSize(); for (int i = 0; i < map->size(); i++) { - size += map->getKeyIndex(i)->getSpaceUsage(); - size += map->getValIndex(i)->getSpaceUsage(); + size += sizeof(int64_t); + size += map->getValue(i)->getSpaceUsage(); } } break; @@ -421,7 +336,7 @@ void SharedVariant::getStats(SharedVariantStats *stats) const { stats->dataTotalSize = sizeof(SharedVariant) + sizeof(VectorData); stats->dataTotalSize += sizeof(SharedVariant*) * m_data.vec->m_size; for (size_t i = 0; i < m_data.vec->m_size; i++) { - SharedVariant *v = m_data.vec->vals()[i]; + SharedVariant *v = m_data.vec->getValue(i); SharedVariantStats childStats; v->getStats(&childStats); stats->addChildStats(&childStats); @@ -431,9 +346,10 @@ void SharedVariant::getStats(SharedVariantStats *stats) const { stats->dataTotalSize = sizeof(SharedVariant) + map->getStructSize(); for (int i = 0; i < map->size(); i++) { SharedVariantStats childStats; - map->getKeyIndex(i)->getStats(&childStats); + // for key + childStats.dataSize = childStats.dataTotalSize = sizeof(int64_t); stats->addChildStats(&childStats); - map->getValIndex(i)->getStats(&childStats); + map->getValue(i)->getStats(&childStats); stats->addChildStats(&childStats); } } diff --git a/hphp/runtime/base/shared/shared_variant.h b/hphp/runtime/base/shared/shared_variant.h index e7e8be3d7..c24a3e96a 100644 --- a/hphp/runtime/base/shared/shared_variant.h +++ b/hphp/runtime/base/shared/shared_variant.h @@ -42,6 +42,8 @@ class SharedMap; class SharedVariantStats; +class VectorData; + /////////////////////////////////////////////////////////////////////////////// class SharedVariant { @@ -50,11 +52,6 @@ public: bool unserializeObj = false); ~SharedVariant(); - // Create will do the wrapped check before creating a SharedVariant - static SharedVariant* Create(CVarRef source, bool serialized, - bool inner = false, - bool unserializeObj = false); - bool is(DataType d) const { return m_type == d; } DataType getType() const { return (DataType)m_type; } CVarRef asCVarRef() const { @@ -86,28 +83,6 @@ public: return m_data.str->hash(); } - size_t arrSize() const { - assert(is(KindOfArray)); - if (getIsVector()) return m_data.vec->m_size; - return m_data.map->size(); - } - - size_t arrCap() const { - assert(is(KindOfArray)); - if (getIsVector()) return m_data.vec->m_size; - return m_data.map->capacity(); - } - - int getIndex(int64_t key); - int getIndex(const StringData* key); - - ArrayData* loadElems(const SharedMap &sharedMap, - bool mapInit = false); - - Variant getKey(ssize_t pos) const; - - SharedVariant* getValue(ssize_t pos) const; - // implementing LeakDetectable void dump(std::string &out); @@ -122,34 +97,7 @@ public: SharedVariant *convertObj(CVarRef var); bool isUnserializedObj() { return getIsObj(); } - int countReachable() const; - private: - class VectorData { - public: - union { - size_t m_size; - SharedVariant* m_align_dummy; - }; - - VectorData() : m_size(0) {} - - ~VectorData() { - SharedVariant** v = vals(); - for (size_t i = 0; i < m_size; i++) { - delete v[i]; - } - } - SharedVariant** vals() { return (SharedVariant**)(this + 1); } - void *operator new(size_t sz, int num) { - assert(sz == sizeof(VectorData)); - return malloc(sizeof(VectorData) + num * sizeof(SharedVariant*)); - } - void operator delete(void* ptr) { free(ptr); } - // just to keep the compiler happy; used if the constructor throws - void operator delete(void* ptr, int num) { free(ptr); } - }; - /* * Keep the object layout binary compatible with Variant for primitive types. * We want to have compile time assertion to guard it but still want to have @@ -211,7 +159,6 @@ private: void setSerializedArray() { m_flags |= SerializedArray;} void clearSerializedArray() { m_flags &= ~SerializedArray;} - bool getIsVector() const { return (bool)(m_flags & IsVector);} void setIsVector() { m_flags |= IsVector;} void clearIsVector() { m_flags &= ~IsVector;} @@ -222,6 +169,44 @@ private: bool getObjAttempted() const { return (bool)(m_flags & ObjAttempted);} void setObjAttempted() { m_flags |= ObjAttempted;} void clearObjAttempted() { m_flags &= ~ObjAttempted;} + +public: + bool getIsVector() const { return (bool)(m_flags & IsVector);} + ImmutableMap* getMap() const { return m_data.map; } +}; + +/* VectorData is used when when all the keys are integers + * in the sequential range 0 to n-1. */ +class VectorData { +public: + union { + size_t m_size; + SharedVariant* m_align_dummy; + }; + + VectorData() : m_size(0) {} + + ~VectorData() { + SharedVariant* v = vals(); + for (size_t i = 0; i < m_size; i++) { + v[i].~SharedVariant(); + } + } + SharedVariant* getValue(ssize_t pos) { + return &((SharedVariant *)(this + 1))[pos]; + } + SharedVariant* vals() { return (SharedVariant*)(this + 1); } + void *operator new(size_t sz, int num) { + assert(sz == sizeof(VectorData)); + return malloc(sizeof(VectorData) + num * sizeof(SharedVariant)); + } + void add(CVarRef val, bool unserializeObj) { + /* placement new */ + new (&vals()[m_size++]) SharedVariant(val, false, true, unserializeObj); + } + void operator delete(void* ptr) { free(ptr); } + // just to keep the compiler happy; used if the constructor throws + void operator delete(void* ptr, int num) { free(ptr); } }; class SharedVariantStats {