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