//===== 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_pPrev = 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; }