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 }