diff --git a/source/numem/collections/vector.d b/source/numem/collections/vector.d index 1861e8e..20502e5 100644 --- a/source/numem/collections/vector.d +++ b/source/numem/collections/vector.d @@ -6,11 +6,11 @@ */ module numem.collections.vector; +import numem.core.hooks; +import numem.core.memory.lifetime; import numem.core.memory.smartptr; import numem.core; import numem.core.exception; -import core.stdc.stdlib : malloc, realloc, free; -import core.stdc.string : memcpy, memmove; import core.atomic : atomicFetchAdd, atomicFetchSub, atomicStore, atomicLoad; import std.math.rounding : quantize, ceil; import std.traits; @@ -47,7 +47,19 @@ private: // Internal resize function pragma(inline, true) - void resize_(size_t size) { + void resize_(size_t size, bool shrinkToFit=false) { + if (shrinkToFit && memory && capacity_ > size_) { + static if (ownsMemory) { + if (size < size_) + this.deleteRange(size, size_); + } + + memory = cast(T*)nuRealloc(cast(void*)memory, size*T.sizeof); + capacity_ = size; + size_ = size; + return; + } + if (size >= capacity_) this.reserve_(size); @@ -65,49 +77,28 @@ private: capacity_ = capacity + (capacity%VECTOR_ALIGN); // Reallocate the malloc'd portion if there is anything to realloc. - if (memory) memory = cast(T*) realloc(cast(void*)memory, capacity_*T.sizeof); - else memory = cast(T*)malloc(capacity_*T.sizeof); + if (memory) memory = cast(T*) nuRealloc(cast(void*)memory, capacity_*T.sizeof); + else memory = cast(T*) nuAlloc(capacity_*T.sizeof); // Initialize newly allocated memory, else if T has postblit or move constructors, // those slots will be mistaken for live objects and their destructor called during // a opOpAssign operation. // But, we need to do this without triggering the postblit or move-constructor here, // or the same problem happen! - for (size_t n = before; n < capacity_; ++n) { - - T tmp = T.init; - memcpy(memory + n, &tmp, T.sizeof); - } + for (size_t n = before; n < capacity_; n++) + initializeAt(memory[n]); } } pragma(inline, true) - void copy(T* dst, T* src, size_t length) { - this.freeRange(dst-memory, length); - - static if (__traits(hasCopyConstructor, T)) { - - // Initializer - T tmp = T.init; - foreach(i; 0..length) { - - // Copy over initializer - memcpy(dst + i, &tmp, T.sizeof); - - // Call copy constructor - dst[i].__ctor(src[i]); - } - - } else static if(__traits(hasPostblit, T)) { - - memcpy(dst, src, T.sizeof*length); - foreach(i; 0..length) { - dst[i].__xpostblit(); - } - } else { + void assign(T[] src) { + this.resize_(src.length); + this.copy(this.memory, src.ptr, src.length); + } - memcpy(dst, src, T.sizeof*length); - } + pragma(inline, true) + void copy(T* dst, T* src, size_t length) { + nogc_copy(dst[0..length], src[0..length]); } // Gets whether src overlaps with the memory in the vector. @@ -117,82 +108,32 @@ private: pragma(inline, true) void move(T* dst, T* src, size_t length) { - if (!src || !dst) - return; - - memmove(dst, src, T.sizeof*length); - if (this.doSourceOverlap(src, length)) { - if (src < dst) { - this.fillRangeInit(src, dst-src); - } else if (src > dst) { - auto ptr = dst+length; - auto len = (src-length)-ptr; - this.fillRangeInit(ptr, len); - } - } + nuMemmove(dst, src, T.sizeof*length); } pragma(inline, true) - void fillRangeInit(T* dst, size_t length) { - foreach(i; 0..length) { - - T tmp = T.init; - memcpy(dst + i, &tmp, T.sizeof); - } + void deleteRange(size_t start, size_t end) { + static if (ownsMemory) + nogc_delete(this.memory[start..end]); } - pragma(inline, true) - void freeRange(size_t offset, size_t length) { - static if (ownsMemory) { - - // Heap allocated items require more smarts. - static if (isPointer!T || is(T == class)) { - import numem.collections.set : weak_set; - - // To prevent double-frees this is neccesary. - weak_set!(void*) freed; - - foreach_reverse(i; offset..offset+length) { - if (cast(void*)memory[i] in freed) - continue; - - freed.insert(cast(void*)memory[i]); - - // Basic types don't need destruction, - // so we can skip this step. - static if (!isBasicType!T) - nogc_delete(memory[i]); - memory[i] = null; - } - - nogc_delete(freed); - } else static if (!isBasicType!T) { - - foreach_reverse(i; offset..offset+length) - nogc_delete(memory[i]); - } - } - } - - void freeAll() { - this.freeRange(0, size_); + void deleteAll() { + this.deleteRange(0, size_); } public: - /// Gets the type of character stored in the string. + /// Gets the type of the values stored in the vector. alias valueType = T; /// Destructor @trusted ~this() { if (this.memory) { - static if (ownsMemory) { - this.freeRange(0, size_); - } + this.deleteAll(); // Free the pointer - free(cast(void*)this.memory); + nuFree(this.memory); } this.memory = null; @@ -209,15 +150,13 @@ public: /// Constructor @trusted this(T[] data) { - this.resize_(data.length); - this.copy(this.memory, data.ptr, data.length); + this.assign(data); } /// Constructor @trusted this(ref T[] data) { - this.resize_(data.length); - this.copy(this.memory, data.ptr, data.length); + this.assign(data); } /** @@ -228,8 +167,7 @@ public: @trusted this(T)(ref T rhs) if(!is(T == selfType) && isSomeVector!T) { if (rhs.memory) { - this.resize_(rhs.size_); - this.copy(this.memory, rhs.memory, rhs.size_); + this.assign(rhs[]); } } @@ -308,9 +246,7 @@ public: @trusted void shrinkToFit() { if (capacity_ > size_) { - capacity_ = size_; - if (size_ > 0) memory = cast(T*) realloc(memory, size_); - else free(memory); + this.resize_(size_, true); } } @@ -370,7 +306,7 @@ public: void clear() { // Delete elements in the array. - this.freeAll(); + this.deleteAll(); this.size_ = 0; } @@ -381,11 +317,11 @@ public: void remove(size_t position) { if (position < size_) { - static if (ownsMemory && !isBasicType!T) + static if (ownsMemory) nogc_delete(memory[position]); // Move memory region around so that the deleted element is overwritten. - this.move(memory+position, memory+position+1, (size_-1)*T.sizeof); + this.move(memory+position, memory+position+1, (size_-1)); size_--; } @@ -397,20 +333,14 @@ public: */ @trusted void remove(size_t start, size_t end) { - assert(start <= end && end <= size_); - // NOTE: the ".." operator is start inclusive, end exclusive. - static if (ownsMemory && !isBasicType!T) { - foreach_reverse(i; start..end) - nogc_delete(memory[i]); - - } + static if (ownsMemory && !isBasicType!T) + this.deleteRange(start, end); // Copy over old elements size_t span = end-start; - // memory[start..start+span] = memory[end..end+span]; - this.move(memory+start, memory+end, span*(T*).sizeof); + this.move(memory+start, memory+end, span); size_ -= span; } @@ -420,16 +350,10 @@ public: */ @trusted void insert(U)(size_t offset, auto ref U item) if (is(U : T)) { - if (offset >= size_) { - this.pushBack(item); - return; - } - - size_t toShift = size_-offset; - this.resize_(size_+1); - this.move(memory+offset+1, memory+offset, toShift); - this.memory[offset] = item; + // Simplify by reusing implementation for ranged inserts. + const U[1] itm = item; + this.insert(offset, itm[]); } /** @@ -470,7 +394,7 @@ public: Pushes an element to the back of the vector */ @trusted - ref auto typeof(this) pushBack(T)(T item) { + ref auto typeof(this) pushBack()(ref auto T item) { this.resize(size_+1); this.memory[size_-1] = item; @@ -493,12 +417,12 @@ public: Pushes an element to the front of the vector */ @trusted - ref auto typeof(this) pushFront(T)(T value) { + ref auto typeof(this) pushFront()(ref auto T value) { size_t cSize = size_; this.resize(size_+1); if (cSize > 0) - this.move(&this.memory[1], this.memory, cSize*(T*).sizeof); + this.move(&this.memory[1], this.memory, cSize); this.memory[0] = value; return this; @@ -534,7 +458,7 @@ public: Add value to vector */ @trusted - ref auto typeof(this) opOpAssign(string op = "~")(T value) { + ref auto typeof(this) opOpAssign(string op = "~")(ref auto T value) { return this.pushBack(value); } @@ -658,7 +582,7 @@ public: if (this.doSourceOverlap(cast(T*)values.ptr, values.length)) return false; - this.copy(cast(T*)memory+offset, cast(T*)values.ptr, values.length); + this.copy(cast(T*)&memory[offset], cast(T*)values.ptr, values.length); return true; } diff --git a/source/numem/core/casting.d b/source/numem/core/casting.d index a06518e..4bfca81 100644 --- a/source/numem/core/casting.d +++ b/source/numem/core/casting.d @@ -9,23 +9,14 @@ Numem casting helpers */ module numem.core.casting; +import numem.core.traits; nothrow @nogc: -/** - Removes all qualifiers from type T. -*/ -template Unqual(T : const U, U) { - static if(is(U == shared V, V)) - alias Unqual = V; - else - alias Unqual = U; -} - /** Safely casts between `T` and `U`. */ -T dynamic_cast(T, U)(auto ref U from) if(is(T : U)) { +auto ref T dynamic_cast(T, U)(auto ref U from) if(is(T : U)) { return cast(T)from; } @@ -36,7 +27,7 @@ T dynamic_cast(T, U)(auto ref U from) if(is(T : U)) { This will NOT call opCast of aggregate types! */ pragma(inline, true) -T reinterpret_cast(T, U)(auto ref U from) if (T.sizeof == U.sizeof) { +auto ref T reinterpret_cast(T, U)(auto ref U from) if (T.sizeof == U.sizeof) { union tmp { U from; T to; } return tmp(from).to; } @@ -76,7 +67,7 @@ unittest { ``` */ pragma(inline, true) -T const_cast(T, U)(auto ref U from) if (is(Unqual!T : Unqual!U) || is(Unqual!U : Unqual!T)) { +auto ref T const_cast(T, U)(auto ref U from) if (isAnyCompatible!(T, U)) { return reinterpret_cast!(T, U)(from); } diff --git a/source/numem/core/hooks.d b/source/numem/core/hooks.d index 3c5aaef..78fa7e2 100644 --- a/source/numem/core/hooks.d +++ b/source/numem/core/hooks.d @@ -19,8 +19,7 @@ */ module numem.core.hooks; public import core.attribute : weak; - -@nogc nothrow: +import numem.core.utils; /** Allocates `bytes` worth of memory. @@ -32,7 +31,8 @@ public import core.attribute : weak; */ @weak extern(C) -void* nuAlloc(size_t bytes) { +void* nuAlloc(size_t bytes) @nogc nothrow { + import core.stdc.stdlib : malloc; return malloc(bytes); } @@ -48,7 +48,8 @@ void* nuAlloc(size_t bytes) { */ @weak extern(C) -void* nuRealloc(void* data, size_t newSize) { +void* nuRealloc(void* data, size_t newSize) @nogc nothrow { + import core.stdc.stdlib : realloc; return realloc(data, newSize); } @@ -63,7 +64,8 @@ void* nuRealloc(void* data, size_t newSize) { */ @weak extern(C) -void nuFree(void* data) { +void nuFree(void* data) @nogc nothrow { + import core.stdc.stdlib : free; free(data); } @@ -79,9 +81,24 @@ void nuFree(void* data) { */ @weak extern(C) -void* nuMemcpy(inout(void)* dst, inout(void)* src, size_t bytes) { +void* nuMemcpy(return scope void* dst, return scope void* src, size_t bytes) @nogc nothrow { + import core.stdc.string : memcpy; - return memcpy(cast(void*)dst, cast(void*)src, bytes); + return memcpy(dst, src, bytes); +} + +/** + Copies `bytes` worth of data from `src` into `dst`. + Memory needs to be allocated and within range. + + This calls `nuMemcpy(inout(void)* dst, inout(void)* src, size_t bytes)` + internally. +*/ +extern(D) +void* nuCopy(T)(inout(T)[] dst, inout(T)[] src) @nogc nothrow { + + assert(dst.length >= src.length, "Destination is shorter than source!"); + return nuMemcpy(dst.ptr, src.ptr, T.sizeof*src.length); } /** @@ -95,7 +112,8 @@ void* nuMemcpy(inout(void)* dst, inout(void)* src, size_t bytes) { */ @weak extern(C) -void* nuMemmove(void* dst, void* src, size_t bytes) { +void* nuMemmove(void* dst, void* src, size_t bytes) @nogc nothrow { + import core.stdc.string : memmove; return memmove(dst, src, bytes); } @@ -108,7 +126,9 @@ void* nuMemmove(void* dst, void* src, size_t bytes) { By default calls C stdlib memset. */ -void* nuMemset(void* dst, ubyte value, size_t bytes) { +extern(C) +void* nuMemset(void* dst, ubyte value, size_t bytes) @nogc nothrow { + import core.stdc.string : memset; return memset(dst, value, bytes); } @@ -123,7 +143,8 @@ void* nuMemset(void* dst, ubyte value, size_t bytes) { */ @weak extern(C) -void nuAbort() { +void nuAbort() @nogc nothrow { + import core.stdc.stdlib : abort; abort(); } \ No newline at end of file diff --git a/source/numem/core/memory/lifetime.d b/source/numem/core/memory/lifetime.d index 1733f19..167a18a 100644 --- a/source/numem/core/memory/lifetime.d +++ b/source/numem/core/memory/lifetime.d @@ -12,8 +12,10 @@ module numem.core.memory.lifetime; import numem.core.utils; import numem.core.hooks; import numem.core.trace; -import core.lifetime : forward; -import core.internal.traits; +import numem.core.traits; +import core.lifetime; +import numem.core.casting; +import numem.core.memory; // Deletion function signature. private extern (D) alias fp_t = void function (Object) @nogc nothrow; @@ -22,9 +24,8 @@ private extern (D) alias fp_t = void function (Object) @nogc nothrow; Destroy element with a destructor. */ @trusted -void destruct(T, bool doFree=true)(ref T obj_) @nogc nothrow { - - static if (isPointer!T || is(T == class)) { +void destruct(T, bool reInit=true)(ref T obj_) @nogc nothrow { + static if (isHeapAllocated!T) { if (obj_ !is null) { auto cInfo = cast(ClassInfo)typeid(obj_); if (cInfo) { @@ -40,37 +41,25 @@ void destruct(T, bool doFree=true)(ref T obj_) @nogc nothrow { } else { // Item is a struct, we can destruct it directly. - static if (__traits(hasMember, T, "__dtor")) { - assumeNothrowNoGC!(typeof(&obj_.__dtor))(&obj_.__dtor)(); - } else static if (__traits(hasMember, T, "__xdtor")) { + static if (__traits(hasMember, T, "__xdtor")) { assumeNothrowNoGC!(typeof(&obj_.__xdtor))(&obj_.__xdtor)(); + } else static if (__traits(hasMember, T, "__dtor")) { + assumeNothrowNoGC!(typeof(&obj_.__dtor))(&obj_.__dtor)(); } } - - static if (doFree) { - nuFree(cast(void*)obj_); - obj_ = null; - } } } else { // Item is a struct, we can destruct it. - static if (__traits(hasMember, T, "__dtor")) { - assumeNothrowNoGC!(typeof(&obj_.__dtor))(&obj_.__dtor)(); - } else static if (__traits(hasMember, T, "__xdtor")) { + static if (__traits(hasMember, T, "__xdtor")) { assumeNothrowNoGC!(typeof(&obj_.__xdtor))(&obj_.__xdtor)(); + } else static if (__traits(hasMember, T, "__dtor")) { + assumeNothrowNoGC!(typeof(&obj_.__dtor))(&obj_.__dtor)(); } } -} -/** - Gets the amount of bytes needed to allocate an instance of type `T`. -*/ -template nuAllocSize(T) { - static if (is(T == class)) - enum nuAllocSize = __traits(classInstanceSize, T); - else - enum nuAllocSize = T.sizeof; + static if (reInit) + initializeAt(obj_); } /** @@ -83,16 +72,49 @@ void initializeAt(T)(scope ref T chunk) @nogc nothrow @trusted { // in general circumstances is null, we don't want this, so class check // should be first! Otherwise the chunk = T.init will mess us up. const void[] initSym = __traits(initSymbol, T); - nuMemcpy(cast(void*)chunk, initSym.ptr, initSym.length); + nuMemcpy(cast(void*)chunk, cast(void*)initSym.ptr, initSym.length); } else static if (__traits(isZeroInit, T)) { + + nuMemset(cast(void*)&chunk, 0, T.sizeof); + } else static if (__traits(isScalar, T) || + (T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, () { T chunk; chunk = T.init; }))) { + + // To avoid triggering postblits/move constructors we need to do a memcpy here as well. + // If the user wants to postblit after initialization, they should call the relevant postblit function. + T tmp = T.init; + nuMemcpy(cast(void*)&chunk, &tmp, T.sizeof); + } else static if (__traits(isStaticArray, T)) { + + foreach(i; 0..T.length) + initializeAt(chunk[i]); + } else { + + const void[] initSym = __traits(initSymbol, T); + nuMemcpy(cast(void*)&chunk, initSym.ptr, initSym.length); + } +} + +/** + Initializes the memory at the specified chunk, but ensures no + context pointers are wiped. +*/ +void initializeAtNoCtx(T)(scope ref T chunk) @nogc nothrow @trusted { + static if (__traits(isZeroInit, T)) { + nuMemset(cast(void*)&chunk, 0, T.sizeof); } else static if (__traits(isScalar, T) || (T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, () { T chunk; chunk = T.init; }))) { - chunk = T.init; + + // To avoid triggering postblits/move constructors we need to do a memcpy here as well. + // If the user wants to postblit after initialization, they should call the relevant postblit function. + T tmp = T.init; + nuMemcpy(cast(void*)&chunk, &tmp, T.sizeof); } else static if (__traits(isStaticArray, T)) { + foreach(i; 0..T.length) initializeAt(chunk[i]); } else { + const void[] initSym = __traits(initSymbol, T); nuMemcpy(cast(void*)&chunk, initSym.ptr, initSym.length); } @@ -196,45 +218,103 @@ void emplace(UT, Args...)(auto ref UT dst, auto ref Args args) @nogc nothrow { } /** - Gets the reference type version of type T. + Copies source to target. */ -template RefT(T) { - static if (is(T == class) || isPointer!T) - alias RefT = T; - else - alias RefT = T*; +void __copy(S, T)(ref S source, ref T target) @nogc { + static if (is(T == struct)) { + static if (!__traits(hasCopyConstructor, T)) + __blit(target, source); + + static if (hasElaborateCopyConstructor!T) + __copy_postblit(source, target); + } else static if (is(T == E[n], E, size_t n)) { + + // Some kind of array or range. + static if (hasElaborateCopyConstructor!E) { + size_t i; + try { + for(i = 0; i < n; i++) + __copy(source[i], target[i]); + } catch(Exception ex) { + while(i--) + nogc_delete(const_cast!(Unconst!(E)*)(&target[i])); + throw e; + } + } else static if (!__traits(hasCopyConstructor, T)) + __blit(target, source); + } else { + *(const_cast!(Unconst!(T)*)(&target)) = *const_cast!(Unconst!(T)*)(&source); + } } /** - Gets whether `T` supports moving. + Moves source to target, via destructive copy if neccesary. + source will be reset to its init state after the move. */ -enum IsMovable(T) = - is(T == struct) || - (__traits(isStaticArray, T) && hasElaborateMove!(T.init[0])); +void __move(S, T)(ref S source, ref T target) @nogc { + static if(is(T == struct)) { + static if (hasElaborateDestructor!T) + destruct!(T, false)(target); + + assert(&source !is &target, "Source and target must not be identical"); + __blit(target, source); + + static if (hasElaborateMove!T) + __move_postblit(target, source); + + // If the source defines a destructor or a postblit the type needs to be + // obliterated to avoid double frees and undue aliasing. + static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) { + initializeAtNoCtx(source); + } + } else static if (is(T == E[n], E, size_t n)) { + static if (!hasElaborateMove!T && + !hasElaborateDestructor!T && + !hasElaborateCopyConstructor!T) { + + assert(source.ptr !is target.ptr, "Source and target must not be identical"); + __blit(target, source); + initializeAt(source); + } else { + foreach(i; 0..source.length) { + __move(source[i], target[i]); + } + } + } else { + target = source; + initializeAt(source); + } +} /** Blits instance `from` to location `to`. + + Effectively this acts as a simple memory copy, + a postblit needs to be run after to finalize the object. */ -void __blit(T, bool copy)(ref T to, ref T from) { - nuMemcpy(to, from, nuAllocSize!T); - static if (copy) __copy_postblit(to, from); - else __move_postblit(to, from); +pragma(inline, true) +void __blit(T)(ref T to, ref T from) @nogc nothrow { + nuMemcpy(const_cast!(Unqual!T*)(&to), const_cast!(Unqual!T*)(&from), AllocSize!T); } /** - Runs copy postblit operations for `dst`. - - If `dst` has a copy constructor it will be run, - otherwise if it has a `this(this)` postblit that will be run. - - If no form of postblit is available, this function will be NO-OP. + Runs postblit operations for a copy operation. */ pragma(inline, true) -void __copy_postblit(T)(ref T dst, ref T src) @nogc nothrow { - static if (__traits(hasCopyConstructor, T)) { - dst.__ctor(src); - } else static if(__traits(hasPostblit, T)) { +void __copy_postblit(S, T)(ref S source, ref T target) @nogc nothrow { + static if (__traits(hasPostblit, T)) { dst.__xpostblit(); + } else static if (__traits(hasCopyConstructor, T)) { + + // https://issues.dlang.org/show_bug.cgi?id=22766 + initializeAt(target); + + // Copy context pointer if needed. + static if (__traits(isNested, T)) + *(cast(void**)&target.tupleof[$-1]) = cast(void*) source.tupleof[$-1]; + + // Invoke copy ctor. + target.__ctor(source); } } @@ -278,4 +358,43 @@ void __move_postblit(T)(ref T newLocation, ref T oldLocation) { __move_postblit(newLocation[i], oldLocation[i]); } } +} + +/** + Forwards function arguments while keeping `out`, `ref`, and `lazy` on + the parameters. + + Params: + args = a parameter list or an $(REF AliasSeq,std,meta). + Returns: + An `AliasSeq` of `args` with `out`, `ref`, and `lazy` saved. +*/ +template forward(args...) +{ + import core.internal.traits : AliasSeq; + + template fwd(alias arg) + { + // by ref || lazy || const/immutable + static if (__traits(isRef, arg) || + __traits(isOut, arg) || + __traits(isLazy, arg) || + !is(typeof(move(arg)))) + alias fwd = arg; + // (r)value + else + @property auto fwd() + { + version (DigitalMars) { /* @@BUG 23890@@ */ } else pragma(inline, true); + return move(arg); + } + } + + alias Result = AliasSeq!(); + static foreach (arg; args) + Result = AliasSeq!(Result, fwd!arg); + static if (Result.length == 1) + alias forward = Result[0]; + else + alias forward = Result; } \ No newline at end of file diff --git a/source/numem/core/memory/package.d b/source/numem/core/memory/package.d index a9a4c60..e3315d4 100644 --- a/source/numem/core/memory/package.d +++ b/source/numem/core/memory/package.d @@ -8,14 +8,16 @@ module numem.core.memory; import numem.core.memory.lifetime; import numem.core.hooks; -import std.traits; +import numem.core.traits; public import numem.core.memory.smartptr; debug(trace) import numem.core.trace; +import numem.core.casting; /** UDA which allows initializing an empty struct, even when copying is disabled. */ +deprecated("This UDA is no longer in use by numem.") struct AllowInitEmpty; /** @@ -23,22 +25,23 @@ struct AllowInitEmpty; Attempting to construct a non-initialized `object` is undefined behaviour. */ -void nogc_construct(T, UT = T, Args...)(ref UT object, Args args) { - static if (is(T == class) || isPointer!T) - emplace(object, args); - else +void nogc_construct(T, Args...)(ref T object, Args args) { + static if (isPointer!T) emplace(*object, args); + else + emplace(object, args); + } /** Allocates a new instance of type T. */ -RefT!T nogc_new(T, Args...)(Args args) { - RefT!T newobject = cast(RefT!T)nuAlloc(nuAllocSize!T); +Ref!T nogc_new(T, Args...)(auto ref Args args) { + Ref!T newobject = cast(Ref!T)nuAlloc(AllocSize!T); if (!newobject) nuAbort(); - nogc_construct!(T, RefT!T, Args)(newobject, args); + nogc_construct(newobject, args); // Tracing debug(trace) @@ -48,17 +51,49 @@ RefT!T nogc_new(T, Args...)(Args args) { } /** - Destroys and frees the memory. + Finalizes `obj_` by calling its destructor (if any). - For structs this will call the struct's destructor if it has any. + If `doFree` is `true`, memory associated with obj_ will additionally be freed + after finalizers have run; otherwise the object is reset to its original state. */ void nogc_delete(T, bool doFree=true)(ref T obj_) { + + // Basic types do not need to be traced. + debug(trace) { + static if(!isBasicType!T) { + dbg_dealloc(obj_); + } + } - destruct!(T, doFree)(obj_); + static if (isHeapAllocated!T) { - // Tracing - debug(trace) - dbg_dealloc(obj_); + // Ensure type is not null. + if (reinterpret_cast!(void*)(obj_) !is null) { + + destruct!(T, !doFree)(obj_); + + // Free memory if need be. + static if (doFree) + nuFree(cast(void*)obj_); + + obj_ = null; + } + } else { + destruct!(T, !doFree)(obj_); + } +} + +/** + Finalizes the objects referenced by `objects` by calling the + destructors of its members (if any). + + If `doFree` is `true`, memory associated with each object + will additionally be freed after finalizers have run; otherwise the object + is reset to its original state. +*/ +void nogc_delete(T, bool doFree=true)(T[] objects) { + foreach(i; 0..objects.length) + nogc_delete!(T, doFree)(objects[i]); } /** @@ -69,6 +104,15 @@ void nogc_initialize(T)(ref T element) { initializeAt(element); } +/** + Initializes the objects at `elements`, filling them out with + their default state. +*/ +void nogc_initialize(T)(T[] elements) { + foreach(i; 0..elements.length) + initializeAt(elements[i]); +} + /** Zero-fills an object */ @@ -77,12 +121,10 @@ void nogc_zeroinit(T)(ref T element) { } /** - Creates an object with all of its bytes set to 0. + Zero-fills an object */ -T nogc_zeroinit(T)() { - T element; - nuMemset(&element, 0, element.sizeof); - return element; +void nogc_zeroinit(T)(T[] elements) { + nuMemset(elements.ptr, 0, elements.length*T.sizeof); } /** @@ -93,6 +135,30 @@ void nogc_emplace(T, Args...)(ref auto T dest, Args args) { emplace!(T, T, Args)(dest, args); } +/** + Moves elements in `src` to `dst` via destructive copy. + + If an element in `src` is not a valid object, + then accessing the moved element in `to` will be undefined behaviour. + + After the move operation, the original memory locations in `from` will be + reset to their base initialized state before any constructors are run. +*/ +void nogc_move(T)(T[] dst, T[] src) { + foreach(i; 0..src.length) + __move(src[i], dst[i]); +} + +/** + Copies `src` to `dst` via a blit operation. + + Postblits and copy constructors will be called subsequently. +*/ +void nogc_copy(T)(T[] dst, T[] src) { + foreach(i; 0..src.length) + __copy(src[i], dst[i]); +} + /** Moves `from` to `to` via a destructive copy. @@ -102,7 +168,15 @@ void nogc_emplace(T, Args...)(ref auto T dest, Args args) { After the move operation, the original memory location of `from` will be reset to its base initialized state before any constructors are run. */ -void moveTo(T)(ref T from, ref T to) if (IsMovable!T) { - __blit(T, false)(to, from); - initializeAt(from); +void moveTo(T)(ref T from, ref T to) { + __move(from, to); +} + +/** + Copies `from` to `to` via a blit operation. + + Postblits and copy constructors will be called subsequently. +*/ +void copyTo(T)(ref T from, ref T to) { + __copy(from, to); } \ No newline at end of file diff --git a/source/numem/core/memory/smartptr.d b/source/numem/core/memory/smartptr.d index 23a582b..9afb6bb 100644 --- a/source/numem/core/memory/smartptr.d +++ b/source/numem/core/memory/smartptr.d @@ -16,7 +16,7 @@ import numem.core; private { struct refcountmg_t(T) { - nothrow @nogc: + nothrow @nogc: T* ref_; size_t strongRefs; size_t weakRefs; diff --git a/source/numem/core/meta.d b/source/numem/core/meta.d new file mode 100644 index 0000000..82daf80 --- /dev/null +++ b/source/numem/core/meta.d @@ -0,0 +1,108 @@ +/* + Copyright © 2024, Inochi2D Project + Distributed under the 2-Clause BSD License, see LICENSE file. + + Authors: Luna the Foxgirl +*/ + +/** + Numem meta templates. + + Most of these are taken directly from the D runtime. +*/ +module numem.core.meta; + +/** + Equivalent to D runtime's AliasSeq. +*/ +alias AliasSeq(AliasList...) = AliasList; + +/** + A template which gets whether all the inputs satisfy the condition + outlined in `F`. +*/ +template allSatisfy(alias F, T...) { + static foreach(U; T) { + static if (!is(typeof(allSatisfy) == bool) && !F!(U)) + enum allSatisfy = false; + } + + static if (!is(typeof(allSatisfy) == bool)) + enum allSatisfy = true; +} + +/** + A template which gets whether any of the inputs satisfy the + condition outlined in `F`. +*/ +template anySatisfy(alias F, T...) { + static foreach(U; T) { + static if (!is(typeof(anySatisfy) == bool) && F!(U)) + enum anySatisfy = false; + } + + static if (!is(typeof(anySatisfy) == bool)) + enum anySatisfy = false; +} + +/** + Returns a sequence of F!(T[0]), F!(T[1]), ..., F!(T[$-1]) +*/ +template staticMap(alias F, T...) { + static if (T.length == 0) + alias staticMap = AliasSeq!(); + else static if (T.length == 1) + alias staticMap = AliasSeq!(F!(T[0])); + else static if (T.length == 2) + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1])); + else static if (T.length == 3) + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2])); + else static if (T.length == 4) + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3])); + else static if (T.length == 5) + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4])); + else static if (T.length == 6) + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5])); + else static if (T.length == 7) + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6])); + else static if (T.length == 8) + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6]), F!(T[7])); + else { + alias staticMap = + AliasSeq!( + staticMap!(F, T[ 0 .. $/2]), + staticMap!(F, T[$/2 .. $ ])); + } +} + +/** + Returns a sequence containing the provided sequence after filtering by `F`. +*/ +template Filter(alias F, T...) { + static if (T.length == 0) + alias Filter = AliasSeq!(); + else static if (T.length == 1) { + + // LHS + static if (F!(T[0])) + alias Filter = AliasSeq!(T[0]); + else + alias Filter = AliasSeq!(); + } else static if (T.length == 2) { + + // LHS + static if (F!(T[0])) + alias Filter = AliasSeq!(T[0]); + else + alias Filter = AliasSeq!(); + + // RHS + static if (F!(T[1])) + alias Filter = AliasSeq!(T[1]); + else + alias Filter = AliasSeq!(); + } else alias Filter = AliasSeq!( + Filter!(F, T[0 .. $/2]), + Filter!(F, T[$/2..$]) + ); +} \ No newline at end of file diff --git a/source/numem/core/trace.d b/source/numem/core/trace.d index cd88f22..5577807 100644 --- a/source/numem/core/trace.d +++ b/source/numem/core/trace.d @@ -9,29 +9,25 @@ Debug tracing module */ module numem.core.trace; +import numem.core.traits; import core.stdc.stdio; pragma(inline, true) -void dbg_alloc(T)(T item) if (is(T == class)) { +void dbg_alloc(T)(ref T item) if (isHeapAllocated!T) { debug(trace) printf("Allocated "~T.stringof~" @ %p\n", cast(void*)item); } pragma(inline, true) -void dbg_alloc(T)(T* item) if (is(T == struct)) { - debug(trace) printf("Allocated "~T.stringof~" @ %p\n", cast(void*)item); -} - -pragma(inline, true) -void dbg_alloc(T)(T* item) if (!is(T == struct) && !is(T == class)) { - debug(trace) printf("Allocated "~T.stringof~" @ %p\n", cast(void*)item); +void dbg_alloc(T)(ref T item) if (!isHeapAllocated!T) { + debug(trace) printf("Allocated "~T.stringof~" (on stack)\n"); } pragma(inline, true) -void dbg_dealloc(T)(ref T item) if (is(T == class)) { - debug(trace) printf("Freed "~T.stringof~" @ %p\n", cast(void*)item); +void dbg_dealloc(T)(ref T item) if (isHeapAllocated!T) { + debug(trace) printf("Deallocated "~T.stringof~" @ %p\n", cast(void*)item); } pragma(inline, true) -void dbg_dealloc(T)(ref T item) if (!is(T == class)) { - debug(trace) printf("Freed "~T.stringof~"\n"); +void dbg_dealloc(T)(ref T item) if (!isHeapAllocated!T) { + debug(trace) printf("Deallocated "~T.stringof~" (on stack)\n"); } \ No newline at end of file diff --git a/source/numem/core/traits.d b/source/numem/core/traits.d new file mode 100644 index 0000000..337e27c --- /dev/null +++ b/source/numem/core/traits.d @@ -0,0 +1,295 @@ +/* + Copyright © 2024, Inochi2D Project + Distributed under the 2-Clause BSD License, see LICENSE file. + + Authors: Luna the Foxgirl +*/ + +/** + Numem traits collection. +*/ +module numem.core.traits; +import numem.core.meta; +import std.traits; + +/** + Gets a sequence over all of the fields in type `T`. + + If `T` is a type with no fields, returns a sequence containing the input. +*/ +template Fields(T) { + static if(is(T == struct) || is(T == union)) + alias Fields = typeof(T.tupleof[0..$-__traits(isNested, T)]); + else static if (is(T == class) || is(T == interface)) + alias Fields = typeof(T.tupleof); + else + alias Fields = AliasSeq!T; +} + +/** + Gets the base element type of type `T`. +*/ +template BaseElemOf(T) { + static if(is(OriginalType!T == E[N], E, size_t N)) + alias BaseElemOf = BaseElemOf!E; + else + alias BaseElemOf = T; +} + +/** + Gets the original type of `T`. +*/ +template OriginalType(T) { + template Impl(T) { + static if(is(T U == enum)) alias Impl = OriginalType!U; + else alias Impl = T; + } + + alias OriginalType = ModifyTypePreservingTQ!(Impl, T); +} + +/** + Modifies type `T` to follow the predicate specified by `Modifier`. +*/ +template ModifyTypePreservingTQ(alias Modifier, T) { + static if (is(T U == immutable U)) alias ModifyTypePreservingTQ = immutable Modifier!U; + else static if (is(T U == shared inout const U)) alias ModifyTypePreservingTQ = shared inout const Modifier!U; + else static if (is(T U == shared inout U)) alias ModifyTypePreservingTQ = shared inout Modifier!U; + else static if (is(T U == shared const U)) alias ModifyTypePreservingTQ = shared const Modifier!U; + else static if (is(T U == shared U)) alias ModifyTypePreservingTQ = shared Modifier!U; + else static if (is(T U == inout const U)) alias ModifyTypePreservingTQ = inout const Modifier!U; + else static if (is(T U == inout U)) alias ModifyTypePreservingTQ = inout Modifier!U; + else static if (is(T U == const U)) alias ModifyTypePreservingTQ = const Modifier!U; + else alias ModifyTypePreservingTQ = Modifier!T; +} + +/** + Removes const type qualifiers from `T`. +*/ +alias Unconst(T : const U, U) = U; + +/** + Removes shared type qualifiers from `T`. +*/ +alias Unshared(T : shared U, U) = U; + +/** + Removes all qualifiers from type T. +*/ +template Unqual(T : const U, U) { + static if(is(U == shared V, V)) + alias Unqual = V; + else + alias Unqual = U; +} + +/** + Gets the reference type version of type `T`. +*/ +template Ref(T) { + static if (is(T == class) || isHeapAllocated!T) + alias Ref = T; + else + alias Ref = T*; +} + +/** + Gets the amount of bytes needed to allocate an instance of type `T`. +*/ +template AllocSize(T) { + static if (is(T == class)) + enum AllocSize = __traits(classInstanceSize, T); + else + enum AllocSize = T.sizeof; +} + +/** + Gets the alignment of type `T` in bytes. +*/ +template AllocAlign(T) { + static if(is(T == class)) + enum AllocAlign = __traits(classInstanceAlignment, T); + else + enum AllocAlign = T.alignof; +} + +private struct __DummyStruct { } + +/** + Returns the rvalue equivalent of T. +*/ +@property T rvalueOf(T)(T val) => val; + +/** + Returns the rvalue equivalent of `T`. + + Can only be used at compile time for type checking. +*/ +@property T rvalueOf(T)(inout(__DummyStruct) = __DummyStruct.init); + +/** + Returns the lvalue equivalent of `T`. + + Can only be used at compile time for type checking. +*/ +@property ref T lvalueOf(T)(inout __DummyStruct = __DummyStruct.init); + +/** + Gets whether `T` supports moving. +*/ +enum isMovable(T) = + (is(T == struct) || is(T == union)) || + (__traits(isStaticArray, T) && hasElaborateMove!(T.init[0])); + +/** + Gets whether `T` is an aggregate type (i.e. a type which contains other types) +*/ +enum isAggregateType(T) = + is(T == class) || is(T == interface) || + is(T == struct) || is(T == union); + +/** + Gets whether `T` is a class-like (i.e. class or interface) +*/ +enum isClasslike(T) = + is(T == class) || is(T == interface); + +/** + Gets whether the provided type is a scalar type. +*/ +enum isScalarType(T) = __traits(isScalar, T) && is(T : real); + +/** + Gets whether the provided type is a basic type. +*/ +enum isBasicType(T) = isScalarType!T || is(immutable T == immutable void); + +/** + Gets whether `T` is a pointer type. +*/ +enum isPointer(T) = + is(T == U*, U) && !isClasslike!T; + +/** + Gets whether `T` is heap allocated. +*/ +enum isHeapAllocated(T) = + is(T == class) || is(T == U*, U); + +/** + Gets whether type `T` is an array. +*/ +enum isArray(T) = is(T == E[n], E, size_t n); + +/** + Gets whether `Lhs` can be assigned to `Rhs`. +*/ +template isAssignable(Lhs, Rhs = Lhs) { + enum isAssignable = + __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs) && + __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs); +} + +/** + Gets whether `Lhs` can be assigned to `Rhs` or `Rhs` can be assigned to `Lhs`. +*/ +enum isAnyAssignable(Lhs, Rhs = Lhs) = + isAssignable!(Lhs, Rhs) || isAssignable!(Rhs, Lhs); + +/** + Gets whether the unqualified versions of `Lhs` and `Rhs` are in + any way compatible in any direction. +*/ +enum isAnyCompatible(Lhs, Rhs) = + is(Unqual!Lhs : Unqual!Rhs) || is(Unqual!Rhs : Unqual!Lhs); + +/** + Gets whether `symbol` has the user defined attribute `attrib`. +*/ +template hasUDA(alias symbol, alias attrib) { + + enum isAttr(T) = is(T == attrib); + enum hasUDA = anySatisfy!(isAttr, __traits(getAttributes, symbol)); +} + +/** + Gets a sequence of all of the attributes within attrib. +*/ +template getUDAs(alias symbol, alias attrib) { + + enum isAttr(T) = is(T == attrib); + alias getUDAs = Filter!(isAttr, __traits(getAttributes, symbol)); +} + +/** + Gets whether T is an inner class in a nested class layout. +*/ +template isInnerClass(T) if(is(T == class)) { + static if (is(typeof(T.outer))) { + template hasOuterMember(T...) { + static if (T.length == 0) + enum hasOuterMember = false; + else + enum hasOuterMember = T[0] == "outer" || hasOuterMember!(T[1..$]); + } + + enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) && !hasOuterMember!(__traits(allMembers, T)); + } else enum isInnerClass = false; +} + +/** + Gets whether `T` or any of its children has an elaborate move. +*/ +template hasElaborateMove(T) { + static if (__traits(isStaticArray, T)) + enum bool hasElaborateMove = T.sizeof && hasElaborateMove!(BaseElemOf!T); + else static if (is(T == struct)) + enum hasElaborateMove = (is(typeof(S.init.opPostMove(lvalueOf!T))) && + !is(typeof(S.init.opPostMove(rvalueOf!T)))) || + anySatisfy!(.hasElaborateMove, Fields!T); + else + enum hasElaborateMove = false; + +} + +/** + Gets whether type `T` has elaborate assign semantics + (i.e. is `opAssign` declared for the type) +*/ +template hasElaborateAssign(T) { + static if (__traits(isStaticArray, T)) + enum bool hasElaborateAssign = T.sizeof && hasElaborateAssign!(BaseElemOf!T); + else static if (is(T == struct)) + enum hasElaborateAssign = (is(typeof(S.init.opPostMove(opAssign!T))) && + !is(typeof(S.init.opPostMove(opAssign!T)))) || + anySatisfy!(.hasElaborateAssign, Fields!T); + else + enum hasElaborateAssign = false; + +} + +/** + Gets whether type `T` has elaborate copy constructor semantics + (i.e. is a copy constructor or postblit constructor declared.) +*/ +template hasElaborateCopyConstructor(T) { + static if (__traits(isStaticArray, T)) + enum bool hasElaborateCopyConstructor = T.sizeof && hasElaborateCopyConstructor!(BaseElemOf!T); + else static if (is(T == struct)) + enum hasElaborateCopyConstructor = __traits(hasCopyConstructor, T) || __traits(hasPostblit, T); + else + enum hasElaborateCopyConstructor = false; +} + +/** + Gets whether type `T` has elaborate destructor semantics (is ~this() declared). +*/ +template hasElaborateDestructor(T) { + static if (__traits(isStaticArray, T)) + enum bool hasElaborateDestructor = T.sizeof && hasElaborateDestructor!(BaseElemOf!T); + else static if (isAggregateType!T) + enum hasElaborateDestructor = __traits(hasMember, T, "__dtor") || + anySatisfy!(.hasElaborateDestructor, Fields!T); + else + enum hasElaborateDestructor = false; +} \ No newline at end of file diff --git a/source/numem/io/endian.d b/source/numem/io/endian.d index b9c2dd2..cf7ac5b 100644 --- a/source/numem/io/endian.d +++ b/source/numem/io/endian.d @@ -8,8 +8,8 @@ module numem.io.endian; import numem.core; import numem.collections.vector; import numem.string; +import core.internal.traits; import std.traits : isNumeric, isIntegral, isBasicType; -import std.traits : Unqual; @nogc nothrow: diff --git a/source/numem/io/package.d b/source/numem/io/package.d index 8c89d90..b2f2bab 100644 --- a/source/numem/io/package.d +++ b/source/numem/io/package.d @@ -8,4 +8,5 @@ module numem.io; public import numem.io.stream; public import numem.io.endian; -public import numem.io.file; \ No newline at end of file +public import numem.io.file; +public import numem.io.stdio; \ No newline at end of file diff --git a/source/numem/io/stdio.d b/source/numem/io/stdio.d index fa3b557..1df1ed2 100644 --- a/source/numem/io/stdio.d +++ b/source/numem/io/stdio.d @@ -2,39 +2,67 @@ helpers for console output */ module numem.io.stdio; +import numem.string; +import numem.core.hooks; -version(NoC) { - // If there's no C this should be disabled. -} else { - - import numem.string; - import numem.conv; - import core.stdc.stdio : printf, puts; - - /** - Writes to standard output - */ - void write(Args...)(Args args) { - static foreach(arg; args) { - static if (is(typeof(arg) == nstring)) { - puts(arg.toCString); - } else static if (is(typeof(arg) == string)) { - printf("%.*s", cast(int)arg.length, arg.ptr); - } else { - puts(arg.toString().toCString()); - } +/** + A hook which writes to the standard output. +*/ +@weak +extern(C) +void nuWrite(nstring str) { + + import core.stdc.stdio : putchar; + foreach(i; 0..str.length) + putchar(str[i]); + putchar('\0'); +} + +/** + The newline representation of the target platform. +*/ +version(Windows) enum string NEWLINE = "\r\n"; +else enum string NEWLINE = "\r"; + +/** + A constant shared newline representation for the platform. +*/ +__gshared const(char)* endl = NEWLINE; + +/** + Writes to standard output +*/ +void write(Args...)(Args args) { + + import numem.conv : toString; + static foreach(arg; args) { + static if (is(typeof(arg) == nstring)) { + nuWrite(arg); + } else static if (is(typeof(arg) == string)) { + nuWrite(nstring(arg)); + } else { + nuWrite(arg.toString()); } } +} - /** - Writes to standard output +/** + Writes to standard output - Adds a newline at the end of the line - */ - void writeln(Args...)(Args args) { - write(args); - puts("\n"); - } + Adds a newline at the end of the line +*/ +void writeln(Args...)(Args args) { + write(args); + nuWrite(nstring(endl)); +} - // TODO: writefln + +/** + Writes to standard output with formatting. + + Adds a newline at the end of the line +*/ +void writefln(Args...)(nstring fmt, Args args) { + import numem.format : format; + writeln(format(fmt.ptr, args)); } \ No newline at end of file diff --git a/source/numem/string.d b/source/numem/string.d index e464fa9..2f1722a 100644 --- a/source/numem/string.d +++ b/source/numem/string.d @@ -5,6 +5,7 @@ Authors: Luna Nielsen */ module numem.string; +import numem.core.hooks; import numem.collections.vector; import numem.core; import std.string; @@ -83,7 +84,7 @@ struct basic_string(T) if (is(T == char) || is(T == dchar) || is(T == wchar)) { nothrow @nogc: private: alias selfType = basic_string!T; - vector!T vec_; + weak_vector!T vec_; void append_(const(T)[] span) { if (span.length == 0) return; @@ -101,7 +102,7 @@ private: void set_(const(T)[] span) { vec_.resize(span.length+1); - vec_.tryReplaceRange(span[0..span.length], 0); + vec_.tryReplaceRange(span[0..$], 0); vec_[this.size()] = '\0'; } @@ -113,7 +114,7 @@ public: /// Destructor @trusted ~this() { - if (this.vec_.data()) { + if (this.ptr) { nogc_delete(this.vec_); } } @@ -187,20 +188,6 @@ public: } } - /** - Makes a copy of a string - */ - @trusted - this(ref return scope selfType rhs) { - - // NOTE: We need to turn these into pointers because - // The D compiler otherwise thinks its supposed - // to free the operands. - selfType* self = &this; - selfType* other = &rhs; - (*self).set_((*other)[]); - } - /** Makes a copy of a string */ @@ -217,7 +204,6 @@ public: } } - /** Gets the length of the string */ @@ -473,8 +459,8 @@ public: import core.stdc.stdlib : malloc; size_t buflen = T.sizeof * this.realLength; - const(T)* str = cast(const(T)*)malloc(buflen); - memcpy(cast(void*)str, cast(void*)this.ptr, buflen); + const(T)* str = cast(const(T)*)nuAlloc(buflen); + nuMemcpy(cast(void*)str, cast(void*)this.ptr, buflen); return str; } } @@ -593,6 +579,9 @@ unittest { vector!nstring copy = strings; nogc_delete(copy); + import numem.io.stdio : writeln; + writeln(strings[0], " ", strings[1]); + assert(strings[0] == "a"); assert(strings[1] == "b");