Separate non-documentation tests from the code
This commit is contained in:
22
middle/dub.json
Normal file
22
middle/dub.json
Normal file
@ -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": [
|
||||
"."
|
||||
]
|
||||
}
|
81
middle/tanya/memory/allocator.d
Normal file
81
middle/tanya/memory/allocator.d
Normal file
@ -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;
|
||||
}
|
815
middle/tanya/memory/lifetime.d
Normal file
815
middle/tanya/memory/lifetime.d
Normal file
@ -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));
|
||||
}
|
||||
})));
|
||||
}
|
218
middle/tanya/memory/mallocator.d
Normal file
218
middle/tanya/memory/mallocator.d
Normal file
@ -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_;
|
||||
}
|
503
middle/tanya/memory/mmappool.d
Normal file
503
middle/tanya/memory/mmappool.d
Normal file
@ -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*;
|
||||
}
|
375
middle/tanya/memory/op.d
Normal file
375
middle/tanya/memory/op.d
Normal file
@ -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"));
|
||||
}
|
132
middle/tanya/memory/package.d
Normal file
132
middle/tanya/memory/package.d
Normal file
@ -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;
|
||||
}
|
634
middle/tanya/memory/smartref.d
Normal file
634
middle/tanya/memory/smartref.d
Normal file
@ -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);
|
||||
}
|
Reference in New Issue
Block a user