1 /** 2 * Copyright: Copyright Auburn Sounds 2015-2016 3 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 4 * Authors: Guillaume Piolat 5 */ 6 module dplug.core.alignedbuffer; 7 8 import std.traits: hasElaborateDestructor; 9 10 import core.stdc.stdlib: malloc, free, realloc; 11 import core.stdc.string: memcpy; 12 13 import core.exception; 14 15 16 // This module deals with aligned memory 17 18 19 /// Allocates an aligned memory chunk. 20 /// Functionally equivalent to Visual C++ _aligned_malloc. 21 /// Do not mix allocations with different alignment. 22 void* alignedMalloc(size_t size, size_t alignment) nothrow @nogc 23 { 24 assert(alignment != 0); 25 26 // Short-cut and use the C allocator to avoid overhead if no alignment 27 if (alignment == 1) 28 return malloc(size); 29 30 if (size == 0) 31 return null; 32 33 size_t request = requestedSize(size, alignment); 34 void* raw = malloc(request); 35 36 if (request > 0 && raw == null) // malloc(0) can validly return anything 37 onOutOfMemoryError(); 38 39 return storeRawPointerPlusSizeAndReturnAligned(raw, size, alignment); 40 } 41 42 /// Frees aligned memory allocated by alignedMalloc or alignedRealloc. 43 /// Functionally equivalent to Visual C++ _aligned_free. 44 /// Do not mix allocations with different alignment. 45 void alignedFree(void* aligned, size_t alignment) nothrow @nogc 46 { 47 assert(alignment != 0); 48 49 // Short-cut and use the C allocator to avoid overhead if no alignment 50 if (alignment == 1) 51 return free(aligned); 52 53 // support for free(NULL) 54 if (aligned is null) 55 return; 56 57 void** rawLocation = cast(void**)(cast(char*)aligned - size_t.sizeof); 58 free(*rawLocation); 59 } 60 61 /// Reallocates an aligned memory chunk allocated by alignedMalloc or alignedRealloc. 62 /// Functionally equivalent to Visual C++ _aligned_realloc. 63 /// Do not mix allocations with different alignment. 64 @nogc void* alignedRealloc(void* aligned, size_t size, size_t alignment) nothrow 65 { 66 // If you fail here, it can mean you've used an uninitialized AlignedBuffer. 67 assert(alignment != 0); 68 69 // Short-cut and use the C allocator to avoid overhead if no alignment 70 if (alignment == 1) 71 return realloc(aligned, size); 72 73 if (aligned is null) 74 return alignedMalloc(size, alignment); 75 76 if (size == 0) 77 { 78 alignedFree(aligned, alignment); 79 return null; 80 } 81 82 size_t previousSize = *cast(size_t*)(cast(char*)aligned - size_t.sizeof * 2); 83 84 85 void* raw = *cast(void**)(cast(char*)aligned - size_t.sizeof); 86 size_t request = requestedSize(size, alignment); 87 88 // Heuristic: if a requested size is within 50% to 100% of what is already allocated 89 // then exit with the same pointer 90 if ( (previousSize < request * 4) && (request <= previousSize) ) 91 return aligned; 92 93 void* newRaw = malloc(request); 94 static if( __VERSION__ > 2067 ) // onOutOfMemoryError wasn't nothrow before July 2014 95 { 96 if (request > 0 && newRaw == null) // realloc(0) can validly return anything 97 onOutOfMemoryError(); 98 } 99 100 void* newAligned = storeRawPointerPlusSizeAndReturnAligned(newRaw, request, alignment); 101 size_t minSize = size < previousSize ? size : previousSize; 102 memcpy(newAligned, aligned, minSize); 103 104 // Free previous data 105 alignedFree(aligned, alignment); 106 return newAligned; 107 } 108 109 private 110 { 111 /// Returns: next pointer aligned with alignment bytes. 112 void* nextAlignedPointer(void* start, size_t alignment) pure nothrow @nogc 113 { 114 return cast(void*)nextMultipleOf(cast(size_t)(start), alignment); 115 } 116 117 // Returns number of bytes to actually allocate when asking 118 // for a particular alignement 119 @nogc size_t requestedSize(size_t askedSize, size_t alignment) pure nothrow 120 { 121 enum size_t pointerSize = size_t.sizeof; 122 return askedSize + alignment - 1 + pointerSize * 2; 123 } 124 125 // Store pointer given my malloc, and size in bytes initially requested (alignedRealloc needs it) 126 @nogc void* storeRawPointerPlusSizeAndReturnAligned(void* raw, size_t size, size_t alignment) nothrow 127 { 128 enum size_t pointerSize = size_t.sizeof; 129 char* start = cast(char*)raw + pointerSize * 2; 130 void* aligned = nextAlignedPointer(start, alignment); 131 void** rawLocation = cast(void**)(cast(char*)aligned - pointerSize); 132 *rawLocation = raw; 133 size_t* sizeLocation = cast(size_t*)(cast(char*)aligned - 2 * pointerSize); 134 *sizeLocation = size; 135 return aligned; 136 } 137 138 // Returns: x, multiple of powerOfTwo, so that x >= n. 139 @nogc size_t nextMultipleOf(size_t n, size_t powerOfTwo) pure nothrow 140 { 141 // check power-of-two 142 assert( (powerOfTwo != 0) && ((powerOfTwo & (powerOfTwo - 1)) == 0)); 143 144 size_t mask = ~(powerOfTwo - 1); 145 return (n + powerOfTwo - 1) & mask; 146 } 147 } 148 149 unittest 150 { 151 assert(nextMultipleOf(0, 4) == 0); 152 assert(nextMultipleOf(1, 4) == 4); 153 assert(nextMultipleOf(2, 4) == 4); 154 assert(nextMultipleOf(3, 4) == 4); 155 assert(nextMultipleOf(4, 4) == 4); 156 assert(nextMultipleOf(5, 4) == 8); 157 158 { 159 void* p = alignedMalloc(23, 16); 160 assert(p !is null); 161 assert(((cast(size_t)p) & 0xf) == 0); 162 163 alignedFree(p, 16); 164 } 165 166 assert(alignedMalloc(0, 16) == null); 167 alignedFree(null, 16); 168 169 { 170 int alignment = 16; 171 int* p = null; 172 173 // check if growing keep values in place 174 foreach(int i; 0..100) 175 { 176 p = cast(int*) alignedRealloc(p, (i + 1) * int.sizeof, alignment); 177 p[i] = i; 178 } 179 180 foreach(int i; 0..100) 181 assert(p[i] == i); 182 183 184 p = cast(int*) alignedRealloc(p, 0, alignment); 185 assert(p is null); 186 } 187 } 188 189 190 191 /// Use throughout dplug:dsp to avoid reliance on GC. 192 /// This works like alignedRealloc except with slices as input. 193 /// You MUST use consistent alignement thoughout the lifetime of this buffer. 194 /// 195 /// Params: 196 /// buffer Existing allocated buffer. Can be null. Input slice length is not considered. 197 /// length desired slice length 198 /// 199 void reallocBuffer(T)(ref T[] buffer, size_t length, int alignment = 1) nothrow @nogc 200 { 201 static if (is(T == struct) && hasElaborateDestructor!T) 202 { 203 static assert(false); // struct with destructors not supported 204 } 205 206 T* pointer = cast(T*) alignedRealloc(buffer.ptr, T.sizeof * length, alignment); 207 if (pointer is null) 208 buffer = null; 209 else 210 buffer = pointer[0..length]; 211 } 212 213 214 /// Returns: A newly created AlignedBuffer. 215 AlignedBuffer!T makeAlignedBuffer(T)(size_t initialSize = 0, int alignment = 1) nothrow @nogc 216 { 217 return AlignedBuffer!T(initialSize, alignment); 218 } 219 220 /// Growable array, points to a memory aligned location. 221 /// This can also work as an output range. 222 /// Bugs: make this class disappear when std.allocator is out. 223 struct AlignedBuffer(T) 224 { 225 public 226 { 227 /// Creates an aligned buffer with given initial size. 228 this(size_t initialSize, int alignment) nothrow @nogc 229 { 230 assert(alignment != 0); 231 _size = 0; 232 _allocated = 0; 233 _data = null; 234 _alignment = alignment; 235 resize(initialSize); 236 } 237 238 ~this() nothrow @nogc 239 { 240 if (_data !is null) 241 { 242 alignedFree(_data, _alignment); 243 _data = null; 244 _allocated = 0; 245 } 246 } 247 248 @disable this(this); 249 250 /// Returns: Length of buffer in elements. 251 size_t length() pure const nothrow @nogc 252 { 253 return _size; 254 } 255 256 /// Returns: Length of buffer in elements. 257 alias opDollar = length; 258 259 /// Resizes a buffer to hold $(D askedSize) elements. 260 void resize(size_t askedSize) nothrow @nogc 261 { 262 // grow only 263 if (_allocated < askedSize) 264 { 265 size_t numBytes = askedSize * 2 * T.sizeof; // gives 2x what is asked to make room for growth 266 _data = cast(T*)(alignedRealloc(_data, numBytes, _alignment)); 267 _allocated = askedSize * 2; 268 } 269 _size = askedSize; 270 } 271 272 /// Pop last element 273 T popBack() nothrow @nogc 274 { 275 assert(_size > 0); 276 _size = _size - 1; 277 return _data[_size]; 278 } 279 280 /// Append an element to this buffer. 281 void pushBack(T x) nothrow @nogc 282 { 283 size_t i = _size; 284 resize(_size + 1); 285 _data[i] = x; 286 } 287 288 // Output range support 289 alias put = pushBack; 290 291 /// Finds an item, returns -1 if not found 292 int indexOf(T x) nothrow @nogc 293 { 294 foreach(int i; 0..cast(int)_size) 295 if (_data[i] is x) 296 return i; 297 return -1; 298 } 299 300 /// Removes an item and replaces it by the last item. 301 /// Warning: this reorders the array. 302 void removeAndReplaceByLastElement(size_t index) nothrow @nogc 303 { 304 assert(index < _size); 305 _data[index] = _data[--_size]; 306 } 307 308 /// Appends another buffer to this buffer. 309 void pushBack(ref AlignedBuffer other) nothrow @nogc 310 { 311 size_t oldSize = _size; 312 resize(_size + other._size); 313 memcpy(_data + oldSize, other._data, T.sizeof * other._size); 314 } 315 316 /// Appends a slice to this buffer. 317 void pushBack(T[] slice) nothrow @nogc 318 { 319 foreach(item; slice) 320 { 321 pushBack(item); 322 } 323 } 324 325 /// Returns: Raw pointer to data. 326 @property inout(T)* ptr() inout nothrow @nogc 327 { 328 return _data; 329 } 330 331 inout(T) opIndex(size_t i) pure nothrow inout @nogc 332 { 333 return _data[i]; 334 } 335 336 T opIndexAssign(T x, size_t i) nothrow @nogc 337 { 338 return _data[i] = x; 339 } 340 341 /// Sets size to zero. 342 void clearContents() nothrow @nogc 343 { 344 _size = 0; 345 } 346 347 /// Returns: Whole content of the array in one slice. 348 inout(T)[] opSlice() inout nothrow @nogc 349 { 350 return opSlice(0, length()); 351 } 352 353 /// Returns: A slice of the array. 354 inout(T)[] opSlice(size_t i1, size_t i2) inout nothrow @nogc 355 { 356 return _data[i1 .. i2]; 357 } 358 359 /// Fills the buffer with the same value. 360 void fill(T x) nothrow @nogc 361 { 362 _data[0.._size] = x; 363 } 364 365 /// Move. Give up owner ship of the data. 366 T[] releaseData() nothrow @nogc 367 { 368 T[] data = _data[0.._size]; 369 assert(_alignment == 1); // else would need to be freed with alignedFree. 370 this._data = null; 371 this._size = 0; 372 this._allocated = 0; 373 this._alignment = 0; 374 return data; 375 } 376 } 377 378 private 379 { 380 size_t _size; 381 T* _data; 382 size_t _allocated; 383 size_t _alignment; 384 } 385 } 386 387 unittest 388 { 389 import std.range.primitives; 390 static assert(isOutputRange!(AlignedBuffer!ubyte, ubyte)); 391 392 393 import std.random; 394 import std.algorithm.comparison; 395 int NBUF = 200; 396 397 Xorshift32 rng; 398 rng.seed(0xBAADC0DE); 399 400 struct box2i { int a, b, c, d; } 401 AlignedBuffer!box2i[] boxes; 402 boxes.length = NBUF; 403 404 foreach(i; 0..NBUF) 405 { 406 boxes[i] = makeAlignedBuffer!box2i(); 407 } 408 409 foreach(j; 0..200) 410 { 411 foreach(i; 0..NBUF) 412 { 413 int previousSize = cast(int)(boxes[i].length); 414 void* previousPtr = boxes[i].ptr; 415 foreach(int k; 0..cast(int)(boxes[i].length)) 416 boxes[i][k] = box2i(k, k, k, k); 417 418 int newSize = uniform(0, 100, rng); 419 boxes[i].resize(newSize); 420 421 int minSize = min(previousSize, boxes[i].length); 422 void* newPtr = boxes[i].ptr; 423 foreach(int k; 0..minSize) 424 { 425 box2i item = boxes[i][k]; 426 box2i shouldBe = box2i(k, k, k, k); 427 assert(item == shouldBe); 428 } 429 430 int sum = 0; 431 foreach(k; 0..newSize) 432 { 433 box2i bb = boxes[i][k]; 434 sum += bb.a + bb.b + bb.c + bb.d; 435 } 436 } 437 } 438 439 foreach(i; 0..NBUF) 440 boxes[i].destroy(); 441 442 { 443 auto buf = makeAlignedBuffer!int; 444 enum N = 10; 445 buf.resize(N); 446 foreach(i ; 0..N) 447 buf[i] = i; 448 449 foreach(i ; 0..N) 450 assert(buf[i] == i); 451 } 452 }