/* Zone memory management. Copyright (C) 1996, 1997 Free Software Foundation, Inc. Author: Yoo C. Chung Date: September 1996 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. /* This uses some GCC specific extensions. But since the library is supposed to compile on GCC 2.7.2 (patched) or higher, and the only other Objective-C compiler I know of (other than NeXT's) is the StepStone compiler, which I haven't the foggiest idea why anyone would prefer it to GCC ;), it should be OK. This uses it's own routines with NSDefaultMallocZone() instead of using malloc() and friends. But if that's a problem, then it's a trivial problem to fix (at least it should be). THe NSZone functions should be thread-safe. But I haven't actually tested them in a multi-threaded environment. In a small block, every chunk has a size that is a multiple of CHUNK. A free chunk in a freeable zone looks like this: unsigned : front : size of chunk back : 0 unsigned : front : position of next free chunk (0 if none) back : position of previous free chunk (0 if none) Unused memory unsigned : front : position of previous free chunk (0 if none) back : position of next free chunk (0 if none) unsigned : front : size of chunk back : 0 A used chunk in a freeable zone looks like this. unsigned : front : size of chunk back : position of this chunk in block Memory that is actually used unsigned : front : size of chunk back : position of this chunk in block All sizes and positions are in units of bytes. The use of unsigned is probably a Bad Thing (tm). This should still work on machines where sizeof(void*) != sizof(unsigned), but you wouldn't be able to allocate as much memory as you might be able to in one chunk, and it's kind of unelegant. The DEC Alpha is such a machine (though in this case, a program that needs memory whose size can't fit in a 32 bit integer should really think about cutting down on its size, or at least divide the problem up). This assumes that sizeof(unsigned) is a multiple of two. I don't think I'll have to worry too much about this assumption. */ #define NDEBUG /* Comment this out to turn on assertions. */ #include #include #include #include #include #include #include #define ONES (~0U) #define BACK (ONES << (sizeof(unsigned)*4)) #define FRONT (ONES >> (sizeof(unsigned)*4)) #define FREEOVERHEAD (4*sizeof(unsigned)) #define USEDOVERHEAD (2*sizeof(unsigned)) #define CHUNK FREEOVERHEAD /* Minimum size of chunk. */ #define BLOCKHEAD roundupto(sizeof(BlockHeader), CHUNK) typedef struct _ZoneTable ZoneTable; typedef struct _BlockHeader BlockHeader; struct _ZoneTable { struct _ZoneTable *next; unsigned ident; /* Identifier for zone table, starts at 1. */ unsigned count, size; NSZone zones[0]; }; struct _BlockHeader { struct _BlockHeader *previous, *next; /* For small block, front is size, back is zone identifier, for big block and free block, the whole thing is size. The size includes the header. */ unsigned size; /* For freeable zone, front is size of biggest free chunk, back is position of biggest free chunk (it's size of the block if none available). For non-freeable zone, position of free chunk (it's size of the block if none available). It's 0 for a big block. */ unsigned free; }; static unsigned bsize = 0; /* Minimum block size. */ static unsigned zunit; /* Number of zones in a zone table. */ static ZoneLock zonelock; /* Lock for zone tables. */ static ZoneLock blocklock; /* Lock for blocks. */ static BlockHeader *freeBlocks = NULL; /* Higher blocks come first. */ static BlockHeader *lastFree = NULL; static NSZone defaultZone; static ZoneTable *zones = NULL; static ZoneTable *endzones = NULL; /* Gets a block with size SIZE. SIZE must be a multiple of bsize. The size given includes overhead. Returns NULL if no block can be returned. */ static BlockHeader *getBlock(unsigned size); static void initialize(void) __attribute((constructor)); static void releaseBlock(BlockHeader *block); static void *getMemInBlock(BlockHeader *block, unsigned size); static void insertFreeChunk(BlockHeader *block, void *chunk); /* Get previously unused zone slot. Return NULL if no zone can be returned. */ static NSZone *getZone(void); static void releaseZone(NSZone *zone); /* Mark zone slot as unused. */ /* Memory functions for freeable zones. */ static void *fmalloc(NSZone *zone, unsigned size); static void *frealloc(NSZone *zone, void *ptr, unsigned size); static void ffree(NSZone *zone, void *ptr); static void frecycle(NSZone *zone); /* Memory functions for non-freeable zones. */ static void *nmalloc(NSZone *zone, unsigned size); static void *nrealloc(NSZone *zone, void *ptr, unsigned size); static void nfree(NSZone *zone, void *ptr); static void nrecycle(NSZone *zone); /* Rounds N up to a multiple of BASE. */ static inline unsigned roundupto(unsigned n, unsigned base) { unsigned a = (n/base)*base; return (n-a)? a+base: n; } /* Return front half of N. */ static inline unsigned splitfront(unsigned n) { return n & FRONT; } /* Return back half of N. Expect this to be slower that splitfront(). */ static inline unsigned splitback(unsigned n) { return (n & BACK) >> (sizeof(unsigned)*4); } /* Check that back half of N is not zero. If so, return non-zero number. */ static inline unsigned backnonzero(unsigned n) { return n & BACK; } /* Set front half of n. Front must fit within half of an unsigned. */ static inline void setfront(unsigned *n, unsigned front) { assert(front <= FRONT); *n = (*n & BACK) | front; assert(splitfront(*n) == front); } /* Set back half of n. Back half must fit in half of an unsigned. Expect this to be slower than setfront(). */ static inline void setback(unsigned *n, unsigned back) { assert(back <= FRONT); *n = (*n & FRONT) | (back << (sizeof(unsigned)*4)); assert(splitback(*n) == back); } /* Return unsigned integer such that front and back are set to the given numbers. The given numbers must fit within half of an unsigned. */ static inline unsigned setfrontback(unsigned front, unsigned back) { assert(front <= FRONT); assert(back <= FRONT); return front | (back << (sizeof(unsigned)*4)); } /* Maximum size for blocks containing small chunks. */ static inline unsigned maxsblock(void) { return 1U << (sizeof(unsigned)*4-1); } /* Create mutex. */ static inline ZoneLock makelock(void) { return objc_mutex_allocate(); } /* Destroy mutex. */ static inline void destroylock(ZoneLock mutex) { objc_mutex_deallocate(mutex); } /* Lock with MUTEX. */ static inline void lock(ZoneLock mutex) { /* The thought of a probable system call is rather unappealing, but what else can I do? */ objc_mutex_lock(mutex); } /* Release the lock on MUTEX. */ static inline void unlock(ZoneLock mutex) { /* Like lock(), a probable system call is rather unappealing. */ objc_mutex_unlock(mutex); } /* Get zone identifier for given zone. */ static inline unsigned getZoneIdent(NSZone *zone) { ZoneTable *table = zone->table; if (zone->table == NULL) return 0; return table->ident*zunit+(zone-table->zones); } static inline NSZone* zoneWithIdent(unsigned ident) { if (ident) { int i, a; ZoneTable *table = zones; for (i = a = ident/zunit; i > 1; i--) table = table->next; return table->zones+(ident-a*zunit); } return &defaultZone; } static inline BlockHeader* addBBlock(BlockHeader *list, BlockHeader *block) { block->previous = NULL; block->next = list; if (list != NULL) list->previous = block; return block; } static inline BlockHeader* addSBlock(BlockHeader *list, BlockHeader *block) { block->previous = NULL; block->next = list; list->previous = block; return block; } static inline void releaseSBlock(BlockHeader *block) { if (block->next != NULL) block->next->previous = block->previous; if (block->previous != NULL) block->previous->next = block->next; block->size = splitfront(block->size); releaseBlock(block); } static inline void releaseBBlock(BlockHeader *block) { if (block->next != NULL) block->next->previous = block->previous; if (block->previous != NULL) block->previous->next = block->next; releaseBlock(block); } /* SIZE includes the overhead. */ static inline void setFreeChunk(void *chunk, unsigned size, unsigned prev, unsigned next) { unsigned tmp = setfrontback(size-USEDOVERHEAD, 0); unsigned *intp = chunk; assert(size%CHUNK == 0); *intp = tmp; *(intp+1) = setfrontback(next, prev); intp = (void*)intp+size; *(intp-1) = tmp; *(intp-2) = setfrontback(prev, next); } /* SIZE includes the overhead. */ static inline void setUsedChunk(void *chunk, unsigned size, unsigned pos) { unsigned n = setfrontback(size-USEDOVERHEAD, pos); unsigned *intp = chunk; assert(size%CHUNK == 0); *intp = n; intp = (void*)intp+size; *(intp-1) = n; return; } NSZone* NSCreateZone(unsigned startSize, unsigned granularity, BOOL canFree) { NSZone *zone; BlockHeader *block; if ((startSize == 0) || (startSize > maxsblock())) startSize = bsize; else startSize = roundupto(startSize, bsize); if ((granularity == 0) || (granularity > maxsblock())) granularity = bsize; zone = getZone(); if (zone == NULL) [NSException raise: NSMallocException format: @"NSCreateZone(): Unable to obtain zone"]; zone->sblocks = block = getBlock(startSize); if (block == NULL) { releaseZone(zone); [NSException raise: NSMallocException format: @"NSCreateZone(): More memory unattainable"]; } zone->granularity = roundupto(granularity, bsize); zone->name = nil; zone->bblocks = NULL; zone->lock = makelock(); block->previous = block->next = NULL; block->size = setfrontback(startSize, getZoneIdent(zone)); if (canFree) { zone->malloc = fmalloc; zone->realloc = frealloc; zone->free = ffree; zone->recycle = frecycle; block->free = setfrontback(startSize-(BLOCKHEAD+USEDOVERHEAD), BLOCKHEAD); setFreeChunk((void*)block+BLOCKHEAD, startSize-BLOCKHEAD, 0, 0); } else { zone->malloc = nmalloc; zone->realloc = nrealloc; zone->free = nfree; zone->recycle = nrecycle; block->free = BLOCKHEAD; } return zone; } NSZone* NSDefaultMallocZone(void) { return &defaultZone; } NSZone* NSZoneFromPointer(void *pointer) { unsigned *intp; BlockHeader *block; intp = pointer-sizeof(unsigned); block = (void*)intp-splitback(*intp); if (block->free) return zoneWithIdent(splitback(block->size)); else { BlockHeader *aBlock = defaultZone.bblocks; NSZone *zone, *endzone; ZoneTable *table; while (aBlock != NULL) { if (aBlock == block) return &defaultZone; aBlock = aBlock->next; } table = zones; while (table != NULL) { zone = table->zones; endzone = zone+table->size; while (zone < endzone) { if (zone->table != NULL) { aBlock = zone->bblocks; while (aBlock != NULL) { if (aBlock == block) return zone; aBlock = aBlock->next; } } zone++; } table = table->next; } } return NULL; /* No zone containing pointer found. */ } inline void* NSZoneMalloc(NSZone *zone, unsigned size) { return (zone->malloc)(zone, size); } void* NSZoneCalloc(NSZone *zone, unsigned numElems, unsigned numBytes) { return memset((zone->malloc)(zone, numElems*numBytes), 0, numElems*numBytes); } inline void* NSZoneRealloc(NSZone *zone, void *pointer, unsigned size) { return (zone->realloc)(zone, pointer, size); } inline void NSRecycleZone(NSZone *zone) { (zone->recycle)(zone); } inline void NSZoneFree(NSZone *zone, void *pointer) { (zone->free)(zone, pointer); } void NSSetZoneName (NSZone *zone, NSString *name) { zone->name = [name copy]; } NSString* NSZoneName (NSZone *zone) { return zone->name; } void NSZonePtrInfo(void *ptr) { /* FIXME: Implement this. */ fprintf(stderr, "NSZonePtrInfo() not implemented yet!\n"); } BOOL NSMallocCheck(void) { /* FIXME: Implement this. */ fprintf(stderr, "NSMallocCheck() not implemented yet!\n"); abort(); return NO; } static void initialize(void) { BlockHeader *block; bsize = NSPageSize(); zunit = (bsize-sizeof(ZoneTable))/sizeof(NSZone); zonelock = makelock(); blocklock = makelock(); defaultZone.lock = makelock(); defaultZone.granularity = bsize; defaultZone.malloc = fmalloc; defaultZone.realloc = frealloc; defaultZone.free = ffree; defaultZone.recycle = NULL; defaultZone.name = nil; defaultZone.table = NULL; defaultZone.bblocks = NULL; block = defaultZone.sblocks = getBlock(bsize); if (block == NULL) { fprintf(stderr, "Unable to allocate memory for default zone.\n"); abort(); /* No point surviving if we can't even use the default zone. */ } block->previous = block->next = NULL; block->size = setfrontback(bsize, 0); block->free = setfrontback(bsize-BLOCKHEAD-USEDOVERHEAD, BLOCKHEAD); setFreeChunk((void*)block+BLOCKHEAD, bsize-BLOCKHEAD, 0, 0); } static BlockHeader* getBlock(unsigned size) { BlockHeader *block; assert(size%bsize == 0); lock(blocklock); block = freeBlocks; while ((block != NULL) && (block->size < size)) block = block->next; if (block == NULL) block = NSAllocateMemoryPages(size); else if (block->size != size) { BlockHeader *splitblock; splitblock = (void*)block+size; splitblock->previous = block->previous; splitblock->next = block->next; splitblock->size = block->size-size; if (block->next != NULL) block->next->previous = splitblock; if (block->previous != NULL) block->previous->next = splitblock; } unlock(blocklock); return block; } static void releaseBlock(BlockHeader *block) { BlockHeader *aBlock; lock(blocklock); aBlock = freeBlocks; while ((aBlock != NULL) && (aBlock > block)) aBlock = aBlock->next; if (aBlock == NULL) { if (lastFree == NULL) { lastFree = freeBlocks = block; block->previous = aBlock->next = NULL; } else if ((void*)block+block->size == (void*)lastFree) { block->size += lastFree->size; block->previous = lastFree->previous; block->next = NULL; if (block->previous != NULL) block->previous->next = block; lastFree = block; } else { block->previous = lastFree; block->next = NULL; lastFree->next = block; lastFree = block; } } else { if (aBlock->previous == NULL) { freeBlocks = block; block->next = aBlock; block->previous = NULL; aBlock->previous = block; } else if ((void*)block+block->size == aBlock->previous) { block->size += aBlock->previous->size; block->previous = aBlock->previous->previous; block->next = aBlock; if (block->previous != NULL) block->previous->next = block; aBlock->previous = block; } if ((void*)aBlock+aBlock->size == block) aBlock->size += block->size; } unlock(blocklock); } static void* getMemInBlock(BlockHeader *block, unsigned size) { unsigned chunksize = roundupto(size+USEDOVERHEAD, CHUNK); unsigned *intp, *intp2; assert(splitfront(block->free) >= size+USEDOVERHEAD); intp = (void*)block+splitback(block->free); intp2 = (void*)block+splitfront(*(intp+1)); if ((void*)intp2 != (void*)block) { setFreeChunk(intp2, splitfront(*intp2)+USEDOVERHEAD, 0, splitfront(*(intp2+1))); block->free = setfrontback(splitfront(*intp2), (void*)intp2-(void*)block); } else block->free = setfrontback(0, splitfront(block->size)); if (splitfront(*intp)+USEDOVERHEAD != chunksize) { setFreeChunk((void*)intp+chunksize, (splitfront(*intp)+USEDOVERHEAD)-chunksize, 0, 0); insertFreeChunk(block, (void*)intp+chunksize); } setUsedChunk(intp, chunksize, (void*)intp-(void*)block); return intp+1; } static void insertFreeChunk(BlockHeader *block, void *chunk) { unsigned *intp = chunk; assert((void*)chunk < (void*)block+splitfront(block->size)); if (splitfront(block->free) == 0) { block->free = setfrontback(splitfront(*intp), chunk-(void*)block); setFreeChunk(chunk, splitfront(*intp)+USEDOVERHEAD, 0, 0); } else { unsigned *intp2 = (void*)block+splitback(block->free); unsigned *intp3 = NULL; while (((void*)intp2 != (void*)block) && (splitfront(*intp) < splitfront(*intp2))) { intp3 = intp2; intp2 = (void*)block+splitfront(*(intp2+1)); } if (intp3 == NULL) { unsigned pos = chunk-(void*)block; setFreeChunk(intp2, splitfront(*intp2)+USEDOVERHEAD, pos, splitfront(*(intp2+1))); setFreeChunk(chunk, splitfront(*intp)+USEDOVERHEAD, 0, (void*)intp2-(void*)block); block->free = setfrontback(splitfront(*intp), pos); } else if ((void*)intp2 == (void*)block) { setFreeChunk(intp3, splitfront(*intp3)+USEDOVERHEAD, splitback(*(intp3+1)), chunk-(void*)block); setFreeChunk(chunk, splitfront(*intp)+USEDOVERHEAD, (void*)intp3-(void*)block, 0); } else { unsigned pos = chunk-(void*)block; setFreeChunk(intp2, splitfront(*intp2)+USEDOVERHEAD, pos, splitfront(*(intp2+1))); setFreeChunk(intp3, splitfront(*intp3)+USEDOVERHEAD, splitback(*(intp3+1)), pos); setFreeChunk(chunk, splitfront(*intp)+USEDOVERHEAD, (void*)intp3-(void*)block, (void*)intp2-(void*)block); } } } static NSZone* getZone(void) { NSZone *zone; ZoneTable *table = zones; lock(zonelock); while ((table != NULL) && (table->count == zunit)) table = table->next; if (table == NULL) { table = NSAllocateMemoryPages(bsize); if (table == NULL) zone = NULL; else { table->size = table->count = 1; table->next = zones; if (zones == NULL) { zones = table; table->ident = 1; } else { endzones->next = table; table->ident = endzones->ident+1; } endzones = table; table->next = NULL; zone = table->zones; } } else { if (table->size == zunit) { zone = table->zones; while (zone->table != NULL) zone++; } else { zone = table->zones+table->size; table->size++; } table->count++; } zone->table = table; unlock(zonelock); return zone; } static void releaseZone(NSZone *zone) { lock(zonelock); ((ZoneTable*)zone->table)->count--; zone->table = NULL; unlock(zonelock); return; } static void* fmalloc(NSZone *zone, unsigned size) { unsigned *intp; BlockHeader *block; void *ptr; lock(zone->lock); if (size+BLOCKHEAD+USEDOVERHEAD > maxsblock()) { unsigned realSize = roundupto(size+BLOCKHEAD+USEDOVERHEAD, bsize); block = getBlock(realSize); if (block == NULL) { unlock(zone->lock); [NSException raise: NSMallocException format: @"NSZoneMalloc(): Unable to get memory"]; } block->size = realSize; block->free = 0; zone->bblocks = addBBlock(zone->bblocks, block); intp = (void*)block+BLOCKHEAD; *intp = setfrontback(0, BLOCKHEAD); ptr = intp+1; } else { block = zone->sblocks; while ((block != NULL) && (splitfront(block->free) < size+USEDOVERHEAD)) block = block->next; if (block == NULL) { unsigned chunk = roundupto(size+USEDOVERHEAD, CHUNK); unsigned tmp, total; total = roundupto(size+BLOCKHEAD+USEDOVERHEAD, zone->granularity); block = getBlock(total); if (block == NULL) { unlock(zone->lock); [NSException raise: NSMallocException format: @"NSZoneMalloc(): Unable to get memory"]; } zone->sblocks = addSBlock(zone->sblocks, block); tmp = total-chunk-BLOCKHEAD-USEDOVERHEAD; block->size = setfrontback(total, getZoneIdent(zone)); block->free = setfrontback(tmp, chunk+BLOCKHEAD); setUsedChunk((void*)block+BLOCKHEAD, chunk, BLOCKHEAD); setFreeChunk((void*)block+(chunk+BLOCKHEAD), tmp+USEDOVERHEAD, 0, 0); ptr = (void*)block+(BLOCKHEAD+sizeof(unsigned)); } else ptr = getMemInBlock(block, size); } unlock(zone->lock); return ptr; } static void* frealloc(NSZone *zone, void *ptr, unsigned size) { /* FIXME: Implement this properly! */ void *newptr; newptr = fmalloc(zone, size); memcpy(newptr, ptr, size); ffree(zone, ptr); return newptr; } static void ffree(NSZone *zone, void *ptr) { unsigned *intp; BlockHeader *block; if (ptr == NULL) return; lock(zone->lock); intp = ptr-sizeof(unsigned); block = (void*)intp-splitback(*intp); if (block->free) insertFreeChunk(block, intp); else { if (block->previous == NULL) zone->bblocks = block->next; else block->previous->next = block->next; if (block->next != NULL) block->next->previous = block->previous; releaseBBlock(block); } unlock(zone->lock); return; } static void frecycle(NSZone *zone) { BlockHeader *block, *nextblock; block = zone->bblocks; while (block != NULL) { nextblock = block->next; defaultZone.bblocks = addBBlock(defaultZone.bblocks, block); block = nextblock; } block = zone->sblocks; while (block != NULL) { nextblock = block->next; if (splitfront(block->size) == splitfront(block->free)+BLOCKHEAD+USEDOVERHEAD) releaseSBlock(block); else defaultZone.sblocks = addSBlock(defaultZone.sblocks, block); block = nextblock; } [zone->name release]; destroylock(zone->lock); releaseZone(zone); return; } static void* nmalloc(NSZone *zone, unsigned size) { unsigned *intp; BlockHeader *block; lock(zone->lock); if (size+BLOCKHEAD+USEDOVERHEAD > maxsblock()) { unsigned realSize = roundupto(size+BLOCKHEAD+USEDOVERHEAD, bsize); block = getBlock(realSize); if (block == NULL) { unlock(zone->lock); [NSException raise: NSMallocException format: @"NSZoneMalloc(): Unable to get memory"]; } block->size = realSize; block->free = 0; zone->bblocks = addBBlock(zone->bblocks, block); intp = (void*)block+BLOCKHEAD; *intp = setfrontback(0, BLOCKHEAD); } else { block = zone->sblocks; if (size+sizeof(unsigned) > splitfront(block->size)-block->free) { unsigned newsize; BlockHeader *newblock; newsize = roundupto(size+USEDOVERHEAD+BLOCKHEAD, zone->granularity); newblock = getBlock(newsize); if (newblock == NULL) { unlock(zone->lock); [NSException raise: NSMallocException format: @"NSZoneMalloc(): Unable to get memory"]; } newblock->size = setfrontback(newsize, getZoneIdent(zone)); newblock->free = roundupto(size+sizeof(unsigned), CHUNK)+BLOCKHEAD; zone->sblocks = addSBlock(zone->sblocks, newblock); intp = (void*)newblock+BLOCKHEAD; *intp = setfrontback(0, BLOCKHEAD); } else { intp = (void*)block+block->free; *intp = (void*)intp-(void*)block; block->free += roundupto(size+sizeof(unsigned), CHUNK); } } unlock(zone->lock); return intp+1; } static void* nrealloc(NSZone *zone, void *ptr, unsigned size) { [NSException raise: NSGenericException format: @"Trying to reallocate memory in non-freeable zone"]; return NULL; } static void nfree(NSZone *zone, void *ptr) { [NSException raise: NSGenericException format: @"Trying to free memory in non-freeable zone"]; return; } static void nrecycle(NSZone *zone) { BlockHeader *block; block = zone->sblocks; while (block != NULL) { releaseSBlock(block); block = block->next; } block = zone->bblocks; while (block != NULL) { releaseBBlock(block); block = block->next; } destroylock(zone->lock); [zone->name release]; releaseZone(zone); return; }