1 module memutils.scoped;
2 
3 import core.thread : Fiber;
4 import memutils.constants;
5 import memutils.allocators;
6 import memutils.pool;
7 import memutils.utils;
8 import memutils.vector;
9 import memutils.refcounted;
10 import memutils.unique;
11 import memutils.hashmap;
12 import memutils.freelist;
13 import memutils.memory;
14 import memutils.helpers;
15 import std.traits : hasElaborateDestructor, isArray;
16 import std.algorithm : min;
17 import std.exception;
18 import core.exception;
19 import core.stdc.string : memcpy;
20 
21 alias ScopedPool = RefCounted!ScopedPoolImpl;
22 
23 final class ScopedPoolImpl {
24 	// TODO: Use a name for debugging?
25 
26 	int id;
27 	/// Initializes a scoped pool with max_mem
28 	/// max_mem doesn't do anything at the moment
29 	this(size_t max_mem = 0) {
30 		PoolStack.push();
31 		id = PoolStack.top.id;
32 		//logDebug("ScopedPool.this id: ", id);
33 	}
34 
35 	~this() {
36 		//logDebug("ScopedPool.~this id: ", id, " PoolStack.top.id: ", PoolStack.top.id);
37 		debug if(id != PoolStack.top.id) {
38 			//logDebug("Unfreezing...");
39 			unfreeze();
40 		}
41 		PoolStack.pop();
42 	}
43 
44 	/// Use only if ScopedPool is the highest on stack.
45 	void freeze() {
46 		enforce(id == PoolStack.top.id);
47 		enforce(PoolStack.freeze(1) == 1, "Failed to freeze pool");
48 	}
49 
50 	void unfreeze() {
51 		enforce(PoolStack.unfreeze(1) == 1, "Failed to unfreeze pool");
52 		enforce(id == PoolStack.top.id);
53 	}
54 }
55 
56 T alloc(T, ARGS...)(auto ref ARGS args)
57 	if (is(T == class) || is(T == interface) || __traits(isAbstractClass, T))
58 {
59 	T ret;
60 	
61 	if (!PoolStack.empty) {
62 		ret = ObjectAllocator!(T, PoolStack).alloc(args);
63 		
64 		// Add destructor to pool
65 		static if (hasElaborateDestructor!T || __traits(hasMember, T, "__xdtor") ) 
66 			PoolStack.top().onDestroy(&ret.__xdtor);
67 	}
68 	else {
69 		ret = new T(args);
70 	}
71 	
72 	return ret;
73 }
74 
75 T* alloc(T, ARGS...)(auto ref ARGS args)
76 	if (!(is(T == class) || is(T == interface) || __traits(isAbstractClass, T)))
77 {
78 	T* ret;
79 	
80 	if (!PoolStack.empty) {
81 		ret = ObjectAllocator!(T, PoolStack).alloc(args);
82 		
83 		// Add destructor to pool
84 		static if (hasElaborateDestructor!T || __traits(hasMember, T, "__xdtor") ) 
85 			PoolStack.top.onDestroy(&ret.__xdtor);
86 		
87 	}
88 	else {
89 		ret = new T(args);
90 	}
91 	
92 	return ret;
93 }
94 
95 /// arrays
96 auto alloc(T)(size_t n)
97 	if (isArray!T)
98 {
99 	import std.range : ElementType;
100 	
101 	T ret;
102 	if (!PoolStack.empty) {
103 		ret = allocArray!(ElementType!T, PoolStack)(n);
104 		registerPoolArray(ret);
105 	}
106 	else {
107 		ret = new T(n);
108 	}
109 	return ret;
110 }
111 
112 auto realloc(T)(ref T arr, size_t n)
113 	if (isArray!T)
114 {
115 	import std.range : ElementType;
116 	T ret;
117 	if (!PoolStack.empty) {
118 		scope(exit) arr = null;
119 		ret = reallocArray!(ElementType!T, PoolStack)(arr, n);
120 		reregisterPoolArray(arr, ret);
121 	}
122 	else {
123 		ret.length = n;
124 		ret[0 .. arr.length] = arr[];
125 		arr = null;
126 	}
127 	return ret;
128 }
129 
130 auto copy(T)(auto ref T arr)
131 	if (isArray!T)
132 {
133 	import std.range : ElementType;
134 	
135 	alias ElType = UnConst!(typeof(arr[0]));
136 
137 	ElType[] ret;
138 	if (!PoolStack.empty) {
139 		ret = allocArray!(ElType, PoolStack)(arr.length);
140 		memcpy(cast(void*)ret.ptr, cast(void*)arr.ptr, arr.length * ElType.sizeof);
141 	} else {
142 		ret.length = arr.length;
143 		ret[] = arr[];
144 	}
145 
146 	return cast(T)ret;
147 }
148 
149 struct PoolStack {
150 static:
151 	@property bool empty() { return m_tstack.empty && m_fstack.empty; }
152 
153 	/// returns the most recent unfrozen pool, null if none available
154 	@property ManagedPool top() {
155 		assert(!m_fstack.empty || !m_tstack.empty, "No Pool found on stack");
156 		assert((Fiber.getThis() && !m_fstack.empty) || !m_tstack.empty, "PoolStack.top() called with empty PoolStack");
157 		if (Fiber.getThis() && !m_fstack.empty) {
158 			return m_fstack.top;
159 		}
160 		return m_tstack.top;
161 	}
162 
163 	/// creates a new pool as the fiber stack top or the thread stack top
164 	void push() {
165 		//logTrace("Pushing PoolStack");
166 		if (Fiber.getThis())
167 			return m_fstack.push();
168 		m_tstack.push();
169 		//logTrace("Pushed ThreadStack");
170 	}
171 
172 	/// destroy the most recent pool and free all its resources, calling destructors
173 	/// if you're in a fiber, search for stack top in the fiber stack and destroy it.
174 	/// otherwise, search in the thread stack and destroy it.
175 	void pop() {
176 		//logTrace("Pop PoolStack");
177 		if (Fiber.getThis() && (!m_fstack.empty || !m_ffreezer.empty))
178 		{
179 			//logTrace("Pop FiberStack");
180 			assert(!m_fstack.empty, "pop() called on empty FiberPoolStack");
181 			return m_fstack.pop();
182 		}
183 		assert(!m_tstack.empty, "pop() called on empty ThreadPoolStack");
184 		return m_tstack.pop();
185 		//logTrace("Destroyign ", ret.back.id);
186 
187 	}
188 
189 	void disable() {
190 		freeze(m_tstack.length + m_fstack.length);
191 	}
192 
193 	void enable() {
194 		unfreeze(m_ffreezer.length + m_tfreezer.length);
195 	}
196 
197 	// returns number of pools frozen
198 	size_t freeze(size_t n = 1) {
199 		auto minsz = min(m_fstack.length, n);
200 
201 		if (minsz > 0) {
202 			auto frozen = m_fstack.freeze(minsz);
203 			m_ffreezer.push(frozen);
204 		}
205 
206 		if (minsz < n) {
207 			auto tsz = min(m_tstack.length, n - minsz);
208 			if (tsz > 0) {
209 				auto frozen = m_tstack.freeze(tsz);
210 			 	m_tfreezer.push(frozen);
211 			}
212 			return tsz + minsz;
213 		}
214 		return minsz;
215 	}
216 
217 	// returns number of pools unfrozen
218 	size_t unfreeze(size_t n = 1) {
219 		auto minsz = min(m_ffreezer.length, n);
220 		
221 		if (minsz > 0) m_fstack.unfreeze(m_ffreezer.pop(minsz));
222 		
223 		if (minsz < n) {
224 			auto tsz = min(m_tfreezer.length, n - minsz);
225 			if (tsz > 0) m_tstack.unfreeze(m_tfreezer.pop(tsz));
226 			return tsz + minsz;
227 		}
228 		return minsz;
229 	}
230 
231 	~this() {
232 		destroy(m_fstack);
233 		destroy(m_tstack);
234 	}
235 
236 private static:
237 	// active
238 	ThreadPoolStack m_tstack;
239 	FiberPoolStack m_fstack;
240 
241 	// frozen
242 	ThreadPoolFreezer m_tfreezer;
243 	FiberPoolFreezer m_ffreezer;
244 
245 }
246 
247 package:
248 
249 alias Pool = PoolAllocator!(AutoFreeListAllocator!(MallocAllocator));
250 alias ManagedPool = RefCounted!(Pool);
251 
252 /// User utility for allocating on lower level pools
253 struct ThreadPoolFreezer 
254 {
255 	@disable this(this);
256 	@property size_t length() const { return m_pools.length; }
257 	@property bool empty() const { return length == 0; }
258 
259 	void push(Array!(ManagedPool, Malloc) pools)
260 	{
261 		//logTrace("Push Thread Freezer of ", m_pools.length);
262 		// insert sorted
263 		foreach(ref item; pools[]) {
264 			bool found;
265 			foreach (size_t i, ref el; m_pools[]) {
266 				if (item.id < el.id) {
267 					m_pools.insertBefore(i, item);
268 					found = true;
269 					break;
270 				}
271 			}
272 			if (!found) m_pools ~= item;
273 		}
274 		//logTrace("Pushed Thread Freezer now ", m_pools.length);
275 	}
276 
277 	Array!(ManagedPool, Malloc) pop(size_t n) {
278 		assert(!empty);
279 		//logTrace("Pop Thread Freezer of ", m_pools.length, " id ", m_pools.back.id);
280 		// already sorted
281 		auto pools = Array!(ManagedPool, Malloc)( m_pools[$-n .. $] );
282 
283 		
284 		m_pools.length = (m_pools.length - 1);
285 		//logTrace("Popped Thread Freezer returning ", pools.length, " expecting ", n);
286 		//logTrace("Returning ID ", pools.back.id);
287 		return pools;
288 	}
289 	
290 package:
291 	Vector!(ManagedPool, Malloc) m_pools;
292 }
293 
294 /// User utility for allocating on lower level pools
295 struct FiberPoolFreezer
296 {
297 	@disable this(this);
298 	@property size_t fibers() const { return m_pools.length; }
299 	
300 	@property size_t length() const { 
301 		Fiber f = Fiber.getThis();
302 		if (auto ptr = (f in m_pools)) {
303 			return (*ptr).length;
304 		}
305 		return 0;
306 	}
307 
308 	@property bool empty() const {
309 		return length == 0; 
310 	}
311 
312 	void push(Array!(ManagedPool, Malloc) pools)
313 	{
314 		logDebug("Push Fiber Freezer of ", length);
315 		Fiber f = Fiber.getThis();
316 		assert(f !is null);
317 		if (auto ptr = (f in m_pools)) {
318 			auto arr = *ptr;
319 
320 			// insert sorted
321 			foreach(ref item; pools[]) {
322 				bool found;
323 				foreach (size_t i, ref el; arr[]) {
324 					if (item.id < el.id) {
325 						arr.insertBefore(i, item);
326 						found = true;
327 						break;
328 					}
329 				}
330 				if (!found) arr ~= item;
331 			}
332 			//logTrace("Pushed Fiber Freezer of ", length);
333 			return;
334 		}
335 		//else
336 		m_pools[f] = pools.cloneToRef;
337 		//logTrace("Pushed Fiber Freezer of ", length);
338 	}
339 
340 	Array!(ManagedPool, Malloc) pop(size_t n) {
341 		logDebug("Pop Fiber Freezer of ", length);
342 		assert(!empty);
343 		
344 		Fiber f = Fiber.getThis();
345 		auto arr = m_pools[f];
346 
347 		if (arr.empty) {
348 			m_pools.remove(f);
349 			return Array!(ManagedPool, Malloc)();
350 		}
351 
352 		// already sorted
353 		auto pools = Array!(ManagedPool, Malloc)( arr[$-n .. $] );
354 		arr.length = (arr.length - n);
355 		//logTrace("Popped Fiber Freezer of ", length);
356 		return pools;
357 	}
358 
359 private:
360 
361 	HashMap!(Fiber, Array!(ManagedPool, Malloc), Malloc) m_pools;
362 }
363 struct ThreadPoolStack
364 {
365 	@disable this(this);
366 	@property size_t length() const { return m_pools.length; }
367 	@property bool empty() const { return length == 0; }
368 	size_t opDollar() const { return length; }
369 	@property bool hasTop() { return length > 0 && cnt-1 == top.id; }
370 
371 
372 	ManagedPool opIndex(size_t n) {
373 		//logTrace("OpIndex[", n, "] in Thread Pool of ", length, " top: ", cnt, " id: ", m_pools[n].id);
374 		return m_pools[n];
375 	}
376 
377 	@property ManagedPool top() 
378 	{
379 		//logTrace("Front Thread Pool of ", length);
380 		if (empty) {
381 			//logTrace("Empty");
382 			return ManagedPool();
383 		}
384 		return m_pools.back;
385 	}
386 
387 	void pop()
388 	{
389 		assert(!empty);
390 		//logTrace("Pop Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
391 		auto pool = m_pools.back;
392 		assert(pool.id == cnt-1);
393 		--cnt;
394 		m_pools.removeBack();
395 		//if (!empty) logTrace("Popped Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
396 	}
397 
398 	void push() {
399 		//if (!m_pools.empty) logTrace("Push Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
400 		//else logTrace("Push Thread Pool of ", length, " top: ", cnt);
401 		ManagedPool pool = ManagedPool();
402 		pool.id = cnt++;
403 		m_pools.pushBack(pool);
404 		//logTrace("Pushed Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
405 	}
406 
407 	Array!(ManagedPool, Malloc) freeze(size_t n) {
408 		assert(!empty);
409 		//if (!m_pools.empty) logTrace("Freeze ", n, " in Thread Pool of ", length, " top: ", cnt);
410 		//else logTrace("Freeze ", n, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
411 		assert(n <= length);
412 		auto ret = Array!(ManagedPool, Malloc)(n);
413 		ret[] = m_pools[$-n .. $];
414 		m_pools.removeBack(n);
415 		//logTrace("Returning ", ret.length);
416 		//if (!empty) logTrace("Freezeed ", n, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
417 		return ret;
418 	}
419 
420 	void unfreeze(Array!(ManagedPool, Malloc) pools) {
421 		//logTrace("Unfreeze ", pools.length, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
422 		// insert sorted
423 		foreach(ref item; pools[]) {
424 			bool found;
425 			foreach (size_t i, ref el; m_pools[]) {
426 				if (item.id < el.id) {
427 					m_pools.insertBefore(i, item);
428 					found = true;
429 					break;
430 				}
431 			}
432 			if (!found) m_pools ~= item;
433 		}
434 		//logTrace("Unfreezed ", pools.length, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id);
435 	}
436 
437 package:
438 	int cnt;
439 	Vector!(ManagedPool, Malloc) m_pools;
440 }
441 
442 struct FiberPoolStack
443 {
444 	@disable this(this);
445 	@property size_t fibers() const { return m_pools.length; }
446 
447 	@property size_t length() const {
448 		Fiber f = Fiber.getThis();
449 		if (auto ptr = (f in m_pools)) {
450 			return (*ptr).length;
451 		}
452 		return 0;
453 	}
454 
455 	@property bool hasTop() { return length > 0 && cnt-1 == top.id; }
456 
457 	@property bool empty() const {
458 		return length == 0; 
459 	}
460 
461 	size_t opDollar() const { return length; }
462 
463 	ManagedPool opIndex(size_t n) {
464 		assert(!empty);
465 		Fiber f = Fiber.getThis();
466 		//logTrace("OpIndex[", n, "] in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f][n].id);
467 		return m_pools[f][n];
468 
469 	}
470 
471 	@property ManagedPool top() 
472 	{
473 		assert(!empty);
474 		Fiber f = Fiber.getThis();
475 		if (auto ptr = (f in m_pools)) {
476 			//logTrace("top in Fiber Pool of ", length, " top: ", cnt, " len: ", (*ptr).back().id);
477 			return (*ptr).back();
478 		}
479 		return ManagedPool();
480 
481 	}
482 
483 	// returns next item ie. top()
484 	void pop() {
485 		assert(!empty);
486 
487 		Fiber f = Fiber.getThis();
488 		logDebug("pop in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
489 		auto arr = m_pools[f];
490 		assert(arr.back.id == cnt-1);
491 		arr.removeBack();
492 		cnt--;
493 		if (arr.empty) 
494 			m_pools.remove(f);
495 		//if (!empty) logTrace("popped in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
496 	}
497 
498 	void push()
499 	{
500 		ManagedPool pool = ManagedPool();
501 		pool.id = cnt++;
502 		Fiber f = Fiber.getThis();
503 		if (!empty) logDebug("Push in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools.get(f).back.id);
504 		assert(f !is null);
505 		if (auto ptr = (f in m_pools)) {
506 			*ptr ~= pool;
507 			//logTrace("Pushed in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
508 			return;
509 		}
510 		//else
511 		m_pools[f] = Array!(ManagedPool, Malloc)();
512 		m_pools[f] ~= pool;
513 		//logDebug("Pushed in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
514 	}
515 
516 	// returns the frozen items
517 	Array!(ManagedPool, Malloc) freeze(size_t n) {
518 		assert(n <= length);
519 		Fiber f = Fiber.getThis();
520 		logDebug("Freeze in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
521 		auto arr = m_pools[f];
522 		logDebug("Got array");
523 		auto ret = Array!(ManagedPool, Malloc)(n);
524 		ret[] = arr[$-n .. $];
525 		arr.removeBack(n);
526 		logDebug("Frozen in Fiber Pool of ", length, " top: ", cnt);
527 		return ret;
528 	}
529 
530 
531 	void unfreeze(Array!(ManagedPool, Malloc) items)
532 	{
533 		Fiber f = Fiber.getThis();
534 		assert(f !is null);
535 		logDebug("Unfreeze in Fiber Pool of ", length, " top: ", cnt);
536 		if (auto ptr = (f in m_pools)) {
537 			auto arr = *ptr;
538 			// insert sorted
539 			foreach(ref item; items[]) {
540 				bool found;
541 				foreach (size_t i, ref el; arr[]) {
542 					if (item.id < el.id) {
543 						arr.insertBefore(i, item);
544 						found = true;
545 						break;
546 					}
547 				}
548 				if (!found) arr ~= item;
549 			}
550 			logDebug("Unfrozen in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id);
551 			return;
552 		}
553 		assert(false);
554 	}
555 package:
556 	int cnt;
557 	HashMap!(Fiber, Array!(ManagedPool, Malloc), Malloc) m_pools;
558 }
559 
560 
561 private void registerPoolArray(T)(ref T arr) {
562 	import std.range : ElementType;
563 	// Add destructors to fiber pool
564 	static if (is(T == struct) && (hasElaborateDestructor!(ElementType!T) || __traits(hasMember, ElementType!T, "__xdtor") )) {
565 		foreach (ref el; arr)
566 			PoolStack.top.onDestroy(&el.__xdtor);
567 	}
568 }
569 
570 private void reregisterPoolArray(T)(ref T arr, ref T arr2) {
571 	import std.range : ElementType;
572 	// Add destructors to fiber pool
573 	static if (is(T == struct) && (hasElaborateDestructor!(ElementType!T) || __traits(hasMember, ElementType!T, "__xdtor") )) {
574 		if (arr.ptr is arr2.ptr && arr2.length > arr.length) {
575 			foreach (ref el; arr2[arr.length - 1 .. $])
576 				PoolStack.top.onDestroy(&el.__xdtor);
577 		}
578 		else {
579 			PoolStack.top.removeArrayDtors(&arr.back.__xdtor, arr.length);
580 			registerPoolArray(arr2);
581 		}
582 	}
583 }