diff --git a/source/tanya/memory/allocator.d b/source/tanya/memory/allocator.d index 3acf08b..33b47e2 100644 --- a/source/tanya/memory/allocator.d +++ b/source/tanya/memory/allocator.d @@ -82,8 +82,15 @@ mixin template DefaultAllocator() * and sets it to the default one, if not. * * Returns: Used allocator. + * + * Postcondition: $(D_INLINECODE allocator_ !is null) */ - @property shared(Allocator) allocator() nothrow @safe @nogc + protected @property shared(Allocator) allocator() nothrow @safe @nogc + out (allocator) + { + assert(allocator !is null); + } + body { if (allocator_ is null) { @@ -91,4 +98,19 @@ mixin template DefaultAllocator() } return allocator_; } + + /// Ditto. + @property shared(Allocator) allocator() const nothrow @trusted @nogc + out (allocator) + { + assert(allocator !is null); + } + body + { + if (allocator_ is null) + { + return defaultAllocator; + } + return cast(shared Allocator) allocator_; + } } diff --git a/source/tanya/memory/package.d b/source/tanya/memory/package.d index e7564c4..8b27dcf 100644 --- a/source/tanya/memory/package.d +++ b/source/tanya/memory/package.d @@ -10,6 +10,7 @@ */ module tanya.memory; +import core.exception; public import std.experimental.allocator : make, makeArray; import std.traits; public import tanya.memory.allocator; @@ -56,40 +57,69 @@ template stateSize(T) } /** + * Params: + * size = Raw size. + * alignment = Alignment. + * + * Returns: Aligned size. + */ +size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc +{ + return (size - 1) / alignment * alignment + alignment; +} + +/** + * Internal function used to create, resize or destroy a dynamic array. It + * throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new + * allocated part of the array is initialized only if $(D_PARAM Init) + * is set. This function can be trusted only in the data structures that + * can ensure that the array is allocated/rellocated/deallocated with the + * same allocator. + * * Params: * T = Element type of the array being created. + * Init = If should be initialized. + * Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn. * allocator = The allocator used for getting memory. * array = A reference to the array being changed. * length = New array length. - * init = The value to fill the new part of the array with if it becomes - * larger. * * Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could * not be reallocated. In the latter */ -bool resizeArray(T)(shared Allocator allocator, - ref T[] array, - in size_t length, - T init = T.init) +package(tanya) bool resize(T, + bool Init = true, + bool Throws = true) + (shared Allocator allocator, + ref T[] array, + in size_t length) @trusted { void[] buf = array; - immutable oldLength = array.length; - - auto result = () @trusted { - if (!allocator.reallocate(buf, length * T.sizeof)) - { - return false; - } - // Casting from void[] is unsafe, but we know we cast to the original type. - array = cast(T[]) buf; - return true; - }(); - if (result && oldLength < length) + static if (Init) { - array[oldLength .. $] = init; + immutable oldLength = array.length; } - return result; + if (!allocator.reallocate(buf, length * T.sizeof)) + { + static if (Throws) + { + onOutOfMemoryError; + } + return false; + } + // Casting from void[] is unsafe, but we know we cast to the original type. + array = cast(T[]) buf; + + static if (Init) + { + if (oldLength < length) + { + array[oldLength .. $] = T.init; + } + } + return true; } +package(tanya) alias resizeArray = resize; /// unittest diff --git a/source/tanya/memory/types.d b/source/tanya/memory/types.d index 8363f8e..4e3eb36 100644 --- a/source/tanya/memory/types.d +++ b/source/tanya/memory/types.d @@ -16,7 +16,6 @@ import std.algorithm.mutation; import std.conv; import std.traits; import tanya.memory; -import tanya.traits; /** * Reference-counted object containing a $(D_PARAM T) value as payload. @@ -28,7 +27,7 @@ import tanya.traits; */ struct RefCounted(T) { - static if (isReference!T) + static if (is(T == class) || is(T == interface)) { private alias Payload = T; } @@ -43,25 +42,14 @@ struct RefCounted(T) private size_t counter = 1; private final size_t opUnary(string op)() pure nothrow @safe @nogc - if (op == "--") + if (op == "--" || 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; + mixin("return " ~ op ~ "counter;"); } private final int opCmp(size_t counter) const pure nothrow @safe @nogc @@ -110,11 +98,9 @@ struct RefCounted(T) invariant { - assert(storage is null || allocator !is null); + assert(storage is null || allocator_ !is null); } - private shared Allocator allocator; - /** * Takes ownership over $(D_PARAM value), setting the counter to 1. * $(D_PARAM value) may be a pointer, an object or a dynamic array. @@ -141,15 +127,15 @@ struct RefCounted(T) } body { - this.allocator = allocator; + this.allocator_ = allocator; } /** * Increases the reference counter by one. */ - this(this) pure nothrow @safe @nogc + this(this) { - if (storage !is null) + if (count != 0) { ++storage; } @@ -158,13 +144,13 @@ struct RefCounted(T) /** * Decreases the reference counter by one. * - * If the counter reaches 0, destroys the owned value. + * If the counter reaches 0, destroys the owned object. */ - ~this() @trusted + ~this() { - if (storage !is null && !--storage) + if (storage !is null && !(storage.counter && --storage)) { - allocator.dispose(storage); + allocator_.dispose(storage); } } @@ -175,124 +161,158 @@ struct RefCounted(T) * If it is the last reference of the previously owned object, * it will be destroyed. * + * To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null). + * * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will * be used. If you need a different allocator, create a new - * $(D_PSYMBOL RefCounted). + * $(D_PSYMBOL RefCounted) and assign it. * * Params: - * rhs = Object whose ownership is taken over. + * rhs = $(D_KEYWORD this). */ - ref T opAssign(T rhs) + ref typeof(this) opAssign(Payload rhs) { - if (allocator is null) - { - 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) + else if (storage > 1) { - if (storage > 1) - { - --storage; - } - else - { - allocator.dispose(storage.payload); - } + --storage; + storage = allocator.make!RefCountedStorage(allocator); } - move(rhs, payload); - return payload; + else if (cast(RefCountedStorage) storage is null) + { + // Created with refCounted. Always destroyed togethter with the pointer. + assert(storage.counter != 0); + allocator.dispose(storage); + storage = allocator.make!RefCountedStorage(allocator); + } + else + { + allocator.dispose(storage.payload); + } + move(rhs, storage.payload); + return this; + } + + /// Ditto. + ref typeof(this) opAssign(typeof(null)) + { + if (storage is null) + { + return this; + } + else if (storage > 1) + { + --storage; + storage = null; + } + else if (cast(RefCountedStorage) storage is null) + { + // Created with refCounted. Always destroyed togethter with the pointer. + assert(storage.counter != 0); + allocator.dispose(storage); + return this; + } + else + { + allocator.dispose(storage.payload); + } + return this; } /// Ditto. ref typeof(this) opAssign(typeof(this) rhs) { - swap(allocator, rhs.allocator); + swap(allocator_, rhs.allocator_); swap(storage, rhs.storage); return this; } - /** - * Defines the casting to the original type. - * - * Params: - * T = Target type. - * - * Returns: Owned value. - */ - inout(T2) opCast(T2)() inout pure nothrow @safe @nogc - if (is(T : T2)) - { - return get; - } - /** * Returns: Reference to the owned object. */ - ref inout(T) get() inout pure nothrow @safe @nogc + inout(Payload) get() inout pure nothrow @safe @nogc in { - assert(storage !is null, "Attempted to access an uninitialized reference."); + assert(count > 0, "Attempted to access an uninitialized reference."); } body { - static if (isReference!T) - { - return storage.payload; - } - else + return storage.payload; + } + + static if (isPointer!Payload) + { + /** + * Params: + * op = Operation. + * + * Dereferences the pointer. It is defined only for pointers, not for + * reference types like classes, that can be accessed directly. + * + * Returns: Reference to the pointed value. + */ + ref T opUnary(string op)() + if (op == "*") { return *storage.payload; } } + /** + * Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal + * storage. + */ + @property bool isInitialized() const + { + return storage !is null; + } + /** * Returns: The number of $(D_PSYMBOL RefCounted) instances that share * 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 size_t count() const pure nothrow @safe @nogc + @property size_t count() const { return storage is null ? 0 : storage.counter; } - pragma(inline, true) - private ref inout(T) payload() inout return pure nothrow @safe @nogc - { - static if (isReference!T) - { - return storage.payload; - } - else - { - return *storage.payload; - } - } - + mixin DefaultAllocator; alias get this; } +/// +unittest +{ + auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator); + auto val = rc.get; + + *val = 8; + assert(*rc.storage.payload == 8); + + val = null; + assert(rc.storage.payload !is null); + assert(*rc.storage.payload == 8); + + *rc = 9; + assert(*rc.storage.payload == 9); +} + version (unittest) { class A { uint *destroyed; - this(ref uint destroyed) + this(ref uint destroyed) @nogc { this.destroyed = &destroyed; } - ~this() + ~this() @nogc { ++(*destroyed); } @@ -302,44 +322,13 @@ version (unittest) { int prop; @disable this(); - this(int param1) + this(int param1) @nogc { prop = param1; } } } -/// -unittest -{ - struct S - { - RefCounted!(ubyte[]) member; - - this(ref ubyte[] member) - { - assert(!this.member.count); - this.member = member; - assert(this.member.count); - } - } - - auto arr = defaultAllocator.makeArray!ubyte(2); - { - auto a = S(arr); - assert(a.member.count == 1); - - void func(S a) - { - assert(a.member.count == 2); - } - func(a); - - assert(a.member.count == 1); - } - // arr is destroyed. -} - private unittest { uint destroyed; @@ -347,7 +336,7 @@ private unittest assert(destroyed == 0); { - auto rc = RefCounted!A(a); + auto rc = RefCounted!A(a, defaultAllocator); assert(rc.count == 1); void func(RefCounted!A rc) @@ -362,23 +351,14 @@ private unittest RefCounted!int rc; assert(rc.count == 0); - rc = 8; + rc = defaultAllocator.make!int(8); assert(rc.count == 1); } private unittest { - auto val = defaultAllocator.make!int(5); - auto rc = RefCounted!int(val); - - //static assert(is(typeof(rc.payload_) == int*)); - static assert(is(typeof(cast(int) rc) == int)); - - //static assert(is(typeof(RefCounted!(int*).payload_) == int*)); - - static assert(is(typeof(cast(A) (RefCounted!A())) == A)); - static assert(is(typeof(cast(Object) (RefCounted!A())) == Object)); - static assert(!is(typeof(cast(int) (RefCounted!A())))); + static assert(is(typeof(RefCounted!int.storage.payload) == int*)); + static assert(is(typeof(RefCounted!A.storage.payload) == A)); static assert(is(RefCounted!B)); static assert(is(RefCounted!A)); @@ -389,6 +369,10 @@ private unittest * $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for * the constructor of $(D_PARAM T). * + * This function is more efficient than the using of $(D_PSYMBOL RefCounted) + * directly, since it allocates only ones (the internal storage and the + * object). + * * Params: * T = Type of the constructed object. * A = Types of the arguments to the constructor of $(D_PARAM T). @@ -397,21 +381,33 @@ private unittest * Returns: Newly created $(D_PSYMBOL RefCounted!T). */ RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args) - if (!is(T == interface) && !isAbstractClass!T) + if (!is(T == interface) && !isAbstractClass!T + && !isArray!T && !isAssociativeArray!T) { auto rc = typeof(return)(allocator); - static if (isDynamicArray!T) + + immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage)); + immutable size = alignedSize(stateSize!T + storageSize); + + auto mem = (() @trusted => allocator.allocate(size))(); + if (mem is null) { - rc = allocator.makeArray!(ForeachType!T)(args); + onOutOfMemoryError(); } - else static if (isReference!T) + scope (failure) { - rc = allocator.make!T(args); + () @trusted { allocator.deallocate(mem); }(); + } + rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]); + + static if (is(T == class)) + { + rc.storage.payload = emplace!T(mem[storageSize .. $], args); } else { - rc.storage = allocator.make!(RefCounted!T.RefCountedStorage)(allocator); - rc.storage.payload = allocator.make!T(args); + auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))(); + rc.storage.payload = emplace!T(ptr, args); } return rc; } @@ -438,13 +434,14 @@ unittest assert(rc.count == 1); } -private unittest +private @nogc unittest { struct E { } - //static assert(is(typeof(defaultAllocator.refCounted!bool(false)))); - static assert(is(typeof(defaultAllocator.refCounted!B(5)))); + auto b = defaultAllocator.refCounted!B(15); + static assert(is(typeof(b.storage.payload) == B*)); + static assert(is(typeof(b.prop) == int)); static assert(!is(typeof(defaultAllocator.refCounted!B()))); static assert(is(typeof(defaultAllocator.refCounted!E())));