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 }