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