From 484cb13317921c78468dc3f9c16a0fc786b119c0 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Mon, 18 Mar 2019 13:03:52 +0100 Subject: Separate non-documentation tests from the code --- .travis.yml | 8 +- appveyor.yml | 3 +- dub.json | 17 +- memory/dub.json | 18 - memory/tanya/memory/allocator.d | 81 --- memory/tanya/memory/lifecycle.d | 984 ---------------------------------- memory/tanya/memory/mallocator.d | 233 -------- memory/tanya/memory/mmappool.d | 657 ----------------------- memory/tanya/memory/op.d | 437 --------------- memory/tanya/memory/package.d | 132 ----- memory/tanya/memory/smartref.d | 903 ------------------------------- memory/tanya/test/assertion.d | 105 ---- memory/tanya/test/package.d | 18 - memory/tanya/test/stub.d | 373 ------------- meta/tanya/meta/metafunction.d | 24 - meta/tanya/meta/trait.d | 165 ------ meta/tanya/meta/transform.d | 47 -- middle/dub.json | 22 + middle/tanya/memory/allocator.d | 81 +++ middle/tanya/memory/lifetime.d | 815 ++++++++++++++++++++++++++++ middle/tanya/memory/mallocator.d | 218 ++++++++ middle/tanya/memory/mmappool.d | 503 +++++++++++++++++ middle/tanya/memory/op.d | 375 +++++++++++++ middle/tanya/memory/package.d | 132 +++++ middle/tanya/memory/smartref.d | 634 ++++++++++++++++++++++ os/tanya/os/error.d | 6 - source/tanya/algorithm/iteration.d | 2 +- source/tanya/algorithm/mutation.d | 20 +- source/tanya/container/entry.d | 2 +- source/tanya/conv.d | 4 +- source/tanya/exception.d | 4 +- source/tanya/functional.d | 4 +- source/tanya/math/mp.d | 2 +- source/tanya/math/nbtheory.d | 2 +- source/tanya/math/package.d | 3 +- source/tanya/math/random.d | 3 +- source/tanya/net/ip.d | 2 +- source/tanya/range/adapter.d | 2 +- source/tanya/range/array.d | 24 +- source/tanya/range/primitive.d | 2 +- source/tanya/typecons.d | 2 +- test/dub.json | 16 + test/tanya/test/assertion.d | 105 ++++ test/tanya/test/package.d | 18 + test/tanya/test/stub.d | 373 +++++++++++++ tests/tanya/memory/tests/lifetime.d | 173 ++++++ tests/tanya/memory/tests/mallocator.d | 23 + tests/tanya/memory/tests/mmappool.d | 159 ++++++ tests/tanya/memory/tests/op.d | 64 +++ tests/tanya/memory/tests/smartref.d | 250 +++++++++ tests/tanya/meta/tests/metafunction.d | 27 + tests/tanya/meta/tests/trait.d | 169 ++++++ tests/tanya/meta/tests/transform.d | 50 ++ tests/tanya/os/tests/error.d | 9 + 54 files changed, 4268 insertions(+), 4237 deletions(-) delete mode 100644 memory/dub.json delete mode 100644 memory/tanya/memory/allocator.d delete mode 100644 memory/tanya/memory/lifecycle.d delete mode 100644 memory/tanya/memory/mallocator.d delete mode 100644 memory/tanya/memory/mmappool.d delete mode 100644 memory/tanya/memory/op.d delete mode 100644 memory/tanya/memory/package.d delete mode 100644 memory/tanya/memory/smartref.d delete mode 100644 memory/tanya/test/assertion.d delete mode 100644 memory/tanya/test/package.d delete mode 100644 memory/tanya/test/stub.d create mode 100644 middle/dub.json create mode 100644 middle/tanya/memory/allocator.d create mode 100644 middle/tanya/memory/lifetime.d create mode 100644 middle/tanya/memory/mallocator.d create mode 100644 middle/tanya/memory/mmappool.d create mode 100644 middle/tanya/memory/op.d create mode 100644 middle/tanya/memory/package.d create mode 100644 middle/tanya/memory/smartref.d create mode 100644 test/dub.json create mode 100644 test/tanya/test/assertion.d create mode 100644 test/tanya/test/package.d create mode 100644 test/tanya/test/stub.d create mode 100644 tests/tanya/memory/tests/lifetime.d create mode 100644 tests/tanya/memory/tests/mallocator.d create mode 100644 tests/tanya/memory/tests/mmappool.d create mode 100644 tests/tanya/memory/tests/op.d create mode 100644 tests/tanya/memory/tests/smartref.d create mode 100644 tests/tanya/meta/tests/metafunction.d create mode 100644 tests/tanya/meta/tests/trait.d create mode 100644 tests/tanya/meta/tests/transform.d create mode 100644 tests/tanya/os/tests/error.d diff --git a/.travis.yml b/.travis.yml index ee8283f..682306a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,19 +53,21 @@ script: dub build :sys -b ddox --compiler=$DC; dub build :os -b ddox --compiler=$DC; dub build :encoding -b ddox --compiler=$DC; - dub build :memory -b ddox --compiler=$DC; + dub build :middle -b ddox --compiler=$DC; + dub build :test -b ddox --compiler=$DC; dub build -b ddox --compiler=$DC; elif [ -z "$DSCANNER" ]; then dub test :meta -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; dub test :sys -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; dub test :os -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; dub test :encoding -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; - dub test :memory -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; + dub test :middle -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; + dub test :test -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; dub test -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC; else dub fetch dscanner --version=$DSCANNER; - FILES=$(find source */source -type f); + FILES=$(find */tanya -type f); dub run dscanner -- --styleCheck $FILES; fi diff --git a/appveyor.yml b/appveyor.yml index dc0e5e8..4e0072e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -61,5 +61,6 @@ test_script: - dub test :sys -b unittest --arch=%Darch% --compiler=%DC% - dub test :os -b unittest --arch=%Darch% --compiler=%DC% - dub test :encoding -b unittest --arch=%Darch% --compiler=%DC% - - dub test :memory -b unittest --arch=%Darch% --compiler=%DC% + - dub test :middle -b unittest --arch=%Darch% --compiler=%DC% + - dub test :test -b unittest --arch=%Darch% --compiler=%DC% - dub test -b unittest --arch=%Darch% --compiler=%DC% diff --git a/dub.json b/dub.json index 3fd1ec6..9883955 100644 --- a/dub.json +++ b/dub.json @@ -14,7 +14,8 @@ "tanya:sys": "*", "tanya:os": "*", "tanya:encoding": "*", - "tanya:memory": "*" + "tanya:middle": "*", + "tanya:test": "*" }, "dependencies-linux": { @@ -26,7 +27,8 @@ "./sys", "./os", "./encoding", - "./memory" + "./middle", + "./test" ], "configurations": [ @@ -47,6 +49,17 @@ "preBuildCommands": ["ninja -C arch"], "lflags": ["arch/tanya.a"], "versions": ["TanyaNative"] + }, + { + "name": "unittest", + "importPaths": [ + "./source", + "./tests" + ], + "sourcePaths": [ + "./source", + "./tests" + ] } ], diff --git a/memory/dub.json b/memory/dub.json deleted file mode 100644 index 7339127..0000000 --- a/memory/dub.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "memory", - "description": "Tools for manual memory management (allocators, smart pointers)", - "targetType": "library", - - "dependencies": { - "tanya:meta": "*", - "tanya:os": "*", - "tanya:sys": "*" - }, - - "sourcePaths": [ - "." - ], - "importPaths": [ - "." - ] -} diff --git a/memory/tanya/memory/allocator.d b/memory/tanya/memory/allocator.d deleted file mode 100644 index e161612..0000000 --- a/memory/tanya/memory/allocator.d +++ /dev/null @@ -1,81 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This module contains the interface for implementing custom allocators. - * - * Allocators are classes encapsulating memory allocation strategy. This allows - * to decouple memory management from the algorithms and the data. - * - * Copyright: Eugene Wissner 2016-2019. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/memory/tanya/memory/allocator.d, - * tanya/memory/allocator.d) - */ -module tanya.memory.allocator; - -/** - * Abstract class implementing a basic allocator. - */ -interface Allocator -{ - /** - * Returns: Alignment offered. - */ - @property uint alignment() const shared pure nothrow @safe @nogc; - - /** - * Allocates $(D_PARAM size) bytes of memory. - * - * Params: - * size = Amount of memory to allocate. - * - * Returns: Pointer to the new allocated memory. - */ - void[] allocate(size_t size) shared pure nothrow @nogc; - - /** - * Deallocates a memory block. - * - * Params: - * p = A pointer to the memory block to be freed. - * - * Returns: Whether the deallocation was successful. - */ - bool deallocate(void[] p) shared pure nothrow @nogc; - - /** - * Increases or decreases the size of a memory block. - * - * Params: - * p = A pointer to the memory block. - * size = Size of the reallocated block. - * - * Returns: Pointer to the allocated memory. - */ - bool reallocate(ref void[] p, size_t size) shared pure nothrow @nogc; - - /** - * Reallocates a memory block in place if possible or returns - * $(D_KEYWORD false). This function cannot be used to allocate or - * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or - * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false). - * - * Params: - * p = A pointer to the memory block. - * size = Size of the reallocated block. - * - * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. - */ - bool reallocateInPlace(ref void[] p, size_t size) - shared pure nothrow @nogc; -} - -package template GetPureInstance(T : Allocator) -{ - alias GetPureInstance = shared(T) function() - pure nothrow @nogc; -} diff --git a/memory/tanya/memory/lifecycle.d b/memory/tanya/memory/lifecycle.d deleted file mode 100644 index 35d251b..0000000 --- a/memory/tanya/memory/lifecycle.d +++ /dev/null @@ -1,984 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Lifecycle management functions, types and related exceptions. - * - * Copyright: Eugene Wissner 2019. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/memory/tanya/memory/lifecycle.d, - * tanya/memory/lifecycle.d) - */ -module tanya.memory.lifecycle; - -import tanya.memory : defaultAllocator; -import tanya.memory.allocator; -import tanya.meta.trait; -import tanya.meta.metafunction; -version (unittest) import tanya.test.stub; - -/** - * Error thrown if memory allocation fails. - */ -final class OutOfMemoryError : Error -{ - /** - * Constructs new error. - * - * Params: - * msg = The message for the exception. - * file = The file where the exception occurred. - * line = The line number where the exception occurred. - * next = The previous exception in the chain of exceptions, if any. - */ - this(string msg = "Out of memory", - string file = __FILE__, - size_t line = __LINE__, - Throwable next = null) @nogc nothrow pure @safe - { - super(msg, file, line, next); - } - - /// ditto - this(string msg, - Throwable next, - string file = __FILE__, - size_t line = __LINE__) @nogc nothrow pure @safe - { - super(msg, file, line, next); - } -} - -/** - * Allocates $(D_PSYMBOL OutOfMemoryError) in a static storage and throws it. - * - * Params: - * msg = Custom error message. - * - * Throws: $(D_PSYMBOL OutOfMemoryError). - */ -void onOutOfMemoryError(string msg = "Out of memory") -@nogc nothrow pure @trusted -{ - static ubyte[stateSize!OutOfMemoryError] memory; - alias PureType = OutOfMemoryError function(string) @nogc nothrow pure; - throw (cast(PureType) () => emplace!OutOfMemoryError(memory))(msg); -} - -// From druntime -extern (C) -private void _d_monitordelete(Object h, bool det) @nogc nothrow pure; - -/* - * Internal function used to create, resize or destroy a dynamic array. It - * may throw $(D_PSYMBOL OutOfMemoryError). The new - * allocated part of the array isn't initialized. 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. - * allocator = The allocator used for getting memory. - * array = A reference to the array being changed. - * length = New array length. - * - * Returns: $(D_PARAM array). - */ -package(tanya) T[] resize(T)(shared Allocator allocator, - auto ref T[] array, - const size_t length) @trusted -{ - if (length == 0) - { - if (allocator.deallocate(array)) - { - return null; - } - else - { - onOutOfMemoryError(); - } - } - - void[] buf = array; - if (!allocator.reallocate(buf, length * T.sizeof)) - { - onOutOfMemoryError(); - } - // Casting from void[] is unsafe, but we know we cast to the original type. - array = cast(T[]) buf; - - return array; -} - -@nogc nothrow pure @safe unittest -{ - int[] p; - - p = defaultAllocator.resize(p, 20); - assert(p.length == 20); - - p = defaultAllocator.resize(p, 30); - assert(p.length == 30); - - p = defaultAllocator.resize(p, 10); - assert(p.length == 10); - - p = defaultAllocator.resize(p, 0); - assert(p is null); -} - -/* - * Destroys the object. - * Returns the memory should be freed. - */ -package(tanya.memory) void[] finalize(T)(ref T* p) -{ - if (p is null) - { - return null; - } - static if (hasElaborateDestructor!T) - { - destroy(*p); - } - return (cast(void*) p)[0 .. T.sizeof]; -} - -package(tanya.memory) void[] finalize(T)(ref T p) -if (isPolymorphicType!T) -{ - if (p is null) - { - return null; - } - static if (is(T == interface)) - { - version(Windows) - { - import core.sys.windows.unknwn : IUnknown; - static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in " - ~ __PRETTY_FUNCTION__); - } - auto ob = cast(Object) p; - } - else - { - alias ob = p; - } - auto ptr = cast(void*) ob; - auto support = ptr[0 .. typeid(ob).initializer.length]; - - auto ppv = cast(void**) ptr; - if (!*ppv) - { - return null; - } - auto pc = cast(ClassInfo*) *ppv; - scope (exit) - { - *ppv = null; - } - - 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) - { - alias DtorType = void function(Object) pure nothrow @safe @nogc; - (cast(DtorType) c.destructor)(ob); - } - } - while ((c = c.base) !is null); - - if (ppv[1]) // if monitor is not null - { - _d_monitordelete(cast(Object) ptr, true); - } - return support; -} - -package(tanya.memory) void[] finalize(T)(ref T[] p) -{ - destroyAllImpl!(T[], T)(p); - return p; -} - -package(tanya) void destroyAllImpl(R, E)(R p) -{ - static if (hasElaborateDestructor!E) - { - foreach (ref e; p) - { - destroy(e); - } - } -} - -/** - * Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T). - * It is assumed the respective entities had been allocated with the same - * allocator. - * - * Params: - * T = Type of $(D_PARAM p). - * allocator = Allocator the $(D_PARAM p) was allocated with. - * p = Object or array to be destroyed. - */ -void dispose(T)(shared Allocator allocator, auto ref T p) -{ - () @trusted { allocator.deallocate(finalize(p)); }(); - p = null; -} - -@nogc nothrow pure @system unittest -{ - static struct S - { - ~this() @nogc nothrow pure @safe - { - } - } - auto p = cast(S[]) defaultAllocator.allocate(S.sizeof); - - defaultAllocator.dispose(p); -} - -// Works with interfaces. -@nogc nothrow pure @safe unittest -{ - interface I - { - } - class C : I - { - } - auto c = defaultAllocator.make!C(); - I i = c; - - defaultAllocator.dispose(i); - defaultAllocator.dispose(i); -} - -/** - * Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args) - * as the parameter list for the constructor of $(D_PARAM T). - * - * Params: - * T = Class type. - * A = Types of the arguments to the constructor of $(D_PARAM T). - * allocator = Allocator. - * args = Constructor arguments of $(D_PARAM T). - * - * Returns: Newly created $(D_PSYMBOL T). - * - * Precondition: $(D_INLINECODE allocator !is null) - */ -T make(T, A...)(shared Allocator allocator, auto ref A args) -if (is(T == class)) -in (allocator !is null) -{ - auto mem = (() @trusted => allocator.allocate(stateSize!T))(); - if (mem is null) - { - onOutOfMemoryError(); - } - scope (failure) - { - () @trusted { allocator.deallocate(mem); }(); - } - - return emplace!T(mem[0 .. stateSize!T], args); -} - -/** - * Constructs a value object of type $(D_PARAM T) using $(D_PARAM args) - * as the parameter list for the constructor of $(D_PARAM T) and returns a - * pointer to the new object. - * - * Params: - * T = Object type. - * A = Types of the arguments to the constructor of $(D_PARAM T). - * allocator = Allocator. - * args = Constructor arguments of $(D_PARAM T). - * - * Returns: Pointer to the created object. - * - * Precondition: $(D_INLINECODE allocator !is null) - */ -T* make(T, A...)(shared Allocator allocator, auto ref A args) -if (!isPolymorphicType!T && !isAssociativeArray!T && !isArray!T) -in (allocator !is null) -{ - auto mem = (() @trusted => allocator.allocate(stateSize!T))(); - if (mem is null) - { - onOutOfMemoryError(); - } - scope (failure) - { - () @trusted { allocator.deallocate(mem); }(); - } - return emplace!T(mem[0 .. stateSize!T], args); -} - -/// -@nogc nothrow pure @safe unittest -{ - int* i = defaultAllocator.make!int(5); - assert(*i == 5); - defaultAllocator.dispose(i); -} - -/** - * Constructs a new array with $(D_PARAM n) elements. - * - * Params: - * T = Array type. - * E = Array element type. - * allocator = Allocator. - * n = Array size. - * - * Returns: Newly created array. - * - * Precondition: $(D_INLINECODE allocator !is null - * && n <= size_t.max / E.sizeof) - */ -T make(T : E[], E)(shared Allocator allocator, size_t n) -in (allocator !is null) -in (n <= size_t.max / E.sizeof) -{ - auto ret = allocator.resize!E(null, n); - - static if (hasElaborateDestructor!E) - { - for (auto range = ret; range.length != 0; range = range[1 .. $]) - { - emplace!E(cast(void[]) range[0 .. 1], E.init); - } - } - else - { - ret[] = E.init; - } - - return ret; -} - -/// -@nogc nothrow pure @safe unittest -{ - int[] i = defaultAllocator.make!(int[])(2); - assert(i.length == 2); - assert(i[0] == int.init && i[1] == int.init); - defaultAllocator.dispose(i); -} - -/** - * Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the - * given arguments. - * - * If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference - * of type $(D_PARAM T), otherwise a pointer to the constructed object is - * returned. - * - * If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer) - * should be an instance of the outer class. - * - * $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If - * $(D_PARAM T) isn't an aggregate type and doesn't have a constructor, - * $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`, - * `Args[0]` should be implicitly convertible to $(D_PARAM T) then. - * - * Params: - * T = Constructed type. - * U = Type of the outer class if $(D_PARAM T) is a nested class. - * Args = Types of the constructor arguments if $(D_PARAM T) has a constructor - * or the type of the initial value. - * outer = Outer class instance if $(D_PARAM T) is a nested class. - * args = Constructor arguments if $(D_PARAM T) has a constructor or the - * initial value. - * - * Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory). - * - * Precondition: `memory.length == stateSize!T`. - * Postcondition: $(D_PARAM memory) and the result point to the same memory. - */ -T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args) -if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U)) -in (memory.length >= stateSize!T) -out (result; memory.ptr is (() @trusted => cast(void*) result)()) -{ - import tanya.memory.op : copy; - - copy(typeid(T).initializer, memory); - - auto result = (() @trusted => cast(T) memory.ptr)(); - result.outer = outer; - - static if (is(typeof(result.__ctor(args)))) - { - result.__ctor(args); - } - - return result; -} - -/// ditto -T emplace(T, Args...)(void[] memory, auto ref Args args) -if (is(T == class) && !isAbstractClass!T && !isInnerClass!T) -in (memory.length == stateSize!T) -out (result; memory.ptr is (() @trusted => cast(void*) result)()) -{ - import tanya.memory.op : copy; - - copy(typeid(T).initializer, memory); - - auto result = (() @trusted => cast(T) memory.ptr)(); - static if (is(typeof(result.__ctor(args)))) - { - result.__ctor(args); - } - return result; -} - -/// -@nogc nothrow pure @safe unittest -{ - class C - { - int i = 5; - class Inner - { - int i; - - this(int param) pure nothrow @safe @nogc - { - this.i = param; - } - } - } - ubyte[stateSize!C] memory1; - ubyte[stateSize!(C.Inner)] memory2; - - auto c = emplace!C(memory1); - assert(c.i == 5); - - auto inner = emplace!(C.Inner)(memory2, c, 8); - assert(c.i == 5); - assert(inner.i == 8); - assert(inner.outer is c); -} - -/// ditto -T* emplace(T, Args...)(void[] memory, auto ref Args args) -if (!isAggregateType!T && (Args.length <= 1)) -in (memory.length >= T.sizeof) -out (result; memory.ptr is result) -{ - auto result = (() @trusted => cast(T*) memory.ptr)(); - static if (Args.length == 1) - { - *result = T(args[0]); - } - else - { - *result = T.init; - } - return result; -} - -private void initializeOne(T)(ref void[] memory, ref T* result) @trusted -{ - import tanya.memory.op : copy, fill; - - static if (!hasElaborateAssign!T && isAssignable!T) - { - *result = T.init; - } - else static if (__VERSION__ >= 2083 // __traits(isZeroInit) available. - && __traits(isZeroInit, T)) - { - memory.ptr[0 .. T.sizeof].fill!0; - } - else - { - static immutable T init = T.init; - copy((&init)[0 .. 1], memory); - } -} - -/// ditto -T* emplace(T, Args...)(void[] memory, auto ref Args args) -if (!isPolymorphicType!T && isAggregateType!T) -in (memory.length >= T.sizeof) -out (result; memory.ptr is result) -{ - auto result = (() @trusted => cast(T*) memory.ptr)(); - - static if (Args.length == 0) - { - static assert(is(typeof({ static T t; })), - "Default constructor is disabled"); - initializeOne(memory, result); - } - else static if (is(typeof(result.__ctor(args)))) - { - initializeOne(memory, result); - result.__ctor(args); - } - else static if (Args.length == 1 && is(typeof({ T t = args[0]; }))) - { - import tanya.memory.op : copy; - - ((ref arg) @trusted => - copy((cast(void*) &arg)[0 .. T.sizeof], memory))(args[0]); - static if (hasElaborateCopyConstructor!T) - { - result.__postblit(); - } - } - else static if (is(typeof({ T t = T(args); }))) - { - auto init = T(args); - (() @trusted => moveEmplace(init, *result))(); - } - else - { - static assert(false, - "Unable to construct value with the given arguments"); - } - return result; -} - -/// -@nogc nothrow pure @safe unittest -{ - ubyte[4] memory; - - auto i = emplace!int(memory); - static assert(is(typeof(i) == int*)); - assert(*i == 0); - - i = emplace!int(memory, 5); - assert(*i == 5); - - static struct S - { - int i; - @disable this(); - @disable this(this); - this(int i) @nogc nothrow pure @safe - { - this.i = i; - } - } - auto s = emplace!S(memory, 8); - static assert(is(typeof(s) == S*)); - assert(s.i == 8); -} - -// Handles "Cannot access frame pointer" error. -@nogc nothrow pure @safe unittest -{ - struct F - { - ~this() @nogc nothrow pure @safe - { - } - } - static assert(is(typeof(emplace!F((void[]).init)))); -} - -// Can emplace structs without a constructor -@nogc nothrow pure @safe unittest -{ - static assert(is(typeof(emplace!WithDtor(null, WithDtor())))); - static assert(is(typeof(emplace!WithDtor(null)))); -} - -// Doesn't call a destructor on uninitialized elements -@nogc nothrow pure @system unittest -{ - static struct SWithDtor - { - private bool canBeInvoked = false; - ~this() @nogc nothrow pure @safe - { - assert(this.canBeInvoked); - } - } - void[SWithDtor.sizeof] memory = void; - auto actual = emplace!SWithDtor(memory[], SWithDtor(true)); - assert(actual.canBeInvoked); -} - -// Initializes structs if no arguments are given -@nogc nothrow pure @safe unittest -{ - static struct SEntry - { - byte content; - } - ubyte[1] mem = [3]; - - assert(emplace!SEntry(cast(void[]) mem[0 .. 1]).content == 0); -} - -// Postblit is called when emplacing a struct -@nogc nothrow pure @system unittest -{ - static struct S - { - bool called = false; - this(this) @nogc nothrow pure @safe - { - this.called = true; - } - } - S target; - S* sp = ⌖ - - emplace!S(sp[0 .. 1], S()); - assert(target.called); -} - -private void deinitialize(bool zero, T)(ref T value) -{ - static if (is(T == U[S], U, size_t S)) - { - foreach (ref e; value) - { - deinitialize!zero(e); - } - } - else - { - import tanya.memory.op : copy, fill; - - static if (isNested!T) - { - // Don't override the context pointer. - enum size_t size = T.sizeof - (void*).sizeof; - } - else - { - enum size_t size = T.sizeof; - } - static if (zero) - { - fill!0((cast(void*) &value)[0 .. size]); - } - else - { - copy(typeid(T).initializer()[0 .. size], (&value)[0 .. 1]); - } - } -} - -/** - * Moves $(D_PARAM source) into $(D_PARAM target) assuming that - * $(D_PARAM target) isn't initialized. - * - * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places - * the $(D_PARAM source) into a valid but unspecified state, which means that - * after moving $(D_PARAM source) can be destroyed or assigned a new value, but - * accessing it yields an unspecified value. No postblits or destructors are - * called. If the $(D_PARAM target) should be destroyed before, use - * $(D_PSYMBOL move). - * - * $(D_PARAM source) and $(D_PARAM target) must be different objects. - * - * Params: - * T = Object type. - * source = Source object. - * target = Target object. - * - * See_Also: $(D_PSYMBOL move), - * $(D_PSYMBOL hasElaborateCopyConstructor), - * $(D_PSYMBOL hasElaborateDestructor). - * - * Precondition: `&source !is &target`. - */ -void moveEmplace(T)(ref T source, ref T target) @system -in -{ - assert(&source !is &target, "Source and target must be different"); -} -do -{ - static if (is(T == struct) || isStaticArray!T) - { - import tanya.memory.op : copy; - - copy((&source)[0 .. 1], (&target)[0 .. 1]); - - static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T) - { - static if (__VERSION__ >= 2083) // __traits(isZeroInit) available. - { - deinitialize!(__traits(isZeroInit, T))(source); - } - else - { - if (typeid(T).initializer().ptr is null) - { - deinitialize!true(source); - } - else - { - deinitialize!false(source); - } - } - } - } - else - { - target = source; - } -} - -/// -@nogc nothrow pure @system unittest -{ - static struct S - { - int member = 5; - - this(this) @nogc nothrow pure @safe - { - assert(false); - } - } - S source, target = void; - moveEmplace(source, target); - assert(target.member == 5); - - int x1 = 5, x2; - moveEmplace(x1, x2); - assert(x2 == 5); -} - -// Is pure. -@nogc nothrow pure @system unittest -{ - struct S - { - this(this) - { - } - } - S source, target = void; - static assert(is(typeof({ moveEmplace(source, target); }))); -} - -// Moves nested. -@nogc nothrow pure @system unittest -{ - struct Nested - { - void method() @nogc nothrow pure @safe - { - } - } - Nested source, target = void; - moveEmplace(source, target); - assert(source == target); -} - -// Emplaces static arrays. -@nogc nothrow pure @system unittest -{ - static struct S - { - size_t member; - this(size_t i) @nogc nothrow pure @safe - { - this.member = i; - } - ~this() @nogc nothrow pure @safe - { - } - } - S[2] source = [ S(5), S(5) ], target = void; - moveEmplace(source, target); - assert(source[0].member == 0); - assert(target[0].member == 5); - assert(source[1].member == 0); - assert(target[1].member == 5); -} - -/** - * Moves $(D_PARAM source) into $(D_PARAM target) assuming that - * $(D_PARAM target) isn't initialized. - * - * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places - * the $(D_PARAM source) into a valid but unspecified state, which means that - * after moving $(D_PARAM source) can be destroyed or assigned a new value, but - * accessing it yields an unspecified value. $(D_PARAM target) is destroyed before - * the new value is assigned. If $(D_PARAM target) isn't initialized and - * therefore shouldn't be destroyed, $(D_PSYMBOL moveEmplace) can be used. - * - * If $(D_PARAM target) isn't specified, $(D_PSYMBOL move) returns the source - * as rvalue without calling its copy constructor or destructor. - * - * $(D_PARAM source) and $(D_PARAM target) are the same object, - * $(D_PSYMBOL move) does nothing. - * - * Params: - * T = Object type. - * source = Source object. - * target = Target object. - * - * See_Also: $(D_PSYMBOL moveEmplace). - */ -void move(T)(ref T source, ref T target) -{ - if ((() @trusted => &source is &target)()) - { - return; - } - static if (hasElaborateDestructor!T) - { - target.__xdtor(); - } - (() @trusted => moveEmplace(source, target))(); -} - -/// ditto -T move(T)(ref T source) @trusted -{ - static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T) - { - T target = void; - moveEmplace(source, target); - return target; - } - else - { - return source; - } -} - -/// -@nogc nothrow pure @safe unittest -{ - static struct S - { - int member = 5; - - this(this) @nogc nothrow pure @safe - { - assert(false); - } - } - S source, target = void; - move(source, target); - assert(target.member == 5); - assert(move(target).member == 5); - - int x1 = 5, x2; - move(x1, x2); - assert(x2 == 5); - assert(move(x2) == 5); -} - -// Moves if source is target. -@nogc nothrow pure @safe unittest -{ - int x = 5; - move(x, x); - assert(x == 5); -} - -/** - * Exchanges the values of $(D_PARAM a) and $(D_PARAM b). - * - * $(D_PSYMBOL swap) moves the contents of $(D_PARAM a) and $(D_PARAM b) - * without calling its postblits or destructors. - * - * Params: - * a = The first object. - * b = The second object. - */ -void swap(T)(ref T a, ref T b) @trusted -{ - T tmp = void; - moveEmplace(a, tmp); - moveEmplace(b, a); - moveEmplace(tmp, b); -} - -/// -@nogc nothrow pure @safe unittest -{ - int a = 3, b = 5; - swap(a, b); - assert(a == 5); - assert(b == 3); -} - -/** - * Forwards its argument list preserving $(D_KEYWORD ref) and $(D_KEYWORD out) - * storage classes. - * - * $(D_PSYMBOL forward) accepts a list of variables or literals. It returns an - * argument list of the same length that can be for example passed to a - * function accepting the arguments of this type. - * - * Params: - * args = Argument list. - * - * Returns: $(D_PARAM args) with their original storage classes. - */ -template forward(args...) -{ - static if (args.length == 0) - { - alias forward = AliasSeq!(); - } - else static if (__traits(isRef, args[0]) || __traits(isOut, args[0])) - { - static if (args.length == 1) - { - alias forward = args[0]; - } - else - { - alias forward = AliasSeq!(args[0], forward!(args[1 .. $])); - } - } - else - { - @property auto forwardOne() - { - return move(args[0]); - } - static if (args.length == 1) - { - alias forward = forwardOne; - } - else - { - alias forward = AliasSeq!(forwardOne, forward!(args[1 .. $])); - } - } -} - -/// -@nogc nothrow pure @safe unittest -{ - static assert(is(typeof((int i) { int v = forward!i; }))); - static assert(is(typeof((ref int i) { int v = forward!i; }))); - static assert(is(typeof({ - void f(int i, ref int j, out int k) - { - f(forward!(i, j, k)); - } - }))); -} diff --git a/memory/tanya/memory/mallocator.d b/memory/tanya/memory/mallocator.d deleted file mode 100644 index 858ad40..0000000 --- a/memory/tanya/memory/mallocator.d +++ /dev/null @@ -1,233 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Allocator based on $(D_PSYMBOL malloc), $(D_PSYMBOL realloc) and - * $(D_PSYMBOL free). - * - * Copyright: Eugene Wissner 2017-2019. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/memory/tanya/memory/mallocator.d, - * tanya/memory/mallocator.d) - */ -module tanya.memory.mallocator; - -version (TanyaNative) -{ -} -else: - -import core.stdc.stdlib; -import tanya.memory.allocator; - -/** - * Wrapper for $(D_PSYMBOL malloc)/$(D_PSYMBOL realloc)/$(D_PSYMBOL free) from - * the C standard library. - */ -final class Mallocator : Allocator -{ - private alias MallocType = extern (C) void* function(size_t) - @nogc nothrow pure @system; - private alias FreeType = extern (C) void function(void*) - @nogc nothrow pure @system; - private alias ReallocType = extern (C) void* function(void*, size_t) - @nogc nothrow pure @system; - - /** - * Allocates $(D_PARAM size) bytes of memory. - * - * Params: - * size = Amount of memory to allocate. - * - * Returns: The pointer to the new allocated memory. - */ - void[] allocate(size_t size) @nogc nothrow pure shared @system - { - if (size == 0) - { - return null; - } - auto p = (cast(MallocType) &malloc)(size + psize); - - return p is null ? null : p[psize .. psize + size]; - } - - /// - @nogc nothrow pure @system unittest - { - auto p = Mallocator.instance.allocate(20); - assert(p.length == 20); - Mallocator.instance.deallocate(p); - - p = Mallocator.instance.allocate(0); - assert(p.length == 0); - } - - /** - * Deallocates a memory block. - * - * Params: - * p = A pointer to the memory block to be freed. - * - * Returns: Whether the deallocation was successful. - */ - bool deallocate(void[] p) @nogc nothrow pure shared @system - { - if (p !is null) - { - (cast(FreeType) &free)(p.ptr - psize); - } - return true; - } - - /// - @nogc nothrow pure @system unittest - { - void[] p; - assert(Mallocator.instance.deallocate(p)); - - p = Mallocator.instance.allocate(10); - assert(Mallocator.instance.deallocate(p)); - } - - /** - * Reallocating in place isn't supported. - * - * Params: - * p = A pointer to the memory block. - * size = Size of the reallocated block. - * - * Returns: $(D_KEYWORD false). - */ - bool reallocateInPlace(ref void[] p, size_t size) - @nogc nothrow pure shared @system - { - cast(void) size; - return false; - } - - /// - @nogc nothrow pure @system unittest - { - void[] p; - assert(!Mallocator.instance.reallocateInPlace(p, 8)); - } - - /** - * Increases or decreases the size of a memory block. - * - * Params: - * p = A pointer to the memory block. - * size = Size of the reallocated block. - * - * Returns: Whether the reallocation was successful. - */ - bool reallocate(ref void[] p, size_t size) - @nogc nothrow pure shared @system - { - if (size == 0) - { - if (deallocate(p)) - { - p = null; - return true; - } - } - else if (p is null) - { - p = allocate(size); - return p is null ? false : true; - } - else - { - auto r = (cast(ReallocType) &realloc)(p.ptr - psize, size + psize); - - if (r !is null) - { - p = r[psize .. psize + size]; - return true; - } - } - return false; - } - - /// - @nogc nothrow pure @system unittest - { - void[] p; - - assert(Mallocator.instance.reallocate(p, 20)); - assert(p.length == 20); - - assert(Mallocator.instance.reallocate(p, 30)); - assert(p.length == 30); - - assert(Mallocator.instance.reallocate(p, 10)); - assert(p.length == 10); - - assert(Mallocator.instance.reallocate(p, 0)); - assert(p is null); - } - - // Fails with false - @nogc nothrow pure @system unittest - { - void[] p = Mallocator.instance.allocate(20); - void[] oldP = p; - assert(!Mallocator.instance.reallocate(p, size_t.max - Mallocator.psize * 2)); - assert(oldP is p); - Mallocator.instance.deallocate(p); - } - - /** - * Returns: The alignment offered. - */ - @property uint alignment() const @nogc nothrow pure @safe shared - { - return (void*).alignof; - } - - private nothrow @nogc unittest - { - assert(Mallocator.instance.alignment == (void*).alignof); - } - - static private shared(Mallocator) instantiate() @nogc nothrow @system - { - if (instance_ is null) - { - const size = __traits(classInstanceSize, Mallocator) + psize; - void* p = malloc(size); - - if (p !is null) - { - p[psize .. size] = typeid(Mallocator).initializer[]; - instance_ = cast(shared Mallocator) p[psize .. size].ptr; - } - } - return instance_; - } - - /** - * Static allocator instance and initializer. - * - * Returns: The global $(D_PSYMBOL Allocator) instance. - */ - static @property shared(Mallocator) instance() @nogc nothrow pure @system - { - return (cast(GetPureInstance!Mallocator) &instantiate)(); - } - - /// - @nogc nothrow pure @system unittest - { - assert(instance is instance); - } - - private enum ushort psize = 8; - - private shared static Mallocator instance_; -} diff --git a/memory/tanya/memory/mmappool.d b/memory/tanya/memory/mmappool.d deleted file mode 100644 index 1bc401b..0000000 --- a/memory/tanya/memory/mmappool.d +++ /dev/null @@ -1,657 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* - * Native allocator. - * - * Copyright: Eugene Wissner 2016-2019. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/memory/tanya/memory/mmappool.d, - * tanya/memory/mmappool.d) - */ -module tanya.memory.mmappool; - -version (TanyaNative): - -import mir.linux._asm.unistd; -import tanya.memory.allocator; -import tanya.memory.op; -import tanya.os.error; -import tanya.sys.linux.syscall; -import tanya.sys.posix.mman; - -private void* mapMemory(const size_t length) @nogc nothrow pure @system -{ - auto p = syscall_(0, - length, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, - 0, - NR_mmap); - return p == -ErrorCode.noMemory ? null : cast(void*) p; -} - -private bool unmapMemory(shared void* addr, const size_t length) -@nogc nothrow pure @system -{ - return syscall_(cast(ptrdiff_t) addr, length, NR_munmap) == 0; -} - -/* - * This allocator allocates memory in regions (multiple of 64 KB for example). - * Each region is then splitted in blocks. So it doesn't request the memory - * from the operating system on each call, but only if there are no large - * enough free blocks in the available regions. - * Deallocation works in the same way. Deallocation doesn't immediately - * gives the memory back to the operating system, but marks the appropriate - * block as free and only if all blocks in the region are free, the complete - * region is deallocated. - * - *
- * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- * |      |     |         |     |            ||      |     |                  |
- * |      |prev <-----------    |            ||      |     |                  |
- * |  R   |  B  |         |  B  |            ||   R  |  B  |                  |
- * |  E   |  L  |         |  L  |           next  E  |  L  |                  |
- * |  G   |  O  |  DATA   |  O  |   FREE    --->  G  |  O  |       DATA       |
- * |  I   |  C  |         |  C  |           <---  I  |  C  |                  |
- * |  O   |  K  |         |  K  |           prev  O  |  K  |                  |
- * |  N   |    -----------> next|            ||   N  |     |                  |
- * |      |     |         |     |            ||      |     |                  |
- * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- * 
- */ -final class MmapPool : Allocator -{ - version (none) - { - @nogc nothrow pure @system invariant - { - for (auto r = &head; *r !is null; r = &((*r).next)) - { - auto block = cast(Block) (cast(void*) *r + RegionEntry.sizeof); - do - { - assert(block.prev is null || block.prev.next is block); - assert(block.next is null || block.next.prev is block); - assert(block.region is *r); - } - while ((block = block.next) !is null); - } - } - } - - /* - * Allocates $(D_PARAM size) bytes of memory. - * - * Params: - * size = Amount of memory to allocate. - * - * Returns: Pointer to the new allocated memory. - */ - void[] allocate(size_t size) @nogc nothrow pure shared @system - { - if (size == 0) - { - return null; - } - const dataSize = addAlignment(size); - if (dataSize < size) - { - return null; - } - - void* data = findBlock(dataSize); - if (data is null) - { - data = initializeRegion(dataSize); - } - - return data is null ? null : data[0 .. size]; - } - - @nogc nothrow pure @system unittest - { - auto p = MmapPool.instance.allocate(20); - assert(p); - MmapPool.instance.deallocate(p); - - p = MmapPool.instance.allocate(0); - assert(p.length == 0); - } - - @nogc nothrow pure @system unittest - { - // allocate() check. - size_t tooMuchMemory = size_t.max - - MmapPool.alignment_ - - BlockEntry.sizeof * 2 - - RegionEntry.sizeof - - pageSize; - assert(MmapPool.instance.allocate(tooMuchMemory) is null); - - assert(MmapPool.instance.allocate(size_t.max) is null); - - // initializeRegion() check. - tooMuchMemory = size_t.max - MmapPool.alignment_; - assert(MmapPool.instance.allocate(tooMuchMemory) is null); - } - - /* - * Search for a block large enough to keep $(D_PARAM size) and split it - * into two blocks if the block is too large. - * - * Params: - * size = Minimum size the block should have (aligned). - * - * Returns: Data the block points to or $(D_KEYWORD null). - */ - private void* findBlock(const ref size_t size) - @nogc nothrow pure shared @system - { - Block block1; - RegionLoop: for (auto r = head; r !is null; r = r.next) - { - block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof); - do - { - if (block1.free && block1.size >= size) - { - break RegionLoop; - } - } - while ((block1 = block1.next) !is null); - } - if (block1 is null) - { - return null; - } - else if (block1.size >= size + alignment_ + BlockEntry.sizeof) - { // Split the block if needed - Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size); - block2.prev = block1; - block2.next = block1.next; - block2.free = true; - block2.size = block1.size - BlockEntry.sizeof - size; - block2.region = block1.region; - - if (block1.next !is null) - { - block1.next.prev = block2; - } - block1.next = block2; - block1.size = size; - } - block1.free = false; - block1.region.blocks = block1.region.blocks + 1; - - return cast(void*) block1 + BlockEntry.sizeof; - } - - // Merge block with the next one. - private void mergeNext(Block block) const @nogc nothrow pure @safe shared - { - block.size = block.size + BlockEntry.sizeof + block.next.size; - if (block.next.next !is null) - { - block.next.next.prev = block; - } - block.next = block.next.next; - } - - /* - * Deallocates a memory block. - * - * Params: - * p = A pointer to the memory block to be freed. - * - * Returns: Whether the deallocation was successful. - */ - bool deallocate(void[] p) @nogc nothrow pure shared @system - { - if (p.ptr is null) - { - return true; - } - - Block block = cast(Block) (p.ptr - BlockEntry.sizeof); - if (block.region.blocks <= 1) - { - if (block.region.prev !is null) - { - block.region.prev.next = block.region.next; - } - else // Replace the list head. It is being deallocated - { - head = block.region.next; - } - if (block.region.next !is null) - { - block.region.next.prev = block.region.prev; - } - return unmapMemory(block.region, block.region.size); - } - // Merge blocks if neigbours are free. - if (block.next !is null && block.next.free) - { - mergeNext(block); - } - if (block.prev !is null && block.prev.free) - { - block.prev.size = block.prev.size + BlockEntry.sizeof + block.size; - if (block.next !is null) - { - block.next.prev = block.prev; - } - block.prev.next = block.next; - } - else - { - block.free = true; - } - block.region.blocks = block.region.blocks - 1; - return true; - } - - @nogc nothrow pure @system unittest - { - auto p = MmapPool.instance.allocate(20); - - assert(MmapPool.instance.deallocate(p)); - } - - /* - * Reallocates a memory block in place if possible or returns - * $(D_KEYWORD false). This function cannot be used to allocate or - * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or - * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false). - * - * Params: - * p = A pointer to the memory block. - * size = Size of the reallocated block. - * - * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. - */ - bool reallocateInPlace(ref void[] p, size_t size) - @nogc nothrow pure shared @system - { - if (p is null || size == 0) - { - return false; - } - if (size <= p.length) - { - // Leave the block as is. - p = p.ptr[0 .. size]; - return true; - } - Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof); - - if (block1.size >= size) - { - // Enough space in the current block. - p = p.ptr[0 .. size]; - return true; - } - const dataSize = addAlignment(size); - const pAlignment = addAlignment(p.length); - assert(pAlignment >= p.length, "Invalid memory chunk length"); - const delta = dataSize - pAlignment; - - if (block1.next is null - || !block1.next.free - || dataSize < size - || block1.next.size + BlockEntry.sizeof < delta) - { - /* - It is the last block in the region - * - The next block isn't free - * - The next block is too small - * - Requested size is too large - */ - return false; - } - if (block1.next.size >= delta + alignment_) - { - // Move size from block2 to block1. - block1.next.size = block1.next.size - delta; - block1.size = block1.size + delta; - - auto block2 = cast(Block) (p.ptr + dataSize); - if (block1.next.next !is null) - { - block1.next.next.prev = block2; - } - copyBackward((cast(void*) block1.next)[0 .. BlockEntry.sizeof], - (cast(void*) block2)[0 .. BlockEntry.sizeof]); - block1.next = block2; - } - else - { - // The next block has enough space, but is too small for further - // allocations. Merge it with the current block. - mergeNext(block1); - } - - p = p.ptr[0 .. size]; - return true; - } - - @nogc nothrow pure @system unittest - { - void[] p; - assert(!MmapPool.instance.reallocateInPlace(p, 5)); - assert(p is null); - - p = MmapPool.instance.allocate(1); - auto orig = p.ptr; - - assert(MmapPool.instance.reallocateInPlace(p, 2)); - assert(p.length == 2); - assert(p.ptr == orig); - - assert(MmapPool.instance.reallocateInPlace(p, 4)); - assert(p.length == 4); - assert(p.ptr == orig); - - assert(MmapPool.instance.reallocateInPlace(p, 2)); - assert(p.length == 2); - assert(p.ptr == orig); - - MmapPool.instance.deallocate(p); - } - - /* - * Increases or decreases the size of a memory block. - * - * Params: - * p = A pointer to the memory block. - * size = Size of the reallocated block. - * - * Returns: Whether the reallocation was successful. - */ - bool reallocate(ref void[] p, size_t size) - @nogc nothrow pure shared @system - { - if (size == 0) - { - if (deallocate(p)) - { - p = null; - return true; - } - return false; - } - else if (reallocateInPlace(p, size)) - { - return true; - } - // Can't reallocate in place, allocate a new block, - // copy and delete the previous one. - void[] reallocP = allocate(size); - if (reallocP is null) - { - return false; - } - if (p !is null) - { - copy(p[0 .. p.length < size ? p.length : size], reallocP); - deallocate(p); - } - p = reallocP; - - return true; - } - - @nogc nothrow pure @system unittest - { - void[] p; - MmapPool.instance.reallocate(p, 10 * int.sizeof); - (cast(int[]) p)[7] = 123; - - assert(p.length == 40); - - MmapPool.instance.reallocate(p, 8 * int.sizeof); - - assert(p.length == 32); - assert((cast(int[]) p)[7] == 123); - - MmapPool.instance.reallocate(p, 20 * int.sizeof); - (cast(int[]) p)[15] = 8; - - assert(p.length == 80); - assert((cast(int[]) p)[15] == 8); - assert((cast(int[]) p)[7] == 123); - - MmapPool.instance.reallocate(p, 8 * int.sizeof); - - assert(p.length == 32); - assert((cast(int[]) p)[7] == 123); - - MmapPool.instance.deallocate(p); - } - - static private shared(MmapPool) instantiate() @nogc nothrow @system - { - if (instance_ is null) - { - const instanceSize = addAlignment(__traits(classInstanceSize, - MmapPool)); - - Region head; // Will become soon our region list head - void* data = initializeRegion(instanceSize, head); - if (data !is null) - { - copy(typeid(MmapPool).initializer, data[0 .. instanceSize]); - instance_ = cast(shared MmapPool) data; - instance_.head = head; - } - } - return instance_; - } - - /* - * Static allocator instance and initializer. - * - * Returns: Global $(D_PSYMBOL MmapPool) instance. - */ - static @property shared(MmapPool) instance() @nogc nothrow pure @system - { - return (cast(GetPureInstance!MmapPool) &instantiate)(); - } - - @nogc nothrow pure @system unittest - { - assert(instance is instance); - } - - /* - * Initializes a region for one element. - * - * Params: - * size = Aligned size of the first data block in the region. - * head = Region list head. - * - * Returns: A pointer to the data. - */ - private static void* initializeRegion(const size_t size, ref Region head) - @nogc nothrow pure @system - { - const regionSize = calculateRegionSize(size); - if (regionSize < size) - { - return null; - } - - void* p = mapMemory(regionSize); - if (p is null) - { - return null; - } - - Region region = cast(Region) p; - region.blocks = 1; - region.size = regionSize; - - // Set the pointer to the head of the region list - if (head !is null) - { - head.prev = region; - } - region.next = head; - region.prev = null; - head = region; - - // Initialize the data block - void* memoryPointer = p + RegionEntry.sizeof; - Block block1 = cast(Block) memoryPointer; - block1.size = size; - block1.free = false; - - // It is what we want to return - void* data = memoryPointer + BlockEntry.sizeof; - - // Free block after data - memoryPointer = data + size; - Block block2 = cast(Block) memoryPointer; - block1.prev = block2.next = null; - block1.next = block2; - block2.prev = block1; - block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2; - block2.free = true; - block1.region = block2.region = region; - - return data; - } - - private void* initializeRegion(const size_t size) - @nogc nothrow pure shared @system - { - return initializeRegion(size, this.head); - } - - /* - * Params: - * x = Space to be aligned. - * - * Returns: Aligned size of $(D_PARAM x). - */ - private static size_t addAlignment(const size_t x) @nogc nothrow pure @safe - { - return (x - 1) / alignment_ * alignment_ + alignment_; - } - - /* - * Params: - * x = Required space. - * - * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). - */ - private static size_t calculateRegionSize(ref const size_t x) - @nogc nothrow pure @safe - { - return (x + RegionEntry.sizeof + BlockEntry.sizeof * 2) - / pageSize * pageSize + pageSize; - } - - /* - * Returns: Alignment offered. - */ - @property uint alignment() const @nogc nothrow pure @safe shared - { - return alignment_; - } - - @nogc nothrow pure @system unittest - { - assert(MmapPool.instance.alignment == MmapPool.alignment_); - } - - private enum uint alignment_ = 8; - - private shared static MmapPool instance_; - - // Page size. - enum size_t pageSize = 65536; - - private shared struct RegionEntry - { - Region prev; - Region next; - uint blocks; - size_t size; - } - private alias Region = shared RegionEntry*; - private shared Region head; - - private shared struct BlockEntry - { - Block prev; - Block next; - Region region; - size_t size; - bool free; - } - private alias Block = shared BlockEntry*; -} - -// A lot of allocations/deallocations, but it is the minimum caused a -// segmentation fault because MmapPool reallocateInPlace moves a block wrong. -@nogc nothrow pure @system unittest -{ - auto a = MmapPool.instance.allocate(16); - auto d = MmapPool.instance.allocate(16); - auto b = MmapPool.instance.allocate(16); - auto e = MmapPool.instance.allocate(16); - auto c = MmapPool.instance.allocate(16); - auto f = MmapPool.instance.allocate(16); - - MmapPool.instance.deallocate(a); - MmapPool.instance.deallocate(b); - MmapPool.instance.deallocate(c); - - a = MmapPool.instance.allocate(50); - MmapPool.instance.reallocateInPlace(a, 64); - MmapPool.instance.deallocate(a); - - a = MmapPool.instance.allocate(1); - auto tmp1 = MmapPool.instance.allocate(1); - auto h1 = MmapPool.instance.allocate(1); - auto tmp2 = cast(ubyte[]) MmapPool.instance.allocate(1); - - auto h2 = MmapPool.instance.allocate(2); - tmp1 = MmapPool.instance.allocate(1); - MmapPool.instance.deallocate(h2); - MmapPool.instance.deallocate(h1); - - h2 = MmapPool.instance.allocate(2); - h1 = MmapPool.instance.allocate(1); - MmapPool.instance.deallocate(h2); - - auto rep = cast(void[]) tmp2; - MmapPool.instance.reallocate(rep, tmp1.length); - tmp2 = cast(ubyte[]) rep; - - MmapPool.instance.reallocate(tmp1, 9); - - rep = cast(void[]) tmp2; - MmapPool.instance.reallocate(rep, tmp1.length); - tmp2 = cast(ubyte[]) rep; - MmapPool.instance.reallocate(tmp1, 17); - - tmp2[$ - 1] = 0; - - MmapPool.instance.deallocate(tmp1); - - b = MmapPool.instance.allocate(16); - - MmapPool.instance.deallocate(h1); - MmapPool.instance.deallocate(a); - MmapPool.instance.deallocate(b); - MmapPool.instance.deallocate(d); - MmapPool.instance.deallocate(e); - MmapPool.instance.deallocate(f); -} diff --git a/memory/tanya/memory/op.d b/memory/tanya/memory/op.d deleted file mode 100644 index d77f3bb..0000000 --- a/memory/tanya/memory/op.d +++ /dev/null @@ -1,437 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Set of operations on memory blocks. - * - * Copyright: Eugene Wissner 2017-2019. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/memory/tanya/memory/op.d, - * tanya/memory/op.d) - */ -module tanya.memory.op; - -version (TanyaNative) -{ - extern private void fillMemory(void[], size_t) pure nothrow @system @nogc; - - extern private void copyMemory(const void[], void[]) - pure nothrow @system @nogc; - - extern private void moveMemory(const void[], void[]) - pure nothrow @system @nogc; - - extern private bool equalMemory(const void[], const void[]) - pure nothrow @system @nogc; -} -else -{ - import core.stdc.string; -} - -version (TanyaNative) -{ - @nogc nothrow pure @system unittest - { - ubyte[2] buffer = 1; - fillMemory(buffer[1 .. $], 0); - assert(buffer[0] == 1 && buffer[1] == 0); - } - - @nogc nothrow pure @safe unittest - { - assert(equal(null, null)); - } -} - -private enum alignMask = size_t.sizeof - 1; - -/** - * Copies $(D_PARAM source) into $(D_PARAM target). - * - * $(D_PARAM source) and $(D_PARAM target) shall not overlap so that - * $(D_PARAM source) points ahead of $(D_PARAM target). - * - * $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length) - * elements. - * - * Params: - * source = Memory to copy from. - * target = Destination memory. - * - * See_Also: $(D_PSYMBOL copyBackward). - * - * Precondition: $(D_INLINECODE source.length <= target.length). - */ -void copy(const void[] source, void[] target) @nogc nothrow pure @trusted -in -{ - assert(source.length <= target.length); - assert(source.length == 0 || source.ptr !is null); - assert(target.length == 0 || target.ptr !is null); -} -do -{ - version (TanyaNative) - { - copyMemory(source, target); - } - else - { - memcpy(target.ptr, source.ptr, source.length); - } -} - -/// -@nogc nothrow pure @safe unittest -{ - ubyte[9] source = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - ubyte[9] target; - source.copy(target); - assert(equal(source, target)); -} - -@nogc nothrow pure @safe unittest -{ - { - ubyte[0] source, target; - source.copy(target); - } - { - ubyte[1] source = [1]; - ubyte[1] target; - source.copy(target); - assert(target[0] == 1); - } - { - ubyte[8] source = [1, 2, 3, 4, 5, 6, 7, 8]; - ubyte[8] target; - source.copy(target); - assert(equal(source, target)); - } -} - -/* - * size_t value each of which bytes is set to `Byte`. - */ -private template filledBytes(ubyte Byte, ubyte I = 0) -{ - static if (I == size_t.sizeof) - { - enum size_t filledBytes = Byte; - } - else - { - enum size_t filledBytes = (filledBytes!(Byte, I + 1) << 8) | Byte; - } -} - -/** - * Fills $(D_PARAM memory) with the single byte $(D_PARAM c). - * - * Param: - * c = The value to fill $(D_PARAM memory) with. - * memory = Memory block. - */ -void fill(ubyte c = 0)(void[] memory) @trusted -in -{ - assert(memory.length == 0 || memory.ptr !is null); -} -do -{ - version (TanyaNative) - { - fillMemory(memory, filledBytes!c); - } - else - { - memset(memory.ptr, c, memory.length); - } -} - -/// -@nogc nothrow pure @safe unittest -{ - ubyte[9] memory = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - memory.fill!0(); - foreach (ubyte v; memory) - { - assert(v == 0); - } -} - -/** - * Copies starting from the end of $(D_PARAM source) into the end of - * $(D_PARAM target). - * - * $(D_PSYMBOL copyBackward) copies the elements in reverse order, but the - * order of elements in the $(D_PARAM target) is exactly the same as in the - * $(D_PARAM source). - * - * $(D_PARAM source) and $(D_PARAM target) shall not overlap so that - * $(D_PARAM target) points ahead of $(D_PARAM source). - * - * $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length) - * elements. - * - * Params: - * source = Memory to copy from. - * target = Destination memory. - * - * See_Also: $(D_PSYMBOL copy). - * - * Precondition: $(D_INLINECODE source.length <= target.length). - */ -void copyBackward(const void[] source, void[] target) @nogc nothrow pure @trusted -in -{ - assert(source.length <= target.length); - assert(source.length == 0 || source.ptr !is null); - assert(target.length == 0 || target.ptr !is null); -} -do -{ - version (TanyaNative) - { - moveMemory(source, target); - } - else - { - memmove(target.ptr, source.ptr, source.length); - } -} - -/// -@nogc nothrow pure @safe unittest -{ - ubyte[6] mem = [ 'a', 'a', 'b', 'b', 'c', 'c' ]; - ubyte[6] expected = [ 'a', 'a', 'a', 'a', 'b', 'b' ]; - - copyBackward(mem[0 .. 4], mem[2 .. $]); - assert(equal(expected, mem)); -} - -@nogc nothrow pure @safe unittest -{ - ubyte[9] r1 = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ]; - ubyte[9] r2; - - copyBackward(r1, r2); - assert(equal(r1, r2)); -} - -/** - * Finds the first occurrence of $(D_PARAM needle) in $(D_PARAM haystack) if - * any. - * - * Params: - * haystack = Memory block. - * needle = A byte. - * - * Returns: The subrange of $(D_PARAM haystack) whose first element is the - * first occurrence of $(D_PARAM needle). If $(D_PARAM needle) - * couldn't be found, an empty `inout void[]` is returned. - */ -inout(void[]) find(return inout void[] haystack, ubyte needle) -@nogc nothrow pure @trusted -in -{ - assert(haystack.length == 0 || haystack.ptr !is null); -} -do -{ - auto length = haystack.length; - const size_t needleWord = size_t.max * needle; - enum size_t highBits = filledBytes!(0x01, 0); - enum size_t mask = filledBytes!(0x80, 0); - - // Align - auto bytes = cast(inout(ubyte)*) haystack; - while (length > 0 && ((cast(size_t) bytes) & 3) != 0) - { - if (*bytes == needle) - { - return bytes[0 .. length]; - } - ++bytes; - --length; - } - - // Check if some of the words has the needle - auto words = cast(inout(size_t)*) bytes; - while (length >= size_t.sizeof) - { - if ((((*words ^ needleWord) - highBits) & (~*words) & mask) != 0) - { - break; - } - ++words; - length -= size_t.sizeof; - } - - // Find the exact needle position in the word - bytes = cast(inout(ubyte)*) words; - while (length > 0) - { - if (*bytes == needle) - { - return bytes[0 .. length]; - } - ++bytes; - --length; - } - - return haystack[$ .. $]; -} - -/// -@nogc nothrow pure @safe unittest -{ - const ubyte[9] haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'b', 'g', 'h']; - - assert(equal(find(haystack, 'a'), haystack[])); - assert(equal(find(haystack, 'b'), haystack[1 .. $])); - assert(equal(find(haystack, 'c'), haystack[2 .. $])); - assert(equal(find(haystack, 'd'), haystack[3 .. $])); - assert(equal(find(haystack, 'e'), haystack[4 .. $])); - assert(equal(find(haystack, 'f'), haystack[5 .. $])); - assert(equal(find(haystack, 'h'), haystack[8 .. $])); - assert(find(haystack, 'i').length == 0); - - assert(find(null, 'a').length == 0); -} - -/** - * Looks for `\0` in the $(D_PARAM haystack) and returns the part of the - * $(D_PARAM haystack) ahead of it. - * - * Returns $(D_KEYWORD null) if $(D_PARAM haystack) doesn't contain a null - * character. - * - * Params: - * haystack = Memory block. - * - * Returns: The subrange that spans all bytes before the null character or - * $(D_KEYWORD null) if the $(D_PARAM haystack) doesn't contain any. - */ -inout(char[]) findNullTerminated(return inout char[] haystack) -@nogc nothrow pure @trusted -in -{ - assert(haystack.length == 0 || haystack.ptr !is null); -} -do -{ - auto length = haystack.length; - enum size_t highBits = filledBytes!(0x01, 0); - enum size_t mask = filledBytes!(0x80, 0); - - // Align - auto bytes = cast(inout(ubyte)*) haystack; - while (length > 0 && ((cast(size_t) bytes) & 3) != 0) - { - if (*bytes == '\0') - { - return haystack[0 .. haystack.length - length]; - } - ++bytes; - --length; - } - - // Check if some of the words contains 0 - auto words = cast(inout(size_t)*) bytes; - while (length >= size_t.sizeof) - { - if (((*words - highBits) & (~*words) & mask) != 0) - { - break; - } - ++words; - length -= size_t.sizeof; - } - - // Find the exact 0 position in the word - bytes = cast(inout(ubyte)*) words; - while (length > 0) - { - if (*bytes == '\0') - { - return haystack[0 .. haystack.length - length]; - } - ++bytes; - --length; - } - - return null; -} - -/// -@nogc nothrow pure @safe unittest -{ - assert(equal(findNullTerminated("abcdef\0gh"), "abcdef")); - assert(equal(findNullTerminated("\0garbage"), "")); - assert(equal(findNullTerminated("\0"), "")); - assert(equal(findNullTerminated("cstring\0"), "cstring")); - assert(findNullTerminated(null) is null); - assert(findNullTerminated("abcdef") is null); -} - -/** - * Compares two memory areas $(D_PARAM r1) and $(D_PARAM r2) for equality. - * - * Params: - * r1 = First memory block. - * r2 = Second memory block. - * - * Returns: $(D_KEYWORD true) if $(D_PARAM r1) and $(D_PARAM r2) are equal, - * $(D_KEYWORD false) otherwise. - */ -bool equal(const void[] r1, const void[] r2) @nogc nothrow pure @trusted -in -{ - assert(r1.length == 0 || r1.ptr !is null); - assert(r2.length == 0 || r2.ptr !is null); -} -do -{ - version (TanyaNative) - { - return equalMemory(r1, r2); - } - else - { - return r1.length == r2.length - && memcmp(r1.ptr, r2.ptr, r1.length) == 0; - } -} - -/// -@nogc nothrow pure @safe unittest -{ - assert(equal("asdf", "asdf")); - assert(!equal("asd", "asdf")); - assert(!equal("asdf", "asd")); - assert(!equal("asdf", "qwer")); -} - -// Compares unanligned memory -@nogc nothrow pure @safe unittest -{ - ubyte[16] r1 = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', - ]; - ubyte[16] r2 = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', - ]; - - assert(equal(r1, r2)); - assert(equal(r1[1 .. $], r2[1 .. $])); - assert(equal(r1[0 .. $ - 1], r2[0 .. $ - 1])); - assert(equal(r1[0 .. 8], r2[0 .. 8])); -} diff --git a/memory/tanya/memory/package.d b/memory/tanya/memory/package.d deleted file mode 100644 index 98aec48..0000000 --- a/memory/tanya/memory/package.d +++ /dev/null @@ -1,132 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Dynamic memory management. - * - * Copyright: Eugene Wissner 2016-2019. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/memory/tanya/memory/package.d, - * tanya/memory/package.d) - */ -module tanya.memory; - -public import tanya.memory.allocator; -public import tanya.memory.lifecycle; -import tanya.meta.trait; -deprecated("Use tanya.meta.trait.stateSize instead") -public import tanya.meta.trait : stateSize; - -/** - * The mixin generates common methods for classes and structs using - * allocators. It provides a protected member, constructor and a read-only property, - * that checks if an allocator was already set and sets it to the default - * one, if not (useful for structs which don't have a default constructor). - */ -mixin template DefaultAllocator() -{ - /// Allocator. - protected shared Allocator allocator_; - - /** - * Params: - * allocator = The allocator should be used. - * - * Precondition: $(D_INLINECODE allocator_ !is null) - */ - this(shared Allocator allocator) @nogc nothrow pure @safe - in (allocator !is null) - { - this.allocator_ = allocator; - } - - /** - * This property checks if the allocator was set in the constructor - * and sets it to the default one, if not. - * - * Returns: Used allocator. - * - * Postcondition: $(D_INLINECODE allocator !is null) - */ - @property shared(Allocator) allocator() @nogc nothrow pure @safe - out (allocator; allocator !is null) - { - if (allocator_ is null) - { - allocator_ = defaultAllocator; - } - return allocator_; - } - - /// ditto - @property shared(Allocator) allocator() const @nogc nothrow pure @trusted - out (allocator; allocator !is null) - { - if (allocator_ is null) - { - return defaultAllocator; - } - return cast(shared Allocator) allocator_; - } -} - -shared Allocator allocator; - -private shared(Allocator) getAllocatorInstance() @nogc nothrow -{ - if (allocator is null) - { - version (TanyaNative) - { - import tanya.memory.mmappool; - defaultAllocator = MmapPool.instance; - } - else - { - import tanya.memory.mallocator; - defaultAllocator = Mallocator.instance; - } - } - return allocator; -} - -/** - * Returns: Default allocator. - * - * Postcondition: $(D_INLINECODE allocator !is null). - */ -@property shared(Allocator) defaultAllocator() @nogc nothrow pure @trusted -out (allocator; allocator !is null) -{ - return (cast(GetPureInstance!Allocator) &getAllocatorInstance)(); -} - -/** - * Sets the default allocator. - * - * Params: - * allocator = $(D_PSYMBOL Allocator) instance. - * - * Precondition: $(D_INLINECODE allocator !is null). - */ -@property void defaultAllocator(shared(Allocator) allocator) @nogc nothrow @safe -in (allocator !is null) -{ - .allocator = allocator; -} - -/** - * Params: - * size = Raw size. - * alignment = Alignment. - * - * Returns: Aligned size. - */ -size_t alignedSize(const size_t size, const size_t alignment = 8) -pure nothrow @safe @nogc -{ - return (size - 1) / alignment * alignment + alignment; -} diff --git a/memory/tanya/memory/smartref.d b/memory/tanya/memory/smartref.d deleted file mode 100644 index 1fe81f3..0000000 --- a/memory/tanya/memory/smartref.d +++ /dev/null @@ -1,903 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Smart pointers. - * - * A smart pointer is an object that wraps a raw pointer or a reference - * (class, dynamic array) to manage its lifetime. - * - * This module provides two kinds of lifetime management strategies: - * $(UL - * $(LI Reference counting) - * $(LI Unique ownership) - * ) - * - * Copyright: Eugene Wissner 2016-2019. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/memory/tanya/memory/smartref.d, - * tanya/memory/smartref.d) - */ -module tanya.memory.smartref; - -import tanya.memory; -import tanya.meta.trait; -version (unittest) import tanya.test.stub; - -private template Payload(T) -{ - static if (isPolymorphicType!T || isDynamicArray!T) - { - alias Payload = T; - } - else - { - alias Payload = T*; - } -} - -private final class RefCountedStore(T) -{ - T payload; - size_t counter = 1; - - size_t opUnary(string op)() - if (op == "--" || op == "++") - in - { - assert(this.counter > 0); - } - do - { - mixin("return " ~ op ~ "counter;"); - } - - int opCmp(const size_t counter) - { - if (this.counter > counter) - { - return 1; - } - else if (this.counter < counter) - { - return -1; - } - else - { - return 0; - } - } -} - -private void separateDeleter(T)(RefCountedStore!T storage, - shared Allocator allocator) -{ - allocator.dispose(storage.payload); - allocator.dispose(storage); -} - -private void unifiedDeleter(T)(RefCountedStore!T storage, - shared Allocator allocator) -{ - auto ptr1 = finalize(storage); - auto ptr2 = finalize(storage.payload); - allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]); -} - -/** - * Reference-counted object containing a $(D_PARAM T) value as payload. - * $(D_PSYMBOL RefCounted) keeps track of all references of an object, and - * when the reference count goes down to zero, frees the underlying store. - * - * Params: - * T = Type of the reference-counted value. - */ -struct RefCounted(T) -{ - private alias Storage = RefCountedStore!(Payload!T); - - private Storage storage; - private void function(Storage storage, - shared Allocator allocator) @nogc deleter; - - invariant - { - assert(this.storage is null || this.allocator_ !is null); - assert(this.storage is null || this.deleter !is null); - } - - /** - * Takes ownership over $(D_PARAM value), setting the counter to 1. - * $(D_PARAM value) may be a pointer, an object or a dynamic array. - * - * Params: - * value = Value whose ownership is taken over. - * allocator = Allocator used to destroy the $(D_PARAM value) and to - * allocate/deallocate internal storage. - * - * Precondition: $(D_INLINECODE allocator !is null) - */ - this(Payload!T value, shared Allocator allocator = defaultAllocator) - { - this(allocator); - this.storage = allocator.make!Storage(); - this.deleter = &separateDeleter!(Payload!T); - - this.storage.payload = value; - } - - /// ditto - this(shared Allocator allocator) - in - { - assert(allocator !is null); - } - do - { - this.allocator_ = allocator; - } - - /** - * Increases the reference counter by one. - */ - this(this) - { - if (count != 0) - { - ++this.storage; - } - } - - /** - * Decreases the reference counter by one. - * - * If the counter reaches 0, destroys the owned object. - */ - ~this() - { - if (this.storage !is null && !(this.storage > 0 && --this.storage)) - { - deleter(this.storage, allocator); - } - } - - /** - * Takes ownership over $(D_PARAM rhs). Initializes this - * $(D_PSYMBOL RefCounted) if needed. - * - * If it is the last reference of the previously owned object, - * it will be destroyed. - * - * To reset $(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) and assign it. - * - * Params: - * rhs = New object. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(Payload!T rhs) - { - if (this.storage is null) - { - this.storage = allocator.make!Storage(); - this.deleter = &separateDeleter!(Payload!T); - } - else if (this.storage > 1) - { - --this.storage; - this.storage = allocator.make!Storage(); - this.deleter = &separateDeleter!(Payload!T); - } - else - { - finalize(this.storage.payload); - this.storage.payload = Payload!T.init; - } - this.storage.payload = rhs; - return this; - } - - /// ditto - ref typeof(this) opAssign(typeof(null)) - { - if (this.storage is null) - { - return this; - } - else if (this.storage > 1) - { - --this.storage; - } - else - { - deleter(this.storage, allocator); - } - this.storage = null; - - return this; - } - - /// ditto - ref typeof(this) opAssign(typeof(this) rhs) - { - swap(this.allocator_, rhs.allocator_); - swap(this.storage, rhs.storage); - swap(this.deleter, rhs.deleter); - return this; - } - - /** - * Returns: Reference to the owned object. - * - * Precondition: $(D_INLINECODE cound > 0). - */ - inout(Payload!T) get() inout - in - { - assert(count > 0, "Attempted to access an uninitialized reference"); - } - do - { - return this.storage.payload; - } - - version (D_Ddoc) - { - /** - * Dereferences the pointer. It is defined only for pointers, not for - * reference types like classes, that can be accessed directly. - * - * Params: - * op = Operation. - * - * Returns: Reference to the pointed value. - */ - ref inout(T) opUnary(string op)() inout - if (op == "*"); - } - else static if (isPointer!(Payload!T)) - { - ref inout(T) opUnary(string op)() inout - if (op == "*") - { - return *this.storage.payload; - } - } - - /** - * Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal - * storage. - */ - @property bool isInitialized() const - { - return this.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`. - */ - @property size_t count() const - { - return this.storage is null ? 0 : this.storage.counter; - } - - mixin DefaultAllocator; - alias get this; -} - -/// -@nogc @system 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); -} - -@nogc @system unittest -{ - auto rc = defaultAllocator.refCounted!int(5); - rc = defaultAllocator.make!int(7); - assert(*rc == 7); -} - -@nogc @system unittest -{ - RefCounted!int rc; - assert(!rc.isInitialized); - rc = null; - assert(!rc.isInitialized); -} - -@nogc @system unittest -{ - auto rc = defaultAllocator.refCounted!int(5); - - void func(RefCounted!int param) @nogc - { - assert(param.count == 2); - param = defaultAllocator.make!int(7); - assert(param.count == 1); - assert(*param == 7); - } - func(rc); - assert(rc.count == 1); - assert(*rc == 5); -} - -@nogc @system unittest -{ - RefCounted!int rc; - - void func(RefCounted!int param) @nogc - { - assert(param.count == 0); - param = defaultAllocator.make!int(7); - assert(param.count == 1); - assert(*param == 7); - } - func(rc); - assert(rc.count == 0); -} - -@nogc @system unittest -{ - RefCounted!int rc1, rc2; - static assert(is(typeof(rc1 = rc2))); -} - -version (unittest) -{ - private class A - { - uint *destroyed; - - this(ref uint destroyed) @nogc - { - this.destroyed = &destroyed; - } - - ~this() @nogc - { - ++(*destroyed); - } - } - - private struct B - { - int prop; - @disable this(); - this(int param1) @nogc - { - prop = param1; - } - } -} - -@nogc @system unittest -{ - uint destroyed; - auto a = defaultAllocator.make!A(destroyed); - - assert(destroyed == 0); - { - auto rc = RefCounted!A(a, defaultAllocator); - assert(rc.count == 1); - - void func(RefCounted!A rc) @nogc @system - { - assert(rc.count == 2); - } - func(rc); - - assert(rc.count == 1); - } - assert(destroyed == 1); - - RefCounted!int rc; - assert(rc.count == 0); - rc = defaultAllocator.make!int(8); - assert(rc.count == 1); -} - -@nogc @system unittest -{ - auto rc = RefCounted!int(defaultAllocator); - assert(!rc.isInitialized); - assert(rc.allocator is defaultAllocator); -} - -@nogc @system unittest -{ - auto rc = defaultAllocator.refCounted!int(5); - assert(rc.count == 1); - - void func(RefCounted!int rc) @nogc - { - assert(rc.count == 2); - rc = null; - assert(!rc.isInitialized); - assert(rc.count == 0); - } - - assert(rc.count == 1); - func(rc); - assert(rc.count == 1); - - rc = null; - assert(!rc.isInitialized); - assert(rc.count == 0); -} - -@nogc @system unittest -{ - auto rc = defaultAllocator.refCounted!int(5); - assert(*rc == 5); - - void func(RefCounted!int rc) @nogc - { - assert(rc.count == 2); - rc = defaultAllocator.refCounted!int(4); - assert(*rc == 4); - assert(rc.count == 1); - } - func(rc); - assert(*rc == 5); -} - -@nogc nothrow pure @safe unittest -{ - 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)); -} - -/** - * Constructs a new object of type $(D_PARAM T) and wraps it in a - * $(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). - * allocator = Allocator. - * args = Constructor arguments of $(D_PARAM T). - * - * Returns: Newly created $(D_PSYMBOL RefCounted!T). - * - * Precondition: $(D_INLINECODE allocator !is null) - */ -RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args) -if (!is(T == interface) && !isAbstractClass!T - && !isAssociativeArray!T && !isArray!T) -in -{ - assert(allocator !is null); -} -do -{ - auto rc = typeof(return)(allocator); - - const storageSize = alignedSize(stateSize!(RefCounted!T.Storage)); - const size = alignedSize(stateSize!T + storageSize); - - auto mem = (() @trusted => allocator.allocate(size))(); - if (mem is null) - { - onOutOfMemoryError(); - } - scope (failure) - { - () @trusted { allocator.deallocate(mem); }(); - } - rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]); - rc.storage.payload = emplace!T(mem[storageSize .. $], args); - - rc.deleter = &unifiedDeleter!(Payload!T); - return rc; -} - -/** - * Constructs a new array with $(D_PARAM size) elements and wraps it in a - * $(D_PSYMBOL RefCounted). - * - * Params: - * T = Array type. - * E = Array element type. - * size = Array size. - * allocator = Allocator. - * - * Returns: Newly created $(D_PSYMBOL RefCounted!T). - * - * Precondition: $(D_INLINECODE allocator !is null - * && size <= size_t.max / E.sizeof) - */ -RefCounted!T refCounted(T : E[], E)(shared Allocator allocator, size_t size) -@trusted -in (allocator !is null) -in (size <= size_t.max / E.sizeof) -{ - return RefCounted!T(allocator.make!T(size), allocator); -} - -/// -@nogc @system unittest -{ - auto rc = defaultAllocator.refCounted!int(5); - assert(rc.count == 1); - - void func(RefCounted!int param) @nogc - { - if (param.count == 2) - { - func(param); - } - else - { - assert(param.count == 3); - } - } - func(rc); - - assert(rc.count == 1); -} - -@nogc @system unittest -{ - struct E - { - } - 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()))); - static assert(!is(typeof(defaultAllocator.refCounted!E(5)))); - { - auto rc = defaultAllocator.refCounted!B(3); - assert(rc.get().prop == 3); - } - { - auto rc = defaultAllocator.refCounted!E(); - assert(rc.count); - } -} - -@nogc @system unittest -{ - auto rc = defaultAllocator.refCounted!(int[])(5); - assert(rc.length == 5); -} - -@nogc @system unittest -{ - auto p1 = defaultAllocator.make!int(5); - auto p2 = p1; - auto rc = RefCounted!int(p1, defaultAllocator); - assert(rc.get() is p2); -} - -@nogc @system unittest -{ - size_t destroyed; - { - auto rc = defaultAllocator.refCounted!WithDtor(destroyed); - } - assert(destroyed == 1); -} - -/** - * $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope. - * - * Params: - * T = Value type. - */ -struct Unique(T) -{ - private Payload!T payload; - - invariant - { - assert(payload is null || allocator_ !is null); - } - - /** - * Takes ownership over $(D_PARAM value), setting the counter to 1. - * $(D_PARAM value) may be a pointer, an object or a dynamic array. - * - * Params: - * value = Value whose ownership is taken over. - * allocator = Allocator used to destroy the $(D_PARAM value) and to - * allocate/deallocate internal storage. - * - * Precondition: $(D_INLINECODE allocator !is null) - */ - this(Payload!T value, shared Allocator allocator = defaultAllocator) - { - this(allocator); - this.payload = value; - } - - /// ditto - this(shared Allocator allocator) - in - { - assert(allocator !is null); - } - do - { - this.allocator_ = allocator; - } - - /** - * $(D_PSYMBOL Unique) is noncopyable. - */ - @disable this(this); - - /** - * Destroys the owned object. - */ - ~this() - { - allocator.dispose(this.payload); - } - - /** - * Initialized this $(D_PARAM Unique) and takes ownership over - * $(D_PARAM rhs). - * - * To reset $(D_PSYMBOL Unique) 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 Unique) and assign it. - * - * Params: - * rhs = New object. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign(Payload!T rhs) - { - allocator.dispose(this.payload); - this.payload = rhs; - return this; - } - - /// ditto - ref typeof(this) opAssign(typeof(null)) - { - allocator.dispose(this.payload); - return this; - } - - /// ditto - ref typeof(this) opAssign(typeof(this) rhs) - { - swap(this.allocator_, rhs.allocator_); - swap(this.payload, rhs.payload); - - return this; - } - - /// - @nogc nothrow pure @system unittest - { - auto rc = defaultAllocator.unique!int(5); - rc = defaultAllocator.make!int(7); - assert(*rc == 7); - } - - /** - * Returns: Reference to the owned object. - */ - inout(Payload!T) get() inout - { - return this.payload; - } - - version (D_Ddoc) - { - /** - * Dereferences the pointer. It is defined only for pointers, not for - * reference types like classes, that can be accessed directly. - * - * Params: - * op = Operation. - * - * Returns: Reference to the pointed value. - */ - ref inout(T) opUnary(string op)() inout - if (op == "*"); - } - else static if (isPointer!(Payload!T)) - { - ref inout(T) opUnary(string op)() inout - if (op == "*") - { - return *this.payload; - } - } - - /** - * Returns: Whether this $(D_PSYMBOL Unique) holds some value. - */ - @property bool isInitialized() const - { - return this.payload !is null; - } - - /// - @nogc nothrow pure @system unittest - { - Unique!int u; - assert(!u.isInitialized); - } - - /** - * Sets the internal pointer to $(D_KEYWORD). The allocator isn't changed. - * - * Returns: Reference to the owned object. - */ - Payload!T release() - { - auto payload = this.payload; - this.payload = null; - return payload; - } - - /// - @nogc nothrow pure @system unittest - { - auto u = defaultAllocator.unique!int(5); - assert(u.isInitialized); - - auto i = u.release(); - assert(*i == 5); - assert(!u.isInitialized); - } - - mixin DefaultAllocator; - alias get this; -} - -/// -@nogc nothrow pure @system unittest -{ - auto p = defaultAllocator.make!int(5); - auto s = Unique!int(p, defaultAllocator); - assert(*s == 5); -} - -/// -@nogc nothrow @system unittest -{ - static bool destroyed; - - static struct F - { - ~this() @nogc nothrow @safe - { - destroyed = true; - } - } - { - auto s = Unique!F(defaultAllocator.make!F(), defaultAllocator); - } - assert(destroyed); -} - -/** - * Constructs a new object of type $(D_PARAM T) and wraps it in a - * $(D_PSYMBOL Unique) using $(D_PARAM args) as the parameter list for - * the constructor of $(D_PARAM T). - * - * Params: - * T = Type of the constructed object. - * A = Types of the arguments to the constructor of $(D_PARAM T). - * allocator = Allocator. - * args = Constructor arguments of $(D_PARAM T). - * - * Returns: Newly created $(D_PSYMBOL Unique!T). - * - * Precondition: $(D_INLINECODE allocator !is null) - */ -Unique!T unique(T, A...)(shared Allocator allocator, auto ref A args) -if (!is(T == interface) && !isAbstractClass!T - && !isAssociativeArray!T && !isArray!T) -in -{ - assert(allocator !is null); -} -do -{ - auto payload = allocator.make!(T, A)(args); - return Unique!T(payload, allocator); -} - -/** - * Constructs a new array with $(D_PARAM size) elements and wraps it in a - * $(D_PSYMBOL Unique). - * - * Params: - * T = Array type. - * E = Array element type. - * size = Array size. - * allocator = Allocator. - * - * Returns: Newly created $(D_PSYMBOL Unique!T). - * - * Precondition: $(D_INLINECODE allocator !is null - * && size <= size_t.max / E.sizeof) - */ -Unique!T unique(T : E[], E)(shared Allocator allocator, size_t size) -@trusted -in (allocator !is null) -in (size <= size_t.max / E.sizeof) -{ - auto payload = allocator.resize!E(null, size); - return Unique!T(payload, allocator); -} - -@nogc nothrow pure @safe unittest -{ - static assert(is(typeof(defaultAllocator.unique!B(5)))); - static assert(is(typeof(defaultAllocator.unique!(int[])(5)))); -} - -@nogc nothrow pure @system unittest -{ - auto s = defaultAllocator.unique!int(5); - assert(*s == 5); - - s = null; - assert(s is null); -} - -@nogc nothrow pure @system unittest -{ - auto s = defaultAllocator.unique!int(5); - assert(*s == 5); - - s = defaultAllocator.unique!int(4); - assert(*s == 4); -} - -@nogc nothrow pure @system unittest -{ - auto p1 = defaultAllocator.make!int(5); - auto p2 = p1; - - auto rc = Unique!int(p1, defaultAllocator); - assert(rc.get() is p2); -} - -@nogc nothrow pure @system unittest -{ - auto rc = Unique!int(defaultAllocator); - assert(rc.allocator is defaultAllocator); -} diff --git a/memory/tanya/test/assertion.d b/memory/tanya/test/assertion.d deleted file mode 100644 index 10105d7..0000000 --- a/memory/tanya/test/assertion.d +++ /dev/null @@ -1,105 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Additional assertions. - * - * This module provides functions that assert whether a given expression - * satisfies some complex condition, that can't be tested with - * $(D_KEYWORD assert) in a single line. Internally all the functions - * just evaluate the expression and call $(D_KEYWORD assert). - * - * The functions can cause segmentation fault if the module is compiled - * in production mode and the condition fails. - * - * Copyright: Eugene Wissner 2017-2018. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/assertion.d, - * tanya/test/assertion.d) - */ -module tanya.test.assertion; - -import tanya.memory; -import tanya.meta.trait; - -/** - * Asserts whether the function $(D_PARAM expr) throws an exception of type - * $(D_PARAM E). If it does, the exception is catched and properly destroyed. - * If it doesn't, an assertion error is thrown. If the exception doesn't match - * $(D_PARAM E) type, it isn't catched and escapes. - * - * Params: - * E = Expected exception type. - * T = Throwing function type. - * Args = Argument types of the throwing function. - * expr = Throwing function. - * args = Arguments for $(D_PARAM expr). - */ -void assertThrown(E : Exception, T, Args...)(T expr, auto ref Args args) -if (isSomeFunction!T) -{ - try - { - cast(void) expr(args); - assert(false, "Expected exception not thrown"); - } - catch (E exception) - { - defaultAllocator.dispose(exception); - } -} - -/// -@nogc nothrow pure @safe unittest -{ - // If you want to test that an expression throws, you can wrap it into an - // arrow function. - static struct CtorThrows - { - this(int i) @nogc pure @safe - { - throw defaultAllocator.make!Exception(); - } - } - assertThrown!Exception(() => CtorThrows(8)); -} - -/** - * Asserts that the function $(D_PARAM expr) doesn't throw. - * - * If it does, the thrown exception is catched, properly destroyed and an - * assertion error is thrown instead. - * - * Params: - * T = Tested function type. - * Args = Argument types of $(D_PARAM expr). - * expr = Tested function. - * args = Arguments for $(D_PARAM expr). - */ -void assertNotThrown(T, Args...)(T expr, auto ref Args args) -if (isSomeFunction!T) -{ - try - { - cast(void) expr(args); - } - catch (Exception exception) - { - defaultAllocator.dispose(exception); - assert(false, "Unexpected exception thrown"); - } -} - -/// -@nogc nothrow pure @safe unittest -{ - // If you want to test that an expression doesn't throw, you can wrap it - // into an arrow function. - static struct S - { - } - assertNotThrown(() => S()); -} diff --git a/memory/tanya/test/package.d b/memory/tanya/test/package.d deleted file mode 100644 index ab6f861..0000000 --- a/memory/tanya/test/package.d +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Test suite for $(D_KEYWORD unittest)-blocks. - * - * Copyright: Eugene Wissner 2017-2018. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/package.d, - * tanya/test/package.d) - */ -module tanya.test; - -public import tanya.test.assertion; -public import tanya.test.stub; diff --git a/memory/tanya/test/stub.d b/memory/tanya/test/stub.d deleted file mode 100644 index e1f8dcb..0000000 --- a/memory/tanya/test/stub.d +++ /dev/null @@ -1,373 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Range and generic type generators. - * - * Copyright: Eugene Wissner 2018. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/stub.d, - * tanya/test/stub.d) - */ -module tanya.test.stub; - -/** - * Attribute signalizing that the generated range should contain the given - * number of elements. - * - * $(D_PSYMBOL Count) should be always specified with some value and not as a - * type, so $(D_INLINECODE Count(1)) instead just $(D_INLINECODE Count), - * otherwise you can just omit $(D_PSYMBOL Count) and it will default to 0. - * - * $(D_PSYMBOL Count) doesn't generate `.length` property - use - * $(D_PSYMBOL Length) for that. - * - * If neither $(D_PSYMBOL Length) nor $(D_PSYMBOL Infinite) is given, - * $(D_ILNINECODE Count(0)) is assumed. - * - * This attribute conflicts with $(D_PSYMBOL Infinite) and $(D_PSYMBOL Length). - */ -struct Count -{ - /// Original range length. - size_t count = 0; - - @disable this(); - - /** - * Constructs the attribute with the given length. - * - * Params: - * count = Original range length. - */ - this(size_t count) @nogc nothrow pure @safe - { - this.count = count; - } -} - -/** - * Attribute signalizing that the generated range should be infinite. - * - * This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Length). - */ -struct Infinite -{ -} - -/** - * Generates `.length` property for the range. - * - * The length of the range can be specified as a constructor argument, - * otherwise it is 0. - * - * This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Infinite). - */ -struct Length -{ - /// Original range length. - size_t length = 0; -} - -/** - * Attribute signalizing that the generated range should return values by - * reference. - * - * This atribute affects the return values of `.front`, `.back` and `[]`. - */ -struct WithLvalueElements -{ -} - -/** - * Generates an input range. - * - * Params: - * E = Element type. - */ -mixin template InputRangeStub(E = int) -{ - import tanya.meta.metafunction : Alias; - import tanya.meta.trait : evalUDA, getUDAs, hasUDA; - - /* - * Aliases for the attribute lookups to access them faster - */ - private enum bool infinite = hasUDA!(typeof(this), Infinite); - private enum bool withLvalueElements = hasUDA!(typeof(this), - WithLvalueElements); - private alias Count = getUDAs!(typeof(this), .Count); - private alias Length = getUDAs!(typeof(this), .Length); - - static if (Count.length != 0) - { - private enum size_t count = Count[0].count; - - static assert (!infinite, - "Range cannot have count and be infinite at the same time"); - static assert (Length.length == 0, - "Range cannot have count and length at the same time"); - } - else static if (Length.length != 0) - { - private enum size_t count = evalUDA!(Length[0]).length; - - static assert (!infinite, - "Range cannot have length and be infinite at the same time"); - } - else static if (!infinite) - { - private enum size_t count = 0; - } - - /* - * Member generation - */ - static if (infinite) - { - enum bool empty = false; - } - else - { - private size_t length_ = count; - - @property bool empty() const @nogc nothrow pure @safe - { - return this.length_ == 0; - } - } - - static if (withLvalueElements) - { - private E* element; // Pointer to enable range copying in save() - } - - void popFront() @nogc nothrow pure @safe - in (!empty) - { - static if (!infinite) - { - --this.length_; - } - } - - static if (withLvalueElements) - { - ref E front() @nogc nothrow pure @safe - in (!empty) - { - return *this.element; - } - } - else - { - E front() @nogc nothrow pure @safe - in (!empty) - { - return E.init; - } - } - - static if (Length.length != 0) - { - size_t length() const @nogc nothrow pure @safe - { - return this.length_; - } - } -} - -/** - * Generates a forward range. - * - * This mixin includes input range primitives as well, but can be combined with - * $(D_PSYMBOL RandomAccessRangeStub). - * - * Params: - * E = Element type. - */ -mixin template ForwardRangeStub(E = int) -{ - static if (!is(typeof(this.InputRangeMixin) == void)) - { - mixin InputRangeStub!E InputRangeMixin; - } - - auto save() @nogc nothrow pure @safe - { - return this; - } -} - -/** - * Generates a bidirectional range. - * - * This mixin includes forward range primitives as well, but can be combined with - * $(D_PSYMBOL RandomAccessRangeStub). - * - * Params: - * E = Element type. - */ -mixin template BidirectionalRangeStub(E = int) -{ - mixin ForwardRangeStub!E; - - void popBack() @nogc nothrow pure @safe - in (!empty) - { - static if (!infinite) - { - --this.length_; - } - } - - static if (withLvalueElements) - { - ref E back() @nogc nothrow pure @safe - in (!empty) - { - return *this.element; - } - } - else - { - E back() @nogc nothrow pure @safe - in (!empty) - { - return E.init; - } - } -} - -/** - * Generates a random-access range. - * - * This mixin includes input range primitives as well, but can be combined with - * $(D_PSYMBOL ForwardRangeStub) or $(D_PSYMBOL BidirectionalRangeStub). - * - * Note that a random-access range also requires $(D_PSYMBOL Length) or - * $(D_PARAM Infinite) by definition. - * - * Params: - * E = Element type. - */ -mixin template RandomAccessRangeStub(E = int) -{ - static if (!is(typeof(this.InputRangeMixin) == void)) - { - mixin InputRangeStub!E InputRangeMixin; - } - - static if (withLvalueElements) - { - ref E opIndex(size_t) @nogc nothrow pure @safe - { - return *this.element; - } - } - else - { - E opIndex(size_t) @nogc nothrow pure @safe - { - return E.init; - } - } -} - -/** - * Struct with a disabled postblit constructor. - * - * $(D_PSYMBOL NonCopyable) can be used as an attribute for - * $(D_PSYMBOL StructStub) or as a standalone type. - */ -struct NonCopyable -{ - @disable this(this); -} - -/** - * Struct with an elaborate destructor. - * - * $(D_PSYMBOL WithDtor) can be used as an attribute for - * $(D_PSYMBOL StructStub) or as a standalone type. - * - * When used as a standalone object the constructor of $(D_PSYMBOL WithDtor) - * accepts an additional `counter` argument, which is incremented by the - * destructor. $(D_PSYMBOL WithDtor) stores a pointer to the passed variable, - * so the variable can be investigated after the struct isn't available - * anymore. - */ -struct WithDtor -{ - size_t* counter; - - this(ref size_t counter) @nogc nothrow pure @trusted - { - this.counter = &counter; - } - - ~this() @nogc nothrow pure @safe - { - if (this.counter !is null) - { - ++*this.counter; - } - } -} - -/** - * Struct supporting hashing. - * - * $(D_PSYMBOL Hashable) can be used as an attribute for - * $(D_PSYMBOL StructStub) or as a standalone type. - * - * The constructor accepts an additional parameter, which is returned by the - * `toHash()`-function. `0U` is returned if no hash value is given. - */ -struct Hashable -{ - size_t hash; - - size_t toHash() const @nogc nothrow pure @safe - { - return this.hash; - } -} - -/** - * Generates a $(D_KEYWORD struct) with common functionality. - * - * To specify the needed functionality use user-defined attributes on the - * $(D_KEYWORD struct) $(D_PSYMBOL StructStub) is mixed in. - * - * Supported attributes are: $(D_PSYMBOL NonCopyable), $(D_PSYMBOL Hashable), - * $(D_PSYMBOL WithDtor). - */ -mixin template StructStub() -{ - import tanya.meta.trait : evalUDA, getUDAs, hasUDA; - - static if (hasUDA!(typeof(this), NonCopyable)) - { - @disable this(this); - } - - private alias Hashable = getUDAs!(typeof(this), .Hashable); - static if (Hashable.length > 0) - { - size_t toHash() const @nogc nothrow pure @safe - { - return evalUDA!(Hashable[0]).hash; - } - } - - static if (hasUDA!(typeof(this), WithDtor)) - { - ~this() @nogc nothrow pure @safe - { - } - } -} diff --git a/meta/tanya/meta/metafunction.d b/meta/tanya/meta/metafunction.d index a05b5cf..8ccafbc 100644 --- a/meta/tanya/meta/metafunction.d +++ b/meta/tanya/meta/metafunction.d @@ -1129,30 +1129,6 @@ if (__traits(isTemplate, cmp)) static assert(!isSorted!(cmp, long, byte, ubyte, short, uint)); } -@nogc nothrow pure @safe unittest -{ - enum cmp(int x, int y) = x - y; - static assert(isSorted!(cmp)); - static assert(isSorted!(cmp, 1)); - static assert(isSorted!(cmp, 1, 2, 2)); - static assert(isSorted!(cmp, 1, 2, 2, 4)); - static assert(isSorted!(cmp, 1, 2, 2, 4, 8)); - static assert(!isSorted!(cmp, 32, 2, 2, 4, 8)); - static assert(isSorted!(cmp, 32, 32)); -} - -@nogc nothrow pure @safe unittest -{ - enum cmp(int x, int y) = x < y; - static assert(isSorted!(cmp)); - static assert(isSorted!(cmp, 1)); - static assert(isSorted!(cmp, 1, 2, 2)); - static assert(isSorted!(cmp, 1, 2, 2, 4)); - static assert(isSorted!(cmp, 1, 2, 2, 4, 8)); - static assert(!isSorted!(cmp, 32, 2, 2, 4, 8)); - static assert(isSorted!(cmp, 32, 32)); -} - /** * Params: * T = A template. diff --git a/meta/tanya/meta/trait.d b/meta/tanya/meta/trait.d index ebea9ce..ae83f24 100644 --- a/meta/tanya/meta/trait.d +++ b/meta/tanya/meta/trait.d @@ -549,19 +549,6 @@ template isPointer(T) static assert(!isPointer!bool); } -// typeof(null) is not a pointer. -@nogc nothrow pure @safe unittest -{ - static assert(!isPointer!(typeof(null))); - static assert(!isPointer!(const shared typeof(null))); - - enum typeOfNull : typeof(null) - { - null_ = null, - } - static assert(!isPointer!typeOfNull); -} - /** * Determines whether $(D_PARAM T) is an array type (dynamic or static, but * not an associative one). @@ -1478,20 +1465,6 @@ if (F.length == 1) static assert(!isCallable!I); } -@nogc nothrow pure @safe unittest -{ - static struct S - { - @property int opCall() - { - return 0; - } - } - S s; - static assert(isCallable!S); - static assert(isCallable!s); -} - /** * Determines whether $(D_PARAM T) defines a symbol $(D_PARAM member). * @@ -1676,63 +1649,6 @@ if (isCallable!F) static assert(is(FunctionTypeOf!(() {}) == function)); } -@nogc nothrow pure @safe unittest -{ - static assert(is(FunctionTypeOf!(void delegate()) == function)); - - static void staticFunc() - { - } - auto functionPointer = &staticFunc; - static assert(is(FunctionTypeOf!staticFunc == function)); - static assert(is(FunctionTypeOf!functionPointer == function)); - - void func() - { - } - auto dg = &func; - static assert(is(FunctionTypeOf!func == function)); - static assert(is(FunctionTypeOf!dg == function)); - - interface I - { - @property int prop(); - } - static assert(is(FunctionTypeOf!(I.prop) == function)); - - static struct S - { - void opCall() - { - } - } - class C - { - static void opCall() - { - } - } - S s; - - static assert(is(FunctionTypeOf!s == function)); - static assert(is(FunctionTypeOf!C == function)); - static assert(is(FunctionTypeOf!S == function)); -} - -@nogc nothrow pure @safe unittest -{ - static struct S2 - { - @property int opCall() - { - return 0; - } - } - S2 s2; - static assert(is(FunctionTypeOf!S2 == function)); - static assert(is(FunctionTypeOf!s2 == function)); -} - /** * Determines the return type of the callable $(D_PARAM F). * @@ -2552,44 +2468,6 @@ template hasElaborateAssign(T) } } -@nogc nothrow pure @safe unittest -{ - static assert(!hasElaborateAssign!int); - - static struct S1 - { - void opAssign(S1) - { - } - } - static struct S2 - { - void opAssign(int) - { - } - } - static struct S3 - { - S1 s; - alias s this; - } - static assert(hasElaborateAssign!S1); - static assert(!hasElaborateAssign!(const S1)); - static assert(hasElaborateAssign!(S1[1])); - static assert(!hasElaborateAssign!(S1[0])); - static assert(!hasElaborateAssign!S2); - static assert(hasElaborateAssign!S3); - - static struct S4 - { - void opAssign(S4) - { - } - @disable this(this); - } - static assert(hasElaborateAssign!S4); -} - /** * Returns all members of $(D_KEYWORD enum) $(D_PARAM T). * @@ -2642,16 +2520,6 @@ if (is(T == enum)) static assert([EnumMembers!E] == [E.one, E.two, E.three]); } -// Produces a tuple for an enum with only one member -@nogc nothrow pure @safe unittest -{ - enum E : int - { - one = 0, - } - static assert(EnumMembers!E == AliasSeq!0); -} - /** * Different than $(D_INLINECODE T.alignof), which is the same for all class * types, $(D_PSYMBOL classInstanceOf) determines the alignment of the class @@ -2996,14 +2864,6 @@ template isInnerClass(T) static assert(!isInnerClass!(O.Fake)); } -@nogc nothrow pure @safe unittest -{ - class RefCountedStore(T) - { - } - static assert(!isInnerClass!(RefCountedStore!int)); -} - /** * Returns the types of all members of $(D_PARAM T). * @@ -3131,28 +2991,3 @@ enum bool isOrderingComparable(T) = ifTestable!(T, a => a > a); { static assert(isOrderingComparable!int); } - -@nogc nothrow pure @safe unittest -{ - static struct DisabledOpEquals - { - @disable bool opEquals(typeof(this)) @nogc nothrow pure @safe; - - int opCmp(typeof(this)) @nogc nothrow pure @safe - { - return 0; - } - } - static assert(!isEqualityComparable!DisabledOpEquals); - static assert(isOrderingComparable!DisabledOpEquals); - - static struct OpEquals - { - bool opEquals(typeof(this)) @nogc nothrow pure @safe - { - return true; - } - } - static assert(isEqualityComparable!OpEquals); - static assert(!isOrderingComparable!OpEquals); -} diff --git a/meta/tanya/meta/transform.d b/meta/tanya/meta/transform.d index 083e7ee..209e4fa 100644 --- a/meta/tanya/meta/transform.d +++ b/meta/tanya/meta/transform.d @@ -855,53 +855,6 @@ if (allSatisfy!(isType, Args)) static assert(is(CommonType!(const A, Object) == const Object)); } -@nogc nothrow pure @safe unittest -{ - static assert(is(CommonType!(void*, int*) == void*)); - static assert(is(CommonType!(void*, const(int)*) == const(void)*)); - static assert(is(CommonType!(void*, const(void)*) == const(void)*)); - static assert(is(CommonType!(int*, void*) == void*)); - static assert(is(CommonType!(const(int)*, void*) == const(void)*)); - static assert(is(CommonType!(const(void)*, void*) == const(void)*)); - - static assert(is(CommonType!() == void)); - static assert(is(CommonType!(int*, const(int)*) == const(int)*)); - static assert(is(CommonType!(int**, const(int)**) == const(int*)*)); - - static assert(is(CommonType!(float, double) == double)); - static assert(is(CommonType!(float, int) == void)); - - static assert(is(CommonType!(bool, const bool) == bool)); - static assert(is(CommonType!(int, bool) == void)); - static assert(is(CommonType!(int, void) == void)); - static assert(is(CommonType!(Object, void*) == void)); - - class A - { - } - static assert(is(CommonType!(A, Object) == Object)); - static assert(is(CommonType!(const(A)*, Object*) == const(Object)*)); - static assert(is(CommonType!(A, typeof(null)) == A)); - - class B : A - { - } - class C : A - { - } - static assert(is(CommonType!(B, C) == A)); - - static struct S - { - int opCast(T : int)() - { - return 1; - } - } - static assert(is(CommonType!(S, int) == void)); - static assert(is(CommonType!(const S, S) == const S)); -} - /** * Finds the type with the smallest size in the $(D_PARAM Args) list. If * several types have the same type, the leftmost is returned. diff --git a/middle/dub.json b/middle/dub.json new file mode 100644 index 0000000..eecf1d4 --- /dev/null +++ b/middle/dub.json @@ -0,0 +1,22 @@ +{ + "name": "middle", + "description": "Runtime, middle-level utilities", + "targetType": "library", + + "dependencies": { + "tanya:meta": "*", + "tanya:os": "*", + "tanya:sys": "*" + }, + + "dependencies-linux": { + "mir-linux-kernel": "~>1.0.0" + }, + + "sourcePaths": [ + "." + ], + "importPaths": [ + "." + ] +} diff --git a/middle/tanya/memory/allocator.d b/middle/tanya/memory/allocator.d new file mode 100644 index 0000000..bf356af --- /dev/null +++ b/middle/tanya/memory/allocator.d @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This module contains the interface for implementing custom allocators. + * + * Allocators are classes encapsulating memory allocation strategy. This allows + * to decouple memory management from the algorithms and the data. + * + * Copyright: Eugene Wissner 2016-2019. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/allocator.d, + * tanya/memory/allocator.d) + */ +module tanya.memory.allocator; + +/** + * Abstract class implementing a basic allocator. + */ +interface Allocator +{ + /** + * Returns: Alignment offered. + */ + @property uint alignment() const shared pure nothrow @safe @nogc; + + /** + * Allocates $(D_PARAM size) bytes of memory. + * + * Params: + * size = Amount of memory to allocate. + * + * Returns: Pointer to the new allocated memory. + */ + void[] allocate(size_t size) shared pure nothrow @nogc; + + /** + * Deallocates a memory block. + * + * Params: + * p = A pointer to the memory block to be freed. + * + * Returns: Whether the deallocation was successful. + */ + bool deallocate(void[] p) shared pure nothrow @nogc; + + /** + * Increases or decreases the size of a memory block. + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: Pointer to the allocated memory. + */ + bool reallocate(ref void[] p, size_t size) shared pure nothrow @nogc; + + /** + * Reallocates a memory block in place if possible or returns + * $(D_KEYWORD false). This function cannot be used to allocate or + * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or + * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false). + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. + */ + bool reallocateInPlace(ref void[] p, size_t size) + shared pure nothrow @nogc; +} + +package template GetPureInstance(T : Allocator) +{ + alias GetPureInstance = shared(T) function() + pure nothrow @nogc; +} diff --git a/middle/tanya/memory/lifetime.d b/middle/tanya/memory/lifetime.d new file mode 100644 index 0000000..9bdc786 --- /dev/null +++ b/middle/tanya/memory/lifetime.d @@ -0,0 +1,815 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Lifetime management functions, types and related exceptions. + * + * Copyright: Eugene Wissner 2019. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/lifetime.d, + * tanya/memory/lifetime.d) + */ +module tanya.memory.lifetime; + +import tanya.memory : defaultAllocator; +import tanya.memory.allocator; +import tanya.meta.metafunction; +import tanya.meta.trait; + +/** + * Error thrown if memory allocation fails. + */ +final class OutOfMemoryError : Error +{ + /** + * Constructs new error. + * + * Params: + * msg = The message for the exception. + * file = The file where the exception occurred. + * line = The line number where the exception occurred. + * next = The previous exception in the chain of exceptions, if any. + */ + this(string msg = "Out of memory", + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) @nogc nothrow pure @safe + { + super(msg, file, line, next); + } + + /// ditto + this(string msg, + Throwable next, + string file = __FILE__, + size_t line = __LINE__) @nogc nothrow pure @safe + { + super(msg, file, line, next); + } +} + +/** + * Allocates $(D_PSYMBOL OutOfMemoryError) in a static storage and throws it. + * + * Params: + * msg = Custom error message. + * + * Throws: $(D_PSYMBOL OutOfMemoryError). + */ +void onOutOfMemoryError(string msg = "Out of memory") +@nogc nothrow pure @trusted +{ + static ubyte[stateSize!OutOfMemoryError] memory; + alias PureType = OutOfMemoryError function(string) @nogc nothrow pure; + throw (cast(PureType) () => emplace!OutOfMemoryError(memory))(msg); +} + +// From druntime +extern (C) +private void _d_monitordelete(Object h, bool det) @nogc nothrow pure; + +/* + * Internal function used to create, resize or destroy a dynamic array. It + * may throw $(D_PSYMBOL OutOfMemoryError). The new + * allocated part of the array isn't initialized. 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. + * allocator = The allocator used for getting memory. + * array = A reference to the array being changed. + * length = New array length. + * + * Returns: $(D_PARAM array). + */ +package(tanya) T[] resize(T)(shared Allocator allocator, + auto ref T[] array, + const size_t length) @trusted +{ + if (length == 0) + { + if (allocator.deallocate(array)) + { + return null; + } + else + { + onOutOfMemoryError(); + } + } + + void[] buf = array; + if (!allocator.reallocate(buf, length * T.sizeof)) + { + onOutOfMemoryError(); + } + // Casting from void[] is unsafe, but we know we cast to the original type. + array = cast(T[]) buf; + + return array; +} + +/* + * Destroys the object. + * Returns the memory should be freed. + */ +package(tanya.memory) void[] finalize(T)(ref T* p) +{ + if (p is null) + { + return null; + } + static if (hasElaborateDestructor!T) + { + destroy(*p); + } + return (cast(void*) p)[0 .. T.sizeof]; +} + +package(tanya.memory) void[] finalize(T)(ref T p) +if (isPolymorphicType!T) +{ + if (p is null) + { + return null; + } + static if (is(T == interface)) + { + version(Windows) + { + import core.sys.windows.unknwn : IUnknown; + static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in " + ~ __PRETTY_FUNCTION__); + } + auto ob = cast(Object) p; + } + else + { + alias ob = p; + } + auto ptr = cast(void*) ob; + auto support = ptr[0 .. typeid(ob).initializer.length]; + + auto ppv = cast(void**) ptr; + if (!*ppv) + { + return null; + } + auto pc = cast(ClassInfo*) *ppv; + scope (exit) + { + *ppv = null; + } + + 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) + { + alias DtorType = void function(Object) pure nothrow @safe @nogc; + (cast(DtorType) c.destructor)(ob); + } + } + while ((c = c.base) !is null); + + if (ppv[1]) // if monitor is not null + { + _d_monitordelete(cast(Object) ptr, true); + } + return support; +} + +package(tanya.memory) void[] finalize(T)(ref T[] p) +{ + destroyAllImpl!(T[], T)(p); + return p; +} + +package(tanya) void destroyAllImpl(R, E)(R p) +{ + static if (hasElaborateDestructor!E) + { + foreach (ref e; p) + { + destroy(e); + } + } +} + +/** + * Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T). + * It is assumed the respective entities had been allocated with the same + * allocator. + * + * Params: + * T = Type of $(D_PARAM p). + * allocator = Allocator the $(D_PARAM p) was allocated with. + * p = Object or array to be destroyed. + */ +void dispose(T)(shared Allocator allocator, auto ref T p) +{ + () @trusted { allocator.deallocate(finalize(p)); }(); + p = null; +} + +/** + * Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args) + * as the parameter list for the constructor of $(D_PARAM T). + * + * Params: + * T = Class type. + * A = Types of the arguments to the constructor of $(D_PARAM T). + * allocator = Allocator. + * args = Constructor arguments of $(D_PARAM T). + * + * Returns: Newly created $(D_PSYMBOL T). + * + * Precondition: $(D_INLINECODE allocator !is null) + */ +T make(T, A...)(shared Allocator allocator, auto ref A args) +if (is(T == class)) +in (allocator !is null) +{ + auto mem = (() @trusted => allocator.allocate(stateSize!T))(); + if (mem is null) + { + onOutOfMemoryError(); + } + scope (failure) + { + () @trusted { allocator.deallocate(mem); }(); + } + + return emplace!T(mem[0 .. stateSize!T], args); +} + +/** + * Constructs a value object of type $(D_PARAM T) using $(D_PARAM args) + * as the parameter list for the constructor of $(D_PARAM T) and returns a + * pointer to the new object. + * + * Params: + * T = Object type. + * A = Types of the arguments to the constructor of $(D_PARAM T). + * allocator = Allocator. + * args = Constructor arguments of $(D_PARAM T). + * + * Returns: Pointer to the created object. + * + * Precondition: $(D_INLINECODE allocator !is null) + */ +T* make(T, A...)(shared Allocator allocator, auto ref A args) +if (!isPolymorphicType!T && !isAssociativeArray!T && !isArray!T) +in (allocator !is null) +{ + auto mem = (() @trusted => allocator.allocate(stateSize!T))(); + if (mem is null) + { + onOutOfMemoryError(); + } + scope (failure) + { + () @trusted { allocator.deallocate(mem); }(); + } + return emplace!T(mem[0 .. stateSize!T], args); +} + +/// +@nogc nothrow pure @safe unittest +{ + int* i = defaultAllocator.make!int(5); + assert(*i == 5); + defaultAllocator.dispose(i); +} + +/** + * Constructs a new array with $(D_PARAM n) elements. + * + * Params: + * T = Array type. + * E = Array element type. + * allocator = Allocator. + * n = Array size. + * + * Returns: Newly created array. + * + * Precondition: $(D_INLINECODE allocator !is null + * && n <= size_t.max / E.sizeof) + */ +T make(T : E[], E)(shared Allocator allocator, size_t n) +in (allocator !is null) +in (n <= size_t.max / E.sizeof) +{ + auto ret = allocator.resize!E(null, n); + + static if (hasElaborateDestructor!E) + { + for (auto range = ret; range.length != 0; range = range[1 .. $]) + { + emplace!E(cast(void[]) range[0 .. 1], E.init); + } + } + else + { + ret[] = E.init; + } + + return ret; +} + +/// +@nogc nothrow pure @safe unittest +{ + int[] i = defaultAllocator.make!(int[])(2); + assert(i.length == 2); + assert(i[0] == int.init && i[1] == int.init); + defaultAllocator.dispose(i); +} + +/** + * Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the + * given arguments. + * + * If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference + * of type $(D_PARAM T), otherwise a pointer to the constructed object is + * returned. + * + * If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer) + * should be an instance of the outer class. + * + * $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If + * $(D_PARAM T) isn't an aggregate type and doesn't have a constructor, + * $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`, + * `Args[0]` should be implicitly convertible to $(D_PARAM T) then. + * + * Params: + * T = Constructed type. + * U = Type of the outer class if $(D_PARAM T) is a nested class. + * Args = Types of the constructor arguments if $(D_PARAM T) has a constructor + * or the type of the initial value. + * outer = Outer class instance if $(D_PARAM T) is a nested class. + * args = Constructor arguments if $(D_PARAM T) has a constructor or the + * initial value. + * + * Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory). + * + * Precondition: `memory.length == stateSize!T`. + * Postcondition: $(D_PARAM memory) and the result point to the same memory. + */ +T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args) +if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U)) +in (memory.length >= stateSize!T) +out (result; memory.ptr is (() @trusted => cast(void*) result)()) +{ + import tanya.memory.op : copy; + + copy(typeid(T).initializer, memory); + + auto result = (() @trusted => cast(T) memory.ptr)(); + result.outer = outer; + + static if (is(typeof(result.__ctor(args)))) + { + result.__ctor(args); + } + + return result; +} + +/// ditto +T emplace(T, Args...)(void[] memory, auto ref Args args) +if (is(T == class) && !isAbstractClass!T && !isInnerClass!T) +in (memory.length == stateSize!T) +out (result; memory.ptr is (() @trusted => cast(void*) result)()) +{ + import tanya.memory.op : copy; + + copy(typeid(T).initializer, memory); + + auto result = (() @trusted => cast(T) memory.ptr)(); + static if (is(typeof(result.__ctor(args)))) + { + result.__ctor(args); + } + return result; +} + +/// +@nogc nothrow pure @safe unittest +{ + class C + { + int i = 5; + class Inner + { + int i; + + this(int param) pure nothrow @safe @nogc + { + this.i = param; + } + } + } + ubyte[stateSize!C] memory1; + ubyte[stateSize!(C.Inner)] memory2; + + auto c = emplace!C(memory1); + assert(c.i == 5); + + auto inner = emplace!(C.Inner)(memory2, c, 8); + assert(c.i == 5); + assert(inner.i == 8); + assert(inner.outer is c); +} + +/// ditto +T* emplace(T, Args...)(void[] memory, auto ref Args args) +if (!isAggregateType!T && (Args.length <= 1)) +in (memory.length >= T.sizeof) +out (result; memory.ptr is result) +{ + auto result = (() @trusted => cast(T*) memory.ptr)(); + static if (Args.length == 1) + { + *result = T(args[0]); + } + else + { + *result = T.init; + } + return result; +} + +private void initializeOne(T)(ref void[] memory, ref T* result) @trusted +{ + import tanya.memory.op : copy, fill; + + static if (!hasElaborateAssign!T && isAssignable!T) + { + *result = T.init; + } + else static if (__VERSION__ >= 2083 // __traits(isZeroInit) available. + && __traits(isZeroInit, T)) + { + memory.ptr[0 .. T.sizeof].fill!0; + } + else + { + static immutable T init = T.init; + copy((&init)[0 .. 1], memory); + } +} + +/// ditto +T* emplace(T, Args...)(void[] memory, auto ref Args args) +if (!isPolymorphicType!T && isAggregateType!T) +in (memory.length >= T.sizeof) +out (result; memory.ptr is result) +{ + auto result = (() @trusted => cast(T*) memory.ptr)(); + + static if (Args.length == 0) + { + static assert(is(typeof({ static T t; })), + "Default constructor is disabled"); + initializeOne(memory, result); + } + else static if (is(typeof(result.__ctor(args)))) + { + initializeOne(memory, result); + result.__ctor(args); + } + else static if (Args.length == 1 && is(typeof({ T t = args[0]; }))) + { + import tanya.memory.op : copy; + + ((ref arg) @trusted => + copy((cast(void*) &arg)[0 .. T.sizeof], memory))(args[0]); + static if (hasElaborateCopyConstructor!T) + { + result.__postblit(); + } + } + else static if (is(typeof({ T t = T(args); }))) + { + auto init = T(args); + (() @trusted => moveEmplace(init, *result))(); + } + else + { + static assert(false, + "Unable to construct value with the given arguments"); + } + return result; +} + +/// +@nogc nothrow pure @safe unittest +{ + ubyte[4] memory; + + auto i = emplace!int(memory); + static assert(is(typeof(i) == int*)); + assert(*i == 0); + + i = emplace!int(memory, 5); + assert(*i == 5); + + static struct S + { + int i; + @disable this(); + @disable this(this); + this(int i) @nogc nothrow pure @safe + { + this.i = i; + } + } + auto s = emplace!S(memory, 8); + static assert(is(typeof(s) == S*)); + assert(s.i == 8); +} + +private void deinitialize(bool zero, T)(ref T value) +{ + static if (is(T == U[S], U, size_t S)) + { + foreach (ref e; value) + { + deinitialize!zero(e); + } + } + else + { + import tanya.memory.op : copy, fill; + + static if (isNested!T) + { + // Don't override the context pointer. + enum size_t size = T.sizeof - (void*).sizeof; + } + else + { + enum size_t size = T.sizeof; + } + static if (zero) + { + fill!0((cast(void*) &value)[0 .. size]); + } + else + { + copy(typeid(T).initializer()[0 .. size], (&value)[0 .. 1]); + } + } +} + +/** + * Moves $(D_PARAM source) into $(D_PARAM target) assuming that + * $(D_PARAM target) isn't initialized. + * + * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places + * the $(D_PARAM source) into a valid but unspecified state, which means that + * after moving $(D_PARAM source) can be destroyed or assigned a new value, but + * accessing it yields an unspecified value. No postblits or destructors are + * called. If the $(D_PARAM target) should be destroyed before, use + * $(D_PSYMBOL move). + * + * $(D_PARAM source) and $(D_PARAM target) must be different objects. + * + * Params: + * T = Object type. + * source = Source object. + * target = Target object. + * + * See_Also: $(D_PSYMBOL move), + * $(D_PSYMBOL hasElaborateCopyConstructor), + * $(D_PSYMBOL hasElaborateDestructor). + * + * Precondition: `&source !is &target`. + */ +void moveEmplace(T)(ref T source, ref T target) @system +in +{ + assert(&source !is &target, "Source and target must be different"); +} +do +{ + static if (is(T == struct) || isStaticArray!T) + { + import tanya.memory.op : copy; + + copy((&source)[0 .. 1], (&target)[0 .. 1]); + + static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T) + { + static if (__VERSION__ >= 2083) // __traits(isZeroInit) available. + { + deinitialize!(__traits(isZeroInit, T))(source); + } + else + { + if (typeid(T).initializer().ptr is null) + { + deinitialize!true(source); + } + else + { + deinitialize!false(source); + } + } + } + } + else + { + target = source; + } +} + +/// +@nogc nothrow pure @system unittest +{ + static struct S + { + int member = 5; + + this(this) @nogc nothrow pure @safe + { + assert(false); + } + } + S source, target = void; + moveEmplace(source, target); + assert(target.member == 5); + + int x1 = 5, x2; + moveEmplace(x1, x2); + assert(x2 == 5); +} + +/** + * Moves $(D_PARAM source) into $(D_PARAM target) assuming that + * $(D_PARAM target) isn't initialized. + * + * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places + * the $(D_PARAM source) into a valid but unspecified state, which means that + * after moving $(D_PARAM source) can be destroyed or assigned a new value, but + * accessing it yields an unspecified value. $(D_PARAM target) is destroyed before + * the new value is assigned. If $(D_PARAM target) isn't initialized and + * therefore shouldn't be destroyed, $(D_PSYMBOL moveEmplace) can be used. + * + * If $(D_PARAM target) isn't specified, $(D_PSYMBOL move) returns the source + * as rvalue without calling its copy constructor or destructor. + * + * $(D_PARAM source) and $(D_PARAM target) are the same object, + * $(D_PSYMBOL move) does nothing. + * + * Params: + * T = Object type. + * source = Source object. + * target = Target object. + * + * See_Also: $(D_PSYMBOL moveEmplace). + */ +void move(T)(ref T source, ref T target) +{ + if ((() @trusted => &source is &target)()) + { + return; + } + static if (hasElaborateDestructor!T) + { + target.__xdtor(); + } + (() @trusted => moveEmplace(source, target))(); +} + +/// ditto +T move(T)(ref T source) @trusted +{ + static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T) + { + T target = void; + moveEmplace(source, target); + return target; + } + else + { + return source; + } +} + +/// +@nogc nothrow pure @safe unittest +{ + static struct S + { + int member = 5; + + this(this) @nogc nothrow pure @safe + { + assert(false); + } + } + S source, target = void; + move(source, target); + assert(target.member == 5); + assert(move(target).member == 5); + + int x1 = 5, x2; + move(x1, x2); + assert(x2 == 5); + assert(move(x2) == 5); +} + +/** + * Exchanges the values of $(D_PARAM a) and $(D_PARAM b). + * + * $(D_PSYMBOL swap) moves the contents of $(D_PARAM a) and $(D_PARAM b) + * without calling its postblits or destructors. + * + * Params: + * a = The first object. + * b = The second object. + */ +void swap(T)(ref T a, ref T b) @trusted +{ + T tmp = void; + moveEmplace(a, tmp); + moveEmplace(b, a); + moveEmplace(tmp, b); +} + +/// +@nogc nothrow pure @safe unittest +{ + int a = 3, b = 5; + swap(a, b); + assert(a == 5); + assert(b == 3); +} + +/** + * Forwards its argument list preserving $(D_KEYWORD ref) and $(D_KEYWORD out) + * storage classes. + * + * $(D_PSYMBOL forward) accepts a list of variables or literals. It returns an + * argument list of the same length that can be for example passed to a + * function accepting the arguments of this type. + * + * Params: + * args = Argument list. + * + * Returns: $(D_PARAM args) with their original storage classes. + */ +template forward(args...) +{ + static if (args.length == 0) + { + alias forward = AliasSeq!(); + } + else static if (__traits(isRef, args[0]) || __traits(isOut, args[0])) + { + static if (args.length == 1) + { + alias forward = args[0]; + } + else + { + alias forward = AliasSeq!(args[0], forward!(args[1 .. $])); + } + } + else + { + @property auto forwardOne() + { + return move(args[0]); + } + static if (args.length == 1) + { + alias forward = forwardOne; + } + else + { + alias forward = AliasSeq!(forwardOne, forward!(args[1 .. $])); + } + } +} + +/// +@nogc nothrow pure @safe unittest +{ + static assert(is(typeof((int i) { int v = forward!i; }))); + static assert(is(typeof((ref int i) { int v = forward!i; }))); + static assert(is(typeof({ + void f(int i, ref int j, out int k) + { + f(forward!(i, j, k)); + } + }))); +} diff --git a/middle/tanya/memory/mallocator.d b/middle/tanya/memory/mallocator.d new file mode 100644 index 0000000..1103053 --- /dev/null +++ b/middle/tanya/memory/mallocator.d @@ -0,0 +1,218 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Allocator based on $(D_PSYMBOL malloc), $(D_PSYMBOL realloc) and + * $(D_PSYMBOL free). + * + * Copyright: Eugene Wissner 2017-2019. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/mallocator.d, + * tanya/memory/mallocator.d) + */ +module tanya.memory.mallocator; + +version (TanyaNative) +{ +} +else: + +import core.stdc.stdlib; +import tanya.memory.allocator; + +/** + * Wrapper for $(D_PSYMBOL malloc)/$(D_PSYMBOL realloc)/$(D_PSYMBOL free) from + * the C standard library. + */ +final class Mallocator : Allocator +{ + private alias MallocType = extern (C) void* function(size_t) + @nogc nothrow pure @system; + private alias FreeType = extern (C) void function(void*) + @nogc nothrow pure @system; + private alias ReallocType = extern (C) void* function(void*, size_t) + @nogc nothrow pure @system; + + /** + * Allocates $(D_PARAM size) bytes of memory. + * + * Params: + * size = Amount of memory to allocate. + * + * Returns: The pointer to the new allocated memory. + */ + void[] allocate(size_t size) @nogc nothrow pure shared @system + { + if (size == 0) + { + return null; + } + auto p = (cast(MallocType) &malloc)(size + psize); + + return p is null ? null : p[psize .. psize + size]; + } + + /// + @nogc nothrow pure @system unittest + { + auto p = Mallocator.instance.allocate(20); + assert(p.length == 20); + Mallocator.instance.deallocate(p); + + p = Mallocator.instance.allocate(0); + assert(p.length == 0); + } + + /** + * Deallocates a memory block. + * + * Params: + * p = A pointer to the memory block to be freed. + * + * Returns: Whether the deallocation was successful. + */ + bool deallocate(void[] p) @nogc nothrow pure shared @system + { + if (p !is null) + { + (cast(FreeType) &free)(p.ptr - psize); + } + return true; + } + + /// + @nogc nothrow pure @system unittest + { + void[] p; + assert(Mallocator.instance.deallocate(p)); + + p = Mallocator.instance.allocate(10); + assert(Mallocator.instance.deallocate(p)); + } + + /** + * Reallocating in place isn't supported. + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: $(D_KEYWORD false). + */ + bool reallocateInPlace(ref void[] p, size_t size) + @nogc nothrow pure shared @system + { + cast(void) size; + return false; + } + + /// + @nogc nothrow pure @system unittest + { + void[] p; + assert(!Mallocator.instance.reallocateInPlace(p, 8)); + } + + /** + * Increases or decreases the size of a memory block. + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: Whether the reallocation was successful. + */ + bool reallocate(ref void[] p, size_t size) + @nogc nothrow pure shared @system + { + if (size == 0) + { + if (deallocate(p)) + { + p = null; + return true; + } + } + else if (p is null) + { + p = allocate(size); + return p is null ? false : true; + } + else + { + auto r = (cast(ReallocType) &realloc)(p.ptr - psize, size + psize); + + if (r !is null) + { + p = r[psize .. psize + size]; + return true; + } + } + return false; + } + + /// + @nogc nothrow pure @system unittest + { + void[] p; + + assert(Mallocator.instance.reallocate(p, 20)); + assert(p.length == 20); + + assert(Mallocator.instance.reallocate(p, 30)); + assert(p.length == 30); + + assert(Mallocator.instance.reallocate(p, 10)); + assert(p.length == 10); + + assert(Mallocator.instance.reallocate(p, 0)); + assert(p is null); + } + + /** + * Returns: The alignment offered. + */ + @property uint alignment() const @nogc nothrow pure @safe shared + { + return (void*).alignof; + } + + static private shared(Mallocator) instantiate() @nogc nothrow @system + { + if (instance_ is null) + { + const size = __traits(classInstanceSize, Mallocator) + psize; + void* p = malloc(size); + + if (p !is null) + { + p[psize .. size] = typeid(Mallocator).initializer[]; + instance_ = cast(shared Mallocator) p[psize .. size].ptr; + } + } + return instance_; + } + + /** + * Static allocator instance and initializer. + * + * Returns: The global $(D_PSYMBOL Allocator) instance. + */ + static @property shared(Mallocator) instance() @nogc nothrow pure @system + { + return (cast(GetPureInstance!Mallocator) &instantiate)(); + } + + /// + @nogc nothrow pure @system unittest + { + assert(instance is instance); + } + + private enum ushort psize = 8; + + private shared static Mallocator instance_; +} diff --git a/middle/tanya/memory/mmappool.d b/middle/tanya/memory/mmappool.d new file mode 100644 index 0000000..05f71c6 --- /dev/null +++ b/middle/tanya/memory/mmappool.d @@ -0,0 +1,503 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Native allocator. + * + * Copyright: Eugene Wissner 2016-2019. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/mmappool.d, + * tanya/memory/mmappool.d) + */ +module tanya.memory.mmappool; + +version (TanyaNative): + +import mir.linux._asm.unistd; +import tanya.memory.allocator; +import tanya.memory.op; +import tanya.os.error; +import tanya.sys.linux.syscall; +import tanya.sys.posix.mman; + +private void* mapMemory(const size_t length) @nogc nothrow pure @system +{ + auto p = syscall_(0, + length, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0, + NR_mmap); + return p == -ErrorCode.noMemory ? null : cast(void*) p; +} + +private bool unmapMemory(shared void* addr, const size_t length) +@nogc nothrow pure @system +{ + return syscall_(cast(ptrdiff_t) addr, length, NR_munmap) == 0; +} + +/* + * This allocator allocates memory in regions (multiple of 64 KB for example). + * Each region is then splitted in blocks. So it doesn't request the memory + * from the operating system on each call, but only if there are no large + * enough free blocks in the available regions. + * Deallocation works in the same way. Deallocation doesn't immediately + * gives the memory back to the operating system, but marks the appropriate + * block as free and only if all blocks in the region are free, the complete + * region is deallocated. + * + *
+ * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * |      |     |         |     |            ||      |     |                  |
+ * |      |prev <-----------    |            ||      |     |                  |
+ * |  R   |  B  |         |  B  |            ||   R  |  B  |                  |
+ * |  E   |  L  |         |  L  |           next  E  |  L  |                  |
+ * |  G   |  O  |  DATA   |  O  |   FREE    --->  G  |  O  |       DATA       |
+ * |  I   |  C  |         |  C  |           <---  I  |  C  |                  |
+ * |  O   |  K  |         |  K  |           prev  O  |  K  |                  |
+ * |  N   |    -----------> next|            ||   N  |     |                  |
+ * |      |     |         |     |            ||      |     |                  |
+ * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * 
+ */ +final class MmapPool : Allocator +{ + version (none) + { + @nogc nothrow pure @system invariant + { + for (auto r = &head; *r !is null; r = &((*r).next)) + { + auto block = cast(Block) (cast(void*) *r + RegionEntry.sizeof); + do + { + assert(block.prev is null || block.prev.next is block); + assert(block.next is null || block.next.prev is block); + assert(block.region is *r); + } + while ((block = block.next) !is null); + } + } + } + + /* + * Allocates $(D_PARAM size) bytes of memory. + * + * Params: + * size = Amount of memory to allocate. + * + * Returns: Pointer to the new allocated memory. + */ + void[] allocate(size_t size) @nogc nothrow pure shared @system + { + if (size == 0) + { + return null; + } + const dataSize = addAlignment(size); + if (dataSize < size) + { + return null; + } + + void* data = findBlock(dataSize); + if (data is null) + { + data = initializeRegion(dataSize); + } + + return data is null ? null : data[0 .. size]; + } + + /* + * Search for a block large enough to keep $(D_PARAM size) and split it + * into two blocks if the block is too large. + * + * Params: + * size = Minimum size the block should have (aligned). + * + * Returns: Data the block points to or $(D_KEYWORD null). + */ + private void* findBlock(const ref size_t size) + @nogc nothrow pure shared @system + { + Block block1; + RegionLoop: for (auto r = head; r !is null; r = r.next) + { + block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof); + do + { + if (block1.free && block1.size >= size) + { + break RegionLoop; + } + } + while ((block1 = block1.next) !is null); + } + if (block1 is null) + { + return null; + } + else if (block1.size >= size + alignment_ + BlockEntry.sizeof) + { // Split the block if needed + Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size); + block2.prev = block1; + block2.next = block1.next; + block2.free = true; + block2.size = block1.size - BlockEntry.sizeof - size; + block2.region = block1.region; + + if (block1.next !is null) + { + block1.next.prev = block2; + } + block1.next = block2; + block1.size = size; + } + block1.free = false; + block1.region.blocks = block1.region.blocks + 1; + + return cast(void*) block1 + BlockEntry.sizeof; + } + + // Merge block with the next one. + private void mergeNext(Block block) const @nogc nothrow pure @safe shared + { + block.size = block.size + BlockEntry.sizeof + block.next.size; + if (block.next.next !is null) + { + block.next.next.prev = block; + } + block.next = block.next.next; + } + + /* + * Deallocates a memory block. + * + * Params: + * p = A pointer to the memory block to be freed. + * + * Returns: Whether the deallocation was successful. + */ + bool deallocate(void[] p) @nogc nothrow pure shared @system + { + if (p.ptr is null) + { + return true; + } + + Block block = cast(Block) (p.ptr - BlockEntry.sizeof); + if (block.region.blocks <= 1) + { + if (block.region.prev !is null) + { + block.region.prev.next = block.region.next; + } + else // Replace the list head. It is being deallocated + { + head = block.region.next; + } + if (block.region.next !is null) + { + block.region.next.prev = block.region.prev; + } + return unmapMemory(block.region, block.region.size); + } + // Merge blocks if neigbours are free. + if (block.next !is null && block.next.free) + { + mergeNext(block); + } + if (block.prev !is null && block.prev.free) + { + block.prev.size = block.prev.size + BlockEntry.sizeof + block.size; + if (block.next !is null) + { + block.next.prev = block.prev; + } + block.prev.next = block.next; + } + else + { + block.free = true; + } + block.region.blocks = block.region.blocks - 1; + return true; + } + + /* + * Reallocates a memory block in place if possible or returns + * $(D_KEYWORD false). This function cannot be used to allocate or + * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or + * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false). + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. + */ + bool reallocateInPlace(ref void[] p, size_t size) + @nogc nothrow pure shared @system + { + if (p is null || size == 0) + { + return false; + } + if (size <= p.length) + { + // Leave the block as is. + p = p.ptr[0 .. size]; + return true; + } + Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof); + + if (block1.size >= size) + { + // Enough space in the current block. + p = p.ptr[0 .. size]; + return true; + } + const dataSize = addAlignment(size); + const pAlignment = addAlignment(p.length); + assert(pAlignment >= p.length, "Invalid memory chunk length"); + const delta = dataSize - pAlignment; + + if (block1.next is null + || !block1.next.free + || dataSize < size + || block1.next.size + BlockEntry.sizeof < delta) + { + /* - It is the last block in the region + * - The next block isn't free + * - The next block is too small + * - Requested size is too large + */ + return false; + } + if (block1.next.size >= delta + alignment_) + { + // Move size from block2 to block1. + block1.next.size = block1.next.size - delta; + block1.size = block1.size + delta; + + auto block2 = cast(Block) (p.ptr + dataSize); + if (block1.next.next !is null) + { + block1.next.next.prev = block2; + } + copyBackward((cast(void*) block1.next)[0 .. BlockEntry.sizeof], + (cast(void*) block2)[0 .. BlockEntry.sizeof]); + block1.next = block2; + } + else + { + // The next block has enough space, but is too small for further + // allocations. Merge it with the current block. + mergeNext(block1); + } + + p = p.ptr[0 .. size]; + return true; + } + + /* + * Increases or decreases the size of a memory block. + * + * Params: + * p = A pointer to the memory block. + * size = Size of the reallocated block. + * + * Returns: Whether the reallocation was successful. + */ + bool reallocate(ref void[] p, size_t size) + @nogc nothrow pure shared @system + { + if (size == 0) + { + if (deallocate(p)) + { + p = null; + return true; + } + return false; + } + else if (reallocateInPlace(p, size)) + { + return true; + } + // Can't reallocate in place, allocate a new block, + // copy and delete the previous one. + void[] reallocP = allocate(size); + if (reallocP is null) + { + return false; + } + if (p !is null) + { + copy(p[0 .. p.length < size ? p.length : size], reallocP); + deallocate(p); + } + p = reallocP; + + return true; + } + + static private shared(MmapPool) instantiate() @nogc nothrow @system + { + if (instance_ is null) + { + const instanceSize = addAlignment(__traits(classInstanceSize, + MmapPool)); + + Region head; // Will become soon our region list head + void* data = initializeRegion(instanceSize, head); + if (data !is null) + { + copy(typeid(MmapPool).initializer, data[0 .. instanceSize]); + instance_ = cast(shared MmapPool) data; + instance_.head = head; + } + } + return instance_; + } + + /* + * Static allocator instance and initializer. + * + * Returns: Global $(D_PSYMBOL MmapPool) instance. + */ + static @property shared(MmapPool) instance() @nogc nothrow pure @system + { + return (cast(GetPureInstance!MmapPool) &instantiate)(); + } + + /* + * Initializes a region for one element. + * + * Params: + * size = Aligned size of the first data block in the region. + * head = Region list head. + * + * Returns: A pointer to the data. + */ + private static void* initializeRegion(const size_t size, ref Region head) + @nogc nothrow pure @system + { + const regionSize = calculateRegionSize(size); + if (regionSize < size) + { + return null; + } + + void* p = mapMemory(regionSize); + if (p is null) + { + return null; + } + + Region region = cast(Region) p; + region.blocks = 1; + region.size = regionSize; + + // Set the pointer to the head of the region list + if (head !is null) + { + head.prev = region; + } + region.next = head; + region.prev = null; + head = region; + + // Initialize the data block + void* memoryPointer = p + RegionEntry.sizeof; + Block block1 = cast(Block) memoryPointer; + block1.size = size; + block1.free = false; + + // It is what we want to return + void* data = memoryPointer + BlockEntry.sizeof; + + // Free block after data + memoryPointer = data + size; + Block block2 = cast(Block) memoryPointer; + block1.prev = block2.next = null; + block1.next = block2; + block2.prev = block1; + block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2; + block2.free = true; + block1.region = block2.region = region; + + return data; + } + + private void* initializeRegion(const size_t size) + @nogc nothrow pure shared @system + { + return initializeRegion(size, this.head); + } + + /* + * Params: + * x = Space to be aligned. + * + * Returns: Aligned size of $(D_PARAM x). + */ + private static size_t addAlignment(const size_t x) @nogc nothrow pure @safe + { + return (x - 1) / alignment_ * alignment_ + alignment_; + } + + /* + * Params: + * x = Required space. + * + * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). + */ + private static size_t calculateRegionSize(ref const size_t x) + @nogc nothrow pure @safe + { + return (x + RegionEntry.sizeof + BlockEntry.sizeof * 2) + / pageSize * pageSize + pageSize; + } + + /* + * Returns: Alignment offered. + */ + @property uint alignment() const @nogc nothrow pure @safe shared + { + return alignment_; + } + + private enum uint alignment_ = 8; + + private shared static MmapPool instance_; + + // Page size. + enum size_t pageSize = 65536; + + private shared struct RegionEntry + { + Region prev; + Region next; + uint blocks; + size_t size; + } + private alias Region = shared RegionEntry*; + private shared Region head; + + private shared struct BlockEntry + { + Block prev; + Block next; + Region region; + size_t size; + bool free; + } + private alias Block = shared BlockEntry*; +} diff --git a/middle/tanya/memory/op.d b/middle/tanya/memory/op.d new file mode 100644 index 0000000..55a0676 --- /dev/null +++ b/middle/tanya/memory/op.d @@ -0,0 +1,375 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Set of operations on memory blocks. + * + * Copyright: Eugene Wissner 2017-2019. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/op.d, + * tanya/memory/op.d) + */ +module tanya.memory.op; + +version (TanyaNative) +{ + extern private void fillMemory(void[], size_t) pure nothrow @system @nogc; + + extern private void copyMemory(const void[], void[]) + pure nothrow @system @nogc; + + extern private void moveMemory(const void[], void[]) + pure nothrow @system @nogc; + + extern private bool equalMemory(const void[], const void[]) + pure nothrow @system @nogc; +} +else +{ + import core.stdc.string; +} + +private enum alignMask = size_t.sizeof - 1; + +/** + * Copies $(D_PARAM source) into $(D_PARAM target). + * + * $(D_PARAM source) and $(D_PARAM target) shall not overlap so that + * $(D_PARAM source) points ahead of $(D_PARAM target). + * + * $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length) + * elements. + * + * Params: + * source = Memory to copy from. + * target = Destination memory. + * + * See_Also: $(D_PSYMBOL copyBackward). + * + * Precondition: $(D_INLINECODE source.length <= target.length). + */ +void copy(const void[] source, void[] target) @nogc nothrow pure @trusted +in +{ + assert(source.length <= target.length); + assert(source.length == 0 || source.ptr !is null); + assert(target.length == 0 || target.ptr !is null); +} +do +{ + version (TanyaNative) + { + copyMemory(source, target); + } + else + { + memcpy(target.ptr, source.ptr, source.length); + } +} + +/// +@nogc nothrow pure @safe unittest +{ + ubyte[9] source = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + ubyte[9] target; + source.copy(target); + assert(equal(source, target)); +} + +/* + * size_t value each of which bytes is set to `Byte`. + */ +private template filledBytes(ubyte Byte, ubyte I = 0) +{ + static if (I == size_t.sizeof) + { + enum size_t filledBytes = Byte; + } + else + { + enum size_t filledBytes = (filledBytes!(Byte, I + 1) << 8) | Byte; + } +} + +/** + * Fills $(D_PARAM memory) with the single byte $(D_PARAM c). + * + * Param: + * c = The value to fill $(D_PARAM memory) with. + * memory = Memory block. + */ +void fill(ubyte c = 0)(void[] memory) @trusted +in +{ + assert(memory.length == 0 || memory.ptr !is null); +} +do +{ + version (TanyaNative) + { + fillMemory(memory, filledBytes!c); + } + else + { + memset(memory.ptr, c, memory.length); + } +} + +/// +@nogc nothrow pure @safe unittest +{ + ubyte[9] memory = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + memory.fill!0(); + foreach (ubyte v; memory) + { + assert(v == 0); + } +} + +/** + * Copies starting from the end of $(D_PARAM source) into the end of + * $(D_PARAM target). + * + * $(D_PSYMBOL copyBackward) copies the elements in reverse order, but the + * order of elements in the $(D_PARAM target) is exactly the same as in the + * $(D_PARAM source). + * + * $(D_PARAM source) and $(D_PARAM target) shall not overlap so that + * $(D_PARAM target) points ahead of $(D_PARAM source). + * + * $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length) + * elements. + * + * Params: + * source = Memory to copy from. + * target = Destination memory. + * + * See_Also: $(D_PSYMBOL copy). + * + * Precondition: $(D_INLINECODE source.length <= target.length). + */ +void copyBackward(const void[] source, void[] target) @nogc nothrow pure @trusted +in +{ + assert(source.length <= target.length); + assert(source.length == 0 || source.ptr !is null); + assert(target.length == 0 || target.ptr !is null); +} +do +{ + version (TanyaNative) + { + moveMemory(source, target); + } + else + { + memmove(target.ptr, source.ptr, source.length); + } +} + +/// +@nogc nothrow pure @safe unittest +{ + ubyte[6] mem = [ 'a', 'a', 'b', 'b', 'c', 'c' ]; + ubyte[6] expected = [ 'a', 'a', 'a', 'a', 'b', 'b' ]; + + copyBackward(mem[0 .. 4], mem[2 .. $]); + assert(equal(expected, mem)); +} + +/** + * Finds the first occurrence of $(D_PARAM needle) in $(D_PARAM haystack) if + * any. + * + * Params: + * haystack = Memory block. + * needle = A byte. + * + * Returns: The subrange of $(D_PARAM haystack) whose first element is the + * first occurrence of $(D_PARAM needle). If $(D_PARAM needle) + * couldn't be found, an empty `inout void[]` is returned. + */ +inout(void[]) find(return inout void[] haystack, ubyte needle) +@nogc nothrow pure @trusted +in +{ + assert(haystack.length == 0 || haystack.ptr !is null); +} +do +{ + auto length = haystack.length; + const size_t needleWord = size_t.max * needle; + enum size_t highBits = filledBytes!(0x01, 0); + enum size_t mask = filledBytes!(0x80, 0); + + // Align + auto bytes = cast(inout(ubyte)*) haystack; + while (length > 0 && ((cast(size_t) bytes) & 3) != 0) + { + if (*bytes == needle) + { + return bytes[0 .. length]; + } + ++bytes; + --length; + } + + // Check if some of the words has the needle + auto words = cast(inout(size_t)*) bytes; + while (length >= size_t.sizeof) + { + if ((((*words ^ needleWord) - highBits) & (~*words) & mask) != 0) + { + break; + } + ++words; + length -= size_t.sizeof; + } + + // Find the exact needle position in the word + bytes = cast(inout(ubyte)*) words; + while (length > 0) + { + if (*bytes == needle) + { + return bytes[0 .. length]; + } + ++bytes; + --length; + } + + return haystack[$ .. $]; +} + +/// +@nogc nothrow pure @safe unittest +{ + const ubyte[9] haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'b', 'g', 'h']; + + assert(equal(find(haystack, 'a'), haystack[])); + assert(equal(find(haystack, 'b'), haystack[1 .. $])); + assert(equal(find(haystack, 'c'), haystack[2 .. $])); + assert(equal(find(haystack, 'd'), haystack[3 .. $])); + assert(equal(find(haystack, 'e'), haystack[4 .. $])); + assert(equal(find(haystack, 'f'), haystack[5 .. $])); + assert(equal(find(haystack, 'h'), haystack[8 .. $])); + assert(find(haystack, 'i').length == 0); + + assert(find(null, 'a').length == 0); +} + +/** + * Looks for `\0` in the $(D_PARAM haystack) and returns the part of the + * $(D_PARAM haystack) ahead of it. + * + * Returns $(D_KEYWORD null) if $(D_PARAM haystack) doesn't contain a null + * character. + * + * Params: + * haystack = Memory block. + * + * Returns: The subrange that spans all bytes before the null character or + * $(D_KEYWORD null) if the $(D_PARAM haystack) doesn't contain any. + */ +inout(char[]) findNullTerminated(return inout char[] haystack) +@nogc nothrow pure @trusted +in +{ + assert(haystack.length == 0 || haystack.ptr !is null); +} +do +{ + auto length = haystack.length; + enum size_t highBits = filledBytes!(0x01, 0); + enum size_t mask = filledBytes!(0x80, 0); + + // Align + auto bytes = cast(inout(ubyte)*) haystack; + while (length > 0 && ((cast(size_t) bytes) & 3) != 0) + { + if (*bytes == '\0') + { + return haystack[0 .. haystack.length - length]; + } + ++bytes; + --length; + } + + // Check if some of the words contains 0 + auto words = cast(inout(size_t)*) bytes; + while (length >= size_t.sizeof) + { + if (((*words - highBits) & (~*words) & mask) != 0) + { + break; + } + ++words; + length -= size_t.sizeof; + } + + // Find the exact 0 position in the word + bytes = cast(inout(ubyte)*) words; + while (length > 0) + { + if (*bytes == '\0') + { + return haystack[0 .. haystack.length - length]; + } + ++bytes; + --length; + } + + return null; +} + +/// +@nogc nothrow pure @safe unittest +{ + assert(equal(findNullTerminated("abcdef\0gh"), "abcdef")); + assert(equal(findNullTerminated("\0garbage"), "")); + assert(equal(findNullTerminated("\0"), "")); + assert(equal(findNullTerminated("cstring\0"), "cstring")); + assert(findNullTerminated(null) is null); + assert(findNullTerminated("abcdef") is null); +} + +/** + * Compares two memory areas $(D_PARAM r1) and $(D_PARAM r2) for equality. + * + * Params: + * r1 = First memory block. + * r2 = Second memory block. + * + * Returns: $(D_KEYWORD true) if $(D_PARAM r1) and $(D_PARAM r2) are equal, + * $(D_KEYWORD false) otherwise. + */ +bool equal(const void[] r1, const void[] r2) @nogc nothrow pure @trusted +in +{ + assert(r1.length == 0 || r1.ptr !is null); + assert(r2.length == 0 || r2.ptr !is null); +} +do +{ + version (TanyaNative) + { + return equalMemory(r1, r2); + } + else + { + return r1.length == r2.length + && memcmp(r1.ptr, r2.ptr, r1.length) == 0; + } +} + +/// +@nogc nothrow pure @safe unittest +{ + assert(equal("asdf", "asdf")); + assert(!equal("asd", "asdf")); + assert(!equal("asdf", "asd")); + assert(!equal("asdf", "qwer")); +} diff --git a/middle/tanya/memory/package.d b/middle/tanya/memory/package.d new file mode 100644 index 0000000..c87eeb9 --- /dev/null +++ b/middle/tanya/memory/package.d @@ -0,0 +1,132 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Dynamic memory management. + * + * Copyright: Eugene Wissner 2016-2019. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/package.d, + * tanya/memory/package.d) + */ +module tanya.memory; + +public import tanya.memory.allocator; +public import tanya.memory.lifetime; +import tanya.meta.trait; +deprecated("Use tanya.meta.trait.stateSize instead") +public import tanya.meta.trait : stateSize; + +/** + * The mixin generates common methods for classes and structs using + * allocators. It provides a protected member, constructor and a read-only property, + * that checks if an allocator was already set and sets it to the default + * one, if not (useful for structs which don't have a default constructor). + */ +mixin template DefaultAllocator() +{ + /// Allocator. + protected shared Allocator allocator_; + + /** + * Params: + * allocator = The allocator should be used. + * + * Precondition: $(D_INLINECODE allocator_ !is null) + */ + this(shared Allocator allocator) @nogc nothrow pure @safe + in (allocator !is null) + { + this.allocator_ = allocator; + } + + /** + * This property checks if the allocator was set in the constructor + * and sets it to the default one, if not. + * + * Returns: Used allocator. + * + * Postcondition: $(D_INLINECODE allocator !is null) + */ + @property shared(Allocator) allocator() @nogc nothrow pure @safe + out (allocator; allocator !is null) + { + if (allocator_ is null) + { + allocator_ = defaultAllocator; + } + return allocator_; + } + + /// ditto + @property shared(Allocator) allocator() const @nogc nothrow pure @trusted + out (allocator; allocator !is null) + { + if (allocator_ is null) + { + return defaultAllocator; + } + return cast(shared Allocator) allocator_; + } +} + +shared Allocator allocator; + +private shared(Allocator) getAllocatorInstance() @nogc nothrow +{ + if (allocator is null) + { + version (TanyaNative) + { + import tanya.memory.mmappool; + defaultAllocator = MmapPool.instance; + } + else + { + import tanya.memory.mallocator; + defaultAllocator = Mallocator.instance; + } + } + return allocator; +} + +/** + * Returns: Default allocator. + * + * Postcondition: $(D_INLINECODE allocator !is null). + */ +@property shared(Allocator) defaultAllocator() @nogc nothrow pure @trusted +out (allocator; allocator !is null) +{ + return (cast(GetPureInstance!Allocator) &getAllocatorInstance)(); +} + +/** + * Sets the default allocator. + * + * Params: + * allocator = $(D_PSYMBOL Allocator) instance. + * + * Precondition: $(D_INLINECODE allocator !is null). + */ +@property void defaultAllocator(shared(Allocator) allocator) @nogc nothrow @safe +in (allocator !is null) +{ + .allocator = allocator; +} + +/** + * Params: + * size = Raw size. + * alignment = Alignment. + * + * Returns: Aligned size. + */ +size_t alignedSize(const size_t size, const size_t alignment = 8) +pure nothrow @safe @nogc +{ + return (size - 1) / alignment * alignment + alignment; +} diff --git a/middle/tanya/memory/smartref.d b/middle/tanya/memory/smartref.d new file mode 100644 index 0000000..953513e --- /dev/null +++ b/middle/tanya/memory/smartref.d @@ -0,0 +1,634 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Smart pointers. + * + * A smart pointer is an object that wraps a raw pointer or a reference + * (class, dynamic array) to manage its lifetime. + * + * This module provides two kinds of lifetime management strategies: + * $(UL + * $(LI Reference counting) + * $(LI Unique ownership) + * ) + * + * Copyright: Eugene Wissner 2016-2019. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/smartref.d, + * tanya/memory/smartref.d) + */ +module tanya.memory.smartref; + +import tanya.memory; +import tanya.meta.trait; + +private template Payload(T) +{ + static if (isPolymorphicType!T || isDynamicArray!T) + { + alias Payload = T; + } + else + { + alias Payload = T*; + } +} + +private final class RefCountedStore(T) +{ + T payload; + size_t counter = 1; + + size_t opUnary(string op)() + if (op == "--" || op == "++") + in (this.counter > 0) + { + mixin("return " ~ op ~ "counter;"); + } + + int opCmp(const size_t counter) + { + if (this.counter > counter) + { + return 1; + } + else if (this.counter < counter) + { + return -1; + } + else + { + return 0; + } + } +} + +private void separateDeleter(T)(RefCountedStore!T storage, + shared Allocator allocator) +{ + allocator.dispose(storage.payload); + allocator.dispose(storage); +} + +private void unifiedDeleter(T)(RefCountedStore!T storage, + shared Allocator allocator) +{ + auto ptr1 = finalize(storage); + auto ptr2 = finalize(storage.payload); + allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]); +} + +/** + * Reference-counted object containing a $(D_PARAM T) value as payload. + * $(D_PSYMBOL RefCounted) keeps track of all references of an object, and + * when the reference count goes down to zero, frees the underlying store. + * + * Params: + * T = Type of the reference-counted value. + */ +struct RefCounted(T) +{ + private alias Storage = RefCountedStore!(Payload!T); + + private Storage storage; + private void function(Storage storage, + shared Allocator allocator) @nogc deleter; + + invariant + { + assert(this.storage is null || this.allocator_ !is null); + assert(this.storage is null || this.deleter !is null); + } + + /** + * Takes ownership over $(D_PARAM value), setting the counter to 1. + * $(D_PARAM value) may be a pointer, an object or a dynamic array. + * + * Params: + * value = Value whose ownership is taken over. + * allocator = Allocator used to destroy the $(D_PARAM value) and to + * allocate/deallocate internal storage. + * + * Precondition: $(D_INLINECODE allocator !is null) + */ + this(Payload!T value, shared Allocator allocator = defaultAllocator) + { + this(allocator); + this.storage = allocator.make!Storage(); + this.deleter = &separateDeleter!(Payload!T); + + this.storage.payload = value; + } + + /// ditto + this(shared Allocator allocator) + in (allocator !is null) + { + this.allocator_ = allocator; + } + + /** + * Increases the reference counter by one. + */ + this(this) + { + if (count != 0) + { + ++this.storage; + } + } + + /** + * Decreases the reference counter by one. + * + * If the counter reaches 0, destroys the owned object. + */ + ~this() + { + if (this.storage !is null && !(this.storage > 0 && --this.storage)) + { + deleter(this.storage, allocator); + } + } + + /** + * Takes ownership over $(D_PARAM rhs). Initializes this + * $(D_PSYMBOL RefCounted) if needed. + * + * If it is the last reference of the previously owned object, + * it will be destroyed. + * + * To reset $(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) and assign it. + * + * Params: + * rhs = New object. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(Payload!T rhs) + { + if (this.storage is null) + { + this.storage = allocator.make!Storage(); + this.deleter = &separateDeleter!(Payload!T); + } + else if (this.storage > 1) + { + --this.storage; + this.storage = allocator.make!Storage(); + this.deleter = &separateDeleter!(Payload!T); + } + else + { + finalize(this.storage.payload); + this.storage.payload = Payload!T.init; + } + this.storage.payload = rhs; + return this; + } + + /// ditto + ref typeof(this) opAssign(typeof(null)) + { + if (this.storage is null) + { + return this; + } + else if (this.storage > 1) + { + --this.storage; + } + else + { + deleter(this.storage, allocator); + } + this.storage = null; + + return this; + } + + /// ditto + ref typeof(this) opAssign(typeof(this) rhs) + { + swap(this.allocator_, rhs.allocator_); + swap(this.storage, rhs.storage); + swap(this.deleter, rhs.deleter); + return this; + } + + /** + * Returns: Reference to the owned object. + * + * Precondition: $(D_INLINECODE cound > 0). + */ + inout(Payload!T) get() inout + in (count > 0, "Attempted to access an uninitialized reference") + { + return this.storage.payload; + } + + version (D_Ddoc) + { + /** + * Dereferences the pointer. It is defined only for pointers, not for + * reference types like classes, that can be accessed directly. + * + * Params: + * op = Operation. + * + * Returns: Reference to the pointed value. + */ + ref inout(T) opUnary(string op)() inout + if (op == "*"); + } + else static if (isPointer!(Payload!T)) + { + ref inout(T) opUnary(string op)() inout + if (op == "*") + { + return *this.storage.payload; + } + } + + /** + * Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal + * storage. + */ + @property bool isInitialized() const + { + return this.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`. + */ + @property size_t count() const + { + return this.storage is null ? 0 : this.storage.counter; + } + + mixin DefaultAllocator; + alias get this; +} + +/// +@nogc @system unittest +{ + auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator); + auto val = rc.get(); + + *val = 8; + assert(*rc.get == 8); + + val = null; + assert(rc.get !is null); + assert(*rc.get == 8); + + *rc = 9; + assert(*rc.get == 9); +} + +/** + * Constructs a new object of type $(D_PARAM T) and wraps it in a + * $(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). + * allocator = Allocator. + * args = Constructor arguments of $(D_PARAM T). + * + * Returns: Newly created $(D_PSYMBOL RefCounted!T). + * + * Precondition: $(D_INLINECODE allocator !is null) + */ +RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args) +if (!is(T == interface) && !isAbstractClass!T + && !isAssociativeArray!T && !isArray!T) +in (allocator !is null) +{ + auto rc = typeof(return)(allocator); + + const storageSize = alignedSize(stateSize!(RefCounted!T.Storage)); + const size = alignedSize(stateSize!T + storageSize); + + auto mem = (() @trusted => allocator.allocate(size))(); + if (mem is null) + { + onOutOfMemoryError(); + } + scope (failure) + { + () @trusted { allocator.deallocate(mem); }(); + } + rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]); + rc.storage.payload = emplace!T(mem[storageSize .. $], args); + + rc.deleter = &unifiedDeleter!(Payload!T); + return rc; +} + +/** + * Constructs a new array with $(D_PARAM size) elements and wraps it in a + * $(D_PSYMBOL RefCounted). + * + * Params: + * T = Array type. + * E = Array element type. + * size = Array size. + * allocator = Allocator. + * + * Returns: Newly created $(D_PSYMBOL RefCounted!T). + * + * Precondition: $(D_INLINECODE allocator !is null + * && size <= size_t.max / E.sizeof) + */ +RefCounted!T refCounted(T : E[], E)(shared Allocator allocator, size_t size) +@trusted +in (allocator !is null) +in (size <= size_t.max / E.sizeof) +{ + return RefCounted!T(allocator.make!T(size), allocator); +} + +/// +@nogc @system unittest +{ + auto rc = defaultAllocator.refCounted!int(5); + assert(rc.count == 1); + + void func(RefCounted!int param) @nogc + { + if (param.count == 2) + { + func(param); + } + else + { + assert(param.count == 3); + } + } + func(rc); + + assert(rc.count == 1); +} + +/** + * $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope. + * + * Params: + * T = Value type. + */ +struct Unique(T) +{ + private Payload!T payload; + + invariant + { + assert(payload is null || allocator_ !is null); + } + + /** + * Takes ownership over $(D_PARAM value), setting the counter to 1. + * $(D_PARAM value) may be a pointer, an object or a dynamic array. + * + * Params: + * value = Value whose ownership is taken over. + * allocator = Allocator used to destroy the $(D_PARAM value) and to + * allocate/deallocate internal storage. + * + * Precondition: $(D_INLINECODE allocator !is null) + */ + this(Payload!T value, shared Allocator allocator = defaultAllocator) + { + this(allocator); + this.payload = value; + } + + /// ditto + this(shared Allocator allocator) + in (allocator !is null) + { + this.allocator_ = allocator; + } + + /** + * $(D_PSYMBOL Unique) is noncopyable. + */ + @disable this(this); + + /** + * Destroys the owned object. + */ + ~this() + { + allocator.dispose(this.payload); + } + + /** + * Initialized this $(D_PARAM Unique) and takes ownership over + * $(D_PARAM rhs). + * + * To reset $(D_PSYMBOL Unique) 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 Unique) and assign it. + * + * Params: + * rhs = New object. + * + * Returns: $(D_KEYWORD this). + */ + ref typeof(this) opAssign(Payload!T rhs) + { + allocator.dispose(this.payload); + this.payload = rhs; + return this; + } + + /// ditto + ref typeof(this) opAssign(typeof(null)) + { + allocator.dispose(this.payload); + return this; + } + + /// ditto + ref typeof(this) opAssign(typeof(this) rhs) + { + swap(this.allocator_, rhs.allocator_); + swap(this.payload, rhs.payload); + + return this; + } + + /// + @nogc nothrow pure @system unittest + { + auto rc = defaultAllocator.unique!int(5); + rc = defaultAllocator.make!int(7); + assert(*rc == 7); + } + + /** + * Returns: Reference to the owned object. + */ + inout(Payload!T) get() inout + { + return this.payload; + } + + version (D_Ddoc) + { + /** + * Dereferences the pointer. It is defined only for pointers, not for + * reference types like classes, that can be accessed directly. + * + * Params: + * op = Operation. + * + * Returns: Reference to the pointed value. + */ + ref inout(T) opUnary(string op)() inout + if (op == "*"); + } + else static if (isPointer!(Payload!T)) + { + ref inout(T) opUnary(string op)() inout + if (op == "*") + { + return *this.payload; + } + } + + /** + * Returns: Whether this $(D_PSYMBOL Unique) holds some value. + */ + @property bool isInitialized() const + { + return this.payload !is null; + } + + /// + @nogc nothrow pure @system unittest + { + Unique!int u; + assert(!u.isInitialized); + } + + /** + * Sets the internal pointer to $(D_KEYWORD). The allocator isn't changed. + * + * Returns: Reference to the owned object. + */ + Payload!T release() + { + auto payload = this.payload; + this.payload = null; + return payload; + } + + /// + @nogc nothrow pure @system unittest + { + auto u = defaultAllocator.unique!int(5); + assert(u.isInitialized); + + auto i = u.release(); + assert(*i == 5); + assert(!u.isInitialized); + } + + mixin DefaultAllocator; + alias get this; +} + +/// +@nogc nothrow pure @system unittest +{ + auto p = defaultAllocator.make!int(5); + auto s = Unique!int(p, defaultAllocator); + assert(*s == 5); +} + +/// +@nogc nothrow @system unittest +{ + static bool destroyed; + + static struct F + { + ~this() @nogc nothrow @safe + { + destroyed = true; + } + } + { + auto s = Unique!F(defaultAllocator.make!F(), defaultAllocator); + } + assert(destroyed); +} + +/** + * Constructs a new object of type $(D_PARAM T) and wraps it in a + * $(D_PSYMBOL Unique) using $(D_PARAM args) as the parameter list for + * the constructor of $(D_PARAM T). + * + * Params: + * T = Type of the constructed object. + * A = Types of the arguments to the constructor of $(D_PARAM T). + * allocator = Allocator. + * args = Constructor arguments of $(D_PARAM T). + * + * Returns: Newly created $(D_PSYMBOL Unique!T). + * + * Precondition: $(D_INLINECODE allocator !is null) + */ +Unique!T unique(T, A...)(shared Allocator allocator, auto ref A args) +if (!is(T == interface) && !isAbstractClass!T + && !isAssociativeArray!T && !isArray!T) +in (allocator !is null) +{ + auto payload = allocator.make!(T, A)(args); + return Unique!T(payload, allocator); +} + +/** + * Constructs a new array with $(D_PARAM size) elements and wraps it in a + * $(D_PSYMBOL Unique). + * + * Params: + * T = Array type. + * E = Array element type. + * size = Array size. + * allocator = Allocator. + * + * Returns: Newly created $(D_PSYMBOL Unique!T). + * + * Precondition: $(D_INLINECODE allocator !is null + * && size <= size_t.max / E.sizeof) + */ +Unique!T unique(T : E[], E)(shared Allocator allocator, size_t size) +@trusted +in (allocator !is null) +in (size <= size_t.max / E.sizeof) +{ + auto payload = allocator.resize!E(null, size); + return Unique!T(payload, allocator); +} diff --git a/os/tanya/os/error.d b/os/tanya/os/error.d index 31c0102..0fabfc2 100644 --- a/os/tanya/os/error.d +++ b/os/tanya/os/error.d @@ -407,12 +407,6 @@ struct ErrorCode assert(ec.toString() == "An invalid pointer address detected"); } - @nogc nothrow pure @safe unittest - { - ErrorCode ec = cast(ErrorCode.ErrorNo) -1; - assert(ec.toString() is null); - } - private ErrorNo value_ = ErrorNo.success; alias ErrorNo this; diff --git a/source/tanya/algorithm/iteration.d b/source/tanya/algorithm/iteration.d index fcfb1c0..0cca49f 100644 --- a/source/tanya/algorithm/iteration.d +++ b/source/tanya/algorithm/iteration.d @@ -21,7 +21,7 @@ module tanya.algorithm.iteration; import tanya.algorithm.comparison; -import tanya.memory.lifecycle; +import tanya.memory.lifetime; import tanya.meta.trait; import tanya.meta.transform; import tanya.range; diff --git a/source/tanya/algorithm/mutation.d b/source/tanya/algorithm/mutation.d index ddb2743..fd6a9d6 100644 --- a/source/tanya/algorithm/mutation.d +++ b/source/tanya/algorithm/mutation.d @@ -14,21 +14,21 @@ */ module tanya.algorithm.mutation; +static import tanya.memory.lifetime; static import tanya.memory.op; -static import tanya.memory.lifecycle; import tanya.meta.trait; import tanya.meta.transform; import tanya.range; version (unittest) import tanya.test.stub; -deprecated("Use tanya.memory.lifecycle.swap instead") -alias swap = tanya.memory.lifecycle.swap; +deprecated("Use tanya.memory.lifetime.swap instead") +alias swap = tanya.memory.lifetime.swap; -deprecated("Use tanya.memory.lifecycle.moveEmplace instead") -alias moveEmplace = tanya.memory.lifecycle.moveEmplace; +deprecated("Use tanya.memory.lifetime.moveEmplace instead") +alias moveEmplace = tanya.memory.lifetime.moveEmplace; -deprecated("Use tanya.memory.lifecycle.move instead") -alias move = tanya.memory.lifecycle.move; +deprecated("Use tanya.memory.lifetime.move instead") +alias move = tanya.memory.lifetime.move; /** * Copies the $(D_PARAM source) range into the $(D_PARAM target) range. @@ -231,7 +231,7 @@ if (isInputRange!Range && hasLvalueElements!Range for (; !range.empty; range.popFront()) { ElementType!Range* p = &range.front; - tanya.memory.lifecycle.emplace!(ElementType!Range)(cast(void[]) (p[0 .. 1]), value); + tanya.memory.lifetime.emplace!(ElementType!Range)(cast(void[]) (p[0 .. 1]), value); } } else @@ -314,7 +314,7 @@ if (isInputRange!Range && hasLvalueElements!Range) void destroyAll(Range)(Range range) if (isInputRange!Range && hasLvalueElements!Range) { - tanya.memory.lifecycle.destroyAllImpl!(Range, ElementType!Range)(range); + tanya.memory.lifetime.destroyAllImpl!(Range, ElementType!Range)(range); } /// @@ -363,7 +363,7 @@ if (isForwardRange!Range && hasSwappableElements!Range) while (!front.empty && !next.empty && !sameHead(front, next)) { - tanya.memory.lifecycle.swap(front.front, next.front); + tanya.memory.lifetime.swap(front.front, next.front); front.popFront(); next.popFront(); diff --git a/source/tanya/container/entry.d b/source/tanya/container/entry.d index 408849a..cf4484c 100644 --- a/source/tanya/container/entry.d +++ b/source/tanya/container/entry.d @@ -16,7 +16,7 @@ module tanya.container.entry; import tanya.container.array; import tanya.memory.allocator; -import tanya.memory.lifecycle; +import tanya.memory.lifetime; import tanya.meta.trait; import tanya.meta.transform; import tanya.typecons; diff --git a/source/tanya/conv.d b/source/tanya/conv.d index f602e01..4010cb5 100644 --- a/source/tanya/conv.d +++ b/source/tanya/conv.d @@ -16,8 +16,8 @@ module tanya.conv; import tanya.container.string; import tanya.memory; -deprecated("Use tanya.memory.lifecycle.emplace instead") -public import tanya.memory.lifecycle : emplace; +deprecated("Use tanya.memory.lifetime.emplace instead") +public import tanya.memory.lifetime : emplace; import tanya.meta.trait; import tanya.meta.transform; import tanya.range; diff --git a/source/tanya/exception.d b/source/tanya/exception.d index 49a2d40..b65efd1 100644 --- a/source/tanya/exception.d +++ b/source/tanya/exception.d @@ -10,7 +10,7 @@ * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/exception.d, * tanya/exception.d) */ -deprecated("Use tanya.memory.lifecycle instead") +deprecated("Use tanya.memory.lifetimeinstead") module tanya.exception; -public import tanya.memory.lifecycle : onOutOfMemoryError, OutOfMemoryError; +public import tanya.memory.lifetime : onOutOfMemoryError, OutOfMemoryError; diff --git a/source/tanya/functional.d b/source/tanya/functional.d index 9a1c3a1..cde1b51 100644 --- a/source/tanya/functional.d +++ b/source/tanya/functional.d @@ -12,7 +12,7 @@ * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/functional.d, * tanya/functional.d) */ -deprecated("Use tanya.memory.lifecycle instead") +deprecated("Use tanya.memory.lifetime instead") module tanya.functional; -public import tanya.memory.lifecycle : forward; +public import tanya.memory.lifetime : forward; diff --git a/source/tanya/math/mp.d b/source/tanya/math/mp.d index b7eb86b..dc470b7 100644 --- a/source/tanya/math/mp.d +++ b/source/tanya/math/mp.d @@ -5,7 +5,7 @@ /** * Arbitrary precision arithmetic. * - * Copyright: Eugene Wissner 2016-2018. + * Copyright: Eugene Wissner 2016-2019. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * Mozilla Public License, v. 2.0). * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) diff --git a/source/tanya/math/nbtheory.d b/source/tanya/math/nbtheory.d index c20cc67..ec5d6c4 100644 --- a/source/tanya/math/nbtheory.d +++ b/source/tanya/math/nbtheory.d @@ -5,7 +5,7 @@ /** * Number theory. * - * Copyright: Eugene Wissner 2017-2018. + * Copyright: Eugene Wissner 2017-2019. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * Mozilla Public License, v. 2.0). * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) diff --git a/source/tanya/math/package.d b/source/tanya/math/package.d index 0fb2456..0ad9caf 100644 --- a/source/tanya/math/package.d +++ b/source/tanya/math/package.d @@ -12,7 +12,7 @@ * be found in its submodules. $(D_PSYMBOL tanya.math) doesn't import any * submodules publically, they should be imported explicitly. * - * Copyright: Eugene Wissner 2016-2018. + * Copyright: Eugene Wissner 2016-2019. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * Mozilla Public License, v. 2.0). * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) @@ -21,7 +21,6 @@ */ module tanya.math; -import tanya.algorithm.mutation; import tanya.math.mp; import tanya.math.nbtheory; import tanya.meta.trait; diff --git a/source/tanya/math/random.d b/source/tanya/math/random.d index cd66950..d5b840b 100644 --- a/source/tanya/math/random.d +++ b/source/tanya/math/random.d @@ -5,7 +5,7 @@ /** * Random number generator. * - * Copyright: Eugene Wissner 2016-2018. + * Copyright: Eugene Wissner 2016-2019. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * Mozilla Public License, v. 2.0). * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) @@ -14,7 +14,6 @@ */ module tanya.math.random; -import std.digest.sha; import tanya.memory; import tanya.typecons; diff --git a/source/tanya/net/ip.d b/source/tanya/net/ip.d index 2afdfe8..2025f15 100644 --- a/source/tanya/net/ip.d +++ b/source/tanya/net/ip.d @@ -21,7 +21,7 @@ import tanya.container.string; import tanya.conv; import tanya.encoding.ascii; import tanya.format; -import tanya.memory.lifecycle; +import tanya.memory.lifetime; import tanya.meta.trait; import tanya.meta.transform; import tanya.net.iface; diff --git a/source/tanya/range/adapter.d b/source/tanya/range/adapter.d index bc20e10..8ab9808 100644 --- a/source/tanya/range/adapter.d +++ b/source/tanya/range/adapter.d @@ -15,7 +15,7 @@ module tanya.range.adapter; import tanya.algorithm.mutation; -import tanya.memory.lifecycle; +import tanya.memory.lifetime; import tanya.meta.trait; import tanya.range; diff --git a/source/tanya/range/array.d b/source/tanya/range/array.d index 97101b9..98cceca 100644 --- a/source/tanya/range/array.d +++ b/source/tanya/range/array.d @@ -55,11 +55,7 @@ module tanya.range.array; * Precondition: $(D_INLINECODE array.length > 0). */ @property ref inout(T) front(T)(return scope inout(T)[] array) -in -{ - assert(array.length > 0); -} -do +in (array.length > 0) { return array[0]; } @@ -95,11 +91,7 @@ do * Precondition: $(D_INLINECODE array.length > 0). */ @property ref inout(T) back(T)(return scope inout(T)[] array) -in -{ - assert(array.length > 0); -} -do +in (array.length > 0) { return array[$ - 1]; } @@ -134,22 +126,14 @@ do * Precondition: $(D_INLINECODE array.length > 0). */ void popFront(T)(scope ref inout(T)[] array) -in -{ - assert(array.length > 0); -} -do +in (array.length > 0) { array = array[1 .. $]; } /// ditto void popBack(T)(scope ref inout(T)[] array) -in -{ - assert(array.length > 0); -} -do +in (array.length > 0) { array = array[0 .. $ - 1]; } diff --git a/source/tanya/range/primitive.d b/source/tanya/range/primitive.d index 0b192b0..21d3584 100644 --- a/source/tanya/range/primitive.d +++ b/source/tanya/range/primitive.d @@ -15,7 +15,7 @@ module tanya.range.primitive; import tanya.algorithm.comparison; -import tanya.memory.lifecycle; +import tanya.memory.lifetime; import tanya.meta.trait; import tanya.meta.transform; import tanya.range.array; diff --git a/source/tanya/typecons.d b/source/tanya/typecons.d index fa09831..700e2dd 100644 --- a/source/tanya/typecons.d +++ b/source/tanya/typecons.d @@ -18,7 +18,7 @@ module tanya.typecons; import tanya.format; -import tanya.memory.lifecycle; +import tanya.memory.lifetime; import tanya.meta.metafunction; import tanya.meta.trait; version (unittest) import tanya.test.stub; diff --git a/test/dub.json b/test/dub.json new file mode 100644 index 0000000..292bd9d --- /dev/null +++ b/test/dub.json @@ -0,0 +1,16 @@ +{ + "name": "test", + "description": "Test suite for unittest-blocks", + "targetType": "library", + + "dependencies": { + "tanya:middle": "*" + }, + + "sourcePaths": [ + "." + ], + "importPaths": [ + "." + ] +} diff --git a/test/tanya/test/assertion.d b/test/tanya/test/assertion.d new file mode 100644 index 0000000..10105d7 --- /dev/null +++ b/test/tanya/test/assertion.d @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Additional assertions. + * + * This module provides functions that assert whether a given expression + * satisfies some complex condition, that can't be tested with + * $(D_KEYWORD assert) in a single line. Internally all the functions + * just evaluate the expression and call $(D_KEYWORD assert). + * + * The functions can cause segmentation fault if the module is compiled + * in production mode and the condition fails. + * + * Copyright: Eugene Wissner 2017-2018. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/assertion.d, + * tanya/test/assertion.d) + */ +module tanya.test.assertion; + +import tanya.memory; +import tanya.meta.trait; + +/** + * Asserts whether the function $(D_PARAM expr) throws an exception of type + * $(D_PARAM E). If it does, the exception is catched and properly destroyed. + * If it doesn't, an assertion error is thrown. If the exception doesn't match + * $(D_PARAM E) type, it isn't catched and escapes. + * + * Params: + * E = Expected exception type. + * T = Throwing function type. + * Args = Argument types of the throwing function. + * expr = Throwing function. + * args = Arguments for $(D_PARAM expr). + */ +void assertThrown(E : Exception, T, Args...)(T expr, auto ref Args args) +if (isSomeFunction!T) +{ + try + { + cast(void) expr(args); + assert(false, "Expected exception not thrown"); + } + catch (E exception) + { + defaultAllocator.dispose(exception); + } +} + +/// +@nogc nothrow pure @safe unittest +{ + // If you want to test that an expression throws, you can wrap it into an + // arrow function. + static struct CtorThrows + { + this(int i) @nogc pure @safe + { + throw defaultAllocator.make!Exception(); + } + } + assertThrown!Exception(() => CtorThrows(8)); +} + +/** + * Asserts that the function $(D_PARAM expr) doesn't throw. + * + * If it does, the thrown exception is catched, properly destroyed and an + * assertion error is thrown instead. + * + * Params: + * T = Tested function type. + * Args = Argument types of $(D_PARAM expr). + * expr = Tested function. + * args = Arguments for $(D_PARAM expr). + */ +void assertNotThrown(T, Args...)(T expr, auto ref Args args) +if (isSomeFunction!T) +{ + try + { + cast(void) expr(args); + } + catch (Exception exception) + { + defaultAllocator.dispose(exception); + assert(false, "Unexpected exception thrown"); + } +} + +/// +@nogc nothrow pure @safe unittest +{ + // If you want to test that an expression doesn't throw, you can wrap it + // into an arrow function. + static struct S + { + } + assertNotThrown(() => S()); +} diff --git a/test/tanya/test/package.d b/test/tanya/test/package.d new file mode 100644 index 0000000..ab6f861 --- /dev/null +++ b/test/tanya/test/package.d @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test suite for $(D_KEYWORD unittest)-blocks. + * + * Copyright: Eugene Wissner 2017-2018. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/package.d, + * tanya/test/package.d) + */ +module tanya.test; + +public import tanya.test.assertion; +public import tanya.test.stub; diff --git a/test/tanya/test/stub.d b/test/tanya/test/stub.d new file mode 100644 index 0000000..e1f8dcb --- /dev/null +++ b/test/tanya/test/stub.d @@ -0,0 +1,373 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Range and generic type generators. + * + * Copyright: Eugene Wissner 2018. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/stub.d, + * tanya/test/stub.d) + */ +module tanya.test.stub; + +/** + * Attribute signalizing that the generated range should contain the given + * number of elements. + * + * $(D_PSYMBOL Count) should be always specified with some value and not as a + * type, so $(D_INLINECODE Count(1)) instead just $(D_INLINECODE Count), + * otherwise you can just omit $(D_PSYMBOL Count) and it will default to 0. + * + * $(D_PSYMBOL Count) doesn't generate `.length` property - use + * $(D_PSYMBOL Length) for that. + * + * If neither $(D_PSYMBOL Length) nor $(D_PSYMBOL Infinite) is given, + * $(D_ILNINECODE Count(0)) is assumed. + * + * This attribute conflicts with $(D_PSYMBOL Infinite) and $(D_PSYMBOL Length). + */ +struct Count +{ + /// Original range length. + size_t count = 0; + + @disable this(); + + /** + * Constructs the attribute with the given length. + * + * Params: + * count = Original range length. + */ + this(size_t count) @nogc nothrow pure @safe + { + this.count = count; + } +} + +/** + * Attribute signalizing that the generated range should be infinite. + * + * This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Length). + */ +struct Infinite +{ +} + +/** + * Generates `.length` property for the range. + * + * The length of the range can be specified as a constructor argument, + * otherwise it is 0. + * + * This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Infinite). + */ +struct Length +{ + /// Original range length. + size_t length = 0; +} + +/** + * Attribute signalizing that the generated range should return values by + * reference. + * + * This atribute affects the return values of `.front`, `.back` and `[]`. + */ +struct WithLvalueElements +{ +} + +/** + * Generates an input range. + * + * Params: + * E = Element type. + */ +mixin template InputRangeStub(E = int) +{ + import tanya.meta.metafunction : Alias; + import tanya.meta.trait : evalUDA, getUDAs, hasUDA; + + /* + * Aliases for the attribute lookups to access them faster + */ + private enum bool infinite = hasUDA!(typeof(this), Infinite); + private enum bool withLvalueElements = hasUDA!(typeof(this), + WithLvalueElements); + private alias Count = getUDAs!(typeof(this), .Count); + private alias Length = getUDAs!(typeof(this), .Length); + + static if (Count.length != 0) + { + private enum size_t count = Count[0].count; + + static assert (!infinite, + "Range cannot have count and be infinite at the same time"); + static assert (Length.length == 0, + "Range cannot have count and length at the same time"); + } + else static if (Length.length != 0) + { + private enum size_t count = evalUDA!(Length[0]).length; + + static assert (!infinite, + "Range cannot have length and be infinite at the same time"); + } + else static if (!infinite) + { + private enum size_t count = 0; + } + + /* + * Member generation + */ + static if (infinite) + { + enum bool empty = false; + } + else + { + private size_t length_ = count; + + @property bool empty() const @nogc nothrow pure @safe + { + return this.length_ == 0; + } + } + + static if (withLvalueElements) + { + private E* element; // Pointer to enable range copying in save() + } + + void popFront() @nogc nothrow pure @safe + in (!empty) + { + static if (!infinite) + { + --this.length_; + } + } + + static if (withLvalueElements) + { + ref E front() @nogc nothrow pure @safe + in (!empty) + { + return *this.element; + } + } + else + { + E front() @nogc nothrow pure @safe + in (!empty) + { + return E.init; + } + } + + static if (Length.length != 0) + { + size_t length() const @nogc nothrow pure @safe + { + return this.length_; + } + } +} + +/** + * Generates a forward range. + * + * This mixin includes input range primitives as well, but can be combined with + * $(D_PSYMBOL RandomAccessRangeStub). + * + * Params: + * E = Element type. + */ +mixin template ForwardRangeStub(E = int) +{ + static if (!is(typeof(this.InputRangeMixin) == void)) + { + mixin InputRangeStub!E InputRangeMixin; + } + + auto save() @nogc nothrow pure @safe + { + return this; + } +} + +/** + * Generates a bidirectional range. + * + * This mixin includes forward range primitives as well, but can be combined with + * $(D_PSYMBOL RandomAccessRangeStub). + * + * Params: + * E = Element type. + */ +mixin template BidirectionalRangeStub(E = int) +{ + mixin ForwardRangeStub!E; + + void popBack() @nogc nothrow pure @safe + in (!empty) + { + static if (!infinite) + { + --this.length_; + } + } + + static if (withLvalueElements) + { + ref E back() @nogc nothrow pure @safe + in (!empty) + { + return *this.element; + } + } + else + { + E back() @nogc nothrow pure @safe + in (!empty) + { + return E.init; + } + } +} + +/** + * Generates a random-access range. + * + * This mixin includes input range primitives as well, but can be combined with + * $(D_PSYMBOL ForwardRangeStub) or $(D_PSYMBOL BidirectionalRangeStub). + * + * Note that a random-access range also requires $(D_PSYMBOL Length) or + * $(D_PARAM Infinite) by definition. + * + * Params: + * E = Element type. + */ +mixin template RandomAccessRangeStub(E = int) +{ + static if (!is(typeof(this.InputRangeMixin) == void)) + { + mixin InputRangeStub!E InputRangeMixin; + } + + static if (withLvalueElements) + { + ref E opIndex(size_t) @nogc nothrow pure @safe + { + return *this.element; + } + } + else + { + E opIndex(size_t) @nogc nothrow pure @safe + { + return E.init; + } + } +} + +/** + * Struct with a disabled postblit constructor. + * + * $(D_PSYMBOL NonCopyable) can be used as an attribute for + * $(D_PSYMBOL StructStub) or as a standalone type. + */ +struct NonCopyable +{ + @disable this(this); +} + +/** + * Struct with an elaborate destructor. + * + * $(D_PSYMBOL WithDtor) can be used as an attribute for + * $(D_PSYMBOL StructStub) or as a standalone type. + * + * When used as a standalone object the constructor of $(D_PSYMBOL WithDtor) + * accepts an additional `counter` argument, which is incremented by the + * destructor. $(D_PSYMBOL WithDtor) stores a pointer to the passed variable, + * so the variable can be investigated after the struct isn't available + * anymore. + */ +struct WithDtor +{ + size_t* counter; + + this(ref size_t counter) @nogc nothrow pure @trusted + { + this.counter = &counter; + } + + ~this() @nogc nothrow pure @safe + { + if (this.counter !is null) + { + ++*this.counter; + } + } +} + +/** + * Struct supporting hashing. + * + * $(D_PSYMBOL Hashable) can be used as an attribute for + * $(D_PSYMBOL StructStub) or as a standalone type. + * + * The constructor accepts an additional parameter, which is returned by the + * `toHash()`-function. `0U` is returned if no hash value is given. + */ +struct Hashable +{ + size_t hash; + + size_t toHash() const @nogc nothrow pure @safe + { + return this.hash; + } +} + +/** + * Generates a $(D_KEYWORD struct) with common functionality. + * + * To specify the needed functionality use user-defined attributes on the + * $(D_KEYWORD struct) $(D_PSYMBOL StructStub) is mixed in. + * + * Supported attributes are: $(D_PSYMBOL NonCopyable), $(D_PSYMBOL Hashable), + * $(D_PSYMBOL WithDtor). + */ +mixin template StructStub() +{ + import tanya.meta.trait : evalUDA, getUDAs, hasUDA; + + static if (hasUDA!(typeof(this), NonCopyable)) + { + @disable this(this); + } + + private alias Hashable = getUDAs!(typeof(this), .Hashable); + static if (Hashable.length > 0) + { + size_t toHash() const @nogc nothrow pure @safe + { + return evalUDA!(Hashable[0]).hash; + } + } + + static if (hasUDA!(typeof(this), WithDtor)) + { + ~this() @nogc nothrow pure @safe + { + } + } +} diff --git a/tests/tanya/memory/tests/lifetime.d b/tests/tanya/memory/tests/lifetime.d new file mode 100644 index 0000000..95da527 --- /dev/null +++ b/tests/tanya/memory/tests/lifetime.d @@ -0,0 +1,173 @@ +module tanya.memory.tests.lifetime; + +import tanya.memory; +import tanya.test.stub; + +@nogc nothrow pure @safe unittest +{ + int[] p; + + p = defaultAllocator.resize(p, 20); + assert(p.length == 20); + + p = defaultAllocator.resize(p, 30); + assert(p.length == 30); + + p = defaultAllocator.resize(p, 10); + assert(p.length == 10); + + p = defaultAllocator.resize(p, 0); + assert(p is null); +} + +@nogc nothrow pure @system unittest +{ + static struct S + { + ~this() @nogc nothrow pure @safe + { + } + } + auto p = cast(S[]) defaultAllocator.allocate(S.sizeof); + + defaultAllocator.dispose(p); +} + +// Works with interfaces. +@nogc nothrow pure @safe unittest +{ + interface I + { + } + class C : I + { + } + auto c = defaultAllocator.make!C(); + I i = c; + + defaultAllocator.dispose(i); + defaultAllocator.dispose(i); +} + +// Handles "Cannot access frame pointer" error. +@nogc nothrow pure @safe unittest +{ + struct F + { + ~this() @nogc nothrow pure @safe + { + } + } + static assert(is(typeof(emplace!F((void[]).init)))); +} + +// Can emplace structs without a constructor +@nogc nothrow pure @safe unittest +{ + static assert(is(typeof(emplace!WithDtor(null, WithDtor())))); + static assert(is(typeof(emplace!WithDtor(null)))); +} + +// Doesn't call a destructor on uninitialized elements +@nogc nothrow pure @system unittest +{ + static struct SWithDtor + { + private bool canBeInvoked = false; + ~this() @nogc nothrow pure @safe + { + assert(this.canBeInvoked); + } + } + void[SWithDtor.sizeof] memory = void; + auto actual = emplace!SWithDtor(memory[], SWithDtor(true)); + assert(actual.canBeInvoked); +} + +// Initializes structs if no arguments are given +@nogc nothrow pure @safe unittest +{ + static struct SEntry + { + byte content; + } + ubyte[1] mem = [3]; + + assert(emplace!SEntry(cast(void[]) mem[0 .. 1]).content == 0); +} + +// Postblit is called when emplacing a struct +@nogc nothrow pure @system unittest +{ + static struct S + { + bool called = false; + this(this) @nogc nothrow pure @safe + { + this.called = true; + } + } + S target; + S* sp = ⌖ + + emplace!S(sp[0 .. 1], S()); + assert(target.called); +} + +// Is pure. +@nogc nothrow pure @system unittest +{ + struct S + { + this(this) + { + } + } + S source, target = void; + static assert(is(typeof({ moveEmplace(source, target); }))); +} + +// Moves nested. +@nogc nothrow pure @system unittest +{ + struct Nested + { + void method() @nogc nothrow pure @safe + { + } + } + Nested source, target = void; + moveEmplace(source, target); + assert(source == target); +} + +// Emplaces static arrays. +@nogc nothrow pure @system unittest +{ + static struct S + { + size_t member; + this(size_t i) @nogc nothrow pure @safe + { + this.member = i; + } + ~this() @nogc nothrow pure @safe + { + } + } + S[2] source = [ S(5), S(5) ], target = void; + moveEmplace(source, target); + assert(source[0].member == 0); + assert(target[0].member == 5); + assert(source[1].member == 0); + assert(target[1].member == 5); +} + +// Moves if source is target. +@nogc nothrow pure @safe unittest +{ + int x = 5; + move(x, x); + assert(x == 5); +} + diff --git a/tests/tanya/memory/tests/mallocator.d b/tests/tanya/memory/tests/mallocator.d new file mode 100644 index 0000000..941f9a8 --- /dev/null +++ b/tests/tanya/memory/tests/mallocator.d @@ -0,0 +1,23 @@ +module tanya.memory.tests.mallocator; + +version (TanyaNative) +{ +} +else: + +import tanya.memory.mallocator; + +// Fails with false +@nogc nothrow pure @system unittest +{ + void[] p = Mallocator.instance.allocate(20); + void[] oldP = p; + assert(!Mallocator.instance.reallocate(p, size_t.max - 16)); + assert(oldP is p); + Mallocator.instance.deallocate(p); +} + +@nogc nothrow pure unittest +{ + assert(Mallocator.instance.alignment == (void*).alignof); +} diff --git a/tests/tanya/memory/tests/mmappool.d b/tests/tanya/memory/tests/mmappool.d new file mode 100644 index 0000000..0e5b257 --- /dev/null +++ b/tests/tanya/memory/tests/mmappool.d @@ -0,0 +1,159 @@ +module tanya.memory.tests.mmappool; + +version (TanyaNative): + +import tanya.memory.mmappool; + +@nogc nothrow pure @system unittest +{ + auto p = MmapPool.instance.allocate(20); + assert(p); + MmapPool.instance.deallocate(p); + + p = MmapPool.instance.allocate(0); + assert(p.length == 0); +} + +@nogc nothrow pure @system unittest +{ + // allocate() check. + size_t tooMuchMemory = size_t.max + - MmapPool.alignment_ + - BlockEntry.sizeof * 2 + - RegionEntry.sizeof + - pageSize; + assert(MmapPool.instance.allocate(tooMuchMemory) is null); + + assert(MmapPool.instance.allocate(size_t.max) is null); + + // initializeRegion() check. + tooMuchMemory = size_t.max - MmapPool.alignment_; + assert(MmapPool.instance.allocate(tooMuchMemory) is null); +} + +@nogc nothrow pure @system unittest +{ + auto p = MmapPool.instance.allocate(20); + + assert(MmapPool.instance.deallocate(p)); +} + +@nogc nothrow pure @system unittest +{ + void[] p; + assert(!MmapPool.instance.reallocateInPlace(p, 5)); + assert(p is null); + + p = MmapPool.instance.allocate(1); + auto orig = p.ptr; + + assert(MmapPool.instance.reallocateInPlace(p, 2)); + assert(p.length == 2); + assert(p.ptr == orig); + + assert(MmapPool.instance.reallocateInPlace(p, 4)); + assert(p.length == 4); + assert(p.ptr == orig); + + assert(MmapPool.instance.reallocateInPlace(p, 2)); + assert(p.length == 2); + assert(p.ptr == orig); + + MmapPool.instance.deallocate(p); +} + +@nogc nothrow pure @system unittest +{ + void[] p; + MmapPool.instance.reallocate(p, 10 * int.sizeof); + (cast(int[]) p)[7] = 123; + + assert(p.length == 40); + + MmapPool.instance.reallocate(p, 8 * int.sizeof); + + assert(p.length == 32); + assert((cast(int[]) p)[7] == 123); + + MmapPool.instance.reallocate(p, 20 * int.sizeof); + (cast(int[]) p)[15] = 8; + + assert(p.length == 80); + assert((cast(int[]) p)[15] == 8); + assert((cast(int[]) p)[7] == 123); + + MmapPool.instance.reallocate(p, 8 * int.sizeof); + + assert(p.length == 32); + assert((cast(int[]) p)[7] == 123); + + MmapPool.instance.deallocate(p); +} + +@nogc nothrow pure @system unittest +{ + assert(instance is instance); +} + +@nogc nothrow pure @system unittest +{ + assert(MmapPool.instance.alignment == MmapPool.alignment_); +} + +// A lot of allocations/deallocations, but it is the minimum caused a +// segmentation fault because MmapPool reallocateInPlace moves a block wrong. +@nogc nothrow pure @system unittest +{ + auto a = MmapPool.instance.allocate(16); + auto d = MmapPool.instance.allocate(16); + auto b = MmapPool.instance.allocate(16); + auto e = MmapPool.instance.allocate(16); + auto c = MmapPool.instance.allocate(16); + auto f = MmapPool.instance.allocate(16); + + MmapPool.instance.deallocate(a); + MmapPool.instance.deallocate(b); + MmapPool.instance.deallocate(c); + + a = MmapPool.instance.allocate(50); + MmapPool.instance.reallocateInPlace(a, 64); + MmapPool.instance.deallocate(a); + + a = MmapPool.instance.allocate(1); + auto tmp1 = MmapPool.instance.allocate(1); + auto h1 = MmapPool.instance.allocate(1); + auto tmp2 = cast(ubyte[]) MmapPool.instance.allocate(1); + + auto h2 = MmapPool.instance.allocate(2); + tmp1 = MmapPool.instance.allocate(1); + MmapPool.instance.deallocate(h2); + MmapPool.instance.deallocate(h1); + + h2 = MmapPool.instance.allocate(2); + h1 = MmapPool.instance.allocate(1); + MmapPool.instance.deallocate(h2); + + auto rep = cast(void[]) tmp2; + MmapPool.instance.reallocate(rep, tmp1.length); + tmp2 = cast(ubyte[]) rep; + + MmapPool.instance.reallocate(tmp1, 9); + + rep = cast(void[]) tmp2; + MmapPool.instance.reallocate(rep, tmp1.length); + tmp2 = cast(ubyte[]) rep; + MmapPool.instance.reallocate(tmp1, 17); + + tmp2[$ - 1] = 0; + + MmapPool.instance.deallocate(tmp1); + + b = MmapPool.instance.allocate(16); + + MmapPool.instance.deallocate(h1); + MmapPool.instance.deallocate(a); + MmapPool.instance.deallocate(b); + MmapPool.instance.deallocate(d); + MmapPool.instance.deallocate(e); + MmapPool.instance.deallocate(f); +} diff --git a/tests/tanya/memory/tests/op.d b/tests/tanya/memory/tests/op.d new file mode 100644 index 0000000..61aa99b --- /dev/null +++ b/tests/tanya/memory/tests/op.d @@ -0,0 +1,64 @@ +module tanya.memory.tests.op; + +import tanya.memory.op; + +@nogc nothrow pure @system unittest +{ + ubyte[2] buffer = 1; + fill!0(buffer[1 .. $]); + assert(buffer[0] == 1 && buffer[1] == 0); +} + +@nogc nothrow pure @safe unittest +{ + assert(equal(null, null)); +} + +@nogc nothrow pure @safe unittest +{ + ubyte[0] source, target; + source.copy(target); +} + +@nogc nothrow pure @safe unittest +{ + ubyte[1] source = [1]; + ubyte[1] target; + source.copy(target); + assert(target[0] == 1); +} + +@nogc nothrow pure @safe unittest +{ + ubyte[8] source = [1, 2, 3, 4, 5, 6, 7, 8]; + ubyte[8] target; + source.copy(target); + assert(equal(source, target)); +} + +@nogc nothrow pure @safe unittest +{ + ubyte[9] r1 = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ]; + ubyte[9] r2; + + copyBackward(r1, r2); + assert(equal(r1, r2)); +} + +// Compares unanligned memory +@nogc nothrow pure @safe unittest +{ + ubyte[16] r1 = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + ]; + ubyte[16] r2 = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + ]; + + assert(equal(r1, r2)); + assert(equal(r1[1 .. $], r2[1 .. $])); + assert(equal(r1[0 .. $ - 1], r2[0 .. $ - 1])); + assert(equal(r1[0 .. 8], r2[0 .. 8])); +} diff --git a/tests/tanya/memory/tests/smartref.d b/tests/tanya/memory/tests/smartref.d new file mode 100644 index 0000000..b91e275 --- /dev/null +++ b/tests/tanya/memory/tests/smartref.d @@ -0,0 +1,250 @@ +module tanya.memory.tests.smartref; + +import tanya.memory; +import tanya.memory.smartref; +import tanya.meta.trait; +import tanya.test.stub; + +@nogc @system unittest +{ + auto rc = defaultAllocator.refCounted!int(5); + rc = defaultAllocator.make!int(7); + assert(*rc == 7); +} + +@nogc @system unittest +{ + RefCounted!int rc; + assert(!rc.isInitialized); + rc = null; + assert(!rc.isInitialized); +} + +@nogc @system unittest +{ + auto rc = defaultAllocator.refCounted!int(5); + + void func(RefCounted!int param) @nogc + { + assert(param.count == 2); + param = defaultAllocator.make!int(7); + assert(param.count == 1); + assert(*param == 7); + } + func(rc); + assert(rc.count == 1); + assert(*rc == 5); +} + +@nogc @system unittest +{ + RefCounted!int rc; + + void func(RefCounted!int param) @nogc + { + assert(param.count == 0); + param = defaultAllocator.make!int(7); + assert(param.count == 1); + assert(*param == 7); + } + func(rc); + assert(rc.count == 0); +} + +@nogc @system unittest +{ + RefCounted!int rc1, rc2; + static assert(is(typeof(rc1 = rc2))); +} + +@nogc @system unittest +{ + auto rc = RefCounted!int(defaultAllocator); + assert(!rc.isInitialized); + assert(rc.allocator is defaultAllocator); +} + +@nogc @system unittest +{ + auto rc = defaultAllocator.refCounted!int(5); + assert(rc.count == 1); + + void func(RefCounted!int rc) @nogc + { + assert(rc.count == 2); + rc = null; + assert(!rc.isInitialized); + assert(rc.count == 0); + } + + assert(rc.count == 1); + func(rc); + assert(rc.count == 1); + + rc = null; + assert(!rc.isInitialized); + assert(rc.count == 0); +} + +@nogc @system unittest +{ + auto rc = defaultAllocator.refCounted!int(5); + assert(*rc == 5); + + void func(RefCounted!int rc) @nogc + { + assert(rc.count == 2); + rc = defaultAllocator.refCounted!int(4); + assert(*rc == 4); + assert(rc.count == 1); + } + func(rc); + assert(*rc == 5); +} + +@nogc @system unittest +{ + auto rc = defaultAllocator.refCounted!(int[])(5); + assert(rc.length == 5); +} + +@nogc @system unittest +{ + auto p1 = defaultAllocator.make!int(5); + auto p2 = p1; + auto rc = RefCounted!int(p1, defaultAllocator); + assert(rc.get() is p2); +} + +@nogc @system unittest +{ + size_t destroyed; + { + auto rc = defaultAllocator.refCounted!WithDtor(destroyed); + } + assert(destroyed == 1); +} + +@nogc nothrow pure @system unittest +{ + auto s = defaultAllocator.unique!int(5); + assert(*s == 5); + + s = null; + assert(s is null); +} + +@nogc nothrow pure @system unittest +{ + auto s = defaultAllocator.unique!int(5); + assert(*s == 5); + + s = defaultAllocator.unique!int(4); + assert(*s == 4); +} + +@nogc nothrow pure @system unittest +{ + auto p1 = defaultAllocator.make!int(5); + auto p2 = p1; + + auto rc = Unique!int(p1, defaultAllocator); + assert(rc.get() is p2); +} + +@nogc nothrow pure @system unittest +{ + auto rc = Unique!int(defaultAllocator); + assert(rc.allocator is defaultAllocator); +} + +@nogc @system unittest +{ + uint destroyed; + auto a = defaultAllocator.make!A(destroyed); + + assert(destroyed == 0); + { + auto rc = RefCounted!A(a, defaultAllocator); + assert(rc.count == 1); + + void func(RefCounted!A rc) @nogc @system + { + assert(rc.count == 2); + } + func(rc); + + assert(rc.count == 1); + } + assert(destroyed == 1); + + RefCounted!int rc; + assert(rc.count == 0); + rc = defaultAllocator.make!int(8); + assert(rc.count == 1); +} + +@nogc nothrow pure @safe unittest +{ + static assert(is(ReturnType!(RefCounted!int.get) == inout int*)); + static assert(is(ReturnType!(RefCounted!A.get) == inout A)); + static assert(is(ReturnType!(RefCounted!B.get) == inout B*)); +} + +@nogc nothrow pure @safe unittest +{ + static assert(is(RefCounted!B)); + static assert(is(RefCounted!A)); +} + +@nogc @system unittest +{ + struct E + { + } + auto b = defaultAllocator.refCounted!B(15); + static assert(is(typeof(b.prop) == int)); + static assert(!is(typeof(defaultAllocator.refCounted!B()))); + + static assert(is(typeof(defaultAllocator.refCounted!E()))); + static assert(!is(typeof(defaultAllocator.refCounted!E(5)))); + { + auto rc = defaultAllocator.refCounted!B(3); + assert(rc.get().prop == 3); + } + { + auto rc = defaultAllocator.refCounted!E(); + assert(rc.count); + } +} + +@nogc nothrow pure @safe unittest +{ + static assert(is(typeof(defaultAllocator.unique!B(5)))); + static assert(is(typeof(defaultAllocator.unique!(int[])(5)))); +} + +private class A +{ + uint *destroyed; + + this(ref uint destroyed) @nogc + { + this.destroyed = &destroyed; + } + + ~this() @nogc + { + ++(*destroyed); + } +} + +private struct B +{ + int prop; + @disable this(); + this(int param1) @nogc + { + prop = param1; + } +} diff --git a/tests/tanya/meta/tests/metafunction.d b/tests/tanya/meta/tests/metafunction.d new file mode 100644 index 0000000..bea3014 --- /dev/null +++ b/tests/tanya/meta/tests/metafunction.d @@ -0,0 +1,27 @@ +module tanya.meta.tests.metafunction; + +import tanya.meta.metafunction; + +@nogc nothrow pure @safe unittest +{ + enum cmp(int x, int y) = x - y; + static assert(isSorted!(cmp)); + static assert(isSorted!(cmp, 1)); + static assert(isSorted!(cmp, 1, 2, 2)); + static assert(isSorted!(cmp, 1, 2, 2, 4)); + static assert(isSorted!(cmp, 1, 2, 2, 4, 8)); + static assert(!isSorted!(cmp, 32, 2, 2, 4, 8)); + static assert(isSorted!(cmp, 32, 32)); +} + +@nogc nothrow pure @safe unittest +{ + enum cmp(int x, int y) = x < y; + static assert(isSorted!(cmp)); + static assert(isSorted!(cmp, 1)); + static assert(isSorted!(cmp, 1, 2, 2)); + static assert(isSorted!(cmp, 1, 2, 2, 4)); + static assert(isSorted!(cmp, 1, 2, 2, 4, 8)); + static assert(!isSorted!(cmp, 32, 2, 2, 4, 8)); + static assert(isSorted!(cmp, 32, 32)); +} diff --git a/tests/tanya/meta/tests/trait.d b/tests/tanya/meta/tests/trait.d new file mode 100644 index 0000000..bc06aef --- /dev/null +++ b/tests/tanya/meta/tests/trait.d @@ -0,0 +1,169 @@ +module tanya.meta.tests.trait; + +import tanya.meta.metafunction; +import tanya.meta.trait; + +// typeof(null) is not a pointer. +@nogc nothrow pure @safe unittest +{ + static assert(!isPointer!(typeof(null))); + static assert(!isPointer!(const shared typeof(null))); + + enum typeOfNull : typeof(null) + { + null_ = null, + } + static assert(!isPointer!typeOfNull); +} + +@nogc nothrow pure @safe unittest +{ + static struct S + { + @property int opCall() + { + return 0; + } + } + S s; + static assert(isCallable!S); + static assert(isCallable!s); +} + +@nogc nothrow pure @safe unittest +{ + static assert(is(FunctionTypeOf!(void delegate()) == function)); + + static void staticFunc() + { + } + auto functionPointer = &staticFunc; + static assert(is(FunctionTypeOf!staticFunc == function)); + static assert(is(FunctionTypeOf!functionPointer == function)); + + void func() + { + } + auto dg = &func; + static assert(is(FunctionTypeOf!func == function)); + static assert(is(FunctionTypeOf!dg == function)); + + interface I + { + @property int prop(); + } + static assert(is(FunctionTypeOf!(I.prop) == function)); + + static struct S + { + void opCall() + { + } + } + class C + { + static void opCall() + { + } + } + S s; + + static assert(is(FunctionTypeOf!s == function)); + static assert(is(FunctionTypeOf!C == function)); + static assert(is(FunctionTypeOf!S == function)); +} + +@nogc nothrow pure @safe unittest +{ + static struct S2 + { + @property int opCall() + { + return 0; + } + } + S2 s2; + static assert(is(FunctionTypeOf!S2 == function)); + static assert(is(FunctionTypeOf!s2 == function)); +} + +@nogc nothrow pure @safe unittest +{ + static assert(!hasElaborateAssign!int); + + static struct S1 + { + void opAssign(S1) + { + } + } + static struct S2 + { + void opAssign(int) + { + } + } + static struct S3 + { + S1 s; + alias s this; + } + static assert(hasElaborateAssign!S1); + static assert(!hasElaborateAssign!(const S1)); + static assert(hasElaborateAssign!(S1[1])); + static assert(!hasElaborateAssign!(S1[0])); + static assert(!hasElaborateAssign!S2); + static assert(hasElaborateAssign!S3); + + static struct S4 + { + void opAssign(S4) + { + } + @disable this(this); + } + static assert(hasElaborateAssign!S4); +} + +// Produces a tuple for an enum with only one member +@nogc nothrow pure @safe unittest +{ + enum E : int + { + one = 0, + } + static assert(EnumMembers!E == AliasSeq!0); +} + +@nogc nothrow pure @safe unittest +{ + class RefCountedStore(T) + { + } + static assert(!isInnerClass!(RefCountedStore!int)); +} + +@nogc nothrow pure @safe unittest +{ + static struct DisabledOpEquals + { + @disable bool opEquals(typeof(this)) @nogc nothrow pure @safe; + + int opCmp(typeof(this)) @nogc nothrow pure @safe + { + return 0; + } + } + static assert(!isEqualityComparable!DisabledOpEquals); + static assert(isOrderingComparable!DisabledOpEquals); + + static struct OpEquals + { + bool opEquals(typeof(this)) @nogc nothrow pure @safe + { + return true; + } + } + static assert(isEqualityComparable!OpEquals); + static assert(!isOrderingComparable!OpEquals); +} diff --git a/tests/tanya/meta/tests/transform.d b/tests/tanya/meta/tests/transform.d new file mode 100644 index 0000000..c81232c --- /dev/null +++ b/tests/tanya/meta/tests/transform.d @@ -0,0 +1,50 @@ +module tanya.meta.tests.transform; + +import tanya.meta.transform; + +@nogc nothrow pure @safe unittest +{ + static assert(is(CommonType!(void*, int*) == void*)); + static assert(is(CommonType!(void*, const(int)*) == const(void)*)); + static assert(is(CommonType!(void*, const(void)*) == const(void)*)); + static assert(is(CommonType!(int*, void*) == void*)); + static assert(is(CommonType!(const(int)*, void*) == const(void)*)); + static assert(is(CommonType!(const(void)*, void*) == const(void)*)); + + static assert(is(CommonType!() == void)); + static assert(is(CommonType!(int*, const(int)*) == const(int)*)); + static assert(is(CommonType!(int**, const(int)**) == const(int*)*)); + + static assert(is(CommonType!(float, double) == double)); + static assert(is(CommonType!(float, int) == void)); + + static assert(is(CommonType!(bool, const bool) == bool)); + static assert(is(CommonType!(int, bool) == void)); + static assert(is(CommonType!(int, void) == void)); + static assert(is(CommonType!(Object, void*) == void)); + + class A + { + } + static assert(is(CommonType!(A, Object) == Object)); + static assert(is(CommonType!(const(A)*, Object*) == const(Object)*)); + static assert(is(CommonType!(A, typeof(null)) == A)); + + class B : A + { + } + class C : A + { + } + static assert(is(CommonType!(B, C) == A)); + + static struct S + { + int opCast(T : int)() + { + return 1; + } + } + static assert(is(CommonType!(S, int) == void)); + static assert(is(CommonType!(const S, S) == const S)); +} diff --git a/tests/tanya/os/tests/error.d b/tests/tanya/os/tests/error.d new file mode 100644 index 0000000..c224438 --- /dev/null +++ b/tests/tanya/os/tests/error.d @@ -0,0 +1,9 @@ +module tanya.os.tests.error; + +import tanya.os.error; + +@nogc nothrow pure @safe unittest +{ + ErrorCode ec = cast(ErrorCode.ErrorNo) -1; + assert(ec.toString() is null); +} -- cgit v1.2.3