From 446c6bc3bd65be3de6e80cb337e7f1435d1bc42f Mon Sep 17 00:00:00 2001 From: Jordan DeLong Date: Wed, 19 Mar 2014 20:32:08 -0700 Subject: [PATCH] Add an array kind for the static empty array The hope here is to reduce packed -> mixed transitions so we can optimized packed better. I measured that many of the CopyPacked calls are happening on the static empty array, and many packedToMixed calls occur on arrays where m_size == 0, but I haven't measured yet how many of the latter actually occured immediately after a CopyPacked. (Also, it appeared that MakeReserve is still dynamically a lot more frequent than MakePacked or MakeStruct, so there is probably more to the story.) Anyway a side benefit here is that we can also do more efficient creation of the initial arrays because we know the new size statically. Reviewed By: @dariorussi Differential Revision: D1235798 --- hphp/runtime/base/apc-array.cpp | 7 +- hphp/runtime/base/apc-typed-value.h | 4 +- hphp/runtime/base/array-data.cpp | 116 +++++- hphp/runtime/base/array-data.h | 1 + hphp/runtime/base/empty-array.cpp | 450 +++++++++++++++++++++++ hphp/runtime/base/empty-array.h | 103 ++++++ hphp/runtime/base/hphp-array-defs.h | 54 ++- hphp/runtime/base/hphp-array.cpp | 112 +----- hphp/runtime/base/hphp-array.h | 26 +- hphp/runtime/ext/ext_array.cpp | 1 + hphp/test/slow/array/empty_array_001.php | 10 + hphp/test/slow/array/empty_array_001.php.expect | 4 + hphp/test/slow/array/empty_array_002.php | 9 + hphp/test/slow/array/empty_array_002.php.expect | 4 + hphp/test/slow/array/empty_array_003.php | 9 + hphp/test/slow/array/empty_array_003.php.expect | 4 + hphp/test/slow/array/empty_array_004.php | 9 + hphp/test/slow/array/empty_array_004.php.expect | 4 + hphp/test/slow/array/empty_array_005.php | 9 + hphp/test/slow/array/empty_array_005.php.expect | 7 + hphp/test/slow/array/empty_array_006.php | 9 + hphp/test/slow/array/empty_array_006.php.expect | 7 + hphp/test/slow/array/empty_array_007.php | 9 + hphp/test/slow/array/empty_array_007.php.expect | 7 + hphp/test/slow/array/empty_array_008.php | 9 + hphp/test/slow/array/empty_array_008.php.expect | 7 + hphp/test/slow/array/empty_array_009.php | 8 + hphp/test/slow/array/empty_array_009.php.expect | 8 + hphp/test/slow/array/empty_array_010.php | 25 ++ hphp/test/slow/array/empty_array_010.php.expectf | 30 ++ 30 files changed, 934 insertions(+), 128 deletions(-) create mode 100644 hphp/runtime/base/empty-array.cpp create mode 100644 hphp/runtime/base/empty-array.h create mode 100644 hphp/test/slow/array/empty_array_001.php create mode 100644 hphp/test/slow/array/empty_array_001.php.expect create mode 100644 hphp/test/slow/array/empty_array_002.php create mode 100644 hphp/test/slow/array/empty_array_002.php.expect create mode 100644 hphp/test/slow/array/empty_array_003.php create mode 100644 hphp/test/slow/array/empty_array_003.php.expect create mode 100644 hphp/test/slow/array/empty_array_004.php create mode 100644 hphp/test/slow/array/empty_array_004.php.expect create mode 100644 hphp/test/slow/array/empty_array_005.php create mode 100644 hphp/test/slow/array/empty_array_005.php.expect create mode 100644 hphp/test/slow/array/empty_array_006.php create mode 100644 hphp/test/slow/array/empty_array_006.php.expect create mode 100644 hphp/test/slow/array/empty_array_007.php create mode 100644 hphp/test/slow/array/empty_array_007.php.expect create mode 100644 hphp/test/slow/array/empty_array_008.php create mode 100644 hphp/test/slow/array/empty_array_008.php.expect create mode 100644 hphp/test/slow/array/empty_array_009.php create mode 100644 hphp/test/slow/array/empty_array_009.php.expect create mode 100644 hphp/test/slow/array/empty_array_010.php create mode 100644 hphp/test/slow/array/empty_array_010.php.expectf diff --git a/hphp/runtime/base/apc-array.cpp b/hphp/runtime/base/apc-array.cpp index 50c2c82a751..f3f040923a7 100644 --- a/hphp/runtime/base/apc-array.cpp +++ b/hphp/runtime/base/apc-array.cpp @@ -43,8 +43,11 @@ APCHandle* APCArray::MakeShared(ArrayData* arr, handle->setSerializedArray(); handle->mustCache(); return handle; - } else if (apcExtension::UseUncounted && - !features.hasObjectOrResource()) { + } + + if (apcExtension::UseUncounted && + !features.hasObjectOrResource() && + !arr->empty()) { return APCTypedValue::MakeSharedArray(arr); } } diff --git a/hphp/runtime/base/apc-typed-value.h b/hphp/runtime/base/apc-typed-value.h index b03f2014a05..27bae6b6814 100644 --- a/hphp/runtime/base/apc-typed-value.h +++ b/hphp/runtime/base/apc-typed-value.h @@ -89,13 +89,13 @@ public: return m_data.dbl; } - StringData *getStringData() const { + StringData* getStringData() const { assert(m_handle.is(KindOfStaticString) || (m_handle.getUncounted() && m_handle.is(KindOfString))); return m_data.str; } - ArrayData *getArrayData() const { + ArrayData* getArrayData() const { assert(m_handle.getUncounted() && m_handle.is(KindOfArray)); return m_data.arr; } diff --git a/hphp/runtime/base/array-data.cpp b/hphp/runtime/base/array-data.cpp index e641265ede8..699987ebfbc 100644 --- a/hphp/runtime/base/array-data.cpp +++ b/hphp/runtime/base/array-data.cpp @@ -15,12 +15,14 @@ */ #include "hphp/runtime/base/array-data.h" +#include +#include #include #include -#include #include "hphp/util/exception.h" #include "hphp/runtime/base/array-init.h" +#include "hphp/runtime/base/empty-array.h" #include "hphp/runtime/base/array-iterator.h" #include "hphp/runtime/base/type-conversions.h" #include "hphp/runtime/base/builtin-functions.h" @@ -33,6 +35,7 @@ #include "hphp/runtime/vm/name-value-table-wrapper.h" #include "hphp/runtime/base/proxy-array.h" #include "hphp/runtime/base/thread-info.h" +#include "hphp/runtime/base/hphp-array.h" namespace HPHP { /////////////////////////////////////////////////////////////////////////////// @@ -46,11 +49,13 @@ typedef tbb::concurrent_hash_map ArrayDataMap; static ArrayDataMap s_arrayDataMap; ArrayData* ArrayData::GetScalarArray(ArrayData* arr) { + if (arr->empty()) return HphpArray::GetStaticEmptyArray(); auto key = f_serialize(arr).toCppString(); return GetScalarArray(arr, key); } -ArrayData *ArrayData::GetScalarArray(ArrayData *arr, const std::string& key) { +ArrayData* ArrayData::GetScalarArray(ArrayData* arr, const std::string& key) { + if (arr->empty()) return HphpArray::GetStaticEmptyArray(); assert(key == f_serialize(arr).toCppString()); ArrayDataMap::accessor acc; if (s_arrayDataMap.insert(acc, key)) { @@ -74,263 +79,356 @@ extern const ArrayFunctions g_array_funcs = { // release { &HphpArray::ReleasePacked, &HphpArray::Release, &APCLocalArray::Release, + &EmptyArray::Release, &NameValueTableWrapper::Release, &ProxyArray::Release }, // nvGetInt { &HphpArray::NvGetIntPacked, &HphpArray::NvGetInt, &APCLocalArray::NvGetInt, + reinterpret_cast( + &EmptyArray::ReturnNull + ), &NameValueTableWrapper::NvGetInt, &ProxyArray::NvGetInt }, // nvGetStr { &HphpArray::NvGetStrPacked, &HphpArray::NvGetStr, &APCLocalArray::NvGetStr, + reinterpret_cast( + &EmptyArray::ReturnNull + ), &NameValueTableWrapper::NvGetStr, &ProxyArray::NvGetStr }, // nvGetKey { &HphpArray::NvGetKeyPacked, &HphpArray::NvGetKey, &APCLocalArray::NvGetKey, + &EmptyArray::NvGetKey, &NameValueTableWrapper::NvGetKey, &ProxyArray::NvGetKey }, // setInt { &HphpArray::SetIntPacked, &HphpArray::SetInt, &APCLocalArray::SetInt, + &EmptyArray::SetInt, &NameValueTableWrapper::SetInt, &ProxyArray::SetInt }, // setStr { &HphpArray::SetStrPacked, &HphpArray::SetStr, &APCLocalArray::SetStr, + &EmptyArray::SetStr, &NameValueTableWrapper::SetStr, &ProxyArray::SetStr }, // vsize { &VsizeNop, &VsizeNop, &VsizeNop, + &VsizeNop, &NameValueTableWrapper::Vsize, &ProxyArray::Vsize }, // getValueRef { &HphpArray::GetValueRef, &HphpArray::GetValueRef, &APCLocalArray::GetValueRef, + &EmptyArray::GetValueRef, &NameValueTableWrapper::GetValueRef, &ProxyArray::GetValueRef }, // noCopyOnWrite { false, false, false, + false, true, // NameValueTableWrapper doesn't support COW. false }, // isVectorData - { &HphpArray::IsVectorDataPacked, &HphpArray::IsVectorData, + { + reinterpret_cast( + // TODO(#3983912): move shared helpers to consolidated location + &EmptyArray::ReturnTrue + ), + &HphpArray::IsVectorData, &APCLocalArray::IsVectorData, + reinterpret_cast( + &EmptyArray::ReturnTrue + ), &NameValueTableWrapper::IsVectorData, &ProxyArray::IsVectorData }, // existsInt { &HphpArray::ExistsIntPacked, &HphpArray::ExistsInt, &APCLocalArray::ExistsInt, + reinterpret_cast( + &EmptyArray::ReturnFalse + ), &NameValueTableWrapper::ExistsInt, &ProxyArray::ExistsInt }, // existsStr - { &HphpArray::ExistsStrPacked, &HphpArray::ExistsStr, + { + reinterpret_cast( + // TODO(#3983912): move shared helpers to consolidated location + &EmptyArray::ReturnFalse + ), + &HphpArray::ExistsStr, &APCLocalArray::ExistsStr, + reinterpret_cast( + &EmptyArray::ReturnFalse + ), &NameValueTableWrapper::ExistsStr, &ProxyArray::ExistsStr }, // lvalInt { &HphpArray::LvalIntPacked, &HphpArray::LvalInt, &APCLocalArray::LvalInt, + &EmptyArray::LvalInt, &NameValueTableWrapper::LvalInt, &ProxyArray::LvalInt }, // lvalStr { &HphpArray::LvalStrPacked, &HphpArray::LvalStr, &APCLocalArray::LvalStr, + &EmptyArray::LvalStr, &NameValueTableWrapper::LvalStr, &ProxyArray::LvalStr }, // lvalNew { &HphpArray::LvalNewPacked, &HphpArray::LvalNew, &APCLocalArray::LvalNew, + &EmptyArray::LvalNew, &NameValueTableWrapper::LvalNew, &ProxyArray::LvalNew }, // setRefInt { &HphpArray::SetRefIntPacked, &HphpArray::SetRefInt, &APCLocalArray::SetRefInt, + &EmptyArray::SetRefInt, &NameValueTableWrapper::SetRefInt, &ProxyArray::SetRefInt }, // setRefStr { &HphpArray::SetRefStrPacked, &HphpArray::SetRefStr, &APCLocalArray::SetRefStr, + &EmptyArray::SetRefStr, &NameValueTableWrapper::SetRefStr, &ProxyArray::SetRefStr }, // addInt { &HphpArray::AddIntPacked, &HphpArray::AddInt, &APCLocalArray::SetInt, // reuse set + &EmptyArray::SetInt, // reuse set &NameValueTableWrapper::SetInt, // reuse set &ProxyArray::SetInt }, // reuse set // addStr { &HphpArray::SetStrPacked, // reuse set &HphpArray::AddStr, &APCLocalArray::SetStr, // reuse set + &EmptyArray::SetStr, // reuse set &NameValueTableWrapper::SetStr, // reuse set &ProxyArray::SetStr }, // reuse set // removeInt { &HphpArray::RemoveIntPacked, &HphpArray::RemoveInt, &APCLocalArray::RemoveInt, + reinterpret_cast( + &EmptyArray::ReturnFirstArg + ), &NameValueTableWrapper::RemoveInt, &ProxyArray::RemoveInt }, // removeStr { &HphpArray::RemoveStrPacked, &HphpArray::RemoveStr, &APCLocalArray::RemoveStr, + reinterpret_cast( + &EmptyArray::ReturnFirstArg + ), &NameValueTableWrapper::RemoveStr, &ProxyArray::RemoveStr }, // iterBegin { &HphpArray::IterBegin, &HphpArray::IterBegin, &APCLocalArray::IterBegin, + &EmptyArray::ReturnInvalidIndex, &NameValueTableWrapper::IterBegin, &ProxyArray::IterBegin }, // iterEnd { &HphpArray::IterEnd, &HphpArray::IterEnd, &APCLocalArray::IterEnd, + &EmptyArray::ReturnInvalidIndex, &NameValueTableWrapper::IterEnd, &ProxyArray::IterEnd }, // iterAdvance { &HphpArray::IterAdvance, &HphpArray::IterAdvance, &APCLocalArray::IterAdvance, + &EmptyArray::IterAdvance, &NameValueTableWrapper::IterAdvance, &ProxyArray::IterAdvance }, // iterRewind { &HphpArray::IterRewind, &HphpArray::IterRewind, &APCLocalArray::IterRewind, + &EmptyArray::IterRewind, &NameValueTableWrapper::IterRewind, &ProxyArray::IterRewind }, // validMArrayIter { &HphpArray::ValidMArrayIter, &HphpArray::ValidMArrayIter, &APCLocalArray::ValidMArrayIter, + &EmptyArray::ValidMArrayIter, &NameValueTableWrapper::ValidMArrayIter, &ProxyArray::ValidMArrayIter }, // advanceMArrayIter { &HphpArray::AdvanceMArrayIter, &HphpArray::AdvanceMArrayIter, &APCLocalArray::AdvanceMArrayIter, + &EmptyArray::AdvanceMArrayIter, &NameValueTableWrapper::AdvanceMArrayIter, &ProxyArray::AdvanceMArrayIter }, // escalateForSort { &HphpArray::EscalateForSort, &HphpArray::EscalateForSort, &APCLocalArray::EscalateForSort, + reinterpret_cast( + &EmptyArray::ReturnFirstArg + ), &NameValueTableWrapper::EscalateForSort, &ProxyArray::EscalateForSort }, // ksort { &HphpArray::Ksort, &HphpArray::Ksort, &ArrayData::Ksort, + reinterpret_cast( + &EmptyArray::NoOp + ), &NameValueTableWrapper::Ksort, &ProxyArray::Ksort }, // sort { &HphpArray::Sort, &HphpArray::Sort, &ArrayData::Sort, + reinterpret_cast( + &EmptyArray::NoOp + ), &NameValueTableWrapper::Sort, &ProxyArray::Sort }, // asort { &HphpArray::Asort, &HphpArray::Asort, &ArrayData::Asort, + reinterpret_cast( + &EmptyArray::NoOp + ), &NameValueTableWrapper::Asort, &ProxyArray::Asort }, // uksort { &HphpArray::Uksort, &HphpArray::Uksort, &ArrayData::Uksort, + reinterpret_cast( + &EmptyArray::ReturnTrue + ), &NameValueTableWrapper::Uksort, &ProxyArray::Uksort }, // usort { &HphpArray::Usort, &HphpArray::Usort, &ArrayData::Usort, + reinterpret_cast( + &EmptyArray::ReturnTrue + ), &NameValueTableWrapper::Usort, &ProxyArray::Usort }, // uasort { &HphpArray::Uasort, &HphpArray::Uasort, &ArrayData::Uasort, + reinterpret_cast( + &EmptyArray::ReturnTrue + ), &NameValueTableWrapper::Uasort, &ProxyArray::Uasort }, // copy { &HphpArray::CopyPacked, &HphpArray::Copy, &APCLocalArray::Copy, + &EmptyArray::Copy, &NameValueTableWrapper::Copy, &ProxyArray::Copy }, // copyWithStrongIterators { &HphpArray::CopyWithStrongIterators, &HphpArray::CopyWithStrongIterators, &APCLocalArray::CopyWithStrongIterators, + &EmptyArray::CopyWithStrongIterators, &NameValueTableWrapper::CopyWithStrongIterators, &ProxyArray::CopyWithStrongIterators }, // nonSmartCopy { &HphpArray::NonSmartCopy, &HphpArray::NonSmartCopy, &ArrayData::NonSmartCopy, - &ArrayData::NonSmartCopy, + &EmptyArray::NonSmartCopy, &ProxyArray::NonSmartCopy }, // append { &HphpArray::AppendPacked, &HphpArray::Append, &APCLocalArray::Append, + &EmptyArray::Append, &NameValueTableWrapper::Append, &ProxyArray::Append }, // appendRef { &HphpArray::AppendRefPacked, &HphpArray::AppendRef, &APCLocalArray::AppendRef, + &EmptyArray::AppendRef, &NameValueTableWrapper::AppendRef, &ProxyArray::AppendRef }, // appendWithRef { &HphpArray::AppendWithRefPacked, &HphpArray::AppendWithRef, &APCLocalArray::AppendRef, + &EmptyArray::AppendWithRef, &NameValueTableWrapper::AppendRef, &ProxyArray::AppendRef }, // plusEq { &HphpArray::PlusEq, &HphpArray::PlusEq, &APCLocalArray::PlusEq, + &EmptyArray::PlusEq, &NameValueTableWrapper::PlusEq, &ProxyArray::PlusEq }, // merge { &HphpArray::Merge, &HphpArray::Merge, &APCLocalArray::Merge, + &EmptyArray::Merge, &NameValueTableWrapper::Merge, &ProxyArray::Merge }, // pop { &HphpArray::PopPacked, &HphpArray::Pop, &APCLocalArray::Pop, + &EmptyArray::PopOrDequeue, &NameValueTableWrapper::Pop, &ProxyArray::Pop }, // dequeue { &HphpArray::DequeuePacked, &HphpArray::Dequeue, &APCLocalArray::Dequeue, + &EmptyArray::PopOrDequeue, &NameValueTableWrapper::Dequeue, &ProxyArray::Dequeue }, // prepend { &HphpArray::PrependPacked, &HphpArray::Prepend, &APCLocalArray::Prepend, + &EmptyArray::Prepend, &NameValueTableWrapper::Prepend, &ProxyArray::Prepend }, // renumber { &HphpArray::RenumberPacked, &HphpArray::Renumber, &APCLocalArray::Renumber, + reinterpret_cast( + &EmptyArray::NoOp + ), &NameValueTableWrapper::Renumber, &ProxyArray::Renumber }, // onSetEvalScalar { &HphpArray::OnSetEvalScalarPacked, &HphpArray::OnSetEvalScalar, &APCLocalArray::OnSetEvalScalar, + &EmptyArray::OnSetEvalScalar, &NameValueTableWrapper::OnSetEvalScalar, &ProxyArray::OnSetEvalScalar }, // escalate { &ArrayData::Escalate, &ArrayData::Escalate, &APCLocalArray::Escalate, &ArrayData::Escalate, + &ArrayData::Escalate, &ProxyArray::Escalate }, // getAPCHandle { &ArrayData::GetAPCHandle, &ArrayData::GetAPCHandle, &APCLocalArray::GetAPCHandle, + reinterpret_cast( + &EmptyArray::ReturnNull + ), &ArrayData::GetAPCHandle, &ProxyArray::GetAPCHandle }, // zSetInt { &HphpArray::ZSetInt, &HphpArray::ZSetInt, &ArrayData::ZSetInt, &ArrayData::ZSetInt, + &ArrayData::ZSetInt, &ProxyArray::ZSetInt }, // zSetStr { &HphpArray::ZSetStr, &HphpArray::ZSetStr, &ArrayData::ZSetStr, &ArrayData::ZSetStr, + &ArrayData::ZSetStr, &ProxyArray::ZSetStr }, // zAppend { &HphpArray::ZAppend, &HphpArray::ZAppend, &ArrayData::ZAppend, &ArrayData::ZAppend, + &ArrayData::ZAppend, &ProxyArray::ZAppend }, }; @@ -661,17 +759,21 @@ void ArrayData::OnSetEvalScalar(ArrayData*) { assert(false); } +// TODO(#3983912): combine with EmptyArray::ReturnFirstArg ArrayData* ArrayData::Escalate(const ArrayData* ad) { return const_cast(ad); } const char* ArrayData::kindToString(ArrayKind kind) { - const char* names[] = { + std::array names = {{ "PackedKind", "MixedKind", "SharedKind", + "EmptyKind", "NvtwKind", - }; + "ProxyKind", + }}; + static_assert(names.size() == kNumKinds, "add new kinds here"); return names[kind]; } diff --git a/hphp/runtime/base/array-data.h b/hphp/runtime/base/array-data.h index 0a83b140918..b463009f88d 100644 --- a/hphp/runtime/base/array-data.h +++ b/hphp/runtime/base/array-data.h @@ -44,6 +44,7 @@ struct ArrayData { kPackedKind, // HphpArray with keys in range [0..size) kMixedKind, // HphpArray arbitrary int or string keys, maybe holes kSharedKind, // SharedArray + kEmptyKind, // The singleton static empty array kNvtwKind, // NameValueTableWrapper kProxyKind, // ProxyArray kNumKinds // insert new values before kNumKinds. diff --git a/hphp/runtime/base/empty-array.cpp b/hphp/runtime/base/empty-array.cpp new file mode 100644 index 00000000000..386a7db5915 --- /dev/null +++ b/hphp/runtime/base/empty-array.cpp @@ -0,0 +1,450 @@ +/* + +----------------------------------------------------------------------+ + | HipHop for PHP | + +----------------------------------------------------------------------+ + | Copyright (c) 2010-2013 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 "hphp/runtime/base/empty-array.h" + +#include +#include + +#include "hphp/util/assertions.h" + +#include "hphp/runtime/base/array-init.h" +#include "hphp/runtime/base/tv-helpers.h" +#include "hphp/runtime/base/array-data.h" +#include "hphp/runtime/base/type-variant.h" +#include "hphp/runtime/base/hphp-array.h" +#include "hphp/runtime/base/hphp-array-defs.h" + +namespace HPHP { + +////////////////////////////////////////////////////////////////////// + +std::aligned_storage< + sizeof(ArrayData), + alignof(ArrayData) +>::type s_theEmptyArray; + +struct EmptyArray::Initializer { + Initializer() { + void* vpEmpty = &s_theEmptyArray; + + auto const ad = static_cast(vpEmpty); + ad->m_kind = ArrayData::kEmptyKind; + ad->m_size = 0; + ad->m_pos = ArrayData::invalid_index; + ad->m_count = 0; + ad->setStatic(); + } +}; +EmptyArray::Initializer EmptyArray::s_initializer; + +////////////////////////////////////////////////////////////////////// + +void EmptyArray::Release(ArrayData*) { + always_assert(!"never try to free the empty array"); +} + +/* + * Used for NvGetInt, NvGetStr. (We never contain the string or int.) + * + * Used for GetAPCHandle (we don't have one). + */ +void* EmptyArray::ReturnNull(...) { + return nullptr; +} + +/* + * Used for ExistsInt, ExistsStr. (We never contain the int or string.) + */ +bool EmptyArray::ReturnFalse(...) { + return false; +} + +/* + * Used for IsVectorData (we're always trivially a vector). + * + * Used for Uksort, Usort, Uasort. These functions return false only + * if the user compare function modified they array, which it can't + * here because we don't call it. + */ +bool EmptyArray::ReturnTrue(...) { + return true; +} + +void EmptyArray::NvGetKey(const ArrayData*, TypedValue* out, ssize_t pos) { + // We have no valid positions---no one should call this function. + not_reached(); +} + +const Variant& EmptyArray::GetValueRef(const ArrayData* ad, ssize_t pos) { + // We have no valid positions---no one should call this function. + not_reached(); +} + +/* + * Used for RemoveInt, RemoveStr. We don't every have the int or str, + * so even if copy is true we can just return the same array. + * + * Used for EscalateForSort---we are already sorted by any imaginable + * method of sorting, so the sort functions are no-ops, so we don't + * need to copy. + * + * (TODO: verify nothing assumes that we /must/ copy when copy is + * true.) + */ +ArrayData* EmptyArray::ReturnFirstArg(ArrayData* a, ...) { + return a; +} + +/* + * Used for IterBegin and IterEnd. We always return the invalid_index. + */ +ssize_t EmptyArray::ReturnInvalidIndex(const ArrayData*) { + return ArrayData::invalid_index; +} + +// Iterators can't be advanced or rewinded, because we have no valid +// iterators. +ssize_t EmptyArray::IterAdvance(const ArrayData*, ssize_t prev) { + not_reached(); +} +ssize_t EmptyArray::IterRewind(const ArrayData*, ssize_t prev) { + not_reached(); +} + +// Strong iterating the empty array doesn't give back any elements. +bool EmptyArray::ValidMArrayIter(const ArrayData*, const MArrayIter& fp) { + return false; +} +bool EmptyArray::AdvanceMArrayIter(ArrayData*, MArrayIter& fp) { + not_reached(); +} + +/* + * Don't do anything. + * + * Used for Ksort, Sort, and Asort. The empty array is already + * sorted, and these functions have no other side-effects. + * + * Used for Renumber---we're trivially numbered properly. + */ +void EmptyArray::NoOp(...) {} + +// We're always already a static array. +void EmptyArray::OnSetEvalScalar(ArrayData*) { not_reached(); } +ArrayData* EmptyArray::NonSmartCopy(const ArrayData* ad) { not_reached(); } + +////////////////////////////////////////////////////////////////////// + +NEVER_INLINE +ArrayData* EmptyArray::Copy(const ArrayData*) { + auto const mask = HphpArray::SmallMask; // 3 + auto const cap = HphpArray::computeMaxElms(mask); // 3 + auto const ad = smartAllocArray(cap, mask); + + ad->m_kindAndSize = ArrayData::kPackedKind; + ad->m_posAndCount = static_cast(ArrayData::invalid_index); + ad->m_capAndUsed = cap; + ad->m_tableMask = mask; + + assert(ad->m_kind == ArrayData::kPackedKind); + assert(ad->m_size == 0); + assert(ad->m_pos == ArrayData::invalid_index); + assert(ad->m_count == 0); + assert(ad->m_cap == cap); + assert(ad->m_used == 0); + assert(ad->checkInvariants()); + return ad; +} + +ArrayData* EmptyArray::CopyWithStrongIterators(const ArrayData* ad) { + // We can never have strong iterators, so we don't need to do + // anything extra. + return Copy(ad); +} + +////////////////////////////////////////////////////////////////////// + +/* + * Note: if you try to tail-call these helper routines, gcc will + * unfortunately still generate functions with frames and and makes a + * call instead of a jump. It's because of std::pair (and is still + * the case if you return a custom struct). + * + * For now we're leaving this, because it's essentially free for these + * routines to leave the lval pointer in the second return register, + * and it seems questionable to clone the whole function just to avoid + * the frame creation in these callers. (It works to reinterpret_cast + * these functions to one that returns ArrayData* instead of a pair in + * the cases we don't need the second value, but this seems a tad too + * sketchy for probably-unmeasurable benefits. I'll admit I didn't + * try to measure it though... ;) + */ + +/* + * Helper for empty array -> packed transitions. Creates an array + * with one element. The element is transfered into the array (should + * already be incref'd). + */ +ALWAYS_INLINE +std::pair EmptyArray::MakePackedInl(TypedValue tv) { + auto const mask = HphpArray::SmallMask; // 3 + auto const cap = HphpArray::computeMaxElms(mask); // 3 + auto const ad = smartAllocArray(cap, mask); + + ad->m_kindAndSize = uint64_t{1} << 32 | ArrayData::kPackedKind; + ad->m_posAndCount = 0; + ad->m_capAndUsed = uint64_t{1} << 32 | cap; + ad->m_tableMask = mask; + + auto& lval = reinterpret_cast(ad + 1)[0].data; + lval.m_data = tv.m_data; + lval.m_type = tv.m_type; + + assert(ad->m_kind == ArrayData::kPackedKind); + assert(ad->m_size == 1); + assert(ad->m_pos == 0); + assert(ad->m_count == 0); + assert(ad->m_cap == cap); + assert(ad->m_used == 1); + assert(ad->checkInvariants()); + return { ad, &lval }; +} + +NEVER_INLINE +std::pair EmptyArray::MakePacked(TypedValue tv) { + return MakePackedInl(tv); +} + +/* + * Helper for creating a single-element mixed array with a string key. + * + * Note: the key is not already incref'd, but the value must be. + */ +NEVER_INLINE +std::pair +EmptyArray::MakeMixed(StringData* key, TypedValue val) { + auto const mask = HphpArray::SmallMask; // 3 + auto const cap = HphpArray::computeMaxElms(mask); // 3 + auto const ad = smartAllocArray(cap, mask); + + ad->m_kindAndSize = uint64_t{1} << 32 | ArrayData::kMixedKind; + ad->m_posAndCount = 0; + ad->m_capAndUsed = uint64_t{1} << 32 | cap; + ad->m_tableMask = mask; + ad->m_nextKI = 0; + ad->m_hLoad = 1; + + auto const data = reinterpret_cast(ad + 1); + auto const hash = reinterpret_cast(data + cap); + + assert(mask + 1 == 4); + auto const emptyVal = int64_t{HphpArray::Empty}; + reinterpret_cast(hash)[0] = emptyVal; + reinterpret_cast(hash)[1] = emptyVal; + + auto const khash = key->hash(); + hash[khash & mask] = 0; + data[0].setStrKey(key, khash); + + auto& lval = data[0].data; + lval.m_data = val.m_data; + lval.m_type = val.m_type; + + assert(ad->m_kind == ArrayData::kMixedKind); + assert(ad->m_size == 1); + assert(ad->m_pos == 0); + assert(ad->m_count == 0); + assert(ad->m_cap == cap); + assert(ad->m_used == 1); + assert(ad->checkInvariants()); + return { ad, &lval }; +} + +/* + * Creating a single-element mixed array with a integer key. The + * value is already incref'd. + */ +std::pair +EmptyArray::MakeMixed(int64_t key, TypedValue val) { + auto const mask = HphpArray::SmallMask; // 3 + auto const cap = HphpArray::computeMaxElms(mask); // 3 + auto const ad = smartAllocArray(cap, mask); + + ad->m_kindAndSize = uint64_t{1} << 32 | ArrayData::kMixedKind; + ad->m_posAndCount = 0; + ad->m_capAndUsed = uint64_t{1} << 32 | cap; + ad->m_tableMask = mask; + ad->m_nextKI = key + 1; + ad->m_hLoad = 1; + + auto const data = reinterpret_cast(ad + 1); + auto const hash = reinterpret_cast(data + cap); + + assert(mask + 1 == 4); + auto const emptyVal = int64_t{HphpArray::Empty}; + reinterpret_cast(hash)[0] = emptyVal; + reinterpret_cast(hash)[1] = emptyVal; + + hash[key & mask] = 0; + data[0].setIntKey(key); + + auto& lval = data[0].data; + lval.m_data = val.m_data; + lval.m_type = val.m_type; + + assert(ad->m_kind == ArrayData::kMixedKind); + assert(ad->m_size == 1); + assert(ad->m_pos == 0); + assert(ad->m_count == 0); + assert(ad->m_cap == cap); + assert(ad->m_used == 1); + assert(ad->checkInvariants()); + return { ad, &lval }; +} + +////////////////////////////////////////////////////////////////////// + +ArrayData* EmptyArray::SetInt(ArrayData*, int64_t k, const Variant& v, bool) { + auto c = *v.asCell(); + tvRefcountedIncRef(&c); + auto const ret = k == 0 ? EmptyArray::MakePacked(c) + : EmptyArray::MakeMixed(k, c); + return ret.first; +} + +ArrayData* EmptyArray::SetStr(ArrayData*, + StringData* k, + const Variant& v, + bool copy) { + auto val = *v.asCell(); + tvRefcountedIncRef(&val); + return EmptyArray::MakeMixed(k, val).first; +} + +ArrayData* EmptyArray::LvalInt(ArrayData*, int64_t k, Variant*& retVar, bool) { + auto const ret = k == 0 ? EmptyArray::MakePacked(make_tv()) + : EmptyArray::MakeMixed(k, make_tv()); + retVar = &tvAsVariant(ret.second); + return ret.first; +} + +ArrayData* EmptyArray::LvalStr(ArrayData*, + StringData* k, + Variant*& retVar, + bool) { + auto const ret = EmptyArray::MakeMixed(k, make_tv()); + retVar = &tvAsVariant(ret.second); + return ret.first; +} + +ArrayData* EmptyArray::LvalNew(ArrayData*, Variant*& retVar, bool) { + auto const ret = EmptyArray::MakePacked(make_tv()); + retVar = &tvAsVariant(ret.second); + return ret.first; +} + +ArrayData* EmptyArray::SetRefInt(ArrayData*, + int64_t k, + const Variant& var, + bool) { + auto ref = *var.asRef(); + tvIncRef(&ref); + auto const ret = k == 0 ? EmptyArray::MakePacked(ref) + : EmptyArray::MakeMixed(k, ref); + return ret.first; +} + +ArrayData* EmptyArray::SetRefStr(ArrayData*, + StringData* k, + const Variant& var, + bool) { + auto ref = *var.asRef(); + tvIncRef(&ref); + return EmptyArray::MakeMixed(k, ref).first; +} + +ArrayData* EmptyArray::Append(ArrayData*, const Variant& vin, bool copy) { + auto cell = *vin.asCell(); + tvRefcountedIncRef(&cell); + return EmptyArray::MakePackedInl(cell).first; +} + +ArrayData* EmptyArray::AppendRef(ArrayData*, const Variant& v, bool copy) { + auto ref = *v.asRef(); + tvIncRef(&ref); + return EmptyArray::MakePacked(ref).first; +} + +ArrayData* EmptyArray::AppendWithRef(ArrayData*, const Variant& v, bool copy) { + auto tv = make_tv(); + tvAsVariant(&tv).setWithRef(v); + return EmptyArray::MakePacked(tv).first; +} + +////////////////////////////////////////////////////////////////////// + +ArrayData* EmptyArray::PlusEq(ArrayData*, const ArrayData* elems) { + elems->incRefCount(); + return const_cast(elems); +} + +ArrayData* EmptyArray::Merge(ArrayData*, const ArrayData* elems) { + auto const ret = HphpArray::MakeReserve(HphpArray::SmallSize); + auto const tmp = HphpArray::Merge(ret, elems); + ret->release(); + return tmp; +} + +ArrayData* EmptyArray::PopOrDequeue(ArrayData* ad, Variant& value) { + value = uninit_null(); + return ad; +} + +ArrayData* EmptyArray::Prepend(ArrayData*, const Variant& vin, bool) { + auto cell = *vin.asCell(); + tvRefcountedIncRef(&cell); + return EmptyArray::MakePacked(cell).first; +} + +////////////////////////////////////////////////////////////////////// + +ArrayData* EmptyArray::ZSetInt(ArrayData* ad, int64_t k, RefData* v) { + auto const arr = HphpArray::MakeReserve(HphpArray::SmallSize); + arr->m_count = 0; + DEBUG_ONLY auto const tmp = arr->zSet(k, v); + assert(tmp == arr); + return arr; +} + +ArrayData* EmptyArray::ZSetStr(ArrayData* ad, StringData* k, RefData* v) { + auto const arr = HphpArray::MakeReserve(HphpArray::SmallSize); + arr->m_count = 0; + DEBUG_ONLY auto const tmp = arr->zSet(k, v); + assert(tmp == arr); + return arr; +} + +ArrayData* EmptyArray::ZAppend(ArrayData* ad, RefData* v) { + auto const arr = HphpArray::MakeReserve(HphpArray::SmallSize); + arr->m_count = 0; + DEBUG_ONLY auto const tmp = arr->zAppend(v); + assert(tmp == arr); + return arr; +} + +////////////////////////////////////////////////////////////////////// + +} diff --git a/hphp/runtime/base/empty-array.h b/hphp/runtime/base/empty-array.h new file mode 100644 index 00000000000..f57ef31c35d --- /dev/null +++ b/hphp/runtime/base/empty-array.h @@ -0,0 +1,103 @@ +/* + +----------------------------------------------------------------------+ + | HipHop for PHP | + +----------------------------------------------------------------------+ + | Copyright (c) 2010-2013 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 incl_HPHP_EMPTY_ARRAY_H_ +#define incl_HPHP_EMPTY_ARRAY_H_ + +#include +#include +#include +#include + +namespace HPHP { + +////////////////////////////////////////////////////////////////////// + +struct Variant; +struct ArrayData; +struct RefData; +struct StringData; +struct TypedValue; +struct MArrayIter; +struct APCHandle; + +////////////////////////////////////////////////////////////////////// + +/* + * Functions relating to the "empty array" kind. These implement + * entries in the array dispatch table for the global empty array. + * Other arrays may also be empty in the sense that size() == 0, but + * this one is dealt with commonly enough to deserve special handlers. + */ +struct EmptyArray { + static void Release(ArrayData*); + + // TODO(#3983912): these helpers should be moved somewhere so we can + // use them for other array shapes, too. + static void* ReturnNull(...); + static bool ReturnFalse(...); + static bool ReturnTrue(...); + static ArrayData* ReturnFirstArg(ArrayData*, ...); + static ssize_t ReturnInvalidIndex(const ArrayData*); + static void NoOp(...); + static ArrayData* PopOrDequeue(ArrayData*, Variant&); + + static void NvGetKey(const ArrayData*, TypedValue* out, ssize_t pos); + static ArrayData* SetInt(ArrayData*, int64_t k, const Variant& v, bool copy); + static ArrayData* SetStr(ArrayData*, StringData* k, const Variant& v, + bool copy); + static const Variant& GetValueRef(const ArrayData* ad, ssize_t pos); + static ArrayData* LvalInt(ArrayData*, int64_t k, Variant*& ret, bool copy); + static ArrayData* LvalStr(ArrayData*, StringData* k, Variant*& ret, + bool copy); + static ArrayData* LvalNew(ArrayData*, Variant*& ret, bool copy); + static ArrayData* SetRefInt(ArrayData*, int64_t k, const Variant& v, + bool copy); + static ArrayData* SetRefStr(ArrayData*, StringData* k, + const Variant& v, bool copy); + static ssize_t IterAdvance(const ArrayData*, ssize_t prev); + static ssize_t IterRewind(const ArrayData*, ssize_t prev); + static bool ValidMArrayIter(const ArrayData*, const MArrayIter& fp); + static bool AdvanceMArrayIter(ArrayData*, MArrayIter& fp); + static ArrayData* Copy(const ArrayData* ad); + static ArrayData* CopyWithStrongIterators(const ArrayData*); + static ArrayData* NonSmartCopy(const ArrayData*); + static ArrayData* ZSetInt(ArrayData* ad, int64_t k, RefData* v); + static ArrayData* ZSetStr(ArrayData* ad, StringData* k, RefData* v); + static ArrayData* ZAppend(ArrayData* ad, RefData* v); + static ArrayData* Append(ArrayData*, const Variant& v, bool copy); + static ArrayData* AppendRef(ArrayData*, const Variant& v, bool copy); + static ArrayData* AppendWithRef(ArrayData*, const Variant& v, bool copy); + static ArrayData* PlusEq(ArrayData*, const ArrayData* elems); + static ArrayData* Merge(ArrayData*, const ArrayData* elems); + static ArrayData* Prepend(ArrayData*, const Variant& v, bool copy); + static void OnSetEvalScalar(ArrayData*); + +private: + static std::pair MakePacked(TypedValue); + static std::pair MakePackedInl(TypedValue); + static std::pair MakeMixed(StringData*, TypedValue); + static std::pair MakeMixed(int64_t, TypedValue); + +private: + struct Initializer; + static Initializer s_initializer; +}; + +////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/hphp/runtime/base/hphp-array-defs.h b/hphp/runtime/base/hphp-array-defs.h index 5a55e8db032..b595692db48 100644 --- a/hphp/runtime/base/hphp-array-defs.h +++ b/hphp/runtime/base/hphp-array-defs.h @@ -245,9 +245,61 @@ private: Elm* const m_stop; }; +////////////////////////////////////////////////////////////////////// + +ALWAYS_INLINE +uint32_t computeMaskFromNumElms(uint32_t n) { + assert(n <= 0x7fffffffU); + auto lgSize = HphpArray::MinLgTableSize; + auto maxElms = HphpArray::SmallSize; + assert(lgSize >= 2); + + // Note: it's tempting to convert this loop into something involving + // x64 bsr and a shift. Naive attempts currently actually add more + // branches, because we need to initially check whether `n' is less + // than SmallSize, and after finding the next power of two we need a + // branch to see if it was big enough for the desired load factor. + // This is probably still worth revisiting (e.g., MakeReserve could + // have a precondition that n is at least SmallSize). + while (maxElms < n) { + ++lgSize; + maxElms <<= 1; + } + assert(lgSize <= 32); + + // return 2^lgSize - 1 + return ((size_t(1U)) << lgSize) - 1; + static_assert(HphpArray::MinLgTableSize >= 2, + "lower limit for 0.75 load factor"); +} + +ALWAYS_INLINE +std::pair computeCapAndMask(uint32_t minimumMaxElms) { + auto const mask = computeMaskFromNumElms(minimumMaxElms); + auto const cap = HphpArray::computeMaxElms(mask); + return std::make_pair(cap, mask); +} + +ALWAYS_INLINE +size_t computeAllocBytes(uint32_t cap, uint32_t mask) { + auto const tabSize = mask + 1; + auto const tabBytes = tabSize * sizeof(int32_t); + auto const dataBytes = cap * sizeof(HphpArray::Elm); + return sizeof(HphpArray) + tabBytes + dataBytes; +} + +ALWAYS_INLINE +HphpArray* smartAllocArray(uint32_t cap, uint32_t mask) { + /* + * Note: we're currently still allocating the memory for the hash + * for a packed array even if we aren't going to use it yet. + */ + auto const allocBytes = computeAllocBytes(cap, mask); + return static_cast(MM().objMallocLogged(allocBytes)); +} ////////////////////////////////////////////////////////////////////// } -#endif // incl_HPHP_HPHP_ARRAY_DEFS_H_ +#endif diff --git a/hphp/runtime/base/hphp-array.cpp b/hphp/runtime/base/hphp-array.cpp index 254c9be58d9..747c7175612 100644 --- a/hphp/runtime/base/hphp-array.cpp +++ b/hphp/runtime/base/hphp-array.cpp @@ -19,6 +19,7 @@ #include "hphp/runtime/base/apc-local-array.h" #include "hphp/runtime/base/array-init.h" #include "hphp/runtime/base/array-iterator.h" +#include "hphp/runtime/base/empty-array.h" #include "hphp/runtime/base/complex-types.h" #include "hphp/runtime/base/execution-context.h" #include "hphp/runtime/base/runtime-option.h" @@ -45,92 +46,9 @@ TRACE_SET_MOD(runtime); ////////////////////////////////////////////////////////////////////// -std::aligned_storage< - sizeof(HphpArray) + - sizeof(HphpArray::Elm) * HphpArray::SmallSize, - // No need for space for the hash because the empty array is - // kPackedKind. - alignof(HphpArray) ->::type s_theEmptyArray; - -struct HphpArray::EmptyArrayInitializer { - EmptyArrayInitializer() { - void* vpEmpty = &s_theEmptyArray; - - auto const ad = static_cast(vpEmpty); - ad->m_kind = kPackedKind; - ad->m_size = 0; - ad->m_pos = ArrayData::invalid_index; - ad->m_count = 0; - ad->m_used = 0; - ad->m_tableMask = SmallHashSize - 1; - - ad->m_cap = SmallSize; - - ad->setStatic(); - - assert(ad->checkInvariants()); - } -}; -HphpArray::EmptyArrayInitializer HphpArray::s_arrayInitializer; - -//============================================================================= -// Helpers. - namespace { ALWAYS_INLINE -uint32_t computeMaskFromNumElms(uint32_t n) { - assert(n <= 0x7fffffffU); - auto lgSize = HphpArray::MinLgTableSize; - auto maxElms = HphpArray::SmallSize; - assert(lgSize >= 2); - - // Note: it's tempting to convert this loop into something involving - // x64 bsr and a shift. Naive attempts currently actually add more - // branches, because we need to initially check whether `n' is less - // than SmallSize, and after finding the next power of two we need a - // branch to see if it was big enough for the desired load factor. - // This is probably still worth revisiting (e.g., MakeReserve could - // have a precondition that n is at least SmallSize). - while (maxElms < n) { - ++lgSize; - maxElms <<= 1; - } - assert(lgSize <= 32); - - // return 2^lgSize - 1 - return ((size_t(1U)) << lgSize) - 1; - static_assert(HphpArray::MinLgTableSize >= 2, - "lower limit for 0.75 load factor"); -} - -ALWAYS_INLINE -std::pair computeCapAndMask(uint32_t minimumMaxElms) { - auto const mask = computeMaskFromNumElms(minimumMaxElms); - auto const cap = HphpArray::computeMaxElms(mask); - return std::make_pair(cap, mask); -} - -ALWAYS_INLINE -size_t computeAllocBytes(uint32_t cap, uint32_t mask) { - auto const tabSize = mask + 1; - auto const tabBytes = tabSize * sizeof(int32_t); - auto const dataBytes = cap * sizeof(HphpArray::Elm); - return sizeof(HphpArray) + tabBytes + dataBytes; -} - -ALWAYS_INLINE -HphpArray* smartAllocArray(uint32_t cap, uint32_t mask) { - /* - * Note: we're currently still allocating the memory for the hash - * for a packed array even if we aren't going to use it yet. - */ - auto const allocBytes = computeAllocBytes(cap, mask); - return static_cast(MM().objMallocLogged(allocBytes)); -} - -ALWAYS_INLINE HphpArray* mallocArray(uint32_t cap, uint32_t mask) { auto const allocBytes = computeAllocBytes(cap, mask); return static_cast(std::malloc(allocBytes)); @@ -138,8 +56,7 @@ HphpArray* mallocArray(uint32_t cap, uint32_t mask) { } -//============================================================================= -// Construction +////////////////////////////////////////////////////////////////////// NEVER_INLINE HphpArray* HphpArray::MakeReserve(uint32_t capacity) { @@ -442,8 +359,12 @@ Variant CreateVarForUncountedArray(const Variant& source) { return StringData::MakeUncounted(source.getStringData()->slice()); } - case KindOfArray: - return HphpArray::MakeUncounted(source.getArrayData()); + case KindOfArray: { + auto const ad = source.getArrayData(); + return ad == HphpArray::GetStaticEmptyArray() + ? ad + : HphpArray::MakeUncounted(ad); + } default: assert(false); // type not allowed @@ -699,12 +620,6 @@ bool HphpArray::checkInvariants() const { break; } - if (this == GetStaticEmptyArray()) { - assert(m_size == 0); - assert(m_used == 0); - assert(isPacked()); - assert(m_pos == invalid_index); - } return true; } @@ -773,10 +688,6 @@ const Variant& HphpArray::GetValueRef(const ArrayData* ad, ssize_t pos) { return tvAsCVarRef(&e.data); } -bool HphpArray::IsVectorDataPacked(const ArrayData*) { - return true; -} - bool HphpArray::IsVectorData(const ArrayData* ad) { auto a = asMixed(ad); if (a->m_size == 0) { @@ -1017,11 +928,6 @@ bool HphpArray::ExistsInt(const ArrayData* ad, int64_t k) { return validPos(a->find(k)); } -bool HphpArray::ExistsStrPacked(const ArrayData* ad, const StringData* k) { - assert(asPacked(ad)); - return false; -} - bool HphpArray::ExistsStr(const ArrayData* ad, const StringData* k) { auto a = asMixed(ad); return validPos(a->find(k, k->hash())); @@ -2409,7 +2315,7 @@ void HphpArray::OnSetEvalScalar(ArrayData* ad) { } } -bool HphpArray::ValidMArrayIter(const ArrayData* ad, const MArrayIter &fp) { +bool HphpArray::ValidMArrayIter(const ArrayData* ad, const MArrayIter& fp) { assert(fp.getContainer() == asHphpArray(ad)); if (fp.getResetFlag()) return false; return fp.m_pos != invalid_index; diff --git a/hphp/runtime/base/hphp-array.h b/hphp/runtime/base/hphp-array.h index ec9350654bd..c7c2d8d630c 100644 --- a/hphp/runtime/base/hphp-array.h +++ b/hphp/runtime/base/hphp-array.h @@ -64,25 +64,31 @@ public: // data.m_type == KindOfInvalid if this is an empty slot in the // array (e.g. after a key is deleted). TypedValueAux data; + bool hasStrKey() const { return data.hash() != 0; } + bool hasIntKey() const { return data.hash() == 0; } + int32_t hash() const { return data.hash(); } + void setStaticKey(StringData* k, strhash_t h) { assert(k->isStatic()); key = k; data.hash() = h | STRHASH_MSB; } + void setStrKey(StringData* k, strhash_t h) { key = k; data.hash() = h | STRHASH_MSB; k->incRefCount(); } + void setIntKey(int64_t k) { ikey = k; data.hash() = 0; @@ -138,7 +144,7 @@ public: * used for initial empty arrays (COW will cause it to escalate to a * request-local array if it is modified). */ - static HphpArray* GetStaticEmptyArray(); + static ArrayData* GetStaticEmptyArray(); // This behaves the same as iter_begin except that it assumes // this array is not empty and its not virtual. @@ -168,7 +174,6 @@ public: // overrides ArrayData static bool IsVectorData(const ArrayData*); - static bool IsVectorDataPacked(const ArrayData*); static ssize_t IterBegin(const ArrayData*); static ssize_t IterEnd(const ArrayData*); static ssize_t IterAdvance(const ArrayData*, ssize_t pos); @@ -178,7 +183,6 @@ public: static bool ExistsInt(const ArrayData*, int64_t k); static bool ExistsStr(const ArrayData*, const StringData* k); static bool ExistsIntPacked(const ArrayData*, int64_t k); - static bool ExistsStrPacked(const ArrayData*, const StringData* k); // implements ArrayData static ArrayData* LvalInt(ArrayData* ad, int64_t k, Variant*& ret, @@ -344,17 +348,14 @@ public: static size_t computeDataSize(uint32_t tableMask); private: - friend class ArrayInit; + friend struct ArrayInit; friend struct MemoryProfile; - struct EmptyArrayInitializer; + friend struct EmptyArray; enum class ClonePacked {}; enum class CloneMixed {}; enum SortFlavor { IntegerSort, StringSort, GenericSort }; private: - static EmptyArrayInitializer s_arrayInitializer; - -private: // Safe downcast helpers static HphpArray* asPacked(ArrayData* ad); static const HphpArray* asPacked(const ArrayData* ad); @@ -555,16 +556,15 @@ private: }; extern std::aligned_storage< - sizeof(HphpArray) + - sizeof(HphpArray::Elm) * HphpArray::SmallSize, - alignof(HphpArray) + sizeof(ArrayData), + alignof(ArrayData) >::type s_theEmptyArray; //============================================================================= -inline HphpArray* HphpArray::GetStaticEmptyArray() { +inline ArrayData* HphpArray::GetStaticEmptyArray() { void* vp = &s_theEmptyArray; - return static_cast(vp); + return static_cast(vp); } /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/ext/ext_array.cpp b/hphp/runtime/ext/ext_array.cpp index 20461d23490..821146dbd3d 100644 --- a/hphp/runtime/ext/ext_array.cpp +++ b/hphp/runtime/ext/ext_array.cpp @@ -810,6 +810,7 @@ Variant f_array_splice(VRefParam input, int offset, input = ArrayUtil::Splice(arr_input, offset, len, replacement, &ret); return ret; } + Variant f_array_sum(const Variant& array) { getCheckedArray(array); int64_t i; diff --git a/hphp/test/slow/array/empty_array_001.php b/hphp/test/slow/array/empty_array_001.php new file mode 100644 index 00000000000..989238e4ff8 --- /dev/null +++ b/hphp/test/slow/array/empty_array_001.php @@ -0,0 +1,10 @@ + + int(2) +} diff --git a/hphp/test/slow/array/empty_array_002.php b/hphp/test/slow/array/empty_array_002.php new file mode 100644 index 00000000000..432bac8f3a9 --- /dev/null +++ b/hphp/test/slow/array/empty_array_002.php @@ -0,0 +1,9 @@ + + int(2) +} diff --git a/hphp/test/slow/array/empty_array_003.php b/hphp/test/slow/array/empty_array_003.php new file mode 100644 index 00000000000..d6793e07738 --- /dev/null +++ b/hphp/test/slow/array/empty_array_003.php @@ -0,0 +1,9 @@ + + int(2) +} diff --git a/hphp/test/slow/array/empty_array_004.php b/hphp/test/slow/array/empty_array_004.php new file mode 100644 index 00000000000..e2c80dd690e --- /dev/null +++ b/hphp/test/slow/array/empty_array_004.php @@ -0,0 +1,9 @@ + + int(2) +} diff --git a/hphp/test/slow/array/empty_array_005.php b/hphp/test/slow/array/empty_array_005.php new file mode 100644 index 00000000000..f76bed7263b --- /dev/null +++ b/hphp/test/slow/array/empty_array_005.php @@ -0,0 +1,9 @@ + + array(1) { + [12]=> + int(2) + } +} diff --git a/hphp/test/slow/array/empty_array_006.php b/hphp/test/slow/array/empty_array_006.php new file mode 100644 index 00000000000..0574db3fa9b --- /dev/null +++ b/hphp/test/slow/array/empty_array_006.php @@ -0,0 +1,9 @@ + + array(1) { + [12]=> + int(2) + } +} diff --git a/hphp/test/slow/array/empty_array_007.php b/hphp/test/slow/array/empty_array_007.php new file mode 100644 index 00000000000..24afe6c5660 --- /dev/null +++ b/hphp/test/slow/array/empty_array_007.php @@ -0,0 +1,9 @@ + + array(1) { + [12]=> + int(2) + } +} diff --git a/hphp/test/slow/array/empty_array_008.php b/hphp/test/slow/array/empty_array_008.php new file mode 100644 index 00000000000..076acf6edbf --- /dev/null +++ b/hphp/test/slow/array/empty_array_008.php @@ -0,0 +1,9 @@ + + array(1) { + [12]=> + int(2) + } +} diff --git a/hphp/test/slow/array/empty_array_009.php b/hphp/test/slow/array/empty_array_009.php new file mode 100644 index 00000000000..4116d503dd1 --- /dev/null +++ b/hphp/test/slow/array/empty_array_009.php @@ -0,0 +1,8 @@ + + int(1) + [1]=> + int(2) + [2]=> + int(3) +} diff --git a/hphp/test/slow/array/empty_array_010.php b/hphp/test/slow/array/empty_array_010.php new file mode 100644 index 00000000000..cf61b4d45ea --- /dev/null +++ b/hphp/test/slow/array/empty_array_010.php @@ -0,0 +1,25 @@ + + int(2) + [43]=> + int(3) +} +array(2) { + [42]=> + array(1) { + [42]=> + int(2) + } + [43]=> + int(3) +} + +Warning: Cannot add element to the array as the next element is already occupied in %s on line 16 +array(1) { + [9223372036854775807]=> + int(2) +} + +Warning: Cannot add element to the array as the next element is already occupied in %s on line 21 +array(1) { + [9223372036854775807]=> + array(1) { + [42]=> + int(2) + } +} -- 2.11.4.GIT