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;
51 import cstdlib = tango.stdc.stdlib;
52 import cstring = tango.stdc.string;
57 // BUG: The following import will likely not work, since the gcc
58 // subdirectory is elsewhere. Instead, perhaps the functions
59 // could be declared directly or some other resolution could
61 static import gcc.builtins; // for __builtin_unwind_int
76 FINALIZE = 0b0000_0001,
77 NO_SCAN = 0b0000_0010,
78 NO_MOVE = 0b0000_0100,
79 ALL_BITS = 0b1111_1111
82 extern (C) void* rt_stackBottom();
83 extern (C) void* rt_stackTop();
85 extern (C) void rt_finalize( void* p, bool det = true );
87 alias void delegate(Object) DEvent;
88 extern (C) void rt_attachDisposeEvent(Object h, DEvent e);
89 extern (C) bool rt_detachDisposeEvent(Object h, DEvent e);
92 alias void delegate( void*, void* ) scanFn;
94 extern (C) void rt_scanStaticData( scanFn scan );
96 extern (C) bool thread_needLock();
97 extern (C) void thread_suspendAll();
98 extern (C) void thread_resumeAll();
100 extern (C) void thread_scanAll( scanFn fn, void* curStackTop = null );
102 extern (C) void onOutOfMemoryError();
106 OPFAIL = ~cast(size_t)0
114 /* ============================ GC =============================== */
117 class GCLock { } // just a dummy so we can get a global lock
120 const uint GCVERSION = 1; // increment every time we change interface
125 // For passing to debug code
129 uint gcversion = GCVERSION;
131 Gcx *gcx; // implementation
132 static ClassInfo gcLock; // global lock
137 gcLock = GCLock.classinfo;
138 gcx = cast(Gcx*) cstdlib.calloc(1, Gcx.sizeof);
140 onOutOfMemoryError();
142 setStackBottom(rt_stackBottom());
162 if (!thread_needLock())
164 assert(gcx.disabled > 0);
167 else synchronized (gcLock)
169 assert(gcx.disabled > 0);
180 if (!thread_needLock())
184 else synchronized (gcLock)
194 uint getAttr(void* p)
203 Pool* pool = gcx.findPool(p);
208 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
210 oldb = gcx.getBits(pool, biti);
215 if (!thread_needLock())
219 else synchronized (gcLock)
229 uint setAttr(void* p, uint mask)
238 Pool* pool = gcx.findPool(p);
243 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
245 oldb = gcx.getBits(pool, biti);
246 gcx.setBits(pool, biti, mask);
251 if (!thread_needLock())
255 else synchronized (gcLock)
265 uint clrAttr(void* p, uint mask)
274 Pool* pool = gcx.findPool(p);
279 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
281 oldb = gcx.getBits(pool, biti);
282 gcx.clrBits(pool, biti, mask);
287 if (!thread_needLock())
291 else synchronized (gcLock)
301 void *malloc(size_t size, uint bits = 0)
308 if (!thread_needLock())
310 return mallocNoSync(size, bits);
312 else synchronized (gcLock)
314 return mallocNoSync(size, bits);
322 private void *mallocNoSync(size_t size, uint bits = 0)
331 size += SENTINEL_EXTRA;
334 // Cache previous binsize lookup - Dave Fladebo.
335 static size_t lastsize = -1;
337 if (size == lastsize)
341 bin = gcx.findBin(size);
351 if (!gcx.allocPage(bin) && !gcx.disabled) // try to find a new page
353 if (!thread_needLock())
355 /* Then we haven't locked it yet. Be sure
356 * and lock for a collection, since a finalizer
357 * may start a new thread.
359 synchronized (gcLock)
361 gcx.fullcollectshell();
364 else if (!gcx.fullcollectshell()) // collect to find a new page
369 if (!gcx.bucket[bin] && !gcx.allocPage(bin))
371 gcx.newPool(1); // allocate new pool to find a new page
372 int result = gcx.allocPage(bin);
374 onOutOfMemoryError();
379 // Return next item from free list
380 gcx.bucket[bin] = (cast(List*)p).next;
381 if( !(bits & BlkAttr.NO_SCAN) )
382 cstring.memset(p + size, 0, binsize[bin] - size);
383 debug (MEMSTOMP) cstring.memset(p, 0xF0, size);
387 p = gcx.bigAlloc(size);
389 onOutOfMemoryError();
391 size -= SENTINEL_EXTRA;
393 sentinel_init(p, size);
397 Pool *pool = gcx.findPool(p);
400 gcx.setBits(pool, cast(size_t)(p - pool.baseAddr) / 16, bits);
409 void *calloc(size_t size, uint bits = 0)
416 if (!thread_needLock())
418 return callocNoSync(size, bits);
420 else synchronized (gcLock)
422 return callocNoSync(size, bits);
430 private void *callocNoSync(size_t size, uint bits = 0)
434 void *p = mallocNoSync(size, bits);
435 cstring.memset(p, 0, size);
443 void *realloc(void *p, size_t size, uint bits = 0)
445 if (!thread_needLock())
447 return reallocNoSync(p, size, bits);
449 else synchronized (gcLock)
451 return reallocNoSync(p, size, bits);
459 private void *reallocNoSync(void *p, size_t size, uint bits = 0)
471 p = mallocNoSync(size, bits);
480 sentinel_Invariant(p);
481 psize = *sentinel_size(p);
486 Pool *pool = gcx.findPool(p);
490 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
494 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
495 gcx.setBits(pool, biti, bits);
499 bits = gcx.getBits(pool, biti);
503 p2 = mallocNoSync(size, bits);
506 cstring.memcpy(p2, p, size);
512 psize = gcx.findSize(p); // find allocated size
513 if (psize >= PAGESIZE && size >= PAGESIZE)
515 auto psz = psize / PAGESIZE;
516 auto newsz = (size + PAGESIZE - 1) / PAGESIZE;
520 auto pool = gcx.findPool(p);
521 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
526 synchronized (gcLock)
529 cstring.memset(p + size, 0xF2, psize - size);
530 pool.freePages(pagenum + newsz, psz - newsz);
534 else if (pagenum + newsz <= pool.npages)
536 // Attempt to expand in place
537 synchronized (gcLock)
539 for (size_t i = pagenum + psz; 1;)
541 if (i == pagenum + newsz)
544 cstring.memset(p + psize, 0xF0,
546 cstring.memset(pool.pagetable + pagenum +
547 psz, B_PAGEPLUS, newsz - psz);
550 if (i == pool.npages)
554 if (pool.pagetable[i] != B_FREE)
561 if (psize < size || // if new size is bigger
562 psize > size * 2) // or less than half
566 Pool *pool = gcx.findPool(p);
570 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
574 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
575 gcx.setBits(pool, biti, bits);
579 bits = gcx.getBits(pool, biti);
583 p2 = mallocNoSync(size, bits);
586 cstring.memcpy(p2, p, size);
596 * Attempt to in-place enlarge the memory block pointed to by p by at least
597 * minbytes beyond its current capacity, up to a maximum of maxsize. This
598 * does not attempt to move the memory block (like realloc() does).
601 * 0 if could not extend p,
602 * total size of entire memory block if successful.
604 size_t extend(void* p, size_t minsize, size_t maxsize)
606 if (!thread_needLock())
608 return extendNoSync(p, minsize, maxsize);
610 else synchronized (gcLock)
612 return extendNoSync(p, minsize, maxsize);
620 private size_t extendNoSync(void* p, size_t minsize, size_t maxsize)
623 assert( minsize <= maxsize );
631 auto psize = gcx.findSize(p); // find allocated size
632 if (psize < PAGESIZE)
633 return 0; // cannot extend buckets
635 auto psz = psize / PAGESIZE;
636 auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE;
637 auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE;
639 auto pool = gcx.findPool(p);
640 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
643 for (sz = 0; sz < maxsz; sz++)
645 auto i = pagenum + psz + sz;
646 if (i == pool.npages)
648 if (pool.pagetable[i] != B_FREE)
658 cstring.memset(p + psize, 0xF0, (psz + sz) * PAGESIZE - psize);
659 cstring.memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz);
662 return (psz + sz) * PAGESIZE;
669 size_t reserve(size_t size)
676 if (!thread_needLock())
678 return reserveNoSync(size);
680 else synchronized (gcLock)
682 return reserveNoSync(size);
690 private size_t reserveNoSync(size_t size)
695 return gcx.reserve(size);
709 if (!thread_needLock())
711 return freeNoSync(p);
713 else synchronized (gcLock)
715 return freeNoSync(p);
723 private void freeNoSync(void *p)
732 // Find which page it is in
733 pool = gcx.findPool(p);
734 if (!pool) // if not one of ours
736 sentinel_Invariant(p);
738 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
739 biti = cast(size_t)(p - pool.baseAddr) / 16;
740 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
742 bin = cast(Bins)pool.pagetable[pagenum];
743 if (bin == B_PAGE) // if large alloc
748 while (++n < pool.npages && pool.pagetable[n] == B_PAGEPLUS)
750 debug (MEMSTOMP) cstring.memset(p, 0xF2, npages * PAGESIZE);
751 pool.freePages(pagenum, npages);
756 List *list = cast(List*)p;
758 debug (MEMSTOMP) cstring.memset(p, 0xF2, binsize[bin]);
760 list.next = gcx.bucket[bin];
761 gcx.bucket[bin] = list;
767 * Determine the base address of the block containing p. If p is not a gc
768 * allocated pointer, return null.
770 void* addrOf(void *p)
777 if (!thread_needLock())
779 return addrOfNoSync(p);
781 else synchronized (gcLock)
783 return addrOfNoSync(p);
791 void* addrOfNoSync(void *p)
798 return gcx.findBase(p);
803 * Determine the allocated size of pointer p. If p is an interior pointer
804 * or not a gc allocated pointer, return 0.
806 size_t sizeOf(void *p)
813 if (!thread_needLock())
815 return sizeOfNoSync(p);
817 else synchronized (gcLock)
819 return sizeOfNoSync(p);
827 private size_t sizeOfNoSync(void *p)
834 size_t size = gcx.findSize(p);
836 // Check for interior pointer
838 // 1) size is a power of 2 for less than PAGESIZE values
839 // 2) base of memory pool is aligned on PAGESIZE boundary
840 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
842 return size ? size - SENTINEL_EXTRA : 0;
846 if (p == gcx.p_cache)
847 return gcx.size_cache;
849 size_t size = gcx.findSize(p);
851 // Check for interior pointer
853 // 1) size is a power of 2 for less than PAGESIZE values
854 // 2) base of memory pool is aligned on PAGESIZE boundary
855 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
860 gcx.size_cache = size;
869 * Determine the base address of the block containing p. If p is not a gc
870 * allocated pointer, return null.
872 BlkInfo query(void *p)
880 if (!thread_needLock())
882 return queryNoSync(p);
884 else synchronized (gcLock)
886 return queryNoSync(p);
894 BlkInfo queryNoSync(void *p)
898 return gcx.getInfo(p);
903 * Verify that pointer p:
904 * 1) belongs to this memory pool
905 * 2) points to the start of an allocated piece of memory
906 * 3) is not on a free list
915 if (!thread_needLock())
919 else synchronized (gcLock)
929 private void checkNoSync(void *p)
933 sentinel_Invariant(p);
942 pool = gcx.findPool(p);
944 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
945 bin = cast(Bins)pool.pagetable[pagenum];
946 assert(bin <= B_PAGE);
948 assert((cast(size_t)p & (size - 1)) == 0);
954 // Check that p is not on a free list
957 for (list = gcx.bucket[bin]; list; list = list.next)
959 assert(cast(void*)list != p);
970 private void setStackBottom(void *p)
972 version (STACKGROWSDOWN)
974 //p = (void *)((uint *)p + 4);
975 if (p > gcx.stackBottom)
982 //p = (void *)((uint *)p - 4);
983 if (p < gcx.stackBottom)
985 gcx.stackBottom = cast(char*)p;
992 * add p to list of roots
994 void addRoot(void *p)
1001 if (!thread_needLock())
1005 else synchronized (gcLock)
1013 * remove p from list of roots
1015 void removeRoot(void *p)
1022 if (!thread_needLock())
1026 else synchronized (gcLock)
1034 * add range to scan for roots
1036 void addRange(void *p, size_t sz)
1043 if (!thread_needLock())
1045 gcx.addRange(p, p + sz);
1047 else synchronized (gcLock)
1049 gcx.addRange(p, p + sz);
1057 void removeRange(void *p)
1064 if (!thread_needLock())
1068 else synchronized (gcLock)
1076 * do full garbage collection
1081 if (!thread_needLock())
1083 gcx.fullcollectshell();
1085 else synchronized (gcLock)
1087 gcx.fullcollectshell();
1100 * do full garbage collection ignoring roots
1102 void fullCollectNoStack()
1104 if (!thread_needLock())
1107 gcx.fullcollectshell();
1110 else synchronized (gcLock)
1113 gcx.fullcollectshell();
1120 * minimize free space usage
1124 if (!thread_needLock())
1128 else synchronized (gcLock)
1136 * Retrieve statistics about garbage collection.
1137 * Useful for debugging and tuning.
1139 void getStats(out GCStats stats)
1141 if (!thread_needLock())
1143 getStatsNoSync(stats);
1145 else synchronized (gcLock)
1147 getStatsNoSync(stats);
1155 private void getStatsNoSync(out GCStats stats)
1164 cstring.memset(&stats, 0, GCStats.sizeof);
1166 for (n = 0; n < gcx.npools; n++)
1168 Pool *pool = gcx.pooltable[n];
1169 psize += pool.npages * PAGESIZE;
1170 for (size_t j = 0; j < pool.npages; j++)
1172 Bins bin = cast(Bins)pool.pagetable[j];
1175 else if (bin == B_PAGE)
1177 else if (bin < B_PAGE)
1182 for (n = 0; n < B_PAGE; n++)
1184 for (List *list = gcx.bucket[n]; list; list = list.next)
1185 flsize += binsize[n];
1188 usize = bsize - flsize;
1190 stats.poolsize = psize;
1191 stats.usedsize = bsize - flsize;
1192 stats.freelistsize = flsize;
1195 /******************* weak-reference support *********************/
1197 // call locked if necessary
1198 private T locked(T)(in T delegate() code)
1200 if (thread_needLock)
1201 synchronized(gcLock) return code();
1206 private struct WeakPointer
1210 void ondestroy(Object r)
1212 assert(r is reference);
1213 // lock for memory consistency (parallel readers)
1214 // also ensures that weakpointerDestroy can be called while another
1215 // thread is freeing the reference with "delete"
1216 locked!(void)({ reference = null; });
1221 * Create a weak pointer to the given object.
1222 * Returns a pointer to an opaque struct allocated in C memory.
1224 void* weakpointerCreate( Object r )
1228 // must be allocated in C memory
1229 // 1. to hide the reference from the GC
1230 // 2. the GC doesn't scan delegates added by rt_attachDisposeEvent
1232 auto wp = cast(WeakPointer*)(cstdlib.malloc(WeakPointer.sizeof));
1234 onOutOfMemoryError();
1236 rt_attachDisposeEvent(r, &wp.ondestroy);
1243 * Destroy a weak pointer returned by weakpointerCreate().
1244 * If null is passed, nothing happens.
1246 void weakpointerDestroy( void* p )
1250 auto wp = cast(WeakPointer*)p;
1251 // must be extra careful about the GC or parallel threads
1252 // finalizing the reference at the same time
1255 rt_detachDisposeEvent(wp.reference, &wp.ondestroy);
1262 * Query a weak pointer and return either the object passed to
1263 * weakpointerCreate, or null if it was free'd in the meantime.
1264 * If null is passed, null is returned.
1266 Object weakpointerGet( void* p )
1270 // NOTE: could avoid the lock by using Fawzi style GC counters but
1271 // that'd require core.sync.Atomic and lots of care about memory
1272 // consistency it's an optional optimization see
1273 // http://dsource.org/projects/tango/browser/trunk/user/tango/core/Lifetime.d?rev=5100#L158
1274 return locked!(Object)({
1275 return (cast(WeakPointer*)p).reference;
1282 /* ============================ Gcx =============================== */
1287 POOLSIZE = (4096*256),
1301 B_PAGE, // start of large alloc
1302 B_PAGEPLUS, // continuation of large alloc
1303 B_FREE, // free page
1324 const uint binsize[B_MAX] = [ 16,32,64,128,256,512,1024,2048,4096 ];
1325 const uint notbinsize[B_MAX] = [ ~(16u-1),~(32u-1),~(64u-1),~(128u-1),~(256u-1),
1326 ~(512u-1),~(1024u-1),~(2048u-1),~(4096u-1) ];
1328 /* ============================ 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);
1375 for (size_t i = 0; i < npools; i++)
1377 Pool *pool = pooltable[i];
1382 // Even when free() can be called with a null pointer, the extra call
1383 // might be significant. On hard GC benchmarks making the test for null
1384 // here (i.e. not making the call) can reduce the GC time by almost
1387 cstdlib.free(pooltable);
1389 cstdlib.free(roots);
1391 cstdlib.free(ranges);
1395 void Invariant() { }
1402 //printf("Gcx.invariant(): this = %p\n", this);
1405 for (i = 0; i < npools; i++)
1407 Pool *pool = pooltable[i];
1411 assert(minAddr == pool.baseAddr);
1415 assert(pool.opCmp(pooltable[i + 1]) < 0);
1417 else if (i + 1 == npools)
1419 assert(maxAddr == pool.topAddr);
1425 assert(rootdim != 0);
1426 assert(nroots <= rootdim);
1431 assert(rangedim != 0);
1432 assert(nranges <= rangedim);
1434 for (i = 0; i < nranges; i++)
1436 assert(ranges[i].pbot);
1437 assert(ranges[i].ptop);
1438 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)
1455 void addRoot(void *p)
1457 if (nroots == rootdim)
1459 size_t newdim = rootdim * 2 + 16;
1462 newroots = cast(void**) cstdlib.malloc(newdim * newroots[0].sizeof);
1464 onOutOfMemoryError();
1467 cstring.memcpy(newroots, roots, nroots * newroots[0].sizeof);
1468 cstdlib.free(roots);
1481 void removeRoot(void *p)
1483 for (size_t i = nroots; i--;)
1488 cstring.memmove(roots + i, roots + i + 1,
1489 (nroots - i) * roots[0].sizeof);
1500 void addRange(void *pbot, void *ptop)
1502 if (nranges == rangedim)
1504 size_t newdim = rangedim * 2 + 16;
1507 newranges = cast(Range*) cstdlib.malloc(newdim * Range.sizeof);
1509 onOutOfMemoryError();
1512 cstring.memcpy(newranges, ranges, nranges * Range.sizeof);
1513 cstdlib.free(ranges);
1518 ranges[nranges].pbot = pbot;
1519 ranges[nranges].ptop = ptop;
1527 void removeRange(void *pbot)
1529 for (size_t i = nranges; i--;)
1531 if (ranges[i].pbot == pbot)
1534 cstring.memmove(ranges + i, ranges + i + 1,
1535 (nranges - i) * ranges[0].sizeof);
1540 // This is a fatal error, but ignore it.
1541 // The problem is that we can get a Close() call on a thread
1542 // other than the one the range was allocated on.
1548 * Find Pool that pointer is in.
1549 * Return null if not in a Pool.
1550 * Assume pooltable[] is sorted.
1552 Pool *findPool(void *p)
1554 if (p >= minAddr && p < maxAddr)
1558 return pooltable[0];
1561 for (size_t i = 0; i < npools; i++)
1565 pool = pooltable[i];
1566 if (p < pool.topAddr)
1568 if (pool.baseAddr <= p)
1579 * Find base address of block containing pointer p.
1580 * Returns null if not a gc'd pointer
1582 void* findBase(void *p)
1589 size_t offset = cast(size_t)(p - pool.baseAddr);
1590 size_t pn = offset / PAGESIZE;
1591 Bins bin = cast(Bins)pool.pagetable[pn];
1593 // Adjust bit to be at start of allocated memory block
1596 return pool.baseAddr + (offset & notbinsize[bin]);
1598 else if (bin == B_PAGEPLUS)
1602 --pn, offset -= PAGESIZE;
1603 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1605 return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1609 // we are in a B_FREE page
1618 * Find size of pointer p.
1619 * Returns 0 if not a gc'd pointer
1621 size_t findSize(void *p)
1632 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
1633 bin = cast(Bins)pool.pagetable[pagenum];
1634 size = binsize[bin];
1640 pt = &pool.pagetable[0];
1641 for (i = pagenum + 1; i < pool.npages; i++)
1643 if (pt[i] != B_PAGEPLUS)
1646 size = (i - pagenum) * PAGESIZE;
1656 BlkInfo getInfo(void* p)
1664 size_t offset = cast(size_t)(p - pool.baseAddr);
1665 size_t pn = offset / PAGESIZE;
1666 Bins bin = cast(Bins)pool.pagetable[pn];
1668 ////////////////////////////////////////////////////////////////////
1670 ////////////////////////////////////////////////////////////////////
1674 info.base = pool.baseAddr + (offset & notbinsize[bin]);
1676 else if (bin == B_PAGEPLUS)
1680 --pn, offset -= PAGESIZE;
1682 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1684 info.base = pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1686 // fix bin for use by size calc below
1687 bin = cast(Bins)pool.pagetable[pn];
1690 ////////////////////////////////////////////////////////////////////
1692 ////////////////////////////////////////////////////////////////////
1694 info.size = binsize[bin];
1700 pt = &pool.pagetable[0];
1701 for (i = pn + 1; i < pool.npages; i++)
1703 if (pt[i] != B_PAGEPLUS)
1706 info.size = (i - pn) * PAGESIZE;
1709 ////////////////////////////////////////////////////////////////////
1711 ////////////////////////////////////////////////////////////////////
1713 info.attr = getBits(pool, cast(size_t)(offset / 16));
1720 * Compute bin for size.
1722 static Bins findBin(size_t size)
1731 else if (size <= 32)
1766 * Allocate a new pool of at least size bytes.
1767 * Sort it into pooltable[].
1768 * Mark all memory in the pool as B_FREE.
1769 * Return the actual number of bytes reserved or 0 on error.
1771 size_t reserve(size_t size)
1773 size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
1774 Pool* pool = newPool(npages);
1778 return pool.npages * PAGESIZE;
1783 * Minimizes physical memory usage by returning free pools to the OS.
1791 for (n = 0; n < npools; n++)
1793 pool = pooltable[n];
1794 for (pn = 0; pn < pool.npages; pn++)
1796 if (cast(Bins)pool.pagetable[pn] != B_FREE)
1799 if (pn < pool.npages)
1806 cstring.memmove(pooltable + n,
1808 (--npools - n) * (Pool*).sizeof);
1809 minAddr = pooltable[0].baseAddr;
1810 maxAddr = pooltable[npools - 1].topAddr;
1816 * Allocate a chunk of memory that is larger than a page.
1817 * Return null if out of memory.
1819 void *bigAlloc(size_t size)
1829 npages = (size + PAGESIZE - 1) / PAGESIZE;
1833 // This code could use some refinement when repeatedly
1834 // allocating very large arrays.
1836 for (n = 0; n < npools; n++)
1838 pool = pooltable[n];
1839 pn = pool.allocPages(npages);
1854 freedpages = fullcollectshell();
1855 if (freedpages >= npools * ((POOLSIZE / PAGESIZE) / 4))
1860 // Release empty pools to prevent bloat
1862 // Allocate new pool
1863 pool = newPool(npages);
1869 pn = pool.allocPages(npages);
1870 assert(pn != OPFAIL);
1873 // Release empty pools to prevent bloat
1875 // Allocate new pool
1876 pool = newPool(npages);
1879 pn = pool.allocPages(npages);
1880 assert(pn != OPFAIL);
1890 pool.pagetable[pn] = B_PAGE;
1892 cstring.memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1);
1893 p = pool.baseAddr + pn * PAGESIZE;
1894 cstring.memset(cast(char *)p + size, 0, npages * PAGESIZE - size);
1895 debug (MEMSTOMP) cstring.memset(p, 0xF1, size);
1899 return null; // let mallocNoSync handle the error
1904 * Allocate a new pool with at least npages in it.
1905 * Sort it into pooltable[].
1906 * Return null if failed.
1908 Pool *newPool(size_t npages)
1911 Pool** newpooltable;
1915 // Minimum of POOLSIZE
1916 if (npages < POOLSIZE/PAGESIZE)
1917 npages = POOLSIZE/PAGESIZE;
1918 else if (npages > POOLSIZE/PAGESIZE)
1920 // Give us 150% of requested size, so there's room to extend
1921 auto n = npages + (npages >> 1);
1922 if (n < size_t.max/PAGESIZE)
1926 // Allocate successively larger pools up to 8 megs
1931 n = 8; // cap pool size at 8 megs
1932 n *= (POOLSIZE / PAGESIZE);
1937 pool = cast(Pool *) cstdlib.calloc(1, Pool.sizeof);
1940 pool.initialize(npages);
1944 newnpools = npools + 1;
1945 newpooltable = cast(Pool **) cstdlib.realloc(pooltable,
1946 newnpools * (Pool *).sizeof);
1950 // Sort pool into newpooltable[]
1951 for (i = 0; i < npools; i++)
1953 if (pool.opCmp(newpooltable[i]) < 0)
1956 cstring.memmove(newpooltable + i + 1, newpooltable + i,
1957 (npools - i) * (Pool *).sizeof);
1958 newpooltable[i] = pool;
1960 pooltable = newpooltable;
1963 minAddr = pooltable[0].baseAddr;
1964 maxAddr = pooltable[npools - 1].topAddr;
1976 * Allocate a page of bin's.
1980 int allocPage(Bins bin)
1988 for (n = 0; n < npools; n++)
1990 pool = pooltable[n];
1991 pn = pool.allocPages(1);
1998 pool.pagetable[pn] = cast(ubyte)bin;
2000 // Convert page to free list
2001 size_t size = binsize[bin];
2002 List **b = &bucket[bin];
2004 p = pool.baseAddr + pn * PAGESIZE;
2005 ptop = p + PAGESIZE;
2006 for (; p < ptop; p += size)
2008 (cast(List *)p).next = *b;
2016 * Search a range of memory values and mark any pointers into the GC pool.
2018 void mark(void *pbot, void *ptop)
2020 void **p1 = cast(void **)pbot;
2021 void **p2 = cast(void **)ptop;
2025 //printf("marking range: %p -> %p\n", pbot, ptop);
2026 for (; p1 < p2; p1++)
2029 byte *p = cast(byte *)(*p1);
2031 if (p >= minAddr && p < maxAddr)
2033 if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
2039 size_t offset = cast(size_t)(p - pool.baseAddr);
2041 size_t pn = offset / PAGESIZE;
2042 Bins bin = cast(Bins)pool.pagetable[pn];
2044 // Adjust bit to be at start of allocated memory block
2046 biti = (offset & notbinsize[bin]) >> 4;
2047 else if (bin == B_PAGEPLUS)
2053 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
2054 biti = pn * (PAGESIZE / 16);
2058 // Don't mark bits in B_FREE pages
2062 if (bin >= B_PAGE) // Cache B_PAGE and B_PAGEPLUS lookups
2063 pcache = cast(size_t)p & ~(PAGESIZE-1);
2065 if (!pool.mark.test(biti))
2067 pool.mark.set(biti);
2068 if (!pool.noscan.test(biti))
2070 pool.scan.set(biti);
2077 anychanges |= changes;
2082 * Return number of full pages free'd.
2084 size_t fullcollectshell()
2086 // The purpose of the 'shell' is to ensure all the registers
2087 // get put on the stack so they'll be scanned
2092 gcc.builtins.__builtin_unwind_init();
2099 uint eax,ecx,edx,ebx,ebp,esi,edi;
2112 else version (X86_64)
2114 ulong rax,rbx,rcx,rdx,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15;
2117 movq rax[RBP], RAX ;
2118 movq rbx[RBP], RBX ;
2119 movq rcx[RBP], RCX ;
2120 movq rdx[RBP], RDX ;
2121 movq rbp[RBP], RBP ;
2122 movq rsi[RBP], RSI ;
2123 movq rdi[RBP], RDI ;
2126 movq r10[RBP], R10 ;
2127 movq r11[RBP], R11 ;
2128 movq r12[RBP], R12 ;
2129 movq r13[RBP], R13 ;
2130 movq r14[RBP], R14 ;
2131 movq r15[RBP], R15 ;
2137 static assert( false, "Architecture not supported." );
2148 result = fullcollect(sp);
2171 size_t fullcollect(void *stackTop)
2176 debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n");
2178 thread_suspendAll();
2184 for (n = 0; n < npools; n++)
2186 pool = pooltable[n];
2189 pool.freebits.zero();
2192 // Mark each free entry, so it doesn't get scanned
2193 for (n = 0; n < B_PAGE; n++)
2195 for (List *list = bucket[n]; list; list = list.next)
2197 pool = findPool(list);
2199 pool.freebits.set(cast(size_t)(cast(byte*)list - pool.baseAddr) / 16);
2203 for (n = 0; n < npools; n++)
2205 pool = pooltable[n];
2206 pool.mark.copy(&pool.freebits);
2209 rt_scanStaticData( &mark );
2213 // Scan stacks and registers for each paused thread
2214 thread_scanAll( &mark, stackTop );
2218 debug(COLLECT_PRINTF) printf("scan roots[]\n");
2219 mark(roots, roots + nroots);
2222 debug(COLLECT_PRINTF) printf("scan ranges[]\n");
2224 for (n = 0; n < nranges; n++)
2226 debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop);
2227 mark(ranges[n].pbot, ranges[n].ptop);
2231 debug(COLLECT_PRINTF) printf("\tscan heap\n");
2235 for (n = 0; n < npools; n++)
2241 pool = pooltable[n];
2243 bbase = pool.scan.base();
2244 btop = bbase + pool.scan.nwords;
2245 for (b = bbase; b < btop;)
2261 o = pool.baseAddr + (b - bbase) * 32 * 16;
2262 if (!(bitm & 0xFFFF))
2267 for (; bitm; o += 16, bitm >>= 1)
2272 pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE;
2273 bin = cast(Bins)pool.pagetable[pn];
2276 mark(o, o + binsize[bin]);
2278 else if (bin == B_PAGE || bin == B_PAGEPLUS)
2280 if (bin == B_PAGEPLUS)
2282 while (pool.pagetable[pn - 1] != B_PAGE)
2286 while (pn + u < pool.npages && pool.pagetable[pn + u] == B_PAGEPLUS)
2288 mark(o, o + u * PAGESIZE);
2297 // Free up everything not marked
2298 debug(COLLECT_PRINTF) printf("\tfree'ing\n");
2299 size_t freedpages = 0;
2301 for (n = 0; n < npools; n++)
2303 pool = pooltable[n];
2304 uint* bbase = pool.mark.base();
2306 for (pn = 0; pn < pool.npages; pn++, bbase += PAGESIZE / (32 * 16))
2308 Bins bin = cast(Bins)pool.pagetable[pn];
2312 auto size = binsize[bin];
2313 byte* p = pool.baseAddr + pn * PAGESIZE;
2314 byte* ptop = p + PAGESIZE;
2315 size_t biti = pn * (PAGESIZE/16);
2316 size_t bitstride = size / 16;
2318 version(none) // BUG: doesn't work because freebits() must also be cleared
2320 // If free'd entire page
2321 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 && bbase[3] == 0 &&
2322 bbase[4] == 0 && bbase[5] == 0 && bbase[6] == 0 && bbase[7] == 0)
2324 for (; p < ptop; p += size, biti += bitstride)
2326 if (pool.finals.nbits && pool.finals.testClear(biti))
2327 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2328 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
2330 List *list = cast(List *)p;
2332 debug (MEMSTOMP) cstring.memset(p, 0xF3, size);
2334 pool.pagetable[pn] = B_FREE;
2339 for (; p < ptop; p += size, biti += bitstride)
2341 if (!pool.mark.test(biti))
2343 sentinel_Invariant(sentinel_add(p));
2345 pool.freebits.set(biti);
2346 if (pool.finals.nbits && pool.finals.testClear(biti))
2347 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2348 clrBits(pool, biti, BlkAttr.ALL_BITS);
2350 List *list = cast(List *)p;
2352 debug (MEMSTOMP) cstring.memset(p, 0xF3, size);
2358 else if (bin == B_PAGE)
2360 size_t biti = pn * (PAGESIZE / 16);
2361 if (!pool.mark.test(biti))
2363 byte *p = pool.baseAddr + pn * PAGESIZE;
2364 sentinel_Invariant(sentinel_add(p));
2365 if (pool.finals.nbits && pool.finals.testClear(biti))
2366 rt_finalize(sentinel_add(p), false/*noStack > 0*/);
2367 clrBits(pool, biti, BlkAttr.ALL_BITS);
2369 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p);
2370 pool.pagetable[pn] = B_FREE;
2372 debug (MEMSTOMP) cstring.memset(p, 0xF3, PAGESIZE);
2373 while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS)
2376 pool.pagetable[pn] = B_FREE;
2382 cstring.memset(p, 0xF3, PAGESIZE);
2393 // Free complete pages, rebuild free list
2394 debug(COLLECT_PRINTF) printf("\tfree complete pages\n");
2395 size_t recoveredpages = 0;
2396 for (n = 0; n < npools; n++)
2398 pool = pooltable[n];
2399 for (size_t pn = 0; pn < pool.npages; pn++)
2401 Bins bin = cast(Bins)pool.pagetable[pn];
2407 size_t size = binsize[bin];
2408 size_t bitstride = size / 16;
2409 size_t bitbase = pn * (PAGESIZE / 16);
2410 size_t bittop = bitbase + (PAGESIZE / 16);
2414 for (biti = bitbase; biti < bittop; biti += bitstride)
2416 if (!pool.freebits.test(biti))
2419 pool.pagetable[pn] = B_FREE;
2424 p = pool.baseAddr + pn * PAGESIZE;
2425 for (u = 0; u < PAGESIZE; u += size)
2427 biti = bitbase + u / 16;
2428 if (pool.freebits.test(biti))
2430 List *list = cast(List *)(p + u);
2431 if (list.next != bucket[bin]) // avoid unnecessary writes
2432 list.next = bucket[bin];
2440 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages);
2441 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, npools);
2443 return freedpages + recoveredpages;
2450 uint getBits(Pool* pool, size_t biti)
2459 if (pool.finals.nbits &&
2460 pool.finals.test(biti))
2461 bits |= BlkAttr.FINALIZE;
2462 if (pool.noscan.test(biti))
2463 bits |= BlkAttr.NO_SCAN;
2464 // if (pool.nomove.nbits &&
2465 // pool.nomove.test(biti))
2466 // bits |= BlkAttr.NO_MOVE;
2474 void setBits(Pool* pool, size_t biti, uint mask)
2481 if (mask & BlkAttr.FINALIZE)
2483 if (!pool.finals.nbits)
2484 pool.finals.alloc(pool.mark.nbits);
2485 pool.finals.set(biti);
2487 if (mask & BlkAttr.NO_SCAN)
2489 pool.noscan.set(biti);
2491 // if (mask & BlkAttr.NO_MOVE)
2493 // if (!pool.nomove.nbits)
2494 // pool.nomove.alloc(pool.mark.nbits);
2495 // pool.nomove.set(biti);
2503 void clrBits(Pool* pool, size_t biti, uint mask)
2510 if (mask & BlkAttr.FINALIZE && pool.finals.nbits)
2511 pool.finals.clear(biti);
2512 if (mask & BlkAttr.NO_SCAN)
2513 pool.noscan.clear(biti);
2514 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits)
2515 // pool.nomove.clear(biti);
2521 /* ============================ Pool =============================== */
2528 GCBits mark; // entries already scanned, or should not be scanned
2529 GCBits scan; // entries that need to be scanned
2530 GCBits freebits; // entries that are on the free list
2531 GCBits finals; // entries that need finalizer run on them
2532 GCBits noscan; // entries that should not be scanned
2538 void initialize(size_t npages)
2540 size_t poolsize = npages * PAGESIZE;
2541 assert(poolsize >= POOLSIZE);
2542 baseAddr = cast(byte *) alloc.os_mem_map(poolsize);
2544 // Some of the code depends on page alignment of memory pools
2545 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0);
2553 topAddr = baseAddr + poolsize;
2555 mark.alloc(cast(size_t)poolsize / 16);
2556 scan.alloc(cast(size_t)poolsize / 16);
2557 freebits.alloc(cast(size_t)poolsize / 16);
2558 noscan.alloc(cast(size_t)poolsize / 16);
2560 pagetable = cast(ubyte*) cstdlib.malloc(npages);
2562 onOutOfMemoryError();
2563 cstring.memset(pagetable, B_FREE, npages);
2565 this.npages = npages;
2577 result = alloc.os_mem_unmap(baseAddr, npages * PAGESIZE);
2585 // See Gcx.Dtor() for the rationale of the null check.
2587 cstdlib.free(pagetable);
2597 void Invariant() { }
2604 //freebits.Invariant();
2605 //finals.Invariant();
2606 //noscan.Invariant();
2610 //if (baseAddr + npages * PAGESIZE != topAddr)
2611 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr);
2612 assert(baseAddr + npages * PAGESIZE == topAddr);
2615 for (size_t i = 0; i < npages; i++)
2617 Bins bin = cast(Bins)pagetable[i];
2618 assert(bin < B_MAX);
2624 * Allocate n pages from Pool.
2625 * Returns OPFAIL on failure.
2627 size_t allocPages(size_t n)
2633 for (i = 0; i < npages; i++)
2635 if (pagetable[i] == B_FREE)
2648 * Free npages pages starting with pagenum.
2650 void freePages(size_t pagenum, size_t npages)
2652 cstring.memset(&pagetable[pagenum], B_FREE, npages);
2657 * Used for sorting pooltable[]
2661 if (baseAddr < p2.baseAddr)
2664 return cast(int)(baseAddr > p2.baseAddr);
2669 /* ============================ SENTINEL =============================== */
2674 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
2675 const ubyte SENTINEL_POST = 0xF5; // 8 bits
2676 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
2679 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; }
2680 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; }
2681 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; }
2684 void sentinel_init(void *p, size_t size)
2686 *sentinel_size(p) = size;
2687 *sentinel_pre(p) = SENTINEL_PRE;
2688 *sentinel_post(p) = SENTINEL_POST;
2692 void sentinel_Invariant(void *p)
2694 assert(*sentinel_pre(p) == SENTINEL_PRE);
2695 assert(*sentinel_post(p) == SENTINEL_POST);
2699 void *sentinel_add(void *p)
2701 return p + 2 * size_t.sizeof;
2705 void *sentinel_sub(void *p)
2707 return p - 2 * size_t.sizeof;
2712 const uint SENTINEL_EXTRA = 0;
2715 void sentinel_init(void *p, size_t size)
2720 void sentinel_Invariant(void *p)
2725 void *sentinel_add(void *p)
2731 void *sentinel_sub(void *p)
2738 // vim: set et sw=4 sts=4 :