/* Copyright (C) 2002-2003 Victor Luchits This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // Z_zone.c #include "qcommon.h" cvar_t *developerMemory; mempool_t *poolChain = NULL; // used for temporary memory allocations around the engine, not for longterm // storage, if anything in this pool stays allocated during gameplay, it is // considered a leak mempool_t *tempMemPool; // only for zone mempool_t *zoneMemPool; void _Mem_Error( char *error, ... ) { #ifdef _DEBUG va_list argptr; char text[1024]; va_start (argptr, error); vsnprintf (text, sizeof(text), error, argptr); va_end (argptr); text[sizeof(text)-1] = 0; Sys_Error(text); #endif } void *_Mem_AllocExt( mempool_t *pool, int size, int z, int musthave, int canthave, const char *filename, int fileline ) { #ifdef MEMCLUMPING int i, j, k, needed, endbit, largest; memclump_t *clump, **clumpChainPointer; #endif memheader_t *mem; if( size <= 0 ) return NULL; if( pool == NULL ) _Mem_Error( "Mem_Alloc: pool == NULL (alloc at %s:%i)", filename, fileline ); if( musthave && ((pool->flags & musthave) != musthave) ) _Mem_Error( "Mem_Alloc: bad pool flags (musthave) (alloc at %s:%i)", filename, fileline ); if( canthave && (pool->flags & canthave) ) _Mem_Error( "Mem_Alloc: bad pool flags (canthave) (alloc at %s:%i)", filename, fileline ); if( developerMemory && developerMemory->integer ) Com_DPrintf( "Mem_Alloc: pool %s, file %s:%i, size %i bytes\n", pool->name, filename, fileline, size ); pool->totalsize += size; #ifdef MEMCLUMPING if( size < 4096 ) { // clumping needed = (sizeof( memheader_t ) + size + sizeof( int ) + (MEMUNIT - 1)) / MEMUNIT; endbit = MEMBITS - needed; for ( clumpChainPointer = &pool->clumpchain; *clumpChainPointer; clumpChainPointer = &(*clumpChainPointer)->chain ) { clump = *clumpChainPointer; if( clump->sentinel1 != MEMCLUMP_SENTINEL ) _Mem_Error( "Mem_Alloc: trashed clump sentinel 1 (alloc at %s:%d)", filename, fileline ); if( clump->sentinel2 != MEMCLUMP_SENTINEL ) _Mem_Error( "Mem_Alloc: trashed clump sentinel 2 (alloc at %s:%d)", filename, fileline ); if( clump->largestavailable >= needed ) { largest = 0; for( i = 0; i < endbit; i++ ) { if( clump->bits[i >> 5] & (1 << (i & 31)) ) continue; k = i + needed; for( j = i; i < k; i++ ) { if( clump->bits[i >> 5] & (1 << (i & 31)) ) goto loopcontinue; } goto choseclump; loopcontinue:; if( largest < j - i ) largest = j - i; } // since clump falsely advertised enough space (nothing wrong // with that), update largest count to avoid wasting time in // later allocations clump->largestavailable = largest; } } pool->realsize += sizeof( memclump_t ); clump = malloc( sizeof( memclump_t ) ); if( clump == NULL ) _Mem_Error( "Mem_Alloc: out of memory (alloc at %s:%i)", filename, fileline ); memset( clump, 0, sizeof( memclump_t ) ); clump->sentinel1 = MEMCLUMP_SENTINEL; clump->sentinel2 = MEMCLUMP_SENTINEL; clump->chain = NULL; clump->blocksinuse = 0; clump->largestavailable = MEMBITS - needed; *clumpChainPointer = clump; j = 0; choseclump: mem = ( memheader_t * )(( qbyte * ) clump->block + j * MEMUNIT); mem->clump = clump; clump->blocksinuse += needed; for( i = j + needed; j < i; j++ ) clump->bits[j >> 5] |= (1 << (j & 31)); } else { // big allocations are not clumped #endif pool->realsize += sizeof( memheader_t ) + size + sizeof( int ); mem = malloc( sizeof( memheader_t ) + size + sizeof( int ) ); if( mem == NULL ) _Mem_Error( "Mem_Alloc: out of memory (alloc at %s:%i)", filename, fileline ); #ifdef MEMCLUMPING mem->clump = NULL; } #endif mem->filename = filename; mem->fileline = fileline; mem->size = size; mem->pool = pool; mem->sentinel1 = MEMHEADER_SENTINEL1; // we have to use only a single byte for this sentinel, because it may not be aligned, and some platforms can't use unaligned accesses *((qbyte *) mem + sizeof(memheader_t) + mem->size) = MEMHEADER_SENTINEL2; // append to head of list mem->next = pool->chain; mem->prev = NULL; pool->chain = mem; if ( mem->next ) mem->next->prev = mem; if( z ) memset((void *)((qbyte *) mem + sizeof(memheader_t)), 0, mem->size); return (void *)((qbyte *) mem + sizeof(memheader_t)); } void *_Mem_Alloc( mempool_t *pool, int size, int musthave, int canthave, const char *filename, int fileline ) { return _Mem_AllocExt( pool, size, 1, musthave, canthave, filename, fileline ); } // FIXME: rewrite this? void *_Mem_Realloc( void *data, int size, const char *filename, int fileline ) { void *newdata; memheader_t *mem; if( data == NULL ) _Mem_Error( "Mem_Realloc: data == NULL (called at %s:%i)", filename, fileline ); if( size <= 0 ) { Mem_Free( data ); return NULL; } mem = ( memheader_t * )((qbyte *) data - sizeof(memheader_t)); if( size <= mem->size ) return data; newdata = Mem_Alloc( mem->pool, size ); memcpy( newdata, data, mem->size ); Mem_Free( data ); return newdata; } void _Mem_Free( void *data, int musthave, int canthave, const char *filename, int fileline ) { #ifdef MEMCLUMPING int i, firstblock, endblock; memclump_t *clump, **clumpChainPointer; #endif memheader_t *mem; mempool_t *pool; if( data == NULL ) // _Mem_Error( "Mem_Free: data == NULL (called at %s:%i)", filename, fileline ); return; mem = ( memheader_t * )((qbyte *) data - sizeof(memheader_t)); if( mem->sentinel1 != MEMHEADER_SENTINEL1 ) _Mem_Error( "Mem_Free: trashed header sentinel 1 (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline ); if( *((qbyte *)mem + sizeof(memheader_t) + mem->size) != MEMHEADER_SENTINEL2 ) _Mem_Error( "Mem_Free: trashed header sentinel 2 (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline ); pool = mem->pool; if( musthave && ((pool->flags & musthave) != musthave) ) _Mem_Error( "Mem_Free: bad pool flags (musthave) (alloc at %s:%i)", filename, fileline ); if( canthave && (pool->flags & canthave) ) _Mem_Error( "Mem_Free: bad pool flags (canthave) (alloc at %s:%i)", filename, fileline ); if( developerMemory && developerMemory->integer ) Com_DPrintf( "Mem_Free: pool %s, alloc %s:%i, free %s:%i, size %i bytes\n", pool->name, mem->filename, mem->fileline, filename, fileline, mem->size ); // unlink memheader from doubly linked list if( (mem->prev ? mem->prev->next != mem : pool->chain != mem) || (mem->next && mem->next->prev != mem) ) _Mem_Error( "Mem_Free: not allocated or double freed (free at %s:%i)", filename, fileline ); if( mem->prev ) mem->prev->next = mem->next; else pool->chain = mem->next; if( mem->next ) mem->next->prev = mem->prev; // memheader has been unlinked, do the actual free now pool->totalsize -= mem->size; #ifdef MEMCLUMPING if ( (clump = mem->clump) ) { if( clump->sentinel1 != MEMCLUMP_SENTINEL ) _Mem_Error( "Mem_Free: trashed clump sentinel 1 (free at %s:%i)", filename, fileline ); if( clump->sentinel2 != MEMCLUMP_SENTINEL ) _Mem_Error( "Mem_Free: trashed clump sentinel 2 (free at %s:%i)", filename, fileline ); firstblock = ((qbyte *) mem - (qbyte *) clump->block); if( firstblock & (MEMUNIT - 1) ) _Mem_Error( "Mem_Free: address not valid in clump (free at %s:%i)", filename, fileline ); firstblock /= MEMUNIT; endblock = firstblock + ((sizeof(memheader_t) + mem->size + sizeof(int) + (MEMUNIT - 1)) / MEMUNIT); clump->blocksinuse -= endblock - firstblock; // could use &, but we know the bit is set for( i = firstblock;i < endblock; i++ ) clump->bits[i >> 5] -= (1 << (i & 31)); if( clump->blocksinuse <= 0 ) { // unlink from chain for( clumpChainPointer = &pool->clumpchain; *clumpChainPointer; clumpChainPointer = &(*clumpChainPointer)->chain ) { if( *clumpChainPointer == clump ) { *clumpChainPointer = clump->chain; break; } } pool->realsize -= sizeof(memclump_t); #ifdef MEMTRASH memset( clump, 0xBF, sizeof(memclump_t) ); #endif free( clump ); } else { // clump still has some allocations // force re-check of largest available space on next alloc clump->largestavailable = MEMBITS - clump->blocksinuse; } } else { #endif pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( int ); #ifdef MEMTRASH memset( mem, 0xBF, sizeof( memheader_t ) + mem->size + sizeof( int ) ); #endif free( mem ); #ifdef MEMCLUMPING } #endif } mempool_t *_Mem_AllocPool( mempool_t *parent, const char *name, int flags, const char *filename, int fileline ) { mempool_t *pool; if( parent && (parent->flags & MEMPOOL_TEMPORARY) ) _Mem_Error( "Mem_AllocPool: nested temporary pools are not allowed (allocpool at %s:%i)", filename, fileline ); if( flags & MEMPOOL_TEMPORARY ) _Mem_Error( "Mem_AllocPool: tried to allocate temporary pool, use Mem_AllocTempPool instead (allocpool at %s:%i)", filename, fileline ); pool = malloc( sizeof(mempool_t) ); if( pool == NULL ) _Mem_Error( "Mem_AllocPool: out of memory (allocpool at %s:%i)", filename, fileline ); memset( pool, 0, sizeof( mempool_t ) ); pool->sentinel1 = MEMHEADER_SENTINEL1; pool->sentinel2 = MEMHEADER_SENTINEL1; pool->filename = filename; pool->fileline = fileline; pool->flags = flags; pool->chain = NULL; pool->parent = parent; pool->child = NULL; pool->totalsize = 0; pool->realsize = sizeof( mempool_t ); Q_strncpyz( pool->name, name, sizeof( pool->name ) ); if( parent ) { pool->next = parent->child; parent->child = pool; } else { pool->next = poolChain; poolChain = pool; } return pool; } mempool_t *_Mem_AllocTempPool( const char *name, const char *filename, int fileline ) { mempool_t *pool; pool = _Mem_AllocPool( NULL, name, 0, filename, fileline ); pool->flags = MEMPOOL_TEMPORARY; return pool; } void _Mem_FreePool( mempool_t **pool, int musthave, int canthave, const char *filename, int fileline ) { mempool_t **chainAddress, **next; if( !(*pool) ) return; if ( musthave && (((*pool)->flags & musthave) != musthave) ) _Mem_Error( "Mem_FreePool: bad pool flags (musthave) (alloc at %s:%i)", filename, fileline ); if ( canthave && ((*pool)->flags & canthave) ) _Mem_Error( "Mem_FreePool: bad pool flags (canthave) (alloc at %s:%i)", filename, fileline ); // recurse into children // note that children will be freed no matter if their flags // do not match musthave\canthave pair if( (*pool)->child ) { for( chainAddress = &(*pool)->child; *chainAddress; chainAddress = next ) { next = &((*chainAddress)->next); _Mem_FreePool( chainAddress, 0, 0, filename, fileline ); } } if( (*pool)->sentinel1 != MEMHEADER_SENTINEL1 ) _Mem_Error( "Mem_FreePool: trashed pool sentinel 1 (allocpool at %s:%i, freepool at %s:%i)", (*pool)->filename, (*pool)->fileline, filename, fileline ); if( (*pool)->sentinel2 != MEMHEADER_SENTINEL1 ) _Mem_Error( "Mem_FreePool: trashed pool sentinel 2 (allocpool at %s:%i, freepool at %s:%i)", (*pool)->filename, (*pool)->fileline, filename, fileline ); // unlink pool from chain if( (*pool)->parent ) for( chainAddress = &(*pool)->parent->child; *chainAddress && *chainAddress != *pool; chainAddress = &((*chainAddress)->next) ); else for( chainAddress = &poolChain; *chainAddress && *chainAddress != *pool; chainAddress = &((*chainAddress)->next) ); if( *chainAddress != *pool ) _Mem_Error( "Mem_FreePool: pool already free (freepool at %s:%i)", filename, fileline ); *chainAddress = (*pool)->next; while( (*pool)->chain ) // free memory owned by the pool Mem_Free( (void *)((qbyte *)(*pool)->chain + sizeof(memheader_t)) ); // free the pool itself #ifdef MEMTRASH memset( *pool, 0xBF, sizeof(mempool_t) ); #endif free( *pool ); *pool = NULL; } void _Mem_EmptyPool( mempool_t *pool, int musthave, int canthave, const char *filename, int fileline ) { mempool_t *child, *next; if( pool == NULL ) _Mem_Error( "Mem_EmptyPool: pool == NULL (emptypool at %s:%i)", filename, fileline ); if( musthave && ((pool->flags & musthave) != musthave) ) _Mem_Error( "Mem_EmptyPool: bad pool flags (musthave) (alloc at %s:%i)", filename, fileline ); if( canthave && (pool->flags & canthave) ) _Mem_Error( "Mem_EmptyPool: bad pool flags (canthave) (alloc at %s:%i)", filename, fileline ); // recurse into children if( pool->child ) { for( child = pool->child; child; child = next ) { next = child->next; _Mem_EmptyPool( child, 0, 0, filename, fileline ); } } if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) _Mem_Error( "Mem_EmptyPool: trashed pool sentinel 1 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline ); if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) _Mem_Error( "Mem_EmptyPool: trashed pool sentinel 2 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline ); while( pool->chain ) // free memory owned by the pool Mem_Free( (void *)((qbyte *) pool->chain + sizeof(memheader_t)) ); } void _Mem_CheckSentinels( void *data, const char *filename, int fileline ) { memheader_t *mem; if( data == NULL ) _Mem_Error( "Mem_CheckSentinels: data == NULL (sentinel check at %s:%i)", filename, fileline ); mem = (memheader_t *)((qbyte *) data - sizeof(memheader_t)); if( mem->sentinel1 != MEMHEADER_SENTINEL1 ) _Mem_Error( "Mem_CheckSentinels: trashed header sentinel 1 (block allocated at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline ); if( *((qbyte *) mem + sizeof(memheader_t) + mem->size) != MEMHEADER_SENTINEL2 ) _Mem_Error( "Mem_CheckSentinels: trashed header sentinel 2 (block allocated at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline ); } #ifdef MEMCLUMPING static void _Mem_CheckClumpSentinels( memclump_t *clump, const char *filename, int fileline ) { // this isn't really very useful if( clump->sentinel1 != MEMCLUMP_SENTINEL ) _Mem_Error( "Mem_CheckClumpSentinels: trashed sentinel 1 (sentinel check at %s:%i)", filename, fileline ); if( clump->sentinel2 != MEMCLUMP_SENTINEL ) _Mem_Error( "Mem_CheckClumpSentinels: trashed sentinel 2 (sentinel check at %s:%i)", filename, fileline ); } #endif void _Mem_CheckSentinelsPool( mempool_t *pool, const char *filename, int fileline ) { memheader_t *mem; #ifdef MEMCLUMPING memclump_t *clump; #endif mempool_t *child; // recurse into children if ( pool->child ) { for( child = pool->child; child; child = child->next ) _Mem_CheckSentinelsPool( child, filename, fileline ); } if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) _Mem_Error( "_Mem_CheckSentinelsPool: trashed pool sentinel 1 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline ); if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) _Mem_Error( "_Mem_CheckSentinelsPool: trashed pool sentinel 2 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline ); for( mem = pool->chain; mem; mem = mem->next ) _Mem_CheckSentinels((void *)((qbyte *) mem + sizeof(memheader_t)), filename, fileline); #ifdef MEMCLUMPING for( clump = pool->clumpchain; clump; clump = clump->chain ) _Mem_CheckClumpSentinels( clump, filename, fileline ); #endif } void _Mem_CheckSentinelsGlobal( const char *filename, int fileline ) { mempool_t *pool; for( pool = poolChain; pool; pool = pool->next ) _Mem_CheckSentinelsPool( pool, filename, fileline ); } void Mem_CountPoolStats( mempool_t *pool, int *count, int *size, int *realsize ) { mempool_t *child; // recurse into children if( pool->child ) { for( child = pool->child; child; child = child->next ) Mem_CountPoolStats( child, count, size, realsize ); } if( count ) (*count)++; if( size ) (*size) += pool->totalsize; if( realsize ) (*realsize) += pool->realsize; } void Mem_PrintStats (void) { int count, size, real; int total, totalsize, realsize; mempool_t *pool; memheader_t *mem; Mem_CheckSentinelsGlobal (); for( total = 0, totalsize = 0, realsize = 0, pool = poolChain; pool; pool = pool->next ) { count = 0; size = 0; real = 0; Mem_CountPoolStats( pool, &count, &size, &real ); total += count; totalsize += size; realsize += real; } Com_Printf( "%i memory pools, totalling %i bytes (%.3fMB), %i bytes (%.3fMB) actual\n", total, totalsize, totalsize / 1048576.0, realsize, realsize / 1048576.0 ); // temporary pools are not nested for ( pool = poolChain; pool; pool = pool->next ) { if ( (pool->flags & MEMPOOL_TEMPORARY) && pool->chain ) { Com_Printf( "%i bytes (%.3fMB) (%i bytes (%.3fMB actual)) of temporary memory still allocated (Leak!)\n", pool->totalsize, pool->totalsize / 1048576.0, pool->realsize, pool->realsize / 1048576.0 ); Com_Printf( "listing temporary memory allocations for %s:\n", pool->name ); for( mem = tempMemPool->chain; mem; mem = mem->next) Com_Printf( "%10i bytes allocated at %s:%i\n", mem->size, mem->filename, mem->fileline ); } } } void Mem_PrintPoolStats( mempool_t *pool, int listchildren, int listallocations ) { mempool_t *child; memheader_t *mem; int totalsize = 0, realsize = 0; Mem_CountPoolStats ( pool, NULL, &totalsize, &realsize ); if ( pool->parent ) { if( pool->lastchecksize != 0 && totalsize != pool->lastchecksize ) Com_Printf( "%6ik (%6ik actual) %s:%s (%i byte change)\n", (totalsize + 1023) / 1024, (realsize + 1023) / 1024, pool->parent->name, pool->name, totalsize - pool->lastchecksize ); else Com_Printf( "%6ik (%6ik actual) %s:%s\n", (totalsize + 1023) / 1024, (realsize + 1023) / 1024, pool->parent->name, pool->name ); } else { if( pool->lastchecksize != 0 && totalsize != pool->lastchecksize ) Com_Printf( "%6ik (%6ik actual) %s (%i byte change)\n", (totalsize + 1023) / 1024, (realsize + 1023) / 1024, pool->name, totalsize - pool->lastchecksize ); else Com_Printf( "%6ik (%6ik actual) %s\n", (totalsize + 1023) / 1024, (realsize + 1023) / 1024, pool->name ); } pool->lastchecksize = totalsize; if( listallocations ) { for( mem = pool->chain; mem; mem = mem->next ) Com_Printf ("%10i bytes allocated at %s:%i\n", mem->size, mem->filename, mem->fileline); } if( listchildren ) { if( pool->child ) { for( child = pool->child; child; child = child->next ) Mem_PrintPoolStats ( child, listchildren, listallocations ); } } } void Mem_PrintList( int listchildren, int listallocations ) { mempool_t *pool; Mem_CheckSentinelsGlobal (); Com_Printf( "memory pool list:\n" "size name\n" ); for( pool = poolChain; pool; pool = pool->next ) Mem_PrintPoolStats( pool, listchildren, listallocations ); } void MemList_f( void ) { switch ( Cmd_Argc () ) { case 1: Mem_PrintList( qtrue, qfalse ); Mem_PrintStats (); break; case 2: if( !Q_stricmp( Cmd_Argv( 1 ), "all" ) ) { Mem_PrintList( qtrue, qtrue ); Mem_PrintStats (); break; } // drop through default: Com_Printf( "MemList_f: unrecognized options\nusage: memlist [all]\n" ); break; } } void MemStats_f( void ) { Mem_CheckSentinelsGlobal (); Mem_PrintStats (); } /* ======================== Memory_Init ======================== */ void Memory_Init( void ) { zoneMemPool = Mem_AllocPool( NULL, "Zone" ); tempMemPool = Mem_AllocTempPool( "Temporary Memory" ); } /* ======================== Memory_InitCommands ======================== */ void Memory_InitCommands( void ) { developerMemory = Cvar_Get( "developerMemory", "0", 0 ); Cmd_AddCommand( "memlist", MemList_f ); Cmd_AddCommand( "memstats", MemStats_f ); } /* ======================== Memory_Shutdown NOTE: Should be the last called function before shutdown! ======================== */ void Memory_Shutdown( void ) { static qboolean isdown = qfalse; mempool_t *pool, *next; if( isdown ) return; // set the cvar to NULL so nothing is printed to non-existing console developerMemory = NULL; Cmd_RemoveCommand( "memlist" ); Cmd_RemoveCommand( "memstats" ); isdown = qtrue; Mem_CheckSentinelsGlobal (); for( pool = poolChain; pool; pool = next ){ // do it here, because pool is to be freed // and the chain will be broken next = pool->next; Mem_FreePool( &pool ); } }