1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * This module contains the interface for implementing custom allocators. 7 * 8 * Allocators are classes encapsulating memory allocation strategy. This allows 9 * to decouple memory management from the algorithms and the data. 10 * 11 * Copyright: Eugene Wissner 2016-2020. 12 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 13 * Mozilla Public License, v. 2.0). 14 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 15 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/allocator.d, 16 * tanya/memory/allocator.d) 17 */ 18 module tanya.memory.allocator; 19 20 import tanya.memory.lifetime; 21 import tanya.meta.trait; 22 23 /** 24 * Abstract class implementing a basic allocator. 25 */ 26 interface Allocator 27 { 28 /** 29 * Returns: Alignment offered. 30 */ 31 @property uint alignment() const shared pure nothrow @safe @nogc; 32 33 /** 34 * Allocates $(D_PARAM size) bytes of memory. 35 * 36 * Params: 37 * size = Amount of memory to allocate. 38 * 39 * Returns: Pointer to the new allocated memory. 40 */ 41 void[] allocate(size_t size) shared pure nothrow @nogc; 42 43 /** 44 * Deallocates a memory block. 45 * 46 * Params: 47 * p = A pointer to the memory block to be freed. 48 * 49 * Returns: Whether the deallocation was successful. 50 */ 51 bool deallocate(void[] p) shared pure nothrow @nogc; 52 53 /** 54 * Increases or decreases the size of a memory block. 55 * 56 * Params: 57 * p = A pointer to the memory block. 58 * size = Size of the reallocated block. 59 * 60 * Returns: Pointer to the allocated memory. 61 */ 62 bool reallocate(ref void[] p, size_t size) shared pure nothrow @nogc; 63 64 /** 65 * Reallocates a memory block in place if possible or returns 66 * $(D_KEYWORD false). This function cannot be used to allocate or 67 * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or 68 * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false). 69 * 70 * Params: 71 * p = A pointer to the memory block. 72 * size = Size of the reallocated block. 73 * 74 * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. 75 */ 76 bool reallocateInPlace(ref void[] p, size_t size) 77 shared pure nothrow @nogc; 78 } 79 80 package template GetPureInstance(T : Allocator) 81 { 82 alias GetPureInstance = shared(T) function() 83 pure nothrow @nogc; 84 } 85 86 /** 87 * The mixin generates common methods for classes and structs using 88 * allocators. It provides a protected member, constructor and a read-only property, 89 * that checks if an allocator was already set and sets it to the default 90 * one, if not (useful for structs which don't have a default constructor). 91 */ 92 mixin template DefaultAllocator() 93 { 94 /// Allocator. 95 protected shared Allocator allocator_; 96 97 /** 98 * Params: 99 * allocator = The allocator should be used. 100 * 101 * Precondition: $(D_INLINECODE allocator_ !is null) 102 */ 103 this(shared Allocator allocator) @nogc nothrow pure @safe 104 in 105 { 106 assert(allocator !is null); 107 } 108 do 109 { 110 this.allocator_ = allocator; 111 } 112 113 /** 114 * This property checks if the allocator was set in the constructor 115 * and sets it to the default one, if not. 116 * 117 * Returns: Used allocator. 118 * 119 * Postcondition: $(D_INLINECODE allocator !is null) 120 */ 121 @property shared(Allocator) allocator() @nogc nothrow pure @safe 122 out (allocator) 123 { 124 assert(allocator !is null); 125 } 126 do 127 { 128 if (allocator_ is null) 129 { 130 allocator_ = defaultAllocator; 131 } 132 return allocator_; 133 } 134 135 /// ditto 136 @property shared(Allocator) allocator() const @nogc nothrow pure @trusted 137 out (allocator) 138 { 139 assert(allocator !is null); 140 } 141 do 142 { 143 if (allocator_ is null) 144 { 145 return defaultAllocator; 146 } 147 return cast(shared Allocator) allocator_; 148 } 149 } 150 151 shared Allocator allocator; 152 153 private shared(Allocator) getAllocatorInstance() @nogc nothrow 154 { 155 if (allocator is null) 156 { 157 version (TanyaNative) 158 { 159 import tanya.memory.mmappool : MmapPool; 160 defaultAllocator = MmapPool.instance; 161 } 162 else 163 { 164 import tanya.memory.mallocator : Mallocator; 165 defaultAllocator = Mallocator.instance; 166 } 167 } 168 return allocator; 169 } 170 171 /** 172 * Returns: Default allocator. 173 * 174 * Postcondition: $(D_INLINECODE allocator !is null). 175 */ 176 @property shared(Allocator) defaultAllocator() @nogc nothrow pure @trusted 177 out (allocator) 178 { 179 assert(allocator !is null); 180 } 181 do 182 { 183 return (cast(GetPureInstance!Allocator) &getAllocatorInstance)(); 184 } 185 186 /** 187 * Sets the default allocator. 188 * 189 * Params: 190 * allocator = $(D_PSYMBOL Allocator) instance. 191 * 192 * Precondition: $(D_INLINECODE allocator !is null). 193 */ 194 @property void defaultAllocator(shared(Allocator) allocator) @nogc nothrow @safe 195 in 196 { 197 assert(allocator !is null); 198 } 199 do 200 { 201 .allocator = allocator; 202 } 203 204 /** 205 * Params: 206 * size = Raw size. 207 * alignment = Alignment. 208 * 209 * Returns: Aligned size. 210 */ 211 size_t alignedSize(const size_t size, const size_t alignment = 8) 212 pure nothrow @safe @nogc 213 { 214 return (size - 1) / alignment * alignment + alignment; 215 } 216 217 /** 218 * Error thrown if memory allocation fails. 219 */ 220 final class OutOfMemoryError : Error 221 { 222 /** 223 * Constructs new error. 224 * 225 * Params: 226 * msg = The message for the exception. 227 * file = The file where the exception occurred. 228 * line = The line number where the exception occurred. 229 * next = The previous exception in the chain of exceptions, if any. 230 */ 231 this(string msg = "Out of memory", 232 string file = __FILE__, 233 size_t line = __LINE__, 234 Throwable next = null) @nogc nothrow pure @safe 235 { 236 super(msg, file, line, next); 237 } 238 239 /// ditto 240 this(string msg, 241 Throwable next, 242 string file = __FILE__, 243 size_t line = __LINE__) @nogc nothrow pure @safe 244 { 245 super(msg, file, line, next); 246 } 247 } 248 249 /** 250 * Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T). 251 * It is assumed the respective entities had been allocated with the same 252 * allocator. 253 * 254 * Params: 255 * T = Type of $(D_PARAM p). 256 * allocator = Allocator the $(D_PARAM p) was allocated with. 257 * p = Object or array to be destroyed. 258 */ 259 void dispose(T)(shared Allocator allocator, auto ref T p) 260 { 261 () @trusted { allocator.deallocate(finalize(p)); }(); 262 p = null; 263 } 264 265 /** 266 * Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args) 267 * as the parameter list for the constructor of $(D_PARAM T). 268 * 269 * Params: 270 * T = Class type. 271 * A = Types of the arguments to the constructor of $(D_PARAM T). 272 * allocator = Allocator. 273 * args = Constructor arguments of $(D_PARAM T). 274 * 275 * Returns: Newly created $(D_PSYMBOL T). 276 * 277 * Precondition: $(D_INLINECODE allocator !is null) 278 */ 279 T make(T, A...)(shared Allocator allocator, auto ref A args) 280 if (is(T == class)) 281 in 282 { 283 assert(allocator !is null); 284 } 285 do 286 { 287 auto mem = (() @trusted => allocator.allocate(stateSize!T))(); 288 if (mem is null) 289 { 290 onOutOfMemoryError(); 291 } 292 scope (failure) 293 { 294 () @trusted { allocator.deallocate(mem); }(); 295 } 296 297 return emplace!T(mem[0 .. stateSize!T], args); 298 } 299 300 /** 301 * Constructs a value object of type $(D_PARAM T) using $(D_PARAM args) 302 * as the parameter list for the constructor of $(D_PARAM T) and returns a 303 * pointer to the new object. 304 * 305 * Params: 306 * T = Object type. 307 * A = Types of the arguments to the constructor of $(D_PARAM T). 308 * allocator = Allocator. 309 * args = Constructor arguments of $(D_PARAM T). 310 * 311 * Returns: Pointer to the created object. 312 * 313 * Precondition: $(D_INLINECODE allocator !is null) 314 */ 315 T* make(T, A...)(shared Allocator allocator, auto ref A args) 316 if (!isPolymorphicType!T && !isAssociativeArray!T && !isArray!T) 317 in 318 { 319 assert(allocator !is null); 320 } 321 do 322 { 323 auto mem = (() @trusted => allocator.allocate(stateSize!T))(); 324 if (mem is null) 325 { 326 onOutOfMemoryError(); 327 } 328 scope (failure) 329 { 330 () @trusted { allocator.deallocate(mem); }(); 331 } 332 return emplace!T(mem[0 .. stateSize!T], args); 333 } 334 335 /// 336 @nogc nothrow pure @safe unittest 337 { 338 int* i = defaultAllocator.make!int(5); 339 assert(*i == 5); 340 defaultAllocator.dispose(i); 341 } 342 343 /** 344 * Constructs a new array with $(D_PARAM n) elements. 345 * 346 * Params: 347 * T = Array type. 348 * E = Array element type. 349 * allocator = Allocator. 350 * n = Array size. 351 * 352 * Returns: Newly created array. 353 * 354 * Precondition: $(D_INLINECODE allocator !is null 355 * && n <= size_t.max / E.sizeof) 356 */ 357 T make(T : E[], E)(shared Allocator allocator, size_t n) 358 in 359 { 360 assert(allocator !is null); 361 assert(n <= size_t.max / E.sizeof); 362 } 363 do 364 { 365 auto ret = allocator.resize!E(null, n); 366 367 static if (hasElaborateDestructor!E) 368 { 369 for (auto range = ret; range.length != 0; range = range[1 .. $]) 370 { 371 emplace!E(cast(void[]) range[0 .. 1], E.init); 372 } 373 } 374 else 375 { 376 ret[] = E.init; 377 } 378 379 return ret; 380 } 381 382 /// 383 @nogc nothrow pure @safe unittest 384 { 385 int[] i = defaultAllocator.make!(int[])(2); 386 assert(i.length == 2); 387 assert(i[0] == int.init && i[1] == int.init); 388 defaultAllocator.dispose(i); 389 } 390 391 /* 392 * Destroys the object. 393 * Returns the memory should be freed. 394 */ 395 package void[] finalize(T)(ref T* p) 396 { 397 if (p is null) 398 { 399 return null; 400 } 401 static if (hasElaborateDestructor!T) 402 { 403 destroy(*p); 404 } 405 return (cast(void*) p)[0 .. T.sizeof]; 406 } 407 408 package void[] finalize(T)(ref T p) 409 if (isPolymorphicType!T) 410 { 411 if (p is null) 412 { 413 return null; 414 } 415 static if (is(T == interface)) 416 { 417 version(Windows) 418 { 419 import core.sys.windows.unknwn : IUnknown; 420 static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in " 421 ~ __PRETTY_FUNCTION__); 422 } 423 auto ob = cast(Object) p; 424 } 425 else 426 { 427 alias ob = p; 428 } 429 auto ptr = cast(void*) ob; 430 auto support = ptr[0 .. typeid(ob).initializer.length]; 431 432 auto ppv = cast(void**) ptr; 433 if (!*ppv) 434 { 435 return null; 436 } 437 auto pc = cast(ClassInfo*) *ppv; 438 scope (exit) 439 { 440 *ppv = null; 441 } 442 443 auto c = *pc; 444 do 445 { 446 // Assume the destructor is @nogc. Leave it nothrow since the destructor 447 // shouldn't throw and if it does, it is an error anyway. 448 if (c.destructor) 449 { 450 alias DtorType = void function(Object) pure nothrow @safe @nogc; 451 (cast(DtorType) c.destructor)(ob); 452 } 453 } 454 while ((c = c.base) !is null); 455 456 if (ppv[1]) // if monitor is not null 457 { 458 _d_monitordelete(cast(Object) ptr, true); 459 } 460 return support; 461 } 462 463 package void[] finalize(T)(ref T[] p) 464 { 465 destroyAllImpl!(T[], T)(p); 466 return p; 467 } 468 469 /** 470 * Allocates $(D_PSYMBOL OutOfMemoryError) in a static storage and throws it. 471 * 472 * Params: 473 * msg = Custom error message. 474 * 475 * Throws: $(D_PSYMBOL OutOfMemoryError). 476 */ 477 void onOutOfMemoryError(string msg = "Out of memory") 478 @nogc nothrow pure @trusted 479 { 480 static ubyte[stateSize!OutOfMemoryError] memory; 481 alias PureType = OutOfMemoryError function(string) @nogc nothrow pure; 482 throw (cast(PureType) () => emplace!OutOfMemoryError(memory))(msg); 483 } 484 485 // From druntime 486 extern (C) 487 private void _d_monitordelete(Object h, bool det) @nogc nothrow pure; 488 489 /* 490 * Internal function used to create, resize or destroy a dynamic array. It 491 * may throw $(D_PSYMBOL OutOfMemoryError). The new 492 * allocated part of the array isn't initialized. This function can be trusted 493 * only in the data structures that can ensure that the array is 494 * allocated/rellocated/deallocated with the same allocator. 495 * 496 * Params: 497 * T = Element type of the array being created. 498 * allocator = The allocator used for getting memory. 499 * array = A reference to the array being changed. 500 * length = New array length. 501 * 502 * Returns: $(D_PARAM array). 503 */ 504 package(tanya) T[] resize(T)(shared Allocator allocator, 505 auto ref T[] array, 506 const size_t length) @trusted 507 { 508 if (length == 0) 509 { 510 if (allocator.deallocate(array)) 511 { 512 return null; 513 } 514 else 515 { 516 onOutOfMemoryError(); 517 } 518 } 519 520 void[] buf = array; 521 if (!allocator.reallocate(buf, length * T.sizeof)) 522 { 523 onOutOfMemoryError(); 524 } 525 // Casting from void[] is unsafe, but we know we cast to the original type. 526 array = cast(T[]) buf; 527 528 return array; 529 }