mirror of
https://github.com/dlang/phobos.git
synced 2025-04-27 05:30:33 +03:00

Fix Issue 19396 - ScopeBuffer can't be used in betterC with inline merged-on-behalf-of: Petar Kirov <ZombineDev@users.noreply.github.com>
399 lines
11 KiB
D
399 lines
11 KiB
D
/*
|
|
* 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);
|
|
}
|