Mercurial > projects > ldc
view lphobos/gc/gcx.d @ 650:aa6a0b7968f7
Added test case for bug #100
Removed dubious check for not emitting static private global in other modules without access. This should be handled properly somewhere else, it's causing unresolved global errors for stuff that should work (in MiniD)
author | Tomas Lindquist Olsen <tomas.l.olsen@gmail.com> |
---|---|
date | Sun, 05 Oct 2008 17:28:15 +0200 |
parents | 373489eeaf90 |
children |
line wrap: on
line source
/** * This module contains the garbage collector implementation. * * Copyright: Copyright (C) 2001-2007 Digital Mars, www.digitalmars.com. * All rights reserved. * License: * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, in both source and binary form, subject to the following * restrictions: * * o The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * o Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * o This notice may not be removed or altered from any source * distribution. * Authors: Walter Bright, David Friedman, Sean Kelly */ /* NOTE: This file has been patched from the original DMD distribution to work with the GDC compiler. Modified by David Friedman, July 2006 */ module gcx; // 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 /***************************************************/ private import gcbits; private import gcstats; private import cstdlib = std.c.stdlib : calloc, free, malloc, realloc; private import cstring = std.c.string : memcpy, memmove, memset; debug private import std.c.stdio; import std.outofmemory; import std.gc; version (GNU) { private import gcc.builtins; } version (Win32) { import win32; import std.c.windows.windows; } else version (GNU) { private import gcgcc; } else version (linux) { import gclinux; } /*version (BigEndian) private import std.intrinsic;*/ version (MULTI_THREADED) { import std.thread; } private void onOutOfMemoryError() { _d_OutOfMemory(); } private void* rt_stackBottom() { version (Win32) { return win32.os_query_stackBottom(); } else version (GNU) { return gcgcc.os_query_stackBottom(); } else version (linux) { return gclinux.os_query_stackBottom(); } } private bool thread_needLock() { return std.thread.Thread.nthreads != 1; } alias GC gc_t; version (X86) version (D_InlineAsm) { version = Asm86; } /* ======================= Leak Detector =========================== */ debug (LOGGING) { struct Log { void* p; size_t 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 { size_t dim; size_t allocdim; Log *data; void Dtor() { if (data) cstdlib.free(data); data = null; } void reserve(size_t nentries) { assert(dim <= allocdim); if (allocdim - dim < nentries) { allocdim = (dim + nentries) * 2; assert(dim + nentries <= allocdim); if (!data) { data = cast(Log *)cstdlib.malloc(allocdim * Log.sizeof); if (!data && allocdim) onOutOfMemoryError(); } else { Log *newdata; newdata = cast(Log *)cstdlib.malloc(allocdim * Log.sizeof); if (!newdata && allocdim) onOutOfMemoryError(); cstring.memcpy(newdata, data, dim * Log.sizeof); cstdlib.free(data); data = newdata; } } } void push(Log log) { reserve(1); data[dim++] = log; } void remove(size_t i) { cstring.memmove(data + i, data + i + 1, (dim - i) * Log.sizeof); dim--; } size_t find(void *p) { for (size_t 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); cstring.memcpy(data, from.data, from.dim * Log.sizeof); dim = from.dim; } } } /* ============================ GC =============================== */ alias void (*GC_FINALIZER)(void *p, bool dummy); class GCLock { } // just a dummy so we can get a global lock const uint GCVERSION = 1; // increment every time we change interface // to GC. class GC { // For passing to debug code static size_t line; static char* file; uint gcversion = GCVERSION; Gcx *gcx; // implementation static ClassInfo gcLock; // global lock void initialize() { gcLock = GCLock.classinfo; gcx = cast(Gcx *)cstdlib.calloc(1, Gcx.sizeof); if (!gcx) onOutOfMemoryError(); printf("GCX set to %p\n", gcx); gcx.initialize(); setStackBottom(rt_stackBottom()); } void Dtor() { version (linux) { //debug(PRINTF) printf("Thread %x ", pthread_self()); //debug(PRINTF) printf("GC.Dtor()\n"); } if (gcx) { gcx.Dtor(); cstdlib.free(gcx); gcx = null; } } invariant() { if (gcx) { gcx.thread_Invariant(); } } /** * */ void enable() { if (!thread_needLock()) { assert(gcx.disabled > 0); gcx.disabled--; } else synchronized (gcLock) { assert(gcx.disabled > 0); gcx.disabled--; } } /** * */ void disable() { if (!thread_needLock()) { gcx.disabled++; } else synchronized (gcLock) { gcx.disabled++; } } /** * */ void *malloc(size_t size) { if (!size) { return null; } if (!thread_needLock()) { return mallocNoSync(size); } else synchronized (gcLock) { return mallocNoSync(size); } } // // // private void *mallocNoSync(size_t size) { assert(size != 0); 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()); size += SENTINEL_EXTRA; // Compute size bin // Cache previous binsize lookup - Dave Fladebo. static size_t lastsize = -1; static Bins lastbin; if (size == lastsize) bin = lastbin; else { bin = gcx.findBin(size); lastsize = size; lastbin = bin; } if (bin < B_PAGE) { p = gcx.bucket[bin]; if (p == null) { if (!gcx.allocPage(bin) && !gcx.disabled) // try to find a new page { if (!thread_needLock()) { /* Then we haven't locked it yet. Be sure * and lock for a collection, since a finalizer * may start a new thread. */ synchronized (gcLock) { gcx.fullcollectshell(); } } else 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] = (cast(List *)p).next; cstring.memset(p + size, 0, binsize[bin] - size); //debug(PRINTF) printf("\tmalloc => %x\n", p); debug (MEMSTOMP) cstring.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) { size_t len = size * n; if (!len) { return null; } if (!thread_needLock()) { return callocNoSync(len); } else synchronized (gcLock) { return callocNoSync(len); } } // // // private void *callocNoSync(size_t size) { assert(size != 0); void *p = mallocNoSync(size); if (p) { //debug(PRINTF) printf("calloc: %x len %d\n", p, len); cstring.memset(p, 0, size); } return p; } /** * */ void *realloc(void *p, size_t size) { if (!thread_needLock()) { return reallocNoSync(p, size); } else synchronized (gcLock) { return reallocNoSync(p, size); } } // // // private void *reallocNoSync(void *p, size_t size) { gcx.p_cache = null; if (!size) { if (p) { freeNoSync(p); p = null; } } else if (!p) { p = mallocNoSync(size); } else { void *p2; size_t 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 = mallocNoSync(size); if (psize < size) size = psize; //debug(PRINTF) printf("\tcopying %d bytes\n",size); cstring.memcpy(p2, p, size); p = p2; } } else { psize = gcx.findSize(p); // find allocated size if (psize >= PAGESIZE && size >= PAGESIZE) { auto psz = psize / PAGESIZE; auto newsz = (size + PAGESIZE - 1) / PAGESIZE; if (newsz == psz) return p; auto pool = gcx.findPool(p); auto pagenum = (p - pool.baseAddr) / PAGESIZE; if (newsz < psz) { // Shrink in place synchronized (gcLock) { debug (MEMSTOMP) cstring.memset(p + size, 0xF2, psize - size); pool.freePages(pagenum + newsz, psz - newsz); } return p; } else if (pagenum + newsz <= pool.npages) { // Attempt to expand in place synchronized (gcLock) { for (size_t i = pagenum + psz; 1;) { if (i == pagenum + newsz) { debug (MEMSTOMP) cstring.memset(p + psize, 0xF0, size - psize); cstring.memset(&pool.pagetable[pagenum + psz], B_PAGEPLUS, newsz - psz); return p; } if (i == pool.ncommitted) { auto u = pool.extendPages(pagenum + newsz - pool.ncommitted); if (u == ~0u) break; i = pagenum + newsz; continue; } if (pool.pagetable[i] != B_FREE) break; i++; } } } } if (psize < size || // if new size is bigger psize > size * 2) // or less than half { p2 = mallocNoSync(size); if (psize < size) size = psize; //debug(PRINTF) printf("\tcopying %d bytes\n",size); cstring.memcpy(p2, p, size); p = p2; } } } return p; } /** * Attempt to in-place enlarge the memory block pointed to by p by at least * minbytes beyond its current capacity, up to a maximum of maxsize. This * does not attempt to move the memory block (like realloc() does). * * Returns: * 0 if could not extend p, * total size of entire memory block if successful. */ size_t extend(void* p, size_t minsize, size_t maxsize) { if (!thread_needLock()) { return extendNoSync(p, minsize, maxsize); } else synchronized (gcLock) { return extendNoSync(p, minsize, maxsize); } } // // // private size_t extendNoSync(void* p, size_t minsize, size_t maxsize) in { assert( minsize <= maxsize ); } body { //debug(PRINTF) printf("GC::extend(p = %x, minsize = %u, maxsize = %u)\n", p, minsize, maxsize); version (SENTINEL) { return 0; } auto psize = gcx.findSize(p); // find allocated size if (psize < PAGESIZE) return 0; // cannot extend buckets auto psz = psize / PAGESIZE; auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE; auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE; auto pool = gcx.findPool(p); auto pagenum = (p - pool.baseAddr) / PAGESIZE; size_t sz; for (sz = 0; sz < maxsz; sz++) { auto i = pagenum + psz + sz; if (i == pool.ncommitted) break; if (pool.pagetable[i] != B_FREE) { if (sz < minsz) return 0; break; } } if (sz >= minsz) { } else if (pagenum + psz + sz == pool.ncommitted) { auto u = pool.extendPages(minsz - sz); if (u == ~0u) return 0; sz = minsz; } else return 0; debug (MEMSTOMP) cstring.memset(p + psize, 0xF0, (psz + sz) * PAGESIZE - psize); cstring.memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz); gcx.p_cache = null; gcx.size_cache = 0; return (psz + sz) * PAGESIZE; } /** * */ void free(void *p) { if (!p) { return; } if (!thread_needLock()) { return freeNoSync(p); } else synchronized (gcLock) { return freeNoSync(p); } } // // // private void freeNoSync(void *p) { assert (p); Pool *pool; uint pagenum; Bins bin; uint biti; // 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; biti = cast(uint)(p - pool.baseAddr) / 16; pool.noscan.clear(biti); if (pool.finals.nbits && pool.finals.testClear(biti)) gcx.rt_finalize(sentinel_add(p), false); gcx.p_cache = null; bin = cast(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) cstring.memset(p, 0xF2, npages * PAGESIZE); pool.freePages(pagenum, npages); } else { // Add to free list List *list = cast(List *)p; debug (MEMSTOMP) cstring.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) { if (!p) { return 0; } if (!thread_needLock()) { return sizeOfNoSync(p); } else synchronized (gcLock) { return sizeOfNoSync(p); } } // // // private size_t sizeOfNoSync(void *p) { assert (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 (cast(size_t)p & (size - 1) & (PAGESIZE - 1)) size = 0; return size ? size - SENTINAL_EXTRA : 0; } else { if (p !is null && 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 (cast(size_t)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) { return; } if (!thread_needLock()) { checkNoSync(p); } else synchronized (gcLock) { checkNoSync(p); } } // // // private void checkNoSync(void *p) { assert(p); sentinel_Invariant(p); debug (PTRCHECK) { Pool* pool; uint pagenum; Bins bin; size_t size; p = sentinel_sub(p); pool = gcx.findPool(p); assert(pool); pagenum = (p - pool.baseAddr) / PAGESIZE; bin = cast(Bins)pool.pagetable[pagenum]; assert(bin <= B_PAGE); size = binsize[bin]; assert((cast(size_t)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(cast(void *)list != p); } } } } } // // // private 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 = cast(char *)p; } } } static void scanStaticData(gc_t g) { void *pbot; void *ptop; size_t nbytes; //debug(PRINTF) printf("+GC.scanStaticData()\n"); os_query_staticdataseg(&pbot, &nbytes); ptop = pbot + nbytes; version (GNU) { if (pbot) { g.addRange(pbot, ptop); } } else { g.addRange(pbot, ptop); } //debug(PRINTF) printf("-GC.scanStaticData()\n"); } static void unscanStaticData(gc_t g) { void *pbot; size_t nbytes; os_query_staticdataseg(&pbot, &nbytes); version (GNU) { if (pbot) { g.removeRange(pbot); } } else { g.removeRange(pbot); } } /** * add p to list of roots */ void addRoot(void *p) { if (!p) { return; } if (!thread_needLock()) { gcx.addRoot(p); } else synchronized (gcLock) { gcx.addRoot(p); } } /** * remove p from list of roots */ void removeRoot(void *p) { if (!p) { return; } if (!thread_needLock()) { gcx.removeRoot(p); } else synchronized (gcLock) { gcx.removeRoot(p); } } /** * add range to scan for roots */ void addRange(void *pbot, void *ptop) { if (!pbot || !ptop) { return; } //debug(PRINTF) printf("+GC.addRange(pbot = x%x, ptop = x%x)\n", pbot, ptop); if (!thread_needLock()) { gcx.addRange(pbot, ptop); } else synchronized (gcLock) { gcx.addRange(pbot, ptop); } //debug(PRINTF) printf("-GC.addRange()\n"); } /** * remove range */ void removeRange(void *p) { if (!p) { return; } if (!thread_needLock()) { gcx.removeRange(p); } else synchronized (gcLock) { gcx.removeRange(p); } } /** * do full garbage collection */ void fullCollect() { debug(PRINTF) printf("GC.fullCollect()\n"); if (!thread_needLock()) { gcx.fullcollectshell(); } else 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(); } /** * do full garbage collection ignoring roots */ void fullCollectNoStack() { if (!thread_needLock()) { gcx.noStack++; gcx.fullcollectshell(); gcx.noStack--; } else synchronized (gcLock) { gcx.noStack++; gcx.fullcollectshell(); gcx.noStack--; } } /** * do generational garbage collection */ void genCollect() { 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 hasPointers(void *p) { synchronized (gcLock) { gcx.HasPointers(p); } } void hasNoPointers(void *p) { if (!gcx.conservative) { synchronized (gcLock) { gcx.HasNoPointers(p); } } } void setV1_0() { gcx.conservative = 1; } /** * Retrieve statistics about garbage collection. * Useful for debugging and tuning. */ void getStats(out GCStats stats) { if (!thread_needLock()) { getStatsNoSync(stats); } else synchronized (gcLock) { getStatsNoSync(stats); } } // // // private void getStatsNoSync(out GCStats stats) { size_t psize = 0; size_t usize = 0; size_t flsize = 0; size_t n; size_t bsize = 0; //debug(PRINTF) printf("getStats()\n"); cstring.memset(&stats, 0, GCStats.sizeof); 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 = cast(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; size_t size_cache; size_t nroots; size_t rootdim; void **roots; size_t nranges; size_t rangedim; Range *ranges; uint conservative; // !=0 means conservative behavior 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) private void rt_finalize( void* p, bool dummy ) { if (finalizer) (*finalizer)(p, dummy); } void initialize() { int dummy; (cast(byte *)this)[0 .. Gcx.sizeof] = 0; stackBottom = cast(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(); cstdlib.free(pool); } if (pooltable) cstdlib.free(pooltable); if (roots) cstdlib.free(roots); if (ranges) cstdlib.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.opCmp(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) { size_t newdim = rootdim * 2 + 16; void** newroots; newroots = cast(void **)cstdlib.malloc(newdim * newroots[0].sizeof); if (!newroots) onOutOfMemoryError(); if (roots) { cstring.memcpy(newroots, roots, nroots * newroots[0].sizeof); cstdlib.free(roots); } roots = newroots; rootdim = newdim; } roots[nroots] = p; nroots++; } /** * */ void removeRoot(void *p) { for (size_t i = nroots; i--;) { if (roots[i] == p) { nroots--; cstring.memmove(roots + i, roots + i + 1, (nroots - i) * roots[0].sizeof); return; } } assert(0); } /** * */ void addRange(void *pbot, void *ptop) { debug(THREADINVARIANT) { 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) { size_t newdim = rangedim * 2 + 16; Range *newranges; newranges = cast(Range *)cstdlib.malloc(newdim * newranges[0].sizeof); if (!newranges) onOutOfMemoryError(); if (ranges) { cstring.memcpy(newranges, ranges, nranges * newranges[0].sizeof); cstdlib.free(ranges); } ranges = newranges; rangedim = newdim; } ranges[nranges].pbot = pbot; ranges[nranges].ptop = ptop; nranges++; } /** * */ void removeRange(void *pbot) { debug(THREADINVARIANT) { debug(PRINTF) printf("Thread %x ", pthread_self()); } debug(PRINTF) printf("%x.Gcx.removeRange(%x), nranges = %d\n", this, pbot, nranges); for (size_t i = nranges; i--;) { if (ranges[i].pbot == pbot) { nranges--; cstring.memmove(ranges + i, ranges + i + 1, (nranges - i) * ranges[0].sizeof); 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 */ size_t findSize(void *p) { Pool *pool; size_t size = 0; pool = findPool(p); if (pool) { uint pagenum; Bins bin; pagenum = (cast(uint)(p - pool.baseAddr)) / PAGESIZE; bin = cast(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(size_t 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(size_t size) { Pool *pool; uint npages; uint n; uint pn; uint freedpages; void *p; int state; npages = (size + PAGESIZE - 1) / PAGESIZE; for (state = 0; ; ) { // This code could use some refinement when repeatedly // allocating very large arrays. for (n = 0; n < npools; n++) { pool = pooltable[n]; pn = pool.allocPages(npages); if (pn != ~0u) goto L1; } // Failed switch (state) { case 0: if (disabled) { state = 1; continue; } // 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) cstring.memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); p = pool.baseAddr + pn * PAGESIZE; cstring.memset(cast(char *)p + size, 0, npages * PAGESIZE - size); debug (MEMSTOMP) cstring.memset(p, 0xF1, size); //debug(PRINTF) printf("\tp = %x\n", p); return p; Lnomemory: onOutOfMemoryError(); 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; else if (npages > POOLSIZE/PAGESIZE) { // Give us 150% of requested size, so there's room to extend auto n = npages + (npages >> 1); if (n < size_t.max/PAGESIZE) npages = n; } // 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 = cast(Pool *)cstdlib.calloc(1, Pool.sizeof); if (pool) { pool.initialize(npages); if (!pool.baseAddr) goto Lerr; newnpools = npools + 1; newpooltable = cast(Pool **)cstdlib.realloc(pooltable, newnpools * (Pool *).sizeof); if (!newpooltable) goto Lerr; // Sort pool into newpooltable[] for (i = 0; i < npools; i++) { if (pool.opCmp(newpooltable[i]) < 0) break; } cstring.memmove(newpooltable + i + 1, newpooltable + i, (npools - i) * (Pool *).sizeof); newpooltable[i] = pool; pooltable = newpooltable; npools = newnpools; minAddr = pooltable[0].baseAddr; maxAddr = pooltable[npools - 1].topAddr; } return pool; Lerr: pool.Dtor(); cstdlib.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] = cast(ubyte)bin; // Convert page to free list size_t size = binsize[bin]; List **b = &bucket[bin]; p = pool.baseAddr + pn * PAGESIZE; ptop = p + PAGESIZE; for (; p < ptop; p += size) { (cast(List *)p).next = *b; *b = cast(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 = cast(void **)pbot; void **p2 = cast(void **)ptop; uint changes = 0; //printf("marking range: %p -> %p\n", pbot, ptop); for (; p1 < p2; p1++) { Pool *pool; byte *p = cast(byte *)(*p1); //if (log) debug(PRINTF) printf("\tmark %x\n", p); if (p >= minAddr) { pool = findPool(p); if (pool) { size_t offset = cast(size_t)(p - pool.baseAddr); uint biti; uint pn = offset / PAGESIZE; Bins bin = cast(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 (cast(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); if (!pool.noscan.test(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. */ size_t fullcollectshell() { // The purpose of the 'shell' is to ensure all the registers // get put on the stack so they'll be scanned void *sp; size_t result; version (GNU) { __builtin_unwind_init(); sp = & sp; } else { /*asm { pushad ; mov sp[EBP],ESP ; }*/ } result = fullcollect(sp); version (GNU) { // nothing to do } else { /*asm { popad ; }*/ } return result; } /** * */ size_t 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(cast(uint)(cast(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(cast(void *)context.Esp, t.stackBottom); mark(&context.Edi, &context.Eip); } else version (GNU) { if (t.isSelf()) t.stackTop = Thread.getESP(); //%%fry printf("top=%08x bot=%08x ext=%08x\n", t.stackTop, t.stackBottom, Thread.getESP());//%%try version (STACKGROWSDOWN) mark(t.stackTop, t.stackBottom); else mark(t.stackBottom, t.stackTop); } else version (linux) { // The registers are already stored in the stack //printf("Thread: ESP = x%x, stackBottom = 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; /* version (BigEndian) bitm = bswap(bitm); */ if (!(bitm & 0xFFFF)) { bitm >>= 16; o += 16 * 16; } for (; bitm; o += 16, bitm >>= 1) { if (!(bitm & 1)) continue; pn = (o - pool.baseAddr) / PAGESIZE; bin = cast(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"); size_t freedpages = 0; size_t 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 = cast(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 (pool.finals.nbits && pool.finals.testClear(biti)) rt_finalize(cast(List *)sentinel_add(p), false); List *list = cast(List *)p; //debug(PRINTF) printf("\tcollecting %x\n", list); log_free(sentinel_add(list)); debug (MEMSTOMP) cstring.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); pool.noscan.clear(biti); if (pool.finals.nbits && pool.finals.testClear(biti)) rt_finalize(cast(List *)sentinel_add(p), false); List *list = cast(List *)p; debug(PRINTF) printf("\tcollecting %x\n", list); log_free(sentinel_add(list)); debug (MEMSTOMP) cstring.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)); pool.noscan.clear(biti); if (pool.finals.nbits && pool.finals.testClear(biti)) rt_finalize(sentinel_add(p), false); debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p); log_free(sentinel_add(p)); pool.pagetable[pn] = B_FREE; freedpages++; debug (MEMSTOMP) cstring.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; cstring.memset(p, 0xF3, PAGESIZE); } } } } } } // Zero buckets bucket[] = null; // Free complete pages, rebuild free list debug(COLLECT_PRINTF) printf("\tfree complete pages\n"); size_t 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 = cast(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 = cast(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); } /** * Indicate that block pointed to by p has possible pointers * to GC allocated memory in it. */ void HasPointers(void *p) { Pool *pool = findPool(p); assert(pool); pool.noscan.clear((p - pool.baseAddr) / 16); } /** * Indicate that block pointed to by p has no possible pointers * to GC allocated memory in it. */ void HasNoPointers(void *p) { //printf("HasNoPointers(%p)\n", p); Pool *pool = findPool(p); assert(pool); pool.noscan.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, size_t 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); size_t 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"); size_t used = 0; for (size_t i = 0; i < current.dim; i++) { size_t 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 (size_t i = 0; i < current.dim; i++) { void *p; size_t 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"); size_t 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); size_t offset = cast(uint)(p - pool.baseAddr); size_t biti; uint pn = offset / PAGESIZE; Bins bin = cast(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, size_t 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; // entries already scanned, or should not be scanned GCBits scan; // entries that need to be scanned GCBits freebits; // entries that are on the free list GCBits finals; // entries that need finalizer run on them GCBits noscan; // entries that should not be scanned uint npages; uint ncommitted; // ncommitted <= npages ubyte* pagetable; void initialize(uint npages) { size_t poolsize; //debug(PRINTF) printf("Pool::Pool(%u)\n", npages); poolsize = npages * PAGESIZE; assert(poolsize >= POOLSIZE); baseAddr = cast(byte *)os_mem_map(poolsize); // Some of the code depends on page alignment of memory pools assert((cast(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); noscan.alloc(poolsize / 16); pagetable = cast(ubyte*)cstdlib.malloc(npages); if (!pagetable) onOutOfMemoryError(); cstring.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) cstdlib.free(pagetable); mark.Dtor(); scan.Dtor(); freebits.Dtor(); finals.Dtor(); noscan.Dtor(); } void Invariant() { } invariant() { //mark.Invariant(); //scan.Invariant(); //freebits.Invariant(); //finals.Invariant(); //noscan.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 = cast(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; } return extendPages(n); } /** * Extend Pool by n pages. * Returns ~0u on failure. */ uint extendPages(uint n) { //debug(PRINTF) printf("Pool::extendPages(n = %d)\n", 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) { cstring.memset(pagetable + ncommitted, B_FREE, tocommit); auto 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) { cstring.memset(&pagetable[pagenum], B_FREE, npages); } /** * Used for sorting pooltable[] */ int opCmp(Pool *p2) { if (baseAddr < p2.baseAddr) return -1; else return cast(int)(baseAddr > p2.baseAddr); } } /* ============================ SENTINEL =============================== */ version (SENTINEL) { const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits const ubyte SENTINEL_POST = 0xF5; // 8 bits const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1; size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; } size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; } ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[sentinel_size(p)]; } void sentinel_init(void *p, size_t 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 * size_t.sizeof; } void *sentinel_sub(void *p) { return p - 2 * size_t.sizeof; } } else { const uint SENTINEL_EXTRA = 0; void sentinel_init(void *p, size_t size) { } void sentinel_Invariant(void *p) { } void *sentinel_add(void *p) { return p; } void *sentinel_sub(void *p) { return p; } }