diff --git a/posix.mak b/posix.mak index cafb4e6c7..b8e22f207 100644 --- a/posix.mak +++ b/posix.mak @@ -208,7 +208,7 @@ EXTRA_MODULES += $(EXTRA_DOCUMENTABLES) $(addprefix \ std/internal/digest/, sha_SSSE3 ) $(addprefix \ std/internal/math/, biguintcore biguintnoasm biguintx86 \ gammafunction errorfunction) $(addprefix std/internal/, \ - processinit uni uni_tab unicode_tables \ + processinit uni uni_tab unicode_tables scopebuffer \ unicode_comp unicode_decomp unicode_grapheme unicode_norm) # Aggregate all D modules relevant to this build diff --git a/std/internal/scopebuffer.d b/std/internal/scopebuffer.d new file mode 100644 index 000000000..b2b7b4282 --- /dev/null +++ b/std/internal/scopebuffer.d @@ -0,0 +1,383 @@ + +/* + * 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; + +private import core.exception; +private import core.stdc.stdlib : realloc; +private import std.traits; + +/************************************** + * ScopeBuffer encapsulates using a local array as a temporary buffer. + * It is initialized with the local array that should be large enough for + * most uses. If the need exceeds the size, ScopeBuffer will resize it + * using malloc() and friends. + * + * ScopeBuffer cannot contain more than (uint.max-16)/2 elements. + * + * ScopeBuffer is an OutputRange. + * + * Since ScopeBuffer potentially stores 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. + * + * 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 $(D @system) and $(D @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 $(D 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.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); + * --- + * 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 + } + body + { + this.buf = buf.ptr; + this.bufLen = cast(uint)buf.length; + } + + 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; + } + + /**************************** + * Copying of ScopeBuffer is not allowed. + */ + //@disable this(this); + + /************************ + * Append element c to the buffer. + * This member function makes ScopeBuffer an OutputRange. + */ + 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. + */ + void put(const(T)[] 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] = cast(T[])s[]; + used = cast(uint)newlen; + } + + /****** + * Retrieve a slice into the result. + * Returns: + * A slice into the temporary buffer that is only + * valid until the next put() or ScopeBuffer goes out of scope. + */ + @system T[] opSlice(size_t lower, size_t upper) + in + { + assert(lower <= bufLen); + assert(upper <= bufLen); + assert(lower <= upper); + } + body + { + return buf[lower .. upper]; + } + + /// ditto + @system T[] opSlice() + { + assert(used <= bufLen); + return buf[0 .. used]; + } + + /******* + * Returns: + * the element at index i. + */ + ref T opIndex(size_t i) + { + 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); + } + body + { + 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); + } + body + { + //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize); + newsize |= wasResized; + void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof); + if (!newBuf) + core.exception.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; } + } +} + +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 + +} + +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"); +} + +/********************************* + * This is a slightly simpler way to create a ScopeBuffer instance + * that uses type deduction. + * Params: + * tmpbuf = the initial buffer to use + * Returns: + * an instance of ScopeBuffer + * Example: +--- +ubyte[10] tmpbuf = void; +auto sb = scopeBuffer(tmpbuf); +scope(exit) sp.free(); +--- + */ + +auto scopeBuffer(T)(T[] tmpbuf) +{ + return ScopeBuffer!T(tmpbuf); +} + +unittest +{ + ubyte[10] tmpbuf = void; + auto sb = scopeBuffer(tmpbuf); + scope(exit) sb.free(); +} + +unittest +{ + ScopeBuffer!(int*) b; + int*[] s; + b.put(s); +} diff --git a/win32.mak b/win32.mak index 5e85c342c..01eaf516b 100644 --- a/win32.mak +++ b/win32.mak @@ -192,8 +192,8 @@ SRC_STD_C_OSX= std\c\osx\socket.d SRC_STD_C_FREEBSD= std\c\freebsd\socket.d SRC_STD_INTERNAL= std\internal\processinit.d std\internal\uni.d std\internal\uni_tab.d \ - std\internal\unicode_tables.d std\internal\unicode_comp.d std\internal\unicode_decomp.d \ - std\internal\unicode_grapheme.d std\internal\unicode_norm.d + std\internal\unicode_tables.d std\internal\unicode_comp.d std\internal\unicode_decomp.d \ + std\internal\unicode_grapheme.d std\internal\unicode_norm.d std\internal\scopebuffer.d SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d diff --git a/win64.mak b/win64.mak index 6a0f427d0..35560474c 100644 --- a/win64.mak +++ b/win64.mak @@ -212,7 +212,7 @@ SRC_STD_C_FREEBSD= std\c\freebsd\socket.d SRC_STD_INTERNAL= std\internal\processinit.d std\internal\uni.d std\internal\uni_tab.d \ std\internal\unicode_tables.d std\internal\unicode_comp.d std\internal\unicode_decomp.d \ - std\internal\unicode_grapheme.d std\internal\unicode_norm.d + std\internal\unicode_grapheme.d std\internal\unicode_norm.d std\internal\scopebuffer.d SRC_STD_INTERNAL_DIGEST= std\internal\digest\sha_SSSE3.d @@ -749,7 +749,7 @@ $(DOC)\etc_c_zlib.html : $(STDDOC) etc\c\zlib.d zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ $(SRC_STD) $(SRC_STD_C) $(SRC_STD_WIN) \ $(SRC_STD_C_WIN) $(SRC_STD_C_LINUX) $(SRC_STD_C_OSX) $(SRC_STD_C_FREEBSD) \ - $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST)\ + $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ $(SRC_STD_INTERNAL_WINDOWS) del phobos.zip