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