Skip to content

Commit

Permalink
add std.internal.scopebuffer
Browse files Browse the repository at this point in the history
  • Loading branch information
WalterBright committed Mar 16, 2014
1 parent e6e3231 commit cb6b8a0
Show file tree
Hide file tree
Showing 4 changed files with 388 additions and 5 deletions.
2 changes: 1 addition & 1 deletion posix.mak
Expand Up @@ -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
Expand Down
383 changes: 383 additions & 0 deletions 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);
}

0 comments on commit cb6b8a0

Please sign in to comment.