/* * Copyright (c) 2020 Project CHIP Authors * Copyright (c) 2013 Nest Labs, Inc. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Defines memory pool classes. */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace chip { template class BitmapActiveObjectIterator; namespace internal { class Statistics { public: Statistics() : mAllocated(0), mHighWaterMark(0) {} size_t Allocated() const { return mAllocated; } size_t HighWaterMark() const { return mHighWaterMark; } void IncreaseUsage() { if (++mAllocated > mHighWaterMark) { mHighWaterMark = mAllocated; } } void DecreaseUsage() { --mAllocated; } protected: size_t mAllocated; size_t mHighWaterMark; }; class StaticAllocatorBase : public Statistics { public: StaticAllocatorBase(size_t capacity) : mCapacity(capacity) {} size_t Capacity() const { return mCapacity; } bool Exhausted() const { return mAllocated == mCapacity; } protected: const size_t mCapacity; }; class StaticAllocatorBitmap : public internal::StaticAllocatorBase { protected: /** * Use the largest data type supported by `std::atomic`. Putting multiple atomic inside a single cache line won't improve * concurrency, while the use of larger data type can improve the performance by reducing the number of outer loop iterations. */ using tBitChunkType = unsigned long; static constexpr const tBitChunkType kBit1 = 1; // make sure bitshifts produce the right type static constexpr const size_t kBitChunkSize = std::numeric_limits::digits; static_assert(ATOMIC_LONG_LOCK_FREE, "StaticAllocatorBitmap is not lock free"); public: StaticAllocatorBitmap(void * storage, std::atomic * usage, size_t capacity, size_t elementSize); protected: void * Allocate(); void Deallocate(void * element); void * At(size_t index) { return static_cast(mElements) + mElementSize * index; } size_t IndexOf(void * element); /// Returns the first index that is active (i.e. allocated data). /// /// If nothing is active, this will return mCapacity size_t FirstActiveIndex(); /// Returns the next active index after `start`. /// /// If nothing else active/allocated, returns mCapacity size_t NextActiveIndexAfter(size_t start); using Lambda = Loop (*)(void * context, void * object); Loop ForEachActiveObjectInner(void * context, Lambda lambda); Loop ForEachActiveObjectInner(void * context, Loop lambda(void * context, const void * object)) const { return const_cast(this)->ForEachActiveObjectInner(context, reinterpret_cast(lambda)); } private: void * mElements; const size_t mElementSize; std::atomic * mUsage; /// allow accessing direct At() calls template friend class ::chip::BitmapActiveObjectIterator; }; template class LambdaProxy { public: LambdaProxy(Function && function) : mFunction(std::move(function)) {} static Loop Call(void * context, void * target) { return static_cast(context)->mFunction(static_cast(target)); } static Loop ConstCall(void * context, const void * target) { return static_cast(context)->mFunction(static_cast(target)); } private: Function mFunction; }; #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP struct HeapObjectListNode { void Remove() { mNext->mPrev = mPrev; mPrev->mNext = mNext; } void * mObject = nullptr; HeapObjectListNode * mNext = nullptr; HeapObjectListNode * mPrev = nullptr; }; struct HeapObjectList : HeapObjectListNode { HeapObjectList() { mNext = mPrev = this; } void Append(HeapObjectListNode * node) { node->mNext = this; node->mPrev = mPrev; mPrev->mNext = node; mPrev = node; } HeapObjectListNode * FindNode(void * object) const; using Lambda = Loop (*)(void *, void *); Loop ForEachNode(void * context, Lambda lambda); Loop ForEachNode(void * context, Loop lambda(void * context, const void * object)) const { return const_cast(this)->ForEachNode(context, reinterpret_cast(lambda)); } /// Cleans up any deferred releases IFF iteration depth is 0 void CleanupDeferredReleases(); size_t mIterationDepth = 0; bool mHaveDeferredNodeRemovals = false; }; #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP } // namespace internal /// Provides iteration over active objects in a Bitmap pool. /// /// Creating and releasing items within a pool does not invalidate /// an iterator, however there are no guarantees which objects the /// iterator will return (i.e. newly created objects while iterating /// may be visible or not to the iterator depending where they are /// allocated). /// /// You are not prevented from releasing the object the iterator /// currently points at. In that case, iterator should be advanced. template class BitmapActiveObjectIterator { public: using value_type = T; using pointer = T *; using reference = T &; explicit BitmapActiveObjectIterator(internal::StaticAllocatorBitmap * pool, size_t idx) : mPool(pool), mIndex(idx) {} BitmapActiveObjectIterator() {} bool operator==(const BitmapActiveObjectIterator & other) const { return (AtEnd() && other.AtEnd()) || ((mPool == other.mPool) && (mIndex == other.mIndex)); } bool operator!=(const BitmapActiveObjectIterator & other) const { return !(*this == other); } BitmapActiveObjectIterator & operator++() { mIndex = mPool->NextActiveIndexAfter(mIndex); return *this; } T * operator*() const { return static_cast(mPool->At(mIndex)); } private: bool AtEnd() const { return (mPool == nullptr) || (mIndex >= mPool->Capacity()); } internal::StaticAllocatorBitmap * mPool = nullptr; // pool that this belongs to size_t mIndex = std::numeric_limits::max(); }; /** * @class ObjectPool * * Depending on build configuration, ObjectPool is either a fixed-size static pool or a heap-allocated pool. * * @tparam T Type of element to be allocated. * @tparam N Number of elements in the pool, in the fixed-size case. * * @fn CreateObject * @memberof ObjectPool * * Create an object from the pool. Forwards its arguments to construct a T. * * @fn ReleaseObject * @memberof ObjectPool * @param object Pointer to object to release (or return to the pool). Its destructor runs. * * @fn ForEachActiveObject * @memberof ObjectPool * @param visitor A function that takes a T* and returns Loop::Continue to continue iterating or Loop::Break to stop iterating. * @returns Loop::Break if a visitor call returned Loop::Break, Loop::Finish otherwise. * * Iteration may be nested. ReleaseObject() can be called during iteration, on the current object or any other. * CreateObject() can be called, but it is undefined whether or not a newly created object will be visited. */ /** * A class template used for allocating objects from a fixed-size static pool. * * @tparam T type of element to be allocated. * @tparam N a positive integer max number of elements the pool provides. */ template class BitMapObjectPool : public internal::StaticAllocatorBitmap { public: BitMapObjectPool() : StaticAllocatorBitmap(mData.mMemory, mUsage, N, sizeof(T)) {} ~BitMapObjectPool() { VerifyOrDieWithObject(Allocated() == 0, this); } BitmapActiveObjectIterator begin() { return BitmapActiveObjectIterator(this, FirstActiveIndex()); } BitmapActiveObjectIterator end() { return BitmapActiveObjectIterator(this, N); } template T * CreateObject(Args &&... args) { T * element = static_cast(Allocate()); if (element != nullptr) return new (element) T(std::forward(args)...); return nullptr; } void ReleaseObject(T * element) { if (element == nullptr) return; element->~T(); Deallocate(element); } void ReleaseAll() { ForEachActiveObjectInner(this, ReleaseObject); } /** * @brief * Run a functor for each active object in the pool * * @param function A functor of type `Loop (*)(T*)`. * Return Loop::Break to break the iteration. * The only modification the functor is allowed to make * to the pool before returning is releasing the * object that was passed to the functor. Any other * desired changes need to be made after iteration * completes. * @return Loop Returns Break if some call to the functor returned * Break. Otherwise returns Finish. * * caution * this function is not thread-safe, make sure all usage of the * pool is protected by a lock, or else avoid using this function */ template Loop ForEachActiveObject(Function && function) { static_assert(std::is_same()))>::value, "The function must take T* and return Loop"); internal::LambdaProxy proxy(std::forward(function)); return ForEachActiveObjectInner(&proxy, &internal::LambdaProxy::Call); } template Loop ForEachActiveObject(Function && function) const { static_assert(std::is_same()))>::value, "The function must take const T* and return Loop"); internal::LambdaProxy proxy(std::forward(function)); return ForEachActiveObjectInner(&proxy, &internal::LambdaProxy::ConstCall); } void DumpToLog() const { ChipLogError(Support, "BitMapObjectPool: %lu allocated", static_cast(Allocated())); if constexpr (IsDumpable::value) { ForEachActiveObject([](const T * object) { object->DumpToLog(); return Loop::Continue; }); } } private: static Loop ReleaseObject(void * context, void * object) { static_cast(context)->ReleaseObject(static_cast(object)); return Loop::Continue; } std::atomic mUsage[(N + kBitChunkSize - 1) / kBitChunkSize]; union Data { Data() {} ~Data() {} alignas(alignof(T)) uint8_t mMemory[N * sizeof(T)]; T mMemoryViewForDebug[N]; // Just for debugger } mData; }; #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP class HeapObjectPoolExitHandling { public: // If IgnoreLeaksOnExit is called, some time after all static initializers have // run, HeapObjectPool will not assert that everything in it has been // released if its destructor runs under exit() (i.e. when the application // is quitting anyway). static void IgnoreLeaksOnExit(); protected: static bool sIgnoringLeaksOnExit; private: static void ExitHandler(); static bool sExitHandlerRegistered; }; /** * A class template used for allocating objects from the heap. * * @tparam T type to be allocated. */ template class HeapObjectPool : public internal::Statistics, public HeapObjectPoolExitHandling { public: HeapObjectPool() {} ~HeapObjectPool() { #ifndef __SANITIZE_ADDRESS__ #ifdef __clang__ #if __has_feature(address_sanitizer) #define __SANITIZE_ADDRESS__ 1 #else #define __SANITIZE_ADDRESS__ 0 #endif // __has_feature(address_sanitizer) #else #define __SANITIZE_ADDRESS__ 0 #endif // __clang__ #endif // __SANITIZE_ADDRESS__ #if __SANITIZE_ADDRESS__ // Free all remaining objects so that ASAN can catch specific use-after-free cases. ReleaseAll(); #else // __SANITIZE_ADDRESS__ if (!sIgnoringLeaksOnExit) { // Verify that no live objects remain, to prevent potential use-after-free. VerifyOrDieWithObject(Allocated() == 0, this); } #endif // __SANITIZE_ADDRESS__ } /// Provides iteration over active objects in the pool. /// /// NOTE: There is extra logic to allow objects release WHILE the iterator is /// active while still allowing to advance the iterator. /// This is done by flagging an iteration depth whenever an active /// iterator exists. This also means that while a pool iterator exists, releasing /// of tracking memory objects may be deferred until the last active iterator is /// released. class ActiveObjectIterator { public: using value_type = T; using pointer = T *; using reference = T &; ActiveObjectIterator() {} ActiveObjectIterator(const ActiveObjectIterator & other) : mCurrent(other.mCurrent), mEnd(other.mEnd) { if (mEnd != nullptr) { // Iteration depth is used to support `Release` while an iterator is active. // // Code was historically using this functionality, so we support it here // as well: while iteration is active, iteration depth is > 0. When it // goes to 0, then any deferred `Release()` calls are executed. mEnd->mIterationDepth++; } } ActiveObjectIterator & operator=(const ActiveObjectIterator & other) { if (mEnd != nullptr) { mEnd->mIterationDepth--; mEnd->CleanupDeferredReleases(); } mCurrent = other.mCurrent; mEnd = other.mEnd; mEnd->mIterationDepth++; } ~ActiveObjectIterator() { if (mEnd != nullptr) { mEnd->mIterationDepth--; mEnd->CleanupDeferredReleases(); } } bool operator==(const ActiveObjectIterator & other) const { // extra current/end compare is to have all "end iterators" // compare as equal (in particular default active object iterator is the end // of an iterator) return (mCurrent == other.mCurrent) || ((mCurrent == mEnd) && (other.mCurrent == other.mEnd)); } bool operator!=(const ActiveObjectIterator & other) const { return !(*this == other); } ActiveObjectIterator & operator++() { do { mCurrent = mCurrent->mNext; } while ((mCurrent != mEnd) && (mCurrent->mObject == nullptr)); return *this; } T * operator*() const { return static_cast(mCurrent->mObject); } protected: friend class HeapObjectPool; explicit ActiveObjectIterator(internal::HeapObjectListNode * current, internal::HeapObjectList * end) : mCurrent(current), mEnd(end) { mEnd->mIterationDepth++; } private: internal::HeapObjectListNode * mCurrent = nullptr; internal::HeapObjectList * mEnd = nullptr; }; ActiveObjectIterator begin() { return ActiveObjectIterator(mObjects.mNext, &mObjects); } ActiveObjectIterator end() { return ActiveObjectIterator(&mObjects, &mObjects); } template T * CreateObject(Args &&... args) { T * object = Platform::New(std::forward(args)...); if (object != nullptr) { auto node = Platform::New(); if (node != nullptr) { node->mObject = object; mObjects.Append(node); IncreaseUsage(); return object; } } return nullptr; } /* * This method exists purely to line up with the static allocator version. * Consequently, return a nonsensically large number to normalize comparison * operations that act on this value. */ size_t Capacity() const { return SIZE_MAX; } /* * This method exists purely to line up with the static allocator version. Heap based object pool will never be exhausted. */ bool Exhausted() const { return false; } void ReleaseObject(T * object) { if (object != nullptr) { internal::HeapObjectListNode * node = mObjects.FindNode(object); // Releasing an object that is not allocated indicates likely memory // corruption; better to safe-crash than proceed at this point. VerifyOrDie(node != nullptr); node->mObject = nullptr; Platform::Delete(object); // The node needs to be released immediately if we are not in the middle of iteration. // Otherwise cleanup is deferred until all iteration on this pool completes and it's safe to release nodes. if (mObjects.mIterationDepth == 0) { node->Remove(); Platform::Delete(node); } else { mObjects.mHaveDeferredNodeRemovals = true; } DecreaseUsage(); } } void ReleaseAll() { mObjects.ForEachNode(this, ReleaseObject); } /** * @brief * Run a functor for each active object in the pool * * @param function A functor of type `Loop (*)(T*)`. * Return Loop::Break to break the iteration. * The only modification the functor is allowed to make * to the pool before returning is releasing the * object that was passed to the functor. Any other * desired changes need to be made after iteration * completes. * @return Loop Returns Break if some call to the functor returned * Break. Otherwise returns Finish. */ template Loop ForEachActiveObject(Function && function) { static_assert(std::is_same()))>::value, "The function must take T* and return Loop"); internal::LambdaProxy proxy(std::forward(function)); return mObjects.ForEachNode(&proxy, &internal::LambdaProxy::Call); } template Loop ForEachActiveObject(Function && function) const { static_assert(std::is_same()))>::value, "The function must take const T* and return Loop"); internal::LambdaProxy proxy(std::forward(function)); return mObjects.ForEachNode(&proxy, &internal::LambdaProxy::ConstCall); } void DumpToLog() const { ChipLogError(Support, "HeapObjectPool: %lu allocated", static_cast(Allocated())); if constexpr (IsDumpable::value) { ForEachActiveObject([](const T * object) { object->DumpToLog(); return Loop::Continue; }); } } private: static Loop ReleaseObject(void * context, void * object) { static_cast(context)->ReleaseObject(static_cast(object)); return Loop::Continue; } internal::HeapObjectList mObjects; }; #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP /** * Specify ObjectPool storage allocation. */ enum class ObjectPoolMem { /** * Use storage inside the containing scope for both objects and pool management state. */ kInline, #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP /** * Allocate objects from the heap, with only pool management state in the containing scope. * * For this case, the ObjectPool size parameter is ignored. */ kHeap, kDefault = kHeap #else // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP kDefault = kInline #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP }; template struct ObjectPoolIterator; template struct ObjectPoolIterator { using Type = BitmapActiveObjectIterator; }; template class ObjectPool; template class ObjectPool : public BitMapObjectPool { }; #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP template struct ObjectPoolIterator { using Type = typename HeapObjectPool::ActiveObjectIterator; }; template class ObjectPool : public HeapObjectPool { }; #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP } // namespace chip