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 }