1 /** 2 * Fast buffer implementation. 3 * 4 * Authors: 5 * $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise) 6 * 7 * Copyright: 8 * © 2015 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise) 9 * 10 * License: 11 * $(LINK2 http://www.gnu.org/licenses/gpl-3.0, GNU General Public License 3.0) 12 */ 13 module fast.buffer; nothrow 14 15 import core.stdc.stdint; 16 import core.stdc.stdlib; 17 import std.range; 18 import core.exception; 19 20 21 enum allocaLimit = 2048; 22 23 24 /******************************************************************************* 25 * 26 * Dynamic array using `malloc`, `realloc` and `free` under the hood. Note that 27 * memory will be released on scope exit. 28 * 29 **************************************/ 30 struct RaiiArray(T) 31 { 32 private: 33 34 T* m_ptr; 35 size_t m_capacity; 36 37 38 public: 39 40 nothrow 41 this(size_t capacity) 42 { 43 if (capacity) 44 { 45 m_ptr = cast(T*) malloc(capacity); 46 if (m_ptr is null) 47 onOutOfMemoryError(); 48 m_capacity = capacity; 49 } 50 } 51 52 53 nothrow @nogc 54 ~this() 55 { 56 if (m_ptr !is null) 57 free(m_ptr); 58 } 59 60 61 @safe pure nothrow @nogc 62 @property inout(T)* ptr() inout 63 { 64 return m_ptr; 65 } 66 67 68 @safe pure nothrow @nogc 69 @property size_t capacity() const 70 { 71 return m_capacity; 72 } 73 74 75 nothrow 76 @property void capacity(size_t value) 77 { 78 if (value != 0) 79 { 80 if (T* ptrNew = cast(T*) realloc(m_ptr, value)) 81 m_ptr = ptrNew; 82 else onOutOfMemoryError(); 83 } 84 else if (m_ptr) 85 { 86 free(m_ptr); 87 m_ptr = null; 88 } 89 m_capacity = value; 90 } 91 92 93 alias length = capacity; 94 95 96 mixin Slicing; 97 mixin CapacityTools; 98 } 99 100 101 /******************************************************************************* 102 * 103 * Fixed maximum number of items on the stack. Memory is a static stack buffer. 104 * This buffer can be filled up and cleared for reuse. 105 * 106 **************************************/ 107 struct LimitedScopeBuffer(T, size_t n) 108 { 109 private: 110 111 T[n] m_data; 112 size_t m_used; 113 114 115 public: 116 117 @safe pure nothrow @nogc 118 @property inout(T)* ptr() inout 119 { 120 return m_data.ptr; 121 } 122 123 124 @safe pure nothrow @nogc 125 @property size_t length() const 126 { 127 return m_used; 128 } 129 130 @safe pure nothrow @nogc 131 @property void length(size_t value) 132 in 133 { 134 assert( value <= n ); 135 } 136 body 137 { 138 m_used = value; 139 } 140 141 142 @safe pure nothrow @nogc 143 inout(T)[] opSlice() inout 144 { 145 return m_data[0 .. m_used]; 146 } 147 } 148 149 150 struct TempBuffer(T) 151 { 152 T[] slice; 153 bool callFree; 154 155 @disable this(this); 156 157 ~this() nothrow 158 { 159 if (this.callFree) 160 free(this.slice.ptr); 161 } 162 163 T[] opSlice() @safe pure nothrow { return this.slice[]; } 164 T[] opSlice(size_t a, size_t b) @safe pure nothrow { return this.slice[a .. b]; } 165 T[] opSliceAssign(const(T)[] value, size_t a, size_t b) @safe pure nothrow { return this.slice[a .. b] = value; } 166 ref T opIndex(size_t idx) @safe pure nothrow { return this.slice[idx]; } 167 @property size_t size() @safe pure nothrow { return T.sizeof * this.slice.length; } 168 @property size_t length() @safe pure nothrow { return this.slice.length; } 169 alias opDollar = length; 170 @property T* ptr() @trusted pure nothrow { return this.slice.ptr; } // must use .ptr here for zero length strings 171 alias ptr this; 172 173 auto makeOutputRange() 174 { 175 struct OutputRange 176 { 177 T* ptr; 178 size_t idx; 179 180 void put(T)(auto ref T t) { ptr[idx++] = t; } 181 T[] opSlice() pure nothrow { return ptr[0 .. idx]; } 182 } 183 return OutputRange(this.slice.ptr, 0); 184 } 185 } 186 187 188 TempBuffer!T tempBuffer(T, alias length, size_t allocaLimit = .allocaLimit) 189 (void* buffer = (T.sizeof * length <= allocaLimit) ? alloca(T.sizeof * length) : null) 190 { 191 return TempBuffer!T((cast(T*) ( 192 buffer is null 193 ? malloc(T.sizeof * length) 194 : buffer))[0 .. length], 195 buffer is null); 196 } 197 198 199 /******************************************************************************* 200 * 201 * Returns a structure to your stack that contains a buffer of $(D bytes) size. 202 * Memory is allocated by calling `.alloc!T(count)` on it in order to get 203 * `count` elements of type `T`. The return value will be a RAII structure 204 * that releases the memory back to the stack buffer upon destruction, so it can 205 * be reused. The pointer within that RAII structure is aligned to 206 * `T.alignof`. If the internal buffer isn't enough to fulfill the request 207 * including padding from alignment, then `malloc()` is used instead. 208 * 209 * Warning: 210 * Always keep the return value of `.alloc()` around on your stack until 211 * you are done with its contents. Never pass it directly into functions as 212 * arguments! 213 * 214 * Params: 215 * bytes = The size of the buffer on the stack. 216 * 217 * Returns: 218 * A stack buffer allocator. 219 * 220 **************************************/ 221 auto stackBuffer(size_t bytes)() @trusted pure 222 { 223 // All that remains of this after inlining is a stack pointer decrement and 224 // a mov instruction for the `null`. 225 StackBuffer!bytes result = void; 226 result.last = cast(StackBufferEntry!void*) &result.last; 227 result.sentinel = null; 228 return result; 229 } 230 231 232 auto asOutputRange(T)(T* t) @safe pure 233 { 234 struct PointerRange 235 { 236 private: 237 238 T* start; 239 T* ptr; 240 241 public: 242 243 void put()(auto ref const(T) t) pure 244 { 245 *this.ptr++ = t; 246 } 247 248 T[] opSlice() pure 249 { 250 return this.start[0 .. this.ptr - this.start]; 251 } 252 } 253 static assert(isOutputRange!(PointerRange, T)); 254 return PointerRange(t, t); 255 } 256 257 258 enum bufferArg(alias size)() 259 { 260 return "((size <= allocaLimit) ? alloca(size) : null)"; 261 } 262 263 264 265 package: 266 267 struct StackBuffer(size_t bytes) 268 { 269 private: 270 271 void[bytes] space = void; 272 StackBufferEntry!void* last; 273 void* sentinel; 274 275 public: 276 277 @disable this(this); 278 279 @trusted 280 StackBufferEntry!T alloc(T)(size_t howMany) 281 { 282 enum max = size_t.max / T.sizeof; 283 alias SBE = StackBufferEntry!T; 284 T* target = cast(T*) (cast(uintptr_t) this.last.ptr / T.alignof * T.alignof); 285 if (target > this.space.ptr && cast(uintptr_t) (target - cast(T*) this.space.ptr) >= howMany) 286 return SBE(target - howMany, this.last); 287 else 288 // TODO: Respect alignment here as well by padding. Optionally also embed a length in the heap block, so we can provide slicing of the whole thing. 289 return SBE(howMany <= max ? cast(T*) malloc(T.sizeof * howMany) : null); 290 } 291 } 292 293 struct StackBufferEntry(T) 294 { 295 private: 296 297 StackBufferEntry!void* prev; 298 299 this(T* ptr) pure { this.ptr = ptr; } 300 301 this(T* ptr, ref StackBufferEntry!void* last) pure 302 { 303 this.ptr = ptr; 304 this.prev = last; 305 last = cast(StackBufferEntry!void*) &this; 306 } 307 308 309 public: 310 311 T* ptr; 312 313 static if (!is(T == void)) 314 { 315 @disable this(this); 316 317 ~this() @trusted 318 { 319 if (this.prev) 320 { 321 StackBufferEntry!void* it = this.prev; 322 while (it.prev) it = it.prev; 323 auto last = cast(StackBufferEntry!void**) &prev.ptr; 324 *last = this.prev; 325 } 326 else free(this.ptr); 327 } 328 329 @system pure nothrow @nogc 330 ref inout(T) opIndex(size_t idx) inout 331 { 332 return ptr[idx]; 333 } 334 335 @system pure nothrow @nogc 336 inout(T)[] opSlice(size_t a, size_t b) inout 337 { 338 return ptr[a .. b]; 339 } 340 341 @safe pure nothrow @nogc 342 @property auto range() 343 { 344 return ptr.asOutputRange(); 345 } 346 } 347 } 348 349 350 351 private: 352 353 mixin template Slicing() 354 { 355 public 356 { 357 @nogc pure nothrow 358 ref inout(T) opIndex(size_t idx) inout 359 in 360 { 361 assert(idx < length); 362 } 363 body 364 { 365 return ptr[idx]; 366 } 367 368 369 @nogc pure nothrow 370 inout(T)[] opSlice() inout 371 { 372 return ptr[0 .. length]; 373 } 374 375 376 @nogc pure nothrow 377 inout(T)[] opSlice(size_t a, size_t b) inout 378 in 379 { 380 assert(a <= b && b <= length); 381 } 382 body 383 { 384 return ptr[a .. b]; 385 } 386 } 387 } 388 389 390 mixin template CapacityTools() 391 { 392 public 393 { 394 nothrow 395 void capacityNeeded(size_t c) 396 { 397 if (capacity < c) 398 capacity = c; 399 } 400 } 401 }