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;
58 // BUG: The following import will likely not work, since the gcc
59 // subdirectory is elsewhere. Instead, perhaps the functions
60 // could be declared directly or some other resolution could
62 static import gcc.builtins; // for __builtin_unwind_int
77 FINALIZE = 0b0000_0001,
78 NO_SCAN = 0b0000_0010,
79 NO_MOVE = 0b0000_0100,
80 ALL_BITS = 0b1111_1111
83 extern (C) void* rt_stackBottom();
84 extern (C) void* rt_stackTop();
86 extern (C) void rt_finalize( void* p, bool det = true );
88 alias void delegate(Object) DEvent;
89 extern (C) void rt_attachDisposeEvent(Object h, DEvent e);
90 extern (C) bool rt_detachDisposeEvent(Object h, DEvent e);
93 alias void delegate( void*, void* ) scanFn;
95 extern (C) void rt_scanStaticData( scanFn scan );
97 extern (C) bool thread_needLock();
98 extern (C) void thread_suspendAll();
99 extern (C) void thread_resumeAll();
101 extern (C) void thread_scanAll( scanFn fn, void* curStackTop = null );
103 extern (C) void onOutOfMemoryError();
107 OPFAIL = ~cast(size_t)0
115 /* ============================ GC =============================== */
118 class GCLock { } // just a dummy so we can get a global lock
121 const uint GCVERSION = 1; // increment every time we change interface
126 // For passing to debug code
130 uint gcversion = GCVERSION;
132 Gcx *gcx; // implementation
133 static ClassInfo gcLock; // global lock
138 gcLock = GCLock.classinfo;
139 gcx = cast(Gcx*) cstdlib.calloc(1, Gcx.sizeof);
141 onOutOfMemoryError();
143 setStackBottom(rt_stackBottom());
152 if (!thread_needLock())
154 assert(gcx.disabled > 0);
157 else synchronized (gcLock)
159 assert(gcx.disabled > 0);
170 if (!thread_needLock())
174 else synchronized (gcLock)
184 uint getAttr(void* p)
193 Pool* pool = gcx.findPool(p);
198 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
200 oldb = gcx.getBits(pool, biti);
205 if (!thread_needLock())
209 else synchronized (gcLock)
219 uint setAttr(void* p, uint mask)
228 Pool* pool = gcx.findPool(p);
233 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
235 oldb = gcx.getBits(pool, biti);
236 gcx.setBits(pool, biti, mask);
241 if (!thread_needLock())
245 else synchronized (gcLock)
255 uint clrAttr(void* p, uint mask)
264 Pool* pool = gcx.findPool(p);
269 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
271 oldb = gcx.getBits(pool, biti);
272 gcx.clrBits(pool, biti, mask);
277 if (!thread_needLock())
281 else synchronized (gcLock)
291 void *malloc(size_t size, uint bits = 0)
298 if (!thread_needLock())
300 return mallocNoSync(size, bits);
302 else synchronized (gcLock)
304 return mallocNoSync(size, bits);
312 private void *mallocNoSync(size_t size, uint bits = 0)
321 size += SENTINEL_EXTRA;
324 // Cache previous binsize lookup - Dave Fladebo.
325 static size_t lastsize = -1;
327 if (size == lastsize)
331 bin = gcx.findBin(size);
341 if (!gcx.allocPage(bin) && !gcx.disabled) // try to find a new page
343 if (!thread_needLock())
345 /* Then we haven't locked it yet. Be sure
346 * and lock for a collection, since a finalizer
347 * may start a new thread.
349 synchronized (gcLock)
351 gcx.fullcollectshell();
354 else if (!gcx.fullcollectshell()) // collect to find a new page
359 if (!gcx.bucket[bin] && !gcx.allocPage(bin))
361 gcx.newPool(1); // allocate new pool to find a new page
362 int result = gcx.allocPage(bin);
364 onOutOfMemoryError();
369 // Return next item from free list
370 gcx.bucket[bin] = (cast(List*)p).next;
371 if( !(bits & BlkAttr.NO_SCAN) )
372 cstring.memset(p + size, 0, binsize[bin] - size);
373 debug (MEMSTOMP) cstring.memset(p, 0xF0, size);
377 p = gcx.bigAlloc(size);
379 onOutOfMemoryError();
381 size -= SENTINEL_EXTRA;
383 sentinel_init(p, size);
387 Pool *pool = gcx.findPool(p);
390 gcx.setBits(pool, cast(size_t)(p - pool.baseAddr) / 16, bits);
399 void *calloc(size_t size, uint bits = 0)
406 if (!thread_needLock())
408 return callocNoSync(size, bits);
410 else synchronized (gcLock)
412 return callocNoSync(size, bits);
420 private void *callocNoSync(size_t size, uint bits = 0)
424 void *p = mallocNoSync(size, bits);
425 cstring.memset(p, 0, size);
433 void *realloc(void *p, size_t size, uint bits = 0)
435 if (!thread_needLock())
437 return reallocNoSync(p, size, bits);
439 else synchronized (gcLock)
441 return reallocNoSync(p, size, bits);
449 private void *reallocNoSync(void *p, size_t size, uint bits = 0)
461 p = mallocNoSync(size, bits);
470 sentinel_Invariant(p);
471 psize = *sentinel_size(p);
476 Pool *pool = gcx.findPool(p);
480 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
484 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
485 gcx.setBits(pool, biti, bits);
489 bits = gcx.getBits(pool, biti);
493 p2 = mallocNoSync(size, bits);
496 cstring.memcpy(p2, p, size);
502 psize = gcx.findSize(p); // find allocated size
503 if (psize >= PAGESIZE && size >= PAGESIZE)
505 auto psz = psize / PAGESIZE;
506 auto newsz = (size + PAGESIZE - 1) / PAGESIZE;
510 auto pool = gcx.findPool(p);
511 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
516 synchronized (gcLock)
519 cstring.memset(p + size, 0xF2, psize - size);
520 pool.freePages(pagenum + newsz, psz - newsz);
524 else if (pagenum + newsz <= pool.npages)
526 // Attempt to expand in place
527 synchronized (gcLock)
529 for (size_t i = pagenum + psz; 1;)
531 if (i == pagenum + newsz)
534 cstring.memset(p + psize, 0xF0,
536 cstring.memset(pool.pagetable + pagenum +
537 psz, B_PAGEPLUS, newsz - psz);
540 if (i == pool.npages)
544 if (pool.pagetable[i] != B_FREE)
551 if (psize < size || // if new size is bigger
552 psize > size * 2) // or less than half
556 Pool *pool = gcx.findPool(p);
560 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
564 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
565 gcx.setBits(pool, biti, bits);
569 bits = gcx.getBits(pool, biti);
573 p2 = mallocNoSync(size, bits);
576 cstring.memcpy(p2, p, size);
586 * Attempt to in-place enlarge the memory block pointed to by p by at least
587 * minbytes beyond its current capacity, up to a maximum of maxsize. This
588 * does not attempt to move the memory block (like realloc() does).
591 * 0 if could not extend p,
592 * total size of entire memory block if successful.
594 size_t extend(void* p, size_t minsize, size_t maxsize)
596 if (!thread_needLock())
598 return extendNoSync(p, minsize, maxsize);
600 else synchronized (gcLock)
602 return extendNoSync(p, minsize, maxsize);
610 private size_t extendNoSync(void* p, size_t minsize, size_t maxsize)
613 assert( minsize <= maxsize );
621 auto psize = gcx.findSize(p); // find allocated size
622 if (psize < PAGESIZE)
623 return 0; // cannot extend buckets
625 auto psz = psize / PAGESIZE;
626 auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE;
627 auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE;
629 auto pool = gcx.findPool(p);
630 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
633 for (sz = 0; sz < maxsz; sz++)
635 auto i = pagenum + psz + sz;
636 if (i == pool.npages)
638 if (pool.pagetable[i] != B_FREE)
648 cstring.memset(p + psize, 0xF0, (psz + sz) * PAGESIZE - psize);
649 cstring.memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz);
652 return (psz + sz) * PAGESIZE;
659 size_t reserve(size_t size)
666 if (!thread_needLock())
668 return reserveNoSync(size);
670 else synchronized (gcLock)
672 return reserveNoSync(size);
680 private size_t reserveNoSync(size_t size)
685 return gcx.reserve(size);
699 if (!thread_needLock())
701 return freeNoSync(p);
703 else synchronized (gcLock)
705 return freeNoSync(p);
713 private void freeNoSync(void *p)
722 // Find which page it is in
723 pool = gcx.findPool(p);
724 if (!pool) // if not one of ours
726 sentinel_Invariant(p);
728 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
729 biti = cast(size_t)(p - pool.baseAddr) / 16;
730 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
732 bin = cast(Bins)pool.pagetable[pagenum];
733 if (bin == B_PAGE) // if large alloc
738 while (++n < pool.npages && pool.pagetable[n] == B_PAGEPLUS)
740 debug (MEMSTOMP) cstring.memset(p, 0xF2, npages * PAGESIZE);
741 pool.freePages(pagenum, npages);
746 List *list = cast(List*)p;
748 debug (MEMSTOMP) cstring.memset(p, 0xF2, binsize[bin]);
750 list.next = gcx.bucket[bin];
751 gcx.bucket[bin] = list;
757 * Determine the base address of the block containing p. If p is not a gc
758 * allocated pointer, return null.
760 void* addrOf(void *p)
767 if (!thread_needLock())
769 return addrOfNoSync(p);
771 else synchronized (gcLock)
773 return addrOfNoSync(p);
781 void* addrOfNoSync(void *p)
788 return gcx.findBase(p);
793 * Determine the allocated size of pointer p. If p is an interior pointer
794 * or not a gc allocated pointer, return 0.
796 size_t sizeOf(void *p)
803 if (!thread_needLock())
805 return sizeOfNoSync(p);
807 else synchronized (gcLock)
809 return sizeOfNoSync(p);
817 private size_t sizeOfNoSync(void *p)
824 size_t size = gcx.findSize(p);
826 // Check for interior pointer
828 // 1) size is a power of 2 for less than PAGESIZE values
829 // 2) base of memory pool is aligned on PAGESIZE boundary
830 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
832 return size ? size - SENTINEL_EXTRA : 0;
836 if (p == gcx.p_cache)
837 return gcx.size_cache;
839 size_t size = gcx.findSize(p);
841 // Check for interior pointer
843 // 1) size is a power of 2 for less than PAGESIZE values
844 // 2) base of memory pool is aligned on PAGESIZE boundary
845 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
850 gcx.size_cache = size;
859 * Determine the base address of the block containing p. If p is not a gc
860 * allocated pointer, return null.
862 BlkInfo query(void *p)
870 if (!thread_needLock())
872 return queryNoSync(p);
874 else synchronized (gcLock)
876 return queryNoSync(p);
884 BlkInfo queryNoSync(void *p)
888 return gcx.getInfo(p);
893 * Verify that pointer p:
894 * 1) belongs to this memory pool
895 * 2) points to the start of an allocated piece of memory
896 * 3) is not on a free list
905 if (!thread_needLock())
909 else synchronized (gcLock)
919 private void checkNoSync(void *p)
923 sentinel_Invariant(p);
932 pool = gcx.findPool(p);
934 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
935 bin = cast(Bins)pool.pagetable[pagenum];
936 assert(bin <= B_PAGE);
938 assert((cast(size_t)p & (size - 1)) == 0);
944 // Check that p is not on a free list
947 for (list = gcx.bucket[bin]; list; list = list.next)
949 assert(cast(void*)list != p);
960 private void setStackBottom(void *p)
962 version (STACKGROWSDOWN)
964 //p = (void *)((uint *)p + 4);
965 if (p > gcx.stackBottom)
972 //p = (void *)((uint *)p - 4);
973 if (p < gcx.stackBottom)
975 gcx.stackBottom = cast(char*)p;
982 * add p to list of roots
984 void addRoot(void *p)
991 if (!thread_needLock())
993 if (roots.append(p) is null)
994 onOutOfMemoryError();
996 else synchronized (gcLock)
998 if (roots.append(p) is null)
999 onOutOfMemoryError();
1005 * remove p from list of roots
1007 void removeRoot(void *p)
1015 if (!thread_needLock())
1017 r = roots.remove(p);
1019 else synchronized (gcLock)
1021 r = roots.remove(p);
1028 * add range to scan for roots
1030 void addRange(void *p, size_t sz)
1037 if (!thread_needLock())
1039 if (ranges.append(Range(p, p+sz)) is null)
1040 onOutOfMemoryError();
1042 else synchronized (gcLock)
1044 if (ranges.append(Range(p, p+sz)) is null)
1045 onOutOfMemoryError();
1053 void removeRange(void *p)
1061 if (!thread_needLock())
1063 r = ranges.remove(Range(p, null));
1065 else synchronized (gcLock)
1067 r = ranges.remove(Range(p, null));
1074 * do full garbage collection
1079 if (!thread_needLock())
1081 gcx.fullcollectshell();
1083 else synchronized (gcLock)
1085 gcx.fullcollectshell();
1098 * do full garbage collection ignoring roots
1100 void fullCollectNoStack()
1102 if (!thread_needLock())
1105 gcx.fullcollectshell();
1108 else synchronized (gcLock)
1111 gcx.fullcollectshell();
1118 * minimize free space usage
1122 if (!thread_needLock())
1126 else synchronized (gcLock)
1134 * Retrieve statistics about garbage collection.
1135 * Useful for debugging and tuning.
1137 void getStats(out GCStats stats)
1139 if (!thread_needLock())
1141 getStatsNoSync(stats);
1143 else synchronized (gcLock)
1145 getStatsNoSync(stats);
1153 private void getStatsNoSync(out GCStats stats)
1162 cstring.memset(&stats, 0, GCStats.sizeof);
1164 for (n = 0; n < gcx.npools; n++)
1166 Pool *pool = gcx.pooltable[n];
1167 psize += pool.npages * PAGESIZE;
1168 for (size_t j = 0; j < pool.npages; j++)
1170 Bins bin = cast(Bins)pool.pagetable[j];
1173 else if (bin == B_PAGE)
1175 else if (bin < B_PAGE)
1180 for (n = 0; n < B_PAGE; n++)
1182 for (List *list = gcx.bucket[n]; list; list = list.next)
1183 flsize += binsize[n];
1186 usize = bsize - flsize;
1188 stats.poolsize = psize;
1189 stats.usedsize = bsize - flsize;
1190 stats.freelistsize = flsize;
1193 /******************* weak-reference support *********************/
1195 // call locked if necessary
1196 private T locked(T)(in T delegate() code)
1198 if (thread_needLock)
1199 synchronized(gcLock) return code();
1204 private struct WeakPointer
1208 void ondestroy(Object r)
1210 assert(r is reference);
1211 // lock for memory consistency (parallel readers)
1212 // also ensures that weakpointerDestroy can be called while another
1213 // thread is freeing the reference with "delete"
1214 locked!(void)({ reference = null; });
1219 * Create a weak pointer to the given object.
1220 * Returns a pointer to an opaque struct allocated in C memory.
1222 void* weakpointerCreate( Object r )
1226 // must be allocated in C memory
1227 // 1. to hide the reference from the GC
1228 // 2. the GC doesn't scan delegates added by rt_attachDisposeEvent
1230 auto wp = cast(WeakPointer*)(cstdlib.malloc(WeakPointer.sizeof));
1232 onOutOfMemoryError();
1234 rt_attachDisposeEvent(r, &wp.ondestroy);
1241 * Destroy a weak pointer returned by weakpointerCreate().
1242 * If null is passed, nothing happens.
1244 void weakpointerDestroy( void* p )
1248 auto wp = cast(WeakPointer*)p;
1249 // must be extra careful about the GC or parallel threads
1250 // finalizing the reference at the same time
1253 rt_detachDisposeEvent(wp.reference, &wp.ondestroy);
1260 * Query a weak pointer and return either the object passed to
1261 * weakpointerCreate, or null if it was free'd in the meantime.
1262 * If null is passed, null is returned.
1264 Object weakpointerGet( void* p )
1268 // NOTE: could avoid the lock by using Fawzi style GC counters but
1269 // that'd require core.sync.Atomic and lots of care about memory
1270 // consistency it's an optional optimization see
1271 // http://dsource.org/projects/tango/browser/trunk/user/tango/core/Lifetime.d?rev=5100#L158
1272 return locked!(Object)({
1273 return (cast(WeakPointer*)p).reference;
1280 /* ============================ Gcx =============================== */
1285 POOLSIZE = (4096*256),
1299 B_PAGE, // start of large alloc
1300 B_PAGEPLUS, // continuation of large alloc
1301 B_FREE, // free page
1319 int opCmp(in Range other)
1321 if (pbot < other.pbot)
1324 return cast(int)(pbot > other.pbot);
1329 const uint binsize[B_MAX] = [ 16,32,64,128,256,512,1024,2048,4096 ];
1330 const uint notbinsize[B_MAX] = [ ~(16u-1),~(32u-1),~(64u-1),~(128u-1),~(256u-1),
1331 ~(512u-1),~(1024u-1),~(2048u-1),~(4096u-1) ];
1333 DynArray!(void*) roots;
1334 DynArray!(Range) ranges;
1336 /* ============================ Gcx =============================== */
1345 uint noStack; // !=0 means don't scan stack
1346 uint log; // turn on logging
1350 int disabled; // turn off collections if >0
1352 byte *minAddr; // min(baseAddr)
1353 byte *maxAddr; // max(topAddr)
1358 List *bucket[B_MAX]; // free list for each size
1364 (cast(byte*)this)[0 .. Gcx.sizeof] = 0;
1365 stackBottom = cast(char*)&dummy;
1366 //printf("gcx = %p, self = %x\n", this, self);
1371 void Invariant() { }
1378 //printf("Gcx.invariant(): this = %p\n", this);
1381 for (i = 0; i < npools; i++)
1383 Pool *pool = pooltable[i];
1387 assert(minAddr == pool.baseAddr);
1391 assert(pool.opCmp(pooltable[i + 1]) < 0);
1393 else if (i + 1 == npools)
1395 assert(maxAddr == pool.topAddr);
1402 for (i = 0; i < ranges.length; i++)
1404 assert(ranges[i].pbot);
1405 assert(ranges[i].ptop);
1406 assert(ranges[i].pbot <= ranges[i].ptop);
1409 for (i = 0; i < B_PAGE; i++)
1411 for (List *list = bucket[i]; list; list = list.next)
1420 * Find Pool that pointer is in.
1421 * Return null if not in a Pool.
1422 * Assume pooltable[] is sorted.
1424 Pool *findPool(void *p)
1426 if (p >= minAddr && p < maxAddr)
1430 return pooltable[0];
1433 for (size_t i = 0; i < npools; i++)
1437 pool = pooltable[i];
1438 if (p < pool.topAddr)
1440 if (pool.baseAddr <= p)
1451 * Find base address of block containing pointer p.
1452 * Returns null if not a gc'd pointer
1454 void* findBase(void *p)
1461 size_t offset = cast(size_t)(p - pool.baseAddr);
1462 size_t pn = offset / PAGESIZE;
1463 Bins bin = cast(Bins)pool.pagetable[pn];
1465 // Adjust bit to be at start of allocated memory block
1468 return pool.baseAddr + (offset & notbinsize[bin]);
1470 else if (bin == B_PAGEPLUS)
1474 --pn, offset -= PAGESIZE;
1475 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1477 return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1481 // we are in a B_FREE page
1490 * Find size of pointer p.
1491 * Returns 0 if not a gc'd pointer
1493 size_t findSize(void *p)
1504 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
1505 bin = cast(Bins)pool.pagetable[pagenum];
1506 size = binsize[bin];
1512 pt = &pool.pagetable[0];
1513 for (i = pagenum + 1; i < pool.npages; i++)
1515 if (pt[i] != B_PAGEPLUS)
1518 size = (i - pagenum) * PAGESIZE;
1528 BlkInfo getInfo(void* p)
1536 size_t offset = cast(size_t)(p - pool.baseAddr);
1537 size_t pn = offset / PAGESIZE;
1538 Bins bin = cast(Bins)pool.pagetable[pn];
1540 ////////////////////////////////////////////////////////////////////
1542 ////////////////////////////////////////////////////////////////////
1546 info.base = pool.baseAddr + (offset & notbinsize[bin]);
1548 else if (bin == B_PAGEPLUS)
1552 --pn, offset -= PAGESIZE;
1554 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1556 info.base = pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1558 // fix bin for use by size calc below
1559 bin = cast(Bins)pool.pagetable[pn];
1562 ////////////////////////////////////////////////////////////////////
1564 ////////////////////////////////////////////////////////////////////
1566 info.size = binsize[bin];
1572 pt = &pool.pagetable[0];
1573 for (i = pn + 1; i < pool.npages; i++)
1575 if (pt[i] != B_PAGEPLUS)
1578 info.size = (i - pn) * PAGESIZE;
1581 ////////////////////////////////////////////////////////////////////
1583 ////////////////////////////////////////////////////////////////////
1585 info.attr = getBits(pool, cast(size_t)(offset / 16));
1592 * Compute bin for size.
1594 static Bins findBin(size_t size)
1603 else if (size <= 32)
1638 * Allocate a new pool of at least size bytes.
1639 * Sort it into pooltable[].
1640 * Mark all memory in the pool as B_FREE.
1641 * Return the actual number of bytes reserved or 0 on error.
1643 size_t reserve(size_t size)
1645 size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
1646 Pool* pool = newPool(npages);
1650 return pool.npages * PAGESIZE;
1655 * Minimizes physical memory usage by returning free pools to the OS.
1663 for (n = 0; n < npools; n++)
1665 pool = pooltable[n];
1666 for (pn = 0; pn < pool.npages; pn++)
1668 if (cast(Bins)pool.pagetable[pn] != B_FREE)
1671 if (pn < pool.npages)
1678 cstring.memmove(pooltable + n,
1680 (--npools - n) * (Pool*).sizeof);
1681 minAddr = pooltable[0].baseAddr;
1682 maxAddr = pooltable[npools - 1].topAddr;
1688 * Allocate a chunk of memory that is larger than a page.
1689 * Return null if out of memory.
1691 void *bigAlloc(size_t size)
1701 npages = (size + PAGESIZE - 1) / PAGESIZE;
1705 // This code could use some refinement when repeatedly
1706 // allocating very large arrays.
1708 for (n = 0; n < npools; n++)
1710 pool = pooltable[n];
1711 pn = pool.allocPages(npages);
1726 freedpages = fullcollectshell();
1727 if (freedpages >= npools * ((POOLSIZE / PAGESIZE) / 4))
1732 // Release empty pools to prevent bloat
1734 // Allocate new pool
1735 pool = newPool(npages);
1741 pn = pool.allocPages(npages);
1742 assert(pn != OPFAIL);
1745 // Release empty pools to prevent bloat
1747 // Allocate new pool
1748 pool = newPool(npages);
1751 pn = pool.allocPages(npages);
1752 assert(pn != OPFAIL);
1762 pool.pagetable[pn] = B_PAGE;
1764 cstring.memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1);
1765 p = pool.baseAddr + pn * PAGESIZE;
1766 cstring.memset(cast(char *)p + size, 0, npages * PAGESIZE - size);
1767 debug (MEMSTOMP) cstring.memset(p, 0xF1, size);
1771 return null; // let mallocNoSync handle the error
1776 * Allocate a new pool with at least npages in it.
1777 * Sort it into pooltable[].
1778 * Return null if failed.
1780 Pool *newPool(size_t npages)
1783 Pool** newpooltable;
1787 // Minimum of POOLSIZE
1788 if (npages < POOLSIZE/PAGESIZE)
1789 npages = POOLSIZE/PAGESIZE;
1790 else if (npages > POOLSIZE/PAGESIZE)
1792 // Give us 150% of requested size, so there's room to extend
1793 auto n = npages + (npages >> 1);
1794 if (n < size_t.max/PAGESIZE)
1798 // Allocate successively larger pools up to 8 megs
1803 n = 8; // cap pool size at 8 megs
1804 n *= (POOLSIZE / PAGESIZE);
1809 pool = cast(Pool *) cstdlib.calloc(1, Pool.sizeof);
1812 pool.initialize(npages);
1816 newnpools = npools + 1;
1817 newpooltable = cast(Pool **) cstdlib.realloc(pooltable,
1818 newnpools * (Pool *).sizeof);
1822 // Sort pool into newpooltable[]
1823 for (i = 0; i < npools; i++)
1825 if (pool.opCmp(newpooltable[i]) < 0)
1828 cstring.memmove(newpooltable + i + 1, newpooltable + i,
1829 (npools - i) * (Pool *).sizeof);
1830 newpooltable[i] = pool;
1832 pooltable = newpooltable;
1835 minAddr = pooltable[0].baseAddr;
1836 maxAddr = pooltable[npools - 1].topAddr;
1848 * Allocate a page of bin's.
1852 int allocPage(Bins bin)
1860 for (n = 0; n < npools; n++)
1862 pool = pooltable[n];
1863 pn = pool.allocPages(1);
1870 pool.pagetable[pn] = cast(ubyte)bin;
1872 // Convert page to free list
1873 size_t size = binsize[bin];
1874 List **b = &bucket[bin];
1876 p = pool.baseAddr + pn * PAGESIZE;
1877 ptop = p + PAGESIZE;
1878 for (; p < ptop; p += size)
1880 (cast(List *)p).next = *b;
1888 * Search a range of memory values and mark any pointers into the GC pool.
1890 void mark(void *pbot, void *ptop)
1892 void **p1 = cast(void **)pbot;
1893 void **p2 = cast(void **)ptop;
1897 //printf("marking range: %p -> %p\n", pbot, ptop);
1898 for (; p1 < p2; p1++)
1901 byte *p = cast(byte *)(*p1);
1903 if (p >= minAddr && p < maxAddr)
1905 if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
1911 size_t offset = cast(size_t)(p - pool.baseAddr);
1913 size_t pn = offset / PAGESIZE;
1914 Bins bin = cast(Bins)pool.pagetable[pn];
1916 // Adjust bit to be at start of allocated memory block
1918 biti = (offset & notbinsize[bin]) >> 4;
1919 else if (bin == B_PAGEPLUS)
1925 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1926 biti = pn * (PAGESIZE / 16);
1930 // Don't mark bits in B_FREE pages
1934 if (bin >= B_PAGE) // Cache B_PAGE and B_PAGEPLUS lookups
1935 pcache = cast(size_t)p & ~(PAGESIZE-1);
1937 if (!pool.mark.test(biti))
1939 pool.mark.set(biti);
1940 if (!pool.noscan.test(biti))
1942 pool.scan.set(biti);
1949 anychanges |= changes;
1954 * Return number of full pages free'd.
1956 size_t fullcollectshell()
1958 // The purpose of the 'shell' is to ensure all the registers
1959 // get put on the stack so they'll be scanned
1964 gcc.builtins.__builtin_unwind_init();
1971 uint eax,ecx,edx,ebx,ebp,esi,edi;
1984 else version (X86_64)
1986 ulong rax,rbx,rcx,rdx,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15;
1989 movq rax[RBP], RAX ;
1990 movq rbx[RBP], RBX ;
1991 movq rcx[RBP], RCX ;
1992 movq rdx[RBP], RDX ;
1993 movq rbp[RBP], RBP ;
1994 movq rsi[RBP], RSI ;
1995 movq rdi[RBP], RDI ;
1998 movq r10[RBP], R10 ;
1999 movq r11[RBP], R11 ;
2000 movq r12[RBP], R12 ;
2001 movq r13[RBP], R13 ;
2002 movq r14[RBP], R14 ;
2003 movq r15[RBP], R15 ;
2009 static assert( false, "Architecture not supported." );
2020 result = fullcollect(sp);
2043 size_t fullcollect(void *stackTop)
2048 debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n");
2050 thread_suspendAll();
2056 for (n = 0; n < npools; n++)
2058 pool = pooltable[n];
2061 pool.freebits.zero();
2064 // Mark each free entry, so it doesn't get scanned
2065 for (n = 0; n < B_PAGE; n++)
2067 for (List *list = bucket[n]; list; list = list.next)
2069 pool = findPool(list);
2071 pool.freebits.set(cast(size_t)(cast(byte*)list - pool.baseAddr) / 16);
2075 for (n = 0; n < npools; n++)
2077 pool = pooltable[n];
2078 pool.mark.copy(&pool.freebits);
2081 rt_scanStaticData( &mark );
2085 // Scan stacks and registers for each paused thread
2086 thread_scanAll( &mark, stackTop );
2090 debug(COLLECT_PRINTF) printf("scan roots[]\n");
2091 mark(roots.ptr, roots.ptr + roots.length);
2094 debug(COLLECT_PRINTF) printf("scan ranges[]\n");
2096 for (n = 0; n < ranges.length; n++)
2098 debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop);
2099 mark(ranges[n].pbot, ranges[n].ptop);
2103 debug(COLLECT_PRINTF) printf("\tscan heap\n");
2107 for (n = 0; n < npools; n++)
2113 pool = pooltable[n];
2115 bbase = pool.scan.base();
2116 btop = bbase + pool.scan.nwords;
2117 for (b = bbase; b < btop;)
2133 o = pool.baseAddr + (b - bbase) * 32 * 16;
2134 if (!(bitm & 0xFFFF))
2139 for (; bitm; o += 16, bitm >>= 1)
2144 pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE;
2145 bin = cast(Bins)pool.pagetable[pn];
2148 mark(o, o + binsize[bin]);
2150 else if (bin == B_PAGE || bin == B_PAGEPLUS)
2152 if (bin == B_PAGEPLUS)
2154 while (pool.pagetable[pn - 1] != B_PAGE)
2158 while (pn + u < pool.npages && pool.pagetable[pn + u] == B_PAGEPLUS)
2160 mark(o, o + u * PAGESIZE);
2169 // Free up everything not marked
2170 debug(COLLECT_PRINTF) printf("\tfree'ing\n");
2171 size_t freedpages = 0;
2173 for (n = 0; n < npools; n++)
2175 pool = pooltable[n];
2176 uint* bbase = pool.mark.base();
2178 for (pn = 0; pn < pool.npages; pn++, bbase += PAGESIZE / (32 * 16))
2180 Bins bin = cast(Bins)pool.pagetable[pn];
2184 auto size = binsize[bin];
2185 byte* p = pool.baseAddr + pn * PAGESIZE;
2186 byte* ptop = p + PAGESIZE;
2187 size_t biti = pn * (PAGESIZE/16);
2188 size_t bitstride = size / 16;
2190 version(none) // BUG: doesn't work because freebits() must also be cleared
2192 // If free'd entire page
2193 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 && bbase[3] == 0 &&
2194 bbase[4] == 0 && bbase[5] == 0 && bbase[6] == 0 && bbase[7] == 0)
2196 for (; p < ptop; p += size, biti += bitstride)
2198 if (pool.finals.nbits && pool.finals.testClear(biti))
2199 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2200 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
2202 List *list = cast(List *)p;
2204 debug (MEMSTOMP) cstring.memset(p, 0xF3, size);
2206 pool.pagetable[pn] = B_FREE;
2211 for (; p < ptop; p += size, biti += bitstride)
2213 if (!pool.mark.test(biti))
2215 sentinel_Invariant(sentinel_add(p));
2217 pool.freebits.set(biti);
2218 if (pool.finals.nbits && pool.finals.testClear(biti))
2219 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2220 clrBits(pool, biti, BlkAttr.ALL_BITS);
2222 List *list = cast(List *)p;
2224 debug (MEMSTOMP) cstring.memset(p, 0xF3, size);
2230 else if (bin == B_PAGE)
2232 size_t biti = pn * (PAGESIZE / 16);
2233 if (!pool.mark.test(biti))
2235 byte *p = pool.baseAddr + pn * PAGESIZE;
2236 sentinel_Invariant(sentinel_add(p));
2237 if (pool.finals.nbits && pool.finals.testClear(biti))
2238 rt_finalize(sentinel_add(p), false/*noStack > 0*/);
2239 clrBits(pool, biti, BlkAttr.ALL_BITS);
2241 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p);
2242 pool.pagetable[pn] = B_FREE;
2244 debug (MEMSTOMP) cstring.memset(p, 0xF3, PAGESIZE);
2245 while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS)
2248 pool.pagetable[pn] = B_FREE;
2254 cstring.memset(p, 0xF3, PAGESIZE);
2265 // Free complete pages, rebuild free list
2266 debug(COLLECT_PRINTF) printf("\tfree complete pages\n");
2267 size_t recoveredpages = 0;
2268 for (n = 0; n < npools; n++)
2270 pool = pooltable[n];
2271 for (size_t pn = 0; pn < pool.npages; pn++)
2273 Bins bin = cast(Bins)pool.pagetable[pn];
2279 size_t size = binsize[bin];
2280 size_t bitstride = size / 16;
2281 size_t bitbase = pn * (PAGESIZE / 16);
2282 size_t bittop = bitbase + (PAGESIZE / 16);
2286 for (biti = bitbase; biti < bittop; biti += bitstride)
2288 if (!pool.freebits.test(biti))
2291 pool.pagetable[pn] = B_FREE;
2296 p = pool.baseAddr + pn * PAGESIZE;
2297 for (u = 0; u < PAGESIZE; u += size)
2299 biti = bitbase + u / 16;
2300 if (pool.freebits.test(biti))
2302 List *list = cast(List *)(p + u);
2303 if (list.next != bucket[bin]) // avoid unnecessary writes
2304 list.next = bucket[bin];
2312 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages);
2313 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, npools);
2315 return freedpages + recoveredpages;
2322 uint getBits(Pool* pool, size_t biti)
2331 if (pool.finals.nbits &&
2332 pool.finals.test(biti))
2333 bits |= BlkAttr.FINALIZE;
2334 if (pool.noscan.test(biti))
2335 bits |= BlkAttr.NO_SCAN;
2336 // if (pool.nomove.nbits &&
2337 // pool.nomove.test(biti))
2338 // bits |= BlkAttr.NO_MOVE;
2346 void setBits(Pool* pool, size_t biti, uint mask)
2353 if (mask & BlkAttr.FINALIZE)
2355 if (!pool.finals.nbits)
2356 pool.finals.alloc(pool.mark.nbits);
2357 pool.finals.set(biti);
2359 if (mask & BlkAttr.NO_SCAN)
2361 pool.noscan.set(biti);
2363 // if (mask & BlkAttr.NO_MOVE)
2365 // if (!pool.nomove.nbits)
2366 // pool.nomove.alloc(pool.mark.nbits);
2367 // pool.nomove.set(biti);
2375 void clrBits(Pool* pool, size_t biti, uint mask)
2382 if (mask & BlkAttr.FINALIZE && pool.finals.nbits)
2383 pool.finals.clear(biti);
2384 if (mask & BlkAttr.NO_SCAN)
2385 pool.noscan.clear(biti);
2386 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits)
2387 // pool.nomove.clear(biti);
2393 /* ============================ Pool =============================== */
2400 GCBits mark; // entries already scanned, or should not be scanned
2401 GCBits scan; // entries that need to be scanned
2402 GCBits freebits; // entries that are on the free list
2403 GCBits finals; // entries that need finalizer run on them
2404 GCBits noscan; // entries that should not be scanned
2410 void initialize(size_t npages)
2412 size_t poolsize = npages * PAGESIZE;
2413 assert(poolsize >= POOLSIZE);
2414 baseAddr = cast(byte *) alloc.os_mem_map(poolsize);
2416 // Some of the code depends on page alignment of memory pools
2417 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0);
2425 topAddr = baseAddr + poolsize;
2427 mark.alloc(cast(size_t)poolsize / 16);
2428 scan.alloc(cast(size_t)poolsize / 16);
2429 freebits.alloc(cast(size_t)poolsize / 16);
2430 noscan.alloc(cast(size_t)poolsize / 16);
2432 pagetable = cast(ubyte*) cstdlib.malloc(npages);
2434 onOutOfMemoryError();
2435 cstring.memset(pagetable, B_FREE, npages);
2437 this.npages = npages;
2449 result = alloc.os_mem_unmap(baseAddr, npages * PAGESIZE);
2457 // See Gcx.Dtor() for the rationale of the null check.
2459 cstdlib.free(pagetable);
2469 void Invariant() { }
2476 //freebits.Invariant();
2477 //finals.Invariant();
2478 //noscan.Invariant();
2482 //if (baseAddr + npages * PAGESIZE != topAddr)
2483 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr);
2484 assert(baseAddr + npages * PAGESIZE == topAddr);
2487 for (size_t i = 0; i < npages; i++)
2489 Bins bin = cast(Bins)pagetable[i];
2490 assert(bin < B_MAX);
2496 * Allocate n pages from Pool.
2497 * Returns OPFAIL on failure.
2499 size_t allocPages(size_t n)
2505 for (i = 0; i < npages; i++)
2507 if (pagetable[i] == B_FREE)
2520 * Free npages pages starting with pagenum.
2522 void freePages(size_t pagenum, size_t npages)
2524 cstring.memset(&pagetable[pagenum], B_FREE, npages);
2529 * Used for sorting pooltable[]
2533 if (baseAddr < p2.baseAddr)
2536 return cast(int)(baseAddr > p2.baseAddr);
2541 /* ============================ SENTINEL =============================== */
2546 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
2547 const ubyte SENTINEL_POST = 0xF5; // 8 bits
2548 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
2551 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; }
2552 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; }
2553 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; }
2556 void sentinel_init(void *p, size_t size)
2558 *sentinel_size(p) = size;
2559 *sentinel_pre(p) = SENTINEL_PRE;
2560 *sentinel_post(p) = SENTINEL_POST;
2564 void sentinel_Invariant(void *p)
2566 assert(*sentinel_pre(p) == SENTINEL_PRE);
2567 assert(*sentinel_post(p) == SENTINEL_POST);
2571 void *sentinel_add(void *p)
2573 return p + 2 * size_t.sizeof;
2577 void *sentinel_sub(void *p)
2579 return p - 2 * size_t.sizeof;
2584 const uint SENTINEL_EXTRA = 0;
2587 void sentinel_init(void *p, size_t size)
2592 void sentinel_Invariant(void *p)
2597 void *sentinel_add(void *p)
2603 void *sentinel_sub(void *p)
2610 // vim: set et sw=4 sts=4 :