1 /++ 2 $(H1 Scoped Buffer) 3 4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 5 Authors: Ilia Ki 6 +/ 7 module mir.appender; 8 9 // import std.traits: isAssignable, hasElaborateDestructorhasElaborateCopyConstructor, hasElaborateAssign; 10 import mir.conv: _mir_destroy = xdestroy; 11 12 private extern(C) @system nothrow @nogc pure void* memcpy(scope void* s1, scope const void* s2, size_t n); 13 14 15 /++ 16 The buffer uses stack memory and C Runtime to allocate temporal memory. 17 18 Shouldn't store references to GC allocated data. 19 +/ 20 struct ScopedBuffer(T, size_t bytes = 4096) 21 if (bytes && T.sizeof <= bytes) 22 { 23 import std.traits: Unqual, isMutable, isIterable, hasElaborateAssign, isAssignable, isArray; 24 import mir.primitives: hasLength; 25 import mir.conv: emplaceRef; 26 27 private enum size_t _bufferLength = bytes / T.sizeof + (bytes % T.sizeof != 0); 28 private T[] _buffer; 29 size_t _currentLength; 30 31 version (mir_secure_memory) 32 private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload; 33 else 34 private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload = void; 35 36 private ref inout(T[_bufferLength]) _scopeBuffer() inout @trusted scope 37 { 38 return *cast(inout(T[_bufferLength])*)&_scopeBufferPayload; 39 } 40 41 /// Reserve `n` more elements. 42 void reserve(size_t n) @safe scope 43 { 44 prepare(n); 45 _currentLength -= n; 46 } 47 48 /// Return a slice to `n` more elements. 49 T[] prepare(size_t n) @trusted scope 50 { 51 import mir.internal.memory: realloc, malloc; 52 _currentLength += n; 53 if (_buffer.length == 0) 54 { 55 if (_currentLength <= _bufferLength) 56 { 57 return _scopeBuffer[0 .. _currentLength]; 58 } 59 else 60 { 61 const newLen = _currentLength << 1; 62 if (auto p = malloc(T.sizeof * newLen)) 63 { 64 _buffer = (cast(T*)p)[0 .. newLen]; 65 } 66 else assert(0); 67 version (mir_secure_memory) 68 { 69 (cast(ubyte[])_buffer)[] = 0; 70 } 71 memcpy(cast(void*)_buffer.ptr, _scopeBuffer.ptr, T.sizeof * (_currentLength - n)); 72 } 73 } 74 else 75 if (_currentLength > _buffer.length) 76 { 77 const newLen = _currentLength << 1; 78 if (auto p = realloc(cast(void*)_buffer.ptr, T.sizeof * newLen)) 79 { 80 _buffer = (cast(T*)p)[0 .. newLen]; 81 } 82 else assert(0); 83 version (mir_secure_memory) 84 { 85 (cast(ubyte[])_buffer[_currentLength .. $])[] = 0; 86 } 87 } 88 return _buffer[0 .. _currentLength]; 89 } 90 91 static if (isAssignable!(T, const T)) 92 private alias R = const T; 93 else 94 private alias R = T; 95 96 /// Copy constructor is enabled only if `T` is mutable type without eleborate assign. 97 static if (isMutable!T && !hasElaborateAssign!T) 98 this(this) 99 { 100 import mir.internal.memory: malloc; 101 if (_buffer.ptr) 102 { 103 typeof(_buffer) buffer; 104 if (auto p = malloc(T.sizeof * _buffer.length)) 105 { 106 buffer = (cast(T*)p)[0 .. T.sizeof * _buffer.length]; 107 } 108 else assert(0); 109 version (mir_secure_memory) 110 { 111 (cast(ubyte[])buffer)[] = 0; 112 } 113 buffer[0 .. _currentLength] = _buffer[0 .. _currentLength]; 114 _buffer = buffer; 115 } 116 } 117 else 118 @disable this(this); 119 120 /// 121 ~this() 122 { 123 import mir.internal.memory: free; 124 data._mir_destroy; 125 version(mir_secure_memory) 126 _currentLength = 0; 127 (() @trusted { if (_buffer.ptr) free(cast(void*)_buffer.ptr); })(); 128 } 129 130 /// 131 void shrinkTo(size_t length) 132 { 133 assert(length <= _currentLength); 134 data[length .. _currentLength]._mir_destroy; 135 _currentLength = length; 136 } 137 138 /// 139 size_t length() scope const @property 140 { 141 return _currentLength; 142 } 143 144 /// 145 void popBackN(size_t n) 146 { 147 sizediff_t t = _currentLength - n; 148 if (t < 0) 149 assert(0, "ScopedBffer.popBackN: n is too large."); 150 data[t .. _currentLength]._mir_destroy; 151 _currentLength = t; 152 } 153 154 /// 155 void put(T e) @safe scope 156 { 157 auto cl = _currentLength; 158 auto d = ()@trusted {return prepare(1);} (); 159 static if (isMutable!T) 160 { 161 import core.lifetime: moveEmplace; 162 ()@trusted{moveEmplace(e, d[cl]);}(); 163 } 164 else 165 { 166 emplaceRef!(Unqual!T)(d[cl], e); 167 } 168 } 169 170 static if (T.sizeof > 8 || hasElaborateAssign!T) 171 /// 172 void put(ref R e) scope 173 { 174 auto cl = _currentLength; 175 auto d = ()@trusted {return prepare(1);} (); 176 emplaceRef!(Unqual!T)(d[cl], e); 177 } 178 179 static if (!hasElaborateAssign!T) 180 /// 181 void put(scope R[] e) scope 182 { 183 auto cl = _currentLength; 184 auto d = ()@trusted {return prepare(e.length);} (); 185 if (!__ctfe) 186 (()@trusted=>memcpy(cast(void*)(d.ptr + cl), e.ptr, e.length * T.sizeof))(); 187 else 188 static if (isMutable!T) 189 (()@trusted=> d[cl .. cl + e.length] = e)(); 190 else 191 assert(0); 192 } 193 194 /// 195 void put(Iterable)(Iterable range) scope 196 if (isIterable!Iterable && !__traits(isStaticArray, Iterable) && (!isArray!Iterable || hasElaborateAssign!T)) 197 { 198 static if (hasLength!Iterable) 199 { 200 auto cl = _currentLength; 201 auto d = ()@trusted {return prepare(range.length);} (); 202 foreach(ref e; range) 203 emplaceRef!(Unqual!T)(d[cl++], e); 204 assert(_currentLength == cl); 205 } 206 else 207 { 208 foreach(ref e; range) 209 put(e); 210 } 211 } 212 213 /// 214 alias opOpAssign(string op : "~") = put; 215 216 /// 217 void reset() @trusted scope nothrow 218 { 219 this.__dtor; 220 _currentLength = 0; 221 _buffer = null; 222 } 223 224 /// 225 void initialize() @system scope nothrow @nogc 226 { 227 _currentLength = 0; 228 _buffer = null; 229 } 230 231 /// 232 inout(T)[] data() inout @property @trusted scope return 233 { 234 return _buffer.length ? _buffer[0 .. _currentLength] : _scopeBuffer[0 .. _currentLength]; 235 } 236 237 /++ 238 Copies data into an array of the same length using `memcpy` C routine. 239 Shrinks the length to `0`. 240 +/ 241 void moveDataAndEmplaceTo(T[] array) @system 242 in { 243 assert(array.length == _currentLength); 244 } 245 do { 246 memcpy(cast(void*)array.ptr, data.ptr, _currentLength * T.sizeof); 247 _currentLength = 0; 248 } 249 } 250 251 /// ditto 252 auto scopedBuffer(T, size_t bytes = 4096)() @trusted 253 { 254 ScopedBuffer!(T, bytes) buffer = void; 255 buffer.initialize; 256 return buffer; 257 } 258 259 /// 260 @safe pure nothrow @nogc 261 version (mir_test) unittest 262 { 263 auto buf = scopedBuffer!char; 264 buf.put('c'); 265 buf.put("str"); 266 assert(buf.data == "cstr"); 267 268 buf.popBackN(2); 269 assert(buf.data == "cs"); 270 } 271 272 /// immutable 273 @safe pure nothrow @nogc 274 version (mir_test) unittest 275 { 276 auto buf = scopedBuffer!(immutable char); 277 buf.put('c'); 278 buf.put("str"); 279 assert(buf.data == "cstr"); 280 281 buf.popBackN(2); 282 assert(buf.data == "cs"); 283 } 284 285 @safe pure nothrow @nogc 286 version (mir_test) unittest 287 { 288 auto buf = scopedBuffer!(char, 3); 289 buf.put('c'); 290 buf.put("str"); 291 assert(buf.data == "cstr"); 292 293 buf.popBackN(2); 294 assert(buf.data == "cs"); 295 } 296 297 @safe pure nothrow @nogc 298 version (mir_test) unittest 299 { 300 alias T = char; 301 const n = 3; 302 303 auto buf = scopedBuffer!(T, n * T.sizeof); 304 assert(buf._scopeBuffer.length == n); // stack 305 assert(buf._buffer.length == 0); // unset 306 307 buf.reserve(n + 1); // transition to heap 308 assert(buf._buffer.length >= n + 1); // heap 309 310 buf ~= 'c'; 311 buf ~= "str"; 312 assert(buf.data == "cstr"); 313 314 buf.popBackN(2); 315 assert(buf.data == "cs"); 316 } 317 318 /// 319 struct UnsafeArrayBuffer(T) 320 { 321 import std.traits: isImplicitlyConvertible; 322 323 /// 324 T[] buffer; 325 /// 326 size_t length; 327 328 /// 329 void put(T a) 330 { 331 import core.lifetime: move; 332 assert(length < buffer.length); 333 buffer[length++] = move(a); 334 } 335 336 static if (isImplicitlyConvertible!(const T, T)) 337 private alias E = const T; 338 else 339 private alias E = T; 340 341 /// 342 void put(E[] a) 343 { 344 import core.lifetime: move; 345 assert(buffer.length >= a.length + length); 346 buffer[length .. length + a.length] = a; 347 length += a.length; 348 } 349 350 /// 351 inout(T)[] data() inout @property @safe scope 352 { 353 return buffer[0 .. length]; 354 } 355 356 /// 357 void popBackN(size_t n) 358 { 359 sizediff_t t = length - n; 360 if (t < 0) 361 assert(0, "UnsafeBuffer.popBackN: n is too large."); 362 buffer[t .. length]._mir_destroy; 363 length = t; 364 } 365 } 366 367 /// 368 @safe pure nothrow @nogc 369 version (mir_test) unittest 370 { 371 char[4] array; 372 auto buf = UnsafeArrayBuffer!char(array); 373 buf.put('c'); 374 buf.put("str"); 375 assert(buf.data == "cstr"); 376 377 buf.popBackN(2); 378 assert(buf.data == "cs"); 379 } 380 381 version(mir_bignum_test) // for DIP1000 382 @safe pure nothrow 383 unittest 384 { 385 import mir.conv: to; 386 import mir.algebraic : Algebraic; 387 static struct S 388 { 389 @safe pure nothrow @nogc: 390 @property string toString() scope const 391 { 392 return "_"; 393 } 394 } 395 Algebraic!(int, string, double) x; 396 x = 42; 397 auto s = x.to!string; 398 assert(s == "42"); 399 x = "abc"; 400 assert(x.to!string == "abc"); 401 x = 42.0; 402 assert(x.to!string == "42.0"); 403 Algebraic!S y; 404 y = S(); 405 assert(y.to!string == "_"); 406 }