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