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