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