1 /** 2 This module provide support for aligned memory. 3 */ 4 module gfm.core.memory; 5 6 import core.memory : GC; 7 import core.exception : onOutOfMemoryError; 8 import core.stdc.string : memcpy; 9 import core.stdc.stdlib : malloc, free, realloc; 10 11 import std.conv : emplace; 12 import std.traits; 13 import std.algorithm: swap; 14 15 16 /// Returns: next pointer aligned with alignment bytes. 17 @nogc void* nextAlignedPointer(void* start, size_t alignment) pure nothrow 18 { 19 return cast(void*)nextMultipleOf(cast(size_t)(start), alignment); 20 } 21 22 /// Allocates an aligned memory chunk. 23 /// Functionally equivalent to Visual C++ _aligned_malloc. 24 @nogc void* alignedMalloc(size_t size, size_t alignment) nothrow 25 { 26 if (size == 0) 27 return null; 28 29 size_t request = requestedSize(size, alignment); 30 void* raw = malloc(request); 31 32 static if( __VERSION__ > 2067 ) // onOutOfMemoryError wasn't nothrow before July 2014 33 { 34 if (request > 0 && raw == null) // malloc(0) can validly return anything 35 onOutOfMemoryError(); 36 } 37 38 return storeRawPointerPlusSizeAndReturnAligned(raw, size, alignment); 39 } 40 41 /// Frees aligned memory allocated by alignedMalloc or alignedRealloc. 42 /// Functionally equivalent to Visual C++ _aligned_free. 43 @nogc void alignedFree(void* aligned) nothrow 44 { 45 // support for free(NULL) 46 if (aligned is null) 47 return; 48 49 void** rawLocation = cast(void**)(cast(char*)aligned - size_t.sizeof); 50 free(*rawLocation); 51 } 52 53 /// Reallocates an aligned memory chunk allocated by alignedMalloc or alignedRealloc. 54 /// Functionally equivalent to Visual C++ _aligned_realloc. 55 @nogc void* alignedRealloc(void* aligned, size_t size, size_t alignment) nothrow 56 { 57 if (aligned is null) 58 return alignedMalloc(size, alignment); 59 60 if (size == 0) 61 { 62 alignedFree(aligned); 63 return null; 64 } 65 66 size_t previousSize = *cast(size_t*)(cast(char*)aligned - size_t.sizeof * 2); 67 68 69 void* raw = *cast(void**)(cast(char*)aligned - size_t.sizeof); 70 size_t request = requestedSize(size, alignment); 71 72 // Heuristic: if new requested size is within 50% to 100% of what is already allocated 73 // then exit with the same pointer 74 if ( (previousSize < request * 4) && (request <= previousSize) ) 75 return aligned; 76 77 void* newRaw = malloc(request); 78 static if( __VERSION__ > 2067 ) // onOutOfMemoryError wasn't nothrow before July 2014 79 { 80 if (request > 0 && newRaw == null) // realloc(0) can validly return anything 81 onOutOfMemoryError(); 82 } 83 84 void* newAligned = storeRawPointerPlusSizeAndReturnAligned(newRaw, request, alignment); 85 size_t minSize = size < previousSize ? size : previousSize; 86 memcpy(newAligned, aligned, minSize); 87 88 // Free previous data 89 alignedFree(aligned); 90 return newAligned; 91 } 92 93 private 94 { 95 // Returns number of bytes to actually allocate when asking 96 // for a particular alignement 97 @nogc size_t requestedSize(size_t askedSize, size_t alignment) pure nothrow 98 { 99 enum size_t pointerSize = size_t.sizeof; 100 return askedSize + alignment - 1 + pointerSize * 2; 101 } 102 103 // Store pointer given my malloc, and size in bytes initially requested (alignedRealloc needs it) 104 @nogc void* storeRawPointerPlusSizeAndReturnAligned(void* raw, size_t size, size_t alignment) nothrow 105 { 106 enum size_t pointerSize = size_t.sizeof; 107 char* start = cast(char*)raw + pointerSize * 2; 108 void* aligned = nextAlignedPointer(start, alignment); 109 void** rawLocation = cast(void**)(cast(char*)aligned - pointerSize); 110 *rawLocation = raw; 111 size_t* sizeLocation = cast(size_t*)(cast(char*)aligned - 2 * pointerSize); 112 *sizeLocation = size; 113 return aligned; 114 } 115 116 // Returns: x, multiple of powerOfTwo, so that x >= n. 117 @nogc size_t nextMultipleOf(size_t n, size_t powerOfTwo) pure nothrow 118 { 119 // check power-of-two 120 assert( (powerOfTwo != 0) && ((powerOfTwo & (powerOfTwo - 1)) == 0)); 121 122 size_t mask = ~(powerOfTwo - 1); 123 return (n + powerOfTwo - 1) & mask; 124 } 125 } 126 127 unittest 128 { 129 assert(nextMultipleOf(0, 4) == 0); 130 assert(nextMultipleOf(1, 4) == 4); 131 assert(nextMultipleOf(2, 4) == 4); 132 assert(nextMultipleOf(3, 4) == 4); 133 assert(nextMultipleOf(4, 4) == 4); 134 assert(nextMultipleOf(5, 4) == 8); 135 136 { 137 void* p = alignedMalloc(23, 16); 138 assert(p !is null); 139 assert(((cast(size_t)p) & 0xf) == 0); 140 141 alignedFree(p); 142 } 143 144 assert(alignedMalloc(0, 16) == null); 145 alignedFree(null); 146 147 { 148 int alignment = 16; 149 int* p = null; 150 151 // check if growing keep values in place 152 foreach(int i; 0..100) 153 { 154 p = cast(int*) alignedRealloc(p, (i + 1) * int.sizeof, alignment); 155 p[i] = i; 156 } 157 158 foreach(int i; 0..100) 159 assert(p[i] == i); 160 161 162 p = cast(int*) alignedRealloc(p, 0, alignment); 163 assert(p is null); 164 } 165 } 166 167 168 /// Destructors called by the GC enjoy a variety of limitations and 169 /// relying on them is dangerous. 170 /// See_also: $(WEB p0nce.github.io/d-idioms/#The-trouble-with-class-destructors) 171 /// Example: 172 /// --- 173 /// class Resource 174 /// { 175 /// ~this() 176 /// { 177 /// if (!alreadyClosed) 178 /// { 179 /// if (isCalledByGC()) 180 /// assert(false, "Resource release relies on Garbage Collection"); 181 /// alreadyClosed = true; 182 /// releaseResource(); 183 /// } 184 /// } 185 /// } 186 /// --- 187 bool isCalledByGC() nothrow 188 { 189 import core.exception; 190 try 191 { 192 import core.memory; 193 cast(void) GC.malloc(1); // not ideal since it allocates 194 return false; 195 } 196 catch(InvalidMemoryOperationError e) 197 { 198 return true; 199 } 200 } 201 202 unittest 203 { 204 import std.stdio; 205 class A 206 { 207 ~this() 208 { 209 assert(!isCalledByGC()); 210 } 211 } 212 import std.typecons; 213 auto a = scoped!A(); 214 } 215 216 /// Crash if the GC is running. 217 /// Useful in destructors to avoid reliance GC resource release. 218 /// See_also: $(WEB p0nce.github.io/d-idioms/#GC-proof-resource-class) 219 void ensureNotInGC(string resourceName = null) nothrow 220 { 221 import core.exception; 222 try 223 { 224 import core.memory; 225 cast(void) GC.malloc(1); // not ideal since it allocates 226 return; 227 } 228 catch(InvalidMemoryOperationError e) 229 { 230 import core.stdc.stdio; 231 fprintf(stderr, "Error: clean-up of %s incorrectly depends on destructors called by the GC.\n", 232 resourceName ? resourceName.ptr : "a resource".ptr); 233 assert(false); // crash 234 } 235 } 236 237 238 /// Allocates and construct a struct or class object. 239 /// Returns: Newly allocated object. 240 auto mallocEmplace(T, Args...)(Args args) 241 { 242 static if (is(T == class)) 243 immutable size_t allocSize = __traits(classInstanceSize, T); 244 else 245 immutable size_t allocSize = T.sizeof; 246 247 void* rawMemory = malloc(allocSize); 248 if (!rawMemory) 249 onOutOfMemoryError(); 250 251 static if (is(T == class)) 252 { 253 T obj = emplace!T(rawMemory[0 .. allocSize], args); 254 } 255 else 256 { 257 T* obj = cast(T*)rawMemory; 258 emplace!T(obj, args); 259 } 260 261 static if (hasIndirections!T) 262 GC.addRange(rawMemory, allocSize); 263 264 return obj; 265 } 266 267 /// Destroys and frees a class object created with $(D mallocEmplace). 268 void destroyFree(T)(T p) if (is(T == class)) 269 { 270 if (p !is null) 271 { 272 destroy(p); 273 274 static if (hasIndirections!T) 275 GC.removeRange(cast(void*)p); 276 277 free(cast(void*)p); 278 } 279 } 280 281 /// Destroys and frees a non-class object created with $(D mallocEmplace). 282 void destroyFree(T)(T* p) if (!is(T == class)) 283 { 284 if (p !is null) 285 { 286 destroy(p); 287 288 static if (hasIndirections!T) 289 GC.removeRange(cast(void*)p); 290 291 free(cast(void*)p); 292 } 293 } 294 295 unittest 296 { 297 class A 298 { 299 int _i; 300 this(int i) 301 { 302 _i = i; 303 } 304 } 305 306 struct B 307 { 308 int i; 309 } 310 311 void testMallocEmplace() 312 { 313 A a = mallocEmplace!A(4); 314 destroyFree(a); 315 316 B* b = mallocEmplace!B(5); 317 destroyFree(b); 318 } 319 320 testMallocEmplace(); 321 } 322 323 version( D_InlineAsm_X86 ) 324 { 325 version = AsmX86; 326 } 327 else version( D_InlineAsm_X86_64 ) 328 { 329 version = AsmX86; 330 } 331 332 /// Inserts a breakpoint instruction. useful to trigger the debugger. 333 void debugBreak() nothrow @nogc 334 { 335 version( AsmX86 ) 336 { 337 asm nothrow @nogc 338 { 339 int 3; 340 } 341 } 342 else version( GNU ) 343 { 344 // __builtin_trap() is not the same thing unfortunately 345 asm 346 { 347 "int $0x03" : : : ; 348 } 349 } 350 else 351 { 352 static assert(false, "No debugBreak() for this compiler"); 353 } 354 } 355 356 auto assumeNoGC(T) (T t) if (isFunctionPointer!T || isDelegate!T) 357 { 358 enum attrs = functionAttributes!T | FunctionAttribute.nogc; 359 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 360 } 361 362 auto assumeNothrow(T) (T t) if (isFunctionPointer!T || isDelegate!T) 363 { 364 enum attrs = functionAttributes!T | FunctionAttribute.nothrow_; 365 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 366 } 367 368 auto assumeNothrowNoGC(T) (T t) if (isFunctionPointer!T || isDelegate!T) 369 { 370 enum attrs = functionAttributes!T | FunctionAttribute.nogc | FunctionAttribute.nothrow_; 371 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 372 } 373 374 unittest 375 { 376 void funcThatDoesGC() 377 { 378 throw new Exception("hello!"); 379 } 380 381 void anotherFunction() nothrow @nogc 382 { 383 assumeNothrowNoGC( (){ funcThatDoesGC(); } )(); 384 } 385 386 void aThirdFunction() @nogc 387 { 388 assumeNoGC( () { funcThatDoesGC(); } )(); 389 } 390 } 391 392 /// Must return -1 if a < b 393 /// 0 if a == b 394 /// 1 if a > b 395 alias nogcComparisonFunction(T) = int delegate(in T a, in T b) nothrow @nogc; 396 397 /// @nogc quicksort 398 /// From the excellent: http://codereview.stackexchange.com/a/77788 399 void nogc_qsort(T)(T[] array, nogcComparisonFunction!T comparison) nothrow @nogc 400 { 401 if (array.length < 2) 402 return; 403 404 int partition(T* arr, int left, int right) nothrow @nogc 405 { 406 immutable int mid = left + (right - left) / 2; 407 immutable T pivot = arr[mid]; 408 // move the mid point value to the front. 409 swap(arr[mid],arr[left]); 410 int i = left + 1; 411 int j = right; 412 while (i <= j) 413 { 414 while(i <= j && comparison(arr[i], pivot) <= 0 ) 415 i++; 416 417 while(i <= j && comparison(arr[j], pivot) > 0) 418 j--; 419 420 if (i < j) 421 swap(arr[i], arr[j]); 422 } 423 swap(arr[i - 1], arr[left]); 424 return i - 1; 425 } 426 427 void doQsort(T* array, int left, int right) nothrow @nogc 428 { 429 if (left >= right) 430 return; 431 432 int part = partition(array, left, right); 433 doQsort(array, left, part - 1); 434 doQsort(array, part + 1, right); 435 } 436 437 doQsort(array.ptr, 0, cast(int)(array.length) - 1); 438 } 439 440 unittest 441 { 442 int[] testData = [110, 5, 10, 3, 22, 100, 1, 23]; 443 nogc_qsort!int(testData, (a, b) => (a - b)); 444 assert(testData == [1, 3, 5, 10, 22, 23, 100, 110]); 445 }