1 /** 2 * This module contains the garbage collector implementation. 3 * 4 * Copyright: Copyright (C) 2001-2007 Digital Mars, www.digitalmars.com. 5 * All rights reserved. 6 * License: 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. 10 * 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 14 * restrictions: 15 * 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 23 * distribution. 24 * Authors: Walter Bright, David Friedman, Sean Kelly, Kris 25 */ 26 module rt.gc.basic.gcx; 27 // D Programming Language Garbage Collector implementation 28 29 /************** Debugging ***************************/ 30 31 //debug = PRINTF; // turn on printf's 32 //debug = COLLECT_PRINTF; // turn on printf's 33 //debug = THREADINVARIANT; // check thread integrity 34 //debug = LOGGING; // log allocations / frees 35 //debug = MEMSTOMP; // stomp on memory 36 //debug = SENTINEL; // add underrun/overrrun protection 37 //debug = PTRCHECK; // more pointer checking 38 //debug = PTRCHECK2; // thorough but slow pointer checking 39 40 /*************** Configuration *********************/ 41 42 version = STACKGROWSDOWN; // growing the stack means subtracting from the stack pointer 43 // (use for Intel X86 CPUs) 44 // else growing the stack means adding to the stack pointer 45 version = MULTI_THREADED; // produce multithreaded version 46 47 /***************************************************/ 48 49 private import rt.gc.basic.gcbits; 50 private import rt.gc.basic.gcstats; 51 private import rt.gc.basic.gcalloc; 52 53 private import cstdlib = tango.stdc.stdlib : calloc, free, malloc, realloc; 54 private import cstring = tango.stdc.string : memcpy, memmove, memset; 55 debug(THREADINVARIANT) private import tango.stdc.posix.pthread : pthread_self, pthread_t; 56 debug(PRINTF) private import tango.stdc.stdio : printf; 57 58 version (GNU) 59 { 60 // BUG: The following import will likely not work, since the gcc 61 // subdirectory is elsewhere. Instead, perhaps the functions 62 // could be declared directly or some other resolution could 63 // be found. 64 private import gcc.builtins; // for __builtin_unwind_init 65 } 66 67 struct BlkInfo 68 { 69 void* base; 70 size_t size; 71 uint attr; 72 } 73 74 private 75 { 76 const USE_CACHE = true; 77 78 enum BlkAttr : uint 79 { 80 FINALIZE = 0b0000_0001, 81 NO_SCAN = 0b0000_0010, 82 NO_MOVE = 0b0000_0100, 83 ALL_BITS = 0b1111_1111 84 } 85 86 extern (C) void* rt_stackBottom(); 87 extern (C) void* rt_stackTop(); 88 89 extern (C) void rt_finalize( void* p, bool det = true ); 90 91 alias void delegate(Object) DEvent; 92 extern (C) void rt_attachDisposeEvent(Object h, DEvent e); 93 extern (C) bool rt_detachDisposeEventNoLock(Object h, DEvent e); 94 95 alias void delegate( void*, void* ) scanFn; 96 97 extern (C) void rt_scanStaticData( scanFn scan ); 98 99 version (MULTI_THREADED) 100 { 101 extern (C) bool thread_needLock(); 102 extern (C) void thread_suspendAll(); 103 extern (C) void thread_resumeAll(); 104 105 extern (C) void thread_scanAll( scanFn fn, void* curStackTop = null ); 106 } 107 108 extern (C) void onOutOfMemoryError(); 109 110 enum 111 { 112 OPFAIL = ~cast(size_t)0 113 } 114 } 115 116 117 alias GC gc_t; 118 119 120 /* ======================= Leak Detector =========================== */ 121 122 123 debug (LOGGING) 124 { 125 struct Log 126 { 127 void* p; 128 size_t size; 129 size_t line; 130 char* file; 131 void* parent; 132 133 void print() 134 { 135 printf(" p = %x, size = %d, parent = %x ", p, size, parent); 136 if (file) 137 { 138 printf("%s(%u)", file, line); 139 } 140 printf("\n"); 141 } 142 } 143 144 145 struct LogArray 146 { 147 size_t dim; 148 size_t allocdim; 149 Log *data; 150 151 void Dtor() 152 { 153 if (data) 154 cstdlib.free(data); 155 data = null; 156 } 157 158 void reserve(size_t nentries) 159 { 160 assert(dim <= allocdim); 161 if (allocdim - dim < nentries) 162 { 163 allocdim = (dim + nentries) * 2; 164 assert(dim + nentries <= allocdim); 165 if (!data) 166 { 167 data = cast(Log*)cstdlib.malloc(allocdim * Log.sizeof); 168 if (!data && allocdim) 169 onOutOfMemoryError(); 170 } 171 else 172 { Log *newdata; 173 174 newdata = cast(Log*)cstdlib.malloc(allocdim * Log.sizeof); 175 if (!newdata && allocdim) 176 onOutOfMemoryError(); 177 cstring.memcpy(newdata, data, dim * Log.sizeof); 178 cstdlib.free(data); 179 data = newdata; 180 } 181 } 182 } 183 184 185 void push(Log log) 186 { 187 reserve(1); 188 data[dim++] = log; 189 } 190 191 void remove(size_t i) 192 { 193 cstring.memmove(data + i, data + i + 1, (dim - i) * Log.sizeof); 194 dim--; 195 } 196 197 198 size_t find(void *p) 199 { 200 for (size_t i = 0; i < dim; i++) 201 { 202 if (data[i].p == p) 203 return i; 204 } 205 return OPFAIL; // not found 206 } 207 208 209 void copy(LogArray *from) 210 { 211 reserve(from.dim - dim); 212 assert(from.dim <= allocdim); 213 cstring.memcpy(data, from.data, from.dim * Log.sizeof); 214 dim = from.dim; 215 } 216 } 217 } 218 219 220 /* ============================ GC =============================== */ 221 222 223 class GCLock { } // just a dummy so we can get a global lock 224 225 226 const uint GCVERSION = 1; // increment every time we change interface 227 // to GC. 228 229 class GC 230 { 231 // For passing to debug code 232 static size_t line; 233 static char* file; 234 235 uint gcversion = GCVERSION; 236 237 Gcx *gcx; // implementation 238 static ClassInfo gcLock; // global lock 239 240 241 final void initialize() 242 { 243 gcLock = GCLock.classinfo; 244 gcx = cast(Gcx*)cstdlib.calloc(1, Gcx.sizeof); 245 if (!gcx) 246 onOutOfMemoryError(); 247 gcx.initialize(); 248 setStackBottom(rt_stackBottom()); 249 } 250 251 252 final void Dtor() 253 { 254 version (linux) 255 { 256 //debug(PRINTF) printf("Thread %x ", pthread_self()); 257 //debug(PRINTF) printf("GC.Dtor()\n"); 258 } 259 260 if (gcx) 261 { 262 gcx.Dtor(); 263 cstdlib.free(gcx); 264 gcx = null; 265 } 266 } 267 268 269 invariant 270 { 271 if (gcx) 272 { 273 gcx.thread_Invariant(); 274 } 275 } 276 277 final void monitor (void delegate() begin, void delegate(int, int) end) 278 { 279 gcx.collectBegin = begin; 280 gcx.collectEnd = end; 281 } 282 283 /** 284 * 285 */ 286 final void enable() 287 { 288 if (!thread_needLock()) 289 { 290 assert(gcx.disabled > 0); 291 gcx.disabled--; 292 } 293 else synchronized (gcLock) 294 { 295 assert(gcx.disabled > 0); 296 gcx.disabled--; 297 } 298 } 299 300 301 /** 302 * 303 */ 304 final void disable() 305 { 306 if (!thread_needLock()) 307 { 308 gcx.disabled++; 309 } 310 else synchronized (gcLock) 311 { 312 gcx.disabled++; 313 } 314 } 315 316 317 /** 318 * 319 */ 320 final uint getAttr(void* p) 321 { 322 if (!p) 323 { 324 return 0; 325 } 326 327 uint go() 328 { 329 Pool* pool = gcx.findPool(p); 330 uint oldb = 0; 331 332 if (pool) 333 { 334 auto biti = cast(size_t)(p - pool.baseAddr) / 16; 335 336 oldb = gcx.getBits(pool, biti); 337 } 338 return oldb; 339 } 340 341 if (!thread_needLock()) 342 { 343 return go(); 344 } 345 else synchronized (gcLock) 346 { 347 return go(); 348 } 349 } 350 351 352 /** 353 * 354 */ 355 final uint setAttr(void* p, uint mask) 356 { 357 if (!p) 358 { 359 return 0; 360 } 361 362 uint go() 363 { 364 Pool* pool = gcx.findPool(p); 365 uint oldb = 0; 366 367 if (pool) 368 { 369 auto biti = cast(size_t)(p - pool.baseAddr) / 16; 370 371 oldb = gcx.getBits(pool, biti); 372 gcx.setBits(pool, biti, mask); 373 } 374 return oldb; 375 } 376 377 if (!thread_needLock()) 378 { 379 return go(); 380 } 381 else synchronized (gcLock) 382 { 383 return go(); 384 } 385 } 386 387 388 /** 389 * 390 */ 391 final uint clrAttr(void* p, uint mask) 392 { 393 if (!p) 394 { 395 return 0; 396 } 397 398 uint go() 399 { 400 Pool* pool = gcx.findPool(p); 401 uint oldb = 0; 402 403 if (pool) 404 { 405 auto biti = cast(size_t)(p - pool.baseAddr) / 16; 406 407 oldb = gcx.getBits(pool, biti); 408 gcx.clrBits(pool, biti, mask); 409 } 410 return oldb; 411 } 412 413 if (!thread_needLock()) 414 { 415 return go(); 416 } 417 else synchronized (gcLock) 418 { 419 return go(); 420 } 421 } 422 423 424 /** 425 * 426 */ 427 final void *malloc(size_t size, uint bits = 0) 428 { 429 if (!size) 430 { 431 return null; 432 } 433 434 // Since a finalizer could launch a new thread, we always need to lock 435 // when collecting. The safest way to do this is to simply always lock 436 // when allocating. 437 synchronized (gcLock) 438 { 439 return mallocNoSync(size, bits); 440 } 441 } 442 443 444 // 445 // 446 // 447 private void *mallocNoSync(size_t size, uint bits = 0) 448 { 449 assert(size != 0); 450 451 void *p = null; 452 Bins bin; 453 454 //debug(PRINTF) printf("GC::malloc(size = %d, gcx = %p)\n", size, gcx); 455 assert(gcx); 456 //debug(PRINTF) printf("gcx.self = %x, pthread_self() = %x\n", gcx.self, pthread_self()); 457 458 size += SENTINEL_EXTRA; 459 460 // Compute size bin 461 // Cache previous binsize lookup - Dave Fladebo. 462 static size_t lastsize = -1; 463 static Bins lastbin; 464 if (size == lastsize) 465 bin = lastbin; 466 else 467 { 468 bin = gcx.findBin(size); 469 lastsize = size; 470 lastbin = bin; 471 } 472 473 if (bin < B_PAGE) 474 { 475 int state = gcx.disabled ? 1 : 0; 476 bool collected = false; 477 478 while (!gcx.bucket[bin] && !gcx.allocPage(bin)) 479 { 480 switch (state) 481 { 482 case 0: 483 auto freedpages = gcx.fullcollectshell(); 484 collected = true; 485 if (freedpages < gcx.npools * ((POOLSIZE / PAGESIZE) / 8)) 486 { /* Didn't free much, so try allocating more anyway. 487 * Note: freedpages is not the amount of memory freed, it's the amount 488 * of full pages freed. Perhaps this should instead be the amount of 489 * memory freed. 490 */ 491 gcx.newPool(1); 492 state = 2; 493 } 494 else 495 state = 1; 496 continue; 497 case 1: 498 gcx.newPool(1); 499 state = 2; 500 continue; 501 case 2: 502 if (collected) 503 onOutOfMemoryError(); 504 state = 0; 505 continue; 506 default: 507 assert(false); 508 } 509 } 510 p = gcx.bucket[bin]; 511 512 // Return next item from free list 513 gcx.bucket[bin] = (cast(List*)p).next; 514 if( !(bits & BlkAttr.NO_SCAN) ) 515 cstring.memset(p + size, 0, binsize[bin] - size); 516 //debug(PRINTF) printf("\tmalloc => %x\n", p); 517 debug (MEMSTOMP) cstring.memset(p, 0xF0, size); 518 } 519 else 520 { 521 p = gcx.bigAlloc(size); 522 if (!p) 523 onOutOfMemoryError(); 524 } 525 size -= SENTINEL_EXTRA; 526 p = sentinel_add(p); 527 sentinel_init(p, size); 528 gcx.log_malloc(p, size); 529 530 if (bits) 531 { 532 Pool *pool = gcx.findPool(p); 533 assert(pool); 534 535 gcx.setBits(pool, cast(size_t)(p - pool.baseAddr) / 16, bits); 536 } 537 return p; 538 } 539 540 541 /** 542 * 543 */ 544 final void *calloc(size_t size, uint bits = 0) 545 { 546 if (!size) 547 { 548 return null; 549 } 550 551 // Since a finalizer could launch a new thread, we always need to lock 552 // when collecting. The safest way to do this is to simply always lock 553 // when allocating. 554 synchronized (gcLock) 555 { 556 return callocNoSync(size, bits); 557 } 558 } 559 560 561 // 562 // 563 // 564 private void *callocNoSync(size_t size, uint bits = 0) 565 { 566 assert(size != 0); 567 568 //debug(PRINTF) printf("calloc: %x len %d\n", p, len); 569 void *p = mallocNoSync(size, bits); 570 cstring.memset(p, 0, size); 571 return p; 572 } 573 574 575 /** 576 * 577 */ 578 final void *realloc(void *p, size_t size, uint bits = 0) 579 { 580 // Since a finalizer could launch a new thread, we always need to lock 581 // when collecting. The safest way to do this is to simply always lock 582 // when allocating. 583 synchronized (gcLock) 584 { 585 return reallocNoSync(p, size, bits); 586 } 587 } 588 589 590 // 591 // 592 // 593 private void *reallocNoSync(void *p, size_t size, uint bits = 0) 594 { 595 if (!size) 596 { if (p) 597 { freeNoSync(p); 598 p = null; 599 } 600 } 601 else if (!p) 602 { 603 p = mallocNoSync(size, bits); 604 } 605 else 606 { void *p2; 607 size_t psize; 608 609 //debug(PRINTF) printf("GC::realloc(p = %x, size = %u)\n", p, size); 610 version (SENTINEL) 611 { 612 sentinel_Invariant(p); 613 psize = *sentinel_size(p); 614 if (psize != size) 615 { 616 if (psize) 617 { 618 Pool *pool = gcx.findPool(p); 619 620 if (pool) 621 { 622 auto biti = cast(size_t)(p - pool.baseAddr) / 16; 623 624 if (bits) 625 { 626 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS); 627 gcx.setBits(pool, biti, bits); 628 } 629 else 630 { 631 bits = gcx.getBits(pool, biti); 632 } 633 } 634 } 635 p2 = mallocNoSync(size, bits); 636 if (psize < size) 637 size = psize; 638 //debug(PRINTF) printf("\tcopying %d bytes\n",size); 639 cstring.memcpy(p2, p, size); 640 p = p2; 641 } 642 } 643 else 644 { 645 psize = gcx.findSize(p); // find allocated size 646 if (psize >= PAGESIZE && size >= PAGESIZE) 647 { 648 auto psz = psize / PAGESIZE; 649 auto newsz = (size + PAGESIZE - 1) / PAGESIZE; 650 if (newsz == psz) 651 return p; 652 653 auto pool = gcx.findPool(p); 654 auto pagenum = (p - pool.baseAddr) / PAGESIZE; 655 656 if (newsz < psz) 657 { // Shrink in place 658 synchronized (gcLock) 659 { 660 debug (MEMSTOMP) cstring.memset(p + size, 0xF2, psize - size); 661 pool.freePages(pagenum + newsz, psz - newsz); 662 } 663 return p; 664 } 665 else if (pagenum + newsz <= pool.npages) 666 { 667 // Attempt to expand in place 668 synchronized (gcLock) 669 { 670 for (size_t i = pagenum + psz; 1;) 671 { 672 if (i == pagenum + newsz) 673 { 674 debug (MEMSTOMP) cstring.memset(p + psize, 0xF0, size - psize); 675 cstring.memset(&pool.pagetable[pagenum + psz], B_PAGEPLUS, newsz - psz); 676 return p; 677 } 678 if (i == pool.ncommitted) 679 { 680 auto u = pool.extendPages(pagenum + newsz - pool.ncommitted); 681 if (u == OPFAIL) 682 break; 683 i = pagenum + newsz; 684 continue; 685 } 686 if (pool.pagetable[i] != B_FREE) 687 break; 688 i++; 689 } 690 } 691 } 692 } 693 if (psize < size || // if new size is bigger 694 psize > size * 2) // or less than half 695 { 696 if (psize) 697 { 698 Pool *pool = gcx.findPool(p); 699 700 if (pool) 701 { 702 auto biti = cast(size_t)(p - pool.baseAddr) / 16; 703 704 if (bits) 705 { 706 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS); 707 gcx.setBits(pool, biti, bits); 708 } 709 else 710 { 711 bits = gcx.getBits(pool, biti); 712 } 713 } 714 } 715 p2 = mallocNoSync(size, bits); 716 if (psize < size) 717 size = psize; 718 //debug(PRINTF) printf("\tcopying %d bytes\n",size); 719 cstring.memcpy(p2, p, size); 720 p = p2; 721 } 722 } 723 } 724 return p; 725 } 726 727 728 /** 729 * Attempt to in-place enlarge the memory block pointed to by p by at least 730 * minbytes beyond its current capacity, up to a maximum of maxsize. This 731 * does not attempt to move the memory block (like realloc() does). 732 * 733 * Returns: 734 * 0 if could not extend p, 735 * total size of entire memory block if successful. 736 */ 737 final size_t extend(void* p, size_t minsize, size_t maxsize) 738 { 739 if (!thread_needLock()) 740 { 741 return extendNoSync(p, minsize, maxsize); 742 } 743 else synchronized (gcLock) 744 { 745 return extendNoSync(p, minsize, maxsize); 746 } 747 } 748 749 750 // 751 // 752 // 753 private size_t extendNoSync(void* p, size_t minsize, size_t maxsize) 754 in 755 { 756 assert( minsize <= maxsize ); 757 } 758 body 759 { 760 //debug(PRINTF) printf("GC::extend(p = %x, minsize = %u, maxsize = %u)\n", p, minsize, maxsize); 761 version (SENTINEL) 762 { 763 return 0; 764 } 765 auto psize = gcx.findSize(p); // find allocated size 766 if (psize < PAGESIZE) 767 return 0; // cannot extend buckets 768 769 auto psz = psize / PAGESIZE; 770 auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE; 771 auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE; 772 773 auto pool = gcx.findPool(p); 774 auto pagenum = (p - pool.baseAddr) / PAGESIZE; 775 776 size_t sz; 777 for (sz = 0; sz < maxsz; sz++) 778 { 779 auto i = pagenum + psz + sz; 780 if (i == pool.ncommitted) 781 break; 782 if (pool.pagetable[i] != B_FREE) 783 { if (sz < minsz) 784 return 0; 785 break; 786 } 787 } 788 if (sz >= minsz) 789 { 790 } 791 else if (pagenum + psz + sz == pool.ncommitted) 792 { 793 auto u = pool.extendPages(minsz - sz); 794 if (u == OPFAIL) 795 return 0; 796 sz = minsz; 797 } 798 else 799 return 0; 800 debug (MEMSTOMP) memset(p + psize, 0xF0, (psz + sz) * PAGESIZE - psize); 801 memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz); 802 if (p == gcx.cached_size_key) 803 gcx.cached_size_val = (psz + sz) * PAGESIZE; 804 if (p == gcx.cached_info_key) 805 gcx.cached_info_val.size = (psz + sz) * PAGESIZE; 806 return (psz + sz) * PAGESIZE; 807 } 808 809 810 /** 811 * 812 */ 813 final size_t reserve(size_t size) 814 { 815 if (!size) 816 { 817 return 0; 818 } 819 820 if (!thread_needLock()) 821 { 822 return reserveNoSync(size); 823 } 824 else synchronized (gcLock) 825 { 826 return reserveNoSync(size); 827 } 828 } 829 830 831 // 832 // 833 // 834 private size_t reserveNoSync(size_t size) 835 { 836 assert(size != 0); 837 assert(gcx); 838 839 return gcx.reserve(size); 840 } 841 842 843 /** 844 * 845 */ 846 final void free(void *p) 847 { 848 if (!p) 849 { 850 return; 851 } 852 853 if (!thread_needLock()) 854 { 855 return freeNoSync(p); 856 } 857 else synchronized (gcLock) 858 { 859 return freeNoSync(p); 860 } 861 } 862 863 864 // 865 // 866 // 867 private void freeNoSync(void *p) 868 { 869 assert (p); 870 871 Pool* pool; 872 size_t pagenum; 873 Bins bin; 874 size_t biti; 875 876 // Find which page it is in 877 pool = gcx.findPool(p); 878 if (!pool) // if not one of ours 879 return; // ignore 880 sentinel_Invariant(p); 881 p = sentinel_sub(p); 882 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE; 883 biti = cast(size_t)(p - pool.baseAddr) / 16; 884 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS); 885 886 bin = cast(Bins)pool.pagetable[pagenum]; 887 if (bin == B_PAGE) // if large alloc 888 { size_t npages; 889 size_t n; 890 891 // Free pages 892 npages = 1; 893 n = pagenum; 894 while (++n < pool.ncommitted && pool.pagetable[n] == B_PAGEPLUS) 895 npages++; 896 debug (MEMSTOMP) cstring.memset(p, 0xF2, npages * PAGESIZE); 897 pool.freePages(pagenum, npages); 898 } 899 else 900 { // Add to free list 901 List *list = cast(List*)p; 902 903 debug (MEMSTOMP) cstring.memset(p, 0xF2, binsize[bin]); 904 905 list.next = gcx.bucket[bin]; 906 gcx.bucket[bin] = list; 907 } 908 gcx.log_free(sentinel_add(p)); 909 } 910 911 912 /** 913 * Determine the base address of the block containing p. If p is not a gc 914 * allocated pointer, return null. 915 */ 916 final void* addrOf(void *p) 917 { 918 if (!p) 919 { 920 return null; 921 } 922 923 if (!thread_needLock()) 924 { 925 return addrOfNoSync(p); 926 } 927 else synchronized (gcLock) 928 { 929 return addrOfNoSync(p); 930 } 931 } 932 933 934 // 935 // 936 // 937 final void* addrOfNoSync(void *p) 938 { 939 if (!p) 940 { 941 return null; 942 } 943 944 return gcx.findBase(p); 945 } 946 947 948 /** 949 * Determine the allocated size of pointer p. If p is an interior pointer 950 * or not a gc allocated pointer, return 0. 951 */ 952 final size_t sizeOf(void *p) 953 { 954 if (!p) 955 { 956 return 0; 957 } 958 959 if (!thread_needLock()) 960 { 961 return sizeOfNoSync(p); 962 } 963 else synchronized (gcLock) 964 { 965 return sizeOfNoSync(p); 966 } 967 } 968 969 970 // 971 // 972 // 973 private size_t sizeOfNoSync(void *p) 974 { 975 assert (p); 976 977 version (SENTINEL) 978 { 979 p = sentinel_sub(p); 980 size_t size = gcx.findSize(p); 981 982 // Check for interior pointer 983 // This depends on: 984 // 1) size is a power of 2 for less than PAGESIZE values 985 // 2) base of memory pool is aligned on PAGESIZE boundary 986 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1)) 987 size = 0; 988 return size ? size - SENTINEL_EXTRA : 0; 989 } 990 else 991 { 992 size_t size = gcx.findSize(p); 993 994 // Check for interior pointer 995 // This depends on: 996 // 1) size is a power of 2 for less than PAGESIZE values 997 // 2) base of memory pool is aligned on PAGESIZE boundary 998 if (cast(size_t)p & (size - 1) & (PAGESIZE - 1)) 999 return 0; 1000 return size; 1001 } 1002 } 1003 1004 1005 /** 1006 * Determine the base address of the block containing p. If p is not a gc 1007 * allocated pointer, return null. 1008 */ 1009 final BlkInfo query(void *p) 1010 { 1011 if (!p) 1012 { 1013 BlkInfo i; 1014 return i; 1015 } 1016 1017 if (!thread_needLock()) 1018 { 1019 return queryNoSync(p); 1020 } 1021 else synchronized (gcLock) 1022 { 1023 return queryNoSync(p); 1024 } 1025 } 1026 1027 1028 // 1029 // 1030 // 1031 final BlkInfo queryNoSync(void *p) 1032 { 1033 assert(p); 1034 1035 return gcx.getInfo(p); 1036 } 1037 1038 1039 /** 1040 * Verify that pointer p: 1041 * 1) belongs to this memory pool 1042 * 2) points to the start of an allocated piece of memory 1043 * 3) is not on a free list 1044 */ 1045 final void check(void *p) 1046 { 1047 if (!p) 1048 { 1049 return; 1050 } 1051 1052 if (!thread_needLock()) 1053 { 1054 checkNoSync(p); 1055 } 1056 else synchronized (gcLock) 1057 { 1058 checkNoSync(p); 1059 } 1060 } 1061 1062 1063 // 1064 // 1065 // 1066 private void checkNoSync(void *p) 1067 { 1068 assert(p); 1069 1070 sentinel_Invariant(p); 1071 debug (PTRCHECK) 1072 { 1073 Pool* pool; 1074 size_t pagenum; 1075 Bins bin; 1076 size_t size; 1077 1078 p = sentinel_sub(p); 1079 pool = gcx.findPool(p); 1080 assert(pool); 1081 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE; 1082 bin = cast(Bins)pool.pagetable[pagenum]; 1083 assert(bin <= B_PAGE); 1084 size = binsize[bin]; 1085 assert((cast(size_t)p & (size - 1)) == 0); 1086 1087 debug (PTRCHECK2) 1088 { 1089 if (bin < B_PAGE) 1090 { 1091 // Check that p is not on a free list 1092 List *list; 1093 1094 for (list = gcx.bucket[bin]; list; list = list.next) 1095 { 1096 assert(cast(void*)list != p); 1097 } 1098 } 1099 } 1100 } 1101 } 1102 1103 1104 // 1105 // 1106 // 1107 private void setStackBottom(void *p) 1108 { 1109 version (STACKGROWSDOWN) 1110 { 1111 //p = (void *)((uint *)p + 4); 1112 if (p > gcx.stackBottom) 1113 { 1114 //debug(PRINTF) printf("setStackBottom(%x)\n", p); 1115 gcx.stackBottom = p; 1116 } 1117 } 1118 else 1119 { 1120 //p = (void *)((uint *)p - 4); 1121 if (p < gcx.stackBottom) 1122 { 1123 //debug(PRINTF) printf("setStackBottom(%x)\n", p); 1124 gcx.stackBottom = cast(char*)p; 1125 } 1126 } 1127 } 1128 1129 1130 /** 1131 * add p to list of roots 1132 */ 1133 final void addRoot(void *p) 1134 { 1135 if (!p) 1136 { 1137 return; 1138 } 1139 1140 if (!thread_needLock()) 1141 { 1142 gcx.addRoot(p); 1143 } 1144 else synchronized (gcLock) 1145 { 1146 gcx.addRoot(p); 1147 } 1148 } 1149 1150 1151 /** 1152 * remove p from list of roots 1153 */ 1154 final void removeRoot(void *p) 1155 { 1156 if (!p) 1157 { 1158 return; 1159 } 1160 1161 if (!thread_needLock()) 1162 { 1163 gcx.removeRoot(p); 1164 } 1165 else synchronized (gcLock) 1166 { 1167 gcx.removeRoot(p); 1168 } 1169 } 1170 1171 1172 /** 1173 * add range to scan for roots 1174 */ 1175 final void addRange(void *p, size_t sz) 1176 { 1177 if (!p || !sz) 1178 { 1179 return; 1180 } 1181 1182 //debug(PRINTF) printf("+GC.addRange(pbot = x%x, ptop = x%x)\n", pbot, ptop); 1183 if (!thread_needLock()) 1184 { 1185 gcx.addRange(p, p + sz); 1186 } 1187 else synchronized (gcLock) 1188 { 1189 gcx.addRange(p, p + sz); 1190 } 1191 //debug(PRINTF) printf("-GC.addRange()\n"); 1192 } 1193 1194 1195 /** 1196 * remove range 1197 */ 1198 final void removeRange(void *p) 1199 { 1200 if (!p) 1201 { 1202 return; 1203 } 1204 1205 if (!thread_needLock()) 1206 { 1207 gcx.removeRange(p); 1208 } 1209 else synchronized (gcLock) 1210 { 1211 gcx.removeRange(p); 1212 } 1213 } 1214 1215 1216 /** 1217 * do full garbage collection 1218 */ 1219 final void fullCollect() 1220 { 1221 debug(PRINTF) printf("GC.fullCollect()\n"); 1222 1223 if (!thread_needLock()) 1224 { 1225 gcx.fullcollectshell(); 1226 } 1227 else synchronized (gcLock) 1228 { 1229 gcx.fullcollectshell(); 1230 } 1231 1232 version (none) 1233 { 1234 GCStats stats; 1235 1236 getStats(stats); 1237 debug(PRINTF) printf("poolsize = %x, usedsize = %x, freelistsize = %x\n", 1238 stats.poolsize, stats.usedsize, stats.freelistsize); 1239 } 1240 1241 gcx.log_collect(); 1242 } 1243 1244 1245 /** 1246 * do full garbage collection ignoring roots 1247 */ 1248 final void fullCollectNoStack() 1249 { 1250 // Since a finalizer could launch a new thread, we always need to lock 1251 // when collecting. 1252 synchronized (gcLock) 1253 { 1254 gcx.noStack++; 1255 gcx.fullcollectshell(); 1256 gcx.noStack--; 1257 } 1258 } 1259 1260 1261 /** 1262 * minimize free space usage 1263 */ 1264 final void minimize() 1265 { 1266 if (!thread_needLock()) 1267 { 1268 gcx.minimize(); 1269 } 1270 else synchronized (gcLock) 1271 { 1272 gcx.minimize(); 1273 } 1274 } 1275 1276 1277 /** 1278 * Retrieve statistics about garbage collection. 1279 * Useful for debugging and tuning. 1280 */ 1281 final void getStats(out GCStats stats) 1282 { 1283 if (!thread_needLock()) 1284 { 1285 getStatsNoSync(stats); 1286 } 1287 else synchronized (gcLock) 1288 { 1289 getStatsNoSync(stats); 1290 } 1291 } 1292 1293 1294 // 1295 // 1296 // 1297 private void getStatsNoSync(out GCStats stats) 1298 { 1299 size_t psize = 0; 1300 size_t flsize = 0; 1301 1302 size_t n; 1303 1304 //debug(PRINTF) printf("getStats()\n"); 1305 cstring.memset(&stats, 0, GCStats.sizeof); 1306 1307 for (n = 0; n < gcx.npools; n++) 1308 { Pool *pool = gcx.pooltable[n]; 1309 1310 psize += pool.ncommitted * PAGESIZE; 1311 for (size_t j = 0; j < pool.ncommitted; j++) 1312 { 1313 Bins bin = cast(Bins)pool.pagetable[j]; 1314 if (bin == B_FREE) 1315 stats.freeblocks++; 1316 else if (bin == B_PAGE) 1317 { 1318 stats.pageblocks++; 1319 } 1320 } 1321 } 1322 1323 for (n = 0; n < B_PAGE; n++) 1324 { 1325 //debug(PRINTF) printf("bin %d\n", n); 1326 for (List *list = gcx.bucket[n]; list; list = list.next) 1327 { 1328 //debug(PRINTF) printf("\tlist %x\n", list); 1329 flsize += binsize[n]; 1330 } 1331 } 1332 1333 stats.poolsize = psize; 1334 stats.usedsize = psize - (flsize + stats.freeblocks * PAGESIZE); 1335 stats.freelistsize = flsize; 1336 } 1337 1338 /******************* weak-reference support *********************/ 1339 1340 //call locked if necessary 1341 private T locked(T)(in T delegate() code) 1342 { 1343 if (thread_needLock) 1344 synchronized(gcLock) return code(); 1345 else 1346 return code(); 1347 } 1348 1349 private struct WeakPointer 1350 { 1351 Object reference; 1352 1353 void ondestroy(Object r) 1354 { 1355 assert(r is reference); 1356 //lock for memory consistency (parallel readers) 1357 // 1358 //also ensures that weakpointerDestroy can be called while another 1359 //thread is freeing the reference with "delete" 1360 locked!(void)({ reference = null; }); 1361 } 1362 } 1363 1364 /** 1365 * Create a weak pointer to the given object. 1366 * Returns a pointer to an opaque struct allocated in C memory. 1367 */ 1368 final void* weakpointerCreate( Object r ) 1369 { 1370 if (r) 1371 { 1372 //must be allocated in C memory 1373 //1. to hide the reference from the GC 1374 //2. the GC doesn't scan delegates added by rt_attachDisposeEvent for references 1375 auto wp = cast(WeakPointer*)(cstdlib.malloc(WeakPointer.sizeof)); 1376 if (!wp) 1377 onOutOfMemoryError(); 1378 wp.reference = r; 1379 rt_attachDisposeEvent(r, &wp.ondestroy); 1380 return wp; 1381 } 1382 return null; 1383 } 1384 1385 /** 1386 * Destroy a weak pointer returned by weakpointerCreate(). 1387 * If null is passed, nothing happens. 1388 */ 1389 final void weakpointerDestroy( void* p ) 1390 { 1391 if (p) 1392 { 1393 auto wp = cast(WeakPointer*)p; 1394 //must be extra careful about the GC or parallel threads finalizing the 1395 //reference at the same time 1396 locked!(void)({ 1397 if (wp.reference) 1398 // don't worry about locking; we've stopped everything already 1399 // and locked the whole gc 1400 rt_detachDisposeEventNoLock(wp.reference, &wp.ondestroy); 1401 }); 1402 cstdlib.free(wp); 1403 } 1404 } 1405 1406 /** 1407 * Query a weak pointer and return either the object passed to 1408 * weakpointerCreate, or null if it was free'd in the meantime. 1409 * If null is passed, null is returned. 1410 */ 1411 final Object weakpointerGet( void* p ) 1412 { 1413 if (p) 1414 { 1415 //NOTE: could avoid the lock by using Fawzi style GC counters 1416 // but that'd require core.sync.Atomic and lots of care about memory consistency 1417 // it's an optional optimization 1418 //see http://dsource.org/projects/tango/browser/trunk/user/tango/core/Lifetime.d?rev=5100#L158 1419 return locked!(Object)({ 1420 return (cast(WeakPointer*)p).reference; 1421 }); 1422 } 1423 return null; 1424 } 1425 } 1426 1427 1428 /* ============================ Gcx =============================== */ 1429 1430 enum 1431 { PAGESIZE = 4096, 1432 COMMITSIZE = (4096*16), 1433 POOLSIZE = (4096*256), 1434 } 1435 1436 1437 enum 1438 { 1439 B_16, 1440 B_32, 1441 B_64, 1442 B_128, 1443 B_256, 1444 B_512, 1445 B_1024, 1446 B_2048, 1447 B_PAGE, // start of large alloc 1448 B_PAGEPLUS, // continuation of large alloc 1449 B_FREE, // free page 1450 B_UNCOMMITTED, // memory not committed for this page 1451 B_MAX 1452 } 1453 1454 1455 alias ubyte Bins; 1456 1457 1458 struct List 1459 { 1460 List *next; 1461 } 1462 1463 1464 struct Range 1465 { 1466 void *pbot; 1467 void *ptop; 1468 } 1469 1470 1471 const size_t binsize[B_MAX] = [ 16,32,64,128,256,512,1024,2048,4096 ]; 1472 const size_t notbinsize[B_MAX] = [ ~(16-1),~(32-1),~(64-1),~(128-1),~(256-1), 1473 ~(512-1),~(1024-1),~(2048-1),~(4096-1) ]; 1474 1475 /* ============================ Gcx =============================== */ 1476 1477 1478 struct Gcx 1479 { 1480 debug (THREADINVARIANT) 1481 { 1482 pthread_t self; 1483 void thread_Invariant() 1484 { 1485 if (self != pthread_self()) 1486 printf("thread_Invariant(): gcx = %x, self = %x, pthread_self() = %x\n", this, self, pthread_self()); 1487 assert(self == pthread_self()); 1488 } 1489 } 1490 else 1491 { 1492 void thread_Invariant() { } 1493 } 1494 1495 void *cached_size_key; 1496 size_t cached_size_val; 1497 1498 void *cached_info_key; 1499 BlkInfo cached_info_val; 1500 1501 size_t nroots; 1502 size_t rootdim; 1503 void **roots; 1504 1505 size_t nranges; 1506 size_t rangedim; 1507 Range *ranges; 1508 1509 uint noStack; // !=0 means don't scan stack 1510 uint log; // turn on logging 1511 uint anychanges; 1512 void *stackBottom; 1513 uint inited; 1514 int disabled; // turn off collections if >0 1515 1516 byte *minAddr; // min(baseAddr) 1517 byte *maxAddr; // max(topAddr) 1518 1519 size_t npools; 1520 Pool **pooltable; 1521 1522 List *bucket[B_MAX]; // free list for each size 1523 1524 1525 void delegate() collectBegin; 1526 void delegate(int freed, int pagebytes) collectEnd; 1527 1528 void initialize() 1529 { int dummy; 1530 1531 (cast(byte*)this)[0 .. Gcx.sizeof] = 0; 1532 stackBottom = cast(char*)&dummy; 1533 log_init(); 1534 debug (THREADINVARIANT) 1535 self = pthread_self(); 1536 //printf("gcx = %p, self = %x\n", this, self); 1537 inited = 1; 1538 } 1539 1540 1541 void Dtor() 1542 { 1543 inited = 0; 1544 1545 for (size_t i = 0; i < npools; i++) 1546 { Pool *pool = pooltable[i]; 1547 1548 pool.Dtor(); 1549 cstdlib.free(pool); 1550 } 1551 if (pooltable) 1552 cstdlib.free(pooltable); 1553 1554 if (roots) 1555 cstdlib.free(roots); 1556 1557 if (ranges) 1558 cstdlib.free(ranges); 1559 } 1560 1561 1562 void Invariant() { } 1563 1564 1565 invariant 1566 { 1567 if (inited) 1568 { 1569 //printf("Gcx.invariant(): this = %p\n", this); 1570 size_t i; 1571 1572 // Assure we're called on the right thread 1573 debug (THREADINVARIANT) assert(self == pthread_self()); 1574 1575 for (i = 0; i < npools; i++) 1576 { Pool *pool = pooltable[i]; 1577 1578 pool.Invariant(); 1579 if (i == 0) 1580 { 1581 assert(minAddr == pool.baseAddr); 1582 } 1583 if (i + 1 < npools) 1584 { 1585 assert(pool.opCmp(pooltable[i + 1]) < 0); 1586 } 1587 else if (i + 1 == npools) 1588 { 1589 assert(maxAddr == pool.topAddr); 1590 } 1591 } 1592 1593 if (roots) 1594 { 1595 assert(rootdim != 0); 1596 assert(nroots <= rootdim); 1597 } 1598 1599 if (ranges) 1600 { 1601 assert(rangedim != 0); 1602 assert(nranges <= rangedim); 1603 1604 for (i = 0; i < nranges; i++) 1605 { 1606 assert(ranges[i].pbot); 1607 assert(ranges[i].ptop); 1608 assert(ranges[i].pbot <= ranges[i].ptop); 1609 } 1610 } 1611 1612 for (i = 0; i < B_PAGE; i++) 1613 { 1614 for (List *list = bucket[i]; list; list = list.next) 1615 { 1616 } 1617 } 1618 } 1619 } 1620 1621 1622 /** 1623 * 1624 */ 1625 void addRoot(void *p) 1626 { 1627 if (nroots == rootdim) 1628 { 1629 size_t newdim = rootdim * 2 + 16; 1630 void** newroots; 1631 1632 newroots = cast(void**)cstdlib.malloc(newdim * newroots[0].sizeof); 1633 if (!newroots) 1634 onOutOfMemoryError(); 1635 if (roots) 1636 { cstring.memcpy(newroots, roots, nroots * newroots[0].sizeof); 1637 cstdlib.free(roots); 1638 } 1639 roots = newroots; 1640 rootdim = newdim; 1641 } 1642 roots[nroots] = p; 1643 nroots++; 1644 } 1645 1646 1647 /** 1648 * 1649 */ 1650 void removeRoot(void *p) 1651 { 1652 for (size_t i = nroots; i--;) 1653 { 1654 if (roots[i] == p) 1655 { 1656 nroots--; 1657 cstring.memmove(roots + i, roots + i + 1, (nroots - i) * roots[0].sizeof); 1658 return; 1659 } 1660 } 1661 assert(0); 1662 } 1663 1664 1665 /** 1666 * 1667 */ 1668 void addRange(void *pbot, void *ptop) 1669 { 1670 debug(PRINTF) debug(THREADINVARIANT) printf("Thread %x ", pthread_self()); 1671 debug(PRINTF) printf("%x.Gcx::addRange(%x, %x), nranges = %d\n", this, pbot, ptop, nranges); 1672 if (nranges == rangedim) 1673 { 1674 size_t newdim = rangedim * 2 + 16; 1675 Range *newranges; 1676 1677 newranges = cast(Range*)cstdlib.malloc(newdim * newranges[0].sizeof); 1678 if (!newranges) 1679 onOutOfMemoryError(); 1680 if (ranges) 1681 { cstring.memcpy(newranges, ranges, nranges * newranges[0].sizeof); 1682 cstdlib.free(ranges); 1683 } 1684 ranges = newranges; 1685 rangedim = newdim; 1686 } 1687 ranges[nranges].pbot = pbot; 1688 ranges[nranges].ptop = ptop; 1689 nranges++; 1690 } 1691 1692 1693 /** 1694 * 1695 */ 1696 void removeRange(void *pbot) 1697 { 1698 debug(PRINTF) debug(THREADINVARIANT) printf("Thread %x ", pthread_self()); 1699 debug(PRINTF) printf("%x.Gcx.removeRange(%x), nranges = %d\n", this, pbot, nranges); 1700 for (size_t i = nranges; i--;) 1701 { 1702 if (ranges[i].pbot == pbot) 1703 { 1704 nranges--; 1705 cstring.memmove(ranges + i, ranges + i + 1, (nranges - i) * ranges[0].sizeof); 1706 return; 1707 } 1708 } 1709 debug(PRINTF) printf("Wrong thread\n"); 1710 1711 // This is a fatal error, but ignore it. 1712 // The problem is that we can get a Close() call on a thread 1713 // other than the one the range was allocated on. 1714 //assert(zero); 1715 } 1716 1717 1718 /** 1719 * Find Pool that pointer is in. 1720 * Return null if not in a Pool. 1721 * Assume pooltable[] is sorted. 1722 */ 1723 Pool *findPool(void *p) 1724 { 1725 if (p >= minAddr && p < maxAddr) 1726 { 1727 if (npools <= 1) 1728 { 1729 return npools == 0 ? null : pooltable[0]; 1730 } 1731 1732 /* The pooltable[] is sorted by address, so do a binary search 1733 */ 1734 auto pt = pooltable; 1735 int low = 0; 1736 int high = npools - 1; 1737 while (low <= high) 1738 { 1739 size_t mid = (low + high) >> 1; 1740 auto pool = pt[mid]; 1741 if (p < pool.baseAddr) 1742 high = mid - 1; 1743 else if (p >= pool.topAddr) 1744 low = mid + 1; 1745 else 1746 return pool; 1747 } 1748 } 1749 return null; 1750 } 1751 1752 1753 /** 1754 * Find base address of block containing pointer p. 1755 * Returns null if not a gc'd pointer 1756 */ 1757 void* findBase(void *p) 1758 { 1759 Pool *pool; 1760 1761 pool = findPool(p); 1762 if (pool) 1763 { 1764 size_t offset = cast(size_t)(p - pool.baseAddr); 1765 size_t pn = offset / PAGESIZE; 1766 Bins bin = cast(Bins)pool.pagetable[pn]; 1767 1768 // Adjust bit to be at start of allocated memory block 1769 if (bin <= B_PAGE) 1770 { 1771 return pool.baseAddr + (offset & notbinsize[bin]); 1772 } 1773 else if (bin == B_PAGEPLUS) 1774 { 1775 do 1776 { --pn, offset -= PAGESIZE; 1777 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS); 1778 1779 return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1))); 1780 } 1781 else 1782 { 1783 // we are in a B_FREE or B_UNCOMMITTED page 1784 return null; 1785 } 1786 } 1787 return null; 1788 } 1789 1790 1791 /** 1792 * Find size of pointer p. 1793 * Returns 0 if not a gc'd pointer 1794 */ 1795 size_t findSize(void *p) 1796 { 1797 Pool* pool; 1798 size_t size = 0; 1799 1800 if (USE_CACHE && p == cached_size_key) 1801 return cached_size_val; 1802 1803 pool = findPool(p); 1804 if (pool) 1805 { 1806 size_t pagenum; 1807 Bins bin; 1808 1809 pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE; 1810 bin = cast(Bins)pool.pagetable[pagenum]; 1811 size = binsize[bin]; 1812 if (bin == B_PAGE) 1813 { size_t npages = pool.ncommitted; 1814 ubyte* pt; 1815 size_t i; 1816 1817 pt = &pool.pagetable[0]; 1818 for (i = pagenum + 1; i < npages; i++) 1819 { 1820 if (pt[i] != B_PAGEPLUS) 1821 break; 1822 } 1823 size = (i - pagenum) * PAGESIZE; 1824 } 1825 cached_size_key = p; 1826 cached_size_val = size; 1827 } 1828 return size; 1829 } 1830 1831 1832 /** 1833 * 1834 */ 1835 BlkInfo getInfo(void* p) 1836 { 1837 Pool* pool; 1838 BlkInfo info; 1839 1840 if (USE_CACHE && p == cached_info_key) 1841 return cached_info_val; 1842 1843 pool = findPool(p); 1844 if (pool) 1845 { 1846 size_t offset = cast(size_t)(p - pool.baseAddr); 1847 size_t pn = offset / PAGESIZE; 1848 Bins bin = cast(Bins)pool.pagetable[pn]; 1849 1850 //////////////////////////////////////////////////////////////////// 1851 // findAddr 1852 //////////////////////////////////////////////////////////////////// 1853 1854 if (bin <= B_PAGE) 1855 { 1856 info.base = cast(void*)((cast(size_t)p) & notbinsize[bin]); 1857 } 1858 else if (bin == B_PAGEPLUS) 1859 { 1860 do 1861 { --pn, offset -= PAGESIZE; 1862 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS); 1863 1864 info.base = pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1))); 1865 1866 // fix bin for use by size calc below 1867 bin = cast(Bins)pool.pagetable[pn]; 1868 } 1869 1870 //////////////////////////////////////////////////////////////////// 1871 // findSize 1872 //////////////////////////////////////////////////////////////////// 1873 1874 info.size = binsize[bin]; 1875 if (bin == B_PAGE) 1876 { size_t npages = pool.ncommitted; 1877 ubyte* pt; 1878 size_t i; 1879 1880 pt = &pool.pagetable[0]; 1881 for (i = pn + 1; i < npages; i++) 1882 { 1883 if (pt[i] != B_PAGEPLUS) 1884 break; 1885 } 1886 info.size = (i - pn) * PAGESIZE; 1887 } 1888 1889 //////////////////////////////////////////////////////////////////// 1890 // getBits 1891 //////////////////////////////////////////////////////////////////// 1892 1893 assert(p >= info.base && p< info.base + info.size); 1894 info.attr = getBits(pool, cast(size_t) (info.base - pool.baseAddr) / 16); 1895 1896 cached_info_key = p; 1897 cached_info_val = info; 1898 } 1899 return info; 1900 } 1901 1902 1903 /** 1904 * Compute bin for size. 1905 */ 1906 static Bins findBin(size_t size) 1907 { Bins bin; 1908 1909 if (size <= 256) 1910 { 1911 if (size <= 64) 1912 { 1913 if (size <= 16) 1914 bin = B_16; 1915 else if (size <= 32) 1916 bin = B_32; 1917 else 1918 bin = B_64; 1919 } 1920 else 1921 { 1922 if (size <= 128) 1923 bin = B_128; 1924 else 1925 bin = B_256; 1926 } 1927 } 1928 else 1929 { 1930 if (size <= 1024) 1931 { 1932 if (size <= 512) 1933 bin = B_512; 1934 else 1935 bin = B_1024; 1936 } 1937 else 1938 { 1939 if (size <= 2048) 1940 bin = B_2048; 1941 else 1942 bin = B_PAGE; 1943 } 1944 } 1945 return bin; 1946 } 1947 1948 1949 /** 1950 * Allocate a new pool of at least size bytes. 1951 * Sort it into pooltable[]. 1952 * Mark all memory in the pool as B_FREE. 1953 * Return the actual number of bytes reserved or 0 on error. 1954 */ 1955 size_t reserve(size_t size) 1956 { 1957 size_t npages = (size + PAGESIZE - 1) / PAGESIZE; 1958 Pool* pool = newPool(npages); 1959 1960 if (!pool || pool.extendPages(npages) == OPFAIL) 1961 return 0; 1962 return pool.ncommitted * PAGESIZE; 1963 } 1964 1965 1966 /** 1967 * Minimizes physical memory usage by returning free pools to the OS. 1968 */ 1969 void minimize() 1970 { 1971 size_t n; 1972 size_t pn; 1973 Pool* pool; 1974 size_t ncommitted; 1975 1976 for (n = 0; n < npools; n++) 1977 { 1978 pool = pooltable[n]; 1979 ncommitted = pool.ncommitted; 1980 for (pn = 0; pn < ncommitted; pn++) 1981 { 1982 if (cast(Bins)pool.pagetable[pn] != B_FREE) 1983 break; 1984 } 1985 if (pn < ncommitted) 1986 { 1987 continue; 1988 } 1989 pool.Dtor(); 1990 cstdlib.free(pool); 1991 cstring.memmove(pooltable + n, 1992 pooltable + n + 1, 1993 (--npools - n) * (Pool*).sizeof); 1994 n--; // without this, we are skipping the first moved pool 1995 } 1996 minAddr = pooltable[0].baseAddr; 1997 maxAddr = pooltable[npools - 1].topAddr; 1998 } 1999 2000 2001 /** 2002 * Allocate a chunk of memory that is larger than a page. 2003 * Return null if out of memory. 2004 */ 2005 void *bigAlloc(size_t size) 2006 { 2007 Pool* pool; 2008 size_t npages; 2009 size_t n; 2010 size_t pn; 2011 size_t freedpages; 2012 void* p; 2013 int state; 2014 bool collected = false; 2015 2016 npages = (size + PAGESIZE - 1) / PAGESIZE; 2017 2018 for (state = disabled ? 1 : 0; ; ) 2019 { 2020 // This code could use some refinement when repeatedly 2021 // allocating very large arrays. 2022 2023 for (n = 0; n < npools; n++) 2024 { 2025 pool = pooltable[n]; 2026 pn = pool.allocPages(npages); 2027 if (pn != OPFAIL) 2028 goto L1; 2029 } 2030 2031 // Failed 2032 switch (state) 2033 { 2034 case 0: 2035 // Try collecting 2036 collected = true; 2037 freedpages = fullcollectshell(); 2038 if (freedpages >= npools * ((POOLSIZE / PAGESIZE) / 4)) 2039 { state = 1; 2040 continue; 2041 } 2042 // Release empty pools to prevent bloat 2043 minimize(); 2044 // Allocate new pool 2045 pool = newPool(npages); 2046 if (!pool) 2047 { state = 2; 2048 continue; 2049 } 2050 pn = pool.allocPages(npages); 2051 assert(pn != OPFAIL); 2052 goto L1; 2053 case 1: 2054 // Release empty pools to prevent bloat 2055 minimize(); 2056 // Allocate new pool 2057 pool = newPool(npages); 2058 if (!pool) 2059 { 2060 if (collected) 2061 goto Lnomemory; 2062 state = 0; 2063 continue; 2064 } 2065 pn = pool.allocPages(npages); 2066 assert(pn != OPFAIL); 2067 goto L1; 2068 case 2: 2069 goto Lnomemory; 2070 default: 2071 assert(false); 2072 } 2073 } 2074 2075 L1: 2076 pool.pagetable[pn] = B_PAGE; 2077 if (npages > 1) 2078 cstring.memset(&pool.pagetable[pn + 1], B_PAGEPLUS, npages - 1); 2079 p = pool.baseAddr + pn * PAGESIZE; 2080 cstring.memset(cast(char *)p + size, 0, npages * PAGESIZE - size); 2081 debug (MEMSTOMP) cstring.memset(p, 0xF1, size); 2082 //debug(PRINTF) printf("\tp = %x\n", p); 2083 return p; 2084 2085 Lnomemory: 2086 return null; // let caller handle the error 2087 } 2088 2089 2090 /** 2091 * Allocate a new pool with at least npages in it. 2092 * Sort it into pooltable[]. 2093 * Return null if failed. 2094 */ 2095 Pool *newPool(size_t npages) 2096 { 2097 Pool* pool; 2098 Pool** newpooltable; 2099 size_t newnpools; 2100 size_t i; 2101 2102 2103 debug(PRINTF) printf("************Gcx::newPool(npages = %x)****************\n", npages); 2104 2105 // Round up to COMMITSIZE pages 2106 npages = (npages + (COMMITSIZE/PAGESIZE) - 1) & ~(COMMITSIZE/PAGESIZE - 1); 2107 2108 // Minimum of POOLSIZE 2109 if (npages < POOLSIZE/PAGESIZE) 2110 npages = POOLSIZE/PAGESIZE; 2111 else if (npages > POOLSIZE/PAGESIZE) 2112 { // Give us 150% of requested size, so there's room to extend 2113 auto n = npages + (npages >> 1); 2114 if (n < size_t.max/PAGESIZE) 2115 npages = n; 2116 } 2117 2118 // Allocate successively larger pools up to 8 megs 2119 if (npools) 2120 { size_t n; 2121 2122 n = npools; 2123 if (n > 32) 2124 n = 32; // cap pool size at 32 megs 2125 else if (n > 8) 2126 n = 16; 2127 n *= (POOLSIZE / PAGESIZE); 2128 if (npages < n) 2129 npages = n; 2130 } 2131 2132 pool = cast(Pool *)cstdlib.calloc(1, Pool.sizeof); 2133 if (pool) 2134 { 2135 pool.initialize(npages); 2136 if (!pool.baseAddr) 2137 goto Lerr; 2138 2139 newnpools = npools + 1; 2140 newpooltable = cast(Pool **)cstdlib.realloc(pooltable, newnpools * (Pool *).sizeof); 2141 if (!newpooltable) 2142 goto Lerr; 2143 2144 // Sort pool into newpooltable[] 2145 for (i = 0; i < npools; i++) 2146 { 2147 if (pool.opCmp(newpooltable[i]) < 0) 2148 break; 2149 } 2150 cstring.memmove(newpooltable + i + 1, newpooltable + i, (npools - i) * (Pool *).sizeof); 2151 newpooltable[i] = pool; 2152 2153 pooltable = newpooltable; 2154 npools = newnpools; 2155 2156 minAddr = pooltable[0].baseAddr; 2157 maxAddr = pooltable[npools - 1].topAddr; 2158 } 2159 return pool; 2160 2161 Lerr: 2162 pool.Dtor(); 2163 cstdlib.free(pool); 2164 return null; 2165 } 2166 2167 2168 /** 2169 * Allocate a page of bin's. 2170 * Returns: 2171 * 0 failed 2172 */ 2173 int allocPage(Bins bin) 2174 { 2175 Pool* pool; 2176 size_t n; 2177 size_t pn; 2178 byte* p; 2179 byte* ptop; 2180 2181 //debug(PRINTF) printf("Gcx::allocPage(bin = %d)\n", bin); 2182 for (n = 0; n < npools; n++) 2183 { 2184 pool = pooltable[n]; 2185 pn = pool.allocPages(1); 2186 if (pn != OPFAIL) 2187 goto L1; 2188 } 2189 return 0; // failed 2190 2191 L1: 2192 pool.pagetable[pn] = cast(ubyte)bin; 2193 2194 // Convert page to free list 2195 size_t size = binsize[bin]; 2196 List **b = &bucket[bin]; 2197 2198 p = pool.baseAddr + pn * PAGESIZE; 2199 ptop = p + PAGESIZE; 2200 for (; p < ptop; p += size) 2201 { 2202 (cast(List *)p).next = *b; 2203 *b = cast(List *)p; 2204 } 2205 return 1; 2206 } 2207 2208 2209 /** 2210 * Search a range of memory values and mark any pointers into the GC pool. 2211 */ 2212 void mark(void *pbot, void *ptop) 2213 { 2214 void **p1 = cast(void **)pbot; 2215 void **p2 = cast(void **)ptop; 2216 size_t pcache = 0; 2217 uint changes = 0; 2218 2219 //printf("marking range: %p -> %p\n", pbot, ptop); 2220 for (; p1 < p2; p1++) 2221 { 2222 auto p = cast(byte *)(*p1); 2223 2224 //if (log) debug(PRINTF) printf("\tmark %x\n", p); 2225 if (p >= minAddr && p < maxAddr) 2226 { 2227 if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache) 2228 continue; 2229 2230 auto pool = findPool(p); 2231 if (pool) 2232 { 2233 size_t offset = cast(size_t)(p - pool.baseAddr); 2234 size_t biti; 2235 size_t pn = offset / PAGESIZE; 2236 Bins bin = cast(Bins)pool.pagetable[pn]; 2237 2238 //debug(PRINTF) printf("\t\tfound pool %x, base=%x, pn = %d, bin = %d, biti = x%x\n", pool, pool.baseAddr, pn, bin, biti); 2239 2240 // Adjust bit to be at start of allocated memory block 2241 if (bin < B_PAGE) 2242 { 2243 biti = (offset & notbinsize[bin]) >> 4; 2244 //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); 2245 } 2246 else if (bin == B_PAGE) 2247 { 2248 biti = (offset & notbinsize[bin]) >> 4; 2249 //debug(PRINTF) printf("\t\tbiti = x%x\n", biti); 2250 2251 pcache = cast(size_t)p & ~(PAGESIZE-1); 2252 } 2253 else if (bin == B_PAGEPLUS) 2254 { 2255 do 2256 { --pn; 2257 } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS); 2258 biti = pn * (PAGESIZE / 16); 2259 2260 pcache = cast(size_t)p & ~(PAGESIZE-1); 2261 2262 bin = B_PAGE; 2263 } 2264 else 2265 { 2266 // Don't mark bits in B_FREE or B_UNCOMMITTED pages 2267 continue; 2268 } 2269 2270 //debug(PRINTF) printf("\t\tmark(x%x) = %d\n", biti, pool.mark.test(biti)); 2271 if (!pool.mark.testSet(biti)) 2272 { 2273 //if (log) debug(PRINTF) printf("\t\tmarking %x\n", p); 2274 if (!pool.noscan.test(biti)) 2275 { 2276 pool.scan.set(biti); 2277 changes = 1; 2278 } 2279 log_parent(sentinel_add(pool.baseAddr + biti * 16), sentinel_add(pbot)); 2280 } 2281 } 2282 } 2283 } 2284 anychanges |= changes; 2285 } 2286 2287 2288 /** 2289 * Return number of full pages free'd. 2290 */ 2291 size_t fullcollectshell() 2292 { 2293 // The purpose of the 'shell' is to ensure all the registers 2294 // get put on the stack so they'll be scanned 2295 void *sp; 2296 size_t result; 2297 version (GNU) 2298 { 2299 __builtin_unwind_init(); 2300 sp = & sp; 2301 } 2302 else version(LDC) 2303 { 2304 version(X86) 2305 { 2306 uint eax,ecx,edx,ebx,ebp,esi,edi; 2307 asm 2308 { 2309 mov eax[EBP], EAX ; 2310 mov ecx[EBP], ECX ; 2311 mov edx[EBP], EDX ; 2312 mov ebx[EBP], EBX ; 2313 mov ebp[EBP], EBP ; 2314 mov esi[EBP], ESI ; 2315 mov edi[EBP], EDI ; 2316 mov sp[EBP], ESP ; 2317 } 2318 } 2319 else version (X86_64) 2320 { 2321 ulong rax,rbx,rcx,rdx,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15; 2322 asm 2323 { 2324 movq rax[RBP], RAX ; 2325 movq rbx[RBP], RBX ; 2326 movq rcx[RBP], RCX ; 2327 movq rdx[RBP], RDX ; 2328 movq rbp[RBP], RBP ; 2329 movq rsi[RBP], RSI ; 2330 movq rdi[RBP], RDI ; 2331 movq r8 [RBP], R8 ; 2332 movq r9 [RBP], R9 ; 2333 movq r10[RBP], R10 ; 2334 movq r11[RBP], R11 ; 2335 movq r12[RBP], R12 ; 2336 movq r13[RBP], R13 ; 2337 movq r14[RBP], R14 ; 2338 movq r15[RBP], R15 ; 2339 movq sp[RBP], RSP ; 2340 } 2341 } 2342 else 2343 { 2344 static assert( false, "Architecture not supported." ); 2345 } 2346 } 2347 else 2348 { 2349 version (D_InlineAsm_X86) 2350 { 2351 asm 2352 { 2353 pushad ; 2354 mov sp[EBP],ESP ; 2355 } 2356 } 2357 else version (D_InlineAsm_X86_64) 2358 { 2359 asm 2360 { 2361 push RAX ; 2362 push RBX ; 2363 push RCX ; 2364 push RDX ; 2365 push RSI ; 2366 push RDI ; 2367 push RBP ; 2368 push R8 ; 2369 push R9 ; 2370 push R10 ; 2371 push R11 ; 2372 push R12 ; 2373 push R13 ; 2374 push R14 ; 2375 push R15 ; 2376 push RAX ; // 16 byte align the stack 2377 } 2378 } 2379 else 2380 { 2381 static assert( false, "Architecture not supported." ); 2382 } 2383 } 2384 result = fullcollect(sp); 2385 version (GNU) 2386 { 2387 // nothing to do 2388 } 2389 else version(LDC) 2390 { 2391 // nothing to do 2392 } 2393 else version (D_InlineAsm_X86) 2394 { 2395 asm 2396 { 2397 popad; 2398 } 2399 } 2400 else version (D_InlineAsm_X86_64) 2401 { 2402 asm 2403 { 2404 pop RAX ; 2405 pop R15 ; 2406 pop R14 ; 2407 pop R13 ; 2408 pop R12 ; 2409 pop R11 ; 2410 pop R10 ; 2411 pop R9 ; 2412 pop R8 ; 2413 pop RBP ; 2414 pop RDI ; 2415 pop RSI ; 2416 pop RDX ; 2417 pop RCX ; 2418 pop RBX ; 2419 pop RAX ; 2420 } 2421 } 2422 else 2423 { 2424 static assert( false, "Architecture not supported." ); 2425 } 2426 return result; 2427 } 2428 2429 2430 /** 2431 * 2432 */ 2433 size_t fullcollect(void *stackTop) 2434 { 2435 size_t n; 2436 Pool* pool; 2437 2438 debug(COLLECT_PRINTF) printf("Gcx.fullcollect()\n"); 2439 if (collectBegin.funcptr) 2440 collectBegin(); 2441 2442 thread_suspendAll(); 2443 2444 cached_size_key = cached_size_key.init; 2445 cached_size_val = cached_size_val.init; 2446 cached_info_key = cached_info_key.init; 2447 cached_info_val = cached_info_val.init; 2448 2449 anychanges = 0; 2450 for (n = 0; n < npools; n++) 2451 { 2452 pool = pooltable[n]; 2453 pool.mark.zero(); 2454 pool.scan.zero(); 2455 pool.freebits.zero(); 2456 } 2457 2458 // Mark each free entry, so it doesn't get scanned 2459 for (n = 0; n < B_PAGE; n++) 2460 { 2461 for (List *list = bucket[n]; list; list = list.next) 2462 { 2463 pool = findPool(list); 2464 assert(pool); 2465 pool.freebits.set(cast(size_t)(cast(byte*)list - pool.baseAddr) / 16); 2466 } 2467 } 2468 2469 for (n = 0; n < npools; n++) 2470 { 2471 pool = pooltable[n]; 2472 pool.mark.copy(&pool.freebits); 2473 } 2474 2475 rt_scanStaticData( &mark ); 2476 2477 version (MULTI_THREADED) 2478 { 2479 if (!noStack) 2480 { 2481 // Scan stacks and registers for each paused thread 2482 thread_scanAll( &mark, stackTop ); 2483 } 2484 } 2485 else 2486 { 2487 if (!noStack) 2488 { 2489 // Scan stack for main thread 2490 debug(PRINTF) printf(" scan stack bot = %x, top = %x\n", stackTop, stackBottom); 2491 version (STACKGROWSDOWN) 2492 mark(stackTop, stackBottom); 2493 else 2494 mark(stackBottom, stackTop); 2495 } 2496 } 2497 2498 // Scan roots[] 2499 debug(COLLECT_PRINTF) printf("scan roots[]\n"); 2500 mark(roots, roots + nroots); 2501 2502 // Scan ranges[] 2503 debug(COLLECT_PRINTF) printf("scan ranges[]\n"); 2504 //log++; 2505 for (n = 0; n < nranges; n++) 2506 { 2507 debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop); 2508 mark(ranges[n].pbot, ranges[n].ptop); 2509 } 2510 //log--; 2511 2512 debug(COLLECT_PRINTF) printf("\tscan heap\n"); 2513 while (anychanges) 2514 { 2515 anychanges = 0; 2516 for (n = 0; n < npools; n++) 2517 { 2518 pool = pooltable[n]; 2519 2520 auto bbase = pool.scan.base(); 2521 auto btop = bbase + pool.scan.nwords; 2522 for (auto b = bbase; b < btop;) 2523 { 2524 auto bitm = *b; 2525 if (!bitm) 2526 { b++; 2527 continue; 2528 } 2529 *b = 0; 2530 2531 auto o = pool.baseAddr + (b - bbase) * (typeof(bitm).sizeof*8) * 16; 2532 if (!(bitm & 0xFFFF)) 2533 { 2534 bitm >>= 16; 2535 o += 16 * 16; 2536 } 2537 if (!(bitm & 0xFF)) 2538 { 2539 bitm >>= 8; 2540 o += 8 * 16; 2541 } 2542 for (; bitm; o += 16, bitm >>= 1) 2543 { 2544 if (!(bitm & 1)) 2545 continue; 2546 2547 auto pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE; 2548 auto bin = cast(Bins)pool.pagetable[pn]; 2549 if (bin < B_PAGE) 2550 { 2551 mark(o, o + binsize[bin]); 2552 } 2553 else if (bin == B_PAGE || bin == B_PAGEPLUS) 2554 { 2555 if (bin == B_PAGEPLUS) 2556 { 2557 while (pool.pagetable[pn - 1] != B_PAGE) 2558 pn--; 2559 } 2560 auto u = 1; 2561 while (pn + u < pool.ncommitted && pool.pagetable[pn + u] == B_PAGEPLUS) 2562 u++; 2563 mark(o, o + u * PAGESIZE); 2564 } 2565 } 2566 } 2567 } 2568 } 2569 2570 thread_resumeAll(); 2571 2572 // Free up everything not marked 2573 debug(COLLECT_PRINTF) printf("\tfree'ing\n"); 2574 size_t freedpages = 0; 2575 size_t freed = 0; 2576 for (n = 0; n < npools; n++) 2577 { size_t pn; 2578 size_t ncommitted; 2579 2580 pool = pooltable[n]; 2581 auto bbase = pool.mark.base(); 2582 ncommitted = pool.ncommitted; 2583 for (pn = 0; pn < ncommitted; pn++, bbase += PAGESIZE / (32 * 16)) 2584 { 2585 Bins bin = cast(Bins)pool.pagetable[pn]; 2586 2587 if (bin < B_PAGE) 2588 { byte* p; 2589 byte* ptop; 2590 size_t biti; 2591 size_t bitstride; 2592 auto size = binsize[bin]; 2593 2594 p = pool.baseAddr + pn * PAGESIZE; 2595 ptop = p + PAGESIZE; 2596 biti = pn * (PAGESIZE/16); 2597 bitstride = size / 16; 2598 2599 version(none) // BUG: doesn't work because freebits() must also be cleared 2600 { 2601 // If free'd entire page 2602 if (bbase[0] == 0 && bbase[1] == 0 && bbase[2] == 0 && bbase[3] == 0 && 2603 bbase[4] == 0 && bbase[5] == 0 && bbase[6] == 0 && bbase[7] == 0) 2604 { 2605 for (; p < ptop; p += size, biti += bitstride) 2606 { 2607 if (pool.finals.nbits && pool.finals.testClear(biti)) 2608 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/); 2609 gcx.clrBits(pool, biti, BlkAttr.ALL_BITS); 2610 2611 List *list = cast(List *)p; 2612 //debug(PRINTF) printf("\tcollecting %x\n", list); 2613 log_free(sentinel_add(list)); 2614 2615 debug (MEMSTOMP) cstring.memset(p, 0xF3, size); 2616 } 2617 pool.pagetable[pn] = B_FREE; 2618 freed += PAGESIZE; 2619 //debug(PRINTF) printf("freeing entire page %d\n", pn); 2620 continue; 2621 } 2622 } 2623 for (; p < ptop; p += size, biti += bitstride) 2624 { 2625 if (!pool.mark.test(biti)) 2626 { 2627 sentinel_Invariant(sentinel_add(p)); 2628 2629 pool.freebits.set(biti); 2630 2631 if (pool.finals.nbits && pool.finals.testClear(biti)) 2632 rt_finalize(cast(List *)sentinel_add(p), false/*noStack > 0*/); 2633 clrBits(pool, biti, BlkAttr.ALL_BITS); 2634 2635 List *list = cast(List *)p; 2636 debug(PRINTF) printf("\tcollecting %x\n", list); 2637 log_free(sentinel_add(list)); 2638 2639 debug (MEMSTOMP) cstring.memset(p, 0xF3, size); 2640 2641 freed += size; 2642 } 2643 } 2644 } 2645 else if (bin == B_PAGE) 2646 { size_t biti = pn * (PAGESIZE / 16); 2647 2648 if (!pool.mark.test(biti)) 2649 { byte *p = pool.baseAddr + pn * PAGESIZE; 2650 2651 sentinel_Invariant(sentinel_add(p)); 2652 if (pool.finals.nbits && pool.finals.testClear(biti)) 2653 rt_finalize(sentinel_add(p), false/*noStack > 0*/); 2654 clrBits(pool, biti, BlkAttr.ALL_BITS); 2655 2656 debug(COLLECT_PRINTF) printf("\tcollecting big %x\n", p); 2657 log_free(sentinel_add(p)); 2658 pool.pagetable[pn] = B_FREE; 2659 freedpages++; 2660 debug (MEMSTOMP) cstring.memset(p, 0xF3, PAGESIZE); 2661 while (pn + 1 < ncommitted && pool.pagetable[pn + 1] == B_PAGEPLUS) 2662 { 2663 pn++; 2664 pool.pagetable[pn] = B_FREE; 2665 freedpages++; 2666 2667 debug (MEMSTOMP) 2668 { p += PAGESIZE; 2669 cstring.memset(p, 0xF3, PAGESIZE); 2670 } 2671 } 2672 } 2673 } 2674 } 2675 } 2676 2677 // Zero buckets 2678 bucket[] = null; 2679 2680 // Free complete pages, rebuild free list 2681 debug(COLLECT_PRINTF) printf("\tfree complete pages\n"); 2682 size_t recoveredpages = 0; 2683 for (n = 0; n < npools; n++) 2684 { size_t pn; 2685 size_t ncommitted; 2686 2687 pool = pooltable[n]; 2688 ncommitted = pool.ncommitted; 2689 for (pn = 0; pn < ncommitted; pn++) 2690 { 2691 Bins bin = cast(Bins)pool.pagetable[pn]; 2692 size_t biti; 2693 size_t u; 2694 2695 if (bin < B_PAGE) 2696 { 2697 size_t size = binsize[bin]; 2698 size_t bitstride = size / 16; 2699 size_t bitbase = pn * (PAGESIZE / 16); 2700 size_t bittop = bitbase + (PAGESIZE / 16); 2701 byte* p; 2702 2703 biti = bitbase; 2704 for (biti = bitbase; biti < bittop; biti += bitstride) 2705 { if (!pool.freebits.test(biti)) 2706 goto Lnotfree; 2707 } 2708 pool.pagetable[pn] = B_FREE; 2709 recoveredpages++; 2710 continue; 2711 2712 Lnotfree: 2713 p = pool.baseAddr + pn * PAGESIZE; 2714 for (u = 0; u < PAGESIZE; u += size) 2715 { biti = bitbase + u / 16; 2716 if (pool.freebits.test(biti)) 2717 { List *list; 2718 2719 list = cast(List *)(p + u); 2720 if (list.next != bucket[bin]) // avoid unnecessary writes 2721 list.next = bucket[bin]; 2722 bucket[bin] = list; 2723 } 2724 } 2725 } 2726 } 2727 } 2728 2729 debug(COLLECT_PRINTF) printf("recovered pages = %d\n", recoveredpages); 2730 debug(COLLECT_PRINTF) printf("\tfree'd %u bytes, %u pages from %u pools\n", freed, freedpages, npools); 2731 if (collectEnd.funcptr) 2732 collectEnd(freed + freedpages * PAGESIZE, (freedpages + recoveredpages) * PAGESIZE); 2733 2734 return freedpages + recoveredpages; 2735 } 2736 2737 2738 /** 2739 * 2740 */ 2741 uint getBits(Pool* pool, size_t biti) 2742 in 2743 { 2744 assert( pool ); 2745 } 2746 body 2747 { 2748 uint bits; 2749 2750 if (pool.finals.nbits && 2751 pool.finals.test(biti)) 2752 bits |= BlkAttr.FINALIZE; 2753 if (pool.noscan.test(biti)) 2754 bits |= BlkAttr.NO_SCAN; 2755 // if (pool.nomove.nbits && 2756 // pool.nomove.test(biti)) 2757 // bits |= BlkAttr.NO_MOVE; 2758 return bits; 2759 } 2760 2761 2762 /** 2763 * 2764 */ 2765 void setBits(Pool* pool, size_t biti, uint mask) 2766 in 2767 { 2768 assert( pool ); 2769 } 2770 body 2771 { 2772 if (mask & BlkAttr.FINALIZE) 2773 { 2774 if (!pool.finals.nbits) 2775 pool.finals.alloc(pool.mark.nbits); 2776 pool.finals.set(biti); 2777 } 2778 if (mask & BlkAttr.NO_SCAN) 2779 { 2780 pool.noscan.set(biti); 2781 } 2782 // if (mask & BlkAttr.NO_MOVE) 2783 // { 2784 // if (!pool.nomove.nbits) 2785 // pool.nomove.alloc(pool.mark.nbits); 2786 // pool.nomove.set(biti); 2787 // } 2788 } 2789 2790 2791 /** 2792 * 2793 */ 2794 void clrBits(Pool* pool, size_t biti, uint mask) 2795 in 2796 { 2797 assert( pool ); 2798 } 2799 body 2800 { 2801 if (mask & BlkAttr.FINALIZE && pool.finals.nbits) 2802 pool.finals.clear(biti); 2803 if (mask & BlkAttr.NO_SCAN) 2804 pool.noscan.clear(biti); 2805 // if (mask & BlkAttr.NO_MOVE && pool.nomove.nbits) 2806 // pool.nomove.clear(biti); 2807 } 2808 2809 2810 /***** Leak Detector ******/ 2811 2812 2813 debug (LOGGING) 2814 { 2815 LogArray current; 2816 LogArray prev; 2817 2818 2819 void log_init() 2820 { 2821 //debug(PRINTF) printf("+log_init()\n"); 2822 current.reserve(1000); 2823 prev.reserve(1000); 2824 //debug(PRINTF) printf("-log_init()\n"); 2825 } 2826 2827 2828 void log_malloc(void *p, size_t size) 2829 { 2830 //debug(PRINTF) printf("+log_malloc(p = %x, size = %d)\n", p, size); 2831 Log log; 2832 2833 log.p = p; 2834 log.size = size; 2835 log.line = GC.line; 2836 log.file = GC.file; 2837 log.parent = null; 2838 2839 GC.line = 0; 2840 GC.file = null; 2841 2842 current.push(log); 2843 //debug(PRINTF) printf("-log_malloc()\n"); 2844 } 2845 2846 2847 void log_free(void *p) 2848 { 2849 //debug(PRINTF) printf("+log_free(%x)\n", p); 2850 size_t i; 2851 2852 i = current.find(p); 2853 if (i == OPFAIL) 2854 { 2855 debug(PRINTF) printf("free'ing unallocated memory %x\n", p); 2856 } 2857 else 2858 current.remove(i); 2859 //debug(PRINTF) printf("-log_free()\n"); 2860 } 2861 2862 2863 void log_collect() 2864 { 2865 //debug(PRINTF) printf("+log_collect()\n"); 2866 // Print everything in current that is not in prev 2867 2868 debug(PRINTF) printf("New pointers this cycle: --------------------------------\n"); 2869 size_t used = 0; 2870 for (size_t i = 0; i < current.dim; i++) 2871 { 2872 size_t j; 2873 2874 j = prev.find(current.data[i].p); 2875 if (j == OPFAIL) 2876 current.data[i].print(); 2877 else 2878 used++; 2879 } 2880 2881 debug(PRINTF) printf("All roots this cycle: --------------------------------\n"); 2882 for (size_t i = 0; i < current.dim; i++) 2883 { 2884 void *p; 2885 size_t j; 2886 2887 p = current.data[i].p; 2888 if (!findPool(current.data[i].parent)) 2889 { 2890 j = prev.find(current.data[i].p); 2891 if (j == OPFAIL) 2892 debug(PRINTF) printf("N"); 2893 else 2894 debug(PRINTF) printf(" ");; 2895 current.data[i].print(); 2896 } 2897 } 2898 2899 debug(PRINTF) printf("Used = %d-------------------------------------------------\n", used); 2900 prev.copy(¤t); 2901 2902 debug(PRINTF) printf("-log_collect()\n"); 2903 } 2904 2905 2906 void log_parent(void *p, void *parent) 2907 { 2908 //debug(PRINTF) printf("+log_parent()\n"); 2909 size_t i; 2910 2911 i = current.find(p); 2912 if (i == OPFAIL) 2913 { 2914 debug(PRINTF) printf("parent'ing unallocated memory %x, parent = %x\n", p, parent); 2915 Pool *pool; 2916 pool = findPool(p); 2917 assert(pool); 2918 size_t offset = cast(size_t)(p - pool.baseAddr); 2919 size_t biti; 2920 size_t pn = offset / PAGESIZE; 2921 Bins bin = cast(Bins)pool.pagetable[pn]; 2922 biti = (offset & notbinsize[bin]); 2923 debug(PRINTF) printf("\tbin = %d, offset = x%x, biti = x%x\n", bin, offset, biti); 2924 } 2925 else 2926 { 2927 current.data[i].parent = parent; 2928 } 2929 //debug(PRINTF) printf("-log_parent()\n"); 2930 } 2931 2932 } 2933 else 2934 { 2935 void log_init() { } 2936 void log_malloc(void *p, size_t size) { } 2937 void log_free(void *p) { } 2938 void log_collect() { } 2939 void log_parent(void *p, void *parent) { } 2940 } 2941 } 2942 2943 2944 /* ============================ Pool =============================== */ 2945 2946 2947 struct Pool 2948 { 2949 byte* baseAddr; 2950 byte* topAddr; 2951 GCBits mark; // entries already scanned, or should not be scanned 2952 GCBits scan; // entries that need to be scanned 2953 GCBits freebits; // entries that are on the free list 2954 GCBits finals; // entries that need finalizer run on them 2955 GCBits noscan; // entries that should not be scanned 2956 2957 size_t npages; 2958 size_t ncommitted; // ncommitted <= npages 2959 ubyte* pagetable; 2960 2961 2962 void initialize(size_t npages) 2963 { 2964 size_t poolsize; 2965 2966 //debug(PRINTF) printf("Pool::Pool(%u)\n", npages); 2967 poolsize = npages * PAGESIZE; 2968 assert(poolsize >= POOLSIZE); 2969 2970 debug(PRINTF) printf("alloc of pool: %p bytes\n", poolsize); 2971 2972 baseAddr = cast(byte *)os_mem_map(poolsize); 2973 2974 // Some of the code depends on page alignment of memory pools 2975 assert((cast(size_t)baseAddr & (PAGESIZE - 1)) == 0); 2976 2977 if (!baseAddr) 2978 { 2979 //debug(PRINTF) printf("GC fail: poolsize = x%x, errno = %d\n", poolsize, errno); 2980 //debug(PRINTF) printf("message = '%s'\n", sys_errlist[errno]); 2981 2982 npages = 0; 2983 poolsize = 0; 2984 } 2985 //assert(baseAddr); 2986 topAddr = baseAddr + poolsize; 2987 2988 mark.alloc(cast(size_t)poolsize / 16); 2989 scan.alloc(cast(size_t)poolsize / 16); 2990 freebits.alloc(cast(size_t)poolsize / 16); 2991 noscan.alloc(cast(size_t)poolsize / 16); 2992 2993 pagetable = cast(ubyte*)cstdlib.malloc(npages); 2994 if (!pagetable) 2995 onOutOfMemoryError(); 2996 cstring.memset(pagetable, B_UNCOMMITTED, npages); 2997 2998 this.npages = npages; 2999 ncommitted = 0; 3000 } 3001 3002 3003 void Dtor() 3004 { 3005 if (baseAddr) 3006 { 3007 int result; 3008 3009 if (ncommitted) 3010 { 3011 result = os_mem_decommit(baseAddr, 0, ncommitted * PAGESIZE); 3012 assert(result == 0); 3013 ncommitted = 0; 3014 } 3015 3016 if (npages) 3017 { 3018 result = os_mem_unmap(baseAddr, npages * PAGESIZE); 3019 assert(result == 0); 3020 npages = 0; 3021 } 3022 3023 baseAddr = null; 3024 topAddr = null; 3025 } 3026 if (pagetable) 3027 cstdlib.free(pagetable); 3028 3029 mark.Dtor(); 3030 scan.Dtor(); 3031 freebits.Dtor(); 3032 finals.Dtor(); 3033 noscan.Dtor(); 3034 } 3035 3036 3037 void Invariant() { } 3038 3039 3040 invariant 3041 { 3042 //mark.Invariant(); 3043 //scan.Invariant(); 3044 //freebits.Invariant(); 3045 //finals.Invariant(); 3046 //noscan.Invariant(); 3047 3048 if (baseAddr) 3049 { 3050 //if (baseAddr + npages * PAGESIZE != topAddr) 3051 //printf("baseAddr = %p, npages = %d, topAddr = %p\n", baseAddr, npages, topAddr); 3052 assert(baseAddr + npages * PAGESIZE == topAddr); 3053 assert(ncommitted <= npages); 3054 } 3055 3056 for (size_t i = 0; i < npages; i++) 3057 { Bins bin = cast(Bins)pagetable[i]; 3058 3059 assert(bin < B_MAX); 3060 } 3061 } 3062 3063 3064 /** 3065 * Allocate n pages from Pool. 3066 * Returns OPFAIL on failure. 3067 */ 3068 size_t allocPages(size_t n) 3069 { 3070 size_t i; 3071 size_t n2; 3072 3073 //debug(PRINTF) printf("Pool::allocPages(n = %d)\n", n); 3074 n2 = n; 3075 for (i = 0; i < ncommitted; i++) 3076 { 3077 if (pagetable[i] == B_FREE) 3078 { 3079 if (--n2 == 0) 3080 { //debug(PRINTF) printf("\texisting pn = %d\n", i - n + 1); 3081 return i - n + 1; 3082 } 3083 } 3084 else 3085 n2 = n; 3086 } 3087 return extendPages(n); 3088 } 3089 3090 /** 3091 * Extend Pool by n pages. 3092 * Returns OPFAIL on failure. 3093 */ 3094 size_t extendPages(size_t n) 3095 { 3096 //debug(PRINTF) printf("Pool::extendPages(n = %d)\n", n); 3097 if (ncommitted + n <= npages) 3098 { 3099 size_t tocommit; 3100 3101 tocommit = (n + (COMMITSIZE/PAGESIZE) - 1) & ~(COMMITSIZE/PAGESIZE - 1); 3102 if (ncommitted + tocommit > npages) 3103 tocommit = npages - ncommitted; 3104 //debug(PRINTF) printf("\tlooking to commit %d more pages\n", tocommit); 3105 //fflush(stdout); 3106 if (os_mem_commit(baseAddr, ncommitted * PAGESIZE, tocommit * PAGESIZE) == 0) 3107 { 3108 cstring.memset(pagetable + ncommitted, B_FREE, tocommit); 3109 auto i = ncommitted; 3110 ncommitted += tocommit; 3111 3112 while (i && pagetable[i - 1] == B_FREE) 3113 i--; 3114 3115 return i; 3116 } 3117 //debug(PRINTF) printf("\tfailed to commit %d pages\n", tocommit); 3118 } 3119 3120 return OPFAIL; 3121 } 3122 3123 3124 /** 3125 * Free npages pages starting with pagenum. 3126 */ 3127 void freePages(size_t pagenum, size_t npages) 3128 { 3129 cstring.memset(&pagetable[pagenum], B_FREE, npages); 3130 } 3131 3132 3133 /** 3134 * Used for sorting pooltable[] 3135 */ 3136 int opCmp(Pool *p2) 3137 { 3138 if (baseAddr < p2.baseAddr) 3139 return -1; 3140 else 3141 return cast(int)(baseAddr > p2.baseAddr); 3142 } 3143 } 3144 3145 3146 /* ============================ SENTINEL =============================== */ 3147 3148 3149 version (SENTINEL) 3150 { 3151 const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits 3152 const ubyte SENTINEL_POST = 0xF5; // 8 bits 3153 const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1; 3154 3155 3156 size_t* sentinel_size(void *p) { return &(cast(size_t *)p)[-2]; } 3157 size_t* sentinel_pre(void *p) { return &(cast(size_t *)p)[-1]; } 3158 ubyte* sentinel_post(void *p) { return &(cast(ubyte *)p)[*sentinel_size(p)]; } 3159 3160 3161 void sentinel_init(void *p, size_t size) 3162 { 3163 *sentinel_size(p) = size; 3164 *sentinel_pre(p) = SENTINEL_PRE; 3165 *sentinel_post(p) = SENTINEL_POST; 3166 } 3167 3168 3169 void sentinel_Invariant(void *p) 3170 { 3171 assert(*sentinel_pre(p) == SENTINEL_PRE); 3172 assert(*sentinel_post(p) == SENTINEL_POST); 3173 } 3174 3175 3176 void *sentinel_add(void *p) 3177 { 3178 return p + 2 * size_t.sizeof; 3179 } 3180 3181 3182 void *sentinel_sub(void *p) 3183 { 3184 return p - 2 * size_t.sizeof; 3185 } 3186 } 3187 else 3188 { 3189 const uint SENTINEL_EXTRA = 0; 3190 3191 3192 void sentinel_init(void *p, size_t size) 3193 { 3194 } 3195 3196 3197 void sentinel_Invariant(void *p) 3198 { 3199 } 3200 3201 3202 void *sentinel_add(void *p) 3203 { 3204 return p; 3205 } 3206 3207 3208 void *sentinel_sub(void *p) 3209 { 3210 return p; 3211 } 3212 } 3213