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 bit_i = cast(size_t)(p - pool.baseAddr) / 16;
219 old_attrs = gcx.getAttr(pool, bit_i);
224 if (!thread_needLock())
228 else synchronized (gcLock)
238 uint setAttr(void* p, uint mask)
247 Pool* pool = gcx.findPool(p);
252 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
254 old_attrs = gcx.getAttr(pool, bit_i);
255 gcx.setAttr(pool, bit_i, mask);
260 if (!thread_needLock())
264 else synchronized (gcLock)
274 uint clrAttr(void* p, uint mask)
283 Pool* pool = gcx.findPool(p);
288 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
290 old_attrs = gcx.getAttr(pool, bit_i);
291 gcx.clrAttr(pool, bit_i, mask);
296 if (!thread_needLock())
300 else synchronized (gcLock)
310 void *malloc(size_t size, uint attrs = 0)
317 if (!thread_needLock())
319 return mallocNoSync(size, attrs);
321 else synchronized (gcLock)
323 return mallocNoSync(size, attrs);
331 private void *mallocNoSync(size_t size, uint attrs = 0)
335 stats.malloc_started(size, attrs);
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( !(attrs & 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.setAttr(pool, cast(size_t)(p - pool.baseAddr) / 16, attrs);
426 void *calloc(size_t size, uint attrs = 0)
433 if (!thread_needLock())
435 return callocNoSync(size, attrs);
437 else synchronized (gcLock)
439 return callocNoSync(size, attrs);
447 private void *callocNoSync(size_t size, uint attrs = 0)
451 void *p = mallocNoSync(size, attrs);
460 void *realloc(void *p, size_t size, uint attrs = 0)
462 if (!thread_needLock())
464 return reallocNoSync(p, size, attrs);
466 else synchronized (gcLock)
468 return reallocNoSync(p, size, attrs);
476 private void *reallocNoSync(void *p, size_t size, uint attrs = 0)
488 p = mallocNoSync(size, attrs);
495 if (opts.options.sentinel)
497 sentinel_Invariant(p);
498 psize = *sentinel_size(p);
503 Pool *pool = gcx.findPool(p);
507 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
511 gcx.clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
512 gcx.setAttr(pool, bit_i, attrs);
516 attrs = gcx.getAttr(pool, bit_i);
520 p2 = mallocNoSync(size, attrs);
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 bit_i = cast(size_t)(p - pool.baseAddr) / 16;
590 gcx.clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
591 gcx.setAttr(pool, bit_i, attrs);
595 attrs = gcx.getAttr(pool, bit_i);
599 p2 = mallocNoSync(size, attrs);
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 bit_i = cast(size_t)(p - pool.baseAddr) / 16;
758 gcx.clrAttr(pool, bit_i, 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 = getAttr(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 bit_i = (offset & notbinsize[bin]) >> 4;
1921 else if (bin == B_PAGEPLUS)
1927 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1928 bit_i = 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(bit_i))
1941 pool.mark.set(bit_i);
1942 if (!pool.noscan.test(bit_i))
1944 pool.scan.set(bit_i);
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 &&
2165 pool.pagetable[pn + u] == B_PAGEPLUS)
2167 mark(o, o + u * PAGESIZE);
2175 stats.world_started();
2177 // Free up everything not marked
2178 debug(COLLECT_PRINTF) printf("\tfree'ing\n");
2179 size_t freedpages = 0;
2181 for (n = 0; n < pools.length; n++)
2184 uint* bbase = pool.mark.base();
2186 for (pn = 0; pn < pool.npages; pn++, bbase += PAGESIZE / (32 * 16))
2188 Bins bin = cast(Bins)pool.pagetable[pn];
2192 auto size = binsize[bin];
2193 byte* p = pool.baseAddr + pn * PAGESIZE;
2194 byte* ptop = p + PAGESIZE;
2195 size_t bit_i = pn * (PAGESIZE/16);
2196 size_t bit_stride = size / 16;
2198 version(none) // BUG: doesn't work because freebits() must also be cleared
2200 // If free'd entire page
2201 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 &&
2202 bbase[3] == 0 && bbase[4] == 0 && bbase[5] == 0 &&
2203 bbase[6] == 0 && bbase[7] == 0)
2205 for (; p < ptop; p += size, bit_i += bit_stride)
2207 if (pool.finals.nbits && pool.finals.testClear(bit_i)) {
2208 if (opts.options.sentinel)
2209 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2211 rt_finalize(cast(List *)p, false/*noStack > 0*/);
2213 gcx.clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
2215 List *list = cast(List *)p;
2217 if (opts.options.mem_stomp)
2218 memset(p, 0xF3, size);
2220 pool.pagetable[pn] = B_FREE;
2225 for (; p < ptop; p += size, bit_i += bit_stride)
2227 if (!pool.mark.test(bit_i))
2229 if (opts.options.sentinel)
2230 sentinel_Invariant(sentinel_add(p));
2232 pool.freebits.set(bit_i);
2233 if (pool.finals.nbits && pool.finals.testClear(bit_i)) {
2234 if (opts.options.sentinel)
2235 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2237 rt_finalize(cast(List *)p, false/*noStack > 0*/);
2239 clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
2241 List *list = cast(List *)p;
2243 if (opts.options.mem_stomp)
2244 memset(p, 0xF3, size);
2250 else if (bin == B_PAGE)
2252 size_t bit_i = pn * (PAGESIZE / 16);
2253 if (!pool.mark.test(bit_i))
2255 byte *p = pool.baseAddr + pn * PAGESIZE;
2256 if (opts.options.sentinel)
2257 sentinel_Invariant(sentinel_add(p));
2258 if (pool.finals.nbits && pool.finals.testClear(bit_i)) {
2259 if (opts.options.sentinel)
2260 rt_finalize(sentinel_add(p), false/*noStack > 0*/);
2262 rt_finalize(p, false/*noStack > 0*/);
2264 clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
2266 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p);
2267 pool.pagetable[pn] = B_FREE;
2269 if (opts.options.mem_stomp)
2270 memset(p, 0xF3, PAGESIZE);
2271 while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS)
2274 pool.pagetable[pn] = B_FREE;
2277 if (opts.options.mem_stomp)
2280 memset(p, 0xF3, PAGESIZE);
2291 // Free complete pages, rebuild free list
2292 debug(COLLECT_PRINTF) printf("\tfree complete pages\n");
2293 size_t recoveredpages = 0;
2294 for (n = 0; n < pools.length; n++)
2297 for (size_t pn = 0; pn < pool.npages; pn++)
2299 Bins bin = cast(Bins)pool.pagetable[pn];
2305 size_t size = binsize[bin];
2306 size_t bit_stride = size / 16;
2307 size_t bit_base = pn * (PAGESIZE / 16);
2308 size_t bit_top = bit_base + (PAGESIZE / 16);
2312 for (; bit_i < bit_top; bit_i += bit_stride)
2314 if (!pool.freebits.test(bit_i))
2317 pool.pagetable[pn] = B_FREE;
2322 p = pool.baseAddr + pn * PAGESIZE;
2323 for (u = 0; u < PAGESIZE; u += size)
2325 bit_i = bit_base + u / 16;
2326 if (pool.freebits.test(bit_i))
2328 List *list = cast(List *)(p + u);
2329 // avoid unnecessary writes
2330 if (list.next != bucket[bin])
2331 list.next = bucket[bin];
2339 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages);
2340 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, pools.length);
2342 return freedpages + recoveredpages;
2349 uint getAttr(Pool* pool, size_t bit_i)
2358 if (pool.finals.nbits &&
2359 pool.finals.test(bit_i))
2360 attrs |= BlkAttr.FINALIZE;
2361 if (pool.noscan.test(bit_i))
2362 attrs |= BlkAttr.NO_SCAN;
2363 // if (pool.nomove.nbits &&
2364 // pool.nomove.test(bit_i))
2365 // attrs |= BlkAttr.NO_MOVE;
2373 void setAttr(Pool* pool, size_t bit_i, uint mask)
2380 if (mask & BlkAttr.FINALIZE)
2382 if (!pool.finals.nbits)
2383 pool.finals.alloc(pool.mark.nbits);
2384 pool.finals.set(bit_i);
2386 if (mask & BlkAttr.NO_SCAN)
2388 pool.noscan.set(bit_i);
2390 // if (mask & BlkAttr.NO_MOVE)
2392 // if (!pool.nomove.nbits)
2393 // pool.nomove.alloc(pool.mark.nbits);
2394 // pool.nomove.set(bit_i);
2402 void clrAttr(Pool* pool, size_t bit_i, uint mask)
2409 if (mask & BlkAttr.FINALIZE && pool.finals.nbits)
2410 pool.finals.clear(bit_i);
2411 if (mask & BlkAttr.NO_SCAN)
2412 pool.noscan.clear(bit_i);
2413 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits)
2414 // pool.nomove.clear(bit_i);
2420 /* ============================ Pool =============================== */
2427 GCBits mark; // entries already scanned, or should not be scanned
2428 GCBits scan; // entries that need to be scanned
2429 GCBits freebits; // entries that are on the free list
2430 GCBits finals; // entries that need finalizer run on them
2431 GCBits noscan; // entries that should not be scanned
2437 void initialize(size_t npages)
2439 size_t poolsize = npages * PAGESIZE;
2440 assert(poolsize >= POOLSIZE);
2441 baseAddr = cast(byte *) alloc.os_mem_map(poolsize);
2443 // Some of the code depends on page alignment of memory pools
2444 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0);
2452 topAddr = baseAddr + poolsize;
2454 mark.alloc(cast(size_t)poolsize / 16);
2455 scan.alloc(cast(size_t)poolsize / 16);
2456 freebits.alloc(cast(size_t)poolsize / 16);
2457 noscan.alloc(cast(size_t)poolsize / 16);
2459 pagetable = cast(ubyte*) cstdlib.malloc(npages);
2461 onOutOfMemoryError();
2462 memset(pagetable, B_FREE, npages);
2464 this.npages = npages;
2476 result = alloc.os_mem_unmap(baseAddr, npages * PAGESIZE);
2484 // See Gcx.Dtor() for the rationale of the null check.
2486 cstdlib.free(pagetable);
2496 void Invariant() { }
2503 //freebits.Invariant();
2504 //finals.Invariant();
2505 //noscan.Invariant();
2509 //if (baseAddr + npages * PAGESIZE != topAddr)
2510 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr);
2511 assert(baseAddr + npages * PAGESIZE == topAddr);
2514 for (size_t i = 0; i < npages; i++)
2516 Bins bin = cast(Bins)pagetable[i];
2517 assert(bin < B_MAX);
2523 * Allocate n pages from Pool.
2524 * Returns OPFAIL on failure.
2526 size_t allocPages(size_t n)
2532 for (i = 0; i < npages; i++)
2534 if (pagetable[i] == B_FREE)
2547 * Free npages pages starting with pagenum.
2549 void freePages(size_t pagenum, size_t npages)
2551 memset(&pagetable[pagenum], B_FREE, npages);
2556 * Used for sorting pools
2558 int opCmp(in Pool other)
2560 if (baseAddr < other.baseAddr)
2563 return cast(int)(baseAddr > other.baseAddr);
2568 /* ============================ SENTINEL =============================== */
2571 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
2572 const ubyte SENTINEL_POST = 0xF5; // 8 bits
2573 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
2576 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; }
2577 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; }
2578 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; }
2581 void sentinel_init(void *p, size_t size)
2583 *sentinel_size(p) = size;
2584 *sentinel_pre(p) = SENTINEL_PRE;
2585 *sentinel_post(p) = SENTINEL_POST;
2589 void sentinel_Invariant(void *p)
2591 assert(*sentinel_pre(p) == SENTINEL_PRE);
2592 assert(*sentinel_post(p) == SENTINEL_POST);
2596 void *sentinel_add(void *p)
2598 return p + 2 * size_t.sizeof;
2602 void *sentinel_sub(void *p)
2604 return p - 2 * size_t.sizeof;
2608 // vim: set et sw=4 sts=4 :