1 /**
2  * Unrolled Linked List.
3  * Copyright: © 2014 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.unrolledlist;
9 
10 /**
11  * Unrolled Linked List.
12  *
13  * Nodes are (by default) sized to fit within a 64-byte cache line. The number
14  * of items stored per node can be read from the $(B nodeCapacity) field.
15  * See_also: $(LINK http://en.wikipedia.org/wiki/Unrolled_linked_list)
16  * Params:
17  *     T = the element type
18  *     supportGC = true to ensure that the GC scans the nodes of the unrolled
19  *         list, false if you are sure that no references to GC-managed memory
20  *         will be stored in this container.
21  *     cacheLineSize = Nodes will be sized to fit within this number of bytes.
22  */
23 struct UnrolledList(T, bool supportGC = true, size_t cacheLineSize = 64)
24 {
25 	this(this) @disable;
26 
27 	~this()
28 	{
29 		clear();
30 	}
31 
32 	/**
33 	 * Removes all items from the list
34 	 */
35 	void clear()
36 	{
37 		Node* prev = null;
38 		Node* cur = _front;
39 		debug (EMSI_CONTAINERS)
40 		{
41 			ulong nodeCount = 0;
42 			for (Node* c = _front; c !is null; c = c.next)
43 				++nodeCount;
44 		}
45 		while (cur !is null)
46 		{
47 			prev = cur;
48 			cur = cur.next;
49 			static if (!is(T == class))
50 				foreach (ref item; cur.items)
51 					typeid(T).destroy(&item);
52 			deallocateNode(prev);
53 		}
54 		debug (EMSI_CONTAINERS)
55 		{
56 			import std.string:format;
57 			assert (allocCount == deallocCount, "
58 	Nodes: %d
59 	allocations: %d
60 	deallocations: %d
61 	nodeCapacity: %d
62 	length: %d".format(nodeCount, allocCount, deallocCount, nodeCapacity, length));
63 		}
64 		_length = 0;
65 	}
66 
67 	/**
68 	 * Inserts the given item into the end of the list.
69 	 */
70 	void insertBack(T item)
71 	{
72 		if (_back is null)
73 		{
74 			assert (_front is null);
75 			_back = allocateNode(item);
76 			_front = _back;
77 		}
78 		else
79 		{
80 			size_t index = _back.nextAvailableIndex();
81 			if (index >= nodeCapacity)
82 			{
83 				Node* n = allocateNode(item);
84 				n.prev = _back;
85 				_back.next = n;
86 				_back = n;
87 				index = 0;
88 			}
89 			else
90 			{
91 				_back.items[index] = item;
92 				_back.markUsed(index);
93 			}
94 		}
95 		_length++;
96 		assert (_back.registry <= fullBits!nodeCapacity);
97 	}
98 
99 	/**
100 	 * Inserts the given range into the end of the list
101 	 */
102 	void insertBack(R)(auto ref R range)
103 	{
104 		foreach (ref r; range)
105 			insertBack(r);
106 	}
107 
108 	/// ditto
109 	alias put = insertBack;
110 	/// ditto
111 	alias insert = insertBack;
112 
113 	/**
114 	 * Inserts the given item in the frontmost available cell, which may put the
115 	 * item anywhere in the list as removal may leave gaps in list nodes. Use
116 	 * this only if the order of elements is not important.
117 	 */
118 	void insertAnywhere(T item)
119 	{
120 		Node* n = _front;
121 		while (_front !is null)
122 		{
123 			size_t i = n.nextAvailableIndex();
124 			if (i >= nodeCapacity)
125 			{
126 				if (n.next is null)
127 					break;
128 				n = n.next;
129 				continue;
130 			}
131 			n.items[i] = item;
132 			n.markUsed(i);
133 			_length++;
134 			assert (n.registry <= fullBits!nodeCapacity);
135 			return;
136 		}
137 		assert (n is _back);
138 		n = allocateNode(item);
139 		_back.next = n;
140 		_back = n;
141 		_length++;
142 		assert (_back.registry <= fullBits!nodeCapacity);
143 	}
144 
145 	/// Returns: the length of the list
146 	size_t length() const nothrow pure @property @safe @nogc
147 	{
148 		return _length;
149 	}
150 
151 	/// Returns: true if the list is empty
152 	bool empty() const nothrow pure @property @safe @nogc
153 	{
154 		return _length == 0;
155 	}
156 
157 	/**
158 	 * Removes the given item from the list.
159 	 * Returns: true if something was removed.
160 	 */
161 	bool remove(T item)
162 	{
163 		import core.bitop : popcnt;
164 		if (_front is null)
165 			return false;
166 		bool retVal = false;
167 		loop: for (Node* n = _front; n !is null; n = n.next)
168 		{
169 			foreach (i; 0 .. nodeCapacity)
170 			{
171 				if (n.items[i] == item)
172 				{
173 					n.markUnused(i);
174 					--_length;
175 					retVal = true;
176 					if (n.registry == 0)
177 						deallocateNode(n);
178 					else if (shouldMerge(n, n.next))
179 						mergeNodes(n, n.next);
180 					else if (shouldMerge(n.prev, n))
181 						mergeNodes(n.prev, n);
182 					break loop;
183 				}
184 			}
185 		}
186 		return retVal;
187 	}
188 
189 	/// Pops the front item off of the list
190 	void popFront()
191 	{
192 		moveFront();
193 		assert (_front is null || _front.registry != 0);
194 	}
195 
196 	/// Pops the front item off of the list and returns it
197 	T moveFront()
198 	in
199 	{
200 		assert (!empty());
201 		assert (_front.registry != 0);
202 	}
203 	body
204 	{
205 		import core.bitop : bsf, popcnt;
206 		size_t index = bsf(_front.registry);
207 		T r = _front.items[index];
208 		_front.markUnused(index);
209 		_length--;
210 		if (_front.registry == 0)
211 		{
212 			auto f = _front;
213 			if (_front.next !is null)
214 				_front.next.prev = null;
215 			assert (_front.next !is _front);
216 			_front = _front.next;
217 			if (_front is null)
218 				_back = null;
219 			else
220 				assert (_front.registry <= fullBits!nodeCapacity);
221 			deallocateNode(f);
222 			return r;
223 		}
224 		if (shouldMerge(_front, _front.next))
225 			mergeNodes(_front, _front.next);
226 		return r;
227 	}
228 
229 	debug (EMSI_CONTAINERS) invariant
230 	{
231 		import std.string: format;
232 		assert (_front is null || _front.registry != 0, format("%x, %b", _front, _front.registry));
233 		assert (_front !is null || _back is null);
234 		if (_front !is null)
235 		{
236 			const(Node)* c = _front;
237 			while (c.next !is null)
238 				c = c.next;
239 			assert(c is _back, "_back pointer is wrong");
240 		}
241 	}
242 
243 	/**
244 	 * Time complexity is O(1)
245 	 * Returns: the item at the front of the list
246 	 */
247 	inout(T) front() inout @property
248 	in
249 	{
250 		assert (!empty);
251 		assert (_front.registry != 0);
252 	}
253 	body
254 	{
255 		import core.bitop: bsf;
256 		import std.string: format;
257 		size_t index = bsf(_front.registry);
258 		assert (index < nodeCapacity, format("%d", index));
259 		return _front.items[index];
260 	}
261 
262 	/**
263 	 * Time complexity is O(n)
264 	 * Returns: the item at the back of the list
265 	 */
266 	inout(T) back() inout @property
267 	in
268 	{
269 		assert (!empty);
270 		assert (!_back.empty);
271 	}
272 	body
273 	{
274 		size_t i = nodeCapacity - 1;
275 		while (_back.isFree(i))
276 			i--;
277 		return _back.items[i];
278 	}
279 
280 	/// Pops the back item off of the list.
281 	void popBack()
282 	{
283 		moveBack();
284 	}
285 
286 	/// Removes an item from the back of the list and returns it.
287 	T moveBack()
288 	in
289 	{
290 		assert (!empty);
291 		assert (!_back.empty);
292 	}
293 	body
294 	{
295 		import core.bitop : popcnt;
296 		size_t i = nodeCapacity - 1;
297 		while (_back.isFree(i))
298 		{
299 			if (i == 0)
300 				break;
301 			else
302 				i--;
303 		}
304 		assert (!_back.isFree(i));
305 		T item = _back.items[i];
306 		_back.markUnused(i);
307 		_length--;
308 		if (_back.registry == 0)
309 		{
310 			deallocateNode(_back);
311 			return item;
312 		}
313 		else if (shouldMerge(_back.prev, _back))
314 			mergeNodes(_back.prev, _back);
315 		return item;
316 	}
317 
318 	/**
319 	 * Number of items stored per node.
320 	 */
321 	enum size_t nodeCapacity = fatNodeCapacity!(T.sizeof, 2, ushort, cacheLineSize);
322 
323 	/// Returns: a range over the list
324 	auto range(this This)() const nothrow pure @nogc @trusted @property
325 	{
326 		return Range!(This)(_front);
327 	}
328 
329 	/// ditto
330 	alias opSlice = range;
331 
332 	static struct Range(ThisT)
333 	{
334 		@disable this();
335 
336 		this(inout(Node)* current)
337 		{
338 			import core.bitop: bsf;
339 			this.current = current;
340 			if (current !is null)
341 			{
342 				index = bsf(current.registry);
343 				assert (index < nodeCapacity);
344 			}
345 		}
346 
347 		ET front() const @property @trusted @nogc
348 		{
349 			return cast(T) current.items[index];
350 		}
351 
352 		void popFront() nothrow pure
353 		{
354 			index++;
355 			while (true)
356 			{
357 				if (current is null)
358 					return;
359 				if (index >= nodeCapacity)
360 				{
361 					current = current.next;
362 					index = 0;
363 				}
364 				else
365 				{
366 					if (current.isFree(index))
367 						index++;
368 					else
369 						return;
370 				}
371 			}
372 		}
373 
374 		bool empty() const nothrow pure @property @safe @nogc
375 		{
376 			return current is null;
377 		}
378 
379 		Range save() const nothrow pure @property @safe @nogc
380 		{
381 			return this;
382 		}
383 
384 	private:
385 
386 		alias ET = ContainerElementType!(ThisT, T);
387 		const(Node)* current;
388 		size_t index;
389 	}
390 
391 private:
392 
393 	import std.experimental.allocator: make, dispose;
394 	import std.experimental.allocator.mallocator : Mallocator;
395 	import containers.internal.node : fatNodeCapacity, shouldAddGCRange,
396 		fullBits, shouldNullSlot;
397 	import containers.internal.storage_type : ContainerStorageType;
398 	import containers.internal.element_type : ContainerElementType;
399 
400 	Node* _back;
401 	Node* _front;
402 	size_t _length;
403 	debug (EMSI_CONTAINERS)
404 	{
405 		ulong allocCount;
406 		ulong deallocCount;
407 	}
408 
409 	Node* allocateNode(T item)
410 	{
411 		Node* n = Mallocator.instance.make!Node();
412 		debug (EMSI_CONTAINERS) ++allocCount;
413 		static if (supportGC && shouldAddGCRange!T)
414 		{
415 			import core.memory: GC;
416 			GC.addRange(n, Node.sizeof);
417 		}
418 		n.items[0] = item;
419 		n.markUsed(0);
420 		return n;
421 	}
422 
423 	void deallocateNode(Node* n)
424 	{
425 		if (n.prev !is null)
426 			n.prev.next = n.next;
427 		if (n.next !is null)
428 			n.next.prev = n.prev;
429 		if (_front is n)
430 			_front = n.next;
431 		if (_back is n)
432 			_back = n.prev;
433 
434 		debug (EMSI_CONTAINERS) ++deallocCount;
435 		Mallocator.instance.dispose(n);
436 		static if (supportGC && shouldAddGCRange!T)
437 		{
438 			import core.memory: GC;
439 			GC.removeRange(n);
440 		}
441 	}
442 
443 	static bool shouldMerge(const Node* first, const Node* second)
444 	{
445 		import core.bitop : popcnt;
446 
447 		if (first is null || second is null)
448 			return false;
449 		immutable f = popcnt(first.registry);
450 		immutable s = popcnt(second.registry);
451 		return f + s <= nodeCapacity;
452 	}
453 
454 	void mergeNodes(Node* first, Node* second)
455 	in
456 	{
457 		assert (first !is null);
458 		assert (second !is null);
459 		assert (second is first.next);
460 	}
461 	body
462 	{
463 		import core.bitop: bsf;
464 		size_t i;
465 		ContainerStorageType!T[nodeCapacity] temp;
466 		foreach (j; 0 .. nodeCapacity)
467 			if (!first.isFree(j))
468 				temp[i++] = first.items[j];
469 		foreach (j; 0 .. nodeCapacity)
470 			if (!second.isFree(j))
471 				temp[i++] = second.items[j];
472 		first.items[0 .. i] = temp[0 .. i];
473 		first.registry = 0;
474 		foreach (k; 0 .. i)
475 			first.markUsed(k);
476 		assert (first.registry <= fullBits!nodeCapacity);
477 		deallocateNode(second);
478 	}
479 
480 	static struct Node
481 	{
482 		size_t nextAvailableIndex() const nothrow pure @safe @nogc
483 		{
484 			import core.bitop: bsf;
485 			return bsf(~registry);
486 		}
487 
488 		void markUsed(size_t index) nothrow pure @safe @nogc
489 		{
490 			registry |= (1 << index);
491 		}
492 
493 		void markUnused(size_t index) nothrow pure @safe @nogc
494 		{
495 			registry &= ~(1 << index);
496 			static if (shouldNullSlot!T)
497 				items[index] = null;
498 		}
499 
500 		bool empty() const nothrow pure @safe @nogc
501 		{
502 			return registry == 0;
503 		}
504 
505 		bool isFree(size_t index) const nothrow pure @safe @nogc
506 		{
507 			return (registry & (1 << index)) == 0;
508 		}
509 
510 		debug(EMSI_CONTAINERS) invariant()
511 		{
512 			import std.string : format;
513 			assert (registry <= fullBits!nodeCapacity, format("%016b %016b", registry, fullBits!nodeCapacity));
514 			assert (prev !is &this);
515 			assert (next !is &this);
516 		}
517 
518 		ushort registry;
519 		ContainerStorageType!T[nodeCapacity] items;
520 		Node* prev;
521 		Node* next;
522 	}
523 }
524 
525 unittest
526 {
527 	import std.algorithm : equal;
528 	import std.range : iota;
529 	import std.string : format;
530 	UnrolledList!int l;
531 	static assert (l.Node.sizeof <= 64);
532 	assert (l.empty);
533 	l.insert(0);
534 	assert (l.length == 1);
535 	assert (!l.empty);
536 	foreach (i; 1 .. 100)
537 		l.insert(i);
538 	assert (l.length == 100);
539 	assert (equal(l[], iota(100)));
540 	foreach (i; 0 .. 100)
541 		assert (l.remove(i), format("%d", i));
542 	assert (l.length == 0, format("%d", l.length));
543 	assert (l.empty);
544 	UnrolledList!int l2;
545 	l2.insert(1);
546 	l2.insert(2);
547 	l2.insert(3);
548 	assert (l2.front == 1);
549 	l2.popFront();
550 	assert (l2.front == 2);
551 	assert (equal(l2[], [2, 3]));
552 	l2.popFront();
553 	assert (equal(l2[], [3]));
554 	l2.popFront();
555 	assert (l2.empty, format("%d", l2.front));
556 	assert (equal(l2[], cast(int[]) []));
557 	UnrolledList!int l3;
558 	foreach (i; 0 .. 200)
559 		l3.insert(i);
560 	foreach (i; 0 .. 200)
561 	{
562 		auto x = l3.moveFront();
563 		assert (x == i, format("%d %d", i, x));
564 	}
565 	assert (l3.empty);
566 	foreach (i; 0 .. 200)
567 		l3.insert(i);
568 	assert (l3.length == 200);
569 	foreach (i; 0 .. 200)
570 	{
571 		assert (l3.length == 200 - i);
572 		auto x = l3.moveBack();
573 		assert (x == 200 - i - 1, format("%d %d", 200 - 1 - 1, x));
574 	}
575 	assert (l3.empty);
576 }
577 
578 unittest
579 {
580 	struct A { int a; int b; }
581 	UnrolledList!(const(A)) objs;
582 	objs.insert(A(10, 11));
583 	static assert (is (typeof(objs.front) == const));
584 	static assert (is (typeof(objs[].front) == const));
585 }