-
-
Save Zshazz/fed0400674f034e09495 to your computer and use it in GitHub Desktop.
Some helper items to make it easier to work with @nogc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std.stdio; | |
// prevent many allocFails from being allocated because of | |
// template instantiations... | |
static immutable Error allocFail = new Error("Allocation failed"); | |
mixin template SemiStackSink(size_t guessSize = 60) | |
{ | |
import core.stdc.stdlib : malloc, realloc, free; | |
struct Data | |
{ | |
char[guessSize] arr = void; | |
char[] arrSlice; | |
size_t capacity = guessSize; | |
bool memoryOwned = true; | |
/+ // untested | |
this(this) | |
{ | |
debug | |
{ | |
import std.stdio: writeln(); | |
writeln("copy called"); | |
} | |
if(arrSlice.length < guessSize) | |
{ | |
// we can use our stack array to hold it | |
arr[0..arrSlice.length][] = arrSlice[]; | |
arrSlice = arr[0..arrSlice.length]; | |
} | |
else if(memoryOwned) // we need to malloc new memory... | |
{ | |
void * newMem = core.stdc.stdlib.malloc(char.sizeof * capacity); | |
if(!newMem) | |
{ | |
throw allocFail; | |
} | |
char[] newSlice = (cast(char*) newMem)[0..capacity]; | |
newSlice[] = arrSlice[]; // copy old info | |
arrSlice = newSlice; | |
} | |
memoryOwned = true; | |
} | |
+/ | |
this(this) @disable; | |
void opAssign(typeof(this) other) @disable; | |
~this() | |
{ | |
// If arrSlice is just pointing to the stack, we don't need to free | |
// Also if we don't own the memory, obviously we shouldn't free it. | |
if(arrSlice.ptr is arr.ptr || !memoryOwned) | |
return; | |
core.stdc.stdlib.free(arrSlice.ptr); | |
} | |
/// Resets the buffer's pointer | |
void reset() | |
{ | |
arrSlice = arrSlice[0..0]; | |
} | |
/// Sink target for usage in @nogc environments | |
void sink(in char[] str) @nogc | |
{ | |
foreach(ch; str) | |
{ | |
if(arrSlice.length == capacity) | |
{ | |
size_t newCapacity = capacity*2; | |
void * newMem; | |
if(arrSlice.ptr is arr.ptr) | |
newMem = core.stdc.stdlib.malloc(char.sizeof * newCapacity); | |
else | |
newMem = core.stdc.stdlib.realloc(arrSlice.ptr, char.sizeof * newCapacity); | |
if(!newMem) | |
{ | |
throw allocFail; | |
} | |
char[] newSlice = (cast(char*) newMem)[0..capacity]; | |
assert(newSlice.ptr is newMem); | |
if(arrSlice.ptr is arr.ptr) | |
{ | |
newSlice[] = arrSlice[]; // we malloc'd, so we need to copy old bytes | |
} | |
arrSlice = newSlice; | |
capacity = newCapacity; | |
} | |
arrSlice = arrSlice.ptr[0..arrSlice.length+1]; | |
arrSlice[$-1] = ch; | |
} | |
} | |
Data move()() | |
{ | |
static assert(0, "No! Don't try to move this thing! You'll destroy _everything_!"); | |
} | |
/+ // untested | |
void release() | |
{ | |
memoryOwned = false; | |
} | |
+/ | |
import std.range: isOutputRange, put; | |
void toString(OutputRange)(scope OutputRange r) | |
if(isOutputRange!(OutputRange, const char[])) | |
{ | |
put(r, arrSlice); | |
} | |
// Regular toString doesn't exist because it couldn't be made @nogc | |
// without expecting the caller to manually free the memory since we'd | |
// really want to malloc a fresh string instead of reusing arrSlice | |
// (as reusing arrSlice would make it so that the string would be free'd | |
// at the end of the current instantiation of Data's scope, which would | |
// likely be unexpected for the caller.) | |
// | |
// Thus, it would either be useless (not @nogc) or error-prone. It's | |
// better to just support the toString(OutputRange) method and allow | |
// the caller to provide something they explicitly manage. | |
// | |
// Also, because this is mixed in, technically the caller can just | |
// grab and use the undocumented arrSlice and they'll understand that | |
// it's all their fault if they use it wrong and it falls apart. | |
} | |
auto makeContext() { | |
Data data; | |
// This looks weird. Logically, data.arr[0..0] should point to the stack | |
// in THIS scope, but it apparently is "smart" and is allocating data on | |
// the parent's stack because it's going to be moved there. | |
data.arrSlice = data.arr[0..0]; | |
return data; | |
} | |
} | |
import std.range: isOutputRange, put; | |
import std.traits: isIntegral, isSigned; | |
void toString(T, OutputRange)(T integer, scope OutputRange r) | |
if(isIntegral!T && isOutputRange!(OutputRange, const char[])) | |
{ | |
import std.algorithm: max; | |
import std.conv: to; // only used for CTFE... | |
enum minStr = std.conv.to!string(T.min); | |
enum maxStr = std.conv.to!string(T.max); | |
enum maxLen = std.algorithm.max( minStr.length, maxStr.length ); | |
static if(isSigned!T) | |
{ | |
// because -T.min can't be represented in a T, | |
// we just hardcode that one case. | |
if(integer == T.min) | |
{ | |
put(r, minStr); | |
return; | |
} | |
bool negative = integer < 0; | |
} | |
char[maxLen] buffer = void; | |
size_t idx = buffer.length; | |
do | |
{ | |
buffer[--idx] = cast(char)(integer%10 + '0'); | |
} | |
while(integer /= 10); | |
static if(isSigned!T) | |
{ | |
if(negative) buffer[--idx] = '-'; | |
} | |
put(r, buffer[idx..$]); | |
} | |
unittest | |
{ | |
import std.typetuple: TypeTuple; | |
mixin SemiStackSink!() MainSinkData; | |
auto inst = MainSinkData.makeContext(); | |
foreach(numToTest; TypeTuple!(ulong.min, ulong.max, long.min, long.max, byte.min, ubyte.max)) | |
{ | |
inst.reset(); | |
assert(inst.arr.ptr is inst.arrSlice.ptr); | |
() @nogc | |
{ | |
inst.sink("Hello "); | |
numToTest.toString(&inst.sink); | |
}(); | |
import std.conv: text; | |
assert(text("Hello ", numToTest) == inst.arrSlice); | |
} | |
inst.reset(); | |
string hugeString; | |
import std.datetime; | |
StopWatch sw = AutoStart.yes; | |
//foreach(_; 0..200_000_000) // w00t, it works! | |
foreach(_; 0 .. 200) | |
hugeString ~= 'a'; | |
sw.stop(); | |
auto gcAllocTime = sw.peek(); | |
//writeln("GC allocation took ", gcAllocTime.msecs, "ms"); | |
sw.reset(); | |
sw.start(); | |
() @nogc | |
{ | |
inst.sink(hugeString); | |
}(); | |
sw.stop(); | |
auto s3AllocTime = sw.peek(); | |
//writeln("SemiStackSink took ", s3AllocTime.msecs, "ms"); | |
// Well, this is kinda risky... but technically it should always | |
// be true (at least, I can't think of a way for it to not) | |
assert(s3AllocTime < gcAllocTime); | |
assert(inst.arrSlice == hugeString); | |
assert(inst.arr.ptr !is inst.arrSlice.ptr); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment