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 }