diff options
| author | Eugen Wissner <belka@caraus.de> | 2019-03-18 13:03:52 +0100 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2019-03-19 07:45:52 +0100 |
| commit | 484cb13317921c78468dc3f9c16a0fc786b119c0 (patch) | |
| tree | 22abd11091872e470112426a97ec9a89809a8e3c /middle | |
| parent | 5ab99cf8873b130284336c52551ccede28f6cb2e (diff) | |
| download | tanya-484cb13317921c78468dc3f9c16a0fc786b119c0.tar.gz | |
Separate non-documentation tests from the code
Diffstat (limited to 'middle')
| -rw-r--r-- | middle/dub.json | 22 | ||||
| -rw-r--r-- | middle/tanya/memory/allocator.d | 81 | ||||
| -rw-r--r-- | middle/tanya/memory/lifetime.d | 815 | ||||
| -rw-r--r-- | middle/tanya/memory/mallocator.d | 218 | ||||
| -rw-r--r-- | middle/tanya/memory/mmappool.d | 503 | ||||
| -rw-r--r-- | middle/tanya/memory/op.d | 375 | ||||
| -rw-r--r-- | middle/tanya/memory/package.d | 132 | ||||
| -rw-r--r-- | middle/tanya/memory/smartref.d | 634 |
8 files changed, 2780 insertions, 0 deletions
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. + * + * <pre> + * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * | | | | | || | | | + * | |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 | | | + * | | | | | || | | | + * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * </pre> + */ +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); +} |
