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 }