/******************************************** zmalloc.c copyright 2008-2023,2024, Thomas E. Dickey copyright 1991-1993,1995, Michael D. Brennan This is a source file for mawk, an implementation of the AWK programming language. Mawk is distributed without warranty under the terms of the GNU General Public License, version 2, 1991. ********************************************/ /* * $MawkId: zmalloc.c,v 1.38 2024/12/14 21:21:20 tom Exp $ */ /* zmalloc.c */ #include #include #if defined(NO_LEAKS) && defined(HAVE_TSEARCH) #define USE_TSEARCH 1 #endif #ifdef USE_TSEARCH #include #endif #define ZSHIFT 3 #define ZBLOCKSZ BlocksToBytes(1) #define BytesToBlocks(size) ((((unsigned)size) + ZBLOCKSZ - 1) >> ZSHIFT) #define BlocksToBytes(size) ((size) << ZSHIFT) /* * zmalloc() gets memory from malloc() in chunks and cuts these blocks into * smaller pieces that are multiples of eight bytes. When a piece is returned * via zfree(), it goes on a linked linear list indexed by its size. The lists * are an array, pool[]. * * For examples, if you ask for 22 bytes with p = zmalloc(22), you actually get * a piece of size 24. When you free it with zfree(p,22), it is added to the * list at pool[2]. */ #define POOLSZ 16 /* size of zmalloc's pool[] array */ #define CHUNK 256 /* number of ZBLOCKSZ's to get from malloc */ /*****************************************************************************/ /* * zmalloc() implements a scheme which makes it hard to use memory-checking * tools such as valgrind to audit the program to find where there are leaks. * That is due to its maintaining (for speed...) pools of memory chunks. * * Define DEBUG_ZMALLOC to build mawk with a simpler interface to the system * malloc, which verifies whether the size-parameter passed to zfree() is * consistent with the zmalloc() parameter. * * The NO_LEAKS code relies upon this simpler interface. */ #if !defined(DEBUG_ZMALLOC) && defined(NO_LEAKS) #define DEBUG_ZMALLOC 1 #endif #ifdef DEBUG_ZMALLOC #define IsPoolable(blocks) 0 #define Malloc(n) calloc(1,n) #else #define IsPoolable(blocks) ((blocks) <= POOLSZ) #define Malloc(n) malloc(n) #endif #ifdef USE_TSEARCH typedef struct { PTR ptr; size_t size; } PTR_DATA; static void *ptr_data; #if 0 static void show_tsearch(const void *nodep, const VISIT which, const int depth) { const PTR_DATA *p = *(PTR_DATA * const *) nodep; if (which == postorder || which == leaf) { TRACE(("show_data %d:%p ->%p %lu\n", depth, p, p->ptr, p->size)); } } static void show_ptr_data(void) { twalk(ptr_data, show_tsearch); } #define ShowPtrData() show_ptr_data() #else #define ShowPtrData() /* nothing */ #endif static void free_ptr_data(void *a) { TRACE(("free_ptr_data %p -> %p\n", a, ((PTR_DATA *) (a))->ptr)); free(a); } static int compare_ptr_data(const void *a, const void *b) { const PTR_DATA *p = (const PTR_DATA *) a; const PTR_DATA *q = (const PTR_DATA *) b; int result; TRACE2(("compare %p->%p %p->%p\n", p, p->ptr, q, q->ptr)); if (p->ptr > q->ptr) { result = 1; } else if (p->ptr < q->ptr) { result = -1; } else { result = 0; } return result; } static void record_ptr(PTR ptr, size_t size) { PTR_DATA *item; PTR_DATA **result; item = malloc(sizeof(PTR_DATA)); item->ptr = ptr; item->size = size; TRACE(("record_ptr %p -> %p %lu\n", (void *) item, ptr, (unsigned long) size)); result = tsearch(item, &ptr_data, compare_ptr_data); assert(result != NULL); assert(*result != NULL); TRACE2(("->%p (%p %lu)\n", (*result), (*result)->ptr, (unsigned long) (*result)->size)); ShowPtrData(); } static void finish_ptr(PTR ptr, size_t size) { PTR_DATA dummy; PTR_DATA *later; PTR_DATA **item; void *check; dummy.ptr = ptr; dummy.size = size; TRACE2(("finish_ptr (%p) -> %p %lu\n", &dummy, ptr, (unsigned long) size)); item = tfind(&dummy, &ptr_data, compare_ptr_data); assert(item != NULL); assert(*item != NULL); TRACE(("finish_ptr %p -> %p %lu\n", (void *) (*item), (*item)->ptr, (unsigned long) (*item)->size)); later = *item; check = tdelete(&dummy, &ptr_data, compare_ptr_data); if (check) { free(later); } ShowPtrData(); } #define FinishPtr(ptr,size) finish_ptr(ptr,size) #define RecordPtr(ptr,size) record_ptr(ptr,size) #else #define FinishPtr(ptr,size) /* nothing */ #define RecordPtr(ptr,size) /* nothing */ #endif /*****************************************************************************/ static GCC_NORETURN void out_of_mem(void) { static char out[] = "out of memory"; if (mawk_state == EXECUTION) { rt_error("%s", out); } else { /* I don't think this will ever happen */ compile_error("%s", out); mawk_exit(2); } } typedef union zblock { char dummy[ZBLOCKSZ]; union zblock *link; } ZBLOCK; /* ZBLOCKS of sizes 1, 2, ... 16 which is bytes of sizes 8, 16, ... , 128 are stored on the linked linear lists in pool[0], pool[1], ... , pool[15] */ static ZBLOCK *pool[POOLSZ]; PTR zmalloc(size_t size) { unsigned blocks = BytesToBlocks(size); size_t bytes = (size_t) BlocksToBytes(blocks); register ZBLOCK *p; static unsigned amt_avail; static ZBLOCK *avail; if (!IsPoolable(blocks)) { p = (ZBLOCK *) Malloc(bytes); if (!p) out_of_mem(); RecordPtr(p, size); } else { if ((p = pool[blocks - 1]) != NULL) { pool[blocks - 1] = p->link; } else { if (blocks > amt_avail) { if (amt_avail != 0) /* free avail */ { avail->link = pool[--amt_avail]; pool[amt_avail] = avail; } if (!(avail = (ZBLOCK *) Malloc((size_t) (CHUNK * ZBLOCKSZ)))) { /* if we get here, almost out of memory */ amt_avail = 0; p = (ZBLOCK *) Malloc(bytes); if (!p) out_of_mem(); RecordPtr(p, bytes); return (PTR) p; } else { RecordPtr(avail, CHUNK * ZBLOCKSZ); amt_avail = CHUNK; } } /* get p from the avail pile */ p = avail; avail += blocks; amt_avail -= blocks; } } return (PTR) p; } void zfree(PTR p, size_t size) { unsigned blocks = BytesToBlocks(size); if (!IsPoolable(blocks)) { FinishPtr(p, size); free(p); } else { ((ZBLOCK *) p)->link = pool[--blocks]; pool[blocks] = (ZBLOCK *) p; } } PTR zrealloc(PTR p, size_t old_size, size_t new_size) { register PTR q; TRACE(("zrealloc %p %lu ->%lu\n", p, (unsigned long) old_size, (unsigned long) new_size)); if (new_size > (BlocksToBytes(POOLSZ)) && old_size > (BlocksToBytes(POOLSZ))) { if (!(q = realloc(p, new_size))) { out_of_mem(); } FinishPtr(p, old_size); RecordPtr(q, new_size); #ifdef DEBUG_ZMALLOC if (new_size > old_size) { memset((char *) q + old_size, 0, new_size - old_size); } #endif } else { q = zmalloc(new_size); memcpy(q, p, old_size < new_size ? old_size : new_size); zfree(p, old_size); } return q; } #ifdef NO_LEAKS void zmalloc_leaks(void) { #ifdef USE_TSEARCH TRACE(("zmalloc_leaks\n")); while (ptr_data != NULL) { PTR_DATA *data = *(PTR_DATA **) ptr_data; tdelete(data, &ptr_data, compare_ptr_data); free_ptr_data(data); } #endif } #endif