1 module d.gc.chunk; 2 3 import d.gc.spec; 4 5 struct PageDescriptor { 6 /** 7 * This is similar but not identical to what jemalloc does. 8 * Visit: http://www.canonware.com/jemalloc/ to know more. 9 * 10 * Run address (or size) and various flags are stored together. The bit 11 * layout looks like: 12 * 13 * ???????? ???????? ????nnnn nnnndula 14 * 15 * ? : Unallocated: Run address for first/last pages, unset for internal 16 * pages. 17 * Small: Run page offset. 18 * Large: Run size for first page, unset for trailing pages. 19 * n : binind for small size class, BININD_INVALID for large size class. 20 * d : dirty? 21 * u : unzeroed? 22 * l : large? 23 * a : allocated? 24 * 25 * Following are example bit patterns for the three types of runs. 26 * 27 * p : run page offset 28 * s : run size 29 * n : binind for size class; large objects set these to BININD_INVALID 30 * x : don't care 31 * - : 0 32 * + : 1 33 * [DULA] : bit set 34 * [dula] : bit unset 35 * 36 * Unallocated (clean): 37 * ssssssss ssssssss ssss++++ ++++du-a 38 * xxxxxxxx xxxxxxxx xxxxxxxx xxxx-Uxx 39 * ssssssss ssssssss ssss++++ ++++dU-a 40 * 41 * Unallocated (dirty): 42 * ssssssss ssssssss ssss++++ ++++D--a 43 * xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx 44 * ssssssss ssssssss ssss++++ ++++D--a 45 * 46 * Small: 47 * pppppppp pppppppp ppppnnnn nnnnd--A 48 * pppppppp pppppppp ppppnnnn nnnn---A 49 * pppppppp pppppppp ppppnnnn nnnnd--A 50 * 51 * Large: 52 * pppppppp pppppppp ppppnnnn nnnnD-LA 53 * pppppppp pppppppp ppppnnnn nnnnD-LA 54 * pppppppp pppppppp ppppnnnn nnnnD-LA 55 */ 56 private uint bits; 57 58 this(bool allocated, bool large, bool zeroed, bool dirty, ubyte binID, 59 uint pages) { 60 // XXX: in contract. 61 assert(allocated || !large); 62 63 bits = allocated; 64 65 bits |= (cast(uint) large) << 1; 66 bits |= (cast(uint) !zeroed) << 2; 67 bits |= (cast(uint) dirty) << 3; 68 bits |= (cast(uint) binID) << 4; 69 bits |= (pages << LgPageSize); 70 71 // Sanity checks. 72 assert(this.allocated == allocated); 73 assert(this.free || this.large == large); 74 assert(this.zeroed == zeroed); 75 assert(this.dirty == dirty); 76 assert(this.binID == binID); 77 } 78 79 @property 80 bool allocated() const { 81 return !free; 82 } 83 84 @property 85 bool free() const { 86 // TODO: VRP for & 87 return !(bits & 0x01); 88 } 89 90 @property 91 bool small() const { 92 assert(allocated); 93 return !(bits & 0x02); 94 } 95 96 @property 97 bool large() const { 98 assert(allocated); 99 return !small; 100 } 101 102 @property 103 bool zeroed() const { 104 return !(bits & 0x04); 105 } 106 107 @property 108 bool dirty() const { 109 return !!(bits & 0x08); 110 } 111 112 @property 113 ubyte binID() const { 114 return cast(ubyte) (bits >> 4); 115 } 116 117 @property 118 uint offset() const { 119 assert(allocated); 120 return bits >> LgPageSize; 121 } 122 123 @property 124 uint pages() const { 125 assert(free); 126 // TODO: VRP 127 return bits >> LgPageSize; 128 } 129 130 @property 131 uint size() const { 132 assert(free); 133 // TODO: VRP 134 return cast(uint) (bits & ~PageMask); 135 } 136 } 137 138 struct Chunk { 139 // header + pad0 + DataPages populate the first page. 140 Header header; 141 alias header this; 142 143 // Pad header so that the first page descriptor is at the right place. 144 PageDescriptor[Pad0Size] pad0; 145 146 // One PageDescriptor per data page pages. 147 PageDescriptor[DataPages] pages; 148 149 // One RunDesc per data page. 150 import d.gc.run; 151 RunDesc[DataPages] runs; 152 153 // Pad metadata up to the end of the current page. 154 uint[Pad1Size] pad1; 155 156 // Actual user data. 157 ulong[PageSize / ulong.sizeof][DataPages] datas; 158 159 import d.gc.arena; 160 static Chunk* allocate(Arena* a) { 161 // XXX: ensure i didn't fucked up the layout. 162 // this better belong to static assert when available. 163 assert(Chunk.sizeof == ChunkSize); 164 165 import d.gc.mman; 166 auto ret = map_chunks(1); 167 if (ret is null) { 168 return null; 169 } 170 171 auto c = cast(Chunk*) ret; 172 assert(findChunk(c) is null); 173 174 // XXX: ensure I didn't fucked up the layout. 175 // this better belong to static assert when available. 176 { 177 auto ci = cast(size_t) ret; 178 auto pi = cast(size_t) &c.pages[0]; 179 auto ri = cast(size_t) &c.runs[0]; 180 auto di = cast(size_t) &c.datas[0]; 181 182 assert(pi == ci + MetaPages * PageDescriptor.sizeof); 183 assert(ri == ci + ChunkPageCount * PageDescriptor.sizeof); 184 assert(di == ci + MetaPages * PageSize); 185 } 186 187 c.arena = a; 188 c.addr = c; 189 c.size = ChunkSize; 190 c.bitmap = null; 191 192 // FIXME: empty array not supported. 193 // c.worklist = []; 194 c.worklist.ptr = null; 195 c.worklist.length = 0; 196 197 import d.gc.sizeclass; 198 enum DataSize = DataPages << LgPageSize; 199 enum FreeBinID = cast(ubyte) (getBinID(DataSize + 1) - 1); 200 201 import d.gc.bin; 202 auto d = 203 PageDescriptor(false, false, true, false, FreeBinID, DataPages); 204 205 assert(d.zeroed == true); 206 assert(!d.allocated); 207 assert(d.size == DataSize); 208 assert(d.binID == FreeBinID); 209 210 c.pages[0] = d; 211 c.pages[DataPages - 1] = d; 212 213 // TODO: register the chunk for dump and scan. 214 return c; 215 } 216 217 void free() { 218 import d.gc.mman; 219 pages_unmap(&this, ChunkSize); 220 } 221 222 uint getPageID(const void* ptr) { 223 // FIXME: in contract 224 assert(findChunk(ptr) is &this); 225 226 auto offset = (cast(uint) ptr) - (cast(uint) &datas[0]); 227 228 import d.gc.spec; 229 return offset >> LgPageSize; 230 } 231 232 uint getRunID(const void* ptr) { 233 // FIXME: in contract 234 assert(findChunk(ptr) is &this); 235 236 auto pageID = getPageID(ptr); 237 auto pd = pages[pageID]; 238 assert(pd.allocated); 239 240 auto runID = pageID - pd.offset; 241 242 // FIXME: out contract 243 assert(pages[runID].allocated); 244 return runID; 245 } 246 247 uint maybeGetRunID(const void* ptr) { 248 // FIXME: in contract 249 assert(findChunk(ptr) is &this); 250 251 auto pageID = getPageID(ptr); 252 if (pageID >= DataPages) { 253 return -1; 254 } 255 256 auto pd = pages[pageID]; 257 if (pd.free) { 258 return -1; 259 } 260 261 // Find the start of the run. 262 auto runID = pageID - pd.offset; 263 pd = pages[runID]; 264 if (pd.free) { 265 return -1; 266 } 267 268 return runID; 269 } 270 271 /** 272 * GC facilities. 273 */ 274 void prepare() { 275 ushort nextBitmapIndex = ((DataPages - 1) / (uint.sizeof * 8)) + 1; 276 uint i = 0; 277 while (i < DataPages) { 278 if (pages[i].free) { 279 auto runID = i; 280 i += pages[i].pages; 281 282 assert(pages[i - 1].free); 283 assert(pages[i - 1].pages == pages[runID].pages); 284 285 continue; 286 } 287 288 auto binID = pages[i].binID; 289 if (pages[i].large) { 290 import d.gc.sizeclass; 291 i += cast(uint) (getSizeFromBinID(binID) >> LgPageSize); 292 continue; 293 } 294 295 // For small runs, we make sure we reserve some place in the chunk's bitmap. 296 assert(pages[i].offset == 0); 297 assert(runs[i].small.bitmapIndex == 0); 298 runs[i].small.bitmapIndex = nextBitmapIndex; 299 300 import d.gc.bin; 301 auto binInfo = binInfos[binID]; 302 auto slots = binInfo.slots; 303 304 i += binInfo.needPages; 305 nextBitmapIndex += 306 cast(ushort) (((slots - 1) / (uint.sizeof * 8)) + 1); 307 } 308 309 // FIXME: It seems that there are some issue with alias this. 310 header.bitmap = 311 cast(uint*) header.arena.calloc(nextBitmapIndex * uint.sizeof); 312 } 313 314 bool mark(const void* ptr) { 315 assert(findChunk(ptr) is &this); 316 317 auto runID = maybeGetRunID(ptr); 318 if (runID == -1) { 319 return false; 320 } 321 322 // The chunk may have been created after the collection started. 323 auto bitmapPtr = header.bitmap; 324 if (bitmapPtr is null) { 325 return false; 326 } 327 328 auto pd = pages[runID]; 329 auto index = runID; 330 if (pd.small) { 331 auto smallRun = &runs[runID].small; 332 333 // This run has been alocated after the start of the collection. 334 // We just consider it alive, no need to check for it. 335 auto bitmapIndex = smallRun.bitmapIndex; 336 if (bitmapIndex == 0) { 337 return false; 338 } 339 340 bitmapPtr = &bitmapPtr[bitmapIndex]; 341 342 // This is duplicated with Arena.freeSmall, need refactoring. 343 auto offset = (cast(uint) ptr) - (cast(uint) &datas[runID]); 344 345 import d.gc.bin; 346 auto binInfo = binInfos[pd.binID]; 347 index = binInfo.computeIndex(offset); 348 if (smallRun.isFree(index)) { 349 return false; 350 } 351 } 352 353 // Already marked 354 auto elementBitmap = bitmapPtr[index / 32]; 355 auto mask = 1 << (index % 32); 356 if (elementBitmap & mask) { 357 return false; 358 } 359 360 // Mark and add to worklist 361 bitmapPtr[index / 32] = elementBitmap | mask; 362 363 auto workLength = header.worklist.length + 1; 364 365 // We realloc everytime. It doesn't really matter at this point. 366 auto workPtr = cast(const(void)**) 367 header.arena 368 .realloc(header.worklist.ptr, workLength * void*.sizeof); 369 370 workPtr[workLength - 1] = ptr; 371 header.worklist = workPtr[0 .. workLength]; 372 373 return true; 374 } 375 376 bool scan() { 377 auto newPtr = false; 378 379 while (header.worklist.length > 0) { 380 auto ptrs = header.worklist; 381 scope(exit) header.arena.free(ptrs.ptr); 382 383 // header.worklist = []; 384 header.worklist.ptr = null; 385 header.worklist.length = 0; 386 387 foreach (ptr; ptrs) { 388 assert(findChunk(ptr) is &this); 389 390 auto runID = maybeGetRunID(ptr); 391 if (runID == -1) { 392 continue; 393 } 394 395 auto pd = pages[runID]; 396 assert(pd.allocated); 397 assert(pd.offset == 0); 398 399 auto base = cast(const(void*)*) &datas[runID]; 400 401 const(void*)[] range; 402 size_t size; 403 404 if (pd.small) { 405 import d.gc.bin; 406 auto binInfo = binInfos[pd.binID]; 407 size = binInfo.itemSize; 408 auto offset = (cast(uint) ptr) - (cast(uint) base); 409 auto index = binInfo.computeIndex(offset); 410 base = 411 cast(const(void*)*) ((cast(void*) base) + size * index); 412 } else { 413 import d.gc.sizeclass; 414 size = getSizeFromBinID(pd.binID); 415 } 416 417 range = base[0 .. size / size_t.sizeof]; 418 newPtr = header.arena.scan(range) || newPtr; 419 } 420 } 421 422 return newPtr; 423 } 424 425 void collect() { 426 uint i = 0; 427 while (i < DataPages) { 428 if (pages[i].free) { 429 auto runID = i; 430 i += pages[i].pages; 431 432 assert(pages[i - 1].free); 433 assert(pages[i - 1].pages == pages[runID].pages); 434 435 continue; 436 } 437 438 auto pd = pages[i]; 439 auto runID = i; 440 auto binID = pd.binID; 441 if (pd.large) { 442 import d.gc.sizeclass; 443 auto pages = 444 cast(uint) (getSizeFromBinID(pd.binID) >> LgPageSize); 445 i += pages; 446 447 // Check if the run is alive. 448 auto bmp = header.bitmap[runID / 32]; 449 auto mask = 1 << (runID % 32); 450 if (bmp & mask) { 451 continue; 452 } 453 454 header.arena.freeRun(&this, runID, pages); 455 continue; 456 } 457 458 assert(pd.offset == 0); 459 460 import d.gc.bin; 461 auto binInfo = binInfos[binID]; 462 i += binInfo.needPages; 463 464 auto small = &runs[runID].small; 465 auto bmpIndex = small.bitmapIndex; 466 // This is a new run, dismiss. 467 if (bmpIndex == 0) { 468 continue; 469 } 470 471 auto bitmapPtr = &header.bitmap[bmpIndex]; 472 auto headerBits = binInfo.slots / 32; 473 if (!headerBits) { 474 headerBits = 1; 475 } else { 476 assert((binInfo.slots % 32) == 0); 477 } 478 479 for (uint j = 0; j < headerBits; j++) { 480 auto liveBmp = bitmapPtr[j]; 481 482 // Zero means allocated, so we flip bits. 483 auto oldBmp = small.bitmap[j]; 484 auto newBmp = oldBmp | ~liveBmp; 485 486 auto freed = newBmp ^ oldBmp; 487 if (freed) { 488 import sdc.intrinsics; 489 small.freeSlots += popCount(freed); 490 small.header = cast(ushort) (small.header | (1 << i)); 491 small.bitmap[j] = newBmp; 492 } 493 } 494 } 495 496 // FIXME: It seems that there are some issue with alias this. 497 header.arena.free(header.bitmap); 498 header.bitmap = null; 499 500 // FIXME: empty array not supported. 501 // header.worklist = []; 502 assert(header.worklist.ptr is null); 503 assert(header.worklist.length == 0); 504 } 505 506 /** 507 * Split run from free space. 508 */ 509 uint splitSmallRun(uint runID, ubyte binID) { 510 // XXX: in contract. 511 import d.gc.bin, d.gc.sizeclass; 512 assert(binID < ClassCount.Small); 513 assert(pages[runID].free); 514 515 auto binInfo = binInfos[binID]; 516 auto needPages = binInfo.needPages; 517 518 assert((needPages << LgPageSize) <= pages[runID].size); 519 auto rem = runSplitRemove(runID, needPages); 520 521 auto dirty = pages[runID].dirty; 522 523 setBitmap(runID, needPages, 524 PageDescriptor(true, false, false, dirty, binID, 0)); 525 526 runs[runID].small.binID = binID; 527 runs[runID].small.freeSlots = binInfo.slots; 528 529 // During the collection process, other runs may be created, 530 // they are considered live and will be scanned during the next 531 // collection cycle. 532 runs[runID].small.bitmapIndex = 0; 533 534 auto headerBits = binInfo.slots / 32; 535 if (!headerBits) { 536 runs[runID].small.header = 1; 537 runs[runID].small.bitmap[0] = (1 << (binInfo.slots % 32)) - 1; 538 return rem; 539 } 540 541 assert((binInfo.slots % 32) == 0); 542 543 runs[runID].small.header = cast(ushort) ((1 << headerBits) - 1); 544 for (uint i = 0; i < headerBits; i++) { 545 runs[runID].small.bitmap[i] = -1; 546 } 547 548 return rem; 549 } 550 551 uint splitLargeRun(size_t size, uint runID, ubyte binID, bool zero) { 552 // XXX: in contract. 553 import d.gc.bin, d.gc.sizeclass; 554 assert(size > SizeClass.Small && size <= SizeClass.Large); 555 assert(binID >= ClassCount.Small && binID < ClassCount.Large); 556 assert(pages[runID].free); 557 assert(size == getAllocSize(size)); 558 assert(getBinID(size) == binID); 559 560 // If we are GCing, mark the new allocation as live. 561 auto bPtr = header.bitmap; 562 if (bPtr !is null) { 563 bPtr = &bPtr[runID / 32]; 564 *bPtr = *bPtr | (1 << (runID % 32)); 565 } 566 567 auto needPages = cast(uint) (size >> LgPageSize); 568 auto rem = runSplitRemove(runID, needPages); 569 570 auto dirty = pages[runID].dirty; 571 if (zero) { 572 if (dirty) { 573 memset(&datas[runID], 0, needPages << LgPageSize); 574 } else { 575 for (uint i = 0; i < needPages; i++) { 576 auto p = runID + i; 577 if (!pages[p].zeroed) { 578 memset(&datas[p], 0, PageSize); 579 } 580 } 581 } 582 } 583 584 setBitmap(runID, needPages, 585 PageDescriptor(true, true, zero, dirty, binID, 0)); 586 587 return rem; 588 } 589 590 private: 591 void setBitmap(uint runID, uint needPages, PageDescriptor base) { 592 // FIXME: in contract 593 assert(base.allocated); 594 595 for (uint i = 0; i < needPages; i++) { 596 auto p = runID + i; 597 auto d = pages[p]; 598 599 pages[p] = PageDescriptor(base.allocated, base.large, base.zeroed, 600 base.dirty, base.binID, i); 601 602 if (d.zeroed && !base.dirty) { 603 for (uint j; j < datas[p].length; j++) { 604 assert(datas[p][j] == 0); 605 } 606 } 607 } 608 } 609 610 uint runSplitRemove(uint runID, uint needPages) { 611 // XXX: in contract. 612 assert(!pages[runID].allocated); 613 614 auto pd = pages[runID]; 615 auto totalPages = pd.pages; 616 auto dirty = pd.dirty; 617 618 assert(needPages <= totalPages); 619 auto remPages = totalPages - needPages; 620 621 if (dirty) { 622 // XXX: arena dirty remove. 623 } 624 625 if (remPages == 0) { 626 return 0; 627 } 628 629 import d.gc.sizeclass; 630 auto remBin = cast(ubyte) (getBinID((remPages << LgPageSize) + 1) - 1); 631 632 // If we have remaining free space, keep track of it. 633 import d.gc.bin; 634 auto d = 635 PageDescriptor(false, false, pd.zeroed, dirty, remBin, remPages); 636 637 pages[runID + needPages] = d; 638 pages[runID + totalPages - 1] = d; 639 640 if (dirty) { 641 // XXX: arena dirty add. 642 } 643 644 return runID + needPages; 645 } 646 } 647 648 Chunk* findChunk(const void* ptr) { 649 import d.gc.spec; 650 auto c = cast(Chunk*) ((cast(size_t) ptr) & ~AlignMask); 651 652 // XXX: type promotion is fucked. 653 auto vc = cast(void*) c; 654 if (vc is ptr) { 655 // Huge alloc, no arena. 656 return null; 657 } 658 659 return c; 660 } 661 662 private: 663 664 struct Header { 665 import d.gc.extent; 666 Extent extent; 667 alias extent this; 668 669 uint* bitmap; 670 const(void)*[] worklist; 671 } 672 673 enum DataPages = computeDataPages(); 674 enum MetaPages = ChunkPageCount - DataPages; 675 676 enum MinMetaPages = ((Header.sizeof - 1) / /+ PageDescriptor.alignof +/ 4) + 1; 677 enum Pad0Size = MetaPages - MinMetaPages; 678 enum Pad1Size = 679 ((MetaPages * PageSize) - computeRunDescEnd(DataPages)) / uint.sizeof; 680 681 auto computeDataPages() { 682 foreach (metaPages; MinMetaPages .. ChunkPageCount) { 683 auto dataPages = ChunkPageCount - metaPages; 684 auto runDescEnd = computeRunDescEnd(dataPages); 685 auto requiredMetaPages = ((runDescEnd - 1) >> LgPageSize) + 1; 686 687 if (requiredMetaPages == metaPages) { 688 return dataPages; 689 } 690 } 691 692 assert(0, "Chunk is too small"); 693 } 694 695 auto computeRunDescEnd(size_t dataPages) { 696 auto pageDescEnd = ChunkPageCount * PageDescriptor.sizeof; 697 698 import d.gc.run; 699 enum RDAlign = /+ RunDesc.alignof +/ 8; 700 auto runDescStart = (((pageDescEnd - 1) / RDAlign) + 1) * RDAlign; 701 702 return runDescStart + dataPages * RunDesc.sizeof; 703 }