From 92639b67934e565b28eb1d5a43ce89c5d240cc5f Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:48:58 +0100 Subject: [PATCH] Tier1: reimplement CUtlMemoryPool Class is slightly modified; it uses 2 CBlob pointers instead of a single CBlob member. CUtlMemoryPool::m_pHeadOfFreeList is also replaced with the new CUtlMemoryPool::m_pPrev pointer. Code has been modified to accommodate this change and aligns with the assembly code of r5. --- src/public/tier1/mempool.h | 642 ++++++++++++++++++++++++++++++++++++- src/tier1/CMakeLists.txt | 1 + src/tier1/mempool.cpp | 347 ++++++++++++++++++++ 3 files changed, 980 insertions(+), 10 deletions(-) create mode 100644 src/tier1/mempool.cpp diff --git a/src/public/tier1/mempool.h b/src/public/tier1/mempool.h index e8e2a94f..24727d33 100644 --- a/src/public/tier1/mempool.h +++ b/src/public/tier1/mempool.h @@ -14,22 +14,644 @@ #ifndef MEMPOOL_H #define MEMPOOL_H -struct CUtlMemoryPool +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/memalloc.h" +#include "tier0/tslist.h" +#include "tier0/platform.h" +#include "tier1/utlvector.h" +#include "tier1/utlrbtree.h" + +//----------------------------------------------------------------------------- +// Purpose: Optimized pool memory allocator +//----------------------------------------------------------------------------- + +typedef void (*MemoryPoolReportFunc_t)(PRINTF_FORMAT_STRING char const* pMsg, ...); + +class CUtlMemoryPool { +public: + // Ways the memory pool can grow when it needs to make a new blob. + enum MemoryPoolGrowType_t + { + GROW_NONE = 0, // Don't allow new blobs. + GROW_FAST = 1, // New blob size is numElements * (i+1) (ie: the blocks it allocates + // get larger and larger each time it allocates one). + GROW_SLOW = 2 // New blob size is numElements. + }; + + CUtlMemoryPool(int blockSize, int numElements, int growMode = GROW_FAST, const char* pszAllocOwner = NULL, unsigned short nAlignment = 0); + ~CUtlMemoryPool(); + + void* Alloc(); // Allocate the element size you specified in the constructor. + void* Alloc(size_t amount); + void* AllocZero(); // Allocate the element size you specified in the constructor, zero the memory before construction + void* AllocZero(size_t amount); + void Free(void* pMem); + + // Frees everything + void Clear(); + + // Error reporting... + static void SetErrorReportFunc(MemoryPoolReportFunc_t func); + + // returns number of allocated blocks + int Count() const { return m_BlocksAllocated; } + int PeakCount() const { return m_PeakAlloc; } + int BlockSize() const { return m_BlockSize; } + int Size() const; + + bool IsAllocationWithinPool(void* pMem) const; + +protected: class CBlob { public: - short m_nAlignment; // to int align the struct. - short m_NumBlobs; // Number of blobs. - const char* m_pszAllocOwner; CBlob* m_pPrev, * m_pNext; + int m_NumBytes; // Number of bytes in this blob. + char m_Data[1]; + char m_Padding[3]; // to int align the struct }; - int m_BlockSize; - int m_BlocksPerBlob; - int m_GrowMode; - int m_BlocksAllocated; - CBlob m_BlobHead; + // Resets the pool + void Init(); + void AddNewBlob(); + void ReportLeaks(); + + int m_BlockSize; + int m_BlocksPerBlob; + + int m_GrowMode; // GROW_ enum. + + int m_BlocksAllocated; + int m_PeakAlloc; + unsigned short m_nAlignment; + unsigned short m_NumBlobs; + + const char* m_pszAllocOwner; + + CUtlMemoryPool::CBlob* m_pPrev; + CUtlMemoryPool::CBlob* m_pNext; + + static MemoryPoolReportFunc_t g_ReportFunc; }; -#endif // MEMPOOL_H \ No newline at end of file + +//----------------------------------------------------------------------------- +// Multi-thread/Thread Safe Memory Class +//----------------------------------------------------------------------------- +class CMemoryPoolMT : public CUtlMemoryPool +{ +public: + CMemoryPoolMT(int blockSize, int numElements, int growMode = GROW_FAST, const char* pszAllocOwner = NULL, unsigned short nAlignment = 0) : CUtlMemoryPool(blockSize, numElements, growMode, pszAllocOwner, nAlignment) {} + + + void* Alloc() { AUTO_LOCK(m_mutex); return CUtlMemoryPool::Alloc(); } + void* Alloc(size_t amount) { AUTO_LOCK(m_mutex); return CUtlMemoryPool::Alloc(amount); } + void* AllocZero() { AUTO_LOCK(m_mutex); return CUtlMemoryPool::AllocZero(); } + void* AllocZero(size_t amount) { AUTO_LOCK(m_mutex); return CUtlMemoryPool::AllocZero(amount); } + void Free(void* pMem) { AUTO_LOCK(m_mutex); CUtlMemoryPool::Free(pMem); } + + // Frees everything + void Clear() { AUTO_LOCK(m_mutex); return CUtlMemoryPool::Clear(); } +private: + CThreadFastMutex m_mutex; // @TODO: Rework to use tslist (toml 7/6/2007) +}; + + +//----------------------------------------------------------------------------- +// Wrapper macro to make an allocator that returns particular typed allocations +// and construction and destruction of objects. +//----------------------------------------------------------------------------- +template< class T > +class CClassMemoryPool : public CUtlMemoryPool +{ +public: + CClassMemoryPool(int numElements, int growMode = GROW_FAST, int nAlignment = 0) : + CUtlMemoryPool(sizeof(T), numElements, growMode, MEM_ALLOC_CLASSNAME(T), nAlignment) {} + + T* Alloc(); + T* AllocZero(); + void Free(T* pMem); + + void Clear(); +}; + +//----------------------------------------------------------------------------- +// Specialized pool for aligned data management (e.g., Xbox textures) +//----------------------------------------------------------------------------- +template +class CAlignedMemPool +{ + enum + { + BLOCK_SIZE = COMPILETIME_MAX(ALIGN_VALUE(ITEM_SIZE, ALIGNMENT), 8), + }; + +public: + CAlignedMemPool(); + + void* Alloc(); + void Free(void* p); + + static int __cdecl CompareChunk(void* const* ppLeft, void* const* ppRight); + void Compact(); + + int NumTotal() { AUTO_LOCK(m_mutex); return m_Chunks.Count() * (CHUNK_SIZE / BLOCK_SIZE); } + int NumAllocated() { AUTO_LOCK(m_mutex); return NumTotal() - m_nFree; } + int NumFree() { AUTO_LOCK(m_mutex); return m_nFree; } + + int BytesTotal() { AUTO_LOCK(m_mutex); return NumTotal() * BLOCK_SIZE; } + int BytesAllocated() { AUTO_LOCK(m_mutex); return NumAllocated() * BLOCK_SIZE; } + int BytesFree() { AUTO_LOCK(m_mutex); return NumFree() * BLOCK_SIZE; } + + int ItemSize() { return ITEM_SIZE; } + int BlockSize() { return BLOCK_SIZE; } + int ChunkSize() { return CHUNK_SIZE; } + +private: + struct FreeBlock_t + { + FreeBlock_t* pNext; + byte reserved[BLOCK_SIZE - sizeof(FreeBlock_t*)]; + }; + + CUtlVector m_Chunks; // Chunks are tracked outside blocks (unlike CUtlMemoryPool) to simplify alignment issues + FreeBlock_t* m_pFirstFree; + int m_nFree; + CAllocator m_Allocator; + double m_TimeLastCompact; + + CThreadFastMutex m_mutex; +}; + +//----------------------------------------------------------------------------- +// Pool variant using standard allocation +// TODO[ AMOS ]: commented from here as CTSList isn't implemented yet; +// structure and the size thereof are unknown. +//----------------------------------------------------------------------------- +//template +//class CObjectPool +//{ +//public: +// CObjectPool() +// { +// int i = nInitialCount; +// while (i-- > 0) +// { +// m_AvailableObjects.PushItem(new T); +// } +// } +// +// ~CObjectPool() +// { +// Purge(); +// } +// +// int NumAvailable() +// { +// return m_AvailableObjects.Count(); +// } +// +// void Purge() +// { +// T* p = NULL; +// while (m_AvailableObjects.PopItem(&p)) +// { +// delete p; +// } +// } +// +// T* GetObject(bool bCreateNewIfEmpty = bDefCreateNewIfEmpty) +// { +// T* p = NULL; +// if (!m_AvailableObjects.PopItem(&p)) +// { +// p = (bCreateNewIfEmpty) ? new T : NULL; +// } +// return p; +// } +// +// void PutObject(T* p) +// { +// m_AvailableObjects.PushItem(p); +// } +// +//private: +// CTSList m_AvailableObjects; +//}; + +////----------------------------------------------------------------------------- +//// Fixed budget pool with overflow to malloc +////----------------------------------------------------------------------------- +//template +//class CFixedBudgetMemoryPool +//{ +//public: +// CFixedBudgetMemoryPool() +// { +// m_pBase = m_pLimit = 0; +// COMPILE_TIME_ASSERT(ITEM_SIZE % 4 == 0); +// } +// +// bool Owns(void* p) +// { +// return (p >= m_pBase && p < m_pLimit); +// } +// +// void* Alloc() +// { +// MEM_ALLOC_CREDIT_CLASS(); +//#ifndef USE_MEM_DEBUG +// if (!m_pBase) +// { +// LOCAL_THREAD_LOCK(); +// if (!m_pBase) +// { +// byte* pMemory = m_pBase = (byte*)malloc(ITEM_COUNT * ITEM_SIZE); +// m_pLimit = m_pBase + (ITEM_COUNT * ITEM_SIZE); +// +// for (int i = 0; i < ITEM_COUNT; i++) +// { +// m_freeList.Push((TSLNodeBase_t*)pMemory); +// pMemory += ITEM_SIZE; +// } +// } +// } +// +// void* p = m_freeList.Pop(); +// if (p) +// return p; +//#endif +// return malloc(ITEM_SIZE); +// } +// +// void Free(void* p) +// { +//#ifndef USE_MEM_DEBUG +// if (Owns(p)) +// m_freeList.Push((TSLNodeBase_t*)p); +// else +//#endif +// free(p); +// } +// +// void Clear() +// { +//#ifndef USE_MEM_DEBUG +// if (m_pBase) +// { +// free(m_pBase); +// } +// m_pBase = m_pLimit = 0; +// Construct(&m_freeList); +//#endif +// } +// +// bool IsEmpty() +// { +//#ifndef USE_MEM_DEBUG +// if (m_pBase && m_freeList.Count() != ITEM_COUNT) +// return false; +//#endif +// return true; +// } +// +// enum +// { +// ITEM_SIZE = ALIGN_VALUE(PROVIDED_ITEM_SIZE, TSLIST_NODE_ALIGNMENT) +// }; +// +// CTSListBase m_freeList; +// byte* m_pBase; +// byte* m_pLimit; +//}; +// +//#define BIND_TO_FIXED_BUDGET_POOL( poolName ) \ +// inline void* operator new( size_t size ) { return poolName.Alloc(); } \ +// inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { return poolName.Alloc(); } \ +// inline void operator delete( void* p ) { poolName.Free(p); } \ +// inline void operator delete( void* p, int nBlockUse, const char *pFileName, int nLine ) { poolName.Free(p); } +// +////----------------------------------------------------------------------------- +// +// +//template< class T > +//inline T* CClassMemoryPool::Alloc() +//{ +// T* pRet; +// +// { +// MEM_ALLOC_CREDIT_CLASS(); +// pRet = (T*)CUtlMemoryPool::Alloc(); +// } +// +// if (pRet) +// { +// Construct(pRet); +// } +// return pRet; +//} +// +//template< class T > +//inline T* CClassMemoryPool::AllocZero() +//{ +// T* pRet; +// +// { +// MEM_ALLOC_CREDIT_CLASS(); +// pRet = (T*)CUtlMemoryPool::AllocZero(); +// } +// +// if (pRet) +// { +// Construct(pRet); +// } +// return pRet; +//} +// +//template< class T > +//inline void CClassMemoryPool::Free(T* pMem) +//{ +// if (pMem) +// { +// Destruct(pMem); +// } +// +// CUtlMemoryPool::Free(pMem); +//} +// +//template< class T > +//inline void CClassMemoryPool::Clear() +//{ +// CUtlRBTree freeBlocks; +// SetDefLessFunc(freeBlocks); +// +// void* pCurFree = m_pHeadOfFreeList; +// while (pCurFree != NULL) +// { +// freeBlocks.Insert(pCurFree); +// pCurFree = *((void**)pCurFree); +// } +// +// for (CBlob* pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; pCur = pCur->m_pNext) +// { +// int nElements = pCur->m_NumBytes / this->m_BlockSize; +// T* p = (T*)AlignValue(pCur->m_Data, this->m_nAlignment); +// T* pLimit = p + nElements; +// while (p < pLimit) +// { +// if (freeBlocks.Find(p) == freeBlocks.InvalidIndex()) +// { +// Destruct(p); +// } +// p++; +// } +// } +// +// CUtlMemoryPool::Clear(); +//} +// +// +// +// +// +////----------------------------------------------------------------------------- +//// Macros that make it simple to make a class use a fixed-size allocator +//// Put DECLARE_FIXEDSIZE_ALLOCATOR in the private section of a class, +//// Put DEFINE_FIXEDSIZE_ALLOCATOR in the CPP file +////----------------------------------------------------------------------------- +//#define DECLARE_FIXEDSIZE_ALLOCATOR( _class ) \ +// public: \ +// inline void* operator new( size_t size ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ +// inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ +// inline void operator delete( void* p ) { s_Allocator.Free(p); } \ +// inline void operator delete( void* p, int nBlockUse, const char *pFileName, int nLine ) { s_Allocator.Free(p); } \ +// private: \ +// static CUtlMemoryPool s_Allocator +// +//#define DEFINE_FIXEDSIZE_ALLOCATOR( _class, _initsize, _grow ) \ +// CUtlMemoryPool _class::s_Allocator(sizeof(_class), _initsize, _grow, #_class " pool") +// +//#define DEFINE_FIXEDSIZE_ALLOCATOR_ALIGNED( _class, _initsize, _grow, _alignment ) \ +// CUtlMemoryPool _class::s_Allocator(sizeof(_class), _initsize, _grow, #_class " pool", _alignment ) +// +//#define DECLARE_FIXEDSIZE_ALLOCATOR_MT( _class ) \ +// public: \ +// inline void* operator new( size_t size ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ +// inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_Allocator.Alloc(size); } \ +// inline void operator delete( void* p ) { s_Allocator.Free(p); } \ +// inline void operator delete( void* p, int nBlockUse, const char *pFileName, int nLine ) { s_Allocator.Free(p); } \ +// private: \ +// static CMemoryPoolMT s_Allocator +// +//#define DEFINE_FIXEDSIZE_ALLOCATOR_MT( _class, _initsize, _grow ) \ +// CMemoryPoolMT _class::s_Allocator(sizeof(_class), _initsize, _grow, #_class " pool") +// +////----------------------------------------------------------------------------- +//// Macros that make it simple to make a class use a fixed-size allocator +//// This version allows us to use a memory pool which is externally defined... +//// Put DECLARE_FIXEDSIZE_ALLOCATOR_EXTERNAL in the private section of a class, +//// Put DEFINE_FIXEDSIZE_ALLOCATOR_EXTERNAL in the CPP file +////----------------------------------------------------------------------------- +// +//#define DECLARE_FIXEDSIZE_ALLOCATOR_EXTERNAL( _class ) \ +// public: \ +// inline void* operator new( size_t size ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_pAllocator->Alloc(size); } \ +// inline void* operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) { MEM_ALLOC_CREDIT_(#_class " pool"); return s_pAllocator->Alloc(size); } \ +// inline void operator delete( void* p ) { s_pAllocator->Free(p); } \ +// private: \ +// static CUtlMemoryPool* s_pAllocator +// +//#define DEFINE_FIXEDSIZE_ALLOCATOR_EXTERNAL( _class, _allocator ) \ +// CUtlMemoryPool* _class::s_pAllocator = _allocator +// +// +//template +//inline CAlignedMemPool::CAlignedMemPool() +// : m_pFirstFree(0), +// m_nFree(0), +// m_TimeLastCompact(0) +//{ +// // These COMPILE_TIME_ASSERT checks need to be in individual scopes to avoid build breaks +// // on MacOS and Linux due to a gcc bug. +// { COMPILE_TIME_ASSERT(sizeof(FreeBlock_t) >= BLOCK_SIZE); } +// { COMPILE_TIME_ASSERT(ALIGN_VALUE(sizeof(FreeBlock_t), ALIGNMENT) == sizeof(FreeBlock_t)); } +//} +// +//template +//inline void* CAlignedMemPool::Alloc() +//{ +// AUTO_LOCK(m_mutex); +// +// if (!m_pFirstFree) +// { +// if (!GROWMODE && m_Chunks.Count()) +// { +// return NULL; +// } +// +// FreeBlock_t* pNew = (FreeBlock_t*)m_Allocator.Alloc(CHUNK_SIZE); +// Assert((unsigned)pNew % ALIGNMENT == 0); +// m_Chunks.AddToTail(pNew); +// m_nFree = CHUNK_SIZE / BLOCK_SIZE; +// m_pFirstFree = pNew; +// for (int i = 0; i < m_nFree - 1; i++) +// { +// pNew->pNext = pNew + 1; +// pNew++; +// } +// pNew->pNext = NULL; +// } +// +// void* p = m_pFirstFree; +// m_pFirstFree = m_pFirstFree->pNext; +// m_nFree--; +// +// return p; +//} +// +//template +//inline void CAlignedMemPool::Free(void* p) +//{ +// AUTO_LOCK(m_mutex); +// +// // Insertion sort to encourage allocation clusters in chunks +// FreeBlock_t* pFree = ((FreeBlock_t*)p); +// FreeBlock_t* pCur = m_pFirstFree; +// FreeBlock_t* pPrev = NULL; +// +// while (pCur && pFree > pCur) +// { +// pPrev = pCur; +// pCur = pCur->pNext; +// } +// +// pFree->pNext = pCur; +// +// if (pPrev) +// { +// pPrev->pNext = pFree; +// } +// else +// { +// m_pFirstFree = pFree; +// } +// m_nFree++; +// +// if (m_nFree >= (CHUNK_SIZE / BLOCK_SIZE) * COMPACT_THRESHOLD) +// { +// double time = Plat_FloatTime(); +// double compactTime = (m_nFree >= (CHUNK_SIZE / BLOCK_SIZE) * COMPACT_THRESHOLD * 4) ? 15.0 : 30.0; +// if (m_TimeLastCompact > time || m_TimeLastCompact + compactTime < time) +// { +// Compact(); +// m_TimeLastCompact = time; +// } +// } +//} +// +//template +//inline int __cdecl CAlignedMemPool::CompareChunk(void* const* ppLeft, void* const* ppRight) +//{ +// return size_cast((intp)*ppLeft - (intp)*ppRight); +//} +// +//template +//inline void CAlignedMemPool::Compact() +//{ +// FreeBlock_t* pCur = m_pFirstFree; +// FreeBlock_t* pPrev = NULL; +// +// m_Chunks.Sort(CompareChunk); +// +//#ifdef VALIDATE_ALIGNED_MEM_POOL +// { +// FreeBlock_t* p = m_pFirstFree; +// while (p) +// { +// if (p->pNext && p > p->pNext) +// { +// __asm { int 3 } +// } +// p = p->pNext; +// } +// +// for (int i = 0; i < m_Chunks.Count(); i++) +// { +// if (i + 1 < m_Chunks.Count()) +// { +// if (m_Chunks[i] > m_Chunks[i + 1]) +// { +// __asm { int 3 } +// } +// } +// } +// } +//#endif +// +// int i; +// +// for (i = 0; i < m_Chunks.Count(); i++) +// { +// int nBlocksPerChunk = CHUNK_SIZE / BLOCK_SIZE; +// FreeBlock_t* pChunkLimit = ((FreeBlock_t*)m_Chunks[i]) + nBlocksPerChunk; +// int nFromChunk = 0; +// if (pCur == m_Chunks[i]) +// { +// FreeBlock_t* pFirst = pCur; +// while (pCur && pCur >= m_Chunks[i] && pCur < pChunkLimit) +// { +// pCur = pCur->pNext; +// nFromChunk++; +// } +// pCur = pFirst; +// +// } +// +// while (pCur && pCur >= m_Chunks[i] && pCur < pChunkLimit) +// { +// if (nFromChunk != nBlocksPerChunk) +// { +// if (pPrev) +// { +// pPrev->pNext = pCur; +// } +// else +// { +// m_pFirstFree = pCur; +// } +// pPrev = pCur; +// } +// else if (pPrev) +// { +// pPrev->pNext = NULL; +// } +// else +// { +// m_pFirstFree = NULL; +// } +// +// pCur = pCur->pNext; +// } +// +// if (nFromChunk == nBlocksPerChunk) +// { +// m_Allocator.Free(m_Chunks[i]); +// m_nFree -= nBlocksPerChunk; +// m_Chunks[i] = 0; +// } +// } +// +// for (i = m_Chunks.Count() - 1; i >= 0; i--) +// { +// if (!m_Chunks[i]) +// { +// m_Chunks.FastRemove(i); +// } +// } +//} + +#endif // MEMPOOL_H diff --git a/src/tier1/CMakeLists.txt b/src/tier1/CMakeLists.txt index 2327b300..85b75857 100644 --- a/src/tier1/CMakeLists.txt +++ b/src/tier1/CMakeLists.txt @@ -18,6 +18,7 @@ add_sources( SOURCE_GROUP "Utility" "utlbuffer.cpp" "utlstring.cpp" "characterset.cpp" + "mempool.cpp" #"memstack.cpp" ) diff --git a/src/tier1/mempool.cpp b/src/tier1/mempool.cpp new file mode 100644 index 00000000..6dadf880 --- /dev/null +++ b/src/tier1/mempool.cpp @@ -0,0 +1,347 @@ +//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//=============================================================================// + +#include "tier1/mempool.h" +#include +#include +#include "tier0/dbg.h" +#include +#include "tier1/strtools.h" + +#ifndef _PS3 +#include +#endif + +// Should be last include +#include "tier0/memdbgon.h" +#include + +MemoryPoolReportFunc_t CUtlMemoryPool::g_ReportFunc = 0; + +//----------------------------------------------------------------------------- +// Error reporting... (debug only) +//----------------------------------------------------------------------------- + +void CUtlMemoryPool::SetErrorReportFunc(MemoryPoolReportFunc_t func) +{ + g_ReportFunc = func; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CUtlMemoryPool::CUtlMemoryPool(int blockSize, int numElements, int growMode, const char* pszAllocOwner, unsigned short nAlignment) +{ +#ifdef _X360 + if (numElements > 0 && growMode != GROW_NONE) + { + numElements = 1; + } +#endif + + m_nAlignment = (nAlignment != 0) ? nAlignment : 1; + Assert(IsPowerOfTwo(m_nAlignment)); + m_BlockSize = blockSize < sizeof(void*) ? sizeof(void*) : blockSize; + m_BlockSize = AlignValue(m_BlockSize, m_nAlignment); + m_BlocksPerBlob = numElements; + m_PeakAlloc = 0; + m_GrowMode = growMode; + + if (!pszAllocOwner) + { + pszAllocOwner = __FILE__; + } + + m_pszAllocOwner = pszAllocOwner; + + Init(); + AddNewBlob(); +} + +//----------------------------------------------------------------------------- +// Purpose: Frees the memory contained in the mempool, and invalidates it for +// any further use. +// Input : *memPool - the mempool to shutdown +//----------------------------------------------------------------------------- +CUtlMemoryPool::~CUtlMemoryPool() +{ + if (m_BlocksAllocated > 0) + { + ReportLeaks(); + } + Clear(); +} + + +//----------------------------------------------------------------------------- +// Resets the pool +//----------------------------------------------------------------------------- +void CUtlMemoryPool::Init() +{ + m_NumBlobs = 0; + m_BlocksAllocated = 0; + m_pNext = m_pPrev = NULL; +} + + +//----------------------------------------------------------------------------- +// Frees everything +//----------------------------------------------------------------------------- +void CUtlMemoryPool::Clear() +{ + // Free everything.. + + CBlob* pPrev; + for (CBlob* pCur = m_pNext; pCur; pCur = pPrev) + { + pPrev = pCur->m_pPrev; + free(pCur); + } + Init(); +} + + +//----------------------------------------------------------------------------- +// Is an allocation within the pool? +//----------------------------------------------------------------------------- +bool CUtlMemoryPool::IsAllocationWithinPool(void* pMem) const +{ + for (CBlob* pCur = m_pNext; pCur; pCur = pCur->m_pPrev) + { + // Is the allocation within the blob? + if ((pMem < pCur->m_Data) || (pMem >= pCur->m_Data + pCur->m_NumBytes)) + continue; + + // Make sure the allocation is on a block boundary + intp pFirstAllocation = AlignValue((intp)pCur->m_Data, m_nAlignment); + + intp nOffset = (intp)pMem - pFirstAllocation; + return (nOffset % m_BlockSize) == 0; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Reports memory leaks +//----------------------------------------------------------------------------- +void CUtlMemoryPool::ReportLeaks() +{ +#ifdef _DEBUG + if (!g_ReportFunc) + return; + + g_ReportFunc("Memory leak: mempool blocks left in memory: %d\n", m_BlocksAllocated); + + // walk and destroy the free list so it doesn't intefere in the scan + while (m_pPrev != NULL) + { + void* next = *((void**)m_pPrev); + memset(m_pPrev, 0, m_BlockSize); + m_pPrev = (CBlob*)next; + } + + g_ReportFunc("Dumping memory: \'"); + + for (CBlob* pCur = m_pNext; pCur; pCur = pCur->m_pPrev) + { + // scan the memory block and dump the leaks + char* scanPoint = (char*)pCur->m_Data; + char* scanEnd = pCur->m_Data + pCur->m_NumBytes; + bool needSpace = false; + + while (scanPoint < scanEnd) + { + // search for and dump any strings + if ((unsigned)(*scanPoint + 1) <= 256 && isprint(*scanPoint)) + { + g_ReportFunc("%c", *scanPoint); + needSpace = true; + } + else if (needSpace) + { + needSpace = false; + g_ReportFunc(" "); + } + + scanPoint++; + } + } + + g_ReportFunc("\'\n"); +#endif // _DEBUG +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CUtlMemoryPool::AddNewBlob() +{ + MEM_ALLOC_CREDIT_(m_pszAllocOwner); + + int sizeMultiplier; + + if (m_GrowMode == GROW_SLOW) + { + sizeMultiplier = 1; + } + else + { + if (m_GrowMode == GROW_NONE) + { + // Can only have one allocation when we're in this mode + if (m_NumBlobs != 0) + { + Assert(!"CUtlMemoryPool::AddNewBlob: mode == GROW_NONE"); + return; + } + } + + // GROW_FAST and GROW_NONE use this. + sizeMultiplier = m_NumBlobs + 1; + } + + // maybe use something other than malloc? + int nElements = m_BlocksPerBlob * sizeMultiplier; + int blobSize = m_BlockSize * nElements; + CBlob* pBlob = (CBlob*)malloc(sizeof(CBlob) - 1 + blobSize + (m_nAlignment - 1)); + Assert(pBlob); + + // Link it in at the end of the blob list. + pBlob->m_NumBytes = blobSize; + pBlob->m_pNext = m_pNext; + + m_pNext = pBlob; + m_pPrev = (CBlob*)AlignValue(pBlob->m_Data, m_nAlignment); + + Assert(m_pPrev); + + void** newBlob = (void**)m_pPrev; + for (int j = 0; j < nElements - 1; j++) + { + newBlob[0] = (char*)newBlob + m_BlockSize; + newBlob = (void**)newBlob[0]; + } + + // null terminate list + newBlob[0] = NULL; + m_NumBlobs++; +} + + +void* CUtlMemoryPool::Alloc() +{ + return Alloc(m_BlockSize); +} + + +void* CUtlMemoryPool::AllocZero() +{ + return AllocZero(m_BlockSize); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allocs a single block of memory from the pool. +// Input : amount - +//----------------------------------------------------------------------------- +void* CUtlMemoryPool::Alloc(size_t amount) +{ + if (amount > (size_t)m_BlockSize) + return NULL; + + if (!m_pPrev) + { + // returning NULL is fine in GROW_NONE + if (m_GrowMode == GROW_NONE && m_NumBlobs > 0) + { + Assert( !"CUtlMemoryPool::Alloc: tried to make new blob with GROW_NONE" ); + return NULL; + } + + // overflow + AddNewBlob(); + + // still failure, error out + if (!m_pPrev) + { + Assert(!"CUtlMemoryPool::Alloc: ran out of memory"); + return NULL; + } + } + m_BlocksAllocated++; + m_PeakAlloc = MAX(m_PeakAlloc, m_BlocksAllocated); + + void* returnBlock = m_pPrev; + + // move the pointer the next block + m_pPrev = m_pPrev->m_pPrev; + + return returnBlock; +} + +//----------------------------------------------------------------------------- +// Purpose: Allocs a single block of memory from the pool, zeroes the memory before returning +// Input : amount - +//----------------------------------------------------------------------------- +void* CUtlMemoryPool::AllocZero(size_t amount) +{ + void* mem = Alloc(amount); + if (mem) + { + memset(mem, 0x00, (int)amount); + } + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: Frees a block of memory +// Input : *memBlock - the memory to free +//----------------------------------------------------------------------------- +void CUtlMemoryPool::Free(void* memBlock) +{ + if (!memBlock) + return; // trying to delete NULL pointer, ignore + +#ifdef _DEBUG + // check to see if the memory is from the allocated range + bool bOK = false; + for (CBlob* pCur = m_pNext; pCur; pCur = pCur->m_pPrev) + { + if (memBlock >= pCur->m_Data && (char*)memBlock < (pCur->m_Data + pCur->m_NumBytes)) + { + bOK = true; + } + } + Assert(bOK); +#endif // _DEBUG + +#ifdef _DEBUG + // invalidate the memory + memset(memBlock, 0xDD, m_BlockSize); +#endif + + m_BlocksAllocated--; + + // make the block point to the first item in the list + *((void**)memBlock) = m_pPrev; + + // the list head is now the new block + m_pPrev = (CBlob*)memBlock; +} + +int CUtlMemoryPool::Size() const +{ + uint32 size = 0; + + for (CBlob* pCur = m_pNext; pCur; pCur = pCur->m_pPrev) + { + size += pCur->m_NumBytes; + } + return size; +}