1 /**
2 This is helpful to break dependency upon dplug:core.
3 
4 Copyright: Guillaume Piolats 2022.
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module audioformats.internals;
8 
9 
10 import core.stdc.stdlib: malloc, free, realloc;
11 import core.stdc.string: memcpy;
12 import core.exception: onOutOfMemoryErrorNoGC;
13 import std.conv: emplace;
14 import std.traits;
15 
16 /// The only kind of exception thrown by audio-formats.
17 /// Those must be catch and destroyed with `destroyAudioFormatException`.
18 class AudioFormatsException : Exception
19 {
20     public nothrow @nogc
21     {
22         @safe pure this(string message,
23                         string file =__FILE__,
24                         size_t line = __LINE__,
25                         Throwable next = null)
26         {
27             super(message, file, line, next);
28         }
29 
30         ~this()
31         {}
32     }
33 }
34 
35 
36 //
37 // Constructing and destroying without the GC.
38 //
39 
40 /// Allocates and construct a struct or class object.
41 /// Returns: Newly allocated object.
42 auto mallocNew(T, Args...)(Args args)
43 {
44     static if (is(T == class))
45         immutable size_t allocSize = __traits(classInstanceSize, T);
46     else
47         immutable size_t allocSize = T.sizeof;
48 
49     void* rawMemory = malloc(allocSize);
50     if (!rawMemory)
51         onOutOfMemoryErrorNoGC();
52 
53     static if (is(T == class))
54     {
55         T obj = emplace!T(rawMemory[0 .. allocSize], args);
56     }
57     else
58     {
59         T* obj = cast(T*)rawMemory;
60         emplace!T(obj, args);
61     }
62 
63     return obj;
64 }
65 
66 /// Destroys and frees a class object created with $(D mallocEmplace).
67 void destroyFree(T)(T p) if (is(T == class))
68 {
69     if (p !is null)
70     {
71         destroyNoGC(p);
72         free(cast(void*)p);
73     }
74 }
75 
76 /// Destroys and frees an interface object created with $(D mallocEmplace).
77 void destroyFree(T)(T p) if (is(T == interface))
78 {
79     if (p !is null)
80     {
81         void* here = cast(void*)(cast(Object)p);
82         destroyNoGC(p);
83         free(cast(void*)here);
84     }
85 }
86 
87 /// Destroys and frees a non-class object created with $(D mallocEmplace).
88 void destroyFree(T)(T* p) if (!is(T == class))
89 {
90     if (p !is null)
91     {
92         destroyNoGC(p);
93         free(cast(void*)p);
94     }
95 }
96 
97 
98 unittest
99 {
100     class A
101     {
102         int _i;
103         this(int i)
104         {
105             _i = i;
106         }
107     }
108 
109     struct B
110     {
111         int i;
112     }
113 
114     void testMallocEmplace()
115     {
116         A a = mallocNew!A(4);
117         destroyFree(a);
118 
119         B* b = mallocNew!B(5);
120         destroyFree(b);
121     }
122 
123     testMallocEmplace();
124 }
125 
126 
127 //
128 // Optimistic .destroy, which is @nogc nothrow by breaking the type-system
129 //
130 
131 // for classes
132 void destroyNoGC(T)(T x) nothrow @nogc if (is(T == class) || is(T == interface))
133 {
134     assumeNothrowNoGC(
135                       (T x)
136                       {
137                         return destroy(x);
138                       })(x);
139 }
140 
141 // for struct
142 void destroyNoGC(T)(ref T obj) nothrow @nogc if (is(T == struct))
143 {
144     assumeNothrowNoGC(
145                       (ref T x)
146                       {
147                         return destroy(x);
148                       })(obj);
149 }
150 
151 void destroyNoGC(T)(ref T obj) nothrow @nogc
152 if (!is(T == struct) && !is(T == class) && !is(T == interface))
153 {
154     assumeNothrowNoGC(
155                       (ref T x)
156                       {
157                         return destroy(x);
158                       })(obj);
159 }
160 
161 
162 auto assumeNothrowNoGC(T) (T t)
163 {
164     static if (isFunctionPointer!T || isDelegate!T)
165     {
166         enum attrs = functionAttributes!T | FunctionAttribute.nogc | FunctionAttribute.nothrow_;
167         return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t;
168     }
169     else
170         static assert(false);
171 }
172 
173 
174 void reallocBuffer(T)(ref T[] buffer, size_t length) nothrow @nogc
175 {
176     static if (is(T == struct) && hasElaborateDestructor!T)
177     {
178         static assert(false); // struct with destructors not supported
179     }
180 
181     /// Size 0 is special-case to free the slice.
182     if (length == 0)
183     {
184         free(buffer.ptr);
185         buffer = null;
186         return;
187     }
188 
189     T* pointer = cast(T*) realloc(buffer.ptr, T.sizeof * length);
190     if (pointer is null)
191         buffer = null; // alignment 1 can still return null
192     else
193         buffer = pointer[0..length];
194 }
195 
196 
197 alias CString = CStringImpl!char;
198 alias CString16 = CStringImpl!wchar;
199 
200 /// Zero-terminated C string, to replace toStringz and toUTF16z
201 struct CStringImpl(CharType) if (is(CharType: char) || is(CharType: wchar))
202 {
203 public:
204 nothrow:
205 @nogc:
206 
207     const(CharType)* storage = null;
208     alias storage this;
209 
210 
211     this(const(CharType)[] s)
212     {
213         // Always copy. We can't assume anything about the input.
214         size_t len = s.length;
215         CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof);
216         buffer[0..len] = s[0..len];
217         buffer[len] = '\0';
218         storage = buffer;
219         wasAllocated = true;
220     }
221 
222     // The constructor taking immutable can safely assume that such memory
223     // has been allocated by the GC or malloc, or an allocator that align
224     // pointer on at least 4 bytes.
225     this(immutable(CharType)[] s)
226     {
227         // Same optimizations that for toStringz
228         if (s.length == 0)
229         {
230             enum emptyString = cast(CharType[])"";
231             storage = emptyString.ptr;
232             return;
233         }
234 
235         /* Peek past end of s[], if it's 0, no conversion necessary.
236         * Note that the compiler will put a 0 past the end of static
237         * strings, and the storage allocator will put a 0 past the end
238         * of newly allocated char[]'s.
239         */
240         const(CharType)* p = s.ptr + s.length;
241         // Is p dereferenceable? A simple test: if the p points to an
242         // address multiple of 4, then conservatively assume the pointer
243         // might be pointing to another block of memory, which might be
244         // unreadable. Otherwise, it's definitely pointing to valid
245         // memory.
246         if ((cast(size_t) p & 3) && *p == 0)
247         {
248             storage = s.ptr;
249             return;
250         }
251 
252         size_t len = s.length;
253         CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof);
254         buffer[0..len] = s[0..len];
255         buffer[len] = '\0';
256         storage = buffer;
257         wasAllocated = true;
258     }
259 
260     ~this()
261     {
262         if (wasAllocated)
263             free(cast(void*)storage);
264     }
265 
266     @disable this(this);
267 
268 private:
269     bool wasAllocated = false;
270 }
271 
272 
273 /// Duplicates a slice with `malloc`. Equivalent to `.dup`
274 /// Has to be cleaned-up with `free(slice.ptr)` or `freeSlice(slice)`.
275 T[] mallocDup(T)(const(T)[] slice) nothrow @nogc if (!is(T == struct))
276 {
277     T[] copy = mallocSliceNoInit!T(slice.length);
278     memcpy(copy.ptr, slice.ptr, slice.length * T.sizeof);
279     return copy;
280 }
281 
282 /// Allocates a slice with `malloc`, but does not initialize the content.
283 T[] mallocSliceNoInit(T)(size_t count) nothrow @nogc
284 {
285     T* p = cast(T*) malloc(count * T.sizeof);
286     return p[0..count];
287 }
288 
289 
290 /// Kind of a std::vector replacement.
291 /// Grow-only array, points to a (optionally aligned) memory location.
292 /// This can also work as an output range.
293 /// `Vec` is designed to work even when uninitialized, without `makeVec`.
294 struct Vec(T)
295 {
296 nothrow:
297 @nogc:
298     public
299     {
300         /// Creates an aligned buffer with given initial size.
301         this(size_t initialSize)
302         {
303             _size = 0;
304             _allocated = 0;
305             _data = null;
306             resize(initialSize);
307         }
308 
309         ~this()
310         {
311             if (_data !is null)
312             {
313                 free(_data);
314                 _data = null;
315                 _allocated = 0;
316             }
317         }
318 
319         @disable this(this);
320 
321         /// Returns: Length of buffer in elements.
322         size_t length() pure const
323         {
324             return _size;
325         }
326 
327         /// Returns: Length of buffer in elements.
328         alias opDollar = length;
329 
330         /// Resizes a buffer to hold $(D askedSize) elements.
331         void resize(size_t askedSize)
332         {
333             // grow only
334             if (_allocated < askedSize)
335             {
336                 size_t numBytes = askedSize * 2 * T.sizeof; // gives 2x what is asked to make room for growth
337                 _data = cast(T*)(realloc(_data, numBytes));
338                 _allocated = askedSize * 2;
339             }
340             _size = askedSize;
341         }
342 
343         /// Pop last element
344         T popBack()
345         {
346             assert(_size > 0);
347             _size = _size - 1;
348             return _data[_size];
349         }
350 
351         /// Append an element to this buffer.
352         void pushBack(T x)
353         {
354             size_t i = _size;
355             resize(_size + 1);
356             _data[i] = x;
357         }
358 
359         // DMD 2.088 deprecates the old D1-operators
360         static if (__VERSION__ >= 2088)
361         {
362             ///ditto
363             void opOpAssign(string op)(T x) if (op == "~")
364             {
365                 pushBack(x);
366             }
367         }
368         else
369         {
370             ///ditto
371             void opCatAssign(T x)
372             {
373                 pushBack(x);
374             }
375         }
376 
377         // Output range support
378         alias put = pushBack;
379 
380         /// Finds an item, returns -1 if not found
381         int indexOf(T x)
382         {
383             foreach(int i; 0..cast(int)_size)
384                 if (_data[i] is x)
385                     return i;
386             return -1;
387         }
388 
389         /// Removes an item and replaces it by the last item.
390         /// Warning: this reorders the array.
391         void removeAndReplaceByLastElement(size_t index)
392         {
393             assert(index < _size);
394             _data[index] = _data[--_size];
395         }
396 
397         /// Removes an item and shift the rest of the array to front by 1.
398         /// Warning: O(N) complexity.
399         void removeAndShiftRestOfArray(size_t index)
400         {
401             assert(index < _size);
402             for (; index + 1 < _size; ++index)
403                 _data[index] = _data[index+1];
404         }
405 
406         /// Appends another buffer to this buffer.
407         void pushBack(ref Vec other)
408         {
409             size_t oldSize = _size;
410             resize(_size + other._size);
411             memcpy(_data + oldSize, other._data, T.sizeof * other._size);
412         }
413 
414         /// Appends a slice to this buffer.
415         /// `slice` should not belong to the same buffer _data.
416         void pushBack(T[] slice)
417         {
418             size_t oldSize = _size;
419             size_t newSize = _size + slice.length;
420             resize(newSize);
421             for (size_t n = 0; n < slice.length; ++n)
422                 _data[oldSize + n] = slice[n];
423         }
424 
425         /// Returns: Raw pointer to data.
426         @property inout(T)* ptr() inout
427         {
428             return _data;
429         }
430 
431         /// Returns: n-th element.
432         ref inout(T) opIndex(size_t i) pure inout
433         {
434             return _data[i];
435         }
436 
437         T opIndexAssign(T x, size_t i)
438         {
439             return _data[i] = x;
440         }
441 
442         /// Sets size to zero, but keeps allocated buffers.
443         void clearContents()
444         {
445             _size = 0;
446         }
447 
448         /// Returns: Whole content of the array in one slice.
449         inout(T)[] opSlice() inout
450         {
451             return opSlice(0, length());
452         }
453 
454         /// Returns: A slice of the array.
455         inout(T)[] opSlice(size_t i1, size_t i2) inout
456         {
457             return _data[i1 .. i2];
458         }
459 
460         /// Fills the buffer with the same value.
461         void fill(T x)
462         {
463             _data[0.._size] = x;
464         }
465 
466         /// Move. Give up owner ship of the data.
467         T[] releaseData()
468         {
469             T[] data = _data[0.._size];
470             this._data = null;
471             this._size = 0;
472             this._allocated = 0;
473             return data;
474         }
475     }
476 
477     private
478     {
479         size_t _size = 0;
480         T* _data = null;
481         size_t _allocated = 0;
482     }
483 }