/** This module contains compiler support for constructing dynamic arrays Copyright: Copyright Digital Mars 2000 - 2019. License: Distributed under the $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). (See accompanying file LICENSE) Source: $(DRUNTIMESRC core/internal/_array/_construction.d) */ module core.internal.array.construction; import core.internal.traits : Unqual; debug(PRINTF) { import core.stdc.stdio : printf; } /** * Does array initialization (not assignment) from another array of the same element type. * Params: * to = what array to initialize * from = what data the array should be initialized with * makeWeaklyPure = unused; its purpose is to prevent the function from becoming * strongly pure and risk being optimised out * Returns: * The created and initialized array `to` * Bugs: * This function template was ported from a much older runtime hook that bypassed safety, * purity, and throwabilty checks. To prevent breaking existing code, this function template * is temporarily declared `@trusted` until the implementation can be brought up to modern D expectations. * * The third parameter is never used, but is necessary in order for the * function be treated as weakly pure, instead of strongly pure. * This is needed because constructions such as the one below can be ignored by * the compiler if `_d_arrayctor` is believed to be pure, because purity would * mean the call to `_d_arrayctor` has no effects (no side effects and the * return value is ignored), despite it actually modifying the contents of `a`. * const S[2] b; * const S[2] a = b; // this would get lowered to _d_arrayctor(a, b) */ Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from, char* makeWeaklyPure = null) @trusted { version (DigitalMars) pragma(inline, false); import core.internal.traits : hasElaborateCopyConstructor; import core.lifetime : copyEmplace; import core.stdc.string : memcpy; import core.stdc.stdint : uintptr_t; debug(PRINTF) printf("_d_arrayctor(from = %p,%zd) size = %zd\n", from.ptr, from.length, T.sizeof); void[] vFrom = (cast(void*) from.ptr)[0..from.length]; void[] vTo = (cast(void*) to.ptr)[0..to.length]; // Force `enforceRawArraysConformable` to remain weakly `pure` void enforceRawArraysConformable(const char[] action, const size_t elementSize, const void[] a1, const void[] a2) @trusted { import core.internal.util.array : enforceRawArraysConformableNogc; alias Type = void function(const char[] action, const size_t elementSize, const void[] a1, const void[] a2, in bool allowOverlap = false) @nogc pure nothrow; (cast(Type)&enforceRawArraysConformableNogc)(action, elementSize, a1, a2, false); } enforceRawArraysConformable("initialization", T.sizeof, vFrom, vTo); static if (hasElaborateCopyConstructor!T) { size_t i; try { for (i = 0; i < to.length; i++) copyEmplace(from[i], to[i]); } catch (Exception o) { /* Destroy, in reverse order, what we've constructed so far */ while (i--) { auto elem = cast(Unqual!T*) &to[i]; destroy(*elem); } throw o; } } else { // blit all elements at once memcpy(cast(void*) to.ptr, from.ptr, to.length * T.sizeof); } return to; } // postblit @safe unittest { int counter; struct S { int val; this(this) { counter++; } } S[4] arr1; S[4] arr2 = [S(0), S(1), S(2), S(3)]; _d_arrayctor(arr1[], arr2[]); assert(counter == 4); assert(arr1 == arr2); } // copy constructor @safe unittest { int counter; struct S { int val; this(int val) { this.val = val; } this(const scope ref S rhs) { val = rhs.val; counter++; } } S[4] arr1; S[4] arr2 = [S(0), S(1), S(2), S(3)]; _d_arrayctor(arr1[], arr2[]); assert(counter == 4); assert(arr1 == arr2); } @safe nothrow unittest { // Test that throwing works int counter; bool didThrow; struct Throw { int val; this(this) { counter++; if (counter == 2) throw new Exception(""); } } try { Throw[4] a; Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)]; _d_arrayctor(a[], b[]); } catch (Exception) { didThrow = true; } assert(didThrow); assert(counter == 2); // Test that `nothrow` works didThrow = false; counter = 0; struct NoThrow { int val; this(this) { counter++; } } try { NoThrow[4] a; NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; _d_arrayctor(a[], b[]); } catch (Exception) { didThrow = false; } assert(!didThrow); assert(counter == 4); } /** * Do construction of an array. * ti[count] p = value; * Params: * p = what array to initialize * value = what data to construct the array with * Bugs: * This function template was ported from a much older runtime hook that bypassed safety, * purity, and throwabilty checks. To prevent breaking existing code, this function template * is temporarily declared `@trusted` until the implementation can be brought up to modern D expectations. */ void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted { version (DigitalMars) pragma(inline, false); import core.lifetime : copyEmplace; size_t i; try { for (i = 0; i < p.length; i++) copyEmplace(value, p[i]); } catch (Exception o) { // Destroy, in reverse order, what we've constructed so far while (i--) { auto elem = cast(Unqual!T*)&p[i]; destroy(*elem); } throw o; } } // postblit @safe unittest { int counter; struct S { int val; this(this) { counter++; } } S[4] arr; S s = S(1234); _d_arraysetctor(arr[], s); assert(counter == arr.length); assert(arr == [S(1234), S(1234), S(1234), S(1234)]); } // copy constructor @safe unittest { int counter; struct S { int val; this(int val) { this.val = val; } this(const scope ref S rhs) { val = rhs.val; counter++; } } S[4] arr; S s = S(1234); _d_arraysetctor(arr[], s); assert(counter == arr.length); assert(arr == [S(1234), S(1234), S(1234), S(1234)]); } @safe nothrow unittest { // Test that throwing works int counter; bool didThrow; struct Throw { int val; this(this) { counter++; if (counter == 2) throw new Exception("Oh no."); } } try { Throw[4] a; Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)]; _d_arrayctor(a[], b[]); } catch (Exception) { didThrow = true; } assert(didThrow); assert(counter == 2); // Test that `nothrow` works didThrow = false; counter = 0; struct NoThrow { int val; this(this) { counter++; } } try { NoThrow[4] a; NoThrow b = NoThrow(1); _d_arraysetctor(a[], b); foreach (ref e; a) assert(e == NoThrow(1)); } catch (Exception) { didThrow = false; } assert(!didThrow); assert(counter == 4); } /** * Allocate an array with the garbage collector. Also initalize elements if * their type has an initializer. Otherwise, not zero-initialize the array. * * Has three variants: * `_d_newarrayU` leaves elements uninitialized * `_d_newarrayT` initializes to 0 or based on initializer * * Params: * length = `.length` of resulting array * * Returns: * newly allocated array */ T[] _d_newarrayUPureNothrow(T)(size_t length, bool isShared=false) pure nothrow @trusted { alias PureType = T[] function(size_t length, bool isShared) pure nothrow @trusted; return (cast(PureType) &_d_newarrayU!T)(length, isShared); } T[] _d_newarrayU(T)(size_t length, bool isShared=false) @trusted { import core.exception : onOutOfMemoryError; import core.internal.traits : Unqual; import core.internal.array.utils : __arrayAlloc; alias UnqT = Unqual!T; size_t elemSize = T.sizeof; size_t arraySize; debug(PRINTF) printf("_d_newarrayU(length = x%zu, size = %zu)\n", length, elemSize); if (length == 0 || elemSize == 0) return null; version (D_InlineAsm_X86) { asm pure nothrow @nogc { mov EAX, elemSize ; mul EAX, length ; mov arraySize, EAX ; jnc Lcontinue ; } } else version (D_InlineAsm_X86_64) { asm pure nothrow @nogc { mov RAX, elemSize ; mul RAX, length ; mov arraySize, RAX ; jnc Lcontinue ; } } else { import core.checkedint : mulu; bool overflow = false; arraySize = mulu(elemSize, length, overflow); if (!overflow) goto Lcontinue; } Loverflow: onOutOfMemoryError(); assert(0); Lcontinue: auto arr = __arrayAlloc!UnqT(arraySize); if (!arr.ptr) goto Loverflow; debug(PRINTF) printf("p = %p\n", arr.ptr); return (cast(T*) arr.ptr)[0 .. length]; } /// ditto T[] _d_newarrayT(T)(size_t length, bool isShared=false) @trusted { T[] result = _d_newarrayU!T(length, isShared); static if (__traits(isZeroInit, T)) { import core.stdc.string : memset; memset(result.ptr, 0, length * T.sizeof); } else { import core.internal.lifetime : emplaceInitializer; foreach (ref elem; result) emplaceInitializer(elem); } return result; } unittest { { // zero-initialization struct S { int x, y; } S[] s = _d_newarrayT!S(10); assert(s !is null); assert(s.length == 10); foreach (ref elem; s) { assert(elem.x == 0); assert(elem.y == 0); } } { // S.init struct S { int x = 2, y = 3; } S[] s = _d_newarrayT!S(10); assert(s.length == 10); foreach (ref elem; s) { assert(elem.x == 2); assert(elem.y == 3); } } } unittest { int pblits; struct S { this(this) { pblits++; } } S[] s = _d_newarrayT!S(2); assert(s.length == 2); assert(pblits == 0); } version (D_ProfileGC) { /** * TraceGC wrapper around $(REF _d_newitemT, core,lifetime). */ T[] _d_newarrayTTrace(T)(size_t length, bool isShared, string file = __FILE__, int line = __LINE__, string funcname = __FUNCTION__) @trusted { version (D_TypeInfo) { import core.internal.array.utils : TraceHook, gcStatsPure, accumulatePure; mixin(TraceHook!(T.stringof, "_d_newarrayT")); return _d_newarrayT!T(length, isShared); } else assert(0, "Cannot create new array if compiling without support for runtime type information!"); } } /** * Create a new multi-dimensional array. Also initalize elements if their type has an initializer. * Otherwise, not zero-initialize the array. * * --- * void main() * { * S[][] s = new S[][](2, 3) * * // lowering: * S[] s = _d_newarraymTX!(S[][], S)([2, 3]); * } * --- * * Params: * dims = array length values for each dimension * isShared = whether the array should be shared * * Returns: * newly allocated array */ Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared=false) @trusted { debug(PRINTF) printf("_d_newarraymTX(dims.length = %zd)\n", dims.length); if (dims.length == 0) return null; alias UnqT = Unqual!(T); void[] __allocateInnerArray(size_t[] dims) { import core.internal.array.utils : __arrayAlloc; auto dim = dims[0]; debug(PRINTF) printf("__allocateInnerArray(UnqT = %s, dim = %lu, ndims = %lu\n", UnqT.stringof.ptr, dim, dims.length); if (dims.length == 1) { auto r = _d_newarrayT!UnqT(dim, isShared); return *cast(void[]*)&r; } auto allocSize = (void[]).sizeof * dim; // the array-of-arrays holds pointers! Don't use UnqT here! auto arr = __arrayAlloc!(void[])(allocSize); foreach (i; 0..dim) { (cast(void[]*)arr.ptr)[i] = __allocateInnerArray(dims[1..$]); } return arr.ptr[0 .. dim]; } auto result = __allocateInnerArray(dims); debug(PRINTF) printf("result = %p\n", result.ptr); return (cast(U*) result.ptr)[0 .. dims[0]]; } unittest { int[][] a = _d_newarraymTX!(int[][], int)([2, 3]); assert(a.length == 2); for (size_t i = 0; i < a.length; i++) { assert(a[i].length == 3); for (size_t j = 0; j < a[i].length; j++) assert(a[i][j] == 0); } } unittest { struct S { int x = 1; } S[][] a = _d_newarraymTX!(S[][], S)([2, 3]); assert(a.length == 2); for (size_t i = 0; i < a.length; i++) { assert(a[i].length == 3); for (size_t j = 0; j < a[i].length; j++) assert(a[i][j].x == 1); } } // Test 3-level array allocation (this uses different code paths). unittest { int[][][] a = _d_newarraymTX!(int[][][], int)([3, 4, 5]); int[5] zeros = 0; assert(a.length == 3); foreach(l2; a) { assert(l2.length == 4); foreach(l3; l2) assert(l3 == zeros[]); } } // https://issues.dlang.org/show_bug.cgi?id=24436 @system unittest { import core.memory : GC; int[][] a = _d_newarraymTX!(int[][], int)([2, 2]); assert(!(GC.getAttr(a.ptr) & GC.BlkAttr.NO_SCAN)); } version (D_ProfileGC) { /** * TraceGC wrapper around $(REF _d_newarraymT, core,internal,array,construction). */ Tarr _d_newarraymTXTrace(Tarr : U[], T, U)(size_t[] dims, bool isShared=false, string file = __FILE__, int line = __LINE__, string funcname = __FUNCTION__) @trusted { version (D_TypeInfo) { import core.internal.array.utils : TraceHook, gcStatsPure, accumulatePure; mixin(TraceHook!(T.stringof, "_d_newarraymTX")); return _d_newarraymTX!(Tarr, T)(dims, isShared); } else assert(0, "Cannot create new multi-dimensional array if compiling without support for runtime type information!"); } }