diff --git a/src/core/internal/array/appending.d b/src/core/internal/array/appending.d index 1a6dcad4c4..61f01193be 100644 --- a/src/core/internal/array/appending.d +++ b/src/core/internal/array/appending.d @@ -12,10 +12,12 @@ module core.internal.array.appending; /// See $(REF _d_arrayappendcTX, rt,lifetime,_d_arrayappendcTX) private extern (C) byte[] _d_arrayappendcTX(const TypeInfo ti, ref byte[] px, size_t n) @trusted pure nothrow; +private enum isCopyingNothrow(T) = __traits(compiles, (ref T rhs) nothrow { T lhs = rhs; }); + /// Implementation of `_d_arrayappendcTX` and `_d_arrayappendcTXTrace` template _d_arrayappendcTXImpl(Tarr : T[], T) { - import core.internal.array.utils : _d_HookTraceImpl, isPostblitNoThrow; + import core.internal.array.utils : _d_HookTraceImpl; private enum errorMessage = "Cannot append to array if compiling without support for runtime type information!"; @@ -32,7 +34,7 @@ template _d_arrayappendcTXImpl(Tarr : T[], T) * purity, and throwabilty checks. To prevent breaking existing code, this function template * is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations. */ - static if (isPostblitNoThrow!T) // `nothrow` deduction doesn't work, so this is needed + static if (isCopyingNothrow!T) // `nothrow` deduction doesn't work, so this is needed ref Tarr _d_arrayappendcTX(return scope ref Tarr px, size_t n) @trusted pure nothrow { pragma(inline, false); @@ -77,7 +79,7 @@ template _d_arrayappendcTXImpl(Tarr : T[], T) /// Implementation of `_d_arrayappendT` and `_d_arrayappendTTrace` template _d_arrayappendTImpl(Tarr : T[], T) { - import core.internal.array.utils : _d_HookTraceImpl, isPostblitNoThrow; + import core.internal.array.utils : _d_HookTraceImpl; private enum errorMessage = "Cannot append to array if compiling without support for runtime type information!"; @@ -93,7 +95,7 @@ template _d_arrayappendTImpl(Tarr : T[], T) * purity, and throwabilty checks. To prevent breaking existing code, this function template * is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations. */ - static if (isPostblitNoThrow!T) + static if (isCopyingNothrow!T) ref Tarr _d_arrayappendT(return scope ref Tarr x, scope Tarr y) @trusted pure nothrow { pragma(inline, false); @@ -110,18 +112,25 @@ template _d_arrayappendTImpl(Tarr : T[], T) private enum _d_arrayappendTBody = q{ import core.stdc.string : memcpy; - import core.internal.traits : Unqual; + import core.internal.traits : hasElaborateCopyConstructor, Unqual; + import core.lifetime : copyEmplace; auto length = x.length; - auto sizeelem = T.sizeof; _d_arrayappendcTXImpl!Tarr._d_arrayappendcTX(x, y.length); - if (y.length) - memcpy(cast(Unqual!T *)&x[length], cast(Unqual!T *)&y[0], y.length * sizeelem); + static if (hasElaborateCopyConstructor!T) + { + foreach (i; 0 .. y.length) + copyEmplace(y[i], x[length + i]); + } + else + { + // blit all elements at once + if (y.length) + memcpy(cast(Unqual!T *)&x[length], cast(Unqual!T *)&y[0], y.length * T.sizeof); + } - // do postblit - __doPostblit(cast(Unqual!Tarr)x[length .. length + y.length]); return x; }; @@ -135,30 +144,6 @@ template _d_arrayappendTImpl(Tarr : T[], T) alias _d_arrayappendTTrace = _d_HookTraceImpl!(Tarr, _d_arrayappendT, errorMessage); } -/** - * Run postblit on `t` if it is a struct and needs it. - * Or if `t` is a array, run it on the children if they have a postblit. - */ -private void __doPostblit(T)(auto ref T t) @trusted pure -{ - import core.internal.traits : hasElaborateCopyConstructor; - - static if (is(T == struct)) - { - // run the postblit function incase the struct has one - static if (__traits(hasMember, T, "__xpostblit") && - // Bugzilla 14746: Check that it's the exact member of S. - __traits(isSame, T, __traits(parent, t.__xpostblit))) - t.__xpostblit(); - } - else static if (is(T U : U[]) && hasElaborateCopyConstructor!U) - { - // only do a postblit if the `U` requires it. - foreach (ref el; t) - __doPostblit(el); - } -} - @safe unittest { double[] arr1; @@ -184,16 +169,16 @@ private void __doPostblit(T)(auto ref T t) @trusted pure arr1_org ~= arr2; _d_arrayappendTImpl!(typeof(arr1))._d_arrayappendT(arr1, arr2); - // postblit should have triggered on atleast the items in arr2 + // postblit should have triggered on at least the items in arr2 assert(blitted >= arr2.length); } -@safe unittest +@safe nothrow unittest { int blitted; struct Item { - this(this) + this(this) nothrow { blitted++; } @@ -204,18 +189,18 @@ private void __doPostblit(T)(auto ref T t) @trusted pure _d_arrayappendTImpl!(typeof(arr1))._d_arrayappendT(arr1, arr2); - // no postblit should have happend because arr{1,2} contains dynamic arrays + // no postblit should have happened because arr{1,2} contain dynamic arrays assert(blitted == 0); } -@safe unittest +@safe nothrow unittest { - int blitted; + int copied; struct Item { - this(this) + this(const scope ref Item) nothrow { - blitted++; + copied++; } } @@ -223,11 +208,11 @@ private void __doPostblit(T)(auto ref T t) @trusted pure Item[1][] arr2 = [[Item()]]; _d_arrayappendTImpl!(typeof(arr1))._d_arrayappendT(arr1, arr2); - // postblit should have happend because arr{1,2} contains static arrays - assert(blitted >= arr2.length); + // copy constructor should have been invoked because arr{1,2} contain static arrays + assert(copied >= arr2.length); } -@safe unittest +@safe nothrow unittest { string str; _d_arrayappendTImpl!(typeof(str))._d_arrayappendT(str, "a"); diff --git a/src/core/internal/array/construction.d b/src/core/internal/array/construction.d index 4b7cc75e1a..b58ed51557 100644 --- a/src/core/internal/array/construction.d +++ b/src/core/internal/array/construction.d @@ -24,8 +24,8 @@ module core.internal.array.construction; Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted { pragma(inline, false); - import core.internal.postblit : postblitRecurse; - import core.internal.traits : Unqual; + import core.internal.traits : hasElaborateCopyConstructor, Unqual; + import core.lifetime : copyEmplace; import core.stdc.string : memcpy; debug(PRINTF) import core.stdc.stdio; @@ -46,33 +46,37 @@ Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted void[] vTo = (cast(void*)to.ptr)[0..to.length]; enforceRawArraysConformable("initialization", element_size, vFrom, vTo, false); - size_t i; - try + static if (hasElaborateCopyConstructor!T) { - for (i = 0; i < to.length; i++) + size_t i; + try { - auto elem = cast(Unqual!T*)&to[i]; - // Copy construction is defined as bit copy followed by postblit. - memcpy(elem, &from[i], element_size); - postblitRecurse(*elem); + 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--) + catch (Exception o) { - auto elem = cast(Unqual!T*)&to[i]; - destroy(*elem); - } + /* Destroy, in reverse order, what we've constructed so far + */ + while (i--) + { + auto elem = cast(Unqual!T*)&to[i]; + destroy(*elem); + } - throw o; + 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; @@ -90,6 +94,29 @@ Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted 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 @@ -159,30 +186,21 @@ Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted { pragma(inline, false); - import core.internal.postblit : postblitRecurse; - import core.stdc.string : memcpy; import core.internal.traits : Unqual; - size_t walker; - auto element_size = T.sizeof; + import core.lifetime : copyEmplace; + size_t i; try { - foreach (i; 0 .. p.length) - { - auto elem = cast(Unqual!T*)&p[walker]; - // Copy construction is defined as bit copy followed by postblit. - memcpy(elem, &value, element_size); - postblitRecurse(*elem); - walker++; - } + 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 (walker > 0) + while (i--) { - walker--; - auto elem = cast(Unqual!T*)&p[walker]; + auto elem = cast(Unqual!T*)&p[i]; destroy(*elem); } @@ -190,6 +208,7 @@ void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted } } +// postblit @safe unittest { int counter; @@ -209,6 +228,28 @@ void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted 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 diff --git a/src/core/lifetime.d b/src/core/lifetime.d index f5b5c6866b..99588e9527 100644 --- a/src/core/lifetime.d +++ b/src/core/lifetime.d @@ -1217,6 +1217,234 @@ pure nothrow @safe /* @nogc */ unittest } // Bulk of emplace unittests ends here +/** + * Emplaces a copy of the specified source value into uninitialized memory, + * i.e., simulates `T target = source` copy-construction for cases where the + * target memory is already allocated and to be initialized with a copy. + * + * Params: + * source = value to be copied into target + * target = uninitialized value to be initialized with a copy of source + */ +void copyEmplace(S, T)(ref S source, ref T target) @system + if (is(immutable S == immutable T) + // this check seems to fail for nested aggregates + /* && __traits(compiles, (ref S src) { T tgt = src; }) */) +{ + import core.internal.traits : hasElaborateCopyConstructor, Unqual; + + void blit() + { + import core.stdc.string : memcpy; + memcpy(cast(void*) &target, &source, T.sizeof); + } + + static if (is(T == struct)) + { + static if (__traits(hasPostblit, T)) + { + blit(); + (cast() target).__xpostblit(); + } + else static if (__traits(hasCopyConstructor, T)) + { + emplace(cast(Unqual!(T)*) &target); // blit T.init + static if (__traits(isNested, T)) + { + // copy context pointer + cast() target.tupleof[$-1] = cast(typeof(target.tupleof[$-1])) source.tupleof[$-1]; + } + target.__ctor(source); // invoke copy ctor + } + else + { + blit(); // no opAssign + } + } + else static if (is(T == E[n], E, size_t n)) + { + static if (hasElaborateCopyConstructor!E) + { + size_t i; + try + { + for (i = 0; i < n; i++) + copyEmplace(source[i], target[i]); + } + catch (Exception e) + { + // destroy, in reverse order, what we've constructed so far + while (i--) + destroy(*cast(Unqual!(E)*) &target[i]); + throw e; + } + } + else // trivial copy + { + blit(); // all elements at once + } + } + else + { + cast() target = source; + } +} + +/// +@system pure nothrow @nogc unittest +{ + int source = 123; + int target = void; + copyEmplace(source, target); + assert(target == 123); +} + +/// +@system pure nothrow @nogc unittest +{ + immutable int[1][1] source = [ [123] ]; + immutable int[1][1] target = void; + copyEmplace(source, target); + assert(target[0][0] == 123); +} + +/// +@system pure nothrow @nogc unittest +{ + struct S + { + int x; + void opAssign(const scope ref S rhs) @safe pure nothrow @nogc + { + assert(0); + } + } + + S source = S(42); + S target = void; + copyEmplace(source, target); + assert(target.x == 42); +} + +version (CoreUnittest) +{ + private void testCopyEmplace(S, T)(const scope T* expected = null) + { + S source; + T target = void; + copyEmplace(source, target); + if (expected) + assert(target == *expected); + else + { + T expectedCopy = source; + assert(target == expectedCopy); + } + } +} + +// postblit +@system pure nothrow @nogc unittest +{ + static struct S + { + @safe pure nothrow @nogc: + int x = 42; + this(this) { x += 10; } + } + + testCopyEmplace!(S, S)(); + testCopyEmplace!(immutable S, S)(); + testCopyEmplace!(S, immutable S)(); + testCopyEmplace!(immutable S, immutable S)(); + + testCopyEmplace!(S[1], S[1])(); + testCopyEmplace!(immutable S[1], S[1])(); + + // copying to an immutable static array works, but `T expected = source` + // wrongly ignores the postblit: https://issues.dlang.org/show_bug.cgi?id=8950 + immutable S[1] expectedImmutable = [S(52)]; + testCopyEmplace!(S[1], immutable S[1])(&expectedImmutable); + testCopyEmplace!(immutable S[1], immutable S[1])(&expectedImmutable); +} + +// copy constructors +@system pure nothrow @nogc unittest +{ + static struct S + { + @safe pure nothrow @nogc: + int x = 42; + this(int x) { this.x = x; } + this(const scope ref S rhs) { x = rhs.x + 10; } + this(const scope ref S rhs) immutable { x = rhs.x + 20; } + } + + testCopyEmplace!(S, S)(); + testCopyEmplace!(immutable S, S)(); + testCopyEmplace!(S, immutable S)(); + testCopyEmplace!(immutable S, immutable S)(); + + // static arrays work, but `T expected = source` wrongly ignores copy ctors + // https://issues.dlang.org/show_bug.cgi?id=20365 + S[1] expectedMutable = [S(52)]; + immutable S[1] expectedImmutable = [immutable S(62)]; + testCopyEmplace!(S[1], S[1])(&expectedMutable); + testCopyEmplace!(immutable S[1], S[1])(&expectedMutable); + testCopyEmplace!(S[1], immutable S[1])(&expectedImmutable); + testCopyEmplace!(immutable S[1], immutable S[1])(&expectedImmutable); +} + +// copy constructor in nested struct +@system pure nothrow unittest +{ + int copies; + struct S + { + @safe pure nothrow @nogc: + size_t x = 42; + this(size_t x) { this.x = x; } + this(const scope ref S rhs) + { + assert(x == 42); // T.init + x = rhs.x; + ++copies; + } + } + + S source = S(666); + immutable S target = void; + copyEmplace(source, target); + assert(target is source); + assert(copies == 1); +} + +// destruction of partially copied static array +@system unittest +{ + static struct S + { + __gshared int[] deletions; + int x; + this(this) { if (x == 5) throw new Exception(""); } + ~this() { deletions ~= x; } + } + + alias T = immutable S[3][2]; + T source = [ [S(1), S(2), S(3)], [S(4), S(5), S(6)] ]; + T target = void; + try + { + copyEmplace(source, target); + assert(0); + } + catch (Exception) + { + assert(S.deletions == [ 4, 3, 2, 1 ] || + S.deletions == [ 4 ]); // FIXME: happens with -O + } +} + /** Forwards function arguments while keeping `out`, `ref`, and `lazy` on the parameters.