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 }