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)**) header.arena 367 .realloc(header.worklist.ptr, workLength * void*.sizeof); 368 369 workPtr[workLength - 1] = ptr; 370 header.worklist = workPtr[0 .. workLength]; 371 372 return true; 373 } 374 375 bool scan() { 376 auto newPtr = false; 377 378 while (header.worklist.length > 0) { 379 auto ptrs = header.worklist; 380 scope(exit) header.arena.free(ptrs.ptr); 381 382 // header.worklist = []; 383 header.worklist.ptr = null; 384 header.worklist.length = 0; 385 386 foreach (ptr; ptrs) { 387 assert(findChunk(ptr) is &this); 388 389 auto runID = maybeGetRunID(ptr); 390 if (runID == -1) { 391 continue; 392 } 393 394 auto pd = pages[runID]; 395 assert(pd.allocated); 396 assert(pd.offset == 0); 397 398 auto base = cast(const(void*)*) &datas[runID]; 399 400 const(void*)[] range; 401 size_t size; 402 403 if (pd.small) { 404 import d.gc.bin; 405 auto binInfo = binInfos[pd.binID]; 406 size = binInfo.itemSize; 407 auto offset = (cast(uint) ptr) - (cast(uint) base); 408 auto index = binInfo.computeIndex(offset); 409 base = 410 cast(const(void*)*) ((cast(void*) base) + size * index); 411 } else { 412 import d.gc.sizeclass; 413 size = getSizeFromBinID(pd.binID); 414 } 415 416 range = base[0 .. size / size_t.sizeof]; 417 newPtr = header.arena.scan(range) || newPtr; 418 } 419 } 420 421 return newPtr; 422 } 423 424 void collect() { 425 uint i = 0; 426 while (i < DataPages) { 427 if (pages[i].free) { 428 auto runID = i; 429 i += pages[i].pages; 430 431 assert(pages[i - 1].free); 432 assert(pages[i - 1].pages == pages[runID].pages); 433 434 continue; 435 } 436 437 auto pd = pages[i]; 438 auto runID = i; 439 auto binID = pd.binID; 440 if (pd.large) { 441 import d.gc.sizeclass; 442 auto pages = 443 cast(uint) (getSizeFromBinID(pd.binID) >> LgPageSize); 444 i += pages; 445 446 // Check if the run is alive. 447 auto bmp = header.bitmap[runID / 32]; 448 auto mask = 1 << (runID % 32); 449 if (bmp & mask) { 450 continue; 451 } 452 453 header.arena.freeRun(&this, runID, pages); 454 continue; 455 } 456 457 assert(pd.offset == 0); 458 459 import d.gc.bin; 460 auto binInfo = binInfos[binID]; 461 i += binInfo.needPages; 462 463 auto small = &runs[runID].small; 464 auto bmpIndex = small.bitmapIndex; 465 // This is a new run, dismiss. 466 if (bmpIndex == 0) { 467 continue; 468 } 469 470 auto bitmapPtr = &header.bitmap[bmpIndex]; 471 auto headerBits = binInfo.slots / 32; 472 if (!headerBits) { 473 headerBits = 1; 474 } else { 475 assert((binInfo.slots % 32) == 0); 476 } 477 478 for (uint j = 0; j < headerBits; j++) { 479 auto liveBmp = bitmapPtr[j]; 480 481 // Zero means allocated, so we flip bits. 482 auto oldBmp = small.bitmap[j]; 483 auto newBmp = oldBmp | ~liveBmp; 484 485 auto freed = newBmp ^ oldBmp; 486 if (freed) { 487 import sdc.intrinsics; 488 small.freeSlots += popCount(freed); 489 small.header = cast(ushort) (small.header | (1 << i)); 490 small.bitmap[j] = newBmp; 491 } 492 } 493 } 494 495 // FIXME: It seems that there are some issue with alias this. 496 header.arena.free(header.bitmap); 497 header.bitmap = null; 498 499 // FIXME: empty array not supported. 500 // header.worklist = []; 501 assert(header.worklist.ptr is null); 502 assert(header.worklist.length == 0); 503 } 504 505 /** 506 * Split run from free space. 507 */ 508 uint splitSmallRun(uint runID, ubyte binID) { 509 // XXX: in contract. 510 import d.gc.bin, d.gc.sizeclass; 511 assert(binID < ClassCount.Small); 512 assert(pages[runID].free); 513 514 auto binInfo = binInfos[binID]; 515 auto needPages = binInfo.needPages; 516 517 assert((needPages << LgPageSize) <= pages[runID].size); 518 auto rem = runSplitRemove(runID, needPages); 519 520 auto dirty = pages[runID].dirty; 521 522 setBitmap(runID, needPages, 523 PageDescriptor(true, false, false, dirty, binID, 0)); 524 525 runs[runID].small.binID = binID; 526 runs[runID].small.freeSlots = binInfo.slots; 527 528 // During the collection process, other runs may be created, 529 // they are considered live and will be scanned during the next 530 // collection cycle. 531 runs[runID].small.bitmapIndex = 0; 532 533 auto headerBits = binInfo.slots / 32; 534 if (!headerBits) { 535 runs[runID].small.header = 1; 536 runs[runID].small.bitmap[0] = (1 << (binInfo.slots % 32)) - 1; 537 return rem; 538 } 539 540 assert((binInfo.slots % 32) == 0); 541 542 runs[runID].small.header = cast(ushort) ((1 << headerBits) - 1); 543 for (uint i = 0; i < headerBits; i++) { 544 runs[runID].small.bitmap[i] = -1; 545 } 546 547 return rem; 548 } 549 550 uint splitLargeRun(size_t size, uint runID, ubyte binID, bool zero) { 551 // XXX: in contract. 552 import d.gc.bin, d.gc.sizeclass; 553 assert(size > SizeClass.Small && size <= SizeClass.Large); 554 assert(binID >= ClassCount.Small && binID < ClassCount.Large); 555 assert(pages[runID].free); 556 assert(size == getAllocSize(size)); 557 assert(getBinID(size) == binID); 558 559 // If we are GCing, mark the new allocation as live. 560 auto bPtr = header.bitmap; 561 if (bPtr !is null) { 562 bPtr = &bPtr[runID / 32]; 563 *bPtr = *bPtr | (1 << (runID % 32)); 564 } 565 566 auto needPages = cast(uint) (size >> LgPageSize); 567 auto rem = runSplitRemove(runID, needPages); 568 569 auto dirty = pages[runID].dirty; 570 if (zero) { 571 if (dirty) { 572 memset(&datas[runID], 0, needPages << LgPageSize); 573 } else { 574 for (uint i = 0; i < needPages; i++) { 575 auto p = runID + i; 576 if (!pages[p].zeroed) { 577 memset(&datas[p], 0, PageSize); 578 } 579 } 580 } 581 } 582 583 setBitmap(runID, needPages, 584 PageDescriptor(true, true, zero, dirty, binID, 0)); 585 586 return rem; 587 } 588 589 private: 590 void setBitmap(uint runID, uint needPages, PageDescriptor base) { 591 // FIXME: in contract 592 assert(base.allocated); 593 594 for (uint i = 0; i < needPages; i++) { 595 auto p = runID + i; 596 auto d = pages[p]; 597 598 pages[p] = PageDescriptor(base.allocated, base.large, base.zeroed, 599 base.dirty, base.binID, i); 600 601 if (d.zeroed && !base.dirty) { 602 for (uint j; j < datas[p].length; j++) { 603 assert(datas[p][j] == 0); 604 } 605 } 606 } 607 } 608 609 uint runSplitRemove(uint runID, uint needPages) { 610 // XXX: in contract. 611 assert(!pages[runID].allocated); 612 613 auto pd = pages[runID]; 614 auto totalPages = pd.pages; 615 auto dirty = pd.dirty; 616 617 assert(needPages <= totalPages); 618 auto remPages = totalPages - needPages; 619 620 if (dirty) { 621 // XXX: arena dirty remove. 622 } 623 624 if (remPages == 0) { 625 return 0; 626 } 627 628 import d.gc.sizeclass; 629 auto remBin = cast(ubyte) (getBinID((remPages << LgPageSize) + 1) - 1); 630 631 // If we have remaining free space, keep track of it. 632 import d.gc.bin; 633 auto d = 634 PageDescriptor(false, false, pd.zeroed, dirty, remBin, remPages); 635 636 pages[runID + needPages] = d; 637 pages[runID + totalPages - 1] = d; 638 639 if (dirty) { 640 // XXX: arena dirty add. 641 } 642 643 return runID + needPages; 644 } 645 } 646 647 Chunk* findChunk(const void* ptr) { 648 import d.gc.spec; 649 auto c = cast(Chunk*) ((cast(size_t) ptr) & ~AlignMask); 650 651 // XXX: type promotion is fucked. 652 auto vc = cast(void*) c; 653 if (vc is ptr) { 654 // Huge alloc, no arena. 655 return null; 656 } 657 658 return c; 659 } 660 661 private: 662 663 struct Header { 664 import d.gc.extent; 665 Extent extent; 666 alias extent this; 667 668 uint* bitmap; 669 const(void)*[] worklist; 670 } 671 672 enum DataPages = computeDataPages(); 673 enum MetaPages = ChunkPageCount - DataPages; 674 675 enum MinMetaPages = ((Header.sizeof - 1) / /+ PageDescriptor.alignof +/ 4) + 1; 676 enum Pad0Size = MetaPages - MinMetaPages; 677 enum Pad1Size = 678 ((MetaPages * PageSize) - computeRunDescEnd(DataPages)) / uint.sizeof; 679 680 auto computeDataPages() { 681 foreach (metaPages; MinMetaPages .. ChunkPageCount) { 682 auto dataPages = ChunkPageCount - metaPages; 683 auto runDescEnd = computeRunDescEnd(dataPages); 684 auto requiredMetaPages = ((runDescEnd - 1) >> LgPageSize) + 1; 685 686 if (requiredMetaPages == metaPages) { 687 return dataPages; 688 } 689 } 690 691 assert(0, "Chunk is too small"); 692 } 693 694 auto computeRunDescEnd(size_t dataPages) { 695 auto pageDescEnd = ChunkPageCount * PageDescriptor.sizeof; 696 697 import d.gc.run; 698 enum RDAlign = /+ RunDesc.alignof +/ 8; 699 auto runDescStart = (((pageDescEnd - 1) / RDAlign) + 1) * RDAlign; 700 701 return runDescStart + dataPages * RunDesc.sizeof; 702 }