1 /**
2 Defines `Vec`, `reallocBuffer` and memory functions.
3 
4 Copyright: Guillaume Piolat 2015-2024.
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 Authors:   Guillaume Piolat
7 */
8 module dplug.core.vec;
9 
10 import std.traits: hasElaborateDestructor;
11 
12 import core.stdc.stdlib: malloc, free, realloc;
13 import core.stdc.string: memcpy, memmove;
14 
15 import core.exception;
16 import inteli.xmmintrin;
17 
18 
19 // This module deals with aligned memory.
20 // You'll also find here a non-copyable std::vector equivalent `Vec`.
21 
22 /// Allocates an aligned memory chunk.
23 /// Functionally equivalent to Visual C++ _aligned_malloc.
24 /// Do not mix allocations with different alignment.
25 ///
26 /// Important: `alignedMalloc(0)` does not necessarily
27 ///            return `null`, and its result
28 ///            _has_ to be freed with `alignedFree`.
29 ///
30 /// Important: Don't call that often in a real-time thread
31 ///            without amortization (keep a capacity).
32 ///            If you need to allocate from `nextBuffer`, do
33 ///            prefer the use of `Vec` which doesn't shrink,
34 ///            and will reuse the allocation.
35 void* alignedMalloc(size_t size, size_t alignment) nothrow @nogc
36 {
37     assert(alignment != 0);
38 
39     // Short-cut and use the C allocator to avoid overhead if no alignment
40     if (alignment == 1)
41     {
42         // C99:
43         // Implementation-defined behavior
44         // Whether the calloc, malloc, and realloc functions return a null pointer
45         // or a pointer to an allocated object when the size requested is zero.
46         // In any case, we'll have to free() it.
47         return malloc(size);
48     }
49 
50     size_t request = requestedSize(size, alignment);
51     void* raw = malloc(request);
52 
53     if (request > 0 && raw == null) // malloc(0) can validly return anything
54         onOutOfMemoryError();
55 
56     return storeRawPointerPlusSizeAndReturnAligned(raw, size, alignment);
57 }
58 
59 /// Frees aligned memory allocated by alignedMalloc or alignedRealloc.
60 /// Functionally equivalent to Visual C++ _aligned_free.
61 /// Do not mix allocations with different alignment.
62 void alignedFree(void* aligned, size_t alignment) nothrow @nogc
63 {
64     // Short-cut and use the C allocator to avoid overhead if no alignment
65     if (alignment == 1)
66         return free(aligned);
67 
68     // support for free(NULL)
69     if (aligned is null)
70         return;
71 
72     assert(alignment != 0);
73     assert(isPointerAligned(aligned, alignment));
74 
75     void** rawLocation = cast(void**)(cast(char*)aligned - size_t.sizeof);
76     free(*rawLocation);
77 }
78 
79 /// Reallocates an aligned memory chunk allocated by `alignedMalloc` or `alignedRealloc`.
80 /// Functionally equivalent to Visual C++ `_aligned_realloc`.
81 /// Do not mix allocations with different alignment.
82 ///
83 /// The "discard" version doesn't preserve data.
84 ///
85 /// Important: `alignedRealloc(p, 0)` does not necessarily return `null`, and its result
86 ///            _has_ to be freed with `alignedFree`.
87 ///
88 /// Important: Don't call that often in a real-time thread
89 ///            without amortization (keep a capacity).
90 ///            If you need to allocate from `nextBuffer`, do
91 ///            prefer the use of `Vec` which doesn't shrink,
92 ///            and will reuse the allocation.
93 void* alignedRealloc(void* aligned, size_t size, size_t alignment) nothrow @nogc
94 {
95     return alignedReallocImpl!true(aligned, size, alignment);
96 }
97 ///ditto
98 void* alignedReallocDiscard(void* aligned, size_t size, size_t alignment) nothrow @nogc
99 {
100     return alignedReallocImpl!false(aligned, size, alignment);
101 }
102 
103 
104 /// Returns: `true` if the pointer is suitably aligned.
105 bool isPointerAligned(void* p, size_t alignment) pure nothrow @nogc
106 {
107     assert(alignment != 0);
108     return ( cast(size_t)p & (alignment - 1) ) == 0;
109 }
110 unittest
111 {
112     ubyte b;
113     align(16) ubyte[5] c;
114     assert(isPointerAligned(&b, 1));
115     assert(!isPointerAligned(&c[1], 2));
116     assert(isPointerAligned(&c[4], 4));
117 }
118 
119 /// Does memory slices a[0..a_size] and b[0..b_size] have an
120 /// overlapping byte?
121 bool isMemoryOverlapping(const(void)* a, ptrdiff_t a_size,
122                          const(void)* b, ptrdiff_t b_size)
123     pure @trusted
124 {
125     assert(a_size >= 0 && b_size >= 0);
126 
127     if (a is null || b is null)
128         return false;
129 
130     if (a_size == 0 || b_size == 0)
131         return false;
132 
133     ubyte* lA = cast(ubyte*)a;
134     ubyte* hA = lA + a_size;
135     ubyte* lB = cast(ubyte*)b;
136     ubyte* hB = lB + b_size;
137 
138     // There is overlapping, if lA is inside lB..hB,
139     // or lB is inside lA..hA
140 
141     if (lA >= lB && lA < hB)
142         return true;
143 
144     if (lB >= lA && lB < hA)
145         return true;
146 
147     return false;
148 }
149 bool isMemoryOverlapping(const(void)[] a, const(void)[] b) pure @trusted
150 {
151     return isMemoryOverlapping(a.ptr, a.length, b.ptr, b.length);
152 }
153 unittest
154 {
155     ubyte[100] a;
156     assert(!isMemoryOverlapping(null, a));
157     assert(!isMemoryOverlapping(a, null));
158     assert(!isMemoryOverlapping(a[1..1], a[0..10]));
159     assert(!isMemoryOverlapping(a[1..10], a[10..100]));
160     assert(!isMemoryOverlapping(a[30..100], a[0..30]));
161     assert(isMemoryOverlapping(a[1..50], a[49..100]));
162     assert(isMemoryOverlapping(a[49..100], a[1..50]));
163     assert(isMemoryOverlapping(a[40..45], a[30..55]));
164     assert(isMemoryOverlapping(a[30..55], a[40..45]));
165 }
166 
167 private nothrow @nogc
168 {
169     void* alignedReallocImpl(bool PreserveDataIfResized)(void* aligned, size_t size, size_t alignment)
170     {
171         // Short-cut and use the C allocator to avoid overhead if no alignment
172         if (alignment == 1)
173         {
174             // C99:
175             // Implementation-defined behavior
176             // Whether the calloc, malloc, and realloc functions return a null pointer
177             // or a pointer to an allocated object when the size requested is zero.
178             // In any case, we'll have to `free()` it.
179             return realloc(aligned, size);
180         }
181 
182         if (aligned is null)
183             return alignedMalloc(size, alignment);
184 
185         assert(alignment != 0);
186         assert(isPointerAligned(aligned, alignment));
187 
188         size_t previousSize = *cast(size_t*)(cast(char*)aligned - size_t.sizeof * 2);
189 
190         void* raw = *cast(void**)(cast(char*)aligned - size_t.sizeof);
191         size_t request = requestedSize(size, alignment);
192         size_t previousRequest = requestedSize(previousSize, alignment);
193         assert(previousRequest - request == previousSize - size); // same alignment
194 
195         // Heuristic: if a requested size is within 50% to 100% of what is already allocated
196         //            then exit with the same pointer
197         if ( (previousRequest < request * 4) && (request <= previousRequest) )
198             return aligned;
199 
200         void* newRaw = malloc(request);
201         static if( __VERSION__ > 2067 ) // onOutOfMemoryError wasn't nothrow before July 2014
202         {
203             if (request > 0 && newRaw == null) // realloc(0) can validly return anything
204                 onOutOfMemoryError();
205         }
206 
207         void* newAligned = storeRawPointerPlusSizeAndReturnAligned(newRaw, size, alignment);
208 
209         static if (PreserveDataIfResized)
210         {
211             size_t minSize = size < previousSize ? size : previousSize;
212             memcpy(newAligned, aligned, minSize); // memcpy OK
213         }
214 
215         // Free previous data
216         alignedFree(aligned, alignment);
217         assert(isPointerAligned(newAligned, alignment));
218         return newAligned;
219     }
220 
221     /// Returns: next pointer aligned with alignment bytes.
222     void* nextAlignedPointer(void* start, size_t alignment) pure
223     {
224         import dplug.core.math : nextMultipleOf;
225         return cast(void*)nextMultipleOf(cast(size_t)(start), alignment);
226     }
227 
228     // Returns number of bytes to actually allocate when asking
229     // for a particular alignement
230     size_t requestedSize(size_t askedSize, size_t alignment) pure
231     {
232         enum size_t pointerSize = size_t.sizeof;
233         return askedSize + alignment - 1 + pointerSize * 2;
234     }
235 
236     // Store pointer given my malloc, and size in bytes initially requested (alignedRealloc needs it)
237     void* storeRawPointerPlusSizeAndReturnAligned(void* raw, size_t size, size_t alignment)
238     {
239         enum size_t pointerSize = size_t.sizeof;
240         char* start = cast(char*)raw + pointerSize * 2;
241         void* aligned = nextAlignedPointer(start, alignment);
242         void** rawLocation = cast(void**)(cast(char*)aligned - pointerSize);
243         *rawLocation = raw;
244         size_t* sizeLocation = cast(size_t*)(cast(char*)aligned - 2 * pointerSize);
245         *sizeLocation = size;
246         assert( isPointerAligned(aligned, alignment) );
247         return aligned;
248     }
249 }
250 
251 unittest
252 {
253     {
254         void* p = alignedMalloc(23, 16);
255         assert(p !is null);
256         assert(((cast(size_t)p) & 0xf) == 0);
257 
258         alignedFree(p, 16);
259     }
260 
261     void* nullAlloc = alignedMalloc(0, 16);
262     assert(nullAlloc != null);
263     nullAlloc = alignedRealloc(nullAlloc, 0, 16);
264     assert(nullAlloc != null);
265     alignedFree(nullAlloc, 16);
266 
267     {
268         int alignment = 16;
269         int* p = null;
270 
271         // check if growing keep values in place
272         foreach(int i; 0..100)
273         {
274             p = cast(int*) alignedRealloc(p, (i + 1) * int.sizeof, alignment);
275             p[i] = i;
276         }
277 
278         foreach(int i; 0..100)
279             assert(p[i] == i);
280 
281         p = cast(int*) alignedRealloc(p, 0, alignment);
282         assert(p !is null);
283 
284         alignedFree(p, alignment);
285     }
286 
287     // Verify that same size alloc preserve pointer.
288     {
289         void* p = null;
290         p = alignedRealloc(p, 254, 16);
291         void* p2 = alignedRealloc(p, 254, 16);
292         assert(p == p2);
293 
294         // Test shrink heuristic
295         void* p3 = alignedRealloc(p, 128, 16);
296         assert(p == p3);
297         alignedFree(p3, 16);
298     }
299 }
300 
301 
302 
303 /// Used throughout dplug:dsp to avoid reliance on GC.
304 /// Important: Size 0 is special-case to free the slice.
305 /// This works a bit like alignedRealloc except with slices as input.
306 /// You MUST use consistent alignement thoughout the lifetime of this buffer.
307 ///
308 /// Params:
309 ///    buffer = Existing allocated buffer. Can be null.
310 ///             Input slice length is not considered.
311 ///    length = Desired slice length.
312 ///    alignment = Alignement if the slice has allocation requirements, 1 else.
313 ///                Must match for deallocation.
314 ///
315 /// Example:
316 /// ---
317 /// import std.stdio;
318 ///
319 /// struct MyDSP
320 /// {
321 /// nothrow @nogc:
322 ///
323 ///     void initialize(int maxFrames)
324 ///     {
325 ///         // mybuf points to maxFrames frames
326 ///         mybuf.reallocBuffer(maxFrames);
327 ///     }
328 ///
329 ///     ~this()
330 ///     {
331 ///         // If you don't free the buffer, it will leak.
332 ///         mybuf.reallocBuffer(0);
333 ///     }
334 ///
335 /// private:
336 ///     float[] mybuf;
337 /// }
338 /// ---
339 /// Important: Don't call that often in a real-time thread
340 ///            without amortization (keep a capacity).
341 ///            If you need to allocate from `nextBuffer`, do
342 ///            prefer the use of `Vec` which doesn't shrink,
343 ///            and will reuse the allocation.
344 ///            This, instead, will shrink the allocation
345 ///            which makes it more expensive.
346 void reallocBuffer(T)(ref T[] buffer, size_t length, int alignment = 1) nothrow @nogc
347 {
348     static if (is(T == struct) && hasElaborateDestructor!T)
349     {
350         static assert(false); // struct with destructors not supported
351     }
352 
353     /// Size 0 is special-case to free the slice.
354     if (length == 0)
355     {
356         alignedFree(buffer.ptr, alignment);
357         buffer = null;
358         return;
359     }
360 
361     T* pointer = cast(T*) alignedRealloc(buffer.ptr, T.sizeof * length, alignment);
362     if (pointer is null)
363         buffer = null; // alignment 1 can still return null
364     else
365         buffer = pointer[0..length];
366 }
367 unittest
368 {
369     int[] arr;
370     arr.reallocBuffer(15);
371     assert(arr.length == 15);
372     arr.reallocBuffer(0);
373     assert(arr.length == 0);
374 }
375 
376 
377 /// Returns: A newly created `Vec`.
378 Vec!T makeVec(T)(size_t initialSize = 0) nothrow @nogc
379 {
380     return Vec!T(initialSize);
381 }
382 
383 deprecated("Vec!T must have alignment 1. This constructor will be removed in Dplug v16")
384 Vec!T makeVec(T)(size_t initialSize = 0, int alignment) nothrow @nogc
385 {
386     return Vec!T(initialSize, alignment);
387 }
388 
389 /// Kind of a std::vector replacement.
390 /// Grow-only array, points to a (optionally aligned) memory location.
391 /// This can also work as an output range.
392 /// `Vec` is designed to work even when uninitialized, without `makeVec`.
393 /// Warning: it is pretty barebones, doesn't respect T.init or call destructors.
394 ///          When used in a GC program, GC roots won't be registered.
395 struct Vec(T)
396 {
397 nothrow:
398 @nogc:
399 
400     static if (is(T == struct) && hasElaborateDestructor!T)
401     {
402         static assert(false, "struct with destructors cannot go in `Vec!T`. You are welcome to use the `numem` package instead or do otherwise.");
403     }
404 
405     public
406     {
407         /// Creates an aligned buffer with given initial size.
408         this(size_t initialSize) @safe
409         {
410             _size = 0;
411             _allocated = 0;
412             _data = null;
413             _alignment = 1;
414             resizeExactly(initialSize);
415         }
416 
417         deprecated("Vec!T must have alignment 1. This constructor will be removed in Dplug v16")
418             this(size_t initialSize, int alignment) @safe
419         {
420             assert(alignment != 0);
421             _size = 0;
422             _allocated = 0;
423             _data = null;
424             _alignment = alignment;
425             resizeExactly(initialSize);
426         }
427 
428         ~this() @trusted
429         {
430             if (_data !is null)
431             {
432                 alignedFree(_data, _alignment);
433                 _data = null;
434                 _allocated = 0;
435             }
436         }
437 
438         @disable this(this);
439 
440         /// Returns: Length of buffer in elements.
441         size_t length() pure const @safe
442         {
443             return _size;
444         }
445         ///ditto
446         alias size = length;
447 
448         /// Returns: Length of buffer in elements, as signed.
449         ptrdiff_t slength() pure const @safe
450         {
451             return _size;
452         }
453         ///ditto
454         alias ssize = length;
455 
456         /// Returns: `true` if length is zero.
457         bool isEmpty() pure const @safe
458         {
459             return _size == 0;
460         }
461 
462         /// Returns: Allocated size of the underlying array.
463         size_t capacity() pure const @safe
464         {
465             return _allocated;
466         }
467 
468         /// Returns: Length of buffer in elements.
469         alias opDollar = length;
470 
471         /// Resizes a buffer to hold $(D askedSize) elements.
472         void resize(size_t askedSize) @trusted
473         {
474             resizeExactly(askedSize);
475         }
476 
477         /// Pop last element
478         T popBack() @trusted
479         {
480             assert(_size > 0);
481             _size = _size - 1;
482             return _data[_size];
483         }
484 
485         /// Append an element to this buffer.
486         void pushBack(T x) @trusted
487         {
488             size_t i = _size;
489             resizeGrow(_size + 1);
490             _data[i] = x;
491         }
492 
493         // DMD 2.088 deprecates the old D1-operators
494         static if (__VERSION__ >= 2088)
495         {
496             ///ditto
497             void opOpAssign(string op)(T x) @safe if (op == "~")
498             {
499                 pushBack(x);
500             }
501         }
502         else
503         {
504             ///ditto
505             void opCatAssign(T x) @safe
506             {
507                 pushBack(x);
508             }
509         }
510 
511         // Output range support
512         alias put = pushBack;
513 
514         /// Finds an item, returns -1 if not found
515         int indexOf(T x) @trusted
516         {
517             enum bool isStaticArray(T) = __traits(isStaticArray, T);
518 
519             static if (isStaticArray!T)
520             {
521                 // static array would be compared by identity as slice, which is not what we want.
522                 foreach(int i; 0..cast(int)_size)
523                     if (_data[i] == x)
524                         return i;
525             }
526             else
527             {
528                 // base types: identity is equality
529                 // reference types: looking for identity
530                 foreach(int i; 0..cast(int)_size)
531                     if (_data[i] is x)
532                         return i;
533             }
534             return -1;
535         }
536 
537         /// Removes an item and replaces it by the last item.
538         /// Warning: this reorders the array.
539         void removeAndReplaceByLastElement(size_t index) @trusted
540         {
541             assert(index < _size);
542             _data[index] = _data[--_size];
543         }
544 
545         /// Removes an item and shift the rest of the array to front by 1.
546         /// Warning: O(N) complexity.
547         void removeAndShiftRestOfArray(size_t index) @trusted
548         {
549             assert(index < _size);
550             for (; index + 1 < _size; ++index)
551                 _data[index] = _data[index+1];
552             --_size;
553         }
554 
555         /// Removes a range of items and shift the rest of the array to front.
556         /// Warning: O(N) complexity.
557         void removeAndShiftRestOfArray(size_t first, size_t last) @trusted
558         {
559             assert(first <= last && first <= _size && (last <= _size));
560             size_t remain = _size - last;
561             for (size_t n = 0; n < remain; ++n)
562                 _data[first + n] = _data[last + n];
563             _size -= (last - first);
564         }
565 
566         /// Appends another buffer to this buffer.
567         void pushBack(ref Vec other) @trusted
568         {
569             size_t oldSize = _size;
570             resizeGrow(_size + other._size);
571             memmove(_data + oldSize, other._data, T.sizeof * other._size);
572         }
573 
574         /// Appends a slice to this buffer.
575         /// `slice` should not belong to the same buffer _data.
576         void pushBack(T[] slice) @trusted
577         {
578             size_t oldSize = _size;
579             size_t newSize = _size + slice.length;
580             resizeGrow(newSize);
581             for (size_t n = 0; n < slice.length; ++n)
582                 _data[oldSize + n] = slice[n];
583         }
584 
585         /// Returns: Raw pointer to data.
586         @property inout(T)* ptr() inout @system
587         {
588             return _data;
589         }
590 
591         /// Returns: n-th element.
592         ref inout(T) opIndex(size_t i) pure inout @trusted
593         {
594             return _data[i];
595         }
596 
597         T opIndexAssign(T x, size_t i) pure @trusted
598         {
599             return _data[i] = x;
600         }
601 
602         /// Sets size to zero, but keeps allocated buffers.
603         void clearContents() pure @safe
604         {
605             _size = 0;
606         }
607 
608         /// Returns: Whole content of the array in one slice.
609         inout(T)[] opSlice() inout @safe
610         {
611             return opSlice(0, length());
612         }
613 
614         /// Returns: A slice of the array.
615         inout(T)[] opSlice(size_t i1, size_t i2) inout @trusted
616         {
617             return _data[i1 .. i2];
618         }
619 
620         /// Fills the buffer with the same value.
621         void fill(T x) @trusted
622         {
623             _data[0.._size] = x;
624         }
625 
626         /// Move. Give up owner ship of the data.
627         T[] releaseData() @system
628         {
629             T[] data = _data[0.._size];
630             assert(_alignment == 1); // else would need to be freed with alignedFree.
631             this._data = null;
632             this._size = 0;
633             this._allocated = 0;
634             this._alignment = 0;
635             return data;
636         }
637     }
638 
639     private
640     {
641         size_t _size = 0;
642         T* _data = null;
643         size_t _allocated = 0;
644         size_t _alignment = 1; // for an unaligned Vec, you probably are not interested in alignment
645 
646         /// Used internally to grow in response to a pushBack operation.
647         /// Different heuristic, since we know that the resize is likely to be repeated for an
648         /// increasing size later.
649         void resizeGrow(size_t askedSize) @trusted
650         {
651             if (_allocated < askedSize)
652             {
653 
654                 version(all)
655                 {
656                     size_t newCap = computeNewCapacity(askedSize, _size);
657                     setCapacity(newCap);
658                 }
659                 else
660                 {
661                     setCapacity(2 * askedSize);
662                 }
663             }
664             _size = askedSize;
665         }
666 
667         // Resizes the `Vector` to hold exactly `askedSize` elements.
668         // Still if the allocated capacity is larger, do nothing.
669         void resizeExactly(size_t askedSize) @trusted
670         {
671             if (_allocated < askedSize)
672             {
673                 setCapacity(askedSize);
674             }
675             _size = askedSize;
676         }
677 
678         // Internal use, realloc internal buffer, copy existing items.
679         // Doesn't initialize the new ones.
680         void setCapacity(size_t cap)
681         {
682             size_t numBytes = cap * T.sizeof;
683             _data = cast(T*)(alignedRealloc(_data, numBytes, _alignment));
684             _allocated = cap;
685         }
686 
687         // Compute new capacity, while growing.
688         size_t computeNewCapacity(size_t newLength, size_t oldLength)
689         {
690             // Optimal value (Windows malloc) not far from there.
691             enum size_t PAGESIZE = 4096;
692 
693             size_t newLengthBytes = newLength * T.sizeof;
694             if (newLengthBytes > PAGESIZE)
695             {
696                 // Larger arrays need a smaller growth factor to avoid wasting too much bytes.
697                 // This was found when tracing curve, not too far from golden ratio for some reason.
698                 return newLength + newLength / 2 + newLength / 8;
699             }
700             else
701             {
702                 // For smaller arrays being pushBack, can bring welcome speed by minimizing realloc.
703                 return newLength * 3;
704             }
705         }
706     }
707 }
708 
709 unittest
710 {
711     import std.range.primitives;
712     static assert(isOutputRange!(Vec!ubyte, ubyte));
713 
714 
715     import std.random;
716     int NBUF = 200;
717 
718     Xorshift32 rng;
719     rng.seed(0xBAADC0DE);
720 
721     struct box2i { int a, b, c, d; }
722     Vec!box2i[] boxes;
723     boxes.length = NBUF;
724 
725     foreach(i; 0..NBUF)
726     {
727         boxes[i] = makeVec!box2i();
728     }
729 
730     foreach(j; 0..200)
731     {
732         foreach(i; 0..NBUF)
733         {
734             int previousSize = cast(int)(boxes[i].length);
735             void* previousPtr = boxes[i].ptr;
736             foreach(int k; 0..cast(int)(boxes[i].length))
737                 boxes[i][k] = box2i(k, k, k, k);
738 
739             int newSize = uniform(0, 100, rng);
740             boxes[i].resize(newSize);
741 
742             int minSize = previousSize;
743             if (minSize > boxes[i].length)
744                 minSize = cast(int)(boxes[i].length);
745 
746             void* newPtr = boxes[i].ptr;
747             foreach(int k; 0..minSize)
748             {
749                 box2i item = boxes[i][k];
750                 box2i shouldBe = box2i(k, k, k, k);
751                 assert(item == shouldBe);
752             }
753 
754             int sum = 0;
755             foreach(k; 0..newSize)
756             {
757                 box2i bb = boxes[i][k];
758                 sum += bb.a + bb.b + bb.c + bb.d;
759             }
760         }
761     }
762 
763     foreach(i; 0..NBUF)
764         boxes[i].destroy();
765 
766     {
767         auto buf = makeVec!int;
768         enum N = 10;
769         buf.resize(N);
770         foreach(i ; 0..N)
771             buf[i] = i;
772 
773         foreach(i ; 0..N)
774             assert(buf[i] == i);
775 
776         auto buf2 = makeVec!int;
777         buf2.pushBack(11);
778         buf2.pushBack(14);
779 
780         // test pushBack(slice)
781         buf.clearContents();
782         buf.pushBack(buf2[]);
783         assert(buf[0] == 11);
784         assert(buf[1] == 14);
785 
786         // test pushBack(slice)
787         buf2[1] = 8;
788         buf.clearContents();
789         buf.pushBack(buf2);
790         assert(buf[0] == 11);
791         assert(buf[1] == 8);
792     }
793 }
794 
795 // Vec should work without any initialization
796 unittest
797 {
798     Vec!string vec;
799 
800     foreach(e; vec[])
801     {
802     }
803 
804     assert(vec.length == 0);
805     assert(vec.size == 0);
806     assert(vec.slength == 0);
807     assert(vec.ssize == 0);
808     vec.clearContents();
809     vec.resize(0);
810     assert(vec == vec.init);
811     vec.fill("filler");
812     assert(vec.ptr is null);
813 }
814 
815 // Issue #312: vec.opIndex not returning ref which break struct assignment
816 unittest
817 {
818     static struct A
819     {
820         int x;
821     }
822     Vec!A vec = makeVec!A();
823     A a;
824     vec.pushBack(a);
825     vec ~= a;
826     vec[0].x = 42; // vec[0] needs to return a ref
827     assert(vec[0].x == 42);
828 }
829 unittest // removeAndShiftRestOfArray was wrong
830 {
831     Vec!int v;
832     v.pushBack(14);
833     v.pushBack(27);
834     v.pushBack(38);
835     assert(v.length == 3);
836     v.removeAndShiftRestOfArray(1);
837     assert(v.length == 2);
838     assert(v[] == [14, 38]);
839 }
840 unittest // removeAndShiftRestOfArray with slice
841 {
842     Vec!int v;
843     v.pushBack([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
844     v.removeAndShiftRestOfArray(3, 5);
845     assert(v[] == [0, 1, 2, 5, 6, 7, 8, 9]);
846     v.removeAndShiftRestOfArray(0, 4);
847     assert(v[] == [6, 7, 8, 9]);
848     v.removeAndShiftRestOfArray(2, 4);
849     assert(v[] == [6, 7]);
850     v.removeAndShiftRestOfArray(2, 2);
851     v.removeAndShiftRestOfArray(0, 0);
852     v.removeAndShiftRestOfArray(1, 1);
853     assert(v[] == [6, 7]);
854 }
855 
856 
857 
858 /// Allows to merge the allocation of several arrays, which saves allocation count and can speed up things thanks to locality.
859 ///
860 /// Example: see below unittest.
861 struct MergedAllocation
862 {
863 nothrow:
864 @nogc:
865 
866     // In debug mode, add stomp detection to `MergedAllocation`.
867     // This takes additional complexity to coarse check for stomping nearby buffers.
868     debug
869     {
870         private enum mergedAllocStompWarning = true;
871     }
872     else
873     {
874         private enum mergedAllocStompWarning = false;
875     }
876 
877 
878     // This adds 32-byte of sentinels around each allocation,
879     // and check at the end of the program that they were unaffected.
880     static if (mergedAllocStompWarning)
881     {
882         // Number of bytes to write between areas to check for stomping.
883         enum SENTINEL_BYTES = 32;
884     }
885 
886     enum maxExpectedAlignment = 32;
887 
888     /// Start defining the area of allocations.
889     void start()
890     {
891         // Detect memory errors in former uses.
892         static if (mergedAllocStompWarning)
893         {
894             checkSentinelAreas();
895             _allocateWasCalled = false;
896         }
897 
898         _base = cast(ubyte*)(cast(size_t)0);
899     }
900 
901     /// Allocate (or count) space needed for `numElems` elements of type `T` with given alignment.
902     /// This gets called twice for each array, see example for usage.
903     ///
904     /// This bumps the internal bump allocator.
905     /// Giving null to this chain and converting the result to size_t give the total needed size
906     /// for the merged allocation.
907     ///
908     /// Warning:
909     ///          - If called after a `start()` call, the area returned are wrong and are only for
910     ///             counting needed bytes. Don't use those.
911     ///
912     ///          - If called after an `allocate()` call, the area returned are an actual merged
913     ///            allocation (if the same calls are done).
914     ///
915     /// Warning: Prefer `allocArray` over `alloc` variant, since the extra length field WILL help
916     ///          you catch memory errors before release. Else it is very common to miss buffer
917     ///          overflows in samplerate changes.
918     void allocArray(T)(out T[] array, size_t numElems, size_t alignment = 1)
919     {
920         assert(alignment <= maxExpectedAlignment);
921         assert( (alignment != 0) && ((alignment & (alignment - 1)) == 0)); // power of two
922 
923         size_t adr = cast(size_t) _base;
924 
925         // 1. Align base address
926         size_t mask = ~(alignment - 1);
927         adr = (adr + alignment - 1) & mask;
928 
929         // 2. Assign array and base.
930         array = (cast(T*)adr)[0..numElems];
931         adr += T.sizeof * numElems;
932         _base = cast(ubyte*) adr;
933 
934         static if (mergedAllocStompWarning)
935         {
936             if (_allocateWasCalled && _allocation !is null)
937             {
938                 // Each allocated area followed with SENTINEL_BYTES bytes of value 0xCC
939                 _base[0..SENTINEL_BYTES] = 0xCC;
940                 registerSentinel(_base);
941             }
942             _base += SENTINEL_BYTES;
943         }
944     }
945 
946     ///ditto
947     void alloc(T)(out T* array, size_t numElems, size_t alignment = 1)
948     {
949         T[] arr;
950         allocArray(arr, numElems, alignment);
951         array = arr.ptr;
952     }
953 
954     /// Allocate actual storage for the merged allocation. From there, you need to define exactly the same area with `alloc` and `allocArray`.
955     /// This time they will get a proper value.
956     void allocate()
957     {
958         static if (mergedAllocStompWarning) _allocateWasCalled = true;
959 
960         size_t sizeNeeded =  cast(size_t)_base; // since it was fed 0 at start.
961 
962         if (sizeNeeded == 0)
963         {
964             // If no bytes are requested, it means no buffer were requested, or only with zero size.
965             // We will return a null pointer in that case, since accessing them would be illegal anyway.
966             _allocation = null;
967         }
968         else
969         {
970             // the merged allocation needs to have the largest expected alignment, else the size could depend on the hazards
971             // of the allocation. With maximum alignment, padding is the same so long as areas have smaller or equal alignment requirements.
972             _allocation = cast(ubyte*) _mm_realloc(_allocation, sizeNeeded, maxExpectedAlignment);
973         }
974 
975         // So that the next layout call points to the right area.
976         _base = _allocation;
977     }
978 
979     ~this()
980     {
981         static if (mergedAllocStompWarning)
982         {
983             checkSentinelAreas();
984         }
985 
986         if (_allocation != null)
987         {
988             _mm_free(_allocation);
989             _allocation = null;
990         }
991     }
992 
993 private:
994 
995     // Location of the allocation.
996     ubyte* _allocation = null;
997 
998     ///
999     ubyte* _base = null;
1000 
1001     static if (mergedAllocStompWarning)
1002     {
1003         bool _allocateWasCalled = false;
1004 
1005         Vec!(ubyte*) _sentinels; // start of sentinel area (SENTINAL_BYTES long)
1006 
1007         void registerSentinel(ubyte* start)
1008         {
1009             _sentinels.pushBack(start);
1010         }
1011 
1012         bool hasMemoryError() // true if sentinel bytes stomped
1013         {
1014             assert(_allocateWasCalled && _allocation !is null);
1015 
1016             foreach(ubyte* s; _sentinels[])
1017             {
1018                 for (int n = 0; n < 32; ++n)
1019                 {
1020                     if (s[n] != 0xCC)
1021                         return true;
1022                 }
1023             }
1024             return false;
1025         }
1026 
1027         // Check existing sentinels, and unregister them
1028         void checkSentinelAreas()
1029         {
1030             if (!_allocateWasCalled)
1031                 return; // still haven't done an allocation, nothing to check.
1032 
1033             if (_allocation is null)
1034                 return; // nothing to check
1035 
1036             // If you fail here, there is a memory error in your access patterns.
1037             // Sentinel bytes of value 0xCC were overwritten in a `MergedAllocation`.
1038             // You can use slices with `allocArray` instead of `alloc` to find the faulty
1039             // access. This check doesn't catch everything!
1040             assert(! hasMemoryError());
1041 
1042             _sentinels.clearContents();
1043         }
1044     }
1045 }
1046 
1047 
1048 // Here is how you should use MergedAllocation.
1049 unittest
1050 {
1051     static struct MyDSPStruct
1052     {
1053     public:
1054     nothrow:
1055     @nogc:
1056         void initialize(int maxFrames)
1057         {
1058             _mergedAlloc.start();
1059             layout(_mergedAlloc, maxFrames); // you need such a layout function to be called twice.
1060             _mergedAlloc.allocate();
1061             layout(_mergedAlloc, maxFrames); // the first time arrays area allocated in the `null` area, the second time in
1062                                              // actually allocated memory (since we now have the needed length).
1063         }
1064 
1065         void layout(ref MergedAllocation ma, int maxFrames)
1066         {
1067             // allocate `maxFrames` elems, and return a slice in `_intermediateBuf`.
1068             ma.allocArray(_intermediateBuf, maxFrames);
1069 
1070             // allocate `maxFrames` elems, aligned to 16-byte boundaries. Return a pointer to that in `_coeffs`.
1071             ma.alloc(_coeffs, maxFrames, 16);
1072         }
1073 
1074     private:
1075         float[] _intermediateBuf;
1076         double* _coeffs;
1077         MergedAllocation _mergedAlloc;
1078     }
1079 
1080     MyDSPStruct s;
1081     s.initialize(14);
1082     s._coeffs[0..14] = 1.0f;
1083     s._intermediateBuf[0..14] = 1.0f;
1084     s.initialize(17);
1085     s._coeffs[0..17] = 1.0f;
1086     s._intermediateBuf[0..17] = 1.0f;
1087 }
1088 
1089 // Should be valid to allocate nothing with a MergedAllocation.
1090 unittest
1091 {
1092     MergedAllocation ma;
1093     ma.start();
1094     ma.allocate();
1095     assert(ma._allocation == null);
1096 }
1097 
1098 // test stomping detection
1099 unittest
1100 {
1101     MergedAllocation ma;
1102     ma.start();
1103     ubyte[] arr, arr2;
1104     ma.allocArray(arr, 17);
1105     ma.allocArray(arr2, 24);
1106     assert(ma._allocation == null);
1107 
1108     ma.allocate();
1109     ma.allocArray(arr, 17);
1110     ma.allocArray(arr2, 24);
1111     assert(ma._allocation != null);
1112     assert(arr.length == 17);
1113     assert(arr2.length == 24);
1114 
1115     // Test memory error detection with a simple case.
1116     static if (MergedAllocation.mergedAllocStompWarning)
1117     {
1118         assert(!ma.hasMemoryError());
1119 
1120         // Create a memory error
1121         arr.ptr[18] = 2;
1122         assert(ma.hasMemoryError());
1123         arr.ptr[18] = 0xCC; // undo the error to avoid stopping the unittests
1124     }
1125 }