1 /** 2 * Fast buffer implementation. 3 * 4 * Authors: 5 * $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise) 6 * 7 * Copyright: 8 * © 2015-2023 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise), $(LINK2 mailto:etienne@cimons.com, Etienne Cimon) 9 * 10 * License: 11 * $(LINK2 https://mit-license.org/, The MIT License (MIT)) 12 */ 13 module fast.buffer; 14 nothrow 15 16 //import core.stdc.stdint; 17 //import core.stdc.stdlib; 18 import std.range; 19 20 enum allocaLimit = 2048; 21 22 /******************************************************************************* 23 * 24 * Fixed maximum number of items on the stack. Memory is a static stack buffer. 25 * This buffer can be filled up and cleared for reuse. 26 * 27 **************************************/ 28 struct LimitedScopeBuffer(T, size_t n) 29 { 30 private: 31 32 T[n] m_data; 33 size_t m_used; 34 35 public: 36 37 @safe pure nothrow @nogc 38 @property inout(T)* ptr() inout 39 { 40 return m_data.ptr; 41 } 42 43 @safe pure nothrow @nogc 44 @property size_t length() const 45 { 46 return m_used; 47 } 48 49 @safe pure nothrow @nogc 50 @property void length(size_t value) 51 in 52 { 53 assert(value <= n); 54 } 55 body 56 { 57 m_used = value; 58 } 59 60 @safe pure nothrow @nogc 61 inout(T)[] opSlice() inout 62 { 63 return m_data[0 .. m_used]; 64 } 65 } 66 67 struct TempBuffer(T) 68 { 69 T[] slice; 70 bool callFree; 71 72 @disable this(this); 73 74 ~this() nothrow 75 { 76 if (this.callFree) 77 free(this.slice.ptr); 78 } 79 80 T[] opSlice() @safe pure nothrow 81 { 82 return this.slice[]; 83 } 84 85 T[] opSlice(size_t a, size_t b) @safe pure nothrow 86 { 87 return this.slice[a .. b]; 88 } 89 90 T[] opSliceAssign(const(T)[] value, size_t a, size_t b) @safe pure nothrow 91 { 92 return this.slice[a .. b] = value; 93 } 94 95 ref T opIndex(size_t idx) @safe pure nothrow 96 { 97 return this.slice[idx]; 98 } 99 100 @property size_t size() @safe pure nothrow 101 { 102 return T.sizeof * this.slice.length; 103 } 104 105 @property size_t length() @safe pure nothrow 106 { 107 return this.slice.length; 108 } 109 110 alias opDollar = length; 111 @property T* ptr() @trusted pure nothrow 112 { 113 return this.slice.ptr; 114 } // must use .ptr here for zero length strings 115 alias ptr this; 116 117 auto makeOutputRange() 118 { 119 struct OutputRange 120 { 121 T* ptr; 122 size_t idx; 123 124 void put(T)(auto ref T t) 125 { 126 ptr[idx++] = t; 127 } 128 129 T[] opSlice() pure nothrow 130 { 131 return ptr[0 .. idx]; 132 } 133 } 134 135 return OutputRange(this.slice.ptr, 0); 136 } 137 } 138 139 TempBuffer!T tempBuffer(T, alias length, size_t allocaLimit = .allocaLimit)( 140 void* buffer = (T.sizeof * length <= allocaLimit) ? alloca(T.sizeof * length) : null) 141 { 142 return TempBuffer!T((cast(T*)( 143 buffer is null 144 ? malloc(T.sizeof * length) : buffer))[0 .. length], 145 buffer is null); 146 } 147 148 /******************************************************************************* 149 * 150 * Returns a structure to your stack that contains a buffer of $(D bytes) size. 151 * Memory is allocated by calling `.alloc!T(count)` on it in order to get 152 * `count` elements of type `T`. The return value will be a RAII structure 153 * that releases the memory back to the stack buffer upon destruction, so it can 154 * be reused. The pointer within that RAII structure is aligned to 155 * `T.alignof`. If the internal buffer isn't enough to fulfill the request 156 * including padding from alignment, then `malloc()` is used instead. 157 * 158 * Warning: 159 * Always keep the return value of `.alloc()` around on your stack until 160 * you are done with its contents. Never pass it directly into functions as 161 * arguments! 162 * 163 * Params: 164 * bytes = The size of the buffer on the stack. 165 * 166 * Returns: 167 * A stack buffer allocator. 168 * 169 **************************************/ 170 auto stackBuffer(size_t bytes)() @trusted pure 171 { 172 // All that remains of this after inlining is a stack pointer decrement and 173 // a mov instruction for the `null`. 174 StackBuffer!bytes result = void; 175 result.last = cast(StackBufferEntry!void*)&result.last; 176 result.sentinel = null; 177 return result; 178 } 179 180 auto asOutputRange(T)(T* t) @safe pure 181 { 182 struct PointerRange 183 { 184 private: 185 186 T* start; 187 T* ptr; 188 189 public: 190 191 void put()(auto ref const(T) t) pure 192 { 193 *this.ptr++ = t; 194 } 195 196 T[] opSlice() pure 197 { 198 return this.start[0 .. this.ptr - this.start]; 199 } 200 } 201 202 static assert(isOutputRange!(PointerRange, T)); 203 return PointerRange(t, t); 204 } 205 206 enum bufferArg(alias size)() 207 { 208 return "((size <= allocaLimit) ? alloca(size) : null)"; 209 } 210 211 package: 212 213 struct StackBuffer(size_t bytes) 214 { 215 private: 216 217 void[bytes] space = void; 218 StackBufferEntry!void* last; 219 void* sentinel; 220 221 public: 222 223 @disable this(this); 224 225 @trusted 226 StackBufferEntry!T alloc(T)(size_t howMany) 227 { 228 enum max = size_t.max / T.sizeof; 229 alias SBE = StackBufferEntry!T; 230 T* target = cast(T*)(cast(uintptr_t) this.last.ptr / T.alignof * T.alignof); 231 if (target > this.space.ptr && cast(uintptr_t)(target - cast(T*) this.space.ptr) >= howMany) 232 return SBE(target - howMany, this.last); 233 else // 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. 234 return SBE(howMany <= max ? cast(T*) malloc(T.sizeof * howMany) : null); 235 } 236 } 237 238 struct StackBufferEntry(T) 239 { 240 private: 241 242 StackBufferEntry!void* prev; 243 244 this(T* ptr) pure 245 { 246 this.ptr = ptr; 247 } 248 249 this(T* ptr, ref StackBufferEntry!void* last) pure 250 { 251 this.ptr = ptr; 252 this.prev = last; 253 last = cast(StackBufferEntry!void*)&this; 254 } 255 256 public: 257 258 T* ptr; 259 260 static if (!is(T == void)) 261 { 262 @disable this(this); 263 264 ~this() @trusted 265 { 266 if (this.prev) 267 { 268 StackBufferEntry!void* it = this.prev; 269 while (it.prev) 270 it = it.prev; 271 auto last = cast(StackBufferEntry!void**)&prev.ptr; 272 *last = this.prev; 273 } 274 else 275 free(this.ptr); 276 } 277 278 @system pure nothrow @nogc 279 ref inout(T) opIndex(size_t idx) inout 280 { 281 return ptr[idx]; 282 } 283 284 @system pure nothrow @nogc 285 inout(T)[] opSlice(size_t a, size_t b) inout 286 { 287 return ptr[a .. b]; 288 } 289 290 @safe pure nothrow @nogc 291 @property auto range() 292 { 293 return ptr.asOutputRange(); 294 } 295 } 296 }