blob: 40e5a61bbded04564309ae6fe171ac9701c47710 [file] [log] [blame]
/**
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!");
}
}