1 /* 2 * Copyright: 2014 by Digital Mars 3 * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 * Authors: Walter Bright 5 * Source: $(PHOBOSSRC std/internal/scopebuffer.d) 6 */ 7 8 module std.internal.scopebuffer; 9 10 11 //debug=ScopeBuffer; 12 13 import core.stdc.stdlib : realloc; 14 import std.traits; 15 import std.internal.attributes : betterC; 16 17 /************************************** 18 * ScopeBuffer encapsulates using a local array as a temporary buffer. 19 * It is initialized with a local array that should be large enough for 20 * most uses. If the need exceeds that size, ScopeBuffer will reallocate 21 * the data using its `realloc` function. 22 * 23 * ScopeBuffer cannot contain more than `(uint.max-16)/2` elements. 24 * 25 * ScopeBuffer is an Output Range. 26 * 27 * Since ScopeBuffer may store elements of type `T` in `malloc`'d memory, 28 * those elements are not scanned when the GC collects. This can cause 29 * memory corruption. Do not use ScopeBuffer when elements of type `T` point 30 * to the GC heap, except when a `realloc` function is provided which supports this. 31 * 32 * Example: 33 --- 34 import core.stdc.stdio; 35 import std.internal.scopebuffer; 36 void main() 37 { 38 char[2] buf = void; 39 auto textbuf = ScopeBuffer!char(buf); 40 scope(exit) textbuf.free(); // necessary for cleanup 41 42 // Put characters and strings into textbuf, verify they got there 43 textbuf.put('a'); 44 textbuf.put('x'); 45 textbuf.put("abc"); 46 assert(textbuf.length == 5); 47 assert(textbuf[1 .. 3] == "xa"); 48 assert(textbuf[3] == 'b'); 49 50 // Can shrink it 51 textbuf.length = 3; 52 assert(textbuf[0 .. textbuf.length] == "axa"); 53 assert(textbuf[textbuf.length - 1] == 'a'); 54 assert(textbuf[1 .. 3] == "xa"); 55 56 textbuf.put('z'); 57 assert(textbuf[] == "axaz"); 58 59 // Can shrink it to 0 size, and reuse same memory 60 textbuf.length = 0; 61 } 62 --- 63 * It is invalid to access ScopeBuffer's contents when ScopeBuffer goes out of scope. 64 * Hence, copying the contents are necessary to keep them around: 65 --- 66 import std.internal.scopebuffer; 67 string cat(string s1, string s2) 68 { 69 char[10] tmpbuf = void; 70 auto textbuf = ScopeBuffer!char(tmpbuf); 71 scope(exit) textbuf.free(); 72 textbuf.put(s1); 73 textbuf.put(s2); 74 textbuf.put("even more"); 75 return textbuf[].idup; 76 } 77 --- 78 * ScopeBuffer is intended for high performance usages in `@system` and `@trusted` code. 79 * It is designed to fit into two 64 bit registers, again for high performance use. 80 * If used incorrectly, memory leaks and corruption can result. Be sure to use 81 * $(D scope(exit) textbuf.free();) for proper cleanup, and do not refer to a ScopeBuffer 82 * instance's contents after `ScopeBuffer.free()` has been called. 83 * 84 * The `realloc` parameter defaults to C's `realloc()`. Another can be supplied to override it. 85 * 86 * ScopeBuffer instances may be copied, as in: 87 --- 88 textbuf = doSomething(textbuf, args); 89 --- 90 * which can be very efficent, but these must be regarded as a move rather than a copy. 91 * Additionally, the code between passing and returning the instance must not throw 92 * exceptions, otherwise when `ScopeBuffer.free()` is called, memory may get corrupted. 93 */ 94 95 @system 96 struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) 97 if (isAssignable!T && 98 !hasElaborateDestructor!T && 99 !hasElaborateCopyConstructor!T && 100 !hasElaborateAssign!T) 101 { 102 import core.exception : onOutOfMemoryError; 103 import core.stdc.string : memcpy; 104 105 106 /************************** 107 * Initialize with buf to use as scratch buffer space. 108 * Params: 109 * buf = Scratch buffer space, must have length that is even 110 * Example: 111 * --- 112 * ubyte[10] tmpbuf = void; 113 * auto sbuf = ScopeBuffer!ubyte(tmpbuf); 114 * --- 115 * Note: 116 * If buf was created by the same `realloc` passed as a parameter 117 * to `ScopeBuffer`, then the contents of `ScopeBuffer` can be extracted without needing 118 * to copy them, and `ScopeBuffer.free()` will not need to be called. 119 */ 120 this(T[] buf) 121 in 122 { 123 assert(!(buf.length & wasResized)); // assure even length of scratch buffer space 124 assert(buf.length <= uint.max); // because we cast to uint later 125 } 126 do 127 { 128 this.buf = buf.ptr; 129 this.bufLen = cast(uint) buf.length; 130 } 131 132 @system @betterC unittest 133 { 134 ubyte[10] tmpbuf = void; 135 auto sbuf = ScopeBuffer!ubyte(tmpbuf); 136 } 137 138 /************************** 139 * Releases any memory used. 140 * This will invalidate any references returned by the `[]` operator. 141 * A destructor is not used, because that would make it not POD 142 * (Plain Old Data) and it could not be placed in registers. 143 */ 144 void free() 145 { 146 debug(ScopeBuffer) buf[0 .. bufLen] = 0; 147 if (bufLen & wasResized) 148 realloc(buf, 0); 149 buf = null; 150 bufLen = 0; 151 used = 0; 152 } 153 154 /************************ 155 * Append element c to the buffer. 156 * This member function makes `ScopeBuffer` an Output Range. 157 */ 158 void put(T c) 159 { 160 /* j will get enregistered, while used will not because resize() may change used 161 */ 162 const j = used; 163 if (j == bufLen) 164 { 165 assert(j <= (uint.max - 16) / 2); 166 resize(j * 2 + 16); 167 } 168 buf[j] = c; 169 used = j + 1; 170 } 171 172 /************************ 173 * Append array s to the buffer. 174 * 175 * If `const(T)` can be converted to `T`, then put will accept 176 * `const(T)[]` as input. It will accept a `T[]` otherwise. 177 */ 178 package alias CT = Select!(is(const(T) : T), const(T), T); 179 /// ditto 180 void put(CT[] s) 181 { 182 const newlen = used + s.length; 183 assert((cast(ulong) used + s.length) <= uint.max); 184 const len = bufLen; 185 if (newlen > len) 186 { 187 assert(len <= uint.max / 2); 188 resize(newlen <= len * 2 ? len * 2 : newlen); 189 } 190 buf[used .. newlen] = s[]; 191 used = cast(uint) newlen; 192 } 193 194 /****** 195 * Returns: 196 * A slice into the temporary buffer. 197 * Warning: 198 * The result is only valid until the next `put()` or `ScopeBuffer` goes out of scope. 199 */ 200 @system inout(T)[] opSlice(size_t lower, size_t upper) inout 201 in 202 { 203 assert(lower <= bufLen); 204 assert(upper <= bufLen); 205 assert(lower <= upper); 206 } 207 do 208 { 209 return buf[lower .. upper]; 210 } 211 212 /// ditto 213 @system inout(T)[] opSlice() inout 214 { 215 assert(used <= bufLen); 216 return buf[0 .. used]; 217 } 218 219 /******* 220 * Returns: 221 * The element at index i. 222 */ 223 ref inout(T) opIndex(size_t i) inout 224 { 225 assert(i < bufLen); 226 return buf[i]; 227 } 228 229 /*** 230 * Returns: 231 * The number of elements in the `ScopeBuffer`. 232 */ 233 @property size_t length() const 234 { 235 return used; 236 } 237 238 /*** 239 * Used to shrink the length of the buffer, 240 * typically to `0` so the buffer can be reused. 241 * Cannot be used to extend the length of the buffer. 242 */ 243 @property void length(size_t i) 244 in 245 { 246 assert(i <= this.used); 247 } 248 do 249 { 250 this.used = cast(uint) i; 251 } 252 253 alias opDollar = length; 254 255 private: 256 T* buf; 257 // Using uint instead of size_t so the struct fits in 2 registers in 64 bit code 258 uint bufLen; 259 enum wasResized = 1; // this bit is set in bufLen if we control the memory 260 uint used; 261 262 void resize(size_t newsize) 263 in 264 { 265 assert(newsize <= uint.max); 266 } 267 do 268 { 269 //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize); 270 newsize |= wasResized; 271 void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof); 272 if (!newBuf) 273 onOutOfMemoryError(); 274 if (!(bufLen & wasResized)) 275 { 276 memcpy(newBuf, buf, used * T.sizeof); 277 debug(ScopeBuffer) buf[0 .. bufLen] = 0; 278 } 279 buf = cast(T*) newBuf; 280 bufLen = cast(uint) newsize; 281 282 /* This function is called only rarely, 283 * inlining results in poorer register allocation. 284 */ 285 version (DigitalMars) 286 /* With dmd, a fake loop will prevent inlining. 287 * Using a hack until a language enhancement is implemented. 288 */ 289 while (1) { break; } 290 } 291 } 292 293 @system @betterC unittest 294 { 295 import core.stdc.stdio; 296 import std.range; 297 298 char[2] tmpbuf = void; 299 { 300 // Exercise all the lines of code except for assert(0)'s 301 auto textbuf = ScopeBuffer!char(tmpbuf); 302 scope(exit) textbuf.free(); 303 304 static assert(isOutputRange!(ScopeBuffer!char, char)); 305 306 textbuf.put('a'); 307 textbuf.put('x'); 308 textbuf.put("abc"); // tickle put([])'s resize 309 assert(textbuf.length == 5); 310 assert(textbuf[1 .. 3] == "xa"); 311 assert(textbuf[3] == 'b'); 312 313 textbuf.length = textbuf.length - 1; 314 assert(textbuf[0 .. textbuf.length] == "axab"); 315 316 textbuf.length = 3; 317 assert(textbuf[0 .. textbuf.length] == "axa"); 318 assert(textbuf[textbuf.length - 1] == 'a'); 319 assert(textbuf[1 .. 3] == "xa"); 320 321 textbuf.put(cast(dchar)'z'); 322 assert(textbuf[] == "axaz"); 323 324 textbuf.length = 0; // reset for reuse 325 assert(textbuf.length == 0); 326 327 foreach (char c; "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj") 328 { 329 textbuf.put(c); // tickle put(c)'s resize 330 } 331 assert(textbuf[] == "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj"); 332 } // run destructor on textbuf here 333 334 } 335 336 @system unittest 337 { 338 string cat(string s1, string s2) 339 { 340 char[10] tmpbuf = void; 341 auto textbuf = ScopeBuffer!char(tmpbuf); 342 scope(exit) textbuf.free(); 343 textbuf.put(s1); 344 textbuf.put(s2); 345 textbuf.put("even more"); 346 return textbuf[].idup; 347 } 348 349 auto s = cat("hello", "betty"); 350 assert(s == "hellobettyeven more"); 351 } 352 353 // const 354 @system @betterC unittest 355 { 356 char[10] tmpbuf = void; 357 auto textbuf = ScopeBuffer!char(tmpbuf); 358 scope(exit) textbuf.free(); 359 foreach (i; 0 .. 10) textbuf.put('w'); 360 const csb = textbuf; 361 const elem = csb[3]; 362 const slice0 = csb[0 .. 5]; 363 const slice1 = csb[]; 364 } 365 366 /********************************* 367 * Creates a `ScopeBuffer` instance using type deduction - see 368 * $(LREF .ScopeBuffer.this) for details. 369 * Params: 370 * tmpbuf = the initial buffer to use 371 * Returns: 372 * An instance of `ScopeBuffer`. 373 */ 374 375 auto scopeBuffer(T)(T[] tmpbuf) 376 { 377 return ScopeBuffer!T(tmpbuf); 378 } 379 380 /// 381 @system @betterC unittest 382 { 383 ubyte[10] tmpbuf = void; 384 auto sb = scopeBuffer(tmpbuf); 385 scope(exit) sb.free(); 386 } 387 388 @system @betterC unittest 389 { 390 ScopeBuffer!(int*) b; 391 int*[] s; 392 b.put(s); 393 394 ScopeBuffer!char c; 395 string s1; 396 char[] s2; 397 c.put(s1); 398 c.put(s2); 399 } 400