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 }