/*------------------------------------------------------------------------- * 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