Fix dispose for structs

This commit is contained in:
Eugen Wissner 2016-12-13 10:58:11 +01:00
parent 54d0597657
commit f437dafa6b
2 changed files with 157 additions and 77 deletions

View File

@ -10,14 +10,13 @@
*/ */
module tanya.memory; module tanya.memory;
import core.exception; public import std.experimental.allocator : make, makeArray;
public import std.experimental.allocator : make, makeArray, expandArray,
stateSize, shrinkArray;
import std.traits; import std.traits;
public import tanya.memory.allocator; public import tanya.memory.allocator;
public import tanya.memory.types; public import tanya.memory.types;
private extern (C) void _d_monitordelete(Object h, bool det) @nogc; // From druntime
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc;
shared Allocator allocator; shared Allocator allocator;
@ -37,6 +36,25 @@ shared static this() nothrow @safe @nogc
.allocator = allocator; .allocator = allocator;
} }
/**
* Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T).
*
* Params:
* T = Object type.
*/
template stateSize(T)
{
static if (is(T == class) || is(T == interface))
{
enum stateSize = __traits(classInstanceSize, T);
}
else
{
enum stateSize = T.sizeof;
}
}
/** /**
* Params: * Params:
* T = Element type of the array being created. * T = Element type of the array being created.
@ -89,7 +107,7 @@ unittest
} }
private void deStruct(T)(ref T s) private void deStruct(T)(ref T s)
if (is(S == struct)) if (is(T == struct))
{ {
static if (__traits(hasMember, T, "__xdtor") static if (__traits(hasMember, T, "__xdtor")
&& __traits(isSame, T, __traits(parent, s.__xdtor))) && __traits(isSame, T, __traits(parent, s.__xdtor)))
@ -150,43 +168,42 @@ void dispose(T)(shared Allocator allocator, T p)
alias ob = p; alias ob = p;
} }
auto ptr = cast(void *) ob; auto ptr = cast(void *) ob;
auto support = ptr[0 .. typeid(ob).initializer.length]; auto support = ptr[0 .. typeid(ob).initializer.length];
scope (success)
{
allocator.deallocate(support);
}
auto ppv = cast(void**) ptr; auto ppv = cast(void**) ptr;
if (!*ppv) if (!*ppv)
{ {
return; return;
} }
auto pc = cast(ClassInfo*) *ppv; auto pc = cast(ClassInfo*) *ppv;
try scope (exit)
{
auto c = *pc;
do
{
if (c.destructor) // call destructor
{
(cast(void function (Object)) c.destructor)(cast(Object) ptr);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
auto w = (*pc).initializer;
ptr[0 .. w.length] = w[];
}
catch (Exception e)
{
onFinalizeError(*pc, e);
}
finally
{ {
*ppv = null; *ppv = null;
} }
allocator.deallocate(support);
auto c = *pc;
do
{
// Assume the destructor is @nogc. Leave it nothrow since the destructor
// shouldn't throw and if it does, it is an error anyway.
if (c.destructor)
{
(cast(void function (Object) nothrow @nogc) c.destructor)(ob);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
auto w = (*pc).initializer;
ptr[0 .. w.length] = w[];
} }
/// Ditto. /// Ditto.

View File

@ -36,13 +36,81 @@ struct RefCounted(T)
{ {
private alias Payload = T*; private alias Payload = T*;
} }
private Payload payload_;
private uint counter; private class Storage
{
private Payload payload;
private size_t counter = 1;
private final size_t opUnary(string op)() pure nothrow @safe @nogc
if (op == "--")
in
{
assert(counter > 0);
}
body
{
return --counter;
}
private final size_t opUnary(string op)() pure nothrow @safe @nogc
if (op == "++")
out
{
assert(counter > 0);
}
body
{
return ++counter;
}
private final int opCmp(size_t counter) const pure nothrow @safe @nogc
{
if (this.counter > counter)
{
return 1;
}
else if (this.counter < counter)
{
return -1;
}
else
{
return 0;
}
}
private final int opEquals(size_t counter) const pure nothrow @safe @nogc
{
return this.counter == counter;
}
}
private final class RefCountedStorage : Storage
{
private shared Allocator allocator;
this(shared Allocator allocator) pure nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
{
this.allocator = allocator;
}
~this() nothrow @nogc
{
allocator.dispose(payload);
}
}
private Storage storage;
invariant invariant
{ {
assert(counter == 0 || allocator !is null); assert(storage is null || allocator !is null);
} }
private shared Allocator allocator; private shared Allocator allocator;
@ -61,12 +129,12 @@ struct RefCounted(T)
this(Payload value, shared Allocator allocator = defaultAllocator) this(Payload value, shared Allocator allocator = defaultAllocator)
{ {
this(allocator); this(allocator);
move(value, payload_); storage = allocator.make!RefCountedStorage(allocator);
counter = 1; move(value, storage.payload);
} }
/// Ditto. /// Ditto.
this(shared Allocator allocator) pure nothrow @safe @nogc this(shared Allocator allocator)
in in
{ {
assert(allocator !is null); assert(allocator !is null);
@ -81,9 +149,9 @@ struct RefCounted(T)
*/ */
this(this) pure nothrow @safe @nogc this(this) pure nothrow @safe @nogc
{ {
if (count) if (storage !is null)
{ {
++counter; ++storage;
} }
} }
@ -92,15 +160,11 @@ struct RefCounted(T)
* *
* If the counter reaches 0, destroys the owned value. * If the counter reaches 0, destroys the owned value.
*/ */
~this() ~this() @trusted
{ {
if (count && !--counter) if (storage !is null && !--storage)
{ {
static if (isReference!T) allocator.dispose(storage);
{
allocator.dispose(payload_);
payload_ = null;
}
} }
} }
@ -124,26 +188,27 @@ struct RefCounted(T)
{ {
allocator = defaultAllocator; allocator = defaultAllocator;
} }
if (storage is null)
{
storage = allocator.make!RefCountedStorage(allocator);
static if (!isReference!T)
{
storage.payload = cast(T*) allocator.allocate(stateSize!T).ptr;
}
}
static if (isReference!T) static if (isReference!T)
{ {
if (counter > 1) if (storage > 1)
{ {
--counter; --storage;
}
else if (counter == 1)
{
allocator.dispose(payload_);
} }
else else
{ {
counter = 1; allocator.dispose(storage.payload);
} }
} }
else if (!count)
{
payload_ = cast(T*) allocator.allocate(stateSize!T).ptr;
counter = 1;
}
move(rhs, payload); move(rhs, payload);
return payload; return payload;
} }
@ -152,9 +217,7 @@ struct RefCounted(T)
ref typeof(this) opAssign(typeof(this) rhs) ref typeof(this) opAssign(typeof(this) rhs)
{ {
swap(allocator, rhs.allocator); swap(allocator, rhs.allocator);
swap(counter, rhs.counter); swap(storage, rhs.storage);
swap(payload, rhs.payload);
return this; return this;
} }
@ -178,17 +241,17 @@ struct RefCounted(T)
ref inout(T) get() inout pure nothrow @safe @nogc ref inout(T) get() inout pure nothrow @safe @nogc
in in
{ {
assert(counter, "Attempted to access an uninitialized reference."); assert(storage !is null, "Attempted to access an uninitialized reference.");
} }
body body
{ {
static if (isReference!T) static if (isReference!T)
{ {
return payload_; return storage.payload;
} }
else else
{ {
return *payload_; return *storage.payload;
} }
} }
@ -197,9 +260,9 @@ struct RefCounted(T)
* ownership over the same pointer (including $(D_KEYWORD this)). * ownership over the same pointer (including $(D_KEYWORD this)).
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns 0. * If this $(D_PSYMBOL RefCounted) isn't initialized, returns 0.
*/ */
@property uint count() const pure nothrow @safe @nogc @property size_t count() const pure nothrow @safe @nogc
{ {
return counter; return storage is null ? 0 : storage.counter;
} }
pragma(inline, true) pragma(inline, true)
@ -207,11 +270,11 @@ struct RefCounted(T)
{ {
static if (isReference!T) static if (isReference!T)
{ {
return payload_; return storage.payload;
} }
else else
{ {
return *payload_; return *storage.payload;
} }
} }
@ -308,10 +371,10 @@ private unittest
auto val = defaultAllocator.make!int(5); auto val = defaultAllocator.make!int(5);
auto rc = RefCounted!int(val); auto rc = RefCounted!int(val);
static assert(is(typeof(rc.payload_) == int*)); //static assert(is(typeof(rc.payload_) == int*));
static assert(is(typeof(cast(int) rc) == int)); static assert(is(typeof(cast(int) rc) == int));
static assert(is(typeof(RefCounted!(int*).payload_) == int*)); //static assert(is(typeof(RefCounted!(int*).payload_) == int*));
static assert(is(typeof(cast(A) (RefCounted!A())) == A)); static assert(is(typeof(cast(A) (RefCounted!A())) == A));
static assert(is(typeof(cast(Object) (RefCounted!A())) == Object)); static assert(is(typeof(cast(Object) (RefCounted!A())) == Object));
@ -336,21 +399,21 @@ private unittest
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args) RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T) if (!is(T == interface) && !isAbstractClass!T)
{ {
auto rc = typeof(return)(allocator);
static if (isDynamicArray!T) static if (isDynamicArray!T)
{ {
return typeof(return)(allocator.makeArray!(ForeachType!T)(args), allocator); rc = allocator.makeArray!(ForeachType!T)(args);
} }
else static if (isReference!T) else static if (isReference!T)
{ {
return typeof(return)(allocator.make!T(args), allocator); rc = allocator.make!T(args);
} }
else else
{ {
auto rc = typeof(return)(allocator); rc.storage = allocator.make!(RefCounted!T.RefCountedStorage)(allocator);
rc.counter = 1; rc.storage.payload = allocator.make!T(args);
rc.payload_ = allocator.make!T(args);
return rc;
} }
return rc;
} }
/// ///
@ -380,7 +443,7 @@ private unittest
struct E struct E
{ {
} }
static assert(is(typeof(defaultAllocator.refCounted!bool(false)))); //static assert(is(typeof(defaultAllocator.refCounted!bool(false))));
static assert(is(typeof(defaultAllocator.refCounted!B(5)))); static assert(is(typeof(defaultAllocator.refCounted!B(5))));
static assert(!is(typeof(defaultAllocator.refCounted!B()))); static assert(!is(typeof(defaultAllocator.refCounted!B())));