/* * Copyright: 2014 by Digital Mars * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). * Authors: Walter Bright * Source: $(PHOBOSSRC std/internal/scopebuffer.d) */ module std.internal.scopebuffer; //debug=ScopeBuffer; import core.stdc.stdlib : realloc; import std.traits; import std.internal.attributes : betterC; /************************************** * ScopeBuffer encapsulates using a local array as a temporary buffer. * It is initialized with a local array that should be large enough for * most uses. If the need exceeds that size, ScopeBuffer will reallocate * the data using its `realloc` function. * * ScopeBuffer cannot contain more than `(uint.max-16)/2` elements. * * ScopeBuffer is an Output Range. * * Since ScopeBuffer may store elements of type `T` in `malloc`'d memory, * those elements are not scanned when the GC collects. This can cause * memory corruption. Do not use ScopeBuffer when elements of type `T` point * to the GC heap, except when a `realloc` function is provided which supports this. * * Example: --- import core.stdc.stdio; import std.internal.scopebuffer; void main() { char[2] buf = void; auto textbuf = ScopeBuffer!char(buf); scope(exit) textbuf.free(); // necessary for cleanup // Put characters and strings into textbuf, verify they got there textbuf.put('a'); textbuf.put('x'); textbuf.put("abc"); assert(textbuf.length == 5); assert(textbuf[1 .. 3] == "xa"); assert(textbuf[3] == 'b'); // Can shrink it textbuf.length = 3; assert(textbuf[0 .. textbuf.length] == "axa"); assert(textbuf[textbuf.length - 1] == 'a'); assert(textbuf[1 .. 3] == "xa"); textbuf.put('z'); assert(textbuf[] == "axaz"); // Can shrink it to 0 size, and reuse same memory textbuf.length = 0; } --- * It is invalid to access ScopeBuffer's contents when ScopeBuffer goes out of scope. * Hence, copying the contents are necessary to keep them around: --- import std.internal.scopebuffer; string cat(string s1, string s2) { char[10] tmpbuf = void; auto textbuf = ScopeBuffer!char(tmpbuf); scope(exit) textbuf.free(); textbuf.put(s1); textbuf.put(s2); textbuf.put("even more"); return textbuf[].idup; } --- * ScopeBuffer is intended for high performance usages in `@system` and `@trusted` code. * It is designed to fit into two 64 bit registers, again for high performance use. * If used incorrectly, memory leaks and corruption can result. Be sure to use * $(D scope(exit) textbuf.free();) for proper cleanup, and do not refer to a ScopeBuffer * instance's contents after `ScopeBuffer.free()` has been called. * * The `realloc` parameter defaults to C's `realloc()`. Another can be supplied to override it. * * ScopeBuffer instances may be copied, as in: --- textbuf = doSomething(textbuf, args); --- * which can be very efficent, but these must be regarded as a move rather than a copy. * Additionally, the code between passing and returning the instance must not throw * exceptions, otherwise when `ScopeBuffer.free()` is called, memory may get corrupted. */ @system struct ScopeBuffer(T, alias realloc = /*core.stdc.stdlib*/.realloc) if (isAssignable!T && !hasElaborateDestructor!T && !hasElaborateCopyConstructor!T && !hasElaborateAssign!T) { import core.exception : onOutOfMemoryError; import core.stdc.string : memcpy; /************************** * Initialize with buf to use as scratch buffer space. * Params: * buf = Scratch buffer space, must have length that is even * Example: * --- * ubyte[10] tmpbuf = void; * auto sbuf = ScopeBuffer!ubyte(tmpbuf); * --- * Note: * If buf was created by the same `realloc` passed as a parameter * to `ScopeBuffer`, then the contents of `ScopeBuffer` can be extracted without needing * to copy them, and `ScopeBuffer.free()` will not need to be called. */ this(T[] buf) in { assert(!(buf.length & wasResized)); // assure even length of scratch buffer space assert(buf.length <= uint.max); // because we cast to uint later } do { this.buf = buf.ptr; this.bufLen = cast(uint) buf.length; } @system @betterC unittest { ubyte[10] tmpbuf = void; auto sbuf = ScopeBuffer!ubyte(tmpbuf); } /************************** * Releases any memory used. * This will invalidate any references returned by the `[]` operator. * A destructor is not used, because that would make it not POD * (Plain Old Data) and it could not be placed in registers. */ void free() { debug(ScopeBuffer) buf[0 .. bufLen] = 0; if (bufLen & wasResized) realloc(buf, 0); buf = null; bufLen = 0; used = 0; } /************************ * Append element c to the buffer. * This member function makes `ScopeBuffer` an Output Range. */ void put(T c) { /* j will get enregistered, while used will not because resize() may change used */ const j = used; if (j == bufLen) { assert(j <= (uint.max - 16) / 2); resize(j * 2 + 16); } buf[j] = c; used = j + 1; } /************************ * Append array s to the buffer. * * If `const(T)` can be converted to `T`, then put will accept * `const(T)[]` as input. It will accept a `T[]` otherwise. */ package alias CT = Select!(is(const(T) : T), const(T), T); /// ditto void put(CT[] s) { const newlen = used + s.length; assert((cast(ulong) used + s.length) <= uint.max); const len = bufLen; if (newlen > len) { assert(len <= uint.max / 2); resize(newlen <= len * 2 ? len * 2 : newlen); } buf[used .. newlen] = s[]; used = cast(uint) newlen; } /****** * Returns: * A slice into the temporary buffer. * Warning: * The result is only valid until the next `put()` or `ScopeBuffer` goes out of scope. */ @system inout(T)[] opSlice(size_t lower, size_t upper) inout in { assert(lower <= bufLen); assert(upper <= bufLen); assert(lower <= upper); } do { return buf[lower .. upper]; } /// ditto @system inout(T)[] opSlice() inout { assert(used <= bufLen); return buf[0 .. used]; } /******* * Returns: * The element at index i. */ ref inout(T) opIndex(size_t i) inout { assert(i < bufLen); return buf[i]; } /*** * Returns: * The number of elements in the `ScopeBuffer`. */ @property size_t length() const { return used; } /*** * Used to shrink the length of the buffer, * typically to `0` so the buffer can be reused. * Cannot be used to extend the length of the buffer. */ @property void length(size_t i) in { assert(i <= this.used); } do { this.used = cast(uint) i; } alias opDollar = length; private: T* buf; // Using uint instead of size_t so the struct fits in 2 registers in 64 bit code uint bufLen; enum wasResized = 1; // this bit is set in bufLen if we control the memory uint used; void resize(size_t newsize) in { assert(newsize <= uint.max); } do { //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize); newsize |= wasResized; void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof); if (!newBuf) onOutOfMemoryError(); if (!(bufLen & wasResized)) { memcpy(newBuf, buf, used * T.sizeof); debug(ScopeBuffer) buf[0 .. bufLen] = 0; } buf = cast(T*) newBuf; bufLen = cast(uint) newsize; /* This function is called only rarely, * inlining results in poorer register allocation. */ version (DigitalMars) /* With dmd, a fake loop will prevent inlining. * Using a hack until a language enhancement is implemented. */ while (1) { break; } } } @system @betterC unittest { import core.stdc.stdio; import std.range; char[2] tmpbuf = void; { // Exercise all the lines of code except for assert(0)'s auto textbuf = ScopeBuffer!char(tmpbuf); scope(exit) textbuf.free(); static assert(isOutputRange!(ScopeBuffer!char, char)); textbuf.put('a'); textbuf.put('x'); textbuf.put("abc"); // tickle put([])'s resize assert(textbuf.length == 5); assert(textbuf[1 .. 3] == "xa"); assert(textbuf[3] == 'b'); textbuf.length = textbuf.length - 1; assert(textbuf[0 .. textbuf.length] == "axab"); textbuf.length = 3; assert(textbuf[0 .. textbuf.length] == "axa"); assert(textbuf[textbuf.length - 1] == 'a'); assert(textbuf[1 .. 3] == "xa"); textbuf.put(cast(dchar)'z'); assert(textbuf[] == "axaz"); textbuf.length = 0; // reset for reuse assert(textbuf.length == 0); foreach (char c; "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj") { textbuf.put(c); // tickle put(c)'s resize } assert(textbuf[] == "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj"); } // run destructor on textbuf here } @system unittest { string cat(string s1, string s2) { char[10] tmpbuf = void; auto textbuf = ScopeBuffer!char(tmpbuf); scope(exit) textbuf.free(); textbuf.put(s1); textbuf.put(s2); textbuf.put("even more"); return textbuf[].idup; } auto s = cat("hello", "betty"); assert(s == "hellobettyeven more"); } // const @system @betterC unittest { char[10] tmpbuf = void; auto textbuf = ScopeBuffer!char(tmpbuf); scope(exit) textbuf.free(); foreach (i; 0 .. 10) textbuf.put('w'); const csb = textbuf; const elem = csb[3]; const slice0 = csb[0 .. 5]; const slice1 = csb[]; } /********************************* * Creates a `ScopeBuffer` instance using type deduction - see * $(LREF .ScopeBuffer.this) for details. * Params: * tmpbuf = the initial buffer to use * Returns: * An instance of `ScopeBuffer`. */ auto scopeBuffer(T)(T[] tmpbuf) { return ScopeBuffer!T(tmpbuf); } /// @system @betterC unittest { ubyte[10] tmpbuf = void; auto sb = scopeBuffer(tmpbuf); scope(exit) sb.free(); } @system @betterC unittest { ScopeBuffer!(int*) b; int*[] s; b.put(s); ScopeBuffer!char c; string s1; char[] s2; c.put(s1); c.put(s2); }