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 cstdlib.free(pooltable);
1385 cstdlib.free(roots);
1388 cstdlib.free(ranges);
1392 void Invariant() { }
1399 //printf("Gcx.invariant(): this = %p\n", this);
1402 for (i = 0; i < npools; i++)
1404 Pool *pool = pooltable[i];
1408 assert(minAddr == pool.baseAddr);
1412 assert(pool.opCmp(pooltable[i + 1]) < 0);
1414 else if (i + 1 == npools)
1416 assert(maxAddr == pool.topAddr);
1422 assert(rootdim != 0);
1423 assert(nroots <= rootdim);
1428 assert(rangedim != 0);
1429 assert(nranges <= rangedim);
1431 for (i = 0; i < nranges; i++)
1433 assert(ranges[i].pbot);
1434 assert(ranges[i].ptop);
1435 assert(ranges[i].pbot <= ranges[i].ptop);
1439 for (i = 0; i < B_PAGE; i++)
1441 for (List *list = bucket[i]; list; list = list.next)
1452 void addRoot(void *p)
1454 if (nroots == rootdim)
1456 size_t newdim = rootdim * 2 + 16;
1459 newroots = cast(void**) cstdlib.malloc(newdim * newroots[0].sizeof);
1461 onOutOfMemoryError();
1464 cstring.memcpy(newroots, roots, nroots * newroots[0].sizeof);
1465 cstdlib.free(roots);
1478 void removeRoot(void *p)
1480 for (size_t i = nroots; i--;)
1485 cstring.memmove(roots + i, roots + i + 1,
1486 (nroots - i) * roots[0].sizeof);
1497 void addRange(void *pbot, void *ptop)
1499 if (nranges == rangedim)
1501 size_t newdim = rangedim * 2 + 16;
1504 newranges = cast(Range*) cstdlib.malloc(newdim * Range.sizeof);
1506 onOutOfMemoryError();
1509 cstring.memcpy(newranges, ranges, nranges * Range.sizeof);
1510 cstdlib.free(ranges);
1515 ranges[nranges].pbot = pbot;
1516 ranges[nranges].ptop = ptop;
1524 void removeRange(void *pbot)
1526 for (size_t i = nranges; i--;)
1528 if (ranges[i].pbot == pbot)
1531 cstring.memmove(ranges + i, ranges + i + 1,
1532 (nranges - i) * ranges[0].sizeof);
1537 // This is a fatal error, but ignore it.
1538 // The problem is that we can get a Close() call on a thread
1539 // other than the one the range was allocated on.
1545 * Find Pool that pointer is in.
1546 * Return null if not in a Pool.
1547 * Assume pooltable[] is sorted.
1549 Pool *findPool(void *p)
1551 if (p >= minAddr && p < maxAddr)
1555 return pooltable[0];
1558 for (size_t i = 0; i < npools; i++)
1562 pool = pooltable[i];
1563 if (p < pool.topAddr)
1565 if (pool.baseAddr <= p)
1576 * Find base address of block containing pointer p.
1577 * Returns null if not a gc'd pointer
1579 void* findBase(void *p)
1586 size_t offset = cast(size_t)(p - pool.baseAddr);
1587 size_t pn = offset / PAGESIZE;
1588 Bins bin = cast(Bins)pool.pagetable[pn];
1590 // Adjust bit to be at start of allocated memory block
1593 return pool.baseAddr + (offset & notbinsize[bin]);
1595 else if (bin == B_PAGEPLUS)
1599 --pn, offset -= PAGESIZE;
1600 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1602 return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1606 // we are in a B_FREE page
1615 * Find size of pointer p.
1616 * Returns 0 if not a gc'd pointer
1618 size_t findSize(void *p)
1629 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
1630 bin = cast(Bins)pool.pagetable[pagenum];
1631 size = binsize[bin];
1637 pt = &pool.pagetable[0];
1638 for (i = pagenum + 1; i < pool.npages; i++)
1640 if (pt[i] != B_PAGEPLUS)
1643 size = (i - pagenum) * PAGESIZE;
1653 BlkInfo getInfo(void* p)
1661 size_t offset = cast(size_t)(p - pool.baseAddr);
1662 size_t pn = offset / PAGESIZE;
1663 Bins bin = cast(Bins)pool.pagetable[pn];
1665 ////////////////////////////////////////////////////////////////////
1667 ////////////////////////////////////////////////////////////////////
1671 info.base = pool.baseAddr + (offset & notbinsize[bin]);
1673 else if (bin == B_PAGEPLUS)
1677 --pn, offset -= PAGESIZE;
1679 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
1681 info.base = pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
1683 // fix bin for use by size calc below
1684 bin = cast(Bins)pool.pagetable[pn];
1687 ////////////////////////////////////////////////////////////////////
1689 ////////////////////////////////////////////////////////////////////
1691 info.size = binsize[bin];
1697 pt = &pool.pagetable[0];
1698 for (i = pn + 1; i < pool.npages; i++)
1700 if (pt[i] != B_PAGEPLUS)
1703 info.size = (i - pn) * PAGESIZE;
1706 ////////////////////////////////////////////////////////////////////
1708 ////////////////////////////////////////////////////////////////////
1710 info.attr = getBits(pool, cast(size_t)(offset / 16));
1717 * Compute bin for size.
1719 static Bins findBin(size_t size)
1728 else if (size <= 32)
1763 * Allocate a new pool of at least size bytes.
1764 * Sort it into pooltable[].
1765 * Mark all memory in the pool as B_FREE.
1766 * Return the actual number of bytes reserved or 0 on error.
1768 size_t reserve(size_t size)
1770 size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
1771 Pool* pool = newPool(npages);
1775 return pool.npages * PAGESIZE;
1780 * Minimizes physical memory usage by returning free pools to the OS.
1788 for (n = 0; n < npools; n++)
1790 pool = pooltable[n];
1791 for (pn = 0; pn < pool.npages; pn++)
1793 if (cast(Bins)pool.pagetable[pn] != B_FREE)
1796 if (pn < pool.npages)
1803 cstring.memmove(pooltable + n,
1805 (--npools - n) * (Pool*).sizeof);
1806 minAddr = pooltable[0].baseAddr;
1807 maxAddr = pooltable[npools - 1].topAddr;
1813 * Allocate a chunk of memory that is larger than a page.
1814 * Return null if out of memory.
1816 void *bigAlloc(size_t size)
1826 npages = (size + PAGESIZE - 1) / PAGESIZE;
1830 // This code could use some refinement when repeatedly
1831 // allocating very large arrays.
1833 for (n = 0; n < npools; n++)
1835 pool = pooltable[n];
1836 pn = pool.allocPages(npages);
1851 freedpages = fullcollectshell();
1852 if (freedpages >= npools * ((POOLSIZE / PAGESIZE) / 4))
1857 // Release empty pools to prevent bloat
1859 // Allocate new pool
1860 pool = newPool(npages);
1866 pn = pool.allocPages(npages);
1867 assert(pn != OPFAIL);
1870 // Release empty pools to prevent bloat
1872 // Allocate new pool
1873 pool = newPool(npages);
1876 pn = pool.allocPages(npages);
1877 assert(pn != OPFAIL);
1887 pool.pagetable[pn] = B_PAGE;
1889 cstring.memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1);
1890 p = pool.baseAddr + pn * PAGESIZE;
1891 cstring.memset(cast(char *)p + size, 0, npages * PAGESIZE - size);
1892 debug (MEMSTOMP) cstring.memset(p, 0xF1, size);
1896 return null; // let mallocNoSync handle the error
1901 * Allocate a new pool with at least npages in it.
1902 * Sort it into pooltable[].
1903 * Return null if failed.
1905 Pool *newPool(size_t npages)
1908 Pool** newpooltable;
1912 // Minimum of POOLSIZE
1913 if (npages < POOLSIZE/PAGESIZE)
1914 npages = POOLSIZE/PAGESIZE;
1915 else if (npages > POOLSIZE/PAGESIZE)
1917 // Give us 150% of requested size, so there's room to extend
1918 auto n = npages + (npages >> 1);
1919 if (n < size_t.max/PAGESIZE)
1923 // Allocate successively larger pools up to 8 megs
1928 n = 8; // cap pool size at 8 megs
1929 n *= (POOLSIZE / PAGESIZE);
1934 pool = cast(Pool *) cstdlib.calloc(1, Pool.sizeof);
1937 pool.initialize(npages);
1941 newnpools = npools + 1;
1942 newpooltable = cast(Pool **) cstdlib.realloc(pooltable,
1943 newnpools * (Pool *).sizeof);
1947 // Sort pool into newpooltable[]
1948 for (i = 0; i < npools; i++)
1950 if (pool.opCmp(newpooltable[i]) < 0)
1953 cstring.memmove(newpooltable + i + 1, newpooltable + i,
1954 (npools - i) * (Pool *).sizeof);
1955 newpooltable[i] = pool;
1957 pooltable = newpooltable;
1960 minAddr = pooltable[0].baseAddr;
1961 maxAddr = pooltable[npools - 1].topAddr;
1973 * Allocate a page of bin's.
1977 int allocPage(Bins bin)
1985 for (n = 0; n < npools; n++)
1987 pool = pooltable[n];
1988 pn = pool.allocPages(1);
1995 pool.pagetable[pn] = cast(ubyte)bin;
1997 // Convert page to free list
1998 size_t size = binsize[bin];
1999 List **b = &bucket[bin];
2001 p = pool.baseAddr + pn * PAGESIZE;
2002 ptop = p + PAGESIZE;
2003 for (; p < ptop; p += size)
2005 (cast(List *)p).next = *b;
2013 * Search a range of memory values and mark any pointers into the GC pool.
2015 void mark(void *pbot, void *ptop)
2017 void **p1 = cast(void **)pbot;
2018 void **p2 = cast(void **)ptop;
2022 //printf("marking range: %p -> %p\n", pbot, ptop);
2023 for (; p1 < p2; p1++)
2026 byte *p = cast(byte *)(*p1);
2028 if (p >= minAddr && p < maxAddr)
2030 if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
2036 size_t offset = cast(size_t)(p - pool.baseAddr);
2038 size_t pn = offset / PAGESIZE;
2039 Bins bin = cast(Bins)pool.pagetable[pn];
2041 // Adjust bit to be at start of allocated memory block
2043 biti = (offset & notbinsize[bin]) >> 4;
2044 else if (bin == B_PAGEPLUS)
2050 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
2051 biti = pn * (PAGESIZE / 16);
2055 // Don't mark bits in B_FREE pages
2059 if (bin >= B_PAGE) // Cache B_PAGE and B_PAGEPLUS lookups
2060 pcache = cast(size_t)p & ~(PAGESIZE-1);
2062 if (!pool.mark.test(biti))
2064 pool.mark.set(biti);
2065 if (!pool.noscan.test(biti))
2067 pool.scan.set(biti);
2074 anychanges |= changes;
2079 * Return number of full pages free'd.
2081 size_t fullcollectshell()
2083 // The purpose of the 'shell' is to ensure all the registers
2084 // get put on the stack so they'll be scanned
2089 gcc.builtins.__builtin_unwind_init();
2096 uint eax,ecx,edx,ebx,ebp,esi,edi;
2109 else version (X86_64)
2111 ulong rax,rbx,rcx,rdx,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15;
2114 movq rax[RBP], RAX ;
2115 movq rbx[RBP], RBX ;
2116 movq rcx[RBP], RCX ;
2117 movq rdx[RBP], RDX ;
2118 movq rbp[RBP], RBP ;
2119 movq rsi[RBP], RSI ;
2120 movq rdi[RBP], RDI ;
2123 movq r10[RBP], R10 ;
2124 movq r11[RBP], R11 ;
2125 movq r12[RBP], R12 ;
2126 movq r13[RBP], R13 ;
2127 movq r14[RBP], R14 ;
2128 movq r15[RBP], R15 ;
2134 static assert( false, "Architecture not supported." );
2145 result = fullcollect(sp);
2168 size_t fullcollect(void *stackTop)
2173 debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n");
2175 thread_suspendAll();
2181 for (n = 0; n < npools; n++)
2183 pool = pooltable[n];
2186 pool.freebits.zero();
2189 // Mark each free entry, so it doesn't get scanned
2190 for (n = 0; n < B_PAGE; n++)
2192 for (List *list = bucket[n]; list; list = list.next)
2194 pool = findPool(list);
2196 pool.freebits.set(cast(size_t)(cast(byte*)list - pool.baseAddr) / 16);
2200 for (n = 0; n < npools; n++)
2202 pool = pooltable[n];
2203 pool.mark.copy(&pool.freebits);
2206 rt_scanStaticData( &mark );
2210 // Scan stacks and registers for each paused thread
2211 thread_scanAll( &mark, stackTop );
2215 debug(COLLECT_PRINTF) printf("scan roots[]\n");
2216 mark(roots, roots + nroots);
2219 debug(COLLECT_PRINTF) printf("scan ranges[]\n");
2221 for (n = 0; n < nranges; n++)
2223 debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop);
2224 mark(ranges[n].pbot, ranges[n].ptop);
2228 debug(COLLECT_PRINTF) printf("\tscan heap\n");
2232 for (n = 0; n < npools; n++)
2238 pool = pooltable[n];
2240 bbase = pool.scan.base();
2241 btop = bbase + pool.scan.nwords;
2242 for (b = bbase; b < btop;)
2258 o = pool.baseAddr + (b - bbase) * 32 * 16;
2259 if (!(bitm & 0xFFFF))
2264 for (; bitm; o += 16, bitm >>= 1)
2269 pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE;
2270 bin = cast(Bins)pool.pagetable[pn];
2273 mark(o, o + binsize[bin]);
2275 else if (bin == B_PAGE || bin == B_PAGEPLUS)
2277 if (bin == B_PAGEPLUS)
2279 while (pool.pagetable[pn - 1] != B_PAGE)
2283 while (pn + u < pool.npages && pool.pagetable[pn + u] == B_PAGEPLUS)
2285 mark(o, o + u * PAGESIZE);
2294 // Free up everything not marked
2295 debug(COLLECT_PRINTF) printf("\tfree'ing\n");
2296 size_t freedpages = 0;
2298 for (n = 0; n < npools; n++)
2300 pool = pooltable[n];
2301 uint* bbase = pool.mark.base();
2303 for (pn = 0; pn < pool.npages; pn++, bbase += PAGESIZE / (32 * 16))
2305 Bins bin = cast(Bins)pool.pagetable[pn];
2309 auto size = binsize[bin];
2310 byte* p = pool.baseAddr + pn * PAGESIZE;
2311 byte* ptop = p + PAGESIZE;
2312 size_t biti = pn * (PAGESIZE/16);
2313 size_t bitstride = size / 16;
2315 version(none) // BUG: doesn't work because freebits() must also be cleared
2317 // If free'd entire page
2318 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 && bbase[3] == 0 &&
2319 bbase[4] == 0 && bbase[5] == 0 && bbase[6] == 0 && bbase[7] == 0)
2321 for (; p < ptop; p += size, biti += bitstride)
2323 if (pool.finals.nbits && pool.finals.testClear(biti))
2324 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2325 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
2327 List *list = cast(List *)p;
2329 debug (MEMSTOMP) cstring.memset(p, 0xF3, size);
2331 pool.pagetable[pn] = B_FREE;
2336 for (; p < ptop; p += size, biti += bitstride)
2338 if (!pool.mark.test(biti))
2340 sentinel_Invariant(sentinel_add(p));
2342 pool.freebits.set(biti);
2343 if (pool.finals.nbits && pool.finals.testClear(biti))
2344 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
2345 clrBits(pool, biti, BlkAttr.ALL_BITS);
2347 List *list = cast(List *)p;
2349 debug (MEMSTOMP) cstring.memset(p, 0xF3, size);
2355 else if (bin == B_PAGE)
2357 size_t biti = pn * (PAGESIZE / 16);
2358 if (!pool.mark.test(biti))
2360 byte *p = pool.baseAddr + pn * PAGESIZE;
2361 sentinel_Invariant(sentinel_add(p));
2362 if (pool.finals.nbits && pool.finals.testClear(biti))
2363 rt_finalize(sentinel_add(p), false/*noStack > 0*/);
2364 clrBits(pool, biti, BlkAttr.ALL_BITS);
2366 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p);
2367 pool.pagetable[pn] = B_FREE;
2369 debug (MEMSTOMP) cstring.memset(p, 0xF3, PAGESIZE);
2370 while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS)
2373 pool.pagetable[pn] = B_FREE;
2379 cstring.memset(p, 0xF3, PAGESIZE);
2390 // Free complete pages, rebuild free list
2391 debug(COLLECT_PRINTF) printf("\tfree complete pages\n");
2392 size_t recoveredpages = 0;
2393 for (n = 0; n < npools; n++)
2395 pool = pooltable[n];
2396 for (size_t pn = 0; pn < pool.npages; pn++)
2398 Bins bin = cast(Bins)pool.pagetable[pn];
2404 size_t size = binsize[bin];
2405 size_t bitstride = size / 16;
2406 size_t bitbase = pn * (PAGESIZE / 16);
2407 size_t bittop = bitbase + (PAGESIZE / 16);
2411 for (biti = bitbase; biti < bittop; biti += bitstride)
2413 if (!pool.freebits.test(biti))
2416 pool.pagetable[pn] = B_FREE;
2421 p = pool.baseAddr + pn * PAGESIZE;
2422 for (u = 0; u < PAGESIZE; u += size)
2424 biti = bitbase + u / 16;
2425 if (pool.freebits.test(biti))
2427 List *list = cast(List *)(p + u);
2428 if (list.next != bucket[bin]) // avoid unnecessary writes
2429 list.next = bucket[bin];
2437 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages);
2438 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, npools);
2440 return freedpages + recoveredpages;
2447 uint getBits(Pool* pool, size_t biti)
2456 if (pool.finals.nbits &&
2457 pool.finals.test(biti))
2458 bits |= BlkAttr.FINALIZE;
2459 if (pool.noscan.test(biti))
2460 bits |= BlkAttr.NO_SCAN;
2461 // if (pool.nomove.nbits &&
2462 // pool.nomove.test(biti))
2463 // bits |= BlkAttr.NO_MOVE;
2471 void setBits(Pool* pool, size_t biti, uint mask)
2478 if (mask & BlkAttr.FINALIZE)
2480 if (!pool.finals.nbits)
2481 pool.finals.alloc(pool.mark.nbits);
2482 pool.finals.set(biti);
2484 if (mask & BlkAttr.NO_SCAN)
2486 pool.noscan.set(biti);
2488 // if (mask & BlkAttr.NO_MOVE)
2490 // if (!pool.nomove.nbits)
2491 // pool.nomove.alloc(pool.mark.nbits);
2492 // pool.nomove.set(biti);
2500 void clrBits(Pool* pool, size_t biti, uint mask)
2507 if (mask & BlkAttr.FINALIZE && pool.finals.nbits)
2508 pool.finals.clear(biti);
2509 if (mask & BlkAttr.NO_SCAN)
2510 pool.noscan.clear(biti);
2511 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits)
2512 // pool.nomove.clear(biti);
2518 /* ============================ Pool =============================== */
2525 GCBits mark; // entries already scanned, or should not be scanned
2526 GCBits scan; // entries that need to be scanned
2527 GCBits freebits; // entries that are on the free list
2528 GCBits finals; // entries that need finalizer run on them
2529 GCBits noscan; // entries that should not be scanned
2535 void initialize(size_t npages)
2537 size_t poolsize = npages * PAGESIZE;
2538 assert(poolsize >= POOLSIZE);
2539 baseAddr = cast(byte *) alloc.os_mem_map(poolsize);
2541 // Some of the code depends on page alignment of memory pools
2542 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0);
2550 topAddr = baseAddr + poolsize;
2552 mark.alloc(cast(size_t)poolsize / 16);
2553 scan.alloc(cast(size_t)poolsize / 16);
2554 freebits.alloc(cast(size_t)poolsize / 16);
2555 noscan.alloc(cast(size_t)poolsize / 16);
2557 pagetable = cast(ubyte*) cstdlib.malloc(npages);
2559 onOutOfMemoryError();
2560 cstring.memset(pagetable, B_FREE, npages);
2562 this.npages = npages;
2574 result = alloc.os_mem_unmap(baseAddr, npages * PAGESIZE);
2583 cstdlib.free(pagetable);
2593 void Invariant() { }
2600 //freebits.Invariant();
2601 //finals.Invariant();
2602 //noscan.Invariant();
2606 //if (baseAddr + npages * PAGESIZE != topAddr)
2607 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr);
2608 assert(baseAddr + npages * PAGESIZE == topAddr);
2611 for (size_t i = 0; i < npages; i++)
2613 Bins bin = cast(Bins)pagetable[i];
2614 assert(bin < B_MAX);
2620 * Allocate n pages from Pool.
2621 * Returns OPFAIL on failure.
2623 size_t allocPages(size_t n)
2629 for (i = 0; i < npages; i++)
2631 if (pagetable[i] == B_FREE)
2644 * Free npages pages starting with pagenum.
2646 void freePages(size_t pagenum, size_t npages)
2648 cstring.memset(&pagetable[pagenum], B_FREE, npages);
2653 * Used for sorting pooltable[]
2657 if (baseAddr < p2.baseAddr)
2660 return cast(int)(baseAddr > p2.baseAddr);
2665 /* ============================ SENTINEL =============================== */
2670 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
2671 const ubyte SENTINEL_POST = 0xF5; // 8 bits
2672 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
2675 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; }
2676 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; }
2677 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; }
2680 void sentinel_init(void *p, size_t size)
2682 *sentinel_size(p) = size;
2683 *sentinel_pre(p) = SENTINEL_PRE;
2684 *sentinel_post(p) = SENTINEL_POST;
2688 void sentinel_Invariant(void *p)
2690 assert(*sentinel_pre(p) == SENTINEL_PRE);
2691 assert(*sentinel_post(p) == SENTINEL_POST);
2695 void *sentinel_add(void *p)
2697 return p + 2 * size_t.sizeof;
2701 void *sentinel_sub(void *p)
2703 return p - 2 * size_t.sizeof;
2708 const uint SENTINEL_EXTRA = 0;
2711 void sentinel_init(void *p, size_t size)
2716 void sentinel_Invariant(void *p)
2721 void *sentinel_add(void *p)
2727 void *sentinel_sub(void *p)
2734 // vim: set et sw=4 sts=4 :