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