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