2018-04-13 18:23:45 +02:00
/*
zone . c - zone memory allocation from DarkPlaces
2023-04-15 01:38:24 +02:00
Copyright ( C ) 1996 - 1997 Id Software , Inc .
Copyright ( C ) 2000 - 2007 DarkPlaces contributors
2018-04-13 18:23:45 +02:00
Copyright ( C ) 2007 Uncle Mike
2023-04-15 01:38:24 +02:00
Copyright ( C ) 2015 - 2023 Xash3D FWGS contributors
2018-04-13 18:23:45 +02:00
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 3 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 .
*/
# include "common.h"
2023-01-12 02:04:53 +01:00
# define MEMHEADER_SENTINEL1 0xDEADF00DU
# define MEMHEADER_SENTINEL2 0xDFU
2018-04-13 18:23:45 +02:00
2019-10-26 07:10:50 +02:00
# ifdef XASH_CUSTOM_SWAP
# include "platform/swap/swap.h"
# define Q_malloc SWAP_Malloc
# define Q_free SWAP_Free
# else
# define Q_malloc malloc
# define Q_free free
# endif
2018-04-13 18:23:45 +02:00
typedef struct memheader_s
{
struct memheader_s * next ; // next and previous memheaders in chain belonging to pool
struct memheader_s * prev ;
struct mempool_s * pool ; // pool this memheader belongs to
size_t size ; // size of the memory after the header (excluding header and sentinel2)
const char * filename ; // file name and line where Mem_Alloc was called
2023-01-12 02:04:53 +01:00
int fileline ;
2023-01-12 02:02:04 +01:00
# if !XASH_64BIT
2023-01-12 02:04:53 +01:00
uint32_t pad0 ; // doesn't have value, only to make Mem_Alloc return aligned addresses on ILP32
2023-01-12 02:02:04 +01:00
# endif
2023-01-12 02:04:53 +01:00
uint32_t sentinel1 ; // should always be MEMHEADER_SENTINEL1
2018-04-13 18:23:45 +02:00
// immediately followed by data, which is followed by a MEMHEADER_SENTINEL2 byte
} memheader_t ;
typedef struct mempool_s
{
2023-01-12 02:04:53 +01:00
uint32_t sentinel1 ; // should always be MEMHEADER_SENTINEL1
2018-04-13 18:23:45 +02:00
struct memheader_s * chain ; // chain of individual memory allocations
size_t totalsize ; // total memory allocated in this pool (inside memheaders)
size_t realsize ; // total memory allocated in this pool (actual malloc total)
size_t lastchecksize ; // updated each time the pool is displayed by memlist
struct mempool_s * next ; // linked into global mempool list
const char * filename ; // file name and line where Mem_AllocPool was called
int fileline ;
2022-12-19 16:38:49 +01:00
# if XASH_64BIT
2021-06-01 18:28:52 +02:00
poolhandle_t idx ;
2022-12-19 16:38:49 +01:00
# endif
2018-04-13 18:23:45 +02:00
char name [ 64 ] ; // name of the pool
2023-01-12 02:04:53 +01:00
uint32_t sentinel2 ; // should always be MEMHEADER_SENTINEL1
2018-04-13 18:23:45 +02:00
} mempool_t ;
2021-06-01 18:28:52 +02:00
static mempool_t * poolchain = NULL ; // critical stuff
2018-04-13 18:23:45 +02:00
2022-12-19 16:38:49 +01:00
# if XASH_64BIT
2021-06-01 18:28:52 +02:00
// a1ba: due to mempool being passed with the model through reused 32-bit field
// which makes engine incompatible with 64-bit pointers I changed mempool type
// from pointer to 32-bit handle, thankfully mempool structure is private
// But! Mempools are handled through linked list so we can't index them safely
2023-01-12 02:04:53 +01:00
static poolhandle_t lastidx = 0 ;
2021-06-01 18:28:52 +02:00
static mempool_t * Mem_FindPool ( poolhandle_t poolptr )
2018-04-13 18:23:45 +02:00
{
2021-06-01 18:28:52 +02:00
mempool_t * pool ;
for ( pool = poolchain ; pool ; pool = pool - > next )
{
if ( pool - > idx = = poolptr )
return pool ;
}
Sys_Error ( " %s: not allocated or double freed pool %d " , __FUNCTION__ , poolptr ) ;
return NULL ;
}
2022-12-19 16:38:49 +01:00
# else
static mempool_t * Mem_FindPool ( poolhandle_t poolptr )
{
return ( mempool_t * ) poolptr ;
}
# endif
2021-06-01 18:28:52 +02:00
void * _Mem_Alloc ( poolhandle_t poolptr , size_t size , qboolean clear , const char * filename , int fileline )
{
memheader_t * mem ;
mempool_t * pool ;
2018-04-13 18:23:45 +02:00
if ( size < = 0 ) return NULL ;
2021-06-01 18:28:52 +02:00
if ( ! poolptr ) Sys_Error ( " Mem_Alloc: pool == NULL (alloc at %s:%i) \n " , filename , fileline ) ;
pool = Mem_FindPool ( poolptr ) ;
2018-04-13 18:23:45 +02:00
pool - > totalsize + = size ;
2018-06-09 00:28:35 +02:00
// big allocations are not clumped
2022-07-16 15:59:41 +02:00
pool - > realsize + = sizeof ( memheader_t ) + size + sizeof ( size_t ) ;
mem = ( memheader_t * ) Q_malloc ( sizeof ( memheader_t ) + size + sizeof ( size_t ) ) ;
2018-06-09 00:28:35 +02:00
if ( mem = = NULL ) Sys_Error ( " Mem_Alloc: out of memory (alloc at %s:%i) \n " , filename , fileline ) ;
2018-04-13 18:23:45 +02:00
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
* ( ( byte * ) 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 ;
2019-10-28 06:25:20 +01:00
if ( clear )
2019-10-26 07:10:50 +02:00
memset ( ( void * ) ( ( byte * ) mem + sizeof ( memheader_t ) ) , 0 , mem - > size ) ;
2018-04-13 18:23:45 +02:00
return ( void * ) ( ( byte * ) mem + sizeof ( memheader_t ) ) ;
}
static const char * Mem_CheckFilename ( const char * filename )
{
static const char * dummy = " <corrupted> \0 " ;
const char * out = filename ;
int i ;
2018-04-26 02:09:36 +02:00
if ( ! COM_CheckString ( out ) )
return dummy ;
2018-06-09 00:28:35 +02:00
for ( i = 0 ; i < MAX_OSPATH ; i + + , out + + )
2018-04-26 02:09:36 +02:00
{
if ( * out = = ' \0 ' )
return filename ; // valid name
}
return dummy ;
2018-04-13 18:23:45 +02:00
}
static void Mem_FreeBlock ( memheader_t * mem , const char * filename , int fileline )
{
mempool_t * pool ;
if ( mem - > sentinel1 ! = MEMHEADER_SENTINEL1 )
{
mem - > filename = Mem_CheckFilename ( mem - > filename ) ; // make sure what we don't crash var_args
Sys_Error ( " Mem_Free: trashed header sentinel 1 (alloc at %s:%i, free at %s:%i) \n " , mem - > filename , mem - > fileline , filename , fileline ) ;
}
if ( * ( ( byte * ) mem + sizeof ( memheader_t ) + mem - > size ) ! = MEMHEADER_SENTINEL2 )
2021-01-03 02:28:45 +01:00
{
2018-04-13 18:23:45 +02:00
mem - > filename = Mem_CheckFilename ( mem - > filename ) ; // make sure what we don't crash var_args
Sys_Error ( " Mem_Free: trashed header sentinel 2 (alloc at %s:%i, free at %s:%i) \n " , mem - > filename , mem - > fileline , filename , fileline ) ;
}
pool = mem - > pool ;
// unlink memheader from doubly linked list
if ( ( mem - > prev ? mem - > prev - > next ! = mem : pool - > chain ! = mem ) | | ( mem - > next & & mem - > next - > prev ! = mem ) )
Sys_Error ( " Mem_Free: not allocated or double freed (free at %s:%i) \n " , 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 ;
2022-07-16 15:59:41 +02:00
pool - > realsize - = sizeof ( memheader_t ) + mem - > size + sizeof ( size_t ) ;
2019-10-26 07:10:50 +02:00
Q_free ( mem ) ;
2018-04-13 18:23:45 +02:00
}
void _Mem_Free ( void * data , const char * filename , int fileline )
{
if ( data = = NULL ) Sys_Error ( " Mem_Free: data == NULL (called at %s:%i) \n " , filename , fileline ) ;
Mem_FreeBlock ( ( memheader_t * ) ( ( byte * ) data - sizeof ( memheader_t ) ) , filename , fileline ) ;
}
2021-06-01 18:28:52 +02:00
void * _Mem_Realloc ( poolhandle_t poolptr , void * memptr , size_t size , qboolean clear , const char * filename , int fileline )
2018-04-13 18:23:45 +02:00
{
memheader_t * memhdr = NULL ;
char * nb ;
if ( size < = 0 ) return memptr ; // no need to reallocate
if ( memptr )
{
memhdr = ( memheader_t * ) ( ( byte * ) memptr - sizeof ( memheader_t ) ) ;
if ( size = = memhdr - > size ) return memptr ;
}
2018-06-09 00:28:35 +02:00
nb = _Mem_Alloc ( poolptr , size , clear , filename , fileline ) ;
2018-04-13 18:23:45 +02:00
if ( memptr ) // first allocate?
2021-01-03 02:28:45 +01:00
{
2018-04-13 18:23:45 +02:00
size_t newsize = memhdr - > size < size ? memhdr - > size : size ; // upper data can be trucnated!
memcpy ( nb , memptr , newsize ) ;
_Mem_Free ( memptr , filename , fileline ) ; // free unused old block
2018-05-28 17:17:25 +02:00
}
2018-04-13 18:23:45 +02:00
return ( void * ) nb ;
}
2021-06-01 18:28:52 +02:00
poolhandle_t _Mem_AllocPool ( const char * name , const char * filename , int fileline )
2018-04-13 18:23:45 +02:00
{
mempool_t * pool ;
2019-10-26 07:10:50 +02:00
pool = ( mempool_t * ) Q_malloc ( sizeof ( mempool_t ) ) ;
2021-06-01 18:28:52 +02:00
if ( pool = = NULL )
{
Sys_Error ( " Mem_AllocPool: out of memory (allocpool at %s:%i) \n " , filename , fileline ) ;
return 0 ;
}
2018-04-13 18:23:45 +02:00
memset ( pool , 0 , sizeof ( mempool_t ) ) ;
// fill header
pool - > sentinel1 = MEMHEADER_SENTINEL1 ;
pool - > sentinel2 = MEMHEADER_SENTINEL1 ;
pool - > filename = filename ;
pool - > fileline = fileline ;
pool - > chain = NULL ;
pool - > totalsize = 0 ;
pool - > realsize = sizeof ( mempool_t ) ;
Q_strncpy ( pool - > name , name , sizeof ( pool - > name ) ) ;
pool - > next = poolchain ;
poolchain = pool ;
2022-12-19 16:38:49 +01:00
# if XASH_64BIT
pool - > idx = + + lastidx ;
2021-06-01 18:28:52 +02:00
return pool - > idx ;
2022-12-19 16:38:49 +01:00
# else
return ( poolhandle_t ) pool ;
# endif
2018-04-13 18:23:45 +02:00
}
2021-06-01 18:28:52 +02:00
void _Mem_FreePool ( poolhandle_t * poolptr , const char * filename , int fileline )
2018-04-13 18:23:45 +02:00
{
2021-06-01 18:28:52 +02:00
mempool_t * pool ;
2018-04-13 18:23:45 +02:00
mempool_t * * chainaddress ;
2019-07-19 19:23:08 +02:00
2021-06-01 18:28:52 +02:00
if ( * poolptr & & ( pool = Mem_FindPool ( * poolptr ) ) )
2018-04-13 18:23:45 +02:00
{
// unlink pool from chain
for ( chainaddress = & poolchain ; * chainaddress & & * chainaddress ! = pool ; chainaddress = & ( ( * chainaddress ) - > next ) ) ;
if ( * chainaddress ! = pool ) Sys_Error ( " Mem_FreePool: pool already free (freepool at %s:%i) \n " , filename , fileline ) ;
if ( pool - > sentinel1 ! = MEMHEADER_SENTINEL1 ) Sys_Error ( " Mem_FreePool: trashed pool sentinel 1 (allocpool at %s:%i, freepool at %s:%i) \n " , pool - > filename , pool - > fileline , filename , fileline ) ;
if ( pool - > sentinel2 ! = MEMHEADER_SENTINEL1 ) Sys_Error ( " Mem_FreePool: trashed pool sentinel 2 (allocpool at %s:%i, freepool at %s:%i) \n " , pool - > filename , pool - > fileline , filename , fileline ) ;
* chainaddress = pool - > next ;
// free memory owned by the pool
while ( pool - > chain ) Mem_FreeBlock ( pool - > chain , filename , fileline ) ;
// free the pool itself
memset ( pool , 0xBF , sizeof ( mempool_t ) ) ;
2019-10-26 07:10:50 +02:00
Q_free ( pool ) ;
2021-06-01 18:28:52 +02:00
* poolptr = 0 ;
2018-04-13 18:23:45 +02:00
}
}
2021-06-01 18:28:52 +02:00
void _Mem_EmptyPool ( poolhandle_t poolptr , const char * filename , int fileline )
2018-04-13 18:23:45 +02:00
{
2021-06-01 18:28:52 +02:00
mempool_t * pool = Mem_FindPool ( poolptr ) ;
if ( ! poolptr ) Sys_Error ( " Mem_EmptyPool: pool == NULL (emptypool at %s:%i) \n " , filename , fileline ) ;
2018-04-13 18:23:45 +02:00
if ( pool - > sentinel1 ! = MEMHEADER_SENTINEL1 ) Sys_Error ( " Mem_EmptyPool: trashed pool sentinel 1 (allocpool at %s:%i, emptypool at %s:%i) \n " , pool - > filename , pool - > fileline , filename , fileline ) ;
if ( pool - > sentinel2 ! = MEMHEADER_SENTINEL1 ) Sys_Error ( " Mem_EmptyPool: trashed pool sentinel 2 (allocpool at %s:%i, emptypool at %s:%i) \n " , pool - > filename , pool - > fileline , filename , fileline ) ;
// free memory owned by the pool
while ( pool - > chain ) Mem_FreeBlock ( pool - > chain , filename , fileline ) ;
}
2021-06-01 18:28:52 +02:00
static qboolean Mem_CheckAlloc ( mempool_t * pool , void * data )
2018-04-13 18:23:45 +02:00
{
memheader_t * header , * target ;
if ( pool )
{
// search only one pool
target = ( memheader_t * ) ( ( byte * ) data - sizeof ( memheader_t ) ) ;
for ( header = pool - > chain ; header ; header = header - > next )
if ( header = = target ) return true ;
}
else
{
// search all pools
for ( pool = poolchain ; pool ; pool = pool - > next )
if ( Mem_CheckAlloc ( pool , data ) )
return true ;
}
return false ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = =
Check pointer for memory
= = = = = = = = = = = = = = = = = = = = = = = =
*/
2021-06-01 18:28:52 +02:00
qboolean Mem_IsAllocatedExt ( poolhandle_t poolptr , void * data )
2018-04-13 18:23:45 +02:00
{
mempool_t * pool = NULL ;
2021-06-01 18:28:52 +02:00
if ( poolptr ) pool = Mem_FindPool ( poolptr ) ;
2018-04-13 18:23:45 +02:00
return Mem_CheckAlloc ( pool , data ) ;
}
2021-06-01 18:28:52 +02:00
static void Mem_CheckHeaderSentinels ( void * data , const char * filename , int fileline )
2018-04-13 18:23:45 +02:00
{
2019-05-19 14:01:23 +02:00
memheader_t * mem ;
2018-04-13 18:23:45 +02:00
if ( data = = NULL )
Sys_Error ( " Mem_CheckSentinels: data == NULL (sentinel check at %s:%i) \n " , filename , fileline ) ;
mem = ( memheader_t * ) ( ( byte * ) data - sizeof ( memheader_t ) ) ;
if ( mem - > sentinel1 ! = MEMHEADER_SENTINEL1 )
{
mem - > filename = Mem_CheckFilename ( mem - > filename ) ; // make sure what we don't crash var_args
Sys_Error ( " Mem_CheckSentinels: trashed header sentinel 1 (block allocated at %s:%i, sentinel check at %s:%i) \n " , mem - > filename , mem - > fileline , filename , fileline ) ;
}
2019-05-19 14:01:23 +02:00
if ( * ( ( byte * ) mem + sizeof ( memheader_t ) + mem - > size ) ! = MEMHEADER_SENTINEL2 )
2021-01-03 02:28:45 +01:00
{
2018-04-13 18:23:45 +02:00
mem - > filename = Mem_CheckFilename ( mem - > filename ) ; // make sure what we don't crash var_args
Sys_Error ( " Mem_CheckSentinels: trashed header sentinel 2 (block allocated at %s:%i, sentinel check at %s:%i) \n " , mem - > filename , mem - > fileline , filename , fileline ) ;
}
}
void _Mem_Check ( const char * filename , int fileline )
{
2021-06-01 18:28:52 +02:00
memheader_t * mem ;
mempool_t * pool ;
2018-04-13 18:23:45 +02:00
for ( pool = poolchain ; pool ; pool = pool - > next )
{
if ( pool - > sentinel1 ! = MEMHEADER_SENTINEL1 )
Sys_Error ( " Mem_CheckSentinelsGlobal: trashed pool sentinel 1 (allocpool at %s:%i, sentinel check at %s:%i) \n " , pool - > filename , pool - > fileline , filename , fileline ) ;
if ( pool - > sentinel2 ! = MEMHEADER_SENTINEL1 )
Sys_Error ( " Mem_CheckSentinelsGlobal: trashed pool sentinel 2 (allocpool at %s:%i, sentinel check at %s:%i) \n " , pool - > filename , pool - > fileline , filename , fileline ) ;
}
for ( pool = poolchain ; pool ; pool = pool - > next )
for ( mem = pool - > chain ; mem ; mem = mem - > next )
Mem_CheckHeaderSentinels ( ( void * ) ( ( byte * ) mem + sizeof ( memheader_t ) ) , filename , fileline ) ;
}
void Mem_PrintStats ( void )
{
2021-06-01 18:28:52 +02:00
size_t count = 0 , size = 0 , realsize = 0 ;
mempool_t * pool ;
2018-04-13 18:23:45 +02:00
Mem_Check ( ) ;
for ( pool = poolchain ; pool ; pool = pool - > next )
{
count + + ;
size + = pool - > totalsize ;
realsize + = pool - > realsize ;
}
2018-10-06 02:34:01 +02:00
Con_Printf ( " ^3%lu^7 memory pools, totalling: ^1%s \n " , count , Q_memprint ( size ) ) ;
2018-04-13 18:23:45 +02:00
Con_Printf ( " total allocated size: ^1%s \n " , Q_memprint ( realsize ) ) ;
}
void Mem_PrintList ( size_t minallocationsize )
{
mempool_t * pool ;
memheader_t * mem ;
Mem_Check ( ) ;
Con_Printf ( " memory pool list: \n " " ^3size name \n " ) ;
for ( pool = poolchain ; pool ; pool = pool - > next )
{
long changed_size = ( long ) pool - > totalsize - ( long ) pool - > lastchecksize ;
// poolnames can contain color symbols, make sure what color is reset
if ( changed_size ! = 0 )
{
char sign = ( changed_size < 0 ) ? ' - ' : ' + ' ;
Con_Printf ( " %10s (%10s actual) %s (^7%c%s change) \n " , Q_memprint ( pool - > totalsize ) , Q_memprint ( pool - > realsize ) ,
pool - > name , sign , Q_memprint ( abs ( changed_size ) ) ) ;
}
else
{
Con_Printf ( " %5s (%5s actual) %s \n " , Q_memprint ( pool - > totalsize ) , Q_memprint ( pool - > realsize ) , pool - > name ) ;
}
pool - > lastchecksize = pool - > totalsize ;
for ( mem = pool - > chain ; mem ; mem = mem - > next )
if ( mem - > size > = minallocationsize )
Con_Printf ( " %10s allocated at %s:%i \n " , Q_memprint ( mem - > size ) , mem - > filename , mem - > fileline ) ;
}
}
/*
= = = = = = = = = = = = = = = = = = = = = = = =
Memory_Init
= = = = = = = = = = = = = = = = = = = = = = = =
*/
void Memory_Init ( void )
{
poolchain = NULL ; // init mem chain
2018-04-23 22:07:54 +02:00
}