1 /**
2  * Copyright: Copyright Auburn Sounds 2015-2016
3  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4  * Authors:   Guillaume Piolat
5  */
6 module dplug.core.alignedbuffer;
7 
8 import std.traits: hasElaborateDestructor;
9 
10 import core.stdc.stdlib: malloc, free, realloc;
11 import core.stdc.string: memcpy;
12 
13 import core.exception;
14 
15 
16 // This module deals with aligned memory
17 
18 
19 /// Allocates an aligned memory chunk.
20 /// Functionally equivalent to Visual C++ _aligned_malloc.
21 /// Do not mix allocations with different alignment.
22 void* alignedMalloc(size_t size, size_t alignment) nothrow @nogc
23 {
24     assert(alignment != 0);
25 
26     // Short-cut and use the C allocator to avoid overhead if no alignment
27     if (alignment == 1)
28         return malloc(size);
29 
30     if (size == 0)
31         return null;
32 
33     size_t request = requestedSize(size, alignment);
34     void* raw = malloc(request);
35 
36     if (request > 0 && raw == null) // malloc(0) can validly return anything
37         onOutOfMemoryError();
38 
39     return storeRawPointerPlusSizeAndReturnAligned(raw, size, alignment);
40 }
41 
42 /// Frees aligned memory allocated by alignedMalloc or alignedRealloc.
43 /// Functionally equivalent to Visual C++ _aligned_free.
44 /// Do not mix allocations with different alignment.
45 void alignedFree(void* aligned, size_t alignment) nothrow @nogc
46 {
47     assert(alignment != 0);
48 
49     // Short-cut and use the C allocator to avoid overhead if no alignment
50     if (alignment == 1)
51         return free(aligned);
52 
53     // support for free(NULL)
54     if (aligned is null)
55         return;
56 
57     void** rawLocation = cast(void**)(cast(char*)aligned - size_t.sizeof);
58     free(*rawLocation);
59 }
60 
61 /// Reallocates an aligned memory chunk allocated by alignedMalloc or alignedRealloc.
62 /// Functionally equivalent to Visual C++ _aligned_realloc.
63 /// Do not mix allocations with different alignment.
64 @nogc void* alignedRealloc(void* aligned, size_t size, size_t alignment) nothrow
65 {
66     // If you fail here, it can mean you've used an uninitialized AlignedBuffer.
67     assert(alignment != 0);
68 
69     // Short-cut and use the C allocator to avoid overhead if no alignment
70     if (alignment == 1)
71         return realloc(aligned, size);
72 
73     if (aligned is null)
74         return alignedMalloc(size, alignment);
75 
76     if (size == 0)
77     {
78         alignedFree(aligned, alignment);
79         return null;
80     }
81 
82     size_t previousSize = *cast(size_t*)(cast(char*)aligned - size_t.sizeof * 2);
83 
84 
85     void* raw = *cast(void**)(cast(char*)aligned - size_t.sizeof);
86     size_t request = requestedSize(size, alignment);
87 
88     // Heuristic: if a requested size is within 50% to 100% of what is already allocated
89     //            then exit with the same pointer
90     if ( (previousSize < request * 4) && (request <= previousSize) )
91         return aligned;
92 
93     void* newRaw = malloc(request);
94     static if( __VERSION__ > 2067 ) // onOutOfMemoryError wasn't nothrow before July 2014
95     {
96         if (request > 0 && newRaw == null) // realloc(0) can validly return anything
97             onOutOfMemoryError();
98     }
99 
100     void* newAligned = storeRawPointerPlusSizeAndReturnAligned(newRaw, request, alignment);
101     size_t minSize = size < previousSize ? size : previousSize;
102     memcpy(newAligned, aligned, minSize);
103 
104     // Free previous data
105     alignedFree(aligned, alignment);
106     return newAligned;
107 }
108 
109 private
110 {
111     /// Returns: next pointer aligned with alignment bytes.
112     void* nextAlignedPointer(void* start, size_t alignment) pure nothrow @nogc
113     {
114         return cast(void*)nextMultipleOf(cast(size_t)(start), alignment);
115     }
116 
117     // Returns number of bytes to actually allocate when asking
118     // for a particular alignement
119     @nogc size_t requestedSize(size_t askedSize, size_t alignment) pure nothrow
120     {
121         enum size_t pointerSize = size_t.sizeof;
122         return askedSize + alignment - 1 + pointerSize * 2;
123     }
124 
125     // Store pointer given my malloc, and size in bytes initially requested (alignedRealloc needs it)
126     @nogc void* storeRawPointerPlusSizeAndReturnAligned(void* raw, size_t size, size_t alignment) nothrow
127     {
128         enum size_t pointerSize = size_t.sizeof;
129         char* start = cast(char*)raw + pointerSize * 2;
130         void* aligned = nextAlignedPointer(start, alignment);
131         void** rawLocation = cast(void**)(cast(char*)aligned - pointerSize);
132         *rawLocation = raw;
133         size_t* sizeLocation = cast(size_t*)(cast(char*)aligned - 2 * pointerSize);
134         *sizeLocation = size;
135         return aligned;
136     }
137 
138     // Returns: x, multiple of powerOfTwo, so that x >= n.
139     @nogc size_t nextMultipleOf(size_t n, size_t powerOfTwo) pure nothrow
140     {
141         // check power-of-two
142         assert( (powerOfTwo != 0) && ((powerOfTwo & (powerOfTwo - 1)) == 0));
143 
144         size_t mask = ~(powerOfTwo - 1);
145         return (n + powerOfTwo - 1) & mask;
146     }
147 }
148 
149 unittest
150 {
151     assert(nextMultipleOf(0, 4) == 0);
152     assert(nextMultipleOf(1, 4) == 4);
153     assert(nextMultipleOf(2, 4) == 4);
154     assert(nextMultipleOf(3, 4) == 4);
155     assert(nextMultipleOf(4, 4) == 4);
156     assert(nextMultipleOf(5, 4) == 8);
157 
158     {
159         void* p = alignedMalloc(23, 16);
160         assert(p !is null);
161         assert(((cast(size_t)p) & 0xf) == 0);
162 
163         alignedFree(p, 16);
164     }
165 
166     assert(alignedMalloc(0, 16) == null);
167     alignedFree(null, 16);
168 
169     {
170         int alignment = 16;
171         int* p = null;
172 
173         // check if growing keep values in place
174         foreach(int i; 0..100)
175         {
176             p = cast(int*) alignedRealloc(p, (i + 1) * int.sizeof, alignment);
177             p[i] = i;
178         }
179 
180         foreach(int i; 0..100)
181             assert(p[i] == i);
182 
183 
184         p = cast(int*) alignedRealloc(p, 0, alignment);
185         assert(p is null);
186     }
187 }
188 
189 
190 
191 /// Use throughout dplug:dsp to avoid reliance on GC.
192 /// This works like alignedRealloc except with slices as input.
193 /// You MUST use consistent alignement thoughout the lifetime of this buffer.
194 ///
195 /// Params:
196 ///    buffer Existing allocated buffer. Can be null. Input slice length is not considered.
197 ///    length desired slice length
198 ///
199 void reallocBuffer(T)(ref T[] buffer, size_t length, int alignment = 1) nothrow @nogc
200 {
201     static if (is(T == struct) && hasElaborateDestructor!T)
202     {
203         static assert(false); // struct with destructors not supported
204     }
205 
206     T* pointer = cast(T*) alignedRealloc(buffer.ptr, T.sizeof * length, alignment);
207     if (pointer is null)
208         buffer = null;
209     else
210         buffer = pointer[0..length];
211 }
212 
213 
214 /// Returns: A newly created AlignedBuffer.
215 AlignedBuffer!T makeAlignedBuffer(T)(size_t initialSize = 0, int alignment = 1) nothrow @nogc
216 {
217     return AlignedBuffer!T(initialSize, alignment);
218 }
219 
220 /// Growable array, points to a memory aligned location.
221 /// This can also work as an output range.
222 /// Bugs: make this class disappear when std.allocator is out.
223 struct AlignedBuffer(T)
224 {
225     public
226     {
227         /// Creates an aligned buffer with given initial size.
228         this(size_t initialSize, int alignment) nothrow @nogc
229         {
230             assert(alignment != 0);
231             _size = 0;
232             _allocated = 0;
233             _data = null;
234             _alignment = alignment;
235             resize(initialSize);
236         }
237 
238         ~this() nothrow @nogc
239         {
240             if (_data !is null)
241             {
242                 alignedFree(_data, _alignment);
243                 _data = null;
244                 _allocated = 0;
245             }
246         }
247 
248         @disable this(this);
249 
250         /// Returns: Length of buffer in elements.
251         size_t length() pure const nothrow @nogc
252         {
253             return _size;
254         }
255 
256         /// Returns: Length of buffer in elements.
257         alias opDollar = length;
258 
259         /// Resizes a buffer to hold $(D askedSize) elements.
260         void resize(size_t askedSize) nothrow @nogc
261         {
262             // grow only
263             if (_allocated < askedSize)
264             {
265                 size_t numBytes = askedSize * 2 * T.sizeof; // gives 2x what is asked to make room for growth
266                 _data = cast(T*)(alignedRealloc(_data, numBytes, _alignment));
267                 _allocated = askedSize * 2;
268             }
269             _size = askedSize;
270         }
271 
272         /// Pop last element
273         T popBack() nothrow @nogc
274         {
275             assert(_size > 0);
276             _size = _size - 1;
277             return _data[_size];
278         }
279 
280         /// Append an element to this buffer.
281         void pushBack(T x) nothrow @nogc
282         {
283             size_t i = _size;
284             resize(_size + 1);
285             _data[i] = x;
286         }
287 
288         // Output range support
289         alias put = pushBack;
290 
291         /// Finds an item, returns -1 if not found
292         int indexOf(T x) nothrow @nogc
293         {
294             foreach(int i; 0..cast(int)_size)
295                 if (_data[i] is x)
296                     return i;
297             return -1;
298         }
299 
300         /// Removes an item and replaces it by the last item.
301         /// Warning: this reorders the array.
302         void removeAndReplaceByLastElement(size_t index) nothrow @nogc
303         {
304             assert(index < _size);
305             _data[index] = _data[--_size];
306         }
307 
308         /// Appends another buffer to this buffer.
309         void pushBack(ref AlignedBuffer other) nothrow @nogc
310         {
311             size_t oldSize = _size;
312             resize(_size + other._size);
313             memcpy(_data + oldSize, other._data, T.sizeof * other._size);
314         }
315 
316         /// Appends a slice to this buffer.
317         void pushBack(T[] slice) nothrow @nogc
318         {
319             foreach(item; slice)
320             {
321                 pushBack(item);
322             }
323         }
324 
325         /// Returns: Raw pointer to data.
326         @property inout(T)* ptr() inout nothrow @nogc
327         {
328             return _data;
329         }
330 
331         inout(T) opIndex(size_t i) pure nothrow inout @nogc
332         {
333             return _data[i];
334         }
335 
336         T opIndexAssign(T x, size_t i) nothrow @nogc
337         {
338             return _data[i] = x;
339         }
340 
341         /// Sets size to zero.
342         void clearContents() nothrow @nogc
343         {
344             _size = 0;
345         }
346 
347         /// Returns: Whole content of the array in one slice.
348         inout(T)[] opSlice() inout nothrow @nogc
349         {
350             return opSlice(0, length());
351         }
352 
353         /// Returns: A slice of the array.
354         inout(T)[] opSlice(size_t i1, size_t i2) inout nothrow @nogc
355         {
356             return _data[i1 .. i2];
357         }
358 
359         /// Fills the buffer with the same value.
360         void fill(T x) nothrow @nogc
361         {
362             _data[0.._size] = x;
363         }
364 
365         /// Move. Give up owner ship of the data.
366         T[] releaseData() nothrow @nogc
367         {
368             T[] data = _data[0.._size];
369             assert(_alignment == 1); // else would need to be freed with alignedFree.
370             this._data = null;
371             this._size = 0;
372             this._allocated = 0;
373             this._alignment = 0;
374             return data;
375         }
376     }
377 
378     private
379     {
380         size_t _size;
381         T* _data;
382         size_t _allocated;
383         size_t _alignment;
384     }
385 }
386 
387 unittest
388 {
389     import std.range.primitives;
390     static assert(isOutputRange!(AlignedBuffer!ubyte, ubyte));
391 
392 
393     import std.random;
394     import std.algorithm.comparison;
395     int NBUF = 200;
396 
397     Xorshift32 rng;
398     rng.seed(0xBAADC0DE);
399 
400     struct box2i { int a, b, c, d; }
401     AlignedBuffer!box2i[] boxes;
402     boxes.length = NBUF;
403 
404     foreach(i; 0..NBUF)
405     {
406         boxes[i] = makeAlignedBuffer!box2i();
407     }
408 
409     foreach(j; 0..200)
410     {
411         foreach(i; 0..NBUF)
412         {
413             int previousSize = cast(int)(boxes[i].length);
414             void* previousPtr = boxes[i].ptr;
415             foreach(int k; 0..cast(int)(boxes[i].length))
416                 boxes[i][k] = box2i(k, k, k, k);
417 
418             int newSize = uniform(0, 100, rng);
419             boxes[i].resize(newSize);
420 
421             int minSize = min(previousSize, boxes[i].length);
422             void* newPtr = boxes[i].ptr;
423             foreach(int k; 0..minSize)
424             {
425                 box2i item = boxes[i][k];
426                 box2i shouldBe = box2i(k, k, k, k);
427                 assert(item == shouldBe);
428             }
429 
430             int sum = 0;
431             foreach(k; 0..newSize)
432             {
433                 box2i bb = boxes[i][k];
434                 sum += bb.a + bb.b + bb.c + bb.d;
435             }
436         }
437     }
438 
439     foreach(i; 0..NBUF)
440         boxes[i].destroy();
441 
442     {
443         auto buf = makeAlignedBuffer!int;
444         enum N = 10;
445         buf.resize(N);
446         foreach(i ; 0..N)
447             buf[i] = i;
448 
449         foreach(i ; 0..N)
450             assert(buf[i] == i);
451     }
452 }