2 * This module contains the garbage collector implementation.
4 * Copyright: Copyright (C) 2001-2007 Digital Mars, www.digitalmars.com.
7 * This software is provided 'as-is', without any express or implied
8 * warranty. In no event will the authors be held liable for any damages
9 * arising from the use of this software.
11 * Permission is granted to anyone to use this software for any purpose,
12 * including commercial applications, and to alter it and redistribute it
13 * freely, in both source and binary form, subject to the following
16 * o The origin of this software must not be misrepresented; you must not
17 * claim that you wrote the original software. If you use this software
18 * in a product, an acknowledgment in the product documentation would be
19 * appreciated but is not required.
20 * o Altered source versions must be plainly marked as such, and must not
21 * be misrepresented as being the original software.
22 * o This notice may not be removed or altered from any source
24 * Authors: Walter Bright, David Friedman, Sean Kelly
29 // D Programming Language Garbage Collector implementation
31 /************** Debugging ***************************/
33 //debug = COLLECT_PRINTF; // turn on printf's
34 //debug = MEMSTOMP; // stomp on memory
35 //debug = SENTINEL; // add underrun/overrrun protection
36 //debug = PTRCHECK; // more pointer checking
37 //debug = PTRCHECK2; // thorough but slow pointer checking
39 /*************** Configuration *********************/
41 version = STACKGROWSDOWN; // growing the stack means subtracting from the stack pointer
42 // (use for Intel X86 CPUs)
43 // else growing the stack means adding to the stack pointer
45 /***************************************************/
47 import rt.gc.cdgc.bits: GCBits;
48 import rt.gc.cdgc.stats: GCStats;
49 import alloc = rt.gc.cdgc.alloc;
50 import rt.gc.cdgc.dynarray: DynArray;
52 import cstdlib = tango.stdc.stdlib;
53 import cstring = tango.stdc.string;
56 * This is a small optimization that proved it's usefulness. For small chunks
57 * or memory memset() seems to be slower (probably because of the call) that
58 * simply doing a simple loop to set the memory.
60 void memset(void* dst, int c, size_t n)
62 // This number (32) has been determined empirically
64 cstring.memset(dst, c, n);
67 auto p = cast(ubyte*)(dst);
74 // BUG: The following import will likely not work, since the gcc
75 // subdirectory is elsewhere. Instead, perhaps the functions
76 // could be declared directly or some other resolution could
78 static import gcc.builtins; // for __builtin_unwind_int
93 FINALIZE = 0b0000_0001,
94 NO_SCAN = 0b0000_0010,
95 NO_MOVE = 0b0000_0100,
96 ALL_BITS = 0b1111_1111
99 extern (C) void* rt_stackBottom();
100 extern (C) void* rt_stackTop();
102 extern (C) void rt_finalize( void* p, bool det = true );
104 alias void delegate(Object) DEvent;
105 extern (C) void rt_attachDisposeEvent(Object h, DEvent e);
106 extern (C) bool rt_detachDisposeEvent(Object h, DEvent e);
109 alias void delegate( void*, void* ) scanFn;
111 extern (C) void rt_scanStaticData( scanFn scan );
113 extern (C) bool thread_needLock();
114 extern (C) void thread_suspendAll();
115 extern (C) void thread_resumeAll();
117 extern (C) void thread_scanAll( scanFn fn, void* curStackTop = null );
119 extern (C) void onOutOfMemoryError();
123 OPFAIL = ~cast(size_t)0
131 /* ============================ GC =============================== */
134 class GCLock { } // just a dummy so we can get a global lock
137 const uint GCVERSION = 1; // increment every time we change interface
142 // For passing to debug code
146 uint gcversion = GCVERSION;
148 Gcx *gcx; // implementation
149 static ClassInfo gcLock; // global lock
154 gcLock = GCLock.classinfo;
155 gcx = cast(Gcx*) cstdlib.calloc(1, Gcx.sizeof);
157 onOutOfMemoryError();
159 setStackBottom(rt_stackBottom());
168 if (!thread_needLock())
170 assert(gcx.disabled > 0);
173 else synchronized (gcLock)
175 assert(gcx.disabled > 0);
186 if (!thread_needLock())
190 else synchronized (gcLock)
200 uint getAttr(void* p)
209 Pool* pool = gcx.findPool(p);
214 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
216 oldb = gcx.getBits(pool, biti);
221 if (!thread_needLock())
225 else synchronized (gcLock)
235 uint setAttr(void* p, uint mask)
244 Pool* pool = gcx.findPool(p);
249 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
251 oldb = gcx.getBits(pool, biti);
252 gcx.setBits(pool, biti, mask);
257 if (!thread_needLock())
261 else synchronized (gcLock)
271 uint clrAttr(void* p, uint mask)
280 Pool* pool = gcx.findPool(p);
285 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
287 oldb = gcx.getBits(pool, biti);
288 gcx.clrBits(pool, biti, mask);
293 if (!thread_needLock())
297 else synchronized (gcLock)
307 void *malloc(size_t size, uint bits = 0)
314 if (!thread_needLock())
316 return mallocNoSync(size, bits);
318 else synchronized (gcLock)
320 return mallocNoSync(size, bits);
328 private void *mallocNoSync(size_t size, uint bits = 0)
337 size += SENTINEL_EXTRA;
340 // Cache previous binsize lookup - Dave Fladebo.
341 static size_t lastsize = -1;
343 if (size == lastsize)
347 bin = gcx.findBin(size);
357 if (!gcx.allocPage(bin) && !gcx.disabled) // try to find a new page
359 if (!thread_needLock())
361 /* Then we haven't locked it yet. Be sure
362 * and lock for a collection, since a finalizer
363 * may start a new thread.
365 synchronized (gcLock)
367 gcx.fullcollectshell();
370 else if (!gcx.fullcollectshell()) // collect to find a new page
375 if (!gcx.bucket[bin] && !gcx.allocPage(bin))
377 gcx.newPool(1); // allocate new pool to find a new page
378 int result = gcx.allocPage(bin);
380 onOutOfMemoryError();
385 // Return next item from free list
386 gcx.bucket[bin] = (cast(List*)p).next;
387 if( !(bits & BlkAttr.NO_SCAN) )
388 memset(p + size, 0, binsize[bin] - size);
389 debug (MEMSTOMP) memset(p, 0xF0, size);
393 p = gcx.bigAlloc(size);
395 onOutOfMemoryError();
397 size -= SENTINEL_EXTRA;
399 sentinel_init(p, size);
403 Pool *pool = gcx.findPool(p);
406 gcx.setBits(pool, cast(size_t)(p - pool.baseAddr) / 16, bits);
415 void *calloc(size_t size, uint bits = 0)
422 if (!thread_needLock())
424 return callocNoSync(size, bits);
426 else synchronized (gcLock)
428 return callocNoSync(size, bits);
436 private void *callocNoSync(size_t size, uint bits = 0)
440 void *p = mallocNoSync(size, bits);
449 void *realloc(void *p, size_t size, uint bits = 0)
451 if (!thread_needLock())
453 return reallocNoSync(p, size, bits);
455 else synchronized (gcLock)
457 return reallocNoSync(p, size, bits);
465 private void *reallocNoSync(void *p, size_t size, uint bits = 0)
477 p = mallocNoSync(size, bits);
486 sentinel_Invariant(p);
487 psize = *sentinel_size(p);
492 Pool *pool = gcx.findPool(p);
496 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
500 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
501 gcx.setBits(pool, biti, bits);
505 bits = gcx.getBits(pool, biti);
509 p2 = mallocNoSync(size, bits);
512 cstring.memcpy(p2, p, size);
518 psize = gcx.findSize(p); // find allocated size
519 if (psize >= PAGESIZE && size >= PAGESIZE)
521 auto psz = psize / PAGESIZE;
522 auto newsz = (size + PAGESIZE - 1) / PAGESIZE;
526 auto pool = gcx.findPool(p);
527 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
532 synchronized (gcLock)
535 memset(p + size, 0xF2, psize - size);
536 pool.freePages(pagenum + newsz, psz - newsz);
540 else if (pagenum + newsz <= pool.npages)
542 // Attempt to expand in place
543 synchronized (gcLock)
545 for (size_t i = pagenum + psz; 1;)
547 if (i == pagenum + newsz)
550 memset(p + psize, 0xF0,
552 memset(pool.pagetable + pagenum +
553 psz, B_PAGEPLUS, newsz - psz);
556 if (i == pool.npages)
560 if (pool.pagetable[i] != B_FREE)
567 if (psize < size || // if new size is bigger
568 psize > size * 2) // or less than half
572 Pool *pool = gcx.findPool(p);
576 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
580 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
581 gcx.setBits(pool, biti, bits);
585 bits = gcx.getBits(pool, biti);
589 p2 = mallocNoSync(size, bits);
592 cstring.memcpy(p2, p, size);
602 * Attempt to in-place enlarge the memory block pointed to by p by at least
603 * minbytes beyond its current capacity, up to a maximum of maxsize. This
604 * does not attempt to move the memory block (like realloc() does).
607 * 0 if could not extend p,
608 * total size of entire memory block if successful.
610 size_t extend(void* p, size_t minsize, size_t maxsize)
612 if (!thread_needLock())
614 return extendNoSync(p, minsize, maxsize);
616 else synchronized (gcLock)
618 return extendNoSync(p, minsize, maxsize);
626 private size_t extendNoSync(void* p, size_t minsize, size_t maxsize)
629 assert( minsize <= maxsize );
637 auto psize = gcx.findSize(p); // find allocated size
638 if (psize < PAGESIZE)
639 return 0; // cannot extend buckets
641 auto psz = psize / PAGESIZE;
642 auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE;
643 auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE;
645 auto pool = gcx.findPool(p);
646 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
649 for (sz = 0; sz < maxsz; sz++)
651 auto i = pagenum + psz + sz;
652 if (i == pool.npages)
654 if (pool.pagetable[i] != B_FREE)
664 memset(p + psize, 0xF0, (psz + sz) * PAGESIZE - psize);
665 memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz);
668 return (psz + sz) * PAGESIZE;
675 size_t reserve(size_t size)
682 if (!thread_needLock())
684 return reserveNoSync(size);
686 else synchronized (gcLock)
688 return reserveNoSync(size);
696 private size_t reserveNoSync(size_t size)
701 return gcx.reserve(size);
715 if (!thread_needLock())
717 return freeNoSync(p);
719 else synchronized (gcLock)
721 return freeNoSync(p);
729 private void freeNoSync(void *p)
738 // Find which page it is in
739 pool = gcx.findPool(p);
740 if (!pool) // if not one of ours
742 sentinel_Invariant(p);
744 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
745 biti = cast(size_t)(p - pool.baseAddr) / 16;
746 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
748 bin = cast(Bins)pool.pagetable[pagenum];
749 if (bin == B_PAGE) // if large alloc
754 while (++n < pool.npages && pool.pagetable[n] == B_PAGEPLUS)
756 debug (MEMSTOMP) memset(p, 0xF2, npages * PAGESIZE);
757 pool.freePages(pagenum, npages);
762 List *list = cast(List*)p;
764 debug (MEMSTOMP) memset(p, 0xF2, binsize[bin]);
766 list.next = gcx.bucket[bin];
767 gcx.bucket[bin] = list;
773 * Determine the base address of the block containing p. If p is not a gc
774 * allocated pointer, return null.
776 void* addrOf(void *p)
783 if (!thread_needLock())
785 return addrOfNoSync(p);
787 else synchronized (gcLock)
789 return addrOfNoSync(p);
797 void* addrOfNoSync(void *p)
804 return gcx.findBase(p);
809 * Determine the allocated size of pointer p. If p is an interior pointer
810 * or not a gc allocated pointer, return 0.
812 size_t sizeOf(void *p)
819 if (!thread_needLock())
821 return sizeOfNoSync(p);
823 else synchronized (gcLock)
825 return sizeOfNoSync(p);
833 private size_t sizeOfNoSync(void *p)
840 size_t size = gcx.findSize(p);
842 // Check for interior pointer
844 // 1) size is a power of 2 for less than PAGESIZE values
845 // 2) base of memory pool is aligned on PAGESIZE boundary
846 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
848 return size ? size - SENTINEL_EXTRA : 0;
852 if (p == gcx.p_cache)
853 return gcx.size_cache;
855 size_t size = gcx.findSize(p);
857 // Check for interior pointer
859 // 1) size is a power of 2 for less than PAGESIZE values
860 // 2) base of memory pool is aligned on PAGESIZE boundary
861 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
866 gcx.size_cache = size;
875 * Determine the base address of the block containing p. If p is not a gc
876 * allocated pointer, return null.
878 BlkInfo query(void *p)
886 if (!thread_needLock())
888 return queryNoSync(p);
890 else synchronized (gcLock)
892 return queryNoSync(p);
900 BlkInfo queryNoSync(void *p)
904 return gcx.getInfo(p);
909 * Verify that pointer p:
910 * 1) belongs to this memory pool
911 * 2) points to the start of an allocated piece of memory
912 * 3) is not on a free list
921 if (!thread_needLock())
925 else synchronized (gcLock)
935 private void checkNoSync(void *p)
939 sentinel_Invariant(p);
948 pool = gcx.findPool(p);
950 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
951 bin = cast(Bins)pool.pagetable[pagenum];
952 assert(bin <= B_PAGE);
954 assert((cast(size_t)p & (size - 1)) == 0);
960 // Check that p is not on a free list
963 for (list = gcx.bucket[bin]; list; list = list.next)
965 assert(cast(void*)list != p);
976 private void setStackBottom(void *p)
978 version (STACKGROWSDOWN)
980 //p = (void *)((uint *)p + 4);
981 if (p > gcx.stackBottom)
988 //p = (void *)((uint *)p - 4);
989 if (p < gcx.stackBottom)
991 gcx.stackBottom = cast(char*)p;
998 * add p to list of roots
1000 void addRoot(void *p)
1007 if (!thread_needLock())
1009 if (roots.append(p) is null)
1010 onOutOfMemoryError();
1012 else synchronized (gcLock)
1014 if (roots.append(p) is null)
1015 onOutOfMemoryError();
1021 * remove p from list of roots
1023 void removeRoot(void *p)
1031 if (!thread_needLock())
1033 r = roots.remove(p);
1035 else synchronized (gcLock)
1037 r = roots.remove(p);
1044 * add range to scan for roots
1046 void addRange(void *p, size_t sz)
1053 if (!thread_needLock())
1055 if (ranges.append(Range(p, p+sz)) is null)
1056 onOutOfMemoryError();
1058 else synchronized (gcLock)
1060 if (ranges.append(Range(p, p+sz)) is null)
1061 onOutOfMemoryError();
1069 void removeRange(void *p)
1077 if (!thread_needLock())
1079 r = ranges.remove(Range(p, null));
1081 else synchronized (gcLock)
1083 r = ranges.remove(Range(p, null));
1090 * do full garbage collection
1095 if (!thread_needLock())
1097 gcx.fullcollectshell();
1099 else synchronized (gcLock)
1101 gcx.fullcollectshell();
1114 * do full garbage collection ignoring roots
1116 void fullCollectNoStack()
1118 if (!thread_needLock())
1121 gcx.fullcollectshell();
1124 else synchronized (gcLock)
1127 gcx.fullcollectshell();
1134 * minimize free space usage
1138 if (!thread_needLock())
1142 else synchronized (gcLock)
1150 * Retrieve statistics about garbage collection.
1151 * Useful for debugging and tuning.
1153 void getStats(out GCStats stats)
1155 if (!thread_needLock())
1157 getStatsNoSync(stats);
1159 else synchronized (gcLock)
1161 getStatsNoSync(stats);
1169 private void getStatsNoSync(out GCStats stats)
1178 memset(&stats, 0, GCStats.sizeof);
1180 for (n = 0; n < pools.length; n++)
1182 Pool* pool = pools[n];
1183 psize += pool.npages * PAGESIZE;
1184 for (size_t j = 0; j < pool.npages; j++)
1186 Bins bin = cast(Bins)pool.pagetable[j];
1189 else if (bin == B_PAGE)
1191 else if (bin < B_PAGE)
1196 for (n = 0; n < B_PAGE; n++)
1198 for (List *list = gcx.bucket[n]; list; list = list.next)
1199 flsize += binsize[n];
1202 usize = bsize - flsize;
1204 stats.poolsize = psize;
1205 stats.usedsize = bsize - flsize;
1206 stats.freelistsize = flsize;
1209 /******************* weak-reference support *********************/
1211 // call locked if necessary
1212 private T locked(T)(in T delegate() code)
1214 if (thread_needLock)
1215 synchronized(gcLock) return code();
1220 private struct WeakPointer
1224 void ondestroy(Object r)
1226 assert(r is reference);
1227 // lock for memory consistency (parallel readers)
1228 // also ensures that weakpointerDestroy can be called while another
1229 // thread is freeing the reference with "delete"
1230 locked!(void)({ reference = null; });
1235 * Create a weak pointer to the given object.
1236 * Returns a pointer to an opaque struct allocated in C memory.
1238 void* weakpointerCreate( Object r )
1242 // must be allocated in C memory
1243 // 1. to hide the reference from the GC
1244 // 2. the GC doesn't scan delegates added by rt_attachDisposeEvent
1246 auto wp = cast(WeakPointer*)(cstdlib.malloc(WeakPointer.sizeof));
1248 onOutOfMemoryError();
1250 rt_attachDisposeEvent(r, &wp.ondestroy);
1257 * Destroy a weak pointer returned by weakpointerCreate().
1258 * If null is passed, nothing happens.
1260 void weakpointerDestroy( void* p )
1264 auto wp = cast(WeakPointer*)p;
1265 // must be extra careful about the GC or parallel threads
1266 // finalizing the reference at the same time
1269 rt_detachDisposeEvent(wp.reference, &wp.ondestroy);
1276 * Query a weak pointer and return either the object passed to
1277 * weakpointerCreate, or null if it was free'd in the meantime.
1278 * If null is passed, null is returned.
1280 Object weakpointerGet( void* p )
1284 // NOTE: could avoid the lock by using Fawzi style GC counters but
1285 // that'd require core.sync.Atomic and lots of care about memory
1286 // consistency it's an optional optimization see
1287 // http://dsource.org/projects/tango/browser/trunk/user/tango/core/Lifetime.d?rev=5100#L158
1288 return locked!(Object)({
1289 return (cast(WeakPointer*)p).reference;
1296 /* ============================ Gcx =============================== */
1301 POOLSIZE = (4096*256),
1315 B_PAGE, // start of large alloc
1316 B_PAGEPLUS, // continuation of large alloc
1317 B_FREE, // free page
1335 int opCmp(in Range other)
1337 if (pbot < other.pbot)
1340 return cast(int)(pbot > other.pbot);
1345 const uint binsize[B_MAX] = [ 16,32,64,128,256,512,1024,2048,4096 ];
1346 const uint notbinsize[B_MAX] = [ ~(16u-1),~(32u-1),~(64u-1),~(128u-1),~(256u-1),
1347 ~(512u-1),~(1024u-1),~(2048u-1),~(4096u-1) ];
1349 DynArray!(void*) roots;
1351 DynArray!(Range) ranges;
1353 DynArray!(Pool) pools;
1356 /* ============================ Gcx =============================== */
1365 uint noStack; // !=0 means don't scan stack
1366 uint log; // turn on logging
1370 int disabled; // turn off collections if >0
1372 byte *minAddr; // min(baseAddr)
1373 byte *maxAddr; // max(topAddr)
1375 List *bucket[B_MAX]; // free list for each size
1381 (cast(byte*)this)[0 .. Gcx.sizeof] = 0;
1382 stackBottom = cast(char*)&dummy;
1383 //printf("gcx = %p, self = %x\n", this, self);
1388 void Invariant() { }
1395 //printf("Gcx.invariant(): this = %p\n", this);
1398 for (i = 0; i < pools.length; i++)
1400 Pool* pool = pools[i];
1404 assert(minAddr == pool.baseAddr);
1406 if (i + 1 < pools.length)
1408 assert(*pool < pools[i + 1]);
1410 else if (i + 1 == pools.length)
1412 assert(maxAddr == pool.topAddr);
1419 for (i = 0; i < ranges.length; i++)
1421 assert(ranges[i].pbot);
1422 assert(ranges[i].ptop);
1423 assert(ranges[i].pbot <= ranges[i].ptop);
1426 for (i = 0; i < B_PAGE; i++)
1428 for (List *list = bucket[i]; list; list = list.next)
1437 * Find Pool that pointer is in.
1438 * Return null if not in a Pool.
1439 * Assume pools is sorted.
1441 Pool *findPool(void *p)
1443 if (p >= minAddr && p < maxAddr)
1445 if (pools.length == 1)
1450 for (size_t i = 0; i < pools.length; i++)
1452 Pool* pool = pools[i];
1453 if (p < pool.topAddr)
1455 if (pool.baseAddr <= p)
1466 * Find base address of block containing pointer p.
1467 * Returns null if not a gc'd pointer
1469 void* findBase(void *p)
1476 size_t offset = cast(size_t)(p - pool.baseAddr);
1477 size_t pn = offset / PAGESIZE;
1478 Bins bin = cast(Bins)pool.pagetable[pn];
1480 // Adjust bit to be at start of allocated memory block
1483 return pool.baseAddr + (offset & notbinsize[bin]);
1485 else if (bin == B_PAGEPLUS)
1489 --pn, offset -= PAGESIZE;
1490 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1492 return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1496 // we are in a B_FREE page
1505 * Find size of pointer p.
1506 * Returns 0 if not a gc'd pointer
1508 size_t findSize(void *p)
1519 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
1520 bin = cast(Bins)pool.pagetable[pagenum];
1521 size = binsize[bin];
1527 pt = &pool.pagetable[0];
1528 for (i = pagenum + 1; i < pool.npages; i++)
1530 if (pt[i] != B_PAGEPLUS)
1533 size = (i - pagenum) * PAGESIZE;
1543 BlkInfo getInfo(void* p)
1551 size_t offset = cast(size_t)(p - pool.baseAddr);
1552 size_t pn = offset / PAGESIZE;
1553 Bins bin = cast(Bins)pool.pagetable[pn];
1555 ////////////////////////////////////////////////////////////////////
1557 ////////////////////////////////////////////////////////////////////
1561 info.base = pool.baseAddr + (offset & notbinsize[bin]);
1563 else if (bin == B_PAGEPLUS)
1567 --pn, offset -= PAGESIZE;
1569 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1571 info.base = pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1573 // fix bin for use by size calc below
1574 bin = cast(Bins)pool.pagetable[pn];
1577 ////////////////////////////////////////////////////////////////////
1579 ////////////////////////////////////////////////////////////////////
1581 info.size = binsize[bin];
1587 pt = &pool.pagetable[0];
1588 for (i = pn + 1; i < pool.npages; i++)
1590 if (pt[i] != B_PAGEPLUS)
1593 info.size = (i - pn) * PAGESIZE;
1596 ////////////////////////////////////////////////////////////////////
1598 ////////////////////////////////////////////////////////////////////
1600 info.attr = getBits(pool, cast(size_t)(offset / 16));
1607 * Compute bin for size.
1609 static Bins findBin(size_t size)
1618 else if (size <= 32)
1653 * Allocate a new pool of at least size bytes.
1654 * Sort it into pools.
1655 * Mark all memory in the pool as B_FREE.
1656 * Return the actual number of bytes reserved or 0 on error.
1658 size_t reserve(size_t size)
1660 size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
1661 Pool* pool = newPool(npages);
1665 return pool.npages * PAGESIZE;
1670 * Minimizes physical memory usage by returning free pools to the OS.
1678 for (n = 0; n < pools.length; n++)
1681 for (pn = 0; pn < pool.npages; pn++)
1683 if (cast(Bins)pool.pagetable[pn] != B_FREE)
1686 if (pn < pool.npages)
1692 minAddr = pools[0].baseAddr;
1693 maxAddr = pools[pools.length - 1].topAddr;
1698 * Allocate a chunk of memory that is larger than a page.
1699 * Return null if out of memory.
1701 void *bigAlloc(size_t size)
1711 npages = (size + PAGESIZE - 1) / PAGESIZE;
1715 // This code could use some refinement when repeatedly
1716 // allocating very large arrays.
1718 for (n = 0; n < pools.length; n++)
1721 pn = pool.allocPages(npages);
1736 freedpages = fullcollectshell();
1737 if (freedpages >= pools.length * ((POOLSIZE / PAGESIZE) / 4))
1742 // Release empty pools to prevent bloat
1744 // Allocate new pool
1745 pool = newPool(npages);
1751 pn = pool.allocPages(npages);
1752 assert(pn != OPFAIL);
1755 // Release empty pools to prevent bloat
1757 // Allocate new pool
1758 pool = newPool(npages);
1761 pn = pool.allocPages(npages);
1762 assert(pn != OPFAIL);
1772 pool.pagetable[pn] = B_PAGE;
1774 memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1);
1775 p = pool.baseAddr + pn * PAGESIZE;
1776 memset(cast(char *)p + size, 0, npages * PAGESIZE - size);
1777 debug (MEMSTOMP) memset(p, 0xF1, size);
1781 return null; // let mallocNoSync handle the error
1786 * Allocate a new pool with at least npages in it.
1787 * Sort it into pools.
1788 * Return null if failed.
1790 Pool *newPool(size_t npages)
1792 // Minimum of POOLSIZE
1793 if (npages < POOLSIZE/PAGESIZE)
1794 npages = POOLSIZE/PAGESIZE;
1795 else if (npages > POOLSIZE/PAGESIZE)
1797 // Give us 150% of requested size, so there's room to extend
1798 auto n = npages + (npages >> 1);
1799 if (n < size_t.max/PAGESIZE)
1803 // Allocate successively larger pools up to 8 megs
1806 size_t n = pools.length;
1808 n = 8; // cap pool size at 8 megs
1809 n *= (POOLSIZE / PAGESIZE);
1815 p.initialize(npages);
1822 Pool* pool = pools.insert_sorted(p);
1825 minAddr = pools[0].baseAddr;
1826 maxAddr = pools[pools.length - 1].topAddr;
1833 * Allocate a page of bin's.
1837 int allocPage(Bins bin)
1845 for (n = 0; n < pools.length; n++)
1848 pn = pool.allocPages(1);
1855 pool.pagetable[pn] = cast(ubyte)bin;
1857 // Convert page to free list
1858 size_t size = binsize[bin];
1859 List **b = &bucket[bin];
1861 p = pool.baseAddr + pn * PAGESIZE;
1862 ptop = p + PAGESIZE;
1863 for (; p < ptop; p += size)
1865 (cast(List *)p).next = *b;
1873 * Search a range of memory values and mark any pointers into the GC pool.
1875 void mark(void *pbot, void *ptop)
1877 void **p1 = cast(void **)pbot;
1878 void **p2 = cast(void **)ptop;
1882 //printf("marking range: %p -> %p\n", pbot, ptop);
1883 for (; p1 < p2; p1++)
1886 byte *p = cast(byte *)(*p1);
1888 if (p >= minAddr && p < maxAddr)
1890 if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
1896 size_t offset = cast(size_t)(p - pool.baseAddr);
1898 size_t pn = offset / PAGESIZE;
1899 Bins bin = cast(Bins)pool.pagetable[pn];
1901 // Adjust bit to be at start of allocated memory block
1903 biti = (offset & notbinsize[bin]) >> 4;
1904 else if (bin == B_PAGEPLUS)
1910 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1911 biti = pn * (PAGESIZE / 16);
1915 // Don't mark bits in B_FREE pages
1919 if (bin >= B_PAGE) // Cache B_PAGE and B_PAGEPLUS lookups
1920 pcache = cast(size_t)p & ~(PAGESIZE-1);
1922 if (!pool.mark.test(biti))
1924 pool.mark.set(biti);
1925 if (!pool.noscan.test(biti))
1927 pool.scan.set(biti);
1934 anychanges |= changes;
1939 * Return number of full pages free'd.
1941 size_t fullcollectshell()
1943 // The purpose of the 'shell' is to ensure all the registers
1944 // get put on the stack so they'll be scanned
1949 gcc.builtins.__builtin_unwind_init();
1956 uint eax,ecx,edx,ebx,ebp,esi,edi;
1969 else version (X86_64)
1971 ulong rax,rbx,rcx,rdx,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15;
1974 movq rax[RBP], RAX ;
1975 movq rbx[RBP], RBX ;
1976 movq rcx[RBP], RCX ;
1977 movq rdx[RBP], RDX ;
1978 movq rbp[RBP], RBP ;
1979 movq rsi[RBP], RSI ;
1980 movq rdi[RBP], RDI ;
1983 movq r10[RBP], R10 ;
1984 movq r11[RBP], R11 ;
1985 movq r12[RBP], R12 ;
1986 movq r13[RBP], R13 ;
1987 movq r14[RBP], R14 ;
1988 movq r15[RBP], R15 ;
1994 static assert( false, "Architecture not supported." );
2005 result = fullcollect(sp);
2028 size_t fullcollect(void *stackTop)
2033 debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n");
2035 thread_suspendAll();
2041 for (n = 0; n < pools.length; n++)
2046 pool.freebits.zero();
2049 // Mark each free entry, so it doesn't get scanned
2050 for (n = 0; n < B_PAGE; n++)
2052 for (List *list = bucket[n]; list; list = list.next)
2054 pool = findPool(list);
2056 pool.freebits.set(cast(size_t)(cast(byte*)list - pool.baseAddr) / 16);
2060 for (n = 0; n < pools.length; n++)
2063 pool.mark.copy(&pool.freebits);
2066 rt_scanStaticData( &mark );
2070 // Scan stacks and registers for each paused thread
2071 thread_scanAll( &mark, stackTop );
2075 debug(COLLECT_PRINTF) printf("scan roots[]\n");
2076 mark(roots.ptr, roots.ptr + roots.length);
2079 debug(COLLECT_PRINTF) printf("scan ranges[]\n");
2081 for (n = 0; n < ranges.length; n++)
2083 debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop);
2084 mark(ranges[n].pbot, ranges[n].ptop);
2088 debug(COLLECT_PRINTF) printf("\tscan heap\n");
2092 for (n = 0; n < pools.length; n++)
2100 bbase = pool.scan.base();
2101 btop = bbase + pool.scan.nwords;
2102 for (b = bbase; b < btop;)
2118 o = pool.baseAddr + (b - bbase) * 32 * 16;
2119 if (!(bitm & 0xFFFF))
2124 for (; bitm; o += 16, bitm >>= 1)
2129 pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE;
2130 bin = cast(Bins)pool.pagetable[pn];
2133 mark(o, o + binsize[bin]);
2135 else if (bin == B_PAGE || bin == B_PAGEPLUS)
2137 if (bin == B_PAGEPLUS)
2139 while (pool.pagetable[pn - 1] != B_PAGE)
2143 while (pn + u < pool.npages && pool.pagetable[pn + u] == B_PAGEPLUS)
2145 mark(o, o + u * PAGESIZE);
2154 // Free up everything not marked
2155 debug(COLLECT_PRINTF) printf("\tfree'ing\n");
2156 size_t freedpages = 0;
2158 for (n = 0; n < pools.length; n++)
2161 uint* bbase = pool.mark.base();
2163 for (pn = 0; pn < pool.npages; pn++, bbase += PAGESIZE / (32 * 16))
2165 Bins bin = cast(Bins)pool.pagetable[pn];
2169 auto size = binsize[bin];
2170 byte* p = pool.baseAddr + pn * PAGESIZE;
2171 byte* ptop = p + PAGESIZE;
2172 size_t biti = pn * (PAGESIZE/16);
2173 size_t bitstride = size / 16;
2175 version(none) // BUG: doesn't work because freebits() must also be cleared
2177 // If free'd entire page
2178 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 && bbase[3] == 0 &&
2179 bbase[4] == 0 && bbase[5] == 0 && bbase[6] == 0 && bbase[7] == 0)
2181 for (; p < ptop; p += size, biti += bitstride)
2183 if (pool.finals.nbits && pool.finals.testClear(biti))
2184 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2185 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
2187 List *list = cast(List *)p;
2189 debug (MEMSTOMP) memset(p, 0xF3, size);
2191 pool.pagetable[pn] = B_FREE;
2196 for (; p < ptop; p += size, biti += bitstride)
2198 if (!pool.mark.test(biti))
2200 sentinel_Invariant(sentinel_add(p));
2202 pool.freebits.set(biti);
2203 if (pool.finals.nbits && pool.finals.testClear(biti))
2204 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2205 clrBits(pool, biti, BlkAttr.ALL_BITS);
2207 List *list = cast(List *)p;
2209 debug (MEMSTOMP) memset(p, 0xF3, size);
2215 else if (bin == B_PAGE)
2217 size_t biti = pn * (PAGESIZE / 16);
2218 if (!pool.mark.test(biti))
2220 byte *p = pool.baseAddr + pn * PAGESIZE;
2221 sentinel_Invariant(sentinel_add(p));
2222 if (pool.finals.nbits && pool.finals.testClear(biti))
2223 rt_finalize(sentinel_add(p), false/*noStack > 0*/);
2224 clrBits(pool, biti, BlkAttr.ALL_BITS);
2226 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p);
2227 pool.pagetable[pn] = B_FREE;
2229 debug (MEMSTOMP) memset(p, 0xF3, PAGESIZE);
2230 while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS)
2233 pool.pagetable[pn] = B_FREE;
2239 memset(p, 0xF3, PAGESIZE);
2250 // Free complete pages, rebuild free list
2251 debug(COLLECT_PRINTF) printf("\tfree complete pages\n");
2252 size_t recoveredpages = 0;
2253 for (n = 0; n < pools.length; n++)
2256 for (size_t pn = 0; pn < pool.npages; pn++)
2258 Bins bin = cast(Bins)pool.pagetable[pn];
2264 size_t size = binsize[bin];
2265 size_t bitstride = size / 16;
2266 size_t bitbase = pn * (PAGESIZE / 16);
2267 size_t bittop = bitbase + (PAGESIZE / 16);
2271 for (biti = bitbase; biti < bittop; biti += bitstride)
2273 if (!pool.freebits.test(biti))
2276 pool.pagetable[pn] = B_FREE;
2281 p = pool.baseAddr + pn * PAGESIZE;
2282 for (u = 0; u < PAGESIZE; u += size)
2284 biti = bitbase + u / 16;
2285 if (pool.freebits.test(biti))
2287 List *list = cast(List *)(p + u);
2288 if (list.next != bucket[bin]) // avoid unnecessary writes
2289 list.next = bucket[bin];
2297 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages);
2298 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, pools.length);
2300 return freedpages + recoveredpages;
2307 uint getBits(Pool* pool, size_t biti)
2316 if (pool.finals.nbits &&
2317 pool.finals.test(biti))
2318 bits |= BlkAttr.FINALIZE;
2319 if (pool.noscan.test(biti))
2320 bits |= BlkAttr.NO_SCAN;
2321 // if (pool.nomove.nbits &&
2322 // pool.nomove.test(biti))
2323 // bits |= BlkAttr.NO_MOVE;
2331 void setBits(Pool* pool, size_t biti, uint mask)
2338 if (mask & BlkAttr.FINALIZE)
2340 if (!pool.finals.nbits)
2341 pool.finals.alloc(pool.mark.nbits);
2342 pool.finals.set(biti);
2344 if (mask & BlkAttr.NO_SCAN)
2346 pool.noscan.set(biti);
2348 // if (mask & BlkAttr.NO_MOVE)
2350 // if (!pool.nomove.nbits)
2351 // pool.nomove.alloc(pool.mark.nbits);
2352 // pool.nomove.set(biti);
2360 void clrBits(Pool* pool, size_t biti, uint mask)
2367 if (mask & BlkAttr.FINALIZE && pool.finals.nbits)
2368 pool.finals.clear(biti);
2369 if (mask & BlkAttr.NO_SCAN)
2370 pool.noscan.clear(biti);
2371 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits)
2372 // pool.nomove.clear(biti);
2378 /* ============================ Pool =============================== */
2385 GCBits mark; // entries already scanned, or should not be scanned
2386 GCBits scan; // entries that need to be scanned
2387 GCBits freebits; // entries that are on the free list
2388 GCBits finals; // entries that need finalizer run on them
2389 GCBits noscan; // entries that should not be scanned
2395 void initialize(size_t npages)
2397 size_t poolsize = npages * PAGESIZE;
2398 assert(poolsize >= POOLSIZE);
2399 baseAddr = cast(byte *) alloc.os_mem_map(poolsize);
2401 // Some of the code depends on page alignment of memory pools
2402 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0);
2410 topAddr = baseAddr + poolsize;
2412 mark.alloc(cast(size_t)poolsize / 16);
2413 scan.alloc(cast(size_t)poolsize / 16);
2414 freebits.alloc(cast(size_t)poolsize / 16);
2415 noscan.alloc(cast(size_t)poolsize / 16);
2417 pagetable = cast(ubyte*) cstdlib.malloc(npages);
2419 onOutOfMemoryError();
2420 memset(pagetable, B_FREE, npages);
2422 this.npages = npages;
2434 result = alloc.os_mem_unmap(baseAddr, npages * PAGESIZE);
2442 // See Gcx.Dtor() for the rationale of the null check.
2444 cstdlib.free(pagetable);
2454 void Invariant() { }
2461 //freebits.Invariant();
2462 //finals.Invariant();
2463 //noscan.Invariant();
2467 //if (baseAddr + npages * PAGESIZE != topAddr)
2468 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr);
2469 assert(baseAddr + npages * PAGESIZE == topAddr);
2472 for (size_t i = 0; i < npages; i++)
2474 Bins bin = cast(Bins)pagetable[i];
2475 assert(bin < B_MAX);
2481 * Allocate n pages from Pool.
2482 * Returns OPFAIL on failure.
2484 size_t allocPages(size_t n)
2490 for (i = 0; i < npages; i++)
2492 if (pagetable[i] == B_FREE)
2505 * Free npages pages starting with pagenum.
2507 void freePages(size_t pagenum, size_t npages)
2509 memset(&pagetable[pagenum], B_FREE, npages);
2514 * Used for sorting pools
2516 int opCmp(in Pool other)
2518 if (baseAddr < other.baseAddr)
2521 return cast(int)(baseAddr > other.baseAddr);
2526 /* ============================ SENTINEL =============================== */
2531 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
2532 const ubyte SENTINEL_POST = 0xF5; // 8 bits
2533 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
2536 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; }
2537 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; }
2538 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; }
2541 void sentinel_init(void *p, size_t size)
2543 *sentinel_size(p) = size;
2544 *sentinel_pre(p) = SENTINEL_PRE;
2545 *sentinel_post(p) = SENTINEL_POST;
2549 void sentinel_Invariant(void *p)
2551 assert(*sentinel_pre(p) == SENTINEL_PRE);
2552 assert(*sentinel_post(p) == SENTINEL_POST);
2556 void *sentinel_add(void *p)
2558 return p + 2 * size_t.sizeof;
2562 void *sentinel_sub(void *p)
2564 return p - 2 * size_t.sizeof;
2569 const uint SENTINEL_EXTRA = 0;
2572 void sentinel_init(void *p, size_t size)
2577 void sentinel_Invariant(void *p)
2582 void *sentinel_add(void *p)
2588 void *sentinel_sub(void *p)
2595 // vim: set et sw=4 sts=4 :