diff --git a/source/qcommon/mem.c b/source/qcommon/mem.c index b78735558f..76ba6f6f70 100644 --- a/source/qcommon/mem.c +++ b/source/qcommon/mem.c @@ -57,10 +57,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "mod_mem.h" #include "mem.h" +#define MAX_SCOPE_NAME 32 #define POOLNAMESIZE 128 #define MEMALIGNMENT_DEFAULT 16 #define MIN_MEM_ALIGNMENT sizeof(void*) - #define AllocHashSize ( 1u << 12u ) static const unsigned int prefixPattern = 0xbaadf00d; // Fill pattern for bytes preceeding allocated blocks @@ -71,6 +71,18 @@ static const unsigned int releasedPattern = 0xdeadbeef; // Fill pattern for d static bool memory_initialized = false; static bool commands_initialized = false; +struct memscope_s { + char name[MAX_SCOPE_NAME]; + struct memscope_s *parent; + struct memscope_s *child; + + struct memscope_s *next; + + size_t size; + size_t reserved; + struct memscope_alloc_s *alloc; + qmutex_t* lock; +}; mempool_t* Q_ParentPool() { return NULL; @@ -153,9 +165,9 @@ struct mempool_s }; -cvar_t *developerMemory; +cvar_t *developerMemory; // 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 @@ -166,6 +178,9 @@ static mempool_t *rootChain = NULL; // root chain of mem pool without parents static qmutex_t *memMutex; +// parenting and unparenting should be a thread safe operation +static qmutex_t *scopeMutex; + static struct memheader_s *hashTable[AllocHashSize]; static struct memheader_s *reservoirHeaders; static struct memheader_s **reservoirAllocHeaderBuffer; @@ -505,12 +520,12 @@ void *__Q_MallocAligned(size_t align, size_t size, const char* sourceFilename, c mem->size = size; mem->realsize = realsize; if(sourceFilename) { - strncpy(mem->sourceFile, sourceFilename, sizeof(mem->sourceFile)); + strncpy(mem->sourceFile, sourceFilename, sizeof(mem->sourceFile) - 1); } else { strcpy(mem->sourceFile, "??"); } if(functionName) { - strncpy(mem->sourceFunc, functionName, sizeof(mem->sourceFunc)); + strncpy(mem->sourceFunc, functionName, sizeof(mem->sourceFunc) - 1); } else { strcpy(mem->sourceFunc, "??"); } @@ -526,7 +541,8 @@ mempool_t *Q_CreatePool( mempool_t *parent, const char *name ) mempool_t *pool = (mempool_t *)malloc( sizeof( mempool_t ) ); if( pool == NULL ) _Mem_Error( "Mem_AllocPool: out of memory" ); - + + QMutex_Lock( memMutex ); memset( pool, 0, sizeof( mempool_t ) ); pool->chain = NULL; pool->parent = parent; @@ -542,7 +558,8 @@ mempool_t *Q_CreatePool( mempool_t *parent, const char *name ) pool->next = rootChain; rootChain = pool; } - + QMutex_Unlock(memMutex); + return pool; } @@ -597,12 +614,12 @@ void *__Q_Realloc( void *ptr, size_t size, const char *sourceFilename, const cha mem->baseAddress = baseAddress; mem->sourceLine = sourceLine; if(sourceFilename) { - strncpy(mem->sourceFile, sourceFilename, sizeof(mem->sourceFile)); + strncpy(mem->sourceFile, sourceFilename, sizeof(mem->sourceFile) - 1); } else { strcpy(mem->sourceFile, "??"); } if(functionName) { - strncpy(mem->sourceFunc, functionName, sizeof(mem->sourceFunc)); + strncpy(mem->sourceFunc, functionName, sizeof(mem->sourceFunc) - 1); } else { strcpy(mem->sourceFunc, "??"); } @@ -785,7 +802,7 @@ void *_Mem_AllocExt( mempool_t *pool, size_t size, size_t alignment, int z, int mem->realsize = realsize; mem->sourceLine = fileline; if(filename) { - strncpy(mem->sourceFile, filename, sizeof(mem->sourceFile)); + strncpy(mem->sourceFile, filename, sizeof(mem->sourceFile) - 1); } else { strcpy(mem->sourceFile, "??"); } @@ -910,7 +927,7 @@ mempool_t *_Mem_AllocPool( mempool_t *parent, const char *name, int flags, const pool->child = NULL; pool->totalsize = 0; pool->realsize = sizeof( mempool_t ); - Q_strncpyz( pool->name, name, sizeof( pool->name ) ); + Q_strncpyz( pool->name, name, sizeof( pool->name )); if( parent ) { @@ -938,7 +955,6 @@ mempool_t *_Mem_AllocTempPool( const char *name, const char *filename, int filel void _Mem_FreePool( mempool_t **pool, int musthave, int canthave, const char *filename, int fileline ) { - mempool_t **chainAddress; #ifdef SHOW_NONFREED memheader_t *mem; #endif @@ -978,6 +994,151 @@ void Mem_ValidationAllAllocations() { QMutex_Unlock( memMutex ); } +memscope_t *Q_CreateScope( memscope_t *parent, const char *name ) +{ + memscope_t *scope = Q_Malloc( sizeof(struct memscope_s) ); + if(!scope) + return NULL; + memset(scope, 0, sizeof(struct memscope_s)); + if(name) { + strncpy(scope->name, name, sizeof(parent->name) - 1); + } else { + strncpy(scope->name, "??", sizeof(parent->name) - 1); + } + scope->lock = QMutex_Create(); + if(parent) { + scope->parent = parent; + QMutex_Lock( scopeMutex ); + scope->next = parent->child; + parent->child = scope; + QMutex_Unlock( scopeMutex ); + } + return scope; +} + +void Q_ScopeAttach( memscope_t *scope, void *ptr ) +{ + Q_ScopeAttachWithFreeHandle( scope, Q_Free, ptr ); +} + +void Q_ScopeAttachWithFreeHandle( memscope_t *scope, free_hander_t handle, void *ptr ) +{ + assert( scope ); + if( !ptr ) { + return; + } + + QMutex_Lock(scope->lock); + for( size_t i = 0; i < scope->size; i++ ) { + if( scope->alloc[i].ptr == ptr ) { + // we're already tracking this allocation + QMutex_Unlock(scope->lock); + return; + } + } + + // need to allocate more to the scope + if( ( scope->size + 1 ) > scope->reserved ) { + scope->reserved = ( scope->reserved == 0 ) ? 16 : ( ( scope->reserved >> 1 ) + scope->reserved ); // grow tracked allocations by 1.5 + scope->alloc = Q_Realloc( scope->alloc, scope->reserved * sizeof( struct memscope_s ) ); + } + + struct memscope_alloc_s *alloc = &scope->alloc[scope->size++]; + alloc->ptr = ptr; + alloc->freeHandle = handle; + QMutex_Unlock(scope->lock); +} + +static bool __findMemAllocScope( memscope_t *scope, void *ptr, size_t *index ) +{ + assert( scope ); + if( ptr == NULL ) + return false; + for( size_t i = 0; i < scope->size; i++ ) { + struct memscope_alloc_s *alloc = &scope->alloc[i]; + if( alloc->ptr == ptr ) { + if( index ) { + *index = i; + } + return true; + } + } + return false; +} + +bool Q_ScopeHas( memscope_t *scope, void *ptr ) +{ + return __findMemAllocScope( scope, ptr, NULL ); +} + +void Q_ScopeRelease( memscope_t *scope, void *ptr ) +{ + assert( scope ); + if( ptr == NULL ) + return; + QMutex_Lock(scope->lock); + size_t index; + if( __findMemAllocScope( scope, ptr, &index ) ) { + const size_t bufSize = ( scope->size - ( index + 1 ) ) * sizeof( struct memscope_alloc_s ); + if( bufSize > 0 ) + memmove( &scope->alloc[index], &scope->alloc[index + 1], bufSize ); + scope->size--; + } + QMutex_Unlock(scope->lock); +} + +void Q_ScopeFree( memscope_t *scope, void *ptr ) +{ + assert( scope ); + if( ptr == NULL ) + return; + + QMutex_Lock(scope->lock); + size_t index; + if( __findMemAllocScope( scope, ptr, &index ) ) { + struct memscope_alloc_s *alloc = &scope->alloc[index]; + alloc->freeHandle( alloc->ptr ); + const size_t bufSize = ( scope->size - ( index + 1 ) ) * sizeof( struct memscope_alloc_s ); + if( bufSize > 0 ) + memmove( &scope->alloc[index], &scope->alloc[index + 1], bufSize ); + scope->size--; + } + QMutex_Unlock(scope->lock); +} + +static void __Q_FreeScopeRecuse(memscope_t *scope) { + for( size_t i = 0; i < scope->size; i++ ) { + struct memscope_alloc_s *alloc = &scope->alloc[i]; + alloc->freeHandle( alloc->ptr ); + } + + for( memscope_t *current = scope->child; current != NULL; current = scope->next) { + __Q_FreeScopeRecuse( current ); + } + QMutex_Destroy(&scope->lock); + Q_Free( scope->alloc ); + Q_Free( scope ); +} + +void Q_FreeScope( memscope_t *scope ) +{ + if( !scope ) + return; + + if( scope->parent ) { + QMutex_Lock( scopeMutex ); + struct memscope_s **current = &scope->parent->child; + while( *current && *current != scope ) { + current = &( *current )->next; + } + assert( *current == scope ); // pool was already freed + assert( current != NULL ); + *current = scope->next; + QMutex_Unlock( scopeMutex ); + } + __Q_FreeScopeRecuse( scope ); +} + void _Mem_EmptyPool( mempool_t *pool, int musthave, int canthave, const char *filename, int fileline ) { assert(pool); @@ -1202,6 +1363,7 @@ void Memory_Init( void ) { assert( !memory_initialized ); + scopeMutex = QMutex_Create(); memMutex = QMutex_Create(); zoneMemPool = Mem_AllocPool( NULL, "Zone" ); diff --git a/source/qcommon/mod_mem.h b/source/qcommon/mod_mem.h index fd01ffb6b7..a55abb30e3 100644 --- a/source/qcommon/mod_mem.h +++ b/source/qcommon/mod_mem.h @@ -5,6 +5,15 @@ struct mempool_s; typedef struct mempool_s mempool_t; +struct memscope_s; +typedef struct memscope_s memscope_t; + +typedef void (*free_hander_t)(void* p); + +struct memscope_alloc_s { + void* ptr; + void (*freeHandle)(void* ptr); +}; struct mempool_stats_s { size_t size; @@ -34,28 +43,55 @@ DECLARE_TYPEDEF_METHOD( void, Q_FreePool, mempool_t *pool ); DECLARE_TYPEDEF_METHOD( void, Q_EmptyPool, mempool_t *pool ); DECLARE_TYPEDEF_METHOD( struct mempool_stats_s, Q_PoolStats, mempool_t *pool ); DECLARE_TYPEDEF_METHOD( void, Mem_ValidationAllAllocations ); + + +// similar to a pool but lighter weight constrction +// user is required to free memory from the scope or trigger a double free error from the memory tracker +DECLARE_TYPEDEF_METHOD( memscope_t*, Q_CreateScope, memscope_t *parent, const char* name); +DECLARE_TYPEDEF_METHOD( bool, Q_ScopeHas, memscope_t* scope, void* ptr); +DECLARE_TYPEDEF_METHOD( void, Q_ScopeAttach, memscope_t* scope, void* ptr); // take ownership of allocation +DECLARE_TYPEDEF_METHOD( void, Q_ScopeAttachWithFreeHandle, memscope_t *scope, free_hander_t handle, void *ptr ); // take ownership of allocation with a custom free +DECLARE_TYPEDEF_METHOD( void, Q_ScopeRelease, memscope_t* scope, void* ptr); +DECLARE_TYPEDEF_METHOD( void, Q_ScopeFree, memscope_t* scope, void* ptr); // free the memory attached to a scope +DECLARE_TYPEDEF_METHOD( void, Q_FreeScope, memscope_t* scope); + + #undef DECLARE_TYPEDEF_METHOD mempool_t* Q_ParentPool(); struct mem_import_s { mempool_t* parent; // the parent pool to link to by default if NULL is passed into a pool - Q_PoolStatsFn Q_PoolStats; - __Q_MallocFn __Q_Malloc; - __Q_CallocFn __Q_Calloc; - __Q_ReallocFn __Q_Realloc; - __Q_MallocAlignedFn __Q_MallocAligned; - __Q_CallocAlignedFn __Q_CallocAligned; - Mem_ValidationAllAllocationsFn Mem_ValidationAllAllocations; - Q_FreeFn Q_Free; - Q_CreatePoolFn Q_CreatePool; - Q_LinkToPoolFn Q_LinkToPool; - Q_FreePoolFn Q_FreePool; - Q_EmptyPoolFn Q_EmptyPool; + Q_CreateScopeFn Q_CreateScope; + Q_ScopeHasFn Q_ScopeHas; + Q_ScopeAttachFn Q_ScopeAttach; + Q_ScopeAttachWithFreeHandleFn Q_ScopeAttachWithFreeHandle; + Q_ScopeReleaseFn Q_ScopeRelease; + Q_ScopeFreeFn Q_ScopeFree; + Q_FreeScopeFn Q_FreeScope; + Q_PoolStatsFn Q_PoolStats; + __Q_MallocFn __Q_Malloc; + __Q_CallocFn __Q_Calloc; + __Q_ReallocFn __Q_Realloc; + __Q_MallocAlignedFn __Q_MallocAligned; + __Q_CallocAlignedFn __Q_CallocAligned; + Mem_ValidationAllAllocationsFn Mem_ValidationAllAllocations; + Q_FreeFn Q_Free; + Q_CreatePoolFn Q_CreatePool; + Q_LinkToPoolFn Q_LinkToPool; + Q_FreePoolFn Q_FreePool; + Q_EmptyPoolFn Q_EmptyPool; }; #define DECLARE_MEM_STRUCT(PARENT) { \ PARENT, \ + Q_CreateScope, \ + Q_ScopeHas, \ + Q_ScopeAttach, \ + Q_ScopeAttachWithFreeHandle, \ + Q_ScopeRelease, \ + Q_ScopeFree, \ + Q_FreeScope, \ Q_PoolStats, \ __Q_Malloc, \ __Q_Calloc, \ @@ -73,6 +109,14 @@ struct mem_import_s { #if MEM_DEFINE_INTERFACE_IMPL static struct mem_import_s mem_import; mempool_t* Q_ParentPool() { return mem_import.parent; } + +memscope_t* Q_CreateScope(memscope_t *parent, const char* name) { return mem_import.Q_CreateScope(parent, name);} +bool Q_ScopeHas(memscope_t* scope, void* ptr) { return mem_import.Q_ScopeHas(scope, ptr);} +void Q_ScopeAttach(memscope_t* scope, void* ptr) { return mem_import.Q_ScopeAttach(scope, ptr);} +void Q_ScopeAttachWithFreeHandle(memscope_t *scope, free_hander_t handle, void *ptr ) { return mem_import.Q_ScopeAttachWithFreeHandle(scope, handle, ptr);} +void Q_ScopeRelease(memscope_t* scope, void* ptr) { return mem_import.Q_ScopeRelease(scope, ptr);} +void Q_ScopeFree(memscope_t* scope, void* ptr) { return mem_import.Q_ScopeFree(scope, ptr);} +void Q_FreeScope(memscope_t* scope) { return mem_import.Q_FreeScope(scope);} void *__Q_Malloc( size_t size, const char *sourceFilename, const char *functionName, int sourceLine ) { return mem_import.__Q_Malloc(size, sourceFilename, functionName, sourceLine); } void* __Q_Calloc(size_t count, size_t size, const char *sourceFilename, const char *functionName, int sourceLine ) { return mem_import.__Q_Calloc(count, size, sourceFilename, functionName, sourceLine); } void *__Q_Realloc( void *ptr, size_t size, const char *sourceFilename, const char *functionName, int sourceLine ) { return mem_import.__Q_Realloc(ptr, size, sourceFilename, functionName, sourceLine); } diff --git a/source/ref_gl/r_light.c b/source/ref_gl/r_light.c index f953b99845..1494a27376 100644 --- a/source/ref_gl/r_light.c +++ b/source/ref_gl/r_light.c @@ -763,7 +763,8 @@ void R_InitLightStyles( model_t *mod ) assert( mod ); loadbmodel = (( mbrushmodel_t * )mod->extradata); - loadbmodel->superLightStyles = Mod_Malloc( mod, sizeof( *loadbmodel->superLightStyles ) * MAX_LIGHTSTYLES ); + loadbmodel->superLightStyles = Q_CallocAligned( MAX_LIGHTSTYLES, 16, sizeof( *loadbmodel->superLightStyles ) ); + Q_ScopeAttach(mod->scope, loadbmodel->superLightStyles); loadbmodel->numSuperLightStyles = 0; for( i = 0; i < MAX_LIGHTSTYLES; i++ ) diff --git a/source/ref_gl/r_model.c b/source/ref_gl/r_model.c index 1034c7eb34..4c93c1294f 100644 --- a/source/ref_gl/r_model.c +++ b/source/ref_gl/r_model.c @@ -45,6 +45,7 @@ model_t *r_prevworldmodel; static mapconfig_t *mod_mapConfigs; static mempool_t *mod_mempool; +static memscope_t *mod_scope; static const modelFormatDescr_t mod_supportedformats[] = { @@ -878,6 +879,7 @@ void Mod_Modellist_f( void ) */ void R_InitModels( void ) { + mod_scope = Q_CreateScope(NULL, "Models"); mod_mempool = R_AllocPool( r_mempool, "Models" ); memset( mod_novis, 0xff, sizeof( mod_novis ) ); mod_isworldmodel = false; @@ -945,6 +947,8 @@ void R_ShutdownModels( void ) mod_numknown = 0; memset( mod_known, 0, sizeof( mod_known ) ); + Q_FreeScope(mod_scope); + mod_scope = NULL; R_FreePool( &mod_mempool ); } @@ -1072,10 +1076,16 @@ model_t *Mod_ForName( const char *name, bool crash ) if( mod->mempool ) { R_FreePool( &mod->mempool ); } + if(mod->scope) { + Q_FreeScope(mod->scope); + mod->scope = NULL; + } mod->type = mod_bad; mod->mempool = R_AllocPool( mod_mempool, name ); - mod->name = Mod_Malloc( mod, strlen( name ) + 1 ); + mod->scope = Q_CreateScope(mod_scope, name); + mod->name = Q_Malloc(strlen( name ) + 1 ); + Q_ScopeAttach(mod->scope, mod->name); strcpy( mod->name, name ); // return the NULL model @@ -1135,6 +1145,7 @@ model_t *Mod_ForName( const char *name, bool crash ) lod->type = mod_bad; lod->lodnum = i+1; lod->mempool = R_AllocPool( mod_mempool, lodname ); + lod->scope = Q_CreateScope(mod_scope, name); lod->name = Mod_Malloc( lod, strlen( lodname ) + 1 ); strcpy( lod->name, lodname ); diff --git a/source/ref_gl/r_model.h b/source/ref_gl/r_model.h index a57a0ebd16..2ac022bea5 100644 --- a/source/ref_gl/r_model.h +++ b/source/ref_gl/r_model.h @@ -413,6 +413,7 @@ typedef struct model_s struct model_s *lods[MOD_MAX_LODS]; mempool_t *mempool; + memscope_t *scope; } model_t; //============================================================================