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 dynarray = rt.gc.cdgc.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
95 package bool has_pointermap(uint attrs)
97 return !opts.options.conservative && !(attrs & BlkAttr.NO_SCAN);
103 extern (C) void* rt_stackBottom();
104 extern (C) void* rt_stackTop();
106 extern (C) void rt_finalize( void* p, bool det = true );
108 alias void delegate(Object) DEvent;
109 extern (C) void rt_attachDisposeEvent(Object h, DEvent e);
110 extern (C) bool rt_detachDisposeEvent(Object h, DEvent e);
113 alias void delegate( void*, void* ) scanFn;
115 extern (C) void rt_scanStaticData( scanFn scan );
117 extern (C) bool thread_needLock();
118 extern (C) void thread_suspendAll();
119 extern (C) void thread_resumeAll();
121 extern (C) void thread_scanAll( scanFn fn, void* curStackTop = null );
123 extern (C) void onOutOfMemoryError();
127 OPFAIL = ~cast(size_t)0
135 POOLSIZE = (4096*256),
149 B_PAGE, // start of large alloc
150 B_PAGEPLUS, // continuation of large alloc
169 int opCmp(in Range other)
171 if (pbot < other.pbot)
174 return cast(int)(pbot > other.pbot);
179 const uint binsize[B_MAX] = [ 16,32,64,128,256,512,1024,2048,4096 ];
180 const uint notbinsize[B_MAX] = [ ~(16u-1),~(32u-1),~(64u-1),~(128u-1),~(256u-1),
181 ~(512u-1),~(1024u-1),~(2048u-1),~(4096u-1) ];
184 /* ============================ GC =============================== */
187 class GCLock { } // just a dummy so we can get a global lock
192 static ClassInfo gcLock; // global lock
197 uint noStack; // !=0 means don't scan stack
201 int disabled; // turn off collections if >0
203 byte *minAddr; // min(baseAddr)
204 byte *maxAddr; // max(topAddr)
206 List *bucket[B_MAX]; // free list for each size
208 dynarray.DynArray!(void*) roots;
209 dynarray.DynArray!(Range) ranges;
210 dynarray.DynArray!(Pool) pools;
219 //printf("Gcx.invariant(): this = %p\n", this);
222 for (i = 0; i < pools.length; i++)
224 Pool* pool = pools[i];
228 assert(minAddr == pool.baseAddr);
230 if (i + 1 < pools.length)
232 assert(*pool < pools[i + 1]);
234 else if (i + 1 == pools.length)
236 assert(maxAddr == pool.topAddr);
243 for (i = 0; i < ranges.length; i++)
245 assert(ranges[i].pbot);
246 assert(ranges[i].ptop);
247 assert(ranges[i].pbot <= ranges[i].ptop);
250 for (i = 0; i < B_PAGE; i++)
252 for (List *list = bucket[i]; list; list = list.next)
261 * Find Pool that pointer is in.
262 * Return null if not in a Pool.
263 * Assume pools is sorted.
265 Pool *findPool(void *p)
267 if (p >= minAddr && p < maxAddr)
269 if (pools.length == 1)
274 for (size_t i = 0; i < pools.length; i++)
276 Pool* pool = pools[i];
277 if (p < pool.topAddr)
279 if (pool.baseAddr <= p)
290 * Find base address of block containing pointer p.
291 * Returns null if not a gc'd pointer
293 void* findBase(void *p)
300 size_t offset = cast(size_t)(p - pool.baseAddr);
301 size_t pn = offset / PAGESIZE;
302 Bins bin = cast(Bins)pool.pagetable[pn];
304 // Adjust bit to be at start of allocated memory block
307 return pool.baseAddr + (offset & notbinsize[bin]);
309 else if (bin == B_PAGEPLUS)
313 --pn, offset -= PAGESIZE;
314 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
316 return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
320 // we are in a B_FREE page
329 * Find size of pointer p.
330 * Returns 0 if not a gc'd pointer
332 size_t findSize(void *p)
343 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
344 bin = cast(Bins)pool.pagetable[pagenum];
351 pt = &pool.pagetable[0];
352 for (i = pagenum + 1; i < pool.npages; i++)
354 if (pt[i] != B_PAGEPLUS)
357 size = (i - pagenum) * PAGESIZE;
367 BlkInfo getInfo(void* p)
375 size_t offset = cast(size_t)(p - pool.baseAddr);
376 size_t pn = offset / PAGESIZE;
377 Bins bin = cast(Bins)pool.pagetable[pn];
379 ////////////////////////////////////////////////////////////////////
381 ////////////////////////////////////////////////////////////////////
385 info.base = pool.baseAddr + (offset & notbinsize[bin]);
387 else if (bin == B_PAGEPLUS)
391 --pn, offset -= PAGESIZE;
393 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
395 info.base = pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
397 // fix bin for use by size calc below
398 bin = cast(Bins)pool.pagetable[pn];
401 ////////////////////////////////////////////////////////////////////
403 ////////////////////////////////////////////////////////////////////
405 info.size = binsize[bin];
411 pt = &pool.pagetable[0];
412 for (i = pn + 1; i < pool.npages; i++)
414 if (pt[i] != B_PAGEPLUS)
417 info.size = (i - pn) * PAGESIZE;
420 ////////////////////////////////////////////////////////////////////
422 ////////////////////////////////////////////////////////////////////
424 info.attr = getAttr(pool, cast(size_t)(offset / 16));
425 if (!(info.attr & BlkAttr.NO_SCAN))
426 info.size -= (size_t*).sizeof; // bitmask
433 * Compute bin for size.
435 static Bins findBin(size_t size)
479 * Allocate a new pool of at least size bytes.
480 * Sort it into pools.
481 * Mark all memory in the pool as B_FREE.
482 * Return the actual number of bytes reserved or 0 on error.
484 size_t reserveNoSync(size_t size)
487 size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
488 Pool* pool = newPool(npages);
492 return pool.npages * PAGESIZE;
497 * Minimizes physical memory usage by returning free pools to the OS.
499 void minimizeNoSync()
505 for (n = 0; n < pools.length; n++)
508 for (pn = 0; pn < pool.npages; pn++)
510 if (cast(Bins)pool.pagetable[pn] != B_FREE)
513 if (pn < pool.npages)
519 minAddr = pools[0].baseAddr;
520 maxAddr = pools[pools.length - 1].topAddr;
525 * Allocate a chunk of memory that is larger than a page.
526 * Return null if out of memory.
528 void *bigAlloc(size_t size)
538 npages = (size + PAGESIZE - 1) / PAGESIZE;
542 // This code could use some refinement when repeatedly
543 // allocating very large arrays.
545 for (n = 0; n < pools.length; n++)
548 pn = pool.allocPages(npages);
563 freedpages = fullcollectshell();
564 if (freedpages >= pools.length * ((POOLSIZE / PAGESIZE) / 4))
569 // Release empty pools to prevent bloat
572 pool = newPool(npages);
578 pn = pool.allocPages(npages);
579 assert(pn != OPFAIL);
582 // Release empty pools to prevent bloat
585 pool = newPool(npages);
588 pn = pool.allocPages(npages);
589 assert(pn != OPFAIL);
599 pool.pagetable[pn] = B_PAGE;
601 memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1);
602 p = pool.baseAddr + pn * PAGESIZE;
603 memset(cast(char *)p + size, 0, npages * PAGESIZE - size);
604 if (opts.options.mem_stomp)
605 memset(p, 0xF1, size);
609 return null; // let mallocNoSync handle the error
614 * Allocate a new pool with at least npages in it.
615 * Sort it into pools.
616 * Return null if failed.
618 Pool *newPool(size_t npages)
620 // Minimum of POOLSIZE
621 if (npages < POOLSIZE/PAGESIZE)
622 npages = POOLSIZE/PAGESIZE;
623 else if (npages > POOLSIZE/PAGESIZE)
625 // Give us 150% of requested size, so there's room to extend
626 auto n = npages + (npages >> 1);
627 if (n < size_t.max/PAGESIZE)
631 // Allocate successively larger pools up to 8 megs
634 size_t n = pools.length;
636 n = 8; // cap pool size at 8 megs
637 n *= (POOLSIZE / PAGESIZE);
643 p.initialize(npages);
650 Pool* pool = pools.insert_sorted(p);
653 minAddr = pools[0].baseAddr;
654 maxAddr = pools[pools.length - 1].topAddr;
661 * Allocate a page of bin's.
665 int allocPage(Bins bin)
673 for (n = 0; n < pools.length; n++)
676 pn = pool.allocPages(1);
683 pool.pagetable[pn] = cast(ubyte)bin;
685 // Convert page to free list
686 size_t size = binsize[bin];
687 List **b = &bucket[bin];
689 p = pool.baseAddr + pn * PAGESIZE;
691 for (; p < ptop; p += size)
693 (cast(List *)p).next = *b;
701 * Marks a range of memory using the conservative bit mask. Used for
702 * the stack, for the data segment, and additional memory ranges.
704 void mark_conservative(void* pbot, void* ptop)
706 mark(pbot, ptop, PointerMap.init.bits.ptr);
711 * Search a range of memory values and mark any pointers into the GC pool.
713 void mark(void *pbot, void *ptop, size_t* pm_bitmask)
715 const BITS_PER_WORD = size_t.sizeof * 8;
717 void **p1 = cast(void **)pbot;
718 void **p2 = cast(void **)ptop;
722 size_t type_size = pm_bitmask[0];
723 size_t* pm_bits = pm_bitmask + 1;
725 //printf("marking range: %p -> %p\n", pbot, ptop);
726 for (; p1 + type_size <= p2; p1 += type_size) {
727 for (size_t n = 0; n < type_size; n++) {
728 // scan bit set for this word
729 if (!(pm_bits[n / BITS_PER_WORD] & (1 << (n % BITS_PER_WORD))))
734 if (p < minAddr || p >= maxAddr)
737 if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
740 Pool* pool = findPool(p);
743 size_t offset = cast(size_t)(p - pool.baseAddr);
745 size_t pn = offset / PAGESIZE;
746 Bins bin = cast(Bins)pool.pagetable[pn];
748 // Adjust bit to be at start of allocated memory block
750 bit_i = (offset & notbinsize[bin]) >> 4;
751 else if (bin == B_PAGEPLUS)
757 while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
758 bit_i = pn * (PAGESIZE / 16);
762 // Don't mark bits in B_FREE pages
766 if (bin >= B_PAGE) // Cache B_PAGE and B_PAGEPLUS lookups
767 pcache = cast(size_t)p & ~(PAGESIZE-1);
769 if (!pool.mark.test(bit_i))
771 pool.mark.set(bit_i);
772 if (!pool.noscan.test(bit_i))
774 pool.scan.set(bit_i);
781 anychanges |= changes;
785 * Return number of full pages free'd.
787 size_t fullcollectshell()
789 stats.collection_started();
791 stats.collection_finished();
793 // The purpose of the 'shell' is to ensure all the registers
794 // get put on the stack so they'll be scanned
799 gcc.builtins.__builtin_unwind_init();
806 uint eax,ecx,edx,ebx,ebp,esi,edi;
819 else version (X86_64)
821 ulong rax,rbx,rcx,rdx,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15;
844 static assert( false, "Architecture not supported." );
855 result = fullcollect(sp);
878 size_t fullcollect(void *stackTop)
883 debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n");
886 stats.world_stopped();
892 for (n = 0; n < pools.length; n++)
897 pool.freebits.zero();
900 // Mark each free entry, so it doesn't get scanned
901 for (n = 0; n < B_PAGE; n++)
903 for (List *list = bucket[n]; list; list = list.next)
905 pool = findPool(list);
907 pool.freebits.set(cast(size_t)(cast(byte*)list - pool.baseAddr) / 16);
911 for (n = 0; n < pools.length; n++)
914 pool.mark.copy(&pool.freebits);
917 rt_scanStaticData( &mark_conservative );
921 // Scan stacks and registers for each paused thread
922 thread_scanAll( &mark_conservative, stackTop );
926 debug(COLLECT_PRINTF) printf("scan roots[]\n");
927 mark_conservative(roots.ptr, roots.ptr + roots.length);
930 debug(COLLECT_PRINTF) printf("scan ranges[]\n");
931 for (n = 0; n < ranges.length; n++)
933 debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop);
934 mark_conservative(ranges[n].pbot, ranges[n].ptop);
937 debug(COLLECT_PRINTF) printf("\tscan heap\n");
941 for (n = 0; n < pools.length; n++)
949 bbase = pool.scan.base();
950 btop = bbase + pool.scan.nwords;
951 for (b = bbase; b < btop;)
967 o = pool.baseAddr + (b - bbase) * 32 * 16;
968 if (!(bitm & 0xFFFF))
973 for (; bitm; o += 16, bitm >>= 1)
978 pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE;
979 bin = cast(Bins)pool.pagetable[pn];
981 if (opts.options.conservative)
982 mark_conservative(o, o + binsize[bin]);
984 auto end_of_blk = cast(size_t**)(o +
985 binsize[bin] - size_t.sizeof);
986 size_t* pm_bitmask = *end_of_blk;
987 mark(o, end_of_blk, pm_bitmask);
990 else if (bin == B_PAGE || bin == B_PAGEPLUS)
992 if (bin == B_PAGEPLUS)
994 while (pool.pagetable[pn - 1] != B_PAGE)
998 while (pn + u < pool.npages &&
999 pool.pagetable[pn + u] == B_PAGEPLUS)
1002 size_t blk_size = u * PAGESIZE;
1003 if (opts.options.conservative)
1004 mark_conservative(o, o + blk_size);
1006 auto end_of_blk = cast(size_t**)(o + blk_size -
1008 size_t* pm_bitmask = *end_of_blk;
1009 mark(o, end_of_blk, pm_bitmask);
1018 stats.world_started();
1020 // Free up everything not marked
1021 debug(COLLECT_PRINTF) printf("\tfree'ing\n");
1022 size_t freedpages = 0;
1024 for (n = 0; n < pools.length; n++)
1027 uint* bbase = pool.mark.base();
1029 for (pn = 0; pn < pool.npages; pn++, bbase += PAGESIZE / (32 * 16))
1031 Bins bin = cast(Bins)pool.pagetable[pn];
1035 auto size = binsize[bin];
1036 byte* p = pool.baseAddr + pn * PAGESIZE;
1037 byte* ptop = p + PAGESIZE;
1038 size_t bit_i = pn * (PAGESIZE/16);
1039 size_t bit_stride = size / 16;
1041 version(none) // BUG: doesn't work because freebits() must also be cleared
1043 // If free'd entire page
1044 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 &&
1045 bbase[3] == 0 && bbase[4] == 0 && bbase[5] == 0 &&
1046 bbase[6] == 0 && bbase[7] == 0)
1048 for (; p < ptop; p += size, bit_i += bit_stride)
1050 if (pool.finals.nbits && pool.finals.testClear(bit_i)) {
1051 if (opts.options.sentinel)
1052 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
1054 rt_finalize(cast(List *)p, false/*noStack > 0*/);
1056 this.clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
1058 List *list = cast(List *)p;
1060 if (opts.options.mem_stomp)
1061 memset(p, 0xF3, size);
1063 pool.pagetable[pn] = B_FREE;
1068 for (; p < ptop; p += size, bit_i += bit_stride)
1070 if (!pool.mark.test(bit_i))
1072 if (opts.options.sentinel)
1073 sentinel_Invariant(sentinel_add(p));
1075 pool.freebits.set(bit_i);
1076 if (pool.finals.nbits && pool.finals.testClear(bit_i)) {
1077 if (opts.options.sentinel)
1078 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/);
1080 rt_finalize(cast(List *)p, false/*noStack > 0*/);
1082 clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
1084 List *list = cast(List *)p;
1086 if (opts.options.mem_stomp)
1087 memset(p, 0xF3, size);
1093 else if (bin == B_PAGE)
1095 size_t bit_i = pn * (PAGESIZE / 16);
1096 if (!pool.mark.test(bit_i))
1098 byte *p = pool.baseAddr + pn * PAGESIZE;
1099 if (opts.options.sentinel)
1100 sentinel_Invariant(sentinel_add(p));
1101 if (pool.finals.nbits && pool.finals.testClear(bit_i)) {
1102 if (opts.options.sentinel)
1103 rt_finalize(sentinel_add(p), false/*noStack > 0*/);
1105 rt_finalize(p, false/*noStack > 0*/);
1107 clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
1109 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p);
1110 pool.pagetable[pn] = B_FREE;
1112 if (opts.options.mem_stomp)
1113 memset(p, 0xF3, PAGESIZE);
1114 while (pn + 1 < pool.npages && pool.pagetable[pn + 1] == B_PAGEPLUS)
1117 pool.pagetable[pn] = B_FREE;
1120 if (opts.options.mem_stomp)
1123 memset(p, 0xF3, PAGESIZE);
1134 // Free complete pages, rebuild free list
1135 debug(COLLECT_PRINTF) printf("\tfree complete pages\n");
1136 size_t recoveredpages = 0;
1137 for (n = 0; n < pools.length; n++)
1140 for (size_t pn = 0; pn < pool.npages; pn++)
1142 Bins bin = cast(Bins)pool.pagetable[pn];
1148 size_t size = binsize[bin];
1149 size_t bit_stride = size / 16;
1150 size_t bit_base = pn * (PAGESIZE / 16);
1151 size_t bit_top = bit_base + (PAGESIZE / 16);
1155 for (; bit_i < bit_top; bit_i += bit_stride)
1157 if (!pool.freebits.test(bit_i))
1160 pool.pagetable[pn] = B_FREE;
1165 p = pool.baseAddr + pn * PAGESIZE;
1166 for (u = 0; u < PAGESIZE; u += size)
1168 bit_i = bit_base + u / 16;
1169 if (pool.freebits.test(bit_i))
1171 List *list = cast(List *)(p + u);
1172 // avoid unnecessary writes
1173 if (list.next != bucket[bin])
1174 list.next = bucket[bin];
1182 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages);
1183 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, pools.length);
1185 return freedpages + recoveredpages;
1192 uint getAttr(Pool* pool, size_t bit_i)
1201 if (pool.finals.nbits &&
1202 pool.finals.test(bit_i))
1203 attrs |= BlkAttr.FINALIZE;
1204 if (pool.noscan.test(bit_i))
1205 attrs |= BlkAttr.NO_SCAN;
1206 // if (pool.nomove.nbits &&
1207 // pool.nomove.test(bit_i))
1208 // attrs |= BlkAttr.NO_MOVE;
1216 void setAttr(Pool* pool, size_t bit_i, uint mask)
1223 if (mask & BlkAttr.FINALIZE)
1225 if (!pool.finals.nbits)
1226 pool.finals.alloc(pool.mark.nbits);
1227 pool.finals.set(bit_i);
1229 if (mask & BlkAttr.NO_SCAN)
1231 pool.noscan.set(bit_i);
1233 // if (mask & BlkAttr.NO_MOVE)
1235 // if (!pool.nomove.nbits)
1236 // pool.nomove.alloc(pool.mark.nbits);
1237 // pool.nomove.set(bit_i);
1245 void clrAttr(Pool* pool, size_t bit_i, uint mask)
1252 if (mask & BlkAttr.FINALIZE && pool.finals.nbits)
1253 pool.finals.clear(bit_i);
1254 if (mask & BlkAttr.NO_SCAN)
1255 pool.noscan.clear(bit_i);
1256 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits)
1257 // pool.nomove.clear(bit_i);
1265 stackBottom = cast(char*)&dummy;
1266 opts.parse(cstdlib.getenv("D_GC_OPTS"));
1267 gcLock = GCLock.classinfo;
1269 setStackBottom(rt_stackBottom());
1270 stats = Stats(this);
1279 if (!thread_needLock())
1281 assert(this.disabled > 0);
1284 else synchronized (gcLock)
1286 assert(this.disabled > 0);
1297 if (!thread_needLock())
1301 else synchronized (gcLock)
1311 uint getAttr(void* p)
1320 Pool* pool = this.findPool(p);
1325 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
1327 old_attrs = this.getAttr(pool, bit_i);
1332 if (!thread_needLock())
1336 else synchronized (gcLock)
1346 uint setAttr(void* p, uint mask)
1355 Pool* pool = this.findPool(p);
1360 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
1362 old_attrs = this.getAttr(pool, bit_i);
1363 this.setAttr(pool, bit_i, mask);
1368 if (!thread_needLock())
1372 else synchronized (gcLock)
1382 uint clrAttr(void* p, uint mask)
1391 Pool* pool = this.findPool(p);
1396 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
1398 old_attrs = this.getAttr(pool, bit_i);
1399 this.clrAttr(pool, bit_i, mask);
1404 if (!thread_needLock())
1408 else synchronized (gcLock)
1418 void *malloc(size_t size, uint attrs, PointerMap ptrmap)
1425 if (!thread_needLock())
1427 return mallocNoSync(size, attrs, ptrmap.bits.ptr);
1429 else synchronized (gcLock)
1431 return mallocNoSync(size, attrs, ptrmap.bits.ptr);
1439 private void *mallocNoSync(size_t size, uint attrs, size_t* pm_bitmask)
1443 stats.malloc_started(size, attrs, pm_bitmask);
1445 stats.malloc_finished(p);
1450 if (opts.options.sentinel)
1451 size += SENTINEL_EXTRA;
1453 bool has_pm = has_pointermap(attrs);
1455 size += size_t.sizeof;
1458 // Cache previous binsize lookup - Dave Fladebo.
1459 static size_t lastsize = -1;
1460 static Bins lastbin;
1461 if (size == lastsize)
1465 bin = this.findBin(size);
1470 size_t capacity; // to figure out where to store the bitmask
1473 p = this.bucket[bin];
1476 if (!this.allocPage(bin) && !this.disabled) // try to find a new page
1478 if (!thread_needLock())
1480 /* Then we haven't locked it yet. Be sure
1481 * and lock for a collection, since a finalizer
1482 * may start a new thread.
1484 synchronized (gcLock)
1486 this.fullcollectshell();
1489 else if (!this.fullcollectshell()) // collect to find a new page
1494 if (!this.bucket[bin] && !this.allocPage(bin))
1496 this.newPool(1); // allocate new pool to find a new page
1497 int result = this.allocPage(bin);
1499 onOutOfMemoryError();
1501 p = this.bucket[bin];
1503 capacity = binsize[bin];
1505 // Return next item from free list
1506 this.bucket[bin] = (cast(List*)p).next;
1507 if (!(attrs & BlkAttr.NO_SCAN))
1508 memset(p + size, 0, capacity - size);
1509 if (opts.options.mem_stomp)
1510 memset(p, 0xF0, size);
1514 p = this.bigAlloc(size);
1516 onOutOfMemoryError();
1517 // Round the size up to the number of pages needed to store it
1518 size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
1519 capacity = npages * PAGESIZE;
1522 // Store the bit mask AFTER SENTINEL_POST
1523 // TODO: store it BEFORE, so the bitmask is protected too
1525 auto end_of_blk = cast(size_t**)(p + capacity - size_t.sizeof);
1526 *end_of_blk = pm_bitmask;
1527 size -= size_t.sizeof;
1530 if (opts.options.sentinel) {
1531 size -= SENTINEL_EXTRA;
1532 p = sentinel_add(p);
1533 sentinel_init(p, size);
1538 Pool *pool = this.findPool(p);
1541 this.setAttr(pool, cast(size_t)(p - pool.baseAddr) / 16, attrs);
1550 void *calloc(size_t size, uint attrs, PointerMap ptrmap)
1557 if (!thread_needLock())
1559 return callocNoSync(size, attrs, ptrmap.bits.ptr);
1561 else synchronized (gcLock)
1563 return callocNoSync(size, attrs, ptrmap.bits.ptr);
1571 private void *callocNoSync(size_t size, uint attrs, size_t* pm_bitmask)
1575 void *p = mallocNoSync(size, attrs, pm_bitmask);
1584 void *realloc(void *p, size_t size, uint attrs, PointerMap ptrmap)
1586 if (!thread_needLock())
1588 return reallocNoSync(p, size, attrs, ptrmap.bits.ptr);
1590 else synchronized (gcLock)
1592 return reallocNoSync(p, size, attrs, ptrmap.bits.ptr);
1600 private void *reallocNoSync(void *p, size_t size, uint attrs,
1613 p = mallocNoSync(size, attrs, pm_bitmask);
1617 Pool* pool = this.findPool(p);
1621 // Set or retrieve attributes as appropriate
1622 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
1624 this.clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
1625 this.setAttr(pool, bit_i, attrs);
1628 attrs = this.getAttr(pool, bit_i);
1630 void* blk_base_addr = this.findBase(p);
1631 size_t blk_size = this.findSize(p);
1632 bool has_pm = has_pointermap(attrs);
1633 size_t pm_bitmask_size = 0;
1635 pm_bitmask_size = size_t.sizeof;
1636 // Retrieve pointer map bit mask if appropriate
1637 if (pm_bitmask is null) {
1638 auto end_of_blk = cast(size_t**)(blk_base_addr +
1639 blk_size - size_t.sizeof);
1640 pm_bitmask = *end_of_blk;
1644 if (opts.options.sentinel)
1646 sentinel_Invariant(p);
1647 size_t sentinel_stored_size = *sentinel_size(p);
1648 if (sentinel_stored_size != size)
1650 void* p2 = mallocNoSync(size, attrs, pm_bitmask);
1651 if (sentinel_stored_size < size)
1652 size = sentinel_stored_size;
1653 cstring.memcpy(p2, p, size);
1659 size += pm_bitmask_size;
1660 if (blk_size >= PAGESIZE && size >= PAGESIZE)
1662 auto psz = blk_size / PAGESIZE;
1663 auto newsz = (size + PAGESIZE - 1) / PAGESIZE;
1667 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
1672 synchronized (gcLock)
1674 if (opts.options.mem_stomp)
1675 memset(p + size - pm_bitmask_size, 0xF2,
1676 blk_size - size - pm_bitmask_size);
1677 pool.freePages(pagenum + newsz, psz - newsz);
1680 auto end_of_blk = cast(size_t**)(
1681 blk_base_addr + (PAGESIZE * newsz) -
1683 *end_of_blk = pm_bitmask;
1687 else if (pagenum + newsz <= pool.npages)
1689 // Attempt to expand in place
1690 synchronized (gcLock)
1692 for (size_t i = pagenum + psz; 1;)
1694 if (i == pagenum + newsz)
1696 if (opts.options.mem_stomp)
1697 memset(p + blk_size - pm_bitmask_size,
1698 0xF0, size - blk_size
1700 memset(pool.pagetable + pagenum +
1701 psz, B_PAGEPLUS, newsz - psz);
1703 auto end_of_blk = cast(size_t**)(
1705 (PAGESIZE * newsz) -
1707 *end_of_blk = pm_bitmask;
1711 if (i == pool.npages)
1715 if (pool.pagetable[i] != B_FREE)
1722 // if new size is bigger or less than half
1723 if (blk_size < size || blk_size > size * 2)
1725 size -= pm_bitmask_size;
1726 blk_size -= pm_bitmask_size;
1727 void* p2 = mallocNoSync(size, attrs, pm_bitmask);
1728 if (blk_size < size)
1730 cstring.memcpy(p2, p, size);
1740 * Attempt to in-place enlarge the memory block pointed to by p by at least
1741 * minbytes beyond its current capacity, up to a maximum of maxsize. This
1742 * does not attempt to move the memory block (like realloc() does).
1745 * 0 if could not extend p,
1746 * total size of entire memory block if successful.
1748 size_t extend(void* p, size_t minsize, size_t maxsize)
1750 if (!thread_needLock())
1752 return extendNoSync(p, minsize, maxsize);
1754 else synchronized (gcLock)
1756 return extendNoSync(p, minsize, maxsize);
1764 private size_t extendNoSync(void* p, size_t minsize, size_t maxsize)
1767 assert( minsize <= maxsize );
1771 if (opts.options.sentinel)
1774 Pool* pool = this.findPool(p);
1778 // Retrieve attributes
1779 auto bit_i = cast(size_t)(p - pool.baseAddr) / 16;
1780 uint attrs = this.getAttr(pool, bit_i);
1782 void* blk_base_addr = this.findBase(p);
1783 size_t blk_size = this.findSize(p);
1784 bool has_pm = has_pointermap(attrs);
1785 size_t* pm_bitmask = null;
1786 size_t pm_bitmask_size = 0;
1788 pm_bitmask_size = size_t.sizeof;
1789 // Retrieve pointer map bit mask
1790 auto end_of_blk = cast(size_t**)(blk_base_addr +
1791 blk_size - size_t.sizeof);
1792 pm_bitmask = *end_of_blk;
1794 minsize += size_t.sizeof;
1795 maxsize += size_t.sizeof;
1798 if (blk_size < PAGESIZE)
1799 return 0; // cannot extend buckets
1801 auto psz = blk_size / PAGESIZE;
1802 auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE;
1803 auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE;
1805 auto pagenum = (p - pool.baseAddr) / PAGESIZE;
1808 for (sz = 0; sz < maxsz; sz++)
1810 auto i = pagenum + psz + sz;
1811 if (i == pool.npages)
1813 if (pool.pagetable[i] != B_FREE)
1823 size_t new_size = (psz + sz) * PAGESIZE;
1825 if (opts.options.mem_stomp)
1826 memset(p + blk_size - pm_bitmask_size, 0xF0,
1827 new_size - blk_size - pm_bitmask_size);
1828 memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz);
1829 this.p_cache = null;
1830 this.size_cache = 0;
1833 new_size -= size_t.sizeof;
1834 auto end_of_blk = cast(size_t**)(blk_base_addr + new_size);
1835 *end_of_blk = pm_bitmask;
1844 size_t reserve(size_t size)
1851 if (!thread_needLock())
1853 return reserveNoSync(size);
1855 else synchronized (gcLock)
1857 return reserveNoSync(size);
1872 if (!thread_needLock())
1874 return freeNoSync(p);
1876 else synchronized (gcLock)
1878 return freeNoSync(p);
1886 private void freeNoSync(void *p)
1895 // Find which page it is in
1896 pool = this.findPool(p);
1897 if (!pool) // if not one of ours
1899 if (opts.options.sentinel) {
1900 sentinel_Invariant(p);
1901 p = sentinel_sub(p);
1903 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
1904 bit_i = cast(size_t)(p - pool.baseAddr) / 16;
1905 this.clrAttr(pool, bit_i, BlkAttr.ALL_BITS);
1907 bin = cast(Bins)pool.pagetable[pagenum];
1908 if (bin == B_PAGE) // if large alloc
1913 while (++n < pool.npages && pool.pagetable[n] == B_PAGEPLUS)
1915 if (opts.options.mem_stomp)
1916 memset(p, 0xF2, npages * PAGESIZE);
1917 pool.freePages(pagenum, npages);
1922 List *list = cast(List*)p;
1924 if (opts.options.mem_stomp)
1925 memset(p, 0xF2, binsize[bin]);
1927 list.next = this.bucket[bin];
1928 this.bucket[bin] = list;
1934 * Determine the base address of the block containing p. If p is not a gc
1935 * allocated pointer, return null.
1937 void* addrOf(void *p)
1944 if (!thread_needLock())
1946 return addrOfNoSync(p);
1948 else synchronized (gcLock)
1950 return addrOfNoSync(p);
1958 void* addrOfNoSync(void *p)
1965 return this.findBase(p);
1970 * Determine the allocated size of pointer p. If p is an interior pointer
1971 * or not a gc allocated pointer, return 0.
1973 size_t sizeOf(void *p)
1980 if (!thread_needLock())
1982 return sizeOfNoSync(p);
1984 else synchronized (gcLock)
1986 return sizeOfNoSync(p);
1994 private size_t sizeOfNoSync(void *p)
1998 if (opts.options.sentinel)
1999 p = sentinel_sub(p);
2001 Pool* pool = this.findPool(p);
2005 auto biti = cast(size_t)(p - pool.baseAddr) / 16;
2006 uint attrs = this.getAttr(pool, biti);
2008 size_t size = this.findSize(p);
2009 size_t pm_bitmask_size = 0;
2010 if (has_pointermap(attrs))
2011 pm_bitmask_size = size_t.sizeof;
2013 if (opts.options.sentinel) {
2014 // Check for interior pointer
2016 // 1) size is a power of 2 for less than PAGESIZE values
2017 // 2) base of memory pool is aligned on PAGESIZE boundary
2018 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
2020 return size - SENTINEL_EXTRA - pm_bitmask_size;
2023 if (p == this.p_cache)
2024 return this.size_cache;
2026 // Check for interior pointer
2028 // 1) size is a power of 2 for less than PAGESIZE values
2029 // 2) base of memory pool is aligned on PAGESIZE boundary
2030 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
2034 this.size_cache = size - pm_bitmask_size;
2036 return this.size_cache;
2042 * Determine the base address of the block containing p. If p is not a gc
2043 * allocated pointer, return null.
2045 BlkInfo query(void *p)
2053 if (!thread_needLock())
2055 return queryNoSync(p);
2057 else synchronized (gcLock)
2059 return queryNoSync(p);
2067 BlkInfo queryNoSync(void *p)
2071 return this.getInfo(p);
2076 * Verify that pointer p:
2077 * 1) belongs to this memory pool
2078 * 2) points to the start of an allocated piece of memory
2079 * 3) is not on a free list
2088 if (!thread_needLock())
2092 else synchronized (gcLock)
2102 private void checkNoSync(void *p)
2106 if (opts.options.sentinel)
2107 sentinel_Invariant(p);
2115 if (opts.options.sentinel)
2116 p = sentinel_sub(p);
2117 pool = this.findPool(p);
2119 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
2120 bin = cast(Bins)pool.pagetable[pagenum];
2121 assert(bin <= B_PAGE);
2122 size = binsize[bin];
2123 assert((cast(size_t)p & (size - 1)) == 0);
2129 // Check that p is not on a free list
2132 for (list = this.bucket[bin]; list; list = list.next)
2134 assert(cast(void*)list != p);
2145 private void setStackBottom(void *p)
2147 version (STACKGROWSDOWN)
2149 //p = (void *)((uint *)p + 4);
2150 if (p > this.stackBottom)
2152 this.stackBottom = p;
2157 //p = (void *)((uint *)p - 4);
2158 if (p < this.stackBottom)
2160 this.stackBottom = cast(char*)p;
2167 * add p to list of roots
2169 void addRoot(void *p)
2176 if (!thread_needLock())
2178 if (roots.append(p) is null)
2179 onOutOfMemoryError();
2181 else synchronized (gcLock)
2183 if (roots.append(p) is null)
2184 onOutOfMemoryError();
2190 * remove p from list of roots
2192 void removeRoot(void *p)
2200 if (!thread_needLock())
2202 r = roots.remove(p);
2204 else synchronized (gcLock)
2206 r = roots.remove(p);
2213 * add range to scan for roots
2215 void addRange(void *p, size_t sz)
2222 if (!thread_needLock())
2224 if (ranges.append(Range(p, p+sz)) is null)
2225 onOutOfMemoryError();
2227 else synchronized (gcLock)
2229 if (ranges.append(Range(p, p+sz)) is null)
2230 onOutOfMemoryError();
2238 void removeRange(void *p)
2246 if (!thread_needLock())
2248 r = ranges.remove(Range(p, null));
2250 else synchronized (gcLock)
2252 r = ranges.remove(Range(p, null));
2259 * do full garbage collection
2264 if (!thread_needLock())
2266 this.fullcollectshell();
2268 else synchronized (gcLock)
2270 this.fullcollectshell();
2283 * do full garbage collection ignoring roots
2285 void fullCollectNoStack()
2287 if (!thread_needLock())
2290 this.fullcollectshell();
2293 else synchronized (gcLock)
2296 this.fullcollectshell();
2303 * minimize free space usage
2307 if (!thread_needLock())
2309 this.minimizeNoSync();
2311 else synchronized (gcLock)
2313 this.minimizeNoSync();
2319 * Retrieve statistics about garbage collection.
2320 * Useful for debugging and tuning.
2322 void getStats(out GCStats stats)
2324 if (!thread_needLock())
2326 getStatsNoSync(stats);
2328 else synchronized (gcLock)
2330 getStatsNoSync(stats);
2338 private void getStatsNoSync(out GCStats stats)
2347 memset(&stats, 0, GCStats.sizeof);
2349 for (n = 0; n < pools.length; n++)
2351 Pool* pool = pools[n];
2352 psize += pool.npages * PAGESIZE;
2353 for (size_t j = 0; j < pool.npages; j++)
2355 Bins bin = cast(Bins)pool.pagetable[j];
2358 else if (bin == B_PAGE)
2360 else if (bin < B_PAGE)
2365 for (n = 0; n < B_PAGE; n++)
2367 for (List *list = this.bucket[n]; list; list = list.next)
2368 flsize += binsize[n];
2371 usize = bsize - flsize;
2373 stats.poolsize = psize;
2374 stats.usedsize = bsize - flsize;
2375 stats.freelistsize = flsize;
2378 /******************* weak-reference support *********************/
2380 // call locked if necessary
2381 private T locked(T)(in T delegate() code)
2383 if (thread_needLock)
2384 synchronized(gcLock) return code();
2389 private struct WeakPointer
2393 void ondestroy(Object r)
2395 assert(r is reference);
2396 // lock for memory consistency (parallel readers)
2397 // also ensures that weakpointerDestroy can be called while another
2398 // thread is freeing the reference with "delete"
2399 locked!(void)({ reference = null; });
2404 * Create a weak pointer to the given object.
2405 * Returns a pointer to an opaque struct allocated in C memory.
2407 void* weakpointerCreate( Object r )
2411 // must be allocated in C memory
2412 // 1. to hide the reference from the GC
2413 // 2. the GC doesn't scan delegates added by rt_attachDisposeEvent
2415 auto wp = cast(WeakPointer*)(cstdlib.malloc(WeakPointer.sizeof));
2417 onOutOfMemoryError();
2419 rt_attachDisposeEvent(r, &wp.ondestroy);
2426 * Destroy a weak pointer returned by weakpointerCreate().
2427 * If null is passed, nothing happens.
2429 void weakpointerDestroy( void* p )
2433 auto wp = cast(WeakPointer*)p;
2434 // must be extra careful about the GC or parallel threads
2435 // finalizing the reference at the same time
2438 rt_detachDisposeEvent(wp.reference, &wp.ondestroy);
2445 * Query a weak pointer and return either the object passed to
2446 * weakpointerCreate, or null if it was free'd in the meantime.
2447 * If null is passed, null is returned.
2449 Object weakpointerGet( void* p )
2453 // NOTE: could avoid the lock by using Fawzi style GC counters but
2454 // that'd require core.sync.Atomic and lots of care about memory
2455 // consistency it's an optional optimization see
2456 // http://dsource.org/projects/tango/browser/trunk/user/tango/core/Lifetime.d?rev=5100#L158
2457 return locked!(Object)({
2458 return (cast(WeakPointer*)p).reference;
2465 /* ============================ Pool =============================== */
2472 GCBits mark; // entries already scanned, or should not be scanned
2473 GCBits scan; // entries that need to be scanned
2474 GCBits freebits; // entries that are on the free list
2475 GCBits finals; // entries that need finalizer run on them
2476 GCBits noscan; // entries that should not be scanned
2482 void initialize(size_t npages)
2484 size_t poolsize = npages * PAGESIZE;
2485 assert(poolsize >= POOLSIZE);
2486 baseAddr = cast(byte *) alloc.os_mem_map(poolsize);
2488 // Some of the code depends on page alignment of memory pools
2489 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0);
2497 topAddr = baseAddr + poolsize;
2499 mark.alloc(cast(size_t)poolsize / 16);
2500 scan.alloc(cast(size_t)poolsize / 16);
2501 freebits.alloc(cast(size_t)poolsize / 16);
2502 noscan.alloc(cast(size_t)poolsize / 16);
2504 pagetable = cast(ubyte*) cstdlib.malloc(npages);
2506 onOutOfMemoryError();
2507 memset(pagetable, B_FREE, npages);
2509 this.npages = npages;
2521 result = alloc.os_mem_unmap(baseAddr, npages * PAGESIZE);
2529 // See Gcx.Dtor() for the rationale of the null check.
2531 cstdlib.free(pagetable);
2541 void Invariant() { }
2548 //freebits.Invariant();
2549 //finals.Invariant();
2550 //noscan.Invariant();
2554 //if (baseAddr + npages * PAGESIZE != topAddr)
2555 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr);
2556 assert(baseAddr + npages * PAGESIZE == topAddr);
2559 for (size_t i = 0; i < npages; i++)
2561 Bins bin = cast(Bins)pagetable[i];
2562 assert(bin < B_MAX);
2568 * Allocate n pages from Pool.
2569 * Returns OPFAIL on failure.
2571 size_t allocPages(size_t n)
2577 for (i = 0; i < npages; i++)
2579 if (pagetable[i] == B_FREE)
2592 * Free npages pages starting with pagenum.
2594 void freePages(size_t pagenum, size_t npages)
2596 memset(&pagetable[pagenum], B_FREE, npages);
2601 * Used for sorting pools
2603 int opCmp(in Pool other)
2605 if (baseAddr < other.baseAddr)
2608 return cast(int)(baseAddr > other.baseAddr);
2613 /* ============================ SENTINEL =============================== */
2616 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
2617 const ubyte SENTINEL_POST = 0xF5; // 8 bits
2618 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
2621 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; }
2622 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; }
2623 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; }
2626 void sentinel_init(void *p, size_t size)
2628 *sentinel_size(p) = size;
2629 *sentinel_pre(p) = SENTINEL_PRE;
2630 *sentinel_post(p) = SENTINEL_POST;
2634 void sentinel_Invariant(void *p)
2636 assert(*sentinel_pre(p) == SENTINEL_PRE);
2637 assert(*sentinel_post(p) == SENTINEL_POST);
2641 void *sentinel_add(void *p)
2643 return p + 2 * size_t.sizeof;
2647 void *sentinel_sub(void *p)
2649 return p - 2 * size_t.sizeof;
2653 // vim: set et sw=4 sts=4 :