1 /** Memory management. The code in this module is borrowed from David Simcha's 2 $(LINK2 http://www.dsource.org/projects/dstats/,dstats) project, 3 specifically the dstats.alloc module. 4 5 Authors: David Simcha 6 License: Boost License 1.0 7 */ 8 // Adapted for SciD by Lars T. Kyllingstad 9 module scid.core.memory; 10 11 12 /* Permission is hereby granted, free of charge, to any person or organization 13 * obtaining a copy of the software and accompanying documentation covered by 14 * this license (the "Software") to use, reproduce, display, distribute, 15 * execute, and transmit the Software, and to prepare derivative works of the 16 * Software, and to permit third-parties to whom the Software is furnished to 17 * do so, all subject to the following: 18 * 19 * The copyright notices in the Software and this entire statement, including 20 * the above license grant, this restriction and the following disclaimer, 21 * must be included in all copies of the Software, in whole or in part, and 22 * all derivative works of the Software, unless such copies or derivative 23 * works are solely in the form of machine-executable object code generated by 24 * a source language processor. 25 * 26 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 29 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 30 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 31 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 * DEALINGS IN THE SOFTWARE. 33 */ 34 35 36 import core.memory; 37 import std.range; 38 import std.traits; 39 static import core.stdc.stdio; 40 41 42 version(unittest) 43 { 44 import std.conv; 45 } 46 47 48 49 50 template IsType(T, Types...) { 51 // Original idea by Burton Radons, modified 52 static if (Types.length == 0) 53 const bool IsType = false; 54 else 55 const bool IsType = is(T == Types[0]) || IsType!(T, Types[1 .. $]); 56 } 57 58 template ArrayType1(T: T[]) { 59 alias T ArrayType1; 60 } 61 62 template isReferenceType(Types...) { //Thanks to Bearophile. 63 static if (Types.length == 0) { 64 const bool isReferenceType = false; 65 } else static if (Types.length == 1) { 66 static if (IsType!(Unqual!(Types[0]), bool, byte, ubyte, short, ushort, 67 int, uint, long, ulong, float, double, real, ifloat, 68 idouble, ireal, cfloat, cdouble, creal, char, dchar, 69 wchar) ) { 70 const bool isReferenceType = false; 71 } else static if ( is(Types[0] == struct) ) { 72 const bool isReferenceType = 73 isReferenceType!(FieldTypeTuple!(Types[0])); 74 } else static if (isStaticArray!(Types[0])) { 75 const bool isReferenceType = isReferenceType!(ArrayType1!(Types[0])); 76 } else 77 const bool isReferenceType = true; 78 } else 79 const bool isReferenceType = isReferenceType!(Types[0]) | 80 isReferenceType!(Types[1 .. $]); 81 } // end isReferenceType!() 82 83 unittest { 84 static assert(!isReferenceType!(typeof("Foo"[0]))); 85 static assert(isReferenceType!(uint*)); 86 //static assert(!isReferenceType!(typeof([0,1,2]))); 87 struct noPtrs { 88 uint f; 89 uint b; 90 } 91 struct ptrs { 92 uint* f; 93 uint b; 94 } 95 static assert(!isReferenceType!(noPtrs)); 96 static assert(isReferenceType!(ptrs)); 97 //pragma(msg, "Passed isReferenceType unittest"); 98 } 99 100 template blockAttribute(T) { 101 static if (isReferenceType!(T)) 102 enum blockAttribute = 0; 103 else enum blockAttribute = GC.BlkAttr.NO_SCAN; 104 } 105 106 ///Returns a new array of type T w/o initializing elements. 107 T[] newVoid(T)(size_t length) { 108 T* ptr = cast(T*) GC.malloc(length * T.sizeof, blockAttribute!(T)); 109 return ptr[0..length]; 110 } 111 112 void lengthVoid(T)(ref T[] input, int newLength) { 113 input.lengthVoid(cast(size_t) newLength); 114 } 115 116 ///Lengthens an array w/o initializing new elements. 117 void lengthVoid(T)(ref T[] input, size_t newLength) { 118 if (newLength <= input.length || 119 GC.sizeOf(input.ptr) >= newLength * T.sizeof) { 120 input = input.ptr[0..newLength]; //Don't realloc if I don't have to. 121 } else { 122 T* newPtr = cast(T*) GC.realloc(input.ptr, 123 T.sizeof * newLength, blockAttribute!(T)); 124 input = newPtr[0..newLength]; 125 } 126 } 127 128 void reserve(T)(ref T[] input, int newLength) { 129 input.reserve(cast(size_t) newLength); 130 } 131 132 /**Reserves more space for an array w/o changing its length or initializing 133 * the space.*/ 134 void reserve(T)(ref T[] input, size_t newLength) { 135 if (newLength <= input.length || capacity(input.ptr) >= newLength * T.sizeof) 136 return; 137 T* newPtr = cast(T*) GC.realloc(input.ptr, T.sizeof * newLength); 138 staticSetTypeInfo!(T)(newPtr); 139 input = newPtr[0..input.length]; 140 } 141 142 private template Appends(T, U) { 143 enum bool Appends = AppendsImpl!(T, U).ret; 144 } 145 146 private template AppendsImpl(T, U) { 147 T[] a; 148 U b; 149 enum bool ret = is(typeof(a ~= b)); 150 } 151 152 ///Appends to an array, deleting the old array if it has to be realloced. 153 void appendDelOld(T, U)(ref T[] to, U from) 154 if(Appends!(T, U)) { 155 auto oldPtr = to.ptr; 156 to ~= from; 157 if (oldPtr != to.ptr) 158 delete oldPtr; 159 } 160 161 unittest { 162 uint[] foo; 163 foo.appendDelOld(5); 164 foo.appendDelOld(4); 165 foo.appendDelOld(3); 166 foo.appendDelOld(2); 167 foo.appendDelOld(1); 168 assert (foo == cast(uint[]) [5,4,3,2,1]); 169 //writefln("Passed appendDelOld test."); 170 } 171 172 // C functions, marked w/ nothrow. 173 extern(C) nothrow int fprintf(shared(void*), in char *,...); 174 extern(C) nothrow void exit(int); 175 176 /**A struct to allocate memory in a strictly first-in last-out order for 177 * things like scratch space. Technically, memory can safely escape the 178 * scope in which it was allocated. However, this is a very bad idea 179 * unless being done within the private API of a class, struct or nested 180 * function, where it can be guaranteed that LIFO will not be violated. 181 * 182 * Under the hood, this works by allocating large blocks (currently 4 MB) 183 * from the GC, and sub-allocating these as a stack. Very large allocations 184 * (currently > 4MB) are simply performed on the heap. There are two ways to 185 * free memory: Calling TempAlloc.free() frees the last allocated block. 186 * Calling TempAlloc.frameFree() frees all memory allocated since the last 187 * call to TempAlloc.frameInit(). 188 * 189 * All allocations are aligned on 16-byte boundaries using padding, since on x86, 190 * 16-byte alignment is necessary to make SSE2 work. Note, however, that this 191 * is implemented based on the assumption that the GC allocates using 16-byte 192 * alignment (which appears to be true in druntime.) 193 */ 194 struct TempAlloc { 195 private: 196 struct Stack(T) { // Simple, fast stack w/o error checking. 197 private size_t capacity; 198 private size_t index; 199 private T* data; 200 private enum sz = T.sizeof; 201 202 private static size_t max(size_t lhs, size_t rhs) pure nothrow { 203 return (rhs > lhs) ? rhs : lhs; 204 } 205 206 void push(T elem) nothrow { 207 if (capacity == index) { 208 capacity = max(16, capacity * 2); 209 data = cast(T*) ntRealloc(data, capacity * sz, cast(GC.BlkAttr) 0); 210 data[index..capacity] = T.init; // Prevent false ptrs. 211 } 212 data[index++] = elem; 213 } 214 215 T pop() nothrow { 216 index--; 217 auto ret = data[index]; 218 data[index] = T.init; // Prevent false ptrs. 219 return ret; 220 } 221 } 222 223 struct Block { 224 size_t used = 0; 225 void* space = null; 226 } 227 228 final class State { 229 size_t used; 230 void* space; 231 size_t totalAllocs; 232 void*[] lastAlloc; 233 uint nblocks; 234 uint nfree; 235 size_t frameIndex; 236 237 // inUse holds info for all blocks except the one currently being 238 // allocated from. freelist holds space ptrs for all free blocks. 239 Stack!(Block) inUse; 240 Stack!(void*) freelist; 241 242 void putLast(void* last) nothrow { 243 // Add an element to lastAlloc, checking length first. 244 if (totalAllocs == lastAlloc.length) 245 doubleSize(lastAlloc); 246 lastAlloc[totalAllocs++] = cast(void*) last; 247 } 248 } 249 250 enum size_t alignBytes = 16U; 251 enum blockSize = 4U * 1024U * 1024U; 252 enum nBookKeep = 1_048_576; // How many bytes to allocate upfront for bookkeeping. 253 static State state; 254 255 static void die() nothrow { 256 fprintf(core.stdc.stdio.stderr, "TempAlloc error: Out of memory.\0".ptr); 257 exit(1); 258 } 259 260 static void doubleSize(ref void*[] lastAlloc) nothrow { 261 size_t newSize = lastAlloc.length * 2; 262 void** ptr = cast(void**) 263 ntRealloc(lastAlloc.ptr, newSize * (void*).sizeof, GC.BlkAttr.NO_SCAN); 264 265 if (lastAlloc.ptr != ptr) { 266 ntFree(lastAlloc.ptr); 267 } 268 269 lastAlloc = ptr[0..newSize]; 270 } 271 272 static void* ntMalloc(size_t size, GC.BlkAttr attr) nothrow { 273 try { return GC.malloc(size, attr); } catch { die(); } 274 return null; // Can't assert b/c then it would throw. 275 } 276 277 static void* ntRealloc(void* ptr, size_t size, GC.BlkAttr attr) nothrow { 278 try { return GC.realloc(ptr, size, attr); } catch { die(); } 279 return null; 280 } 281 282 static void ntFree(void* ptr) nothrow { 283 try { GC.free(ptr); } catch {} 284 return; 285 } 286 287 static State stateInit() nothrow { 288 State stateCopy; 289 try { stateCopy = new State; } catch { die(); } 290 291 with(stateCopy) { 292 space = ntMalloc(blockSize, GC.BlkAttr.NO_SCAN); 293 lastAlloc = (cast(void**) ntMalloc(nBookKeep, GC.BlkAttr.NO_SCAN)) 294 [0..nBookKeep / (void*).sizeof]; 295 nblocks++; 296 } 297 298 state = stateCopy; 299 return stateCopy; 300 } 301 302 static size_t getAligned(size_t nbytes) pure nothrow { 303 size_t rem = nbytes % alignBytes; 304 return (rem == 0) ? nbytes : nbytes - rem + alignBytes; 305 } 306 307 public: 308 /**Allows caller to cache the state class on the stack and pass it in as a 309 * parameter. This is ugly, but results in a speed boost that can be 310 * significant in some cases because it avoids a thread-local storage 311 * lookup. Also used internally.*/ 312 static State getState() nothrow { 313 State stateCopy = state; 314 return (stateCopy is null) ? stateInit() : stateCopy; 315 } 316 317 /**Initializes a frame, i.e. marks the current allocation position. 318 * Memory past the position at which this was last called will be 319 * freed when frameFree() is called. Returns a reference to the 320 * State class in case the caller wants to cache it for speed.*/ 321 static State frameInit() nothrow { 322 return frameInit(getState()); 323 } 324 325 /**Same as frameInit() but uses stateCopy cached on stack by caller 326 * to avoid a thread-local storage lookup. Strictly a speed hack.*/ 327 static State frameInit(State stateCopy) nothrow { 328 with(stateCopy) { 329 putLast( cast(void*) frameIndex ); 330 frameIndex = totalAllocs; 331 } 332 return stateCopy; 333 } 334 335 /**Frees all memory allocated by TempAlloc since the last call to 336 * frameInit().*/ 337 static void frameFree() nothrow { 338 frameFree(getState()); 339 } 340 341 /**Same as frameFree() but uses stateCopy cached on stack by caller 342 * to avoid a thread-local storage lookup. Strictly a speed hack.*/ 343 static void frameFree(State stateCopy) nothrow { 344 with(stateCopy) { 345 while (totalAllocs > frameIndex) { 346 free(stateCopy); 347 } 348 frameIndex = cast(size_t) lastAlloc[--totalAllocs]; 349 } 350 } 351 352 /**Purely a convenience overload, forwards arguments to TempAlloc.malloc().*/ 353 static void* opCall(T...)(T args) nothrow { 354 return TempAlloc.malloc(args); 355 } 356 357 /**Allocates nbytes bytes on the TempAlloc stack. NOT safe for real-time 358 * programming, since if there's not enough space on the current block, 359 * a new one will automatically be created. Also, very large objects 360 * (currently over 4MB) will simply be heap-allocated. 361 * 362 * Bugs: Memory allocated by TempAlloc is not scanned by the GC. 363 * This is necessary for performance and to avoid false pointer issues. 364 * Do not store the only reference to a GC-allocated object in 365 * TempAlloc-allocated memory.*/ 366 static void* malloc(size_t nbytes) nothrow { 367 return malloc(nbytes, getState()); 368 } 369 370 /**Same as malloc() but uses stateCopy cached on stack by caller 371 * to avoid a thread-local storage lookup. Strictly a speed hack.*/ 372 static void* malloc(size_t nbytes, State stateCopy) nothrow { 373 nbytes = getAligned(nbytes); 374 with(stateCopy) { 375 void* ret; 376 if (blockSize - used >= nbytes) { 377 ret = space + used; 378 used += nbytes; 379 } else if (nbytes > blockSize) { 380 ret = ntMalloc(nbytes, GC.BlkAttr.NO_SCAN); 381 } else if (nfree > 0) { 382 inUse.push(Block(used, space)); 383 space = freelist.pop(); 384 used = nbytes; 385 nfree--; 386 nblocks++; 387 ret = space; 388 } else { // Allocate more space. 389 inUse.push(Block(used, space)); 390 space = ntMalloc(blockSize, GC.BlkAttr.NO_SCAN); 391 nblocks++; 392 used = nbytes; 393 ret = space; 394 } 395 putLast(ret); 396 return ret; 397 } 398 } 399 400 /**Frees the last piece of memory allocated by TempAlloc. Since 401 * all memory must be allocated and freed in strict LIFO order, 402 * there's no need to pass a pointer in. All bookkeeping for figuring 403 * out what to free is done internally.*/ 404 static void free() nothrow { 405 free(getState()); 406 } 407 408 /**Same as free() but uses stateCopy cached on stack by caller 409 * to avoid a thread-local storage lookup. Strictly a speed hack.*/ 410 static void free(State stateCopy) nothrow { 411 with(stateCopy) { 412 void* lastPos = lastAlloc[--totalAllocs]; 413 414 // Handle large blocks. 415 if (lastPos > space + blockSize || lastPos < space) { 416 ntFree(lastPos); 417 return; 418 } 419 420 used = (cast(size_t) lastPos) - (cast(size_t) space); 421 if (nblocks > 1 && used == 0) { 422 freelist.push(space); 423 Block newHead = inUse.pop(); 424 space = newHead.space; 425 used = newHead.used; 426 nblocks--; 427 nfree++; 428 429 if (nfree >= nblocks * 2) { 430 foreach(i; 0..nfree / 2) { 431 ntFree(freelist.pop()); 432 nfree--; 433 } 434 } 435 } 436 } 437 } 438 } 439 440 /**Allocates an array of type T and size size using TempAlloc. 441 * Note that appending to this array using the ~= operator, 442 * or enlarging it using the .length property, will result in 443 * undefined behavior. This is because, if the array is located 444 * at the beginning of a TempAlloc block, the GC will think the 445 * capacity is as large as a TempAlloc block, and will overwrite 446 * adjacent TempAlloc-allocated data, instead of reallocating it. 447 * 448 * Bugs: Do not store the only reference to a GC-allocated reference object 449 * in an array allocated by newStack because this memory is not 450 * scanned by the GC.*/ 451 T[] newStack(T)(size_t size, TempAlloc.State state = null) nothrow { 452 if(state is null) { 453 state = TempAlloc.getState(); 454 } 455 456 size_t bytes = size * T.sizeof; 457 T* ptr = cast(T*) TempAlloc.malloc(bytes, state); 458 return ptr[0..size]; 459 } 460 461 ///**Same as newStack(size_t) but uses stateCopy cached on stack by caller 462 //* to avoid a thread-local storage lookup. Strictly a speed hack.*/ 463 //T[] newStack(T)(size_t size, TempAlloc.State state) nothrow { 464 // size_t bytes = size * T.sizeof; 465 // T* ptr = cast(T*) TempAlloc.malloc(bytes, state); 466 // return ptr[0..size]; 467 //} 468 469 /**Concatenate any number of arrays of the same type, placing results on 470 * the TempAlloc stack.*/ 471 T[0] stackCat(T...)(T data) { 472 foreach(array; data) { 473 static assert(is(typeof(array) == typeof(data[0]))); 474 } 475 476 size_t totalLen = 0; 477 foreach(array; data) { 478 totalLen += array.length; 479 } 480 auto ret = newStack!(Unqual!(typeof(T[0][0])))(totalLen); 481 482 size_t offset = 0; 483 foreach(array; data) { 484 ret[offset..offset + array.length] = array[0..$]; 485 offset += array.length; 486 } 487 return cast(T[0]) ret; 488 } 489 490 void rangeCopy(T, U)(T to, U from) { 491 static if(is(typeof(to[] = from[]))) { 492 to[] = from[]; 493 } else static if(isRandomAccessRange!(T)) { 494 size_t i = 0; 495 foreach(elem; from) { 496 to[i++] = elem; 497 } 498 } 499 } 500 501 /**Creates a duplicate of a range for temporary use within a function in the 502 * best wsy that can be done safely. If ElementType!(T) is a value type 503 * or T is an array, the results can safely be placed in TempAlloc because 504 * either it doesn't need to be scanned by the GC or there's guaranteed to be 505 * another reference to the contents somewhere. Otherwise, the results 506 * are placed on the GC heap. 507 * 508 * This function is much faster if T has a length, but works even if it doesn't. 509 */ 510 Unqual!(ElementType!(T))[] tempdup(T)(T data) 511 if(isInputRange!(T) && (isArray!(T) || !isReferenceType!(ElementType!(T)))) { 512 alias ElementType!(T) E; 513 alias Unqual!(E) U; 514 static if(hasLength!(T)) { 515 U[] ret = newStack!(U)(data.length); 516 rangeCopy(ret, data); 517 return ret; 518 } else { 519 auto state = TempAlloc.getState(); 520 auto startPtr = TempAlloc(0, state); 521 size_t bytesCopied = 0; 522 523 while(!data.empty) { // Make sure range interface is being used. 524 auto elem = data.front; 525 if(state.used + U.sizeof <= TempAlloc.blockSize) { 526 data.popFront; 527 *(cast(U*) (startPtr + bytesCopied)) = elem; 528 bytesCopied += U.sizeof; 529 state.used += U.sizeof; 530 } else { 531 if(bytesCopied + U.sizeof >= TempAlloc.blockSize / 2) { 532 // Then just heap-allocate. 533 U[] result = newVoid!(U)(bytesCopied / U.sizeof); 534 result[] = (cast(U*) startPtr)[0..result.length]; 535 finishCopy(result, data); 536 TempAlloc.free; 537 state.putLast(result.ptr); 538 return result; 539 } else { 540 U[] oldData = (cast(U*) startPtr)[0..bytesCopied / U.sizeof]; 541 state.used -= bytesCopied; 542 state.totalAllocs--; 543 U[] newArray = newStack!(U)(bytesCopied / U.sizeof + 1, state); 544 newArray[0..oldData.length] = oldData[]; 545 startPtr = state.space; 546 newArray[$ - 1] = elem; 547 bytesCopied += U.sizeof; 548 data.popFront; 549 } 550 } 551 } 552 auto rem = bytesCopied % TempAlloc.alignBytes; 553 if(rem != 0) { 554 auto toAdd = 16 - rem; 555 if(state.used + toAdd < TempAlloc.blockSize) { 556 state.used += toAdd; 557 } else { 558 state.used = TempAlloc.blockSize; 559 } 560 } 561 return (cast(U*) startPtr)[0..bytesCopied / U.sizeof]; 562 } 563 } 564 565 Unqual!(ElementType!(T))[] tempdup(T)(T data) 566 if(isInputRange!(T) && !(isArray!(T) || !isReferenceType!(ElementType!(T)))) { 567 auto ret = toArray(data); 568 TempAlloc.getState().putLast(ret.ptr); 569 return ret; 570 } 571 572 private void finishCopy(T, U)(ref T[] result, U range) { 573 auto app = appender(&result); 574 foreach(elem; range) { 575 app.put(elem); 576 } 577 } 578 579 // See Bugzilla 2873. This can be removed once that's fixed. 580 template hasLength(R) { 581 enum bool hasLength = is(typeof(R.init.length) : ulong) || 582 is(typeof(R.init.length()) : ulong); 583 } 584 585 /**Converts any range to an array on the GC heap by the most efficient means 586 * available. If it is already an array, duplicates the range.*/ 587 Unqual!(IterType!(T))[] toArray(T)(T range) if(isIterable!(T)) { 588 static if(isArray!(T)) { 589 // Allow fast copying by assuming that the input is an array. 590 return range.dup; 591 } else static if(hasLength!(T)) { 592 // Preallocate array, then copy. 593 auto ret = newVoid!(Unqual!(IterType!(T)))(range.length); 594 static if(is(typeof(ret[] = range[]))) { 595 ret[] = range[]; 596 } else { 597 size_t pos = 0; 598 foreach(elem; range) { 599 ret[pos++] = elem; 600 } 601 } 602 return ret; 603 } else { 604 // Don't have length, have to use appending. 605 Unqual!(IterType!(T))[] ret; 606 auto app = appender(&ret); 607 foreach(elem; range) { 608 app.put(elem); 609 } 610 return ret; 611 } 612 } 613 614 version (TempAllocUnittest) unittest { 615 // Create quick and dirty finite but lengthless range. 616 struct Count { 617 uint num; 618 uint upTo; 619 uint front() { 620 return num; 621 } 622 void popFront() { 623 num++; 624 } 625 bool empty() { 626 return num >= upTo; 627 } 628 } 629 630 TempAlloc(1024 * 1024 * 3); 631 Count count; 632 count.upTo = 1024 * 1025; 633 auto asArray = tempdup(count); 634 foreach(i, elem; asArray) { 635 assert (i == elem, to!(string)(i) ~ "\t" ~ to!(string)(elem)); 636 } 637 assert (asArray.length == 1024 * 1025); 638 TempAlloc.free; 639 TempAlloc.free; 640 while(TempAlloc.getState().freelist.index > 0) { 641 TempAlloc.getState().freelist.pop; 642 } 643 } 644 645 /**A string to mixin at the beginning of a scope, purely for 646 * convenience. Initializes a TempAlloc frame using frameInit(), 647 * and inserts a scope statement to delete this frame at the end 648 * of the current scope. 649 * 650 * Slower than calling free() manually when only a few pieces 651 * of memory will be allocated in the current scope, due to the 652 * extra bookkeeping involved. Can be faster, however, when 653 * large amounts of allocations, such as arrays of arrays, 654 * are allocated, due to caching of data stored in thread-local 655 * storage.*/ 656 immutable char[] newFrame = 657 "TempAlloc.frameInit(); scope(exit) TempAlloc.frameFree();"; 658 659 version (TempAllocUnittest) unittest { 660 /* Not a particularly good unittest in that it depends on knowing the 661 * internals of TempAlloc, but it's the best I could come up w/. This 662 * is really more of a stress test/sanity check than a normal unittest.*/ 663 664 // First test to make sure a large number of allocations does what it's 665 // supposed to in terms of reallocing lastAlloc[], etc. 666 enum nIter = TempAlloc.blockSize * 5 / TempAlloc.alignBytes; 667 foreach(i; 0..nIter) { 668 TempAlloc(TempAlloc.alignBytes); 669 } 670 assert(TempAlloc.getState().nblocks == 5, to!string(TempAlloc.getState().nblocks)); 671 assert(TempAlloc.getState().nfree == 0); 672 foreach(i; 0..nIter) { 673 TempAlloc.free; 674 } 675 assert(TempAlloc.getState().nblocks == 1); 676 assert(TempAlloc.getState().nfree == 2); 677 678 // Make sure logic for freeing excess blocks works. If it doesn't this 679 // test will run out of memory. 680 enum allocSize = TempAlloc.blockSize / 2; 681 void*[] oldStates; 682 foreach(i; 0..50) { 683 foreach(j; 0..50) { 684 TempAlloc(allocSize); 685 } 686 foreach(j; 0..50) { 687 TempAlloc.free; 688 } 689 oldStates ~= cast(void*) TempAlloc.state; 690 TempAlloc.state = null; 691 } 692 oldStates = null; 693 694 // Make sure data is stored properly. 695 foreach(i; 0..10) { 696 TempAlloc(allocSize); 697 } 698 foreach(i; 0..5) { 699 TempAlloc.free; 700 } 701 GC.collect; // Make sure nothing that shouldn't is getting GC'd. 702 void* space = TempAlloc.state.space; 703 size_t used = TempAlloc.state.used; 704 705 TempAlloc.frameInit; 706 // This array of arrays should not be scanned by the GC because otherwise 707 // bugs caused th not having the GC scan certain internal things in 708 // TempAlloc that it should would not be exposed. 709 uint[][] arrays = (cast(uint[]*) GC.malloc((uint[]).sizeof * 10, 710 GC.BlkAttr.NO_SCAN))[0..10]; 711 foreach(i; 0..10) { 712 uint[] data = newStack!(uint)(250_000); 713 foreach(j, ref e; data) { 714 e = j * (i + 1); // Arbitrary values that can be read back later. 715 } 716 arrays[i] = data; 717 } 718 719 // Make stuff get overwrriten if blocks are getting GC'd when they're not 720 // supposed to. 721 GC.minimize; // Free up all excess pools. 722 uint[][] foo; 723 foreach(i; 0..40) { 724 foo ~= new uint[1_048_576]; 725 } 726 foo = null; 727 728 for(size_t i = 9; i != size_t.max; i--) { 729 foreach(j, e; arrays[i]) { 730 assert (e == j * (i + 1)); 731 } 732 } 733 TempAlloc.frameFree; 734 assert (space == TempAlloc.state.space); 735 assert (used == TempAlloc.state.used); 736 while(TempAlloc.state.nblocks > 1 || TempAlloc.state.used > 0) { 737 TempAlloc.free; 738 } 739 }