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