#include "gmqcc.h" #include /* * GMQCC does a lot of allocations on shortly lived objects all of which * call down to malloc/free internally. The overhead involved with these * allocations makes GMQCC slow. To combat this, a special allocator was * in need. This is an implementation of a user-space buddy allocator * that sits ontop of malloc/free. I'd like to thank Lee Salzman for * guiding me in the right direction for designing this. */ #define GMQCC_MEM_USED 0xEDCA10A1EDCA10A1 #define GMQCC_MEM_FREE 0xEEF8EEF8EEF8EEF8 #define GMQCC_MEM_BSL -1 #define GMQCC_MEM_BSR 1 #define GMQCC_MEM_DEBUG 1 /* debug info for dumping heap contents nicely */ #define GMQCC_MEM_DEBUG_BPL 32 /* bytes per line */ #define GMQCC_MEM_DEBUG_BIG 8 /* bytes in group */ #define GMQCC_MEM_DEBUG_BSG 4 /* bytes split group */ #ifdef GMQCC_MEM_DEBUG # include # define GMQCC_MEM_TRACE(TAG, ...) \ do { \ printf("[mem:%s]: %s ", TAG, __func__); \ printf(__VA_ARGS__); \ printf("\n"); \ } while (0) #else # define GMQCC_MEM_TRACE(TAG, ...) #endif typedef unsigned long int mem_addr; static void *mem_heap = NULL; static size_t mem_look = 0; /* lookup table offset */ static size_t mem_size = 0; /* heap size */ /* read or write to heap */ #define GMQCC_MEM_WRITEHEAP(OFFSET, TYPE, VALUE) \ do { \ TYPE *T = (TYPE*)((unsigned char*)mem_heap + (OFFSET)); \ *T = VALUE; \ } while (0) #define GMQCC_MEM_READHEAP(OFFSET, TYPE) ((TYPE)*((TYPE *)(((unsigned char*)mem_heap + (OFFSET))))) /* read of write first block to heap */ #define GMQCC_MEM_WRITEFBA(SIZE, ADDR) GMQCC_MEM_WRITEHEAP(mem_look + (SIZE) * sizeof(mem_addr), mem_addr, (ADDR)) #define GMQCC_MEM_READFBA(SIZE) GMQCC_MEM_READHEAP (mem_look + (SIZE) * sizeof(mem_addr), mem_addr) /* read and write block sizes from heap */ #define GMQCC_MEM_WRITEBS(ADDR, SIZE) GMQCC_MEM_WRITEHEAP(ADDR, mem_addr, (SIZE)) #define GMQCC_MEM_READBS(ADDR) GMQCC_MEM_READHEAP (ADDR, mem_addr); /* * Getting address of previous/following siblings, as well as * setting address of previous/following siblings. */ #define GMQCC_MEM_GETADDROFPS(ADDR) GMQCC_MEM_READHEAP ((ADDR) + 2 * sizeof(mem_addr), mem_addr) #define GMQCC_MEM_GETADDROFFS(ADDR) GMQCC_MEM_READHEAP ((ADDR) + 3 * sizeof(mem_addr), mem_addr) #define GMQCC_MEM_SETADDROFPS(ADDR,V) GMQCC_MEM_WRITEHEAP((ADDR) + 2 * sizeof(mem_addr), mem_addr, V) #define GMQCC_MEM_SETADDROFFS(ADDR,V) GMQCC_MEM_WRITEHEAP((ADDR) + 3 * sizeof(mem_addr), mem_addr, V) /* Marking blocks as used or free */ #define GMQCC_MEM_MARKUSED(ADDR) GMQCC_MEM_WRITEHEAP((ADDR) + 1 * sizeof(mem_addr), mem_addr, GMQCC_MEM_USED) #define GMQCC_MEM_MARKFREE(ADDR) GMQCC_MEM_WRITEHEAP((ADDR) + 1 * sizeof(mem_addr), mem_addr, GMQCC_MEM_FREE) /* Has block? */ #define GMQCC_MEM_HASBLOCK(SIZE) (GMQCC_MEM_READFBA(SIZE) != 0) /* Block free? */ #define GMQCC_MEM_BLOCKFREE(ADDR) ((GMQCC_MEM_READHEAP((ADDR) + 1 * sizeof(mem_addr), mem_addr)) == GMQCC_MEM_FREE) /* * Must be first since it's used internally, but also should be exposed * to the outside, statics exist after this. */ void mem_dump() { size_t addr = 0; unsigned char *ptr = (unsigned char*)mem_heap; while (addr < mem_size) { size_t offset = 0; printf("% 8X: ", addr); while (offset < GMQCC_MEM_DEBUG_BPL) { if (addr + offset >= mem_size) break; ptr ++; offset ++; printf ( !(offset%GMQCC_MEM_DEBUG_BSG) && (offset%GMQCC_MEM_DEBUG_BIG) ? "%02X " : !(offset%GMQCC_MEM_DEBUG_BIG) ? "%02X " : "%02X ", *ptr ); } printf("\n"); addr += GMQCC_MEM_DEBUG_BPL; } } static void mem_init_table(size_t size) { size_t i; GMQCC_MEM_TRACE("flow", "(%lu)", size); mem_look = 8 * ((mem_addr)1 << (size - 1)) + sizeof(mem_addr); GMQCC_MEM_WRITEHEAP(0, mem_addr, mem_look); GMQCC_MEM_WRITEHEAP(mem_look, mem_addr, size); /* write pointers to first free bock of said size */ for (i = 1; i < size; i++) GMQCC_MEM_WRITEHEAP(mem_look + sizeof(mem_addr) * i, mem_addr, 0); GMQCC_MEM_WRITEHEAP(mem_look + sizeof(mem_addr) * size, mem_addr, sizeof(mem_addr)); GMQCC_MEM_WRITEHEAP(sizeof(mem_addr), mem_addr, size); GMQCC_MEM_MARKFREE (sizeof(mem_addr) * 2); GMQCC_MEM_WRITEHEAP(sizeof(mem_addr) * 3, mem_addr, 0); GMQCC_MEM_WRITEHEAP(sizeof(mem_addr) * 4, mem_addr, 0); } /* get needed block size */ static size_t mem_getnbs(const size_t need) { size_t b = 8; size_t s = 1; while (need > b) { b *= 2; s ++; } return s; } static void mem_removeblock(mem_addr a, size_t size) { mem_addr p = GMQCC_MEM_GETADDROFPS(a); mem_addr n = GMQCC_MEM_GETADDROFFS(a); GMQCC_MEM_TRACE("flow", "(%lu, %lu)", a, size); GMQCC_MEM_SETADDROFPS(a, ~((mem_addr)0)); GMQCC_MEM_SETADDROFFS(a, ~((mem_addr)0)); /* handle singles in list */ if ((p == 0) && (n == 0)) { GMQCC_MEM_WRITEFBA(size, 0); return; } /* first in list has different sibling semantics */ if (p == 0) { GMQCC_MEM_WRITEFBA (size, n); GMQCC_MEM_SETADDROFPS(n, 0); return; } /* last item also has special meaning :) */ if (n == 0) { GMQCC_MEM_SETADDROFFS(p, 0); return; } /* middle of list */ GMQCC_MEM_SETADDROFPS(n, p); GMQCC_MEM_SETADDROFFS(p, n); } static int mem_createblock(const size_t size) { mem_addr parent; int test; GMQCC_MEM_TRACE("flow", "(%lu)", size); if (GMQCC_MEM_HASBLOCK(size)) return 0; if (size > GMQCC_MEM_READHEAP(mem_look, mem_addr)) abort(); /* recrusive ... */ test = mem_createblock(size + 1); if (test != 0) return test; /* safe splits assured */ parent = GMQCC_MEM_READFBA(size + 1); mem_removeblock(parent, size + 1); /* split it */ GMQCC_MEM_WRITEFBA(size, parent); { /* find center and split */ mem_addr block = parent + 8 * ((mem_addr)1 << (size - 1)); mem_addr left = parent; mem_addr right = block; GMQCC_MEM_TRACE( "dump", "left: %lu right: %lu parent: %lu", left, right, parent ); /* left half */ GMQCC_MEM_WRITEHEAP (left, mem_addr, size); GMQCC_MEM_MARKFREE (left); GMQCC_MEM_SETADDROFPS(left, 0); GMQCC_MEM_SETADDROFFS(left, right); /* right half */ GMQCC_MEM_WRITEHEAP (right, mem_addr, size); GMQCC_MEM_MARKFREE (right); GMQCC_MEM_SETADDROFPS(right, left); GMQCC_MEM_SETADDROFFS(right, 0); } mem_dump(); return 0; } static mem_addr mem_allocblock(const size_t size) { GMQCC_MEM_TRACE("flow", "(%lu)", size); int test = mem_createblock(size); mem_addr first; mem_addr next; if (test != 0) return 0; /* first free one */ first = GMQCC_MEM_READFBA (size); next = GMQCC_MEM_GETADDROFFS(first); mem_removeblock(first, size); GMQCC_MEM_WRITEFBA(next, size); GMQCC_MEM_MARKUSED(first); return first; } static int mem_getside(mem_addr addr, const size_t size) { size_t start = addr - sizeof(mem_addr); size_t test = 0; start /= 8; test = ((mem_addr)1 << (size)); return ((start % test) == 0) ? GMQCC_MEM_BSL : GMQCC_MEM_BSR; } static mem_addr mem_getaddr(mem_addr start, const size_t size) { size_t length = ((mem_addr)1 << (size - 1)); length *= 8; switch (mem_getside(start, size)) { case GMQCC_MEM_BSL: return start + length; case GMQCC_MEM_BSR: return start - length; } /* if reached blow up */ return (abort(), 1); } static void mem_addblock(mem_addr a, size_t s) { mem_addr first = GMQCC_MEM_READFBA(s); if (first == 0) { /* only block */ GMQCC_MEM_WRITEFBA (s, a); GMQCC_MEM_SETADDROFPS(a, 0); GMQCC_MEM_SETADDROFFS(a, 0); } else { /* add to front */ GMQCC_MEM_WRITEFBA (s, a); GMQCC_MEM_SETADDROFPS(a, 0); GMQCC_MEM_SETADDROFFS(a, first); GMQCC_MEM_SETADDROFPS(first, a); } } void mem_init(size_t size) { size_t alloc = size; size_t count = 1; size_t block = 1; /* blow up if too small */ assert (sizeof(void*) == sizeof(mem_addr)); if (!(mem_heap = malloc(size))) abort(); memset(mem_heap, 170, size); mem_size = size; alloc -= 2 * sizeof(mem_addr); while (alloc + sizeof(mem_addr) > 8 * block) { alloc -= sizeof(mem_addr); block *= 2; count ++; } /* over shot ? */ block /= 2; count --; mem_init_table(count); } /* doesn't get any simpler :-) */ void mem_destroy() { free(mem_heap); mem_heap = NULL; } void *mem_alloc(size_t amount) { GMQCC_MEM_TRACE("flow", "(%lu)", amount); size_t need = amount + 4 * sizeof(mem_addr); size_t size = mem_getnbs (need); mem_addr block = mem_allocblock(size); GMQCC_MEM_TRACE("dump", "will allocate %lu size block", size); /* standard behaviour */ if (block == 0) return NULL; GMQCC_MEM_TRACE("dump", "returning offset %lu", block); return mem_heap + block + 4 * sizeof(mem_addr); } void mem_free(void *ptr) { mem_addr start = (mem_addr)(ptr - mem_heap) - 4 * sizeof(mem_addr); size_t size = GMQCC_MEM_READHEAP(start, mem_addr); mem_addr addr = mem_getaddr(start, size); int side = mem_getside(start, size); GMQCC_MEM_TRACE ( "dump", "deallocating %s buddy (neighbour at %lu)", (side == GMQCC_MEM_BSL) ? "left" : "right", addr ); GMQCC_MEM_MARKFREE(start); /* while free block merge */ while (GMQCC_MEM_BLOCKFREE(addr)) { GMQCC_MEM_TRACE("dump", "merging ..."); mem_removeblock(addr, size); /* find new start */ start = addr < start ? addr : start; size ++; if (size == GMQCC_MEM_READHEAP(mem_look, mem_addr)) break; /* blow up */ addr = mem_getaddr(start, size); GMQCC_MEM_TRACE("dump", "new block start is %lu, buddy at %lu", start, addr); } /* add it */ GMQCC_MEM_WRITEBS(start, size); mem_addblock (start, size); }