// // Copyright (C) 2001-2003 by Digital Mars // All Rights Reserved // Written by Walter Bright // www.digitalmars.com // D Garbage Collector implementation /************** Debugging ***************************/ //debug = PRINTF; // turn on printf's //debug = COLLECT_PRINTF; // turn on printf's //debug = THREADINVARIANT; // check thread integrity //debug = LOGGING; // log allocations / frees //debug = MEMSTOMP; // stomp on memory //debug = SENTINEL; // add underrun/overrrun protection //debug = PTRCHECK; // more pointer checking //debug = PTRCHECK2; // thorough but slow pointer checking /*************** Configuration *********************/ version = STACKGROWSDOWN; // growing the stack means subtracting from the stack pointer // (use for Intel X86 CPUs) // else growing the stack means adding to the stack pointer version = MULTI_THREADED; // produce multithreaded version /***************************************************/ debug (PRINTF) import c.stdio; import c.stdlib; import gcbits; import outofmemory; import gc; import gcstats; version (Win32) { import win32; } version (linux) { import gclinux; } version (MULTI_THREADED) { import thread; } /* ======================= Leak Detector =========================== */ debug (LOGGING) { struct Log { void *p; uint size; uint line; char *file; void *parent; void print() { printf(" p = %x, size = %d, parent = %x ", p, size, parent); if (file) { printf("%s(%u)", file, line); } printf("\n"); } } struct LogArray { uint dim; uint allocdim; Log *data; void Dtor() { if (data) c.stdlib.free(data); data = null; } void reserve(uint nentries) { assert(dim <= allocdim); if (allocdim - dim < nentries) { allocdim = (dim + nentries) * 2; assert(dim + nentries <= allocdim); if (!data) { data = (Log *)c.stdlib.malloc(allocdim * Log.size); } else { Log *newdata; newdata = (Log *)c.stdlib.malloc(allocdim * Log.size); assert(newdata); memcpy(newdata, data, dim * Log.size); c.stdlib.free(data); data = newdata; } assert(!allocdim || data); } } void push(Log log) { reserve(1); data[dim++] = log; } void remove(uint i) { memmove(data + i, data + i + 1, (dim - i) * Log.size); dim--; } uint find(void *p) { for (uint i = 0; i < dim; i++) { if (data[i].p == p) return i; } return ~0u; // not found } void copy(LogArray *from) { reserve(from.dim - dim); assert(from.dim <= allocdim); memcpy(data, from.data, from.dim * Log.size); dim = from.dim; } } } /* ============================ GC =============================== */ //alias int size_t; alias void (*GC_FINALIZER)(void *p, void *dummy); class GCLock { } // just a dummy so we can get a global lock struct GC { // For passing to debug code static uint line; static char *file; Gcx *gcx; // implementation static ClassInfo gcLock; // global lock void init() { gcLock = GCLock.classinfo; gcx = (Gcx *)c.stdlib.calloc(1, Gcx.size); gcx.init(); version (Win32) { setStackBottom(win32.os_query_stackBottom()); } version (linux) { setStackBottom(gclinux.os_query_stackBottom()); } } void Dtor() { version (linux) { //debug(PRINTF) printf("Thread %x ", pthread_self()); //debug(PRINTF) printf("GC.Dtor()\n"); } if (gcx) { gcx.Dtor(); c.stdlib.free(gcx); gcx = null; } } invariant { if (gcx) gcx.thread_Invariant(); } void *malloc(size_t size) { void *p = null; Bins bin; //debug(PRINTF) printf("GC::malloc(size = %d, gcx = %p)\n", size, gcx); assert(gcx); //debug(PRINTF) printf("gcx.self = %x, pthread_self() = %x\n", gcx.self, pthread_self()); synchronized (gcLock) { if (size) { size += SENTINEL_EXTRA; // Compute size bin bin = gcx.findBin(size); if (bin < B_PAGE) { p = gcx.bucket[bin]; if (p == null) { if (!gcx.allocPage(bin)) // try to find a new page { if (!gcx.fullcollectshell()) // collect to find a new page { //gcx.newPool(1); } } if (!gcx.bucket[bin] && !gcx.allocPage(bin)) { int result; gcx.newPool(1); // allocate new pool to find a new page result = gcx.allocPage(bin); if (!result) return null; } p = gcx.bucket[bin]; } // Return next item from free list gcx.bucket[bin] = ((List *)p).next; memset(p + size, 0, binsize[bin] - size); //debug(PRINTF) printf("\tmalloc => %x\n", p); debug (MEMSTOMP) memset(p, 0xF0, size); } else { p = gcx.bigAlloc(size); if (!p) return null; } size -= SENTINEL_EXTRA; p = sentinel_add(p); sentinel_init(p, size); gcx.log_malloc(p, size); } } return p; } void *calloc(size_t size, size_t n) { uint len; void *p; len = size * n; p = malloc(len); if (p) { //debug(PRINTF) printf("calloc: %x len %d\n", p, len); memset(p, 0, len); } return p; } void *realloc(void *p, size_t size) { if (!size) { if (p) { free(p); p = null; } } else if (!p) { p = malloc(size); } else { void *p2; uint psize; //debug(PRINTF) printf("GC::realloc(p = %x, size = %u)\n", p, size); version (SENTINEL) { sentinel_Invariant(p); psize = *sentinel_size(p); if (psize != size) { p2 = malloc(size); if (psize < size) size = psize; //debug(PRINTF) printf("\tcopying %d bytes\n",size); memcpy(p2, p, size); p = p2; } } else { psize = gcx.findSize(p); // find allocated size if (psize < size || // if new size is bigger psize > size * 2) // or less than half { p2 = malloc(size); if (psize < size) size = psize; //debug(PRINTF) printf("\tcopying %d bytes\n",size); memcpy(p2, p, size); p = p2; } } } return p; } void free(void *p) { Pool *pool; uint pagenum; Bins bin; uint biti; if (!p) return; // Find which page it is in pool = gcx.findPool(p); if (!pool) // if not one of ours return; // ignore sentinel_Invariant(p); p = sentinel_sub(p); pagenum = (p - pool.baseAddr) / PAGESIZE; synchronized (gcLock) { if (pool.finals.nbits && gcx.finalizer) { biti = (uint)(p - pool.baseAddr) / 16; if (pool.finals.testClear(biti)) { (*gcx.finalizer)(sentinel_add(p), null); } } bin = (Bins)pool.pagetable[pagenum]; if (bin == B_PAGE) // if large alloc { int npages; uint n; // Free pages npages = 1; n = pagenum; while (++n < pool.ncommitted && pool.pagetable[n] == B_PAGEPLUS) npages++; debug (MEMSTOMP) memset(p, 0xF2, npages * PAGESIZE); pool.freePages(pagenum, npages); } else { // Add to free list List *list = (List *)p; debug (MEMSTOMP) memset(p, 0xF2, binsize[bin]); list.next = gcx.bucket[bin]; gcx.bucket[bin] = list; } } gcx.log_free(sentinel_add(p)); } /**************************************** * Determine the allocated size of pointer p. * If p is an interior pointer or not a gc allocated pointer, * return 0. */ size_t capacity(void *p) { version (SENTINEL) { p = sentinel_sub(p); size_t size = gcx.findSize(p); // Check for interior pointer // This depends on: // 1) size is a power of 2 for less than PAGESIZE values // 2) base of memory pool is aligned on PAGESIZE boundary if ((uint)p & (size - 1) & (PAGESIZE - 1)) size = 0; return size ? size - SENTINAL_EXTRA : 0; } else { if (p == gcx.p_cache) return gcx.size_cache; size_t size = gcx.findSize(p); // Check for interior pointer // This depends on: // 1) size is a power of 2 for less than PAGESIZE values // 2) base of memory pool is aligned on PAGESIZE boundary if ((uint)p & (size - 1) & (PAGESIZE - 1)) size = 0; else { gcx.p_cache = p; gcx.size_cache = size; } return size; } } /**************************************** * Verify that pointer p: * 1) belongs to this memory pool * 2) points to the start of an allocated piece of memory * 3) is not on a free list */ void check(void *p) { if (p) { synchronized (gcLock) { sentinel_Invariant(p); debug (PTRCHECK) { Pool *pool; uint pagenum; Bins bin; uint size; p = sentinel_sub(p); pool = gcx.findPool(p); assert(pool); pagenum = (p - pool.baseAddr) / PAGESIZE; bin = (Bins)pool.pagetable[pagenum]; assert(bin <= B_PAGE); size = binsize[bin]; assert(((uint)p & (size - 1)) == 0); debug (PTRCHECK2) { if (bin < B_PAGE) { // Check that p is not on a free list List *list; for (list = gcx.bucket[bin]; list; list = list.next) { assert((void *)list != p); } } } } } } } void setStackBottom(void *p) { version (STACKGROWSDOWN) { //p = (void *)((uint *)p + 4); if (p > gcx.stackBottom) { //debug(PRINTF) printf("setStackBottom(%x)\n", p); gcx.stackBottom = p; } } else { //p = (void *)((uint *)p - 4); if (p < gcx.stackBottom) { //debug(PRINTF) printf("setStackBottom(%x)\n", p); gcx.stackBottom = (char *)p; } } } void scanStaticData() { void *pbot; void *ptop; uint nbytes; //debug(PRINTF) printf("+GC.scanStaticData()\n"); os_query_staticdataseg(&pbot, &nbytes); ptop = pbot + nbytes; addRange(pbot, ptop); //debug(PRINTF) printf("-GC.scanStaticData()\n"); } void addRoot(void *p) // add p to list of roots { synchronized (gcLock) { gcx.addRoot(p); } } void removeRoot(void *p) // remove p from list of roots { synchronized (gcLock) { gcx.removeRoot(p); } } void addRange(void *pbot, void *ptop) // add range to scan for roots { //debug(PRINTF) printf("+GC.addRange(pbot = x%x, ptop = x%x)\n", pbot, ptop); synchronized (gcLock) { gcx.addRange(pbot, ptop); } //debug(PRINTF) printf("-GC.addRange()\n"); } void removeRange(void *pbot) // remove range { synchronized (gcLock) { gcx.removeRange(pbot); } } void fullCollect() // do full garbage collection { debug(PRINTF) printf("GC.fullCollect()\n"); synchronized (gcLock) { gcx.fullcollectshell(); } version (none) { GCStats stats; getStats(stats); debug(PRINTF) printf("poolsize = %x, usedsize = %x, freelistsize = %x\n", stats.poolsize, stats.usedsize, stats.freelistsize); } gcx.log_collect(); } void fullCollectNoStack() // do full garbage collection { gcx.noStack++; fullCollect(); gcx.noStack--; } void genCollect() // do generational garbage collection { synchronized (gcLock) { gcx.fullcollectshell(); } } void minimize() // minimize physical memory usage { // Not implemented, ignore } void setFinalizer(void *p, GC_FINALIZER pFn) { synchronized (gcLock) { gcx.finalizer = pFn; gcx.doFinalize(p); } } void enable() { synchronized (gcLock) { assert(gcx.disabled > 0); gcx.disabled--; } } void disable() { synchronized (gcLock) { gcx.disabled++; } } /***************************************** * Retrieve statistics about garbage collection. * Useful for debugging and tuning. */ void getStats(out GCStats stats) { uint psize = 0; uint usize = 0; uint flsize = 0; uint n; uint bsize = 0; //debug(PRINTF) printf("getStats()\n"); memset(&stats, 0, GCStats.size); synchronized (gcLock) { for (n = 0; n < gcx.npools; n++) { Pool *pool = gcx.pooltable[n]; psize += pool.ncommitted * PAGESIZE; for (uint j = 0; j < pool.ncommitted; j++) { Bins bin = (Bins)pool.pagetable[j]; if (bin == B_FREE) stats.freeblocks++; else if (bin == B_PAGE) stats.pageblocks++; else if (bin < B_PAGE) bsize += PAGESIZE; } } for (n = 0; n < B_PAGE; n++) { //debug(PRINTF) printf("bin %d\n", n); for (List *list = gcx.bucket[n]; list; list = list.next) { //debug(PRINTF) printf("\tlist %x\n", list); flsize += binsize[n]; } } } usize = bsize - flsize; stats.poolsize = psize; stats.usedsize = bsize - flsize; stats.freelistsize = flsize; } } /* ============================ Gcx =============================== */ enum { PAGESIZE = 4096, COMMITSIZE = (4096*16), POOLSIZE = (4096*256), } enum { B_16, B_32, B_64, B_128, B_256, B_512, B_1024, B_2048, B_PAGE, // start of large alloc B_PAGEPLUS, // continuation of large alloc B_FREE, // free page B_UNCOMMITTED, // memory not committed for this page B_MAX } alias ubyte Bins; struct List { List *next; } struct Range { void *pbot; void *ptop; } const uint binsize[B_MAX] = [ 16,32,64,128,256,512,1024,2048,4096 ]; const uint notbinsize[B_MAX] = [ ~(16u-1),~(32u-1),~(64u-1),~(128u-1),~(256u-1), ~(512u-1),~(1024u-1),~(2048u-1),~(4096u-1) ]; /* ============================ Gcx =============================== */ struct Gcx { debug (THREADINVARIANT) { pthread_t self; void thread_Invariant() { if (self != pthread_self()) printf("thread_Invariant(): gcx = %x, self = %x, pthread_self() = %x\n", this, self, pthread_self()); assert(self == pthread_self()); } } else { void thread_Invariant() { } } void *p_cache; uint size_cache; uint nroots; uint rootdim; void **roots; uint nranges; uint rangedim; Range *ranges; uint noStack; // !=0 means don't scan stack uint log; // turn on logging uint anychanges; void *stackBottom; uint inited; int disabled; // turn off collections if >0 byte *minAddr; // min(baseAddr) byte *maxAddr; // max(topAddr) uint npools; Pool **pooltable; List *bucket[B_MAX]; // free list for each size GC_FINALIZER finalizer; // finalizer function (one per GC) void init() { int dummy; ((byte *)this)[0 .. Gcx.size] = 0; stackBottom = (char *)&dummy; log_init(); debug (THREADINVARIANT) self = pthread_self(); //printf("gcx = %p, self = %x\n", this, self); inited = 1; } void Dtor() { inited = 0; for (uint i = 0; i < npools; i++) { Pool *pool = pooltable[i]; pool.Dtor(); c.stdlib.free(pool); } if (pooltable) c.stdlib.free(pooltable); if (roots) c.stdlib.free(roots); if (ranges) c.stdlib.free(ranges); } void Invariant() { } invariant { if (inited) { //printf("Gcx.invariant(): this = %p\n", this); uint i; // Assure we're called on the right thread debug (THREADINVARIANT) assert(self == pthread_self()); for (i = 0; i < npools; i++) { Pool *pool = pooltable[i]; pool.Invariant(); if (i == 0) { assert(minAddr == pool.baseAddr); } if (i + 1 < npools) { assert(pool.cmp(pooltable[i + 1]) < 0); } else if (i + 1 == npools) { assert(maxAddr == pool.topAddr); } } if (roots) { assert(rootdim != 0); assert(nroots <= rootdim); } if (ranges) { assert(rangedim != 0); assert(nranges <= rangedim); for (i = 0; i < nranges; i++) { assert(ranges[i].pbot); assert(ranges[i].ptop); assert(ranges[i].pbot <= ranges[i].ptop); } } for (i = 0; i < B_PAGE; i++) { for (List *list = bucket[i]; list; list = list.next) { } } } } /*************************************** */ void addRoot(void *p) { if (nroots == rootdim) { uint newdim = rootdim * 2 + 16; void **newroots; newroots = (void **)c.stdlib.malloc(newdim * newroots[0].size); assert(newroots); if (roots) { memcpy(newroots, roots, nroots * newroots[0].size); c.stdlib.free(roots); } roots = newroots; rootdim = newdim; } roots[nroots] = p; nroots++; } void removeRoot(void *p) { uint i; for (i = nroots; i--;) { if (roots[i] == p) { nroots--; memmove(roots + i, roots + i + 1, (nroots - i) * roots[0].size); return; } } assert(0); } /*************************************** */ void addRange(void *pbot, void *ptop) { debug(PRINTF) printf("Thread %x ", pthread_self()); debug(PRINTF) printf("%x.Gcx::addRange(%x, %x), nranges = %d\n", this, pbot, ptop, nranges); if (nranges == rangedim) { uint newdim = rangedim * 2 + 16; Range *newranges; newranges = (Range *)c.stdlib.malloc(newdim * newranges[0].size); assert(newranges); if (ranges) { memcpy(newranges, ranges, nranges * newranges[0].size); c.stdlib.free(ranges); } ranges = newranges; rangedim = newdim; } ranges[nranges].pbot = pbot; ranges[nranges].ptop = ptop; nranges++; } void removeRange(void *pbot) { debug(PRINTF) printf("Thread %x ", pthread_self()); debug(PRINTF) printf("%x.Gcx.removeRange(%x), nranges = %d\n", this, pbot, nranges); for (uint i = nranges; i--;) { if (ranges[i].pbot == pbot) { nranges--; memmove(ranges + i, ranges + i + 1, (nranges - i) * ranges[0].size); return; } } debug(PRINTF) printf("Wrong thread\n"); // This is a fatal error, but ignore it. // The problem is that we can get a Close() call on a thread // other than the one the range was allocated on. //assert(zero); } /******************************* * Find Pool that pointer is in. * Return null if not in a Pool. * Assume pooltable[] is sorted. */ Pool *findPool(void *p) { if (p >= minAddr && p < maxAddr) { if (npools == 1) { return pooltable[0]; } for (uint i = 0; i < npools; i++) { Pool *pool; pool = pooltable[i]; if (p < pool.topAddr) { if (pool.baseAddr <= p) return pool; break; } } } return null; } /******************************* * Find size of pointer p. * Returns 0 if not a gc'd pointer */ uint findSize(void *p) { Pool *pool; uint size = 0; pool = findPool(p); if (pool) { uint pagenum; Bins bin; pagenum = ((uint)(p - pool.baseAddr)) / PAGESIZE; bin = (Bins)pool.pagetable[pagenum]; size = binsize[bin]; if (bin == B_PAGE) { uint npages = pool.ncommitted; ubyte* pt; uint i; pt = &pool.pagetable[0]; for (i = pagenum + 1; i < npages; i++) { if (pt[i] != B_PAGEPLUS) break; } size = (i - pagenum) * PAGESIZE; } } return size; } /******************************* * Compute bin for size. */ static Bins findBin(uint size) { Bins bin; if (size <= 256) { if (size <= 64) { if (size <= 16) bin = B_16; else if (size <= 32) bin = B_32; else bin = B_64; } else { if (size <= 128) bin = B_128; else bin = B_256; } } else { if (size <= 1024) { if (size <= 512) bin = B_512; else bin = B_1024; } else { if (size <= 2048) bin = B_2048; else bin = B_PAGE; } } return bin; } /**************************************** * Allocate a chunk of memory that is larger than a page. * Return null if out of memory. */ void *bigAlloc(uint size) { Pool *pool; uint npages; uint n; uint pn; uint freedpages; void *p; int state; npages = (size + PAGESIZE - 1) / PAGESIZE; for (state = 0; ; ) { for (n = 0; n < npools; n++) { pool = pooltable[n]; pn = pool.allocPages(npages); if (pn != ~0u) goto L1; } // Failed switch (state) { case 0: // Try collecting freedpages = fullcollectshell(); if (freedpages >= npools * ((POOLSIZE / PAGESIZE) / 4)) { state = 1; continue; } // Allocate new pool pool = newPool(npages); if (!pool) { state = 2; continue; } pn = pool.allocPages(npages); assert(pn != ~0u); goto L1; case 1: // Allocate new pool pool = newPool(npages); if (!pool) goto Lnomemory; pn = pool.allocPages(npages); assert(pn != ~0u); goto L1; case 2: goto Lnomemory; } } L1: pool.pagetable[pn] = B_PAGE; if (npages > 1) memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); p = pool.baseAddr + pn * PAGESIZE; memset((char *)p + size, 0, npages * PAGESIZE - size); debug (MEMSTOMP) memset(p, 0xF1, size); //debug(PRINTF) printf("\tp = %x\n", p); return p; Lnomemory: assert(0); return null; } /*********************************** * Allocate a new pool with at least npages in it. * Sort it into pooltable[]. * Return null if failed. */ Pool *newPool(uint npages) { Pool *pool; Pool **newpooltable; uint newnpools; uint i; //debug(PRINTF) printf("************Gcx::newPool(npages = %d)****************\n", npages); // Round up to COMMITSIZE pages npages = (npages + (COMMITSIZE/PAGESIZE) - 1) & ~(COMMITSIZE/PAGESIZE - 1); // Minimum of POOLSIZE if (npages < POOLSIZE/PAGESIZE) npages = POOLSIZE/PAGESIZE; // Allocate successively larger pools up to 8 megs if (npools) { uint n; n = npools; if (n > 8) n = 8; // cap pool size at 8 megs n *= (POOLSIZE / PAGESIZE); if (npages < n) npages = n; } pool = (Pool *)c.stdlib.calloc(1, Pool.size); if (pool) { pool.init(npages); if (!pool.baseAddr) goto Lerr; newnpools = npools + 1; newpooltable = (Pool **)c.stdlib.realloc(pooltable, newnpools * (Pool *).size); if (!newpooltable) goto Lerr; // Sort pool into newpooltable[] for (i = 0; i < npools; i++) { if (pool.cmp(newpooltable[i]) < 0) break; } memmove(newpooltable + i + 1, newpooltable + i, (npools - i) * (Pool *).size); newpooltable[i] = pool; pooltable = newpooltable; npools = newnpools; minAddr = pooltable[0].baseAddr; maxAddr = pooltable[npools - 1].topAddr; } return pool; Lerr: pool.Dtor(); c.stdlib.free(pool); return null; } /******************************* * Allocate a page of bin's. * Returns: * 0 failed */ int allocPage(Bins bin) { Pool *pool; uint n; uint pn; byte *p; byte *ptop; //debug(PRINTF) printf("Gcx::allocPage(bin = %d)\n", bin); for (n = 0; n < npools; n++) { pool = pooltable[n]; pn = pool.allocPages(1); if (pn != ~0u) goto L1; } return 0; // failed L1: pool.pagetable[pn] = (ubyte)bin; // Convert page to free list uint size = binsize[bin]; List **b = &bucket[bin]; p = pool.baseAddr + pn * PAGESIZE; ptop = p + PAGESIZE; for (; p < ptop; p += size) { ((List *)p).next = *b; *b = (List *)p; } return 1; } /************************************ * Search a range of memory values and mark any pointers into the GC pool. */ void mark(void *pbot, void *ptop) { void **p1 = (void **)pbot; void **p2 = (void **)ptop; uint changes = 0; //if (log) debug(PRINTF) printf("Gcx::mark(%x .. %x)\n", pbot, ptop); for (; p1 < p2; p1++) { Pool *pool; byte *p = (byte *)(*p1); //if (log) debug(PRINTF) printf("\tmark %x\n", p); if (p >= minAddr) { pool = findPool(p); if (pool) { uint offset = (uint)(p - pool.baseAddr); uint biti; uint pn = offset / PAGESIZE; Bins bin = (Bins)pool.pagetable[pn]; //debug(PRINTF) printf("\t\tfound pool %x, base=%x, pn = %d, bin = %d, biti = x%x\n", pool, pool.baseAddr, pn, bin, biti); // Adjust bit to be at start of allocated memory block if (bin <= B_PAGE) { biti = (offset & notbinsize[bin]) >> 4; //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); } else if (bin == B_PAGEPLUS) { do { --pn; } while ((Bins)pool.pagetable[pn] == B_PAGEPLUS); biti = pn * (PAGESIZE / 16); } else { // Don't mark bits in B_FREE or B_UNCOMMITTED pages continue; } //debug(PRINTF) printf("\t\tmark(x%x) = %d\n", biti, pool.mark.test(biti)); if (!pool.mark.test(biti)) { //if (log) debug(PRINTF) printf("\t\tmarking %x\n", p); pool.mark.set(biti); pool.scan.set(biti); changes = 1; log_parent(sentinel_add(pool.baseAddr + biti * 16), sentinel_add(pbot)); } } } } anychanges |= changes; } /********************************* * Return number of full pages free'd. */ uint fullcollectshell() { // The purpose of the 'shell' is to ensure all the registers // get put on the stack so they'll be scanned void *sp; uint result; asm { pushad ; mov sp[EBP],ESP ; } result = fullcollect(sp); asm { popad ; } return result; } uint fullcollect(void *stackTop) { uint n; Pool *pool; debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n"); Thread.pauseAll(); p_cache = null; size_cache = 0; anychanges = 0; for (n = 0; n < npools; n++) { pool = pooltable[n]; pool.mark.zero(); pool.scan.zero(); pool.freebits.zero(); } // Mark each free entry, so it doesn't get scanned for (n = 0; n < B_PAGE; n++) { for (List *list = bucket[n]; list; list = list.next) { pool = findPool(list); assert(pool); pool.freebits.set((uint)((byte *)list - pool.baseAddr) / 16); } } for (n = 0; n < npools; n++) { pool = pooltable[n]; pool.mark.copy(&pool.freebits); } version (MULTI_THREADED) { // Scan stacks and registers for each paused thread Thread[] threads = Thread.getAll(); //thread_id id = cast(thread_id) GetCurrentThread(); for (n = 0; n < threads.length; n++) { Thread t = threads[n]; if (t && t.getState() == Thread.TS.RUNNING) { if (noStack && threads.length == 1) break; version (Win32) { CONTEXT context; context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; if (!GetThreadContext(t.hdl, &context)) { assert(0); } debug (PRINTF) printf("mt scan stack bot = %x, top = %x\n", context.Esp, t.stackBottom); mark((void *)context.Esp, t.stackBottom); mark(&context.Edi, &context.Eip); } version (linux) { // The registers are already stored in the stack //printf("foo x%x, x%x, isSelf = %d\n", Thread.getESP(), t.stackBottom, t.isSelf()); if (t.isSelf()) t.stackTop = Thread.getESP(); version (STACKGROWSDOWN) mark(t.stackTop, t.stackBottom); else mark(t.stackBottom, t.stackTop); } } } } else { if (!noStack) { // Scan stack for main thread debug(PRINTF) printf(" scan stack bot = %x, top = %x\n", stackTop, stackBottom); version (STACKGROWSDOWN) mark(stackTop, stackBottom); else mark(stackBottom, stackTop); } } // Scan roots[] debug(COLLECT_PRINTF) printf("scan roots[]\n"); mark(roots, roots + nroots); // Scan ranges[] debug(COLLECT_PRINTF) printf("scan ranges[]\n"); //log++; for (n = 0; n < nranges; n++) { debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop); mark(ranges[n].pbot, ranges[n].ptop); } //log--; debug(COLLECT_PRINTF) printf("\tscan heap\n"); while (anychanges) { anychanges = 0; for (n = 0; n < npools; n++) { uint *bbase; uint *b; uint *btop; pool = pooltable[n]; bbase = pool.scan.base(); btop = bbase + pool.scan.nwords; for (b = bbase; b < btop;) { Bins bin; uint pn; uint u; uint bitm; byte *o; bitm = *b; if (!bitm) { b++; continue; } *b = 0; o = pool.baseAddr + (b - bbase) * 32 * 16; if (!(bitm & 0xFFFF)) { bitm >>= 16; o += 16 * 16; } for (; bitm; o += 16, bitm >>= 1) { if (!(bitm & 1)) continue; pn = (o - pool.baseAddr) / PAGESIZE; bin = (Bins)pool.pagetable[pn]; if (bin < B_PAGE) { mark(o, o + binsize[bin]); } else if (bin == B_PAGE || bin == B_PAGEPLUS) { if (bin == B_PAGEPLUS) { while (pool.pagetable[pn - 1] != B_PAGE) pn--; } u = 1; while (pn + u < pool.ncommitted && pool.pagetable[pn + u] == B_PAGEPLUS) u++; mark(o, o + u * PAGESIZE); } } } } } // Free up everything not marked debug(COLLECT_PRINTF) printf("\tfree'ing\n"); uint freedpages = 0; uint freed = 0; for (n = 0; n < npools; n++) { uint pn; uint ncommitted; uint *bbase; pool = pooltable[n]; bbase = pool.mark.base(); ncommitted = pool.ncommitted; for (pn = 0; pn < ncommitted; pn++, bbase += PAGESIZE / (32 * 16)) { Bins bin = (Bins)pool.pagetable[pn]; if (bin < B_PAGE) { byte *p; byte *ptop; uint biti; uint bitstride; uint size = binsize[bin]; p = pool.baseAddr + pn * PAGESIZE; ptop = p + PAGESIZE; biti = pn * (PAGESIZE/16); bitstride = size / 16; version(none) // BUG: doesn't work because freebits() must also be cleared { // If free'd entire page if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 && bbase[3] == 0 && bbase[4] == 0 && bbase[5] == 0 && bbase[6] == 0 && bbase[7] == 0) { for (; p < ptop; p += size, biti += bitstride) { if (finalizer && pool.finals.nbits && pool.finals.testClear(biti)) { (*finalizer)((List *)sentinel_add(p), null); } List *list = (List *)p; //debug(PRINTF) printf("\tcollecting %x\n", list); log_free(sentinel_add(list)); debug (MEMSTOMP) memset(p, 0xF3, size); } pool.pagetable[pn] = B_FREE; freed += PAGESIZE; //debug(PRINTF) printf("freeing entire page %d\n", pn); continue; } } for (; p < ptop; p += size, biti += bitstride) { if (!pool.mark.test(biti)) { sentinel_Invariant(sentinel_add(p)); pool.freebits.set(biti); if (finalizer && pool.finals.nbits && pool.finals.testClear(biti)) { (*finalizer)((List *)sentinel_add(p), null); } List *list = (List *)p; debug(PRINTF) printf("\tcollecting %x\n", list); log_free(sentinel_add(list)); debug (MEMSTOMP) memset(p, 0xF3, size); freed += size; } } } else if (bin == B_PAGE) { uint biti = pn * (PAGESIZE / 16); if (!pool.mark.test(biti)) { byte *p = pool.baseAddr + pn * PAGESIZE; sentinel_Invariant(sentinel_add(p)); if (finalizer && pool.finals.nbits && pool.finals.testClear(biti)) { (*finalizer)(sentinel_add(p), null); } debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p); log_free(sentinel_add(p)); pool.pagetable[pn] = B_FREE; freedpages++; debug (MEMSTOMP) memset(p, 0xF3, PAGESIZE); while (pn + 1 < ncommitted && pool.pagetable[pn + 1] == B_PAGEPLUS) { pn++; pool.pagetable[pn] = B_FREE; freedpages++; debug (MEMSTOMP) { p += PAGESIZE; memset(p, 0xF3, PAGESIZE); } } } } } } // Zero buckets bucket[] = null; // Free complete pages, rebuild free list debug(COLLECT_PRINTF) printf("\tfree complete pages\n"); uint recoveredpages = 0; for (n = 0; n < npools; n++) { uint pn; uint ncommitted; pool = pooltable[n]; ncommitted = pool.ncommitted; for (pn = 0; pn < ncommitted; pn++) { Bins bin = (Bins)pool.pagetable[pn]; uint biti; uint u; if (bin < B_PAGE) { uint size = binsize[bin]; uint bitstride = size / 16; uint bitbase = pn * (PAGESIZE / 16); uint bittop = bitbase + (PAGESIZE / 16); byte *p; biti = bitbase; for (biti = bitbase; biti < bittop; biti += bitstride) { if (!pool.freebits.test(biti)) goto Lnotfree; } pool.pagetable[pn] = B_FREE; recoveredpages++; continue; Lnotfree: p = pool.baseAddr + pn * PAGESIZE; for (u = 0; u < PAGESIZE; u += size) { biti = bitbase + u / 16; if (pool.freebits.test(biti)) { List *list; list = (List *)(p + u); if (list.next != bucket[bin]) // avoid unnecessary writes list.next = bucket[bin]; bucket[bin] = list; } } } } } debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages); debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, npools); Thread.resumeAll(); return freedpages + recoveredpages; } /********************************* * Run finalizer on p when it is free'd. */ void doFinalize(void *p) { Pool *pool = findPool(p); assert(pool); // Only allocate finals[] if we actually need it if (!pool.finals.nbits) pool.finals.alloc(pool.mark.nbits); pool.finals.set((p - pool.baseAddr) / 16); } /***** Leak Detector ******/ debug (LOGGING) { LogArray current; LogArray prev; void log_init() { //debug(PRINTF) printf("+log_init()\n"); current.reserve(1000); prev.reserve(1000); //debug(PRINTF) printf("-log_init()\n"); } void log_malloc(void *p, uint size) { //debug(PRINTF) printf("+log_malloc(p = %x, size = %d)\n", p, size); Log log; log.p = p; log.size = size; log.line = GC.line; log.file = GC.file; log.parent = null; GC.line = 0; GC.file = null; current.push(log); //debug(PRINTF) printf("-log_malloc()\n"); } void log_free(void *p) { //debug(PRINTF) printf("+log_free(%x)\n", p); uint i; i = current.find(p); if (i == ~0u) { debug(PRINTF) printf("free'ing unallocated memory %x\n", p); } else current.remove(i); //debug(PRINTF) printf("-log_free()\n"); } void log_collect() { //debug(PRINTF) printf("+log_collect()\n"); // Print everything in current that is not in prev debug(PRINTF) printf("New pointers this cycle: --------------------------------\n"); int used = 0; for (uint i = 0; i < current.dim; i++) { uint j; j = prev.find(current.data[i].p); if (j == ~0u) current.data[i].print(); else used++; } debug(PRINTF) printf("All roots this cycle: --------------------------------\n"); for (uint i = 0; i < current.dim; i++) { void *p; uint j; p = current.data[i].p; if (!findPool(current.data[i].parent)) { j = prev.find(current.data[i].p); if (j == ~0u) debug(PRINTF) printf("N"); else debug(PRINTF) printf(" ");; current.data[i].print(); } } debug(PRINTF) printf("Used = %d-------------------------------------------------\n", used); prev.copy(¤t); debug(PRINTF) printf("-log_collect()\n"); } void log_parent(void *p, void *parent) { //debug(PRINTF) printf("+log_parent()\n"); uint i; i = current.find(p); if (i == ~0u) { debug(PRINTF) printf("parent'ing unallocated memory %x, parent = %x\n", p, parent); Pool *pool; pool = findPool(p); assert(pool); uint offset = (uint)(p - pool.baseAddr); uint biti; uint pn = offset / PAGESIZE; Bins bin = (Bins)pool.pagetable[pn]; biti = (offset & notbinsize[bin]); debug(PRINTF) printf("\tbin = %d, offset = x%x, biti = x%x\n", bin, offset, biti); } else { current.data[i].parent = parent; } //debug(PRINTF) printf("-log_parent()\n"); } } else { void log_init() { } void log_malloc(void *p, uint size) { } void log_free(void *p) { } void log_collect() { } void log_parent(void *p, void *parent) { } } }; /* ============================ Pool =============================== */ struct Pool { byte* baseAddr; byte* topAddr; GCBits mark; GCBits scan; GCBits finals; GCBits freebits; uint npages; uint ncommitted; // ncommitted <= npages ubyte* pagetable; void init(uint npages) { uint poolsize; //debug(PRINTF) printf("Pool::Pool(%u)\n", npages); poolsize = npages * PAGESIZE; assert(poolsize >= POOLSIZE); baseAddr = (byte *)os_mem_map(poolsize); // Some of the code depends on page alignment of memory pools assert(((uint)baseAddr & (PAGESIZE - 1)) == 0); if (!baseAddr) { //debug(PRINTF) printf("GC fail: poolsize = x%x, errno = %d\n", poolsize, errno); //debug(PRINTF) printf("message = '%s'\n", sys_errlist[errno]); npages = 0; poolsize = 0; } //assert(baseAddr); topAddr = baseAddr + poolsize; mark.alloc(poolsize / 16); scan.alloc(poolsize / 16); freebits.alloc(poolsize / 16); pagetable = (ubyte*)c.stdlib.malloc(npages); memset(pagetable, B_UNCOMMITTED, npages); this.npages = npages; ncommitted = 0; } void Dtor() { if (baseAddr) { int result; if (ncommitted) { result = os_mem_decommit(baseAddr, 0, ncommitted * PAGESIZE); assert(result == 0); ncommitted = 0; } if (npages) { result = os_mem_unmap(baseAddr, npages * PAGESIZE); assert(result == 0); npages = 0; } baseAddr = null; topAddr = null; } if (pagetable) c.stdlib.free(pagetable); mark.Dtor(); scan.Dtor(); finals.Dtor(); freebits.Dtor(); } void Invariant() { } invariant { //mark.Invariant(); //scan.Invariant(); //finals.Invariant(); //freebits.Invariant(); if (baseAddr) { //if (baseAddr + npages * PAGESIZE != topAddr) //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr); assert(baseAddr + npages * PAGESIZE == topAddr); assert(ncommitted <= npages); } for (uint i = 0; i < npages; i++) { Bins bin = (Bins)pagetable[i]; assert(bin < B_MAX); } } /************************************** * Allocate n pages from Pool. * Returns ~0u on failure. */ uint allocPages(uint n) { uint i; uint n2; //debug(PRINTF) printf("Pool::allocPages(n = %d)\n", n); n2 = n; for (i = 0; i < ncommitted; i++) { if (pagetable[i] == B_FREE) { if (--n2 == 0) { //debug(PRINTF) printf("\texisting pn = %d\n", i - n + 1); return i - n + 1; } } else n2 = n; } if (ncommitted + n <= npages) { uint tocommit; tocommit = (n + (COMMITSIZE/PAGESIZE) - 1) & ~(COMMITSIZE/PAGESIZE - 1); if (ncommitted + tocommit > npages) tocommit = npages - ncommitted; //debug(PRINTF) printf("\tlooking to commit %d more pages\n", tocommit); //fflush(stdout); if (os_mem_commit(baseAddr, ncommitted * PAGESIZE, tocommit * PAGESIZE) == 0) { memset(pagetable + ncommitted, B_FREE, tocommit); i = ncommitted; ncommitted += tocommit; while (i && pagetable[i - 1] == B_FREE) i--; return i; } //debug(PRINTF) printf("\tfailed to commit %d pages\n", tocommit); } return ~0u; } /********************************** * Free npages pages starting with pagenum. */ void freePages(uint pagenum, uint npages) { memset(&pagetable[pagenum], B_FREE, npages); } /*************************** * Used for sorting pooltable[] */ int cmp(Pool *p2) { return baseAddr - p2.baseAddr; } } /* ============================ SENTINEL =============================== */ version (SENTINEL) { const uint SENTINEL_PRE = 0xF4F4F4F4; // 32 bits const ubyte SENTINEL_POST = 0xF5; // 8 bits const uint SENTINEL_EXTRA = 2 * uint.size + 1; uint* sentinel_size(void *p) { return &((uint *)p)[-2]; } uint* sentinel_pre(void *p) { return &((uint *)p)[-1]; } ubyte* sentinel_post(void *p) { return &((ubyte *)p)[sentinel_size(p)]; } void sentinel_init(void *p, uint size) { *sentinel_size(p) = size; *sentinel_pre(p) = SENTINEL_PRE; *sentinel_post(p) = SENTINEL_POST; } void sentinel_Invariant(void *p) { assert(*sentinel_pre(p) == SENTINEL_PRE); assert(*sentinel_post(p) == SENTINEL_POST); } void *sentinel_add(void *p) { return p + 2 * uint.size; } void *sentinel_sub(void *p) { return p - 2 * uint.size; } } else { const uint SENTINEL_EXTRA = 0; void sentinel_init(void *p, uint size) { } void sentinel_Invariant(void *p) { } void *sentinel_add(void *p) { return p; } void *sentinel_sub(void *p) { return p; } }