/* Copyright 1989-91 GROUPE BULL -- See license conditions in file COPYRIGHT */ /*****************************************************************************\ * * * WOOL malloc: * * A fast malloc, with optional: * * -DSTATS stats * * -DMLEAK memory lossage tracing via hash coding * * -DDEBUG debugging information * * range checking (on_line and with a dbx call) * * -DMALLOCDEBUG more info * * -DUSE_STANDARD_MALLOC : use the system functions (do NOT work with -DDEBUG) * * * \*****************************************************************************/ #ifdef MALLOCDEBUG0 #ifndef MALLOCDEBUG #define MALLOCDEBUG #endif #endif #ifdef USE_STANDARD_MALLOC #ifdef DEBUG error=USE_STANDARD_MALLOC_and_DEBUG_are_incompatible_options; #endif #ifdef MALLOCDEBUG error=USE_STANDARD_MALLOC_and_MALLOCDEBUG_are_incompatible_options; #endif #ifdef MALLOCDEBUG0 error=USE_STANDARD_MALLOC_and_MALLOCDEBUG0_are_incompatible_options; #endif #ifdef MLEAK error=USE_STANDARD_MALLOC_and_MLEAK_are_incompatible_options; #endif #ifdef DO_NOT_REDEFINE_MALLOC error=USE_STANDARD_MALLOC_and_DO_NOT_REDEFINE_MALLOC_are_incompatible_options; #endif int malloc_init(){} #else #define WlO WOOL_OBJECT #define WlError wool_error #define WlAtom WOOL_Atom #define WlPuts wool_puts #define WlPrintf wool_printf #define WlMallocInit int malloc_init #define WlMallocZoneBegin malloc_zone_begin #define WlMallocZoneEnd malloc_zone_end #define WlAtomFindNextPrefixedAtom find_next_prefixed_atom #define WlHashSlot HashSlot #define WlIntern wool_atom #define WlHashTableSize HashTableSize #include "EXTERN.h" #ifdef STATS #include #else #define NULL 0 #endif #include #if defined(i386) && !defined(sequent) && !defined(SCO) && !defined(linux) && !defined(__FreeBSD__) typedef unsigned char u_char; typedef unsigned int u_int; #endif /* i386 */ #include "wool.h" #ifndef VOID_MALLOC #define MALLOC_TYPE char * #else #define MALLOC_TYPE void * #endif #ifdef STANDARD_MALLOC # ifndef DO_NOT_REDEFINE_MALLOC # define DO_NOT_REDEFINE_MALLOC # endif # ifndef DO_NOT_USE_SBRK # define DO_NOT_USE_SBRK # endif #endif #ifndef DO_NOT_REDEFINE_MALLOC #define REAL_malloc(nbytes) malloc(nbytes) #define REAL_free(ptr) free(ptr) #define REAL_realloc(ptr, nbytes) realloc(ptr, nbytes) #define REAL_calloc(n, s) calloc(n, s) #else /* DO_NOT_REDEFINE_MALLOC */ #define REAL_malloc(nbytes) wool_malloc(nbytes) #define REAL_free(ptr) wool_free(ptr) #define REAL_realloc(ptr, nbytes) wool_realloc(ptr, nbytes) #define REAL_calloc(n, s) wool_calloc(n, s) #endif /* DO_NOT_REDEFINE_MALLOC */ #ifdef MLEAK #include "wl_atom.h" WlAtom *WlHashSlot(); int MLEAK_on = 0; int MLEAK_count = 0; int MLEAK_num = 0; MLEAK_break(){} /* MLEAK_num reached! */ extern WlAtom WlAtomFindNextPrefixedAtom(); extern int WlHashTableSize; #endif /* MLEAK */ /* * This storage allocator trades space for speed in separate arenas for each * 2^n sizes (minus the header of 4 bytes (12 with debug on) * * The overhead on a block is at least 4 bytes. When free, this space * contains a pointer to the next free block, and the bottom two bits must * be zero. When in use, the first byte is set to MAGIC, and the second * byte is the size index. The remaining bytes are for alignment. * Whith DEBUG enabled, if the size of the block fits * in two bytes, then the top two bytes hold the size of the requested block * plus the range checking words, and the header word MINUS ONE. */ union overhead { union overhead *ov_next; /* when free */ struct { u_char ovu_magic; /* magic number */ u_char ovu_index; /* bucket # */ #ifdef DEBUG u_short ovu_rmagic; /* range magic number */ u_int ovu_size; /* actual block size */ #endif } ovu; #ifdef DOUBLE_ALIGN double dummy; /* Put union on double word boundary */ #endif /* DOUBLE_ALIGN */ }; #define ov_magic ovu.ovu_magic #define ov_index ovu.ovu_index #define ov_rmagic ovu.ovu_rmagic #define ov_size ovu.ovu_size #define MAGIC 0xef /* = 239 magic # on accounting info */ #define RMAGIC 0x5555 /* = 21845 magic # on range info */ #define WlFREED_MAGIC 0x31416 /* block has been freed */ #ifdef DEBUG #define RSLOP sizeof (u_short) #else #define RSLOP 0 #endif /* * nextf[i] is the pointer to the next free block of size 2^(i+3). The * smallest allocatable block is 8 bytes. The overhead information * precedes the data area returned to the user. */ #define NBUCKETS 30 static union overhead *nextf[NBUCKETS]; extern char *WlSbrk(); static int pagesz; /* page size */ static int pagebucket; /* page size bucket */ static void morecore(); #ifdef STATS /* * nmalloc[i] is the difference between the number of mallocs and frees * for a given block size. */ static u_int nmalloc[NBUCKETS]; #endif char *WlMallocZoneBegin, *WlMallocZoneEnd; /* WlMallocCheck is a routine that you can call regularily to verify validity * of the whole malloced blocks * you MUST compile with -DMALLOCDEBUG, but not necessarily with -DDEBUG * then malloc, free & realloc will call it each time */ #ifdef MALLOCDEBUG #define MDbucketMax 10000 char *MDbuckets[MDbucketMax]; int MDbucketsSize[MDbucketMax]; int MDbucket = 0; #ifdef MALLOCDEBUG0 int MDnocheck = 1; #else int MDnocheck = 0; #endif int WlMallocCheck() { int i, j, sz, nblks, bucket; union overhead *op, *pop; if (MDnocheck) return 0; for (i = 0; i < MDbucket; i++) { sz = MDbucketsSize[i]; bucket = 0; while (sz > (1 << (bucket + 3))) bucket++; if (sz < pagesz) { nblks = pagesz / sz; } else { nblks = 1; } for (j = 0; j < nblks; j++) { op = (union overhead *) (MDbuckets[i] + j*sz); if (((int)(op->ovu.ovu_magic)) == MAGIC) { /* in use */ ASSERT(op->ovu.ovu_index == bucket); #ifdef DEBUG ASSERT(op -> ovu.ovu_rmagic == RMAGIC); ASSERT(*(u_short *)((caddr_t)(op + 1)+op->ovu.ovu_size)==RMAGIC); #endif /*DEBUG*/ } else { /* free, follow chain */ pop = op; while (op) { op = op->ov_next; } } } } return 0; } #endif /*MALLOCDEBUG*/ #ifndef DO_NOT_USE_SBRK #define WlSbrk(n) sbrk(n) #else /*DO_NOT_USE_SBRK*/ /* for OSes with buggy malloc, we cannot use sbrk directly, so we just waste * memory... */ /* TODO: be smart, allocate a big arena, and do the splitting ourselves */ char * WlSbrk(amt) long unsigned amt; { return (char *) malloc(amt); } #endif /*DO_NOT_USE_SBRK*/ /* * Init malloc lists: to be called AT THE TOP of your program!!! * setup page size and align break pointer so all data will be page aligned. */ WlMallocInit() { union overhead *op; int bucket; unsigned amt, n; ASSERT(!pagesz); pagesz = n = getpagesize(); op = (union overhead *) WlSbrk(0); WlMallocZoneBegin = (char *) op; n = n - sizeof(*op) - ((int) op & (n - 1)); if ((int) n < 0) n += pagesz; if (n) { if ((char *) WlSbrk(n) == (char *) -1) WlError("no more memory%s", ""); } bucket = 0; amt = 8; while (pagesz > amt) { amt <<= 1; bucket++; } pagebucket = bucket; } /* * Convert amount of memory requested into closest block size stored in * hash buckets which satisfies request. Account for space used per block * for accounting. */ MALLOC_TYPE REAL_malloc(nbytes) unsigned nbytes; { union overhead *op; int bucket; unsigned amt, n; #ifdef DEBUG char *result; #endif #ifdef MALLOCDEBUG WlMallocCheck(); #endif #ifdef DEBUG if ((nbytes > 100000) # ifdef DEBUG2 || (nbytes == 0) # endif ) { fprintf(stderr, "MALLOC: trying to allocate %d bytes\n", nbytes); stop_if_in_dbx(); } #endif /* DEBUG */ if (nbytes <= (n = pagesz - sizeof(*op) - RSLOP)) { #ifndef DEBUG amt = 8; /* size of first bucket */ bucket = 0; #else amt = 16; /* size of first bucket */ bucket = 1; #endif n = -(sizeof(*op) + RSLOP); } else { amt = pagesz; bucket = pagebucket; } while (nbytes > amt + n) { /* find bucket */ amt <<= 1; bucket++; } /* if no hash bucket, allocates more memory */ if ((op = nextf[bucket]) == NULL) { morecore(bucket); if ((op = nextf[bucket]) == NULL) /* TO_DO: compact space? */ WlError("no more memory%s", ""); } /* remove from linked list */ nextf[bucket] = op -> ov_next; op -> ovu.ovu_index = bucket; #ifdef STATS nmalloc[bucket]++; #endif #ifdef DEBUG /* Record allocated size of block and bound space with magic numbers. */ op -> ovu.ovu_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); op -> ovu.ovu_magic = MAGIC; op -> ovu.ovu_rmagic = RMAGIC; *(u_short *) ((caddr_t) (op + 1) + op -> ovu.ovu_size) = RMAGIC; ASSERT(((char *) (op + 1))); #else # ifdef MALLOCDEBUG op -> ovu.ovu_magic = MAGIC; # endif #endif /*DEBUG*/ #ifndef MLEAK #ifdef DEBUG result = ((char *) (op + 1)); Trace('m', ("malloc: 0x%x\n", result)); return result; #else return ((char *) (op + 1)); #endif /*DEBUG*/ #else if (MLEAK_on) { char name[20]; WlAtom atom; char *ptr = (char *) (op + 1); sprintf(name + 1, "0x%x", ptr); name[0] = '\r'; MLEAK_on = 0; atom = WlIntern(name); MLEAK_on = 1; atom -> c_val = (WlO) ++MLEAK_count; if (MLEAK_num == MLEAK_count) MLEAK_break(); return ptr; } else { #ifdef DEBUG result = ((char *) (op + 1)); Trace('m', ("malloc: 0x%x\n", result)); return result; #else return ((char *) (op + 1)); #endif /*DEBUG*/ } #endif /* MLEAK */ } /* * Allocate more memory to the indicated bucket. */ static void morecore(bucket) int bucket; { union overhead *op; int sz; /* size of desired block */ int amt; /* amount to allocate */ int nblks; /* how many blocks we get */ sz = 1 << (bucket + 3); if (sz < pagesz) { amt = pagesz; nblks = amt / sz; } else { amt = sz + pagesz; nblks = 1; } op = (union overhead *) WlSbrk(amt); WlMallocZoneEnd = ((char *) op) + amt; #ifdef MALLOCDEBUG ASSERT(MDbucket < MDbucketMax); MDbuckets[MDbucket] = (char *) op; MDbucketsSize[MDbucket] = sz; MDbucket++; #endif /* no more room! */ if ((int) op == -1) return; /* Add new memory allocated to that on free list for this hash bucket. */ nextf[bucket] = op; while (--nblks > 0) { op -> ov_next = (union overhead *) ((caddr_t) op + sz); op = (union overhead *) ((caddr_t) op + sz); } op -> ov_next = NULL; } #ifdef VOID_MALLOC void #endif REAL_free(cp) MALLOC_TYPE cp; { int size; union overhead *op; #ifdef DEBUG MALLOC_TYPE last_ptr; #endif /* DEBUG */ #ifdef MALLOCDEBUG WlMallocCheck(); #endif #ifdef MLEAK if (WlHashTableSize) { char name[20]; WlAtom *patom; sprintf(name + 1, "0x%x", cp); name[0] = '\r'; patom = WlHashSlot(name); if (*patom) (*patom) -> c_val = 0; } #endif /* MLEAK */ ASSERT(cp != NULL); if(!cp) return; op = (union overhead *) ((caddr_t) cp - sizeof(union overhead)); ASSERT(op -> ovu.ovu_magic == MAGIC); /* make sure it was in use */ ASSERT(op -> ovu.ovu_rmagic == RMAGIC); ASSERT(*(u_short *) ((caddr_t) (op + 1) + op -> ovu.ovu_size) == RMAGIC); size = op -> ovu.ovu_index; ASSERT(size < NBUCKETS); #ifdef DEBUG last_ptr = cp + op -> ovu.ovu_size; #endif /* DEBUG */ op -> ov_next = nextf[size]; nextf[size] = op; #ifdef DEBUG /* we erase all the data zone, to detect earlier re-using a freed block */ #ifdef SIMPLE_LHS WlIntFill(cp, last_ptr, WlFREED_MAGIC); #else /* SIMPLE_LHS */ while (cp < last_ptr) *(((int *) cp)++) = WlFREED_MAGIC; #endif /* SIMPLE_LHS */ #endif /* DEBUG */ #ifdef STATS nmalloc[size]--; #endif Trace('m', ("free: 0x%x\n", cp)); } #ifdef SIMPLE_LHS WlIntFill(p, last, value) int *p, *last, value; { while (p < last) *p++ = value; } #endif /* SIMPLE_LHS */ /* * Simple realloc without storage compaction * can be handled NULL pointer, but not 0 size... */ MALLOC_TYPE REAL_realloc(cp, nbytes) MALLOC_TYPE cp; unsigned nbytes; { u_int onb, i; union overhead *op; char *res; #ifdef MALLOCDEBUG WlMallocCheck(); ASSERT(nbytes); #endif if (!cp) return REAL_malloc(nbytes); op = (union overhead *) ((caddr_t) cp - sizeof(union overhead)); i = op -> ovu.ovu_index; onb = 1 << (i + 3); if (onb < pagesz) onb -= sizeof(*op) + RSLOP; else onb += pagesz - sizeof(*op) - RSLOP; /* avoid the copy if same size block */ if (i) { i = 1 << (i + 2); if (i < pagesz) i -= sizeof(*op) + RSLOP; else i += pagesz - sizeof(*op) - RSLOP; } if (nbytes <= onb && nbytes > i) { #ifdef DEBUG op -> ovu.ovu_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); *(u_short *) ((caddr_t) (op + 1) + op -> ovu.ovu_size) = RMAGIC; #endif return (cp); } else { if ((res = REAL_malloc(nbytes)) == NULL) return (NULL); ASSERT(cp != res); bcopy(cp, res, (nbytes < onb) ? nbytes : onb); REAL_free(cp); return (res); } } MALLOC_TYPE REAL_calloc(nelem, elsize) unsigned nelem, elsize; { char *result = REAL_malloc(nelem * elsize); if (result) bzero(result, nelem * elsize); return result; } /*****************************************************************************\ * misc utilities * \*****************************************************************************/ /* returns room in current chunk * we set up things in debug mode so that all the chunk can be allocated * without trashing end markers */ int WlMallocedSize(cp) char *cp; { u_int onb, i; union overhead *op; if (!cp) return 0; op = (union overhead *) ((caddr_t) cp - sizeof(union overhead)); i = op -> ovu.ovu_index; onb = 1 << (i + 3); if (onb < pagesz) onb -= sizeof(*op) + RSLOP; else onb += pagesz - sizeof(*op) - RSLOP; #ifdef DEBUG /* set Recorded allocated size to max */ op -> ovu.ovu_size = (onb + RSLOP - 1) & ~(RSLOP - 1); op -> ovu.ovu_magic = MAGIC; op -> ovu.ovu_rmagic = RMAGIC; *(u_short *) ((caddr_t) (op + 1) + op -> ovu.ovu_size) = RMAGIC; #endif return onb; } #ifdef STATS /* * WlMstats - print out statistics about malloc * * Prints two lines of numbers, one showing the length of the free list * for each size category, the second showing the number of mallocs - * frees for each size category. */ WlO WlMstats() { int i, j; union overhead *p; int totfree = 0, totused = 0; int Nbuckets = (NBUCKETS > 14 ? 14 : NBUCKETS); WlPuts("Memory allocation statistics \n"); WlPuts("size"); for (i = 0; i < Nbuckets; i++) { if (i < 7) { WlPrintf("%5ld", (long) (1 << (i + 3))); } else { WlPrintf("%4ldk", (long) (1 << (i - 7))); } } WlPuts("\nfree"); for (i = 0; i < Nbuckets; i++) { for (j = 0, p = nextf[i]; p; p = p -> ov_next, j++); WlPrintf("%5ld", (long) j); totfree += j * (1 << (i + 3)); } WlPuts("\nused"); for (i = 0; i < Nbuckets; i++) { WlPrintf("%5ld", (long) nmalloc[i]); totused += nmalloc[i] * (1 << (i + 3)); } WlPrintf("\n\tTotal in use: %d,", (long) totused); WlPrintf("total free: %d\n", (long) totfree); return NIL; } #endif /* STATS */ /************\ * * * DBX tools * * * \************/ #ifdef DEBUG /* * verify if pointer is still valid */ int WlVerifyMalloc(cp) char *cp; { int size; union overhead *op; if (cp == NULL) return 0; op = (union overhead *) ((caddr_t) cp - sizeof(union overhead)); ASSERT(op -> ovu.ovu_magic == MAGIC); /* make sure it was in use */ if (op -> ovu.ovu_magic != MAGIC) return 0; /* sanity */ ASSERT(op -> ovu.ovu_rmagic == RMAGIC); ASSERT(*(u_short *) ((caddr_t) (op + 1) + op -> ovu.ovu_size) == RMAGIC); size = op -> ovu.ovu_index; ASSERT(size < NBUCKETS); return 1; } #endif /* DEBUG */ #ifdef MLEAK /************************************************************************\ * * * malloc-leak tracing: (colas: uses the hash table!) * * * * Under dbx: * * * * Turn tracing on by setting MLEAK_on to 1 * * When malloced blocks are done, reset it to 0 * * then MLEAK_print(file,n) prints the nth first (if any) traced blocks * * remain allocated then. (file=0 means stdout) * * * * then by re-executing the program and setting MLEAK_num to the desired * * number and setting a beakpoint in MLEAK_break, bdx will halt when the * * desired block will be allocated * * * * Exemple of test file: * * * * (load ".gwmrc.gwm") * * (? "set MLEAK_on = 1\n") * * (break) * * (load ".gwmrc.gwm") * * (? "set MLEAK_on = 0\n") * * (break) * * (load ".gwmrc.gwm") * * (load ".gwmrc.gwm") * * (? "call MLEAK_print(\"FOO\", 10000)\n") * * (? "stop in MLEAK_break\n") * * (? "continue\n") * * (break) * * * \************************************************************************/ typedef struct _WlMleakCell { int number; char *object_addr; } *WlMleakCell; #define WlMleakCellSize 100 static struct _WlMleakCell wlmltab[WlMleakCellSize]; static int WlMleakCellCompare(a, b) WlMleakCell a, b; { return (a->number - b->number); } static int WlMleakCellMax(tab, size) WlMleakCell tab; int size; { int i; int max = 0; for (i = 0; i< size; i++) { if (tab[i].number > max) { max = tab[i].number; } } return max; } MLEAK_print() { int i = 0, old_MLEAK_on = MLEAK_on; WlAtom atom; int curcell = 0; int max_dirty = 1; int max_index; MLEAK_on = 0; /* collect entries */ WlAtomFindNextPrefixedAtom('\0'); while (atom = WlAtomFindNextPrefixedAtom('\r')) { int slot; i++; if (curcell >= WlMleakCellSize) { if (max_dirty) { max_index = WlMleakCellMax(wlmltab, WlMleakCellSize); max_dirty = 0; } if (((int) atom->c_val) < wlmltab[max_index].number) { max_dirty = 1; slot = max_index; } else { curcell++; continue; } } else { slot = curcell; } wlmltab[slot].number = (int) atom->c_val; wlmltab[slot].object_addr = atom->p_name + 1; curcell++; } /* sort entries */ qsort(wlmltab, Min(WlMleakCellSize, curcell), sizeof(struct _WlMleakCell), WlMleakCellCompare); /* print entries */ printf("\n"); if (curcell > WlMleakCellSize) { printf("Too many leaks (%d), printing only the first %d ones\n", curcell, WlMleakCellSize); curcell = WlMleakCellSize; } for (i = 0; i < curcell; i++) { WlO obj; union overhead *op; printf("%2d th malloc at %s remains", wlmltab[i].number, wlmltab[i].object_addr); sscanf(wlmltab[i].object_addr, "0x%x", &obj); op = (union overhead *) (((caddr_t) obj) - sizeof(union overhead)); printf(" bucket#%d", op->ovu.ovu_index); #ifdef DEBUG printf(" size %3d bytes", op->ovu.ovu_size); #endif if (wool_object_is_valid(obj)) { printf(" of type: %s\n = ", ((WOOL_Atom) obj->type[0])->p_name); wool_print(obj); } printf("\n"); } MLEAK_on = old_MLEAK_on; } WlO MLEAK_printW() { MLEAK_print(); return NIL; } #ifdef __OBSOLETE MLEAK_print(filename, n) char *filename; int n; { int i = 0, old_MLEAK_on = MLEAK_on; WlAtom atom; FILE *ptr; MLEAK_on = 0; if ((!filename) || filename[0] == '\0') { filename = NULL; ptr = NULL; } else { ptr = fopen(filename, "w+"); } WlAtomFindNextPrefixedAtom('\0'); while ((atom = WlAtomFindNextPrefixedAtom('\r')) && i++ < n) { fprintf((ptr ? ptr : stdout), "%d th malloc at %s is still there\n", atom -> c_val, atom -> p_name + 1); } if (filename) fclose(ptr); MLEAK_on = old_MLEAK_on; } #endif __OBSOLETE #endif /* MLEAK */ #endif /* USE_STANDARD_MALLOC */