1 module memutils.utils;
2 
3 import core.thread : Fiber;	
4 import std.traits : isPointer, hasIndirections, hasElaborateDestructor, isArray, ReturnType;
5 import std.conv : emplace;
6 import core.stdc.string : memset, memcpy;
7 import memutils.allocators;
8 import std.algorithm : startsWith;
9 import memutils.constants;
10 import memutils.vector : Array;
11 import std.range : ElementType;
12 import memutils.helpers : UnConst;
13 import std.conv;
14 
15 struct AppMem {
16 	mixin ConvenienceAllocators!(NativeGC, typeof(this));
17 }
18 
19 struct ThreadMem {
20 	mixin ConvenienceAllocators!(LocklessFreeList, typeof(this));
21 }
22 
23 struct SecureMem {
24 	mixin ConvenienceAllocators!(CryptoSafe, typeof(this));
25 }
26 // Reserved for containers
27 struct Malloc {
28 	enum ident = Mallocator;
29 }
30 
31 package:
32 
33 
34 template ObjectAllocator(T, ALLOC)
35 {
36 	import std.traits : ReturnType;
37 	import core.memory : GC;
38 	enum ElemSize = AllocSize!T;
39 
40 	static if (ALLOC.stringof == "PoolStack") {
41 		ReturnType!(ALLOC.top) function() m_getAlloc = &ALLOC.top;
42 	}
43 	static if (__traits(hasMember, T, "NOGC")) enum NOGC = T.NOGC;
44 	else enum NOGC = false;
45 
46 	alias TR = RefTypeOf!T;
47 
48 
49 	TR alloc(ARGS...)(auto ref ARGS args)
50 	{
51 		static if (ALLOC.stringof != "PoolStack") {
52 			auto allocator_ = getAllocator!(ALLOC.ident)(false);
53 			auto mem = allocator_.alloc(ElemSize);
54 		}
55 		else {
56 			import memutils.scoped : PoolStack;
57 			if (PoolStack.empty()) {
58 				return new T(args);
59 			}
60 			auto mem = m_getAlloc().alloc(ElemSize);
61 		}
62 		static if ( ALLOC.stringof != "AppMem" && hasIndirections!T && !NOGC) 
63 		{
64 			static if (__traits(compiles, { GC.addRange(null, 0, typeid(string)); }()))
65 				GC.addRange(mem.ptr, ElemSize, typeid(T));
66 			else
67 				GC.addRange(mem.ptr, ElemSize);	
68 		}
69 		static if (!__traits(compiles, (){ emplace!T(mem,args); }))
70 		return cast(TR)T.init;
71 		else return cast(TR)emplace!T(mem, args);
72 
73 	}
74 
75 	void free(TR obj)
76 	{
77 
78 		static if( ALLOC.stringof != "AppMem" && hasIndirections!T && !NOGC) {
79 			GC.removeRange(cast(void*)obj);
80 		}
81 
82 		TR objc = obj;
83 		static if (is(TR == T*)) .destroy(*objc);
84 		else .destroy(objc);
85 
86 		static if (ALLOC.stringof != "PoolStack") {
87 			if (auto a = getAllocator!(ALLOC.ident)(true))
88 				a.free((cast(void*)obj)[0 .. ElemSize]);
89 		}
90 		else {
91 			import memutils.scoped : PoolStack;
92 			if (PoolStack.empty()) {
93 				return;
94 			}
95 			m_getAlloc().free((cast(void*)obj)[0 .. ElemSize]);
96 		}
97 
98 	}
99 }
100 
101 /// Allocates an array without touching the memory.
102 T[] allocArray(T, ALLOC = ThreadMem)(size_t n)
103 {
104 	import core.memory : GC;
105 	static if (ALLOC.stringof == "PoolStack") {
106 		import memutils.scoped : PoolStack;
107 		if (PoolStack.empty())
108 			return new T[n];
109 	}
110 	mixin(translateAllocator());
111 	
112 	auto allocator = thisAllocator();
113 
114 	auto mem = allocator.alloc(T.sizeof * n);
115 	// logTrace("alloc ", T.stringof, ": ", mem.ptr);
116 	auto ret = (cast(T*)mem.ptr)[0 .. n];
117 	// logTrace("alloc ", ALLOC.stringof, ": ", mem.ptr, ":", mem.length);
118 	static if (__traits(hasMember, T, "NOGC")) enum NOGC = T.NOGC;
119 	else enum NOGC = false;
120 	
121 	static if( ALLOC.stringof != "AppMem" && hasIndirections!T && !NOGC) {
122 		static if (__traits(compiles, { GC.addRange(null, 0, typeid(string)); }()))
123 			GC.addRange(mem.ptr, mem.length, typeid(T));
124 		else
125 			GC.addRange(mem.ptr, mem.length);
126 	}
127 
128 	// don't touch the memory - all practical uses of this function will handle initialization.
129 	return ret;
130 }
131 
132 T[] reallocArray(T, ALLOC = ThreadMem)(T[] array, size_t n) {
133 	import core.memory : GC;
134 	assert(n > array.length, "Cannot reallocate to smaller sizes");
135 	static if (ALLOC.stringof == "PoolStack") {
136 		import memutils.scoped : PoolStack;
137 		if (PoolStack.empty()) {
138 			UnConst!(T)[] ret;
139 			ret.length = n;
140 			ret[0 .. array.length] = array[];
141 			array = null;
142 			return cast(T[])ret;
143 		}
144 	}
145 	mixin(translateAllocator());
146 	auto allocator = thisAllocator();
147 	// logTrace("realloc before ", ALLOC.stringof, ": ", cast(void*)array.ptr, ":", array.length);
148 
149 	//logTrace("realloc fre ", T.stringof, ": ", array.ptr);
150 	auto mem = allocator.realloc((cast(void*)array.ptr)[0 .. array.length * T.sizeof], T.sizeof * n);
151 	//logTrace("realloc ret ", T.stringof, ": ", mem.ptr);
152 	auto ret = (cast(T*)mem.ptr)[0 .. n];
153 	// logTrace("realloc after ", ALLOC.stringof, ": ", mem.ptr, ":", mem.length);
154 	
155 	static if (__traits(hasMember, T, "NOGC")) enum NOGC = T.NOGC;
156 	else enum NOGC = false;
157 	
158 	static if (ALLOC.stringof != "AppMem" && hasIndirections!T && !NOGC) {
159 		GC.removeRange(cast(void*)array.ptr);
160 		static if (__traits(compiles, { GC.addRange(null, 0, typeid(string)); }()))
161                 GC.addRange(mem.ptr, mem.length, typeid(T));
162         else
163                 GC.addRange(mem.ptr, mem.length);
164 		// Zero out unused capacity to prevent gc from seeing false pointers
165 		memset(cast(void*)(mem.ptr + (array.length * T.sizeof)), 0, (n - array.length) * T.sizeof);
166 	}
167 	
168 	return ret;
169 }
170 
171 void freeArray(T, ALLOC = ThreadMem)(auto ref T[] array, size_t max_destroy = size_t.max, size_t offset = 0)
172 {
173 	import core.memory : GC;
174 
175 	mixin(translateAllocator());
176 	static if (hasElaborateDestructor!T) { // calls destructors, but not for indirections...
177 		size_t i;
178 		foreach (ref e; array) {
179 			if (i < offset) { i++; continue; }
180 			if (i + offset == max_destroy) break;
181 			static if (is(T == struct) && !isPointer!T) .destroy(e);
182 			i++;
183 		}
184 	}	
185 	static if (ALLOC.stringof != "PoolStack")
186 		auto allocator = thisAllocator(true); // freeing. Avoid allocating in a dtor
187 	else {
188 		import memutils.scoped : PoolStack;
189 		if (PoolStack.empty()) {
190 			array = null;
191 			return;
192 		}
193 		auto allocator = thisAllocator();
194 	}
195 	if (!allocator) return;
196 
197 	// logTrace("free ", ALLOC.stringof, ": ", cast(void*)array.ptr, ":", array.length);
198 	static if (__traits(hasMember, T, "NOGC")) enum NOGC = T.NOGC;
199 	else enum NOGC = false;
200 	
201 	static if (ALLOC.stringof != "AppMem" && hasIndirections!T && !NOGC) {
202 		GC.removeRange(cast(void*)array.ptr);
203 	}
204 
205 	allocator.free((cast(void*)array.ptr)[0 .. array.length * T.sizeof]);
206 	array = null;
207 }
208 
209 mixin template ConvenienceAllocators(alias ALLOC, alias THIS) {
210 	package enum ident = ALLOC;
211 static:
212 	// objects
213 	auto alloc(T, ARGS...)(auto ref ARGS args) 
214 		if (!isArray!T)
215 	{
216 		return ObjectAllocator!(T, THIS).alloc(args);
217 	}
218 	
219 	void free(T)(auto ref T* obj)
220 		if (!isArray!T && !is(T : Object))
221 	{
222 		scope(exit) obj = null;
223 		ObjectAllocator!(T, THIS).free(obj);
224 	}
225 	
226 	void free(T)(auto ref T obj)
227 		if (!isArray!T && is(T  : Object))
228 	{
229 		scope(exit) obj = null;
230 		ObjectAllocator!(T, THIS).free(obj);
231 	}
232 
233 	/// arrays
234 	auto alloc(T)(size_t n)
235 		if (isArray!T)
236 	{
237 		alias ElType = UnConst!(typeof(T.init[0]));
238 		return allocArray!(ElType, THIS)(n);
239 	}
240 
241 	auto copy(T)(auto ref T arr)
242 		if (isArray!T)
243 	{
244 		alias ElType = UnConst!(typeof(arr[0]));
245 		auto arr_copy = allocArray!(ElType, THIS)(arr.length);
246 		memcpy(cast(void*)arr_copy.ptr, cast(void*)arr.ptr, arr.length * ElType.sizeof);
247 
248 		return cast(T)arr_copy;
249 	}
250 
251 	auto realloc(T)(auto ref T arr, size_t n)
252 		if (isArray!T)
253 	{
254 		alias ElType = UnConst!(typeof(arr[0]));
255 		scope(exit) arr = null;
256 		auto arr_copy = reallocArray!(typeof(arr[0]), THIS)(arr, n);
257 		return cast(T) arr_copy;
258 	}
259 	
260 	void free(T)(auto ref T arr)
261 		if (isArray!T)
262 	{
263 		alias ElType = typeof(arr[0]);
264 		scope(exit) arr = null;
265 		freeArray!(ElType, THIS)(arr);
266 	}
267 
268 }
269 
270 string translateAllocator() { /// requires (ALLOC) template parameter
271 	return `
272 	static if (ALLOC.stringof != "PoolStack") {
273 		ReturnType!(getAllocator!(ALLOC.ident)) thisAllocator(bool is_freeing = false) {
274 			return getAllocator!(ALLOC.ident)(is_freeing);
275 		}
276 	}
277 	else {
278 		ReturnType!(ALLOC.top) function() thisAllocator = &ALLOC.top;
279 	}
280 	`;
281 }