The OpenD Programming Language

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