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 }