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 = PTRCHECK; // more pointer checking
35 //debug = PTRCHECK2; // thorough but slow pointer checking
37 /*************** Configuration *********************/
39 version = STACKGROWSDOWN; // growing the stack means subtracting from the stack pointer
40 // (use for Intel X86 CPUs)
41 // else growing the stack means adding to the stack pointer
43 /***************************************************/
45 import rt.gc.cdgc.bits: GCBits;
46 import rt.gc.cdgc.stats: GCStats, Stats;
47 import rt.gc.cdgc.dynarray: DynArray;
48 import alloc = rt.gc.cdgc.alloc;
49 import opts = rt.gc.cdgc.opts;
51 import cstdlib = tango.stdc.stdlib;
52 import cstring = tango.stdc.string;
55 * This is a small optimization that proved it's usefulness. For small chunks
56 * or memory memset() seems to be slower (probably because of the call) that
57 * simply doing a simple loop to set the memory.
59 void memset(void* dst, int c, size_t n)
61 // This number (32) has been determined empirically
63 cstring.memset(dst, c, n);
66 auto p = cast(ubyte*)(dst);
73 // BUG: The following import will likely not work, since the gcc
74 // subdirectory is elsewhere. Instead, perhaps the functions
75 // could be declared directly or some other resolution could
77 static import gcc.builtins; // for __builtin_unwind_int
87 package enum BlkAttr : uint
89 FINALIZE = 0b0000_0001,
90 NO_SCAN = 0b0000_0010,
91 NO_MOVE = 0b0000_0100,
92 ALL_BITS = 0b1111_1111
98 extern (C) void* rt_stackBottom();
99 extern (C) void* rt_stackTop();
101 extern (C) void rt_finalize( void* p, bool det = true );
103 alias void delegate(Object) DEvent;
104 extern (C) void rt_attachDisposeEvent(Object h, DEvent e);
105 extern (C) bool rt_detachDisposeEvent(Object h, DEvent e);
108 alias void delegate( void*, void* ) scanFn;
110 extern (C) void rt_scanStaticData( scanFn scan );
112 extern (C) bool thread_needLock();
113 extern (C) void thread_suspendAll();
114 extern (C) void thread_resumeAll();
116 extern (C) void thread_scanAll( scanFn fn, void* curStackTop = null );
118 extern (C) void onOutOfMemoryError();
122 OPFAIL = ~cast(size_t)0
130 /* ============================ GC =============================== */
133 class GCLock { } // just a dummy so we can get a global lock
136 const uint GCVERSION = 1; // increment every time we change interface
143 // For passing to debug code
147 uint gcversion = GCVERSION;
149 Gcx *gcx; // implementation
150 static ClassInfo gcLock; // global lock
155 opts.parse(cstdlib.getenv("D_GC_OPTS"));
156 gcLock = GCLock.classinfo;
157 gcx = cast(Gcx*) cstdlib.calloc(1, Gcx.sizeof);
159 onOutOfMemoryError();
161 setStackBottom(rt_stackBottom());
171 if (!thread_needLock())
173 assert(gcx.disabled > 0);
176 else synchronized (gcLock)
178 assert(gcx.disabled > 0);
189 if (!thread_needLock())
193 else synchronized (gcLock)
203 uint getAttr(void* p)
212 Pool* pool = gcx.findPool(p);
217 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
219 oldb = gcx.getBits(pool, biti);
224 if (!thread_needLock())
228 else synchronized (gcLock)
238 uint setAttr(void* p, uint mask)
247 Pool* pool = gcx.findPool(p);
252 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
254 oldb = gcx.getBits(pool, biti);
255 gcx.setBits(pool, biti, mask);
260 if (!thread_needLock())
264 else synchronized (gcLock)
274 uint clrAttr(void* p, uint mask)
283 Pool* pool = gcx.findPool(p);
288 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
290 oldb = gcx.getBits(pool, biti);
291 gcx.clrBits(pool, biti, mask);
296 if (!thread_needLock())
300 else synchronized (gcLock)
310 void *malloc(size_t size, uint bits = 0)
317 if (!thread_needLock())
319 return mallocNoSync(size, bits);
321 else synchronized (gcLock)
323 return mallocNoSync(size, bits);
331 private void *mallocNoSync(size_t size, uint bits = 0)
335 stats.malloc_started(size, bits);
337 stats.malloc_finished();
344 if (opts.options.sentinel)
345 size += SENTINEL_EXTRA;
348 // Cache previous binsize lookup - Dave Fladebo.
349 static size_t lastsize = -1;
351 if (size == lastsize)
355 bin = gcx.findBin(size);
365 if (!gcx.allocPage(bin) && !gcx.disabled) // try to find a new page
367 if (!thread_needLock())
369 /* Then we haven't locked it yet. Be sure
370 * and lock for a collection, since a finalizer
371 * may start a new thread.
373 synchronized (gcLock)
375 gcx.fullcollectshell();
378 else if (!gcx.fullcollectshell()) // collect to find a new page
383 if (!gcx.bucket[bin] && !gcx.allocPage(bin))
385 gcx.newPool(1); // allocate new pool to find a new page
386 int result = gcx.allocPage(bin);
388 onOutOfMemoryError();
393 // Return next item from free list
394 gcx.bucket[bin] = (cast(List*)p).next;
395 if( !(bits & BlkAttr.NO_SCAN) )
396 memset(p + size, 0, binsize[bin] - size);
397 if (opts.options.mem_stomp)
398 memset(p, 0xF0, size);
402 p = gcx.bigAlloc(size);
404 onOutOfMemoryError();
406 if (opts.options.sentinel) {
407 size -= SENTINEL_EXTRA;
409 sentinel_init(p, size);
414 Pool *pool = gcx.findPool(p);
417 gcx.setBits(pool, cast(size_t)(p - pool.baseAddr) / 16, bits);
426 void *calloc(size_t size, uint bits = 0)
433 if (!thread_needLock())
435 return callocNoSync(size, bits);
437 else synchronized (gcLock)
439 return callocNoSync(size, bits);
447 private void *callocNoSync(size_t size, uint bits = 0)
451 void *p = mallocNoSync(size, bits);
460 void *realloc(void *p, size_t size, uint bits = 0)
462 if (!thread_needLock())
464 return reallocNoSync(p, size, bits);
466 else synchronized (gcLock)
468 return reallocNoSync(p, size, bits);
476 private void *reallocNoSync(void *p, size_t size, uint bits = 0)
488 p = mallocNoSync(size, bits);
495 if (opts.options.sentinel)
497 sentinel_Invariant(p);
498 psize = *sentinel_size(p);
503 Pool *pool = gcx.findPool(p);
507 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
511 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
512 gcx.setBits(pool, biti, bits);
516 bits = gcx.getBits(pool, biti);
520 p2 = mallocNoSync(size, bits);
523 cstring.memcpy(p2, p, size);
529 psize = gcx.findSize(p); // find allocated size
530 if (psize >= PAGESIZE && size >= PAGESIZE)
532 auto psz = psize / PAGESIZE;
533 auto newsz = (size + PAGESIZE - 1) / PAGESIZE;
537 auto pool = gcx.findPool(p);
538 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
543 synchronized (gcLock)
545 if (opts.options.mem_stomp)
546 memset(p + size, 0xF2, psize - size);
547 pool.freePages(pagenum + newsz, psz - newsz);
551 else if (pagenum + newsz <= pool.npages)
553 // Attempt to expand in place
554 synchronized (gcLock)
556 for (size_t i = pagenum + psz; 1;)
558 if (i == pagenum + newsz)
560 if (opts.options.mem_stomp)
561 memset(p + psize, 0xF0, size - psize);
562 memset(pool.pagetable + pagenum +
563 psz, B_PAGEPLUS, newsz - psz);
566 if (i == pool.npages)
570 if (pool.pagetable[i] != B_FREE)
577 if (psize < size || // if new size is bigger
578 psize > size * 2) // or less than half
582 Pool *pool = gcx.findPool(p);
586 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
590 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
591 gcx.setBits(pool, biti, bits);
595 bits = gcx.getBits(pool, biti);
599 p2 = mallocNoSync(size, bits);
602 cstring.memcpy(p2, p, size);
612 * Attempt to in-place enlarge the memory block pointed to by p by at least
613 * minbytes beyond its current capacity, up to a maximum of maxsize. This
614 * does not attempt to move the memory block (like realloc() does).
617 * 0 if could not extend p,
618 * total size of entire memory block if successful.
620 size_t extend(void* p, size_t minsize, size_t maxsize)
622 if (!thread_needLock())
624 return extendNoSync(p, minsize, maxsize);
626 else synchronized (gcLock)
628 return extendNoSync(p, minsize, maxsize);
636 private size_t extendNoSync(void* p, size_t minsize, size_t maxsize)
639 assert( minsize <= maxsize );
643 if (opts.options.sentinel)
647 auto psize = gcx.findSize(p); // find allocated size
648 if (psize < PAGESIZE)
649 return 0; // cannot extend buckets
651 auto psz = psize / PAGESIZE;
652 auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE;
653 auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE;
655 auto pool = gcx.findPool(p);
656 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
659 for (sz = 0; sz < maxsz; sz++)
661 auto i = pagenum + psz + sz;
662 if (i == pool.npages)
664 if (pool.pagetable[i] != B_FREE)
673 if (opts.options.mem_stomp)
674 memset(p + psize, 0xF0, (psz + sz) * PAGESIZE - psize);
675 memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz);
678 return (psz + sz) * PAGESIZE;
685 size_t reserve(size_t size)
692 if (!thread_needLock())
694 return reserveNoSync(size);
696 else synchronized (gcLock)
698 return reserveNoSync(size);
706 private size_t reserveNoSync(size_t size)
711 return gcx.reserve(size);
725 if (!thread_needLock())
727 return freeNoSync(p);
729 else synchronized (gcLock)
731 return freeNoSync(p);
739 private void freeNoSync(void *p)
748 // Find which page it is in
749 pool = gcx.findPool(p);
750 if (!pool) // if not one of ours
752 if (opts.options.sentinel) {
753 sentinel_Invariant(p);
756 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
757 biti = cast(size_t)(p - pool.baseAddr) / 16;
758 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
760 bin = cast(Bins)pool.pagetable[pagenum];
761 if (bin == B_PAGE) // if large alloc
766 while (++n < pool.npages && pool.pagetable[n] == B_PAGEPLUS)
768 if (opts.options.mem_stomp)
769 memset(p, 0xF2, npages * PAGESIZE);
770 pool.freePages(pagenum, npages);
775 List *list = cast(List*)p;
777 if (opts.options.mem_stomp)
778 memset(p, 0xF2, binsize[bin]);
780 list.next = gcx.bucket[bin];
781 gcx.bucket[bin] = list;
787 * Determine the base address of the block containing p. If p is not a gc
788 * allocated pointer, return null.
790 void* addrOf(void *p)
797 if (!thread_needLock())
799 return addrOfNoSync(p);
801 else synchronized (gcLock)
803 return addrOfNoSync(p);
811 void* addrOfNoSync(void *p)
818 return gcx.findBase(p);
823 * Determine the allocated size of pointer p. If p is an interior pointer
824 * or not a gc allocated pointer, return 0.
826 size_t sizeOf(void *p)
833 if (!thread_needLock())
835 return sizeOfNoSync(p);
837 else synchronized (gcLock)
839 return sizeOfNoSync(p);
847 private size_t sizeOfNoSync(void *p)
851 if (opts.options.sentinel)
854 size_t size = gcx.findSize(p);
856 // Check for interior pointer
858 // 1) size is a power of 2 for less than PAGESIZE values
859 // 2) base of memory pool is aligned on PAGESIZE boundary
860 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
862 return size ? size - SENTINEL_EXTRA : 0;
866 if (p == gcx.p_cache)
867 return gcx.size_cache;
869 size_t size = gcx.findSize(p);
871 // Check for interior pointer
873 // 1) size is a power of 2 for less than PAGESIZE values
874 // 2) base of memory pool is aligned on PAGESIZE boundary
875 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
880 gcx.size_cache = size;
889 * Determine the base address of the block containing p. If p is not a gc
890 * allocated pointer, return null.
892 BlkInfo query(void *p)
900 if (!thread_needLock())
902 return queryNoSync(p);
904 else synchronized (gcLock)
906 return queryNoSync(p);
914 BlkInfo queryNoSync(void *p)
918 return gcx.getInfo(p);
923 * Verify that pointer p:
924 * 1) belongs to this memory pool
925 * 2) points to the start of an allocated piece of memory
926 * 3) is not on a free list
935 if (!thread_needLock())
939 else synchronized (gcLock)
949 private void checkNoSync(void *p)
953 if (opts.options.sentinel)
954 sentinel_Invariant(p);
962 if (opts.options.sentinel)
964 pool = gcx.findPool(p);
966 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
967 bin = cast(Bins)pool.pagetable[pagenum];
968 assert(bin <= B_PAGE);
970 assert((cast(size_t)p & (size - 1)) == 0);
976 // Check that p is not on a free list
979 for (list = gcx.bucket[bin]; list; list = list.next)
981 assert(cast(void*)list != p);
992 private void setStackBottom(void *p)
994 version (STACKGROWSDOWN)
996 //p = (void *)((uint *)p + 4);
997 if (p > gcx.stackBottom)
1004 //p = (void *)((uint *)p - 4);
1005 if (p < gcx.stackBottom)
1007 gcx.stackBottom = cast(char*)p;
1014 * add p to list of roots
1016 void addRoot(void *p)
1023 if (!thread_needLock())
1025 if (roots.append(p) is null)
1026 onOutOfMemoryError();
1028 else synchronized (gcLock)
1030 if (roots.append(p) is null)
1031 onOutOfMemoryError();
1037 * remove p from list of roots
1039 void removeRoot(void *p)
1047 if (!thread_needLock())
1049 r = roots.remove(p);
1051 else synchronized (gcLock)
1053 r = roots.remove(p);
1060 * add range to scan for roots
1062 void addRange(void *p, size_t sz)
1069 if (!thread_needLock())
1071 if (ranges.append(Range(p, p+sz)) is null)
1072 onOutOfMemoryError();
1074 else synchronized (gcLock)
1076 if (ranges.append(Range(p, p+sz)) is null)
1077 onOutOfMemoryError();
1085 void removeRange(void *p)
1093 if (!thread_needLock())
1095 r = ranges.remove(Range(p, null));
1097 else synchronized (gcLock)
1099 r = ranges.remove(Range(p, null));
1106 * do full garbage collection
1111 if (!thread_needLock())
1113 gcx.fullcollectshell();
1115 else synchronized (gcLock)
1117 gcx.fullcollectshell();
1130 * do full garbage collection ignoring roots
1132 void fullCollectNoStack()
1134 if (!thread_needLock())
1137 gcx.fullcollectshell();
1140 else synchronized (gcLock)
1143 gcx.fullcollectshell();
1150 * minimize free space usage
1154 if (!thread_needLock())
1158 else synchronized (gcLock)
1166 * Retrieve statistics about garbage collection.
1167 * Useful for debugging and tuning.
1169 void getStats(out GCStats stats)
1171 if (!thread_needLock())
1173 getStatsNoSync(stats);
1175 else synchronized (gcLock)
1177 getStatsNoSync(stats);
1185 private void getStatsNoSync(out GCStats stats)
1194 memset(&stats, 0, GCStats.sizeof);
1196 for (n = 0; n < pools.length; n++)
1198 Pool* pool = pools[n];
1199 psize += pool.npages * PAGESIZE;
1200 for (size_t j = 0; j < pool.npages; j++)
1202 Bins bin = cast(Bins)pool.pagetable[j];
1205 else if (bin == B_PAGE)
1207 else if (bin < B_PAGE)
1212 for (n = 0; n < B_PAGE; n++)
1214 for (List *list = gcx.bucket[n]; list; list = list.next)
1215 flsize += binsize[n];
1218 usize = bsize - flsize;
1220 stats.poolsize = psize;
1221 stats.usedsize = bsize - flsize;
1222 stats.freelistsize = flsize;
1225 /******************* weak-reference support *********************/
1227 // call locked if necessary
1228 private T locked(T)(in T delegate() code)
1230 if (thread_needLock)
1231 synchronized(gcLock) return code();
1236 private struct WeakPointer
1240 void ondestroy(Object r)
1242 assert(r is reference);
1243 // lock for memory consistency (parallel readers)
1244 // also ensures that weakpointerDestroy can be called while another
1245 // thread is freeing the reference with "delete"
1246 locked!(void)({ reference = null; });
1251 * Create a weak pointer to the given object.
1252 * Returns a pointer to an opaque struct allocated in C memory.
1254 void* weakpointerCreate( Object r )
1258 // must be allocated in C memory
1259 // 1. to hide the reference from the GC
1260 // 2. the GC doesn't scan delegates added by rt_attachDisposeEvent
1262 auto wp = cast(WeakPointer*)(cstdlib.malloc(WeakPointer.sizeof));
1264 onOutOfMemoryError();
1266 rt_attachDisposeEvent(r, &wp.ondestroy);
1273 * Destroy a weak pointer returned by weakpointerCreate().
1274 * If null is passed, nothing happens.
1276 void weakpointerDestroy( void* p )
1280 auto wp = cast(WeakPointer*)p;
1281 // must be extra careful about the GC or parallel threads
1282 // finalizing the reference at the same time
1285 rt_detachDisposeEvent(wp.reference, &wp.ondestroy);
1292 * Query a weak pointer and return either the object passed to
1293 * weakpointerCreate, or null if it was free'd in the meantime.
1294 * If null is passed, null is returned.
1296 Object weakpointerGet( void* p )
1300 // NOTE: could avoid the lock by using Fawzi style GC counters but
1301 // that'd require core.sync.Atomic and lots of care about memory
1302 // consistency it's an optional optimization see
1303 // http://dsource.org/projects/tango/browser/trunk/user/tango/core/Lifetime.d?rev=5100#L158
1304 return locked!(Object)({
1305 return (cast(WeakPointer*)p).reference;
1312 /* ============================ Gcx =============================== */
1317 POOLSIZE = (4096*256),
1331 B_PAGE, // start of large alloc
1332 B_PAGEPLUS, // continuation of large alloc
1333 B_FREE, // free page
1351 int opCmp(in Range other)
1353 if (pbot < other.pbot)
1356 return cast(int)(pbot > other.pbot);
1361 const uint binsize[B_MAX] = [ 16,32,64,128,256,512,1024,2048,4096 ];
1362 const uint notbinsize[B_MAX] = [ ~(16u-1),~(32u-1),~(64u-1),~(128u-1),~(256u-1),
1363 ~(512u-1),~(1024u-1),~(2048u-1),~(4096u-1) ];
1365 DynArray!(void*) roots;
1367 DynArray!(Range) ranges;
1369 DynArray!(Pool) pools;
1372 /* ============================ Gcx =============================== */
1381 uint noStack; // !=0 means don't scan stack
1382 uint log; // turn on logging
1386 int disabled; // turn off collections if >0
1388 byte *minAddr; // min(baseAddr)
1389 byte *maxAddr; // max(topAddr)
1391 List *bucket[B_MAX]; // free list for each size
1397 (cast(byte*)this)[0 .. Gcx.sizeof] = 0;
1398 stackBottom = cast(char*)&dummy;
1399 //printf("gcx = %p, self = %x\n", this, self);
1404 void Invariant() { }
1411 //printf("Gcx.invariant(): this = %p\n", this);
1414 for (i = 0; i < pools.length; i++)
1416 Pool* pool = pools[i];
1420 assert(minAddr == pool.baseAddr);
1422 if (i + 1 < pools.length)
1424 assert(*pool < pools[i + 1]);
1426 else if (i + 1 == pools.length)
1428 assert(maxAddr == pool.topAddr);
1435 for (i = 0; i < ranges.length; i++)
1437 assert(ranges[i].pbot);
1438 assert(ranges[i].ptop);
1439 assert(ranges[i].pbot <= ranges[i].ptop);
1442 for (i = 0; i < B_PAGE; i++)
1444 for (List *list = bucket[i]; list; list = list.next)
1453 * Find Pool that pointer is in.
1454 * Return null if not in a Pool.
1455 * Assume pools is sorted.
1457 Pool *findPool(void *p)
1459 if (p >= minAddr && p < maxAddr)
1461 if (pools.length == 1)
1466 for (size_t i = 0; i < pools.length; i++)
1468 Pool* pool = pools[i];
1469 if (p < pool.topAddr)
1471 if (pool.baseAddr <= p)
1482 * Find base address of block containing pointer p.
1483 * Returns null if not a gc'd pointer
1485 void* findBase(void *p)
1492 size_t offset = cast(size_t)(p - pool.baseAddr);
1493 size_t pn = offset / PAGESIZE;
1494 Bins bin = cast(Bins)pool.pagetable[pn];
1496 // Adjust bit to be at start of allocated memory block
1499 return pool.baseAddr + (offset & notbinsize[bin]);
1501 else if (bin == B_PAGEPLUS)
1505 --pn, offset -= PAGESIZE;
1506 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1508 return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1512 // we are in a B_FREE page
1521 * Find size of pointer p.
1522 * Returns 0 if not a gc'd pointer
1524 size_t findSize(void *p)
1535 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
1536 bin = cast(Bins)pool.pagetable[pagenum];
1537 size = binsize[bin];
1543 pt = &pool.pagetable[0];
1544 for (i = pagenum + 1; i < pool.npages; i++)
1546 if (pt[i] != B_PAGEPLUS)
1549 size = (i - pagenum) * PAGESIZE;
1559 BlkInfo getInfo(void* p)
1567 size_t offset = cast(size_t)(p - pool.baseAddr);
1568 size_t pn = offset / PAGESIZE;
1569 Bins bin = cast(Bins)pool.pagetable[pn];
1571 ////////////////////////////////////////////////////////////////////
1573 ////////////////////////////////////////////////////////////////////
1577 info.base = pool.baseAddr + (offset & notbinsize[bin]);
1579 else if (bin == B_PAGEPLUS)
1583 --pn, offset -= PAGESIZE;
1585 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1587 info.base = pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1589 // fix bin for use by size calc below
1590 bin = cast(Bins)pool.pagetable[pn];
1593 ////////////////////////////////////////////////////////////////////
1595 ////////////////////////////////////////////////////////////////////
1597 info.size = binsize[bin];
1603 pt = &pool.pagetable[0];
1604 for (i = pn + 1; i < pool.npages; i++)
1606 if (pt[i] != B_PAGEPLUS)
1609 info.size = (i - pn) * PAGESIZE;
1612 ////////////////////////////////////////////////////////////////////
1614 ////////////////////////////////////////////////////////////////////
1616 info.attr = getBits(pool, cast(size_t)(offset / 16));
1623 * Compute bin for size.
1625 static Bins findBin(size_t size)
1634 else if (size <= 32)
1669 * Allocate a new pool of at least size bytes.
1670 * Sort it into pools.
1671 * Mark all memory in the pool as B_FREE.
1672 * Return the actual number of bytes reserved or 0 on error.
1674 size_t reserve(size_t size)
1676 size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
1677 Pool* pool = newPool(npages);
1681 return pool.npages * PAGESIZE;
1686 * Minimizes physical memory usage by returning free pools to the OS.
1694 for (n = 0; n < pools.length; n++)
1697 for (pn = 0; pn < pool.npages; pn++)
1699 if (cast(Bins)pool.pagetable[pn] != B_FREE)
1702 if (pn < pool.npages)
1708 minAddr = pools[0].baseAddr;
1709 maxAddr = pools[pools.length - 1].topAddr;
1714 * Allocate a chunk of memory that is larger than a page.
1715 * Return null if out of memory.
1717 void *bigAlloc(size_t size)
1727 npages = (size + PAGESIZE - 1) / PAGESIZE;
1731 // This code could use some refinement when repeatedly
1732 // allocating very large arrays.
1734 for (n = 0; n < pools.length; n++)
1737 pn = pool.allocPages(npages);
1752 freedpages = fullcollectshell();
1753 if (freedpages >= pools.length * ((POOLSIZE / PAGESIZE) / 4))
1758 // Release empty pools to prevent bloat
1760 // Allocate new pool
1761 pool = newPool(npages);
1767 pn = pool.allocPages(npages);
1768 assert(pn != OPFAIL);
1771 // Release empty pools to prevent bloat
1773 // Allocate new pool
1774 pool = newPool(npages);
1777 pn = pool.allocPages(npages);
1778 assert(pn != OPFAIL);
1788 pool.pagetable[pn] = B_PAGE;
1790 memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1);
1791 p = pool.baseAddr + pn * PAGESIZE;
1792 memset(cast(char *)p + size, 0, npages * PAGESIZE - size);
1793 if (opts.options.mem_stomp)
1794 memset(p, 0xF1, size);
1798 return null; // let mallocNoSync handle the error
1803 * Allocate a new pool with at least npages in it.
1804 * Sort it into pools.
1805 * Return null if failed.
1807 Pool *newPool(size_t npages)
1809 // Minimum of POOLSIZE
1810 if (npages < POOLSIZE/PAGESIZE)
1811 npages = POOLSIZE/PAGESIZE;
1812 else if (npages > POOLSIZE/PAGESIZE)
1814 // Give us 150% of requested size, so there's room to extend
1815 auto n = npages + (npages >> 1);
1816 if (n < size_t.max/PAGESIZE)
1820 // Allocate successively larger pools up to 8 megs
1823 size_t n = pools.length;
1825 n = 8; // cap pool size at 8 megs
1826 n *= (POOLSIZE / PAGESIZE);
1832 p.initialize(npages);
1839 Pool* pool = pools.insert_sorted(p);
1842 minAddr = pools[0].baseAddr;
1843 maxAddr = pools[pools.length - 1].topAddr;
1850 * Allocate a page of bin's.
1854 int allocPage(Bins bin)
1862 for (n = 0; n < pools.length; n++)
1865 pn = pool.allocPages(1);
1872 pool.pagetable[pn] = cast(ubyte)bin;
1874 // Convert page to free list
1875 size_t size = binsize[bin];
1876 List **b = &bucket[bin];
1878 p = pool.baseAddr + pn * PAGESIZE;
1879 ptop = p + PAGESIZE;
1880 for (; p < ptop; p += size)
1882 (cast(List *)p).next = *b;
1890 * Search a range of memory values and mark any pointers into the GC pool.
1892 void mark(void *pbot, void *ptop)
1894 void **p1 = cast(void **)pbot;
1895 void **p2 = cast(void **)ptop;
1899 //printf("marking range: %p -> %p\n", pbot, ptop);
1900 for (; p1 < p2; p1++)
1903 byte *p = cast(byte *)(*p1);
1905 if (p >= minAddr && p < maxAddr)
1907 if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
1913 size_t offset = cast(size_t)(p - pool.baseAddr);
1915 size_t pn = offset / PAGESIZE;
1916 Bins bin = cast(Bins)pool.pagetable[pn];
1918 // Adjust bit to be at start of allocated memory block
1920 biti = (offset & notbinsize[bin]) >> 4;
1921 else if (bin == B_PAGEPLUS)
1927 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1928 biti = pn * (PAGESIZE / 16);
1932 // Don't mark bits in B_FREE pages
1936 if (bin >= B_PAGE) // Cache B_PAGE and B_PAGEPLUS lookups
1937 pcache = cast(size_t)p & ~(PAGESIZE-1);
1939 if (!pool.mark.test(biti))
1941 pool.mark.set(biti);
1942 if (!pool.noscan.test(biti))
1944 pool.scan.set(biti);
1951 anychanges |= changes;
1955 * Return number of full pages free'd.
1957 size_t fullcollectshell()
1959 stats.collection_started();
1961 stats.collection_finished();
1963 // The purpose of the 'shell' is to ensure all the registers
1964 // get put on the stack so they'll be scanned
1969 gcc.builtins.__builtin_unwind_init();
1976 uint eax,ecx,edx,ebx,ebp,esi,edi;
1989 else version (X86_64)
1991 ulong rax,rbx,rcx,rdx,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15;
1994 movq rax[RBP], RAX ;
1995 movq rbx[RBP], RBX ;
1996 movq rcx[RBP], RCX ;
1997 movq rdx[RBP], RDX ;
1998 movq rbp[RBP], RBP ;
1999 movq rsi[RBP], RSI ;
2000 movq rdi[RBP], RDI ;
2003 movq r10[RBP], R10 ;
2004 movq r11[RBP], R11 ;
2005 movq r12[RBP], R12 ;
2006 movq r13[RBP], R13 ;
2007 movq r14[RBP], R14 ;
2008 movq r15[RBP], R15 ;
2014 static assert( false, "Architecture not supported." );
2025 result = fullcollect(sp);
2048 size_t fullcollect(void *stackTop)
2053 debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n");
2055 thread_suspendAll();
2056 stats.world_stopped();
2062 for (n = 0; n < pools.length; n++)
2067 pool.freebits.zero();
2070 // Mark each free entry, so it doesn't get scanned
2071 for (n = 0; n < B_PAGE; n++)
2073 for (List *list = bucket[n]; list; list = list.next)
2075 pool = findPool(list);
2077 pool.freebits.set(cast(size_t)(cast(byte*)list - pool.baseAddr) / 16);
2081 for (n = 0; n < pools.length; n++)
2084 pool.mark.copy(&pool.freebits);
2087 rt_scanStaticData( &mark );
2091 // Scan stacks and registers for each paused thread
2092 thread_scanAll( &mark, stackTop );
2096 debug(COLLECT_PRINTF) printf("scan roots[]\n");
2097 mark(roots.ptr, roots.ptr + roots.length);
2100 debug(COLLECT_PRINTF) printf("scan ranges[]\n");
2102 for (n = 0; n < ranges.length; n++)
2104 debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop);
2105 mark(ranges[n].pbot, ranges[n].ptop);
2109 debug(COLLECT_PRINTF) printf("\tscan heap\n");
2113 for (n = 0; n < pools.length; n++)
2121 bbase = pool.scan.base();
2122 btop = bbase + pool.scan.nwords;
2123 for (b = bbase; b < btop;)
2139 o = pool.baseAddr + (b - bbase) * 32 * 16;
2140 if (!(bitm & 0xFFFF))
2145 for (; bitm; o += 16, bitm >>= 1)
2150 pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE;
2151 bin = cast(Bins)pool.pagetable[pn];
2154 mark(o, o + binsize[bin]);
2156 else if (bin == B_PAGE || bin == B_PAGEPLUS)
2158 if (bin == B_PAGEPLUS)
2160 while (pool.pagetable[pn - 1] != B_PAGE)
2164 while (pn + u < pool.npages && pool.pagetable[pn + u] == B_PAGEPLUS)
2166 mark(o, o + u * PAGESIZE);
2174 stats.world_started();
2176 // Free up everything not marked
2177 debug(COLLECT_PRINTF) printf("\tfree'ing\n");
2178 size_t freedpages = 0;
2180 for (n = 0; n < pools.length; n++)
2183 uint* bbase = pool.mark.base();
2185 for (pn = 0; pn < pool.npages; pn++, bbase += PAGESIZE / (32 * 16))
2187 Bins bin = cast(Bins)pool.pagetable[pn];
2191 auto size = binsize[bin];
2192 byte* p = pool.baseAddr + pn * PAGESIZE;
2193 byte* ptop = p + PAGESIZE;
2194 size_t biti = pn * (PAGESIZE/16);
2195 size_t bitstride = size / 16;
2197 version(none) // BUG: doesn't work because freebits() must also be cleared
2199 // If free'd entire page
2200 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 && bbase[3] == 0 &&
2201 bbase[4] == 0 && bbase[5] == 0 && bbase[6] == 0 && bbase[7] == 0)
2203 for (; p < ptop; p += size, biti += bitstride)
2205 if (pool.finals.nbits && pool.finals.testClear(biti)) {
2206 if (opts.options.sentinel)
2207 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2209 rt_finalize(cast(List *)p, false/*noStack > 0*/);
2211 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
2213 List *list = cast(List *)p;
2215 if (opts.options.mem_stomp)
2216 memset(p, 0xF3, size);
2218 pool.pagetable[pn] = B_FREE;
2223 for (; p < ptop; p += size, biti += bitstride)
2225 if (!pool.mark.test(biti))
2227 if (opts.options.sentinel)
2228 sentinel_Invariant(sentinel_add(p));
2230 pool.freebits.set(biti);
2231 if (pool.finals.nbits && pool.finals.testClear(biti)) {
2232 if (opts.options.sentinel)
2233 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2235 rt_finalize(cast(List *)p, false/*noStack > 0*/);
2237 clrBits(pool, biti, BlkAttr.ALL_BITS);
2239 List *list = cast(List *)p;
2241 if (opts.options.mem_stomp)
2242 memset(p, 0xF3, size);
2248 else if (bin == B_PAGE)
2250 size_t biti = pn * (PAGESIZE / 16);
2251 if (!pool.mark.test(biti))
2253 byte *p = pool.baseAddr + pn * PAGESIZE;
2254 if (opts.options.sentinel)
2255 sentinel_Invariant(sentinel_add(p));
2256 if (pool.finals.nbits && pool.finals.testClear(biti)) {
2257 if (opts.options.sentinel)
2258 rt_finalize(sentinel_add(p), false/*noStack > 0*/);
2260 rt_finalize(p, false/*noStack > 0*/);
2262 clrBits(pool, biti, BlkAttr.ALL_BITS);
2264 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p);
2265 pool.pagetable[pn] = B_FREE;
2267 if (opts.options.mem_stomp)
2268 memset(p, 0xF3, PAGESIZE);
2269 while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS)
2272 pool.pagetable[pn] = B_FREE;
2275 if (opts.options.mem_stomp)
2278 memset(p, 0xF3, PAGESIZE);
2289 // Free complete pages, rebuild free list
2290 debug(COLLECT_PRINTF) printf("\tfree complete pages\n");
2291 size_t recoveredpages = 0;
2292 for (n = 0; n < pools.length; n++)
2295 for (size_t pn = 0; pn < pool.npages; pn++)
2297 Bins bin = cast(Bins)pool.pagetable[pn];
2303 size_t size = binsize[bin];
2304 size_t bitstride = size / 16;
2305 size_t bitbase = pn * (PAGESIZE / 16);
2306 size_t bittop = bitbase + (PAGESIZE / 16);
2310 for (biti = bitbase; biti < bittop; biti += bitstride)
2312 if (!pool.freebits.test(biti))
2315 pool.pagetable[pn] = B_FREE;
2320 p = pool.baseAddr + pn * PAGESIZE;
2321 for (u = 0; u < PAGESIZE; u += size)
2323 biti = bitbase + u / 16;
2324 if (pool.freebits.test(biti))
2326 List *list = cast(List *)(p + u);
2327 if (list.next != bucket[bin]) // avoid unnecessary writes
2328 list.next = bucket[bin];
2336 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages);
2337 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, pools.length);
2339 return freedpages + recoveredpages;
2346 uint getBits(Pool* pool, size_t biti)
2355 if (pool.finals.nbits &&
2356 pool.finals.test(biti))
2357 bits |= BlkAttr.FINALIZE;
2358 if (pool.noscan.test(biti))
2359 bits |= BlkAttr.NO_SCAN;
2360 // if (pool.nomove.nbits &&
2361 // pool.nomove.test(biti))
2362 // bits |= BlkAttr.NO_MOVE;
2370 void setBits(Pool* pool, size_t biti, uint mask)
2377 if (mask & BlkAttr.FINALIZE)
2379 if (!pool.finals.nbits)
2380 pool.finals.alloc(pool.mark.nbits);
2381 pool.finals.set(biti);
2383 if (mask & BlkAttr.NO_SCAN)
2385 pool.noscan.set(biti);
2387 // if (mask & BlkAttr.NO_MOVE)
2389 // if (!pool.nomove.nbits)
2390 // pool.nomove.alloc(pool.mark.nbits);
2391 // pool.nomove.set(biti);
2399 void clrBits(Pool* pool, size_t biti, uint mask)
2406 if (mask & BlkAttr.FINALIZE && pool.finals.nbits)
2407 pool.finals.clear(biti);
2408 if (mask & BlkAttr.NO_SCAN)
2409 pool.noscan.clear(biti);
2410 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits)
2411 // pool.nomove.clear(biti);
2417 /* ============================ Pool =============================== */
2424 GCBits mark; // entries already scanned, or should not be scanned
2425 GCBits scan; // entries that need to be scanned
2426 GCBits freebits; // entries that are on the free list
2427 GCBits finals; // entries that need finalizer run on them
2428 GCBits noscan; // entries that should not be scanned
2434 void initialize(size_t npages)
2436 size_t poolsize = npages * PAGESIZE;
2437 assert(poolsize >= POOLSIZE);
2438 baseAddr = cast(byte *) alloc.os_mem_map(poolsize);
2440 // Some of the code depends on page alignment of memory pools
2441 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0);
2449 topAddr = baseAddr + poolsize;
2451 mark.alloc(cast(size_t)poolsize / 16);
2452 scan.alloc(cast(size_t)poolsize / 16);
2453 freebits.alloc(cast(size_t)poolsize / 16);
2454 noscan.alloc(cast(size_t)poolsize / 16);
2456 pagetable = cast(ubyte*) cstdlib.malloc(npages);
2458 onOutOfMemoryError();
2459 memset(pagetable, B_FREE, npages);
2461 this.npages = npages;
2473 result = alloc.os_mem_unmap(baseAddr, npages * PAGESIZE);
2481 // See Gcx.Dtor() for the rationale of the null check.
2483 cstdlib.free(pagetable);
2493 void Invariant() { }
2500 //freebits.Invariant();
2501 //finals.Invariant();
2502 //noscan.Invariant();
2506 //if (baseAddr + npages * PAGESIZE != topAddr)
2507 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr);
2508 assert(baseAddr + npages * PAGESIZE == topAddr);
2511 for (size_t i = 0; i < npages; i++)
2513 Bins bin = cast(Bins)pagetable[i];
2514 assert(bin < B_MAX);
2520 * Allocate n pages from Pool.
2521 * Returns OPFAIL on failure.
2523 size_t allocPages(size_t n)
2529 for (i = 0; i < npages; i++)
2531 if (pagetable[i] == B_FREE)
2544 * Free npages pages starting with pagenum.
2546 void freePages(size_t pagenum, size_t npages)
2548 memset(&pagetable[pagenum], B_FREE, npages);
2553 * Used for sorting pools
2555 int opCmp(in Pool other)
2557 if (baseAddr < other.baseAddr)
2560 return cast(int)(baseAddr > other.baseAddr);
2565 /* ============================ SENTINEL =============================== */
2568 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
2569 const ubyte SENTINEL_POST = 0xF5; // 8 bits
2570 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
2573 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; }
2574 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; }
2575 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; }
2578 void sentinel_init(void *p, size_t size)
2580 *sentinel_size(p) = size;
2581 *sentinel_pre(p) = SENTINEL_PRE;
2582 *sentinel_post(p) = SENTINEL_POST;
2586 void sentinel_Invariant(void *p)
2588 assert(*sentinel_pre(p) == SENTINEL_PRE);
2589 assert(*sentinel_post(p) == SENTINEL_POST);
2593 void *sentinel_add(void *p)
2595 return p + 2 * size_t.sizeof;
2599 void *sentinel_sub(void *p)
2601 return p - 2 * size_t.sizeof;
2605 // vim: set et sw=4 sts=4 :