| /*------------------------------------------------------------------------- |
| * drawElements Memory Pool Library |
| * -------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * 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. |
| * |
| *//*! |
| * \file |
| * \brief Memory pool management. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "deMemPool.h" |
| #include "deMemory.h" |
| #include "deInt32.h" |
| |
| #if defined(DE_SUPPORT_FAILING_POOL_ALLOC) |
| # include "deRandom.h" |
| #endif |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| enum |
| { |
| INITIAL_PAGE_SIZE = 128, /*!< Size for the first allocated memory page. */ |
| MAX_PAGE_SIZE = 8096, /*!< Maximum size for a memory page. */ |
| MEM_PAGE_BASE_ALIGN = 4 /*!< Base alignment guarantee for mem page data ptr. */ |
| }; |
| |
| typedef struct MemPage_s MemPage; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \internal |
| * \brief Memory page header. |
| * |
| * Represent a page of memory allocate by a memory pool. |
| *//*--------------------------------------------------------------------*/ |
| struct MemPage_s |
| { |
| int capacity; |
| int bytesAllocated; |
| |
| MemPage* nextPage; |
| }; |
| |
| #if defined(DE_SUPPORT_DEBUG_POOLS) |
| typedef struct DebugAlloc_s DebugAlloc; |
| |
| struct DebugAlloc_s |
| { |
| void* memPtr; |
| DebugAlloc* next; |
| }; |
| #endif |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Memory pool. |
| * |
| * A pool of memory from which individual memory allocations can be made. |
| * The memory pools don't have a freeing operation for individual allocations, |
| * but rather all of the memory allocated from a pool is freed when the pool |
| * is destroyed. |
| * |
| * The pools can be arranged into a hierarchy. If a pool with children is |
| * destroyed, all of the children are first recursively destroyed and then |
| * the pool itself. |
| * |
| * The memory pools support a feature where individual allocations can be |
| * made to simulate failure (i.e., return null). This can be enabled by |
| * creating the root pool with the deMemPool_createFailingRoot() function. |
| * When the feature is enabled, also creation of sub-pools occasionally |
| * fails. |
| *//*--------------------------------------------------------------------*/ |
| struct deMemPool_s |
| { |
| deUint32 flags; /*!< Flags. */ |
| deMemPool* parent; /*!< Pointer to parent (null for root pools). */ |
| deMemPoolUtil* util; /*!< Utilities (callbacks etc.). */ |
| int numChildren; /*!< Number of child pools. */ |
| deMemPool* firstChild; /*!< Pointer to first child pool in linked list. */ |
| deMemPool* prevPool; /*!< Previous pool in parent's linked list. */ |
| deMemPool* nextPool; /*!< Next pool in parent's linked list. */ |
| |
| MemPage* currentPage; /*!< Current memory page from which to allocate. */ |
| |
| #if defined(DE_SUPPORT_FAILING_POOL_ALLOC) |
| deBool allowFailing; /*!< Is allocation failure simulation enabled? */ |
| deRandom failRandom; /*!< RNG for failing allocations. */ |
| #endif |
| #if defined(DE_SUPPORT_DEBUG_POOLS) |
| deBool enableDebugAllocs; /*!< If true, always allocates using deMalloc(). */ |
| DebugAlloc* debugAllocListHead; /*!< List of allocation in debug mode. */ |
| |
| int lastAllocatedIndex; /*!< Index of last allocated pool (rootPool only). */ |
| int allocIndex; /*!< Allocation index (running counter). */ |
| #endif |
| #if defined(DE_SUPPORT_POOL_MEMORY_TRACKING) |
| int maxMemoryAllocated; /*!< Maximum amount of memory allocated from pools. */ |
| int maxMemoryCapacity; /*!< Maximum amount of memory allocated for pools. */ |
| #endif |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \internal |
| * \brief Initialize a memory page. |
| * \param page Memory page to initialize. |
| * \param capacity Capacity allocated for the memory page. |
| *//*--------------------------------------------------------------------*/ |
| static void MemPage_init (MemPage* page, size_t capacity) |
| { |
| memset(page, 0, sizeof(MemPage)); |
| #if defined(DE_DEBUG) |
| memset(page + 1, 0xCD, capacity); |
| #endif |
| page->capacity = (int)capacity; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \internal |
| * \brief Create a new memory page. |
| * \param capacity Capacity for the memory page. |
| * \return The created memory page (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| static MemPage* MemPage_create (size_t capacity) |
| { |
| MemPage* page = (MemPage*)deMalloc(sizeof(MemPage) + capacity); |
| if (!page) |
| return DE_NULL; |
| |
| DE_ASSERT(deIsAlignedPtr(page+1, MEM_PAGE_BASE_ALIGN)); |
| |
| MemPage_init(page, capacity); |
| return page; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \internal |
| * \brief Destroy a memory page. |
| * \param page Memory page to destroy. |
| *//*--------------------------------------------------------------------*/ |
| static void MemPage_destroy (MemPage* page) |
| { |
| #if defined(DE_DEBUG) |
| /* Fill with garbage to hopefully catch dangling pointer bugs easier. */ |
| deUint8* dataPtr = (deUint8*)(page + 1); |
| memset(dataPtr, 0xCD, (size_t)page->capacity); |
| #endif |
| deFree(page); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \internal |
| * \brief Internal function for creating a new memory pool. |
| * \param parent Parent pool (may be null). |
| * \return The created memory pool (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| static deMemPool* createPoolInternal (deMemPool* parent) |
| { |
| deMemPool* pool; |
| MemPage* initialPage; |
| |
| #if defined(DE_SUPPORT_FAILING_POOL_ALLOC) |
| if (parent && parent->allowFailing) |
| { |
| if ((deRandom_getUint32(&parent->failRandom) & 16383) <= 15) |
| return DE_NULL; |
| } |
| #endif |
| |
| /* Init first page. */ |
| initialPage = MemPage_create(INITIAL_PAGE_SIZE); |
| if (!initialPage) |
| return DE_NULL; |
| |
| /* Alloc pool from initial page. */ |
| DE_ASSERT((int)sizeof(deMemPool) <= initialPage->capacity); |
| pool = (deMemPool*)(initialPage + 1); |
| initialPage->bytesAllocated += (int)sizeof(deMemPool); |
| |
| memset(pool, 0, sizeof(deMemPool)); |
| pool->currentPage = initialPage; |
| |
| /* Register to parent. */ |
| pool->parent = parent; |
| if (parent) |
| { |
| parent->numChildren++; |
| if (parent->firstChild) parent->firstChild->prevPool = pool; |
| pool->nextPool = parent->firstChild; |
| parent->firstChild = pool; |
| } |
| |
| /* Get utils from parent. */ |
| pool->util = parent ? parent->util : DE_NULL; |
| |
| #if defined(DE_SUPPORT_FAILING_POOL_ALLOC) |
| pool->allowFailing = parent ? parent->allowFailing : DE_FALSE; |
| deRandom_init(&pool->failRandom, parent ? deRandom_getUint32(&parent->failRandom) : 0x1234abcd); |
| #endif |
| |
| #if defined(DE_SUPPORT_DEBUG_POOLS) |
| pool->enableDebugAllocs = parent ? parent->enableDebugAllocs : DE_FALSE; |
| pool->debugAllocListHead = DE_NULL; |
| |
| /* Pool allocation index. */ |
| { |
| deMemPool* root = pool; |
| while (root->parent) |
| root = root->parent; |
| |
| if (pool == root) |
| root->lastAllocatedIndex = 0; |
| |
| pool->allocIndex = ++root->lastAllocatedIndex; |
| |
| /* \note Put the index of leaking pool here and add a breakpoint to catch leaks easily. */ |
| /* if (pool->allocIndex == 51) |
| root = root;*/ |
| } |
| #endif |
| |
| return pool; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Create a new root memory pool. |
| * \return The created memory pool (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| deMemPool* deMemPool_createRoot (const deMemPoolUtil* util, deUint32 flags) |
| { |
| deMemPool* pool = createPoolInternal(DE_NULL); |
| if (!pool) |
| return DE_NULL; |
| #if defined(DE_SUPPORT_FAILING_POOL_ALLOC) |
| if (flags & DE_MEMPOOL_ENABLE_FAILING_ALLOCS) |
| pool->allowFailing = DE_TRUE; |
| #endif |
| #if defined(DE_SUPPORT_DEBUG_POOLS) |
| if (flags & DE_MEMPOOL_ENABLE_DEBUG_ALLOCS) |
| { |
| pool->enableDebugAllocs = DE_TRUE; |
| pool->debugAllocListHead = DE_NULL; |
| } |
| #endif |
| DE_UNREF(flags); /* in case no debug features enabled */ |
| |
| /* Get copy of utilities. */ |
| if (util) |
| { |
| deMemPoolUtil* utilCopy = DE_POOL_NEW(pool, deMemPoolUtil); |
| DE_ASSERT(util->allocFailCallback); |
| if (!utilCopy) |
| { |
| deMemPool_destroy(pool); |
| return DE_NULL; |
| } |
| |
| memcpy(utilCopy, util, sizeof(deMemPoolUtil)); |
| pool->util = utilCopy; |
| } |
| |
| return pool; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Create a sub-pool for an existing memory pool. |
| * \return The created memory pool (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| deMemPool* deMemPool_create (deMemPool* parent) |
| { |
| deMemPool* pool; |
| DE_ASSERT(parent); |
| pool = createPoolInternal(parent); |
| if (!pool && parent->util) |
| parent->util->allocFailCallback(parent->util->userPointer); |
| return pool; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Destroy a memory pool. |
| * \param pool Pool to be destroyed. |
| * |
| * Frees all the memory allocated from the pool. Also destroyed any child |
| * pools that the pool has (recursively). |
| *//*--------------------------------------------------------------------*/ |
| void deMemPool_destroy (deMemPool* pool) |
| { |
| deMemPool* iter; |
| deMemPool* iterNext; |
| |
| #if defined(DE_SUPPORT_POOL_MEMORY_TRACKING) |
| /* Update memory consumption statistics. */ |
| if (pool->parent) |
| { |
| deMemPool* root = pool->parent; |
| while (root->parent) |
| root = root->parent; |
| root->maxMemoryAllocated = deMax32(root->maxMemoryAllocated, deMemPool_getNumAllocatedBytes(root, DE_TRUE)); |
| root->maxMemoryCapacity = deMax32(root->maxMemoryCapacity, deMemPool_getCapacity(root, DE_TRUE)); |
| } |
| #endif |
| |
| /* Destroy all children. */ |
| iter = pool->firstChild; |
| while (iter) |
| { |
| iterNext = iter->nextPool; |
| deMemPool_destroy(iter); |
| iter = iterNext; |
| } |
| |
| DE_ASSERT(pool->numChildren == 0); |
| |
| /* Update pointers. */ |
| if (pool->prevPool) pool->prevPool->nextPool = pool->nextPool; |
| if (pool->nextPool) pool->nextPool->prevPool = pool->prevPool; |
| |
| if (pool->parent) |
| { |
| deMemPool* parent = pool->parent; |
| if (parent->firstChild == pool) |
| parent->firstChild = pool->nextPool; |
| |
| parent->numChildren--; |
| DE_ASSERT(parent->numChildren >= 0); |
| } |
| |
| #if defined(DE_SUPPORT_DEBUG_POOLS) |
| /* Free all debug allocations. */ |
| if (pool->enableDebugAllocs) |
| { |
| DebugAlloc* alloc = pool->debugAllocListHead; |
| DebugAlloc* next; |
| |
| while (alloc) |
| { |
| next = alloc->next; |
| deAlignedFree(alloc->memPtr); |
| deFree(alloc); |
| alloc = next; |
| } |
| |
| pool->debugAllocListHead = DE_NULL; |
| } |
| #endif |
| |
| /* Free pages. */ |
| /* \note Pool itself is allocated from first page, so we must not touch the pool after freeing the page! */ |
| { |
| MemPage* page = pool->currentPage; |
| MemPage* nextPage; |
| |
| while (page) |
| { |
| nextPage = page->nextPage; |
| MemPage_destroy(page); |
| page = nextPage; |
| } |
| } |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Get the number of children for a pool. |
| * \return The number of (immediate) child pools a memory pool has. |
| *//*--------------------------------------------------------------------*/ |
| int deMemPool_getNumChildren (const deMemPool* pool) |
| { |
| return pool->numChildren; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Get the number of bytes allocated (by the user) from the pool. |
| * \param pool Pool pointer. |
| * \param recurse Is operation recursive to child pools? |
| * \return The number of bytes allocated by the pool (including child pools |
| * if 'recurse' is true). |
| *//*--------------------------------------------------------------------*/ |
| int deMemPool_getNumAllocatedBytes (const deMemPool* pool, deBool recurse) |
| { |
| int numAllocatedBytes = 0; |
| MemPage* memPage; |
| |
| for (memPage = pool->currentPage; memPage; memPage = memPage->nextPage) |
| numAllocatedBytes += memPage->bytesAllocated; |
| |
| if (recurse) |
| { |
| deMemPool* child; |
| for (child = pool->firstChild; child; child = child->nextPool) |
| numAllocatedBytes += deMemPool_getNumAllocatedBytes(child, DE_TRUE); |
| } |
| |
| return numAllocatedBytes; |
| } |
| |
| int deMemPool_getCapacity (const deMemPool* pool, deBool recurse) |
| { |
| int numCapacityBytes = 0; |
| MemPage* memPage; |
| |
| for (memPage = pool->currentPage; memPage; memPage = memPage->nextPage) |
| numCapacityBytes += memPage->capacity; |
| |
| if (recurse) |
| { |
| deMemPool* child; |
| for (child = pool->firstChild; child; child = child->nextPool) |
| numCapacityBytes += deMemPool_getCapacity(child, DE_TRUE); |
| } |
| |
| return numCapacityBytes; |
| } |
| |
| DE_INLINE void* deMemPool_allocInternal (deMemPool* pool, size_t numBytes, deUint32 alignBytes) |
| { |
| MemPage* curPage = pool->currentPage; |
| |
| #if defined(DE_SUPPORT_FAILING_POOL_ALLOC) |
| if (pool->allowFailing) |
| { |
| if ((deRandom_getUint32(&pool->failRandom) & 16383) <= 15) |
| return DE_NULL; |
| } |
| #endif |
| |
| #if defined(DE_SUPPORT_DEBUG_POOLS) |
| if (pool->enableDebugAllocs) |
| { |
| DebugAlloc* header = DE_NEW(DebugAlloc); |
| void* ptr = deAlignedMalloc(numBytes, alignBytes); |
| |
| if (!header || !ptr) |
| { |
| deFree(header); |
| deAlignedFree(ptr); |
| return DE_NULL; |
| } |
| |
| header->memPtr = ptr; |
| header->next = pool->debugAllocListHead; |
| pool->debugAllocListHead = header; |
| |
| return ptr; |
| } |
| #endif |
| |
| DE_ASSERT(curPage); |
| DE_ASSERT(deIsPowerOfTwo32((int)alignBytes)); |
| { |
| void* curPagePtr = (void*)((deUint8*)(curPage + 1) + curPage->bytesAllocated); |
| void* alignedPtr = deAlignPtr(curPagePtr, alignBytes); |
| size_t alignPadding = (size_t)((deUintptr)alignedPtr - (deUintptr)curPagePtr); |
| |
| if (numBytes + alignPadding > (size_t)(curPage->capacity - curPage->bytesAllocated)) |
| { |
| /* Does not fit to current page. */ |
| int maxAlignPadding = deMax32(0, ((int)alignBytes)-MEM_PAGE_BASE_ALIGN); |
| int newPageCapacity = deMax32(deMin32(2*curPage->capacity, MAX_PAGE_SIZE), ((int)numBytes)+maxAlignPadding); |
| |
| curPage = MemPage_create((size_t)newPageCapacity); |
| if (!curPage) |
| return DE_NULL; |
| |
| curPage->nextPage = pool->currentPage; |
| pool->currentPage = curPage; |
| |
| DE_ASSERT(curPage->bytesAllocated == 0); |
| |
| curPagePtr = (void*)(curPage + 1); |
| alignedPtr = deAlignPtr(curPagePtr, alignBytes); |
| alignPadding = (size_t)((deUintptr)alignedPtr - (deUintptr)curPagePtr); |
| |
| DE_ASSERT(numBytes + alignPadding <= (size_t)curPage->capacity); |
| } |
| |
| curPage->bytesAllocated += (int)(numBytes + alignPadding); |
| return alignedPtr; |
| } |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Allocate memory from a pool. |
| * \param pool Memory pool to allocate from. |
| * \param numBytes Number of bytes to allocate. |
| * \return Pointer to the allocate memory (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| void* deMemPool_alloc (deMemPool* pool, size_t numBytes) |
| { |
| void* ptr; |
| DE_ASSERT(pool); |
| DE_ASSERT(numBytes > 0); |
| ptr = deMemPool_allocInternal(pool, numBytes, DE_POOL_DEFAULT_ALLOC_ALIGNMENT); |
| if (!ptr && pool->util) |
| pool->util->allocFailCallback(pool->util->userPointer); |
| return ptr; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Allocate aligned memory from a pool. |
| * \param pool Memory pool to allocate from. |
| * \param numBytes Number of bytes to allocate. |
| * \param alignBytes Required alignment in bytes, must be power of two. |
| * \return Pointer to the allocate memory (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| void* deMemPool_alignedAlloc (deMemPool* pool, size_t numBytes, deUint32 alignBytes) |
| { |
| void* ptr; |
| DE_ASSERT(pool); |
| DE_ASSERT(numBytes > 0); |
| DE_ASSERT(deIsPowerOfTwo32((int)alignBytes)); |
| ptr = deMemPool_allocInternal(pool, numBytes, alignBytes); |
| DE_ASSERT(deIsAlignedPtr(ptr, alignBytes)); |
| if (!ptr && pool->util) |
| pool->util->allocFailCallback(pool->util->userPointer); |
| return ptr; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Duplicate a piece of memory into a memory pool. |
| * \param pool Memory pool to allocate from. |
| * \param ptr Piece of memory to duplicate. |
| * \return Pointer to the copied memory block (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| void* deMemPool_memDup (deMemPool* pool, const void* ptr, size_t numBytes) |
| { |
| void* newPtr = deMemPool_alloc(pool, numBytes); |
| if (newPtr) |
| memcpy(newPtr, ptr, numBytes); |
| return newPtr; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Duplicate a string into a memory pool. |
| * \param pool Memory pool to allocate from. |
| * \param str String to duplicate. |
| * \return Pointer to the new string (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| char* deMemPool_strDup (deMemPool* pool, const char* str) |
| { |
| size_t len = strlen(str); |
| char* newStr = (char*)deMemPool_alloc(pool, len+1); |
| if (newStr) |
| memcpy(newStr, str, len+1); |
| return newStr; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Duplicate a string into a memory pool, with a maximum length. |
| * \param pool Memory pool to allocate from. |
| * \param str String to duplicate. |
| * \param maxLength Maximum number of characters to duplicate. |
| * \return Pointer to the new string (or null on failure). |
| *//*--------------------------------------------------------------------*/ |
| char* deMemPool_strnDup (deMemPool* pool, const char* str, int maxLength) |
| { |
| size_t len = (size_t)deMin32((int)strlen(str), deMax32(0, maxLength)); |
| char* newStr = (char*)deMemPool_alloc(pool, len + 1); |
| |
| DE_ASSERT(maxLength >= 0); |
| |
| if (newStr) |
| { |
| memcpy(newStr, str, len); |
| newStr[len] = 0; |
| } |
| return newStr; |
| } |
| |
| #if defined(DE_SUPPORT_POOL_MEMORY_TRACKING) |
| |
| int deMemPool_getMaxNumAllocatedBytes (const deMemPool* pool) |
| { |
| DE_ASSERT(pool && !pool->parent); /* must be root */ |
| return deMax32(pool->maxMemoryAllocated, deMemPool_getNumAllocatedBytes(pool, DE_TRUE)); |
| } |
| |
| int deMemPool_getMaxCapacity (const deMemPool* pool) |
| { |
| DE_ASSERT(pool && !pool->parent); /* must be root */ |
| return deMax32(pool->maxMemoryCapacity, deMemPool_getCapacity(pool, DE_TRUE)); |
| } |
| |
| #endif |