APCHandle* APCArray::MakeShared(ArrayData* arr, size_t& size, bool inner, bool unserializeObj) { if (!inner) { // only need to call traverseData() on the toplevel array DataWalker walker(DataWalker::LookupFeature::HasObjectOrResource); DataWalker::DataFeature features = walker.traverseData(arr); if (features.isCircular() || features.hasCollection()) { String s = apc_serialize(arr); APCHandle* handle = APCString::MakeShared(KindOfArray, s.get(), size); handle->setSerializedArray(); return handle; } if (apcExtension::UseUncounted && !features.hasObjectOrResource() && !arr->empty()) { size = getMemSize(arr) + sizeof(APCTypedValue); return APCTypedValue::MakeSharedArray(arr); } } if (arr->isVectorData()) { return APCArray::MakePackedShared(arr, size, unserializeObj); } return APCArray::MakeShared(arr, size, unserializeObj); }
bool ConcurrentTableSharedStore::constructPrime(const String& v, KeyValuePair& item, bool serialized) { if (s_apc_file_storage.getState() != APCFileStorage::StorageState::Invalid && (!v.get()->isStatic() || serialized)) { // StaticString for non-object should consume limited amount of space, // not worth going through the file storage // TODO: currently we double serialize string for uniform handling later, // hopefully the unserialize won't be called often. We could further // optimize by storing more type info. String s = apc_serialize(v); char *sAddr = s_apc_file_storage.put(s.data(), s.size()); if (sAddr) { item.sAddr = sAddr; item.sSize = serialized ? 0 - s.size() : s.size(); return false; } } auto pair = APCHandle::Create(v, serialized, APCHandleLevel::Outer, false); item.value = pair.handle; item.sSize = pair.size; return true; }
APCHandle* APCObject::MakeShared( ObjectData* objectData, size_t& size, bool inner, bool createFromSer) { if (!canOptimize(objectData)) { return createFromSer ? MakeShared(apc_serialize(objectData), size) : nullptr; } if (objectData->isCollection()) { return APCCollection::MakeShared(objectData, size, inner); } bool serialize = false; if (!inner) { PointerSet visited; visited.insert(reinterpret_cast<void*>(objectData)); traverseDataRecursive(objectData, [&](const Variant& var) { if (var.isReferenced()) { Variant *pvar = var.getRefData(); if (chekVisited(visited, pvar)) { serialize = true; return true; } } DataType type = var.getType(); if (type == KindOfObject) { auto data = var.getObjectData(); if (chekVisited(visited, reinterpret_cast<void*>(data))) { serialize = true; return true; } } else if (type == KindOfResource) { serialize = true; return true; } return false; } ); } if (serialize) { return createFromSer ? MakeShared(apc_serialize(objectData), size) : nullptr; } return Construct(objectData, size); }
bool ConcurrentTableSharedStore::constructPrime(CVarRef v, KeyValuePair& item) { if (s_apc_file_storage.getState() != SharedStoreFileStorage::StateInvalid && (IS_REFCOUNTED_TYPE(v.getType()))) { // Only do the storage for ref-counted type String s = apc_serialize(v); char *sAddr = s_apc_file_storage.put(s.data(), s.size()); if (sAddr) { item.sAddr = sAddr; item.sSize = s.size(); return false; } } item.value = SharedVariant::Create(v, false); return true; }
bool ConcurrentTableSharedStore::constructPrime(const Variant& v, KeyValuePair& item) { if (s_apc_file_storage.getState() != APCFileStorage::StorageState::Invalid && (isRefcountedType(v.getType()))) { // Only do the storage for ref-counted type String s = apc_serialize(v); char *sAddr = s_apc_file_storage.put(s.data(), s.size()); if (sAddr) { item.sAddr = sAddr; item.sSize = s.size(); return false; } } auto pair = APCHandle::Create(v, false, APCHandleLevel::Outer, false); item.value = pair.handle; item.sSize = pair.size; return true; }
APCHandle::Pair APCCollection::Make(const ObjectData* obj, APCHandleLevel level, bool unserializeObj) { auto bail = [&] { return APCString::MakeSerializedObject( apc_serialize(Variant(const_cast<ObjectData*>(obj))) ); }; auto const array = collections::asArray(obj); if (!array) return bail(); /* * Create an uncounted array if we can. * * If this collection is an OuterHandle, then we need to do a full check on * this array for things like circularity. If we're an InnerHandle, someone * already checked that, but we want to check for whether it's uncounted to * use a better representation. For the OuterHandle case, we just delegate * to APCArray below (which will do the full DataWalker pass). */ if (level == APCHandleLevel::Inner && apcExtension::UseUncounted && !array->empty()) { DataWalker walker(DataWalker::LookupFeature::HasObjectOrResource); auto const features = walker.traverseData(const_cast<ArrayData*>(array)); assert(!features.isCircular); if (!features.hasObjectOrResource) { return WrapArray( { APCArray::MakeUncountedArray(const_cast<ArrayData*>(array)), getMemSize(array) + sizeof(APCTypedValue) }, obj->collectionType() ); } } return WrapArray( APCArray::MakeSharedArray(const_cast<ArrayData*>(array), level, unserializeObj), obj->collectionType() ); }
APCHandle::Pair APCArray::MakeSharedArray(ArrayData* arr, APCHandleLevel level, bool unserializeObj) { if (level == APCHandleLevel::Outer) { // only need to call traverseData() on the toplevel array DataWalker walker(DataWalker::LookupFeature::HasObjectOrResource); DataWalker::DataFeature features = walker.traverseData(arr); if (features.isCircular) { String s = apc_serialize(Variant{arr}); return APCString::MakeSerializedArray(s.get()); } if (apcExtension::UseUncounted && !features.hasObjectOrResource && !arr->empty()) { return {MakeUncountedArray(arr), getMemSize(arr) + sizeof(APCTypedValue)}; } } return arr->isVectorData() ? MakePacked(arr, unserializeObj) : MakeHash(arr, unserializeObj); }
APCHandle::Pair APCHandle::Create(const Variant& source, bool serialized, APCHandleLevel level, bool unserializeObj) { auto type = source.getType(); // this gets rid of the ref, if it was one switch (type) { case KindOfUninit: { auto value = APCTypedValue::tvUninit(); return {value->getHandle(), sizeof(APCTypedValue)}; } case KindOfNull: { auto value = APCTypedValue::tvNull(); return {value->getHandle(), sizeof(APCTypedValue)}; } case KindOfBoolean: { auto value = source.getBoolean() ? APCTypedValue::tvTrue() : APCTypedValue::tvFalse(); return {value->getHandle(), sizeof(APCTypedValue)}; } case KindOfInt64: { auto value = new APCTypedValue(source.getInt64()); return {value->getHandle(), sizeof(APCTypedValue)}; } case KindOfDouble: { auto value = new APCTypedValue(source.getDouble()); return {value->getHandle(), sizeof(APCTypedValue)}; } case KindOfPersistentString: case KindOfString: { StringData* s = source.getStringData(); if (serialized) { // It is priming, and there might not be the right class definitions // for unserialization. return APCString::MakeSerializedObject(apc_reserialize(String{s})); } if (s->isStatic()) { auto value = new APCTypedValue(APCTypedValue::StaticStr{}, s); return APCHandle::Pair{value->getHandle(), sizeof(APCTypedValue)}; } auto const st = lookupStaticString(s); if (st) { auto value = new APCTypedValue(APCTypedValue::StaticStr{}, st); return {value->getHandle(), sizeof(APCTypedValue)}; } if (apcExtension::UseUncounted) { auto st = StringData::MakeUncounted(s->slice()); auto value = new APCTypedValue(APCTypedValue::UncountedStr{}, st); return {value->getHandle(), st->size() + sizeof(APCTypedValue)}; } return APCString::MakeSharedString(s); } case KindOfPersistentVec: case KindOfVec: { auto ad = source.getArrayData(); assert(ad->isVecArray()); if (ad->isStatic()) { auto value = new APCTypedValue(APCTypedValue::StaticVec{}, ad); return {value->getHandle(), sizeof(APCTypedValue)}; } return APCArray::MakeSharedVec(ad, level, unserializeObj); } case KindOfPersistentDict: case KindOfDict: { auto ad = source.getArrayData(); assert(ad->isDict()); if (ad->isStatic()) { auto value = new APCTypedValue(APCTypedValue::StaticDict{}, ad); return {value->getHandle(), sizeof(APCTypedValue)}; } return APCArray::MakeSharedDict(ad, level, unserializeObj); } case KindOfPersistentKeyset: case KindOfKeyset: { auto ad = source.getArrayData(); assert(ad->isKeyset()); if (ad->isStatic()) { auto value = new APCTypedValue(APCTypedValue::StaticKeyset{}, ad); return {value->getHandle(), sizeof(APCTypedValue)}; } return APCArray::MakeSharedKeyset(ad, level, unserializeObj); } case KindOfPersistentArray: case KindOfArray: { auto ad = source.getArrayData(); assert(ad->isPHPArray()); if (ad->isStatic()) { auto value = new APCTypedValue(APCTypedValue::StaticArr{}, ad); return {value->getHandle(), sizeof(APCTypedValue)}; } return APCArray::MakeSharedArray(ad, level, unserializeObj); } case KindOfObject: if (source.getObjectData()->isCollection()) { return APCCollection::Make(source.getObjectData(), level, unserializeObj); } return unserializeObj ? APCObject::Construct(source.getObjectData()) : APCString::MakeSerializedObject(apc_serialize(source)); case KindOfResource: // TODO Task #2661075: Here and elsewhere in the runtime, we convert // Resources to the empty array during various serialization operations, // which does not match Zend behavior. We should fix this. return APCArray::MakeSharedEmptyArray(); case KindOfRef: case KindOfClass: return {nullptr, 0}; } not_reached(); }
SharedVariant::SharedVariant(CVarRef source, bool serialized, bool inner /* = false */, bool unserializeObj /* = false */) : m_count (1), m_shouldCache(false), m_flags(0){ ASSERT(!serialized || source.isString()); m_type = source.getType(); switch (m_type) { case KindOfBoolean: { m_data.num = source.toBoolean(); break; } case KindOfByte: case KindOfInt16: case KindOfInt32: case KindOfInt64: { m_type = KindOfInt64; m_data.num = source.toInt64(); break; } case KindOfDouble: { m_data.dbl = source.toDouble(); break; } case KindOfStaticString: case KindOfString: { String s = source.toString(); if (serialized) { m_type = KindOfObject; // It is priming, and there might not be the right class definitions // for unserialization. s = apc_reserialize(s); } m_data.str = s->copy(true); break; } case KindOfArray: { ArrayData *arr = source.getArrayData(); if (!inner) { // only need to call hasInternalReference() on the toplevel array PointerSet seen; if (arr->hasInternalReference(seen)) { setSerializedArray(); m_shouldCache = true; String s = apc_serialize(source); m_data.str = new StringData(s.data(), s.size(), CopyString); break; } } size_t size = arr->size(); if (arr->isVectorData()) { setIsVector(); m_data.vec = new VectorData(size); uint i = 0; for (ArrayIter it(arr); !it.end(); it.next(), i++) { SharedVariant* val = Create(it.secondRef(), false, true, unserializeObj); if (val->m_shouldCache) m_shouldCache = true; m_data.vec->vals[i] = val; } } else { m_data.map = new ImmutableMap(size); for (ArrayIter it(arr); !it.end(); it.next()) { SharedVariant* key = Create(it.first(), false, true, unserializeObj); SharedVariant* val = Create(it.secondRef(), false, true, unserializeObj); if (val->m_shouldCache) m_shouldCache = true; m_data.map->add(key, val); } } break; } case KindOfNull: { m_data.num = 0; break; } default: { ASSERT(source.isObject()); m_shouldCache = true; if (unserializeObj) { // This assumes hasInternalReference(seen, true) is false ImmutableObj* obj = new ImmutableObj(source.getObjectData()); m_data.obj = obj; setIsObj(); } else { String s = apc_serialize(source); m_data.str = new StringData(s.data(), s.size(), CopyString); } break; } } }
APCHandle* APCHandle::Create(const Variant& source, size_t& size, bool serialized, bool inner /* = false */, bool unserializeObj /* = false */) { auto type = source.getType(); // this gets rid of the ref, if it was one switch (type) { case KindOfBoolean: { auto value = new APCTypedValue(type, static_cast<int64_t>(source.getBoolean())); size = sizeof(APCTypedValue); return value->getHandle(); } case KindOfInt64: { auto value = new APCTypedValue(type, source.getInt64()); size = sizeof(APCTypedValue); return value->getHandle(); } case KindOfDouble: { auto value = new APCTypedValue(type, source.getDouble()); size = sizeof(APCTypedValue); return value->getHandle(); } case KindOfUninit: case KindOfNull: { auto value = new APCTypedValue(type); size = sizeof(APCTypedValue); return value->getHandle(); } case KindOfStaticString: { if (serialized) goto StringCase; auto value = new APCTypedValue(type, source.getStringData()); size = sizeof(APCTypedValue); return value->getHandle(); } StringCase: case KindOfString: { StringData* s = source.getStringData(); if (serialized) { // It is priming, and there might not be the right class definitions // for unserialization. return APCObject::MakeShared(apc_reserialize(s), size); } auto const st = lookupStaticString(s); if (st) { APCTypedValue* value = new APCTypedValue(KindOfStaticString, st); size = sizeof(APCTypedValue); return value->getHandle(); } assert(!s->isStatic()); // would've been handled above if (!inner && apcExtension::UseUncounted) { StringData* st = StringData::MakeUncounted(s->slice()); APCTypedValue* value = new APCTypedValue(st); size = sizeof(APCTypedValue) + st->size(); return value->getHandle(); } return APCString::MakeShared(type, s, size); } case KindOfArray: return APCArray::MakeShared(source.getArrayData(), size, inner, unserializeObj); case KindOfResource: // TODO Task #2661075: Here and elsewhere in the runtime, we convert // Resources to the empty array during various serialization operations, // which does not match Zend behavior. We should fix this. size = sizeof(APCArray); return APCArray::MakeShared(); case KindOfObject: return unserializeObj ? APCObject::Construct(source.getObjectData(), size) : APCObject::MakeShared(apc_serialize(source), size); default: return nullptr; } }
ThreadSharedVariant::ThreadSharedVariant(CVarRef source, bool serialized, bool inner /* = false */) { ASSERT(!serialized || source.isString()); setOwner(); m_ref = 1; switch (source.getType()) { case KindOfBoolean: { m_type = KindOfBoolean; m_data.num = source.toBoolean(); break; } case KindOfByte: case KindOfInt16: case KindOfInt32: case KindOfInt64: { m_type = KindOfInt64; m_data.num = source.toInt64(); break; } case KindOfDouble: { m_type = KindOfDouble; m_data.dbl = source.toDouble(); break; } case KindOfStaticString: case KindOfString: { String s = source.toString(); m_type = serialized ? KindOfObject : KindOfString; if (serialized) { // It is priming, and there might not be the right class definitions // for unserialization. s = apc_reserialize(s); } m_data.str = s->copy(true); break; } case KindOfArray: { m_type = KindOfArray; ArrayData *arr = source.getArrayData(); if (!inner) { // only need to call hasInternalReference() on the toplevel array PointerSet seen; if (arr->hasInternalReference(seen)) { setSerializedArray(); setShouldCache(); String s = apc_serialize(source); m_data.str = new StringData(s.data(), s.size(), CopyString); break; } } size_t size = arr->size(); if (arr->isVectorData()) { setIsVector(); m_data.vec = new VectorData(size); uint i = 0; for (ArrayIter it(arr); !it.end(); it.next(), i++) { ThreadSharedVariant* val = createAnother(it.second(), false, true); if (val->shouldCache()) setShouldCache(); m_data.vec->vals[i] = val; } } else { m_data.map = new ImmutableMap(size); uint i = 0; for (ArrayIter it(arr); !it.end(); it.next(), i++) { ThreadSharedVariant* key = createAnother(it.first(), false); ThreadSharedVariant* val = createAnother(it.second(), false, true); if (val->shouldCache()) setShouldCache(); m_data.map->add(key, val); } } break; } default: { m_type = KindOfObject; setShouldCache(); String s = apc_serialize(source); m_data.str = new StringData(s.data(), s.size(), CopyString); break; } } }
SharedVariant::SharedVariant(CVarRef source, bool serialized, bool inner /* = false */, bool unserializeObj /* = false */) : m_shouldCache(false), m_flags(0) { assert(!serialized || source.isString()); m_count = 1; m_type = source.getType(); switch (m_type) { case KindOfBoolean: { m_data.num = source.toBoolean(); break; } case KindOfInt64: { m_type = KindOfInt64; m_data.num = source.toInt64(); break; } case KindOfDouble: { m_data.dbl = source.toDouble(); break; } case KindOfStaticString: { if (serialized) goto StringCase; m_data.str = source.getStringData(); break; } StringCase: case KindOfString: { String s = source.toString(); if (serialized) { m_type = KindOfObject; // It is priming, and there might not be the right class definitions // for unserialization. s = apc_reserialize(s); } StringData* st = StringData::LookupStaticString(s.get()); if (st) { m_data.str = st; m_type = KindOfStaticString; break; } m_data.str = s->copy(true); break; } case KindOfArray: { ArrayData *arr = source.getArrayData(); if (!inner) { // only need to call hasInternalReference() on the toplevel array PointerSet seen; if (arr->hasInternalReference(seen)) { setSerializedArray(); m_shouldCache = true; String s = apc_serialize(source); m_data.str = StringData::MakeMalloced(s.data(), s.size()); break; } } if (arr->isVectorData()) { setIsVector(); m_data.vec = new (arr->size()) VectorData(); for (ArrayIter it(arr); !it.end(); it.next()) { SharedVariant* val = Create(it.secondRef(), false, true, unserializeObj); if (val->m_shouldCache) m_shouldCache = true; m_data.vec->vals()[m_data.vec->m_size++] = val; } } else { m_data.map = ImmutableMap::Create(arr, unserializeObj, m_shouldCache); } break; } case KindOfUninit: case KindOfNull: { break; } case KindOfResource: { // TODO Task #2661075: Here and elsewhere in the runtime, we convert // Resources to the empty array during various serialization operations, // which does not match Zend behavior. We should fix this. m_type = KindOfArray; setIsVector(); m_data.vec = new (0) VectorData(); break; } default: { assert(source.isObject()); m_shouldCache = true; if (unserializeObj) { // This assumes hasInternalReference(seen, true) is false ImmutableObj* obj = new ImmutableObj(source.getObjectData()); m_data.obj = obj; setIsObj(); } else { String s = apc_serialize(source); m_data.str = StringData::MakeMalloced(s.data(), s.size()); } break; } } assert(m_type != KindOfResource); }