1 module mergearray.impl.allocator; 2 3 /++ 4 Namespace for a static, thread-local region allocator wrapping the GC. 5 +/ 6 struct GCAllocator { 7 private: 8 static bool in_region = false; 9 10 public: 11 /++ 12 Returns: Whether or not the allocator's region is active for this thread. 13 +/ 14 @property 15 static bool inRegion() { return in_region; } 16 /++ 17 Have the calling thread enter the region. 18 19 This allocator does not require scoping allocations within the region, but 20 the functionality is provided to be consistent with other allocators. 21 +/ 22 static void enter(size_t) { in_region = true; } 23 /++ 24 Have the calling thread exit the region. 25 26 WARNING: This does not deallocate allocated memory, nor are allocations 27 guaranteed to survive until this is called. 28 +/ 29 static void exit() { in_region = false; } 30 /++ 31 Allocates the size bytes from the GC and returns a slice to it. 32 Params: 33 size = Number of bytes to allocate. 34 Returns: new void[](size) 35 +/ 36 static void[] alloc(size_t size) in { assert(inRegion()); } body { 37 return new void[](size); 38 } 39 /++ 40 Allocates a T onto the GC and returns a pointer or class reference to it. 41 Params: 42 args = Arguments to the constructor of T. 43 Returns: new T(args) 44 +/ 45 static auto alloc(T, Args...)(Args args) in { assert(inRegion()); } body { 46 return new T(args); 47 } 48 } 49 50 /++ 51 Namespace for a static, thread-local region allocator wrapping an 52 std.experimental.allocator.IAllocator. 53 +/ 54 struct StdAllocator { 55 private: 56 import std.experimental.allocator : IAllocator, processAllocator, make; 57 58 static bool in_region = false; 59 60 public: 61 /++ 62 Thread-local reference to the allocator backing the region, defaulting to 63 std.experimental.allocator.processAllocator. 64 +/ 65 static IAllocator my_allocator = null; 66 67 static this() { 68 my_allocator = processAllocator; 69 } 70 71 /++ 72 Returns: Whether the calling thread is in the region or not. 73 +/ 74 @property 75 static bool inRegion() { return in_region; } 76 /++ 77 Have the calling thread enter the region without changing my_allocator. 78 +/ 79 static void enter(size_t) { 80 in_region = true; 81 } 82 /++ 83 Have the calling thread enter the region, and assign a new my_allocator. 84 Params: 85 ia = An allocator to assign to my_allocator. 86 +/ 87 static void enter(IAllocator ia) { 88 in_region = true; 89 my_allocator = ia; 90 } 91 /++ 92 Have the calling thread exit the region. 93 94 WARNING: Does not mutate my_allocator. Deconstructors and deallocation must 95 be handled manually through my_allocator. 96 +/ 97 static void exit() { 98 in_region = false; 99 } 100 /++ 101 Allocates size bytes from my_allocator and returns a slice to it. 102 Params: 103 size = Number of bytes from my_allocator to allocate. 104 Returns: A slice to the size bytes from my_allocator. 105 +/ 106 static auto alloc(size_t size) in { assert(inRegion()); } body { 107 import std.stdio; 108 debug(mergearray_allocator) writeln(size); 109 return my_allocator.allocate(size); 110 } 111 /++ 112 Makes a T using my_allocator, and returns a pointer to it. 113 Params: 114 args = Arguments to the constructor of T. 115 Returns: A pointer or class reference to the T that my_allocator made. 116 +/ 117 static auto alloc(T, Args...)(Args args) in { assert(inRegion()); } body { 118 import std.stdio; 119 debug(mergearray_allocator) writeln(T.stringof, " = ", T.sizeof); 120 return my_allocator.make!T(args); 121 } 122 } 123 124 /++ 125 Namespace for a static, thread-local, non-nested, unbounded-size allocator. 126 127 Memory is allocated bump-the-pointer style within large GC-allocated chunks. 128 A new chunk is allocated when the current one is too full for the next request. 129 Deallocation of chunks is handled by the GC, and destructors are not called. 130 +/ 131 struct ChunkGCAllocator { 132 private: 133 static void[] region; 134 static size_t curPos = 0; 135 static size_t allocSize = 0;//2 ^^ 27; // 128MB 136 static size_t prevAllocs = 0; 137 static bool in_region = false; 138 139 debug(mergearray_allocator) import std.stdio; 140 141 static void replace() { 142 debug(mergearray_allocator) { 143 writefln("%s replace: allocating %s bytes.", typeof(this).stringof, allocSize); 144 } 145 prevAllocs += region.length; 146 147 region = new void[](allocSize); 148 149 curPos = 0; 150 } 151 152 public: 153 /++ 154 Peek at the current head of the region. 155 This value can be used to check how many bytes have been allocated so far 156 in the current chunk. 157 +/ 158 static size_t peek() { return curPos; } 159 /++ 160 Returns: Whether the calling thread is in the region or not. 161 +/ 162 static bool inRegion() { return in_region; } 163 /++ 164 Have the calling thread enter the region with a chunks of size chunkSize. 165 Params: 166 chunkSize = The size of allocated chunks throughout the region. 167 +/ 168 static void enter(size_t chunkSize) { 169 allocSize = chunkSize; 170 in_region = true; 171 replace(); 172 debug(mergearray_allocator) { 173 writefln("%s enter: %s/%s bytes free.", typeof(this).stringof, region.length - curPos, region.length + prevAllocs); 174 } 175 } 176 /++ 177 Have the calling thread exit the region, releasing any references to an 178 allocated chunk. 179 180 WARNING: This does not deallocate allocated memory, nor are allocations 181 guaranteed to survive until this is called, nor are deconstructors called. 182 +/ 183 static void exit() { 184 debug(mergearray_allocator) { 185 writefln("%s exit: %s/%s bytes free.", typeof(this).stringof, region.length - curPos, region.length + prevAllocs); 186 } 187 in_region = false; 188 region = null; 189 prevAllocs = 0; 190 } 191 /++ 192 Allocates the next size bytes from the region and returns a slice to it. 193 194 If the current chunk is too full, then a new one is allocated and used. 195 Params: 196 size = Number of bytes from the region to allocate. 197 Returns: A slice to the next size unused bytes from the region. 198 +/ 199 static void[] alloc(size_t size) in { assert(inRegion()); } body { 200 if (curPos + size > region.length) { 201 replace(); 202 } 203 scope(success) curPos += size; 204 205 debug(mergearray_allocator) writefln("%s alloc: %s bytes allocated.", typeof(this).stringof, size); 206 207 return region[curPos .. curPos + size]; 208 } 209 /++ 210 Allocates at least T.sizeof bytes, emplaces a T there, and returns a pointer to it. 211 212 The allocation IS automatically padded to ensure proper alignment. 213 Params: 214 args = Arguments to the constructor of T. 215 Returns: A pointer or class reference to the allocated and emplaced T. 216 +/ 217 static auto alloc(T, Args...)(Args args) in { assert(inRegion()); } body { 218 size_t alignOffset = (T.alignof - curPos % T.alignof); 219 220 if (curPos + T.sizeof + alignOffset > region.length) { 221 replace(); 222 alignOffset = 0; 223 } 224 225 immutable size = T.sizeof + alignOffset; 226 227 scope(success) curPos += size; 228 229 assert(curPos + size <= region.length); 230 void[] slice = region[curPos + alignOffset .. curPos + size]; 231 232 assert(slice.length == T.sizeof); 233 return emplace!T(slice, args); 234 } 235 } 236 237 /++ 238 Namespace for a static, thread-local, non-nested, bounded-size region allocator. 239 240 Memory is allocated bump-the-pointer style within one buffer which must not 241 overflow. Exiting will free the buffer without calling destructors. 242 +/ 243 struct TLRegionAllocator { 244 private: 245 import core.memory; 246 247 static void[] buffer = null; 248 static size_t curPos = 0; 249 250 debug(mergearray_allocator) import std.stdio; 251 252 public: 253 /++ 254 Returns: Whether the calling thread is in the region or not. 255 +/ 256 @property 257 static bool inRegion() { 258 return buffer !is null; 259 } 260 /++ 261 Peek at the current head of the region. 262 263 This value can be used to check how many bytes have been allocated so far, 264 and to determine the alignment of the next allocation. 265 +/ 266 static size_t peek() { return curPos; } 267 268 /++ 269 Have the calling thread enter the region with a given size maxBytes. 270 Params: 271 maxBytes = The number of bytes to initialize the region to. 272 +/ 273 static void enter(size_t maxBytes) { 274 debug(mergearray_allocator) writefln("%s enter: %s bytes allocating...", typeof(this).stringof, maxBytes); 275 276 buffer = GC.malloc(maxBytes)[0 .. maxBytes]; 277 278 assert(buffer.length == maxBytes); 279 280 curPos = 0; 281 debug(mergearray_allocator) writefln("%s enter: %s bytes allocated.", typeof(this).stringof, buffer.length); 282 } 283 284 /++ 285 Have the calling thread exit the region, deallocating the memory. 286 WARNING: Destructors of allocated structs or objects are not called. 287 +/ 288 static void exit() { 289 debug(mergearray_allocator) writefln("%s exit: %s/%s (%s%%) bytes used.", typeof(this).stringof, curPos, buffer.length, curPos * 100.0 / buffer.length); 290 291 auto ptr = buffer.ptr; 292 buffer = null; 293 GC.free(ptr); 294 } 295 296 /++ 297 Allocates the next size bytes from the region and returns a slice to it. 298 Params: 299 size = Number of bytes from the region to allocate. 300 Returns: A slice to the next size unused bytes from the region. 301 +/ 302 static void[] alloc(size_t size) 303 in { 304 assert(inRegion()); 305 306 version(assert) { 307 import std.string : format; 308 309 assert(curPos + size <= buffer.length, 310 "%s alloc: attempt to alloc %s but only %s/%s free" 311 .format(typeof(this).stringof, size, buffer.length - curPos, buffer.length) 312 ); 313 } 314 } 315 body { 316 scope(exit) curPos += size; 317 318 debug(mergearray_allocator) writefln("%s alloc: %s bytes allocated.", typeof(this).stringof, size); 319 320 return buffer[curPos .. curPos + size]; 321 } 322 /++ 323 Allocates T.sizeof bytes, emplaces a T there, and returns a pointer to it. 324 Warning: The allocation is NOT automatically padded to ensure proper alignment. 325 Params: 326 args = Arguments to the constructor of T. 327 Returns: A pointer or class reference to the allocated and emplaced T. 328 +/ 329 static auto alloc(T, Args...)(Args args) 330 in { 331 assert(inRegion()); 332 assert(curPos % T.alignof == 0); 333 } 334 body { 335 auto space = alloc(T.sizeof); 336 337 import std.conv : emplace; 338 339 return emplace!T(space, args); 340 } 341 } 342 343 // unused/unpolished 344 static if (false) 345 struct TLStaticAllocator { 346 private: 347 static void[length] buffer = void; 348 static size_t curPos = size_t.max; 349 350 debug(mergearray_allocator) import std.stdio; 351 352 public: 353 enum size_t length = 2 ^^ 26; 354 355 static bool inRegion() { 356 return curPos <= length; 357 } 358 359 static size_t peek() { return curPos; } 360 361 static void enter(size_t maxBytes = 0) in { assert(maxBytes <= length); } body { 362 curPos = 0; 363 364 debug(mergearray_allocator) writefln("%s enter: %s bytes allocated.", typeof(this).stringof, length); 365 } 366 367 static void exit() { 368 debug(mergearray_allocator) writefln("%s exit: %s/%s (%s%%) bytes used.", typeof(this).stringof, curPos, buffer.length, curPos * 100.0 / buffer.length); 369 curPos = size_t.max; 370 } 371 372 static auto alloc(size_t size) 373 in { 374 assert(inRegion()); 375 assert(curPos + size <= buffer.length); 376 } 377 body { 378 scope(exit) curPos += size; 379 380 debug(mergearray_allocator) writefln("%s alloc: %s bytes allocated.", typeof(this).stringof, size); 381 382 return buffer[curPos .. curPos + size]; 383 } 384 385 static auto alloc(T, Args...)(Args args) 386 in { 387 assert(inRegion()); 388 assert(curPos + T.sizeof <= buffer.length); 389 assert(curPos % T.alignof == 0); 390 } 391 body { 392 scope(exit) curPos += T.sizeof; 393 394 import std.conv; 395 396 return emplace!T(buffer[curPos .. curPos + T.sizeof], args); 397 } 398 } 399