From c83dff2e1b235be072725ba545ff3fc827e70ae4 Mon Sep 17 00:00:00 2001 From: Teodor Dutu Date: Tue, 2 Aug 2022 08:11:40 +0000 Subject: [PATCH] Translate `_d_arrayassign{,_l,_r}` to templates (#14310) - Implement template `_d_arrayassign{_l,_r}` - Lower array asignment expressions to the above templates - Remove old lowering from e2ir.d - Remove the old `_d_arrayassign{,_l,_r}` hooks - Merge the usage of `_d_arrayassign` with that of `_d_arrayassign_l` Signed-off-by: Teodor Dutu --- compiler/src/dmd/canthrow.d | 4 +- compiler/src/dmd/dinterpret.d | 35 +- compiler/src/dmd/e2ir.d | 42 +-- compiler/src/dmd/expressionsem.d | 80 +++++ compiler/src/dmd/frontend.h | 2 + compiler/src/dmd/id.d | 2 + compiler/test/fail_compilation/fail10968.d | 29 +- druntime/mak/COPY | 1 + druntime/mak/SRCS | 1 + .../src/core/internal/array/arrayassign.d | 304 ++++++++++++++++++ druntime/src/object.d | 2 + druntime/src/rt/arrayassign.d | 165 ---------- 12 files changed, 440 insertions(+), 227 deletions(-) create mode 100644 druntime/src/core/internal/array/arrayassign.d diff --git a/compiler/src/dmd/canthrow.d b/compiler/src/dmd/canthrow.d index fe6e1e344b..088ca61537 100644 --- a/compiler/src/dmd/canthrow.d +++ b/compiler/src/dmd/canthrow.d @@ -114,8 +114,10 @@ extern (C++) /* CT */ BE canThrow(Expression e, FuncDeclaration func, bool mustN import dmd.id : Id; auto sd = ts.sym; + const id = ce.f.ident; if (sd.postblit && - (ce.f.ident == Id._d_arrayctor || ce.f.ident == Id._d_arraysetctor)) + (id == Id._d_arrayctor || id == Id._d_arraysetctor || + id == Id._d_arrayassign_l || id == Id._d_arrayassign_r)) { checkFuncThrows(ce, sd.postblit); return; diff --git a/compiler/src/dmd/dinterpret.d b/compiler/src/dmd/dinterpret.d index 890c3b6fef..a9fd0f56bc 100644 --- a/compiler/src/dmd/dinterpret.d +++ b/compiler/src/dmd/dinterpret.d @@ -4778,6 +4778,12 @@ public: // If `_d_HookTraceImpl` is found, resolve the underlying hook and replace `e` and `fd` with it. removeHookTraceImpl(e, fd); + bool isArrayConstructionOrAssign(FuncDeclaration fd) + { + return fd.ident == Id._d_arrayctor || fd.ident == Id._d_arraysetctor || + fd.ident == Id._d_arrayassign_l || fd.ident == Id._d_arrayassign_r; + } + if (fd.ident == Id.__ArrayPostblit || fd.ident == Id.__ArrayDtor) { assert(e.arguments.dim == 1); @@ -4831,27 +4837,36 @@ public: result = interpretRegion(ae, istate); return; } - else if (fd.ident == Id._d_arrayctor || fd.ident == Id._d_arraysetctor) + else if (isArrayConstructionOrAssign(fd)) { - // In expressionsem.d `T[x] ea = eb;` was lowered to `_d_array{,set}ctor(ea[], eb[]);`. - // The following code will rewrite it back to `ea = eb` and then interpret that expression. - if (fd.ident == Id._d_arraysetctor) - assert(e.arguments.dim == 2); - else + // In expressionsem.d, the following lowerings were performed: + // * `T[x] ea = eb;` to `_d_array{,set}ctor(ea[], eb[]);`. + // * `ea = eb` (ea and eb are arrays) to `_d_arrayassign_{l,r}(ea[], eb[])`. + // The following code will rewrite them back to `ea = eb` and + // then interpret that expression. + + if (fd.ident == Id._d_arrayctor) assert(e.arguments.dim == 3); + else + assert(e.arguments.dim == 2); Expression ea = (*e.arguments)[0]; if (ea.isCastExp) ea = ea.isCastExp.e1; Expression eb = (*e.arguments)[1]; - if (eb.isCastExp && fd.ident == Id._d_arrayctor) + if (eb.isCastExp() && fd.ident != Id._d_arraysetctor) eb = eb.isCastExp.e1; - ConstructExp ce = new ConstructExp(e.loc, ea, eb); - ce.type = ea.type; + Expression rewrittenExp; + if (fd.ident == Id._d_arrayctor || fd.ident == Id._d_arraysetctor) + rewrittenExp = new ConstructExp(e.loc, ea, eb); + else + rewrittenExp = new AssignExp(e.loc, ea, eb); + + rewrittenExp.type = ea.type; + result = interpret(rewrittenExp, istate); - result = interpret(ce, istate); return; } else if (fd.ident == Id._d_arrayappendT || fd.ident == Id._d_arrayappendTTrace) diff --git a/compiler/src/dmd/e2ir.d b/compiler/src/dmd/e2ir.d index 30290413c8..22756f1272 100644 --- a/compiler/src/dmd/e2ir.d +++ b/compiler/src/dmd/e2ir.d @@ -2312,22 +2312,7 @@ elem* toElem(Expression e, IRState *irs) else if ((postblit || destructor) && ae.op != EXP.blit && ae.op != EXP.construct) - { - /* Generate: - * _d_arrayassign(ti, efrom, eto) - */ - el_free(esize); - elem *eti = getTypeInfo(ae.e1, t1.nextOf().toBasetype(), irs); - if (irs.target.os == Target.OS.Windows && irs.target.is64bit) - { - eto = addressElem(eto, Type.tvoid.arrayOf()); - efrom = addressElem(efrom, Type.tvoid.arrayOf()); - } - elem *ep = el_params(eto, efrom, eti, null); - auto rtl = RTLSYM.ARRAYASSIGN; - elem* e = el_bin(OPcall, totym(ae.type), el_var(getRtlsym(rtl)), ep); - return setResult(e); - } + assert(0, "Trying to reference `_d_arrayassign`, this should not happen!"); else { // Generate: @@ -2636,27 +2621,10 @@ elem* toElem(Expression e, IRState *irs) } else { - e1 = sarray_toDarray(ae.e1.loc, ae.e1.type, null, e1); - e2 = sarray_toDarray(ae.e2.loc, ae.e2.type, null, e2); - - Symbol *stmp = symbol_genauto(Type_toCtype(t1b.nextOf())); - elem *etmp = el_una(OPaddr, TYnptr, el_var(stmp)); - - /* Generate: - * _d_arrayassign_l(ti, e2, e1, etmp) - * or: - * _d_arrayassign_r(ti, e2, e1, etmp) - */ - elem *eti = getTypeInfo(ae.e1, t1b.nextOf().toBasetype(), irs); - if (irs.target.os == Target.OS.Windows && irs.target.is64bit) - { - e1 = addressElem(e1, Type.tvoid.arrayOf()); - e2 = addressElem(e2, Type.tvoid.arrayOf()); - } - elem *ep = el_params(etmp, e1, e2, eti, null); - const rtl = lvalueElem ? RTLSYM.ARRAYASSIGN_L : RTLSYM.ARRAYASSIGN_R; - elem* e = el_bin(OPcall, TYdarray, el_var(getRtlsym(rtl)), ep); - return setResult2(e); + if (ae.e2.isLvalue) + assert(0, "Trying to reference `_d_arrayassign_l`, this should not happen!"); + else + assert(0, "Trying to reference `_d_arrayassign_r`, this should not happen!"); } } else diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 30baabdad5..bc37ca6337 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -9976,10 +9976,90 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor message("lowered %s =>\n %s", exp.toChars(), res.toChars()); } } + else if (auto ae = res.isAssignExp()) + res = lowerArrayAssign(ae); + else if (auto ce = res.isCommaExp()) + { + if (auto ae1 = ce.e1.isAssignExp()) + ce.e1 = lowerArrayAssign(ae1, true); + if (auto ae2 = ce.e2.isAssignExp()) + ce.e2 = lowerArrayAssign(ae2, true); + } return setResult(res); } + /*************************************** + * Lower AssignExp to `_d_arrayassign_{l,r}` if needed. + * + * Params: + * ae = the AssignExp to be lowered + * fromCommaExp = indicates whether `ae` is part of a CommaExp or not, + * so no unnecessary temporay variable is created. + * Returns: + * a CommaExp contiaining call a to `_d_arrayassign_{l,r}` if needed or + * `ae` otherwise + */ + private Expression lowerArrayAssign(AssignExp ae, bool fromCommaExp = false) + { + Type t1b = ae.e1.type.toBasetype(); + if (t1b.ty != Tsarray && t1b.ty != Tarray) + return ae; + + const isArrayAssign = + (ae.e1.isSliceExp || ae.e1.type.ty == Tsarray) && + (ae.e2.type.ty == Tsarray || ae.e2.type.ty == Tarray) && + (ae.e1.type.nextOf && ae.e2.type.nextOf && ae.e1.type.nextOf.mutableOf.equals(ae.e2.type.nextOf.mutableOf)); + + if (!isArrayAssign) + return ae; + + const ts = t1b.nextOf().baseElemOf().isTypeStruct(); + if (!ts || (!ts.sym.postblit && !ts.sym.dtor)) + return ae; + + Expression res; + auto func = ae.e2.isLvalue || ae.e2.isSliceExp ? Id._d_arrayassign_l : Id._d_arrayassign_r; + + // Lower to `.object._d_arrayassign_l{r}(e1, e2)`` + Expression id = new IdentifierExp(ae.loc, Id.empty); + id = new DotIdExp(ae.loc, id, Id.object); + id = new DotIdExp(ae.loc, id, func); + + auto arguments = new Expressions(); + arguments.push(new CastExp(ae.loc, ae.e1, ae.e1.type.nextOf.arrayOf) + .expressionSemantic(sc)); + + Expression eValue2, value2 = ae.e2; + if (ae.e2.isLvalue) + value2 = new CastExp(ae.loc, ae.e2, ae.e2.type.nextOf.arrayOf) + .expressionSemantic(sc); + else if (!fromCommaExp) + { + // Rvalues from CommaExps were introduced in `visit(AssignExp)` + // and are temporary variables themselves. Rvalues from trivial + // SliceExps are simply passed by reference without any copying. + + // `__assigntmp` will be destroyed together with the array `ae.e1`. + // When `ae.e2` is a variadic arg array, it is also `scope`, so + // `__assigntmp` may also be scope. + auto vd = copyToTemp(STC.rvalue | STC.nodtor | STC.maybescope, + "__assigntmp", ae.e2); + eValue2 = new DeclarationExp(vd.loc, vd).expressionSemantic(sc); + value2 = new VarExp(vd.loc, vd).expressionSemantic(sc); + } + arguments.push(value2); + + Expression ce = new CallExp(ae.loc, id, arguments); + res = Expression.combine(eValue2, ce).expressionSemantic(sc); + res = Expression.combine(res, ae.e1).expressionSemantic(sc); + + if (global.params.verbose) + message("lowered %s =>\n %s", ae.toChars(), res.toChars()); + + return res; + } + override void visit(PowAssignExp exp) { if (exp.type) diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index 6766e57fc5..1cd8fbec54 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -8366,6 +8366,8 @@ struct Id final static Identifier* _aaApply2; static Identifier* _d_arrayctor; static Identifier* _d_arraysetctor; + static Identifier* _d_arrayassign_l; + static Identifier* _d_arrayassign_r; static Identifier* Pinline; static Identifier* lib; static Identifier* linkerDirective; diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index 5142daa513..b121fe81de 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -318,6 +318,8 @@ immutable Msgtable[] msgtable = { "_aaApply2" }, { "_d_arrayctor" }, { "_d_arraysetctor" }, + { "_d_arrayassign_l" }, + { "_d_arrayassign_r" }, // For pragma's { "Pinline", "inline" }, diff --git a/compiler/test/fail_compilation/fail10968.d b/compiler/test/fail_compilation/fail10968.d index d9f554a4b7..e969b2493f 100644 --- a/compiler/test/fail_compilation/fail10968.d +++ b/compiler/test/fail_compilation/fail10968.d @@ -1,26 +1,27 @@ /* TEST_OUTPUT: --- -fail_compilation/fail10968.d(41): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(41): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(29): `fail10968.SA.__postblit` is declared here fail_compilation/fail10968.d(42): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` fail_compilation/fail10968.d(42): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(29): `fail10968.SA.__postblit` is declared here +fail_compilation/fail10968.d(30): `fail10968.SA.__postblit` is declared here fail_compilation/fail10968.d(43): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` fail_compilation/fail10968.d(43): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(29): `fail10968.SA.__postblit` is declared here -fail_compilation/fail10968.d(46): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(46): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(29): `fail10968.SA.__postblit` is declared here +fail_compilation/fail10968.d(30): `fail10968.SA.__postblit` is declared here +fail_compilation/fail10968.d(44): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` +fail_compilation/fail10968.d(44): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` +fail_compilation/fail10968.d(30): `fail10968.SA.__postblit` is declared here +fail_compilation/fail10968.d(44): Error: `pure` function `fail10968.bar` cannot call impure function `core.internal.array.arrayassign._d_arrayassign_l!(SA[], SA)._d_arrayassign_l` fail_compilation/fail10968.d(47): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` fail_compilation/fail10968.d(47): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(29): `fail10968.SA.__postblit` is declared here -fail_compilation/fail10968.d(47): Error: `pure` function `fail10968.bar` cannot call impure function `core.internal.array.construction._d_arraysetctor!(SA[], SA)._d_arraysetctor` +fail_compilation/fail10968.d(30): `fail10968.SA.__postblit` is declared here fail_compilation/fail10968.d(48): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` fail_compilation/fail10968.d(48): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` -fail_compilation/fail10968.d(29): `fail10968.SA.__postblit` is declared here -fail_compilation/fail10968.d(48): Error: `pure` function `fail10968.bar` cannot call impure function `core.internal.array.construction._d_arrayctor!(SA[], SA)._d_arrayctor` +fail_compilation/fail10968.d(30): `fail10968.SA.__postblit` is declared here +fail_compilation/fail10968.d(48): Error: `pure` function `fail10968.bar` cannot call impure function `core.internal.array.construction._d_arraysetctor!(SA[], SA)._d_arraysetctor` +fail_compilation/fail10968.d(49): Error: `pure` function `fail10968.bar` cannot call impure function `fail10968.SA.__postblit` +fail_compilation/fail10968.d(49): Error: `@safe` function `fail10968.bar` cannot call `@system` function `fail10968.SA.__postblit` +fail_compilation/fail10968.d(30): `fail10968.SA.__postblit` is declared here +fail_compilation/fail10968.d(49): Error: `pure` function `fail10968.bar` cannot call impure function `core.internal.array.construction._d_arrayctor!(SA[], SA)._d_arrayctor` --- */ @@ -51,12 +52,12 @@ void bar() pure @safe /* TEST_OUTPUT: --- -fail_compilation/fail10968.d(74): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit fail_compilation/fail10968.d(75): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit fail_compilation/fail10968.d(76): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit -fail_compilation/fail10968.d(79): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit +fail_compilation/fail10968.d(77): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit fail_compilation/fail10968.d(80): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit fail_compilation/fail10968.d(81): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit +fail_compilation/fail10968.d(82): Error: struct `fail10968.SD` is not copyable because it has a disabled postblit --- */ diff --git a/druntime/mak/COPY b/druntime/mak/COPY index 6499ddfd0e..5fa84398d0 100644 --- a/druntime/mak/COPY +++ b/druntime/mak/COPY @@ -46,6 +46,7 @@ COPY=\ $(IMPDIR)\core\internal\lifetime.d \ \ $(IMPDIR)\core\internal\array\appending.d \ + $(IMPDIR)\core\internal\array\arrayassign.d \ $(IMPDIR)\core\internal\array\comparison.d \ $(IMPDIR)\core\internal\array\construction.d \ $(IMPDIR)\core\internal\array\equality.d \ diff --git a/druntime/mak/SRCS b/druntime/mak/SRCS index 908b6abdcd..27c9fa45a7 100644 --- a/druntime/mak/SRCS +++ b/druntime/mak/SRCS @@ -43,6 +43,7 @@ SRCS=\ src\core\internal\lifetime.d \ \ src\core\internal\array\appending.d \ + src\core\internal\array\arrayassign.d \ src\core\internal\array\comparison.d \ src\core\internal\array\construction.d \ src\core\internal\array\equality.d \ diff --git a/druntime/src/core/internal/array/arrayassign.d b/druntime/src/core/internal/array/arrayassign.d new file mode 100644 index 0000000000..6132e68db1 --- /dev/null +++ b/druntime/src/core/internal/array/arrayassign.d @@ -0,0 +1,304 @@ +module core.internal.array.arrayassign; + +// Force `enforceRawArraysConformable` to remain `pure` `@nogc` +private void enforceRawArraysConformable(const char[] action, const size_t elementSize, + const void[] a1, const void[] a2, const bool allowOverlap) @trusted @nogc pure nothrow +{ + import core.internal.util.array : enforceRawArraysConformable; + + 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)&enforceRawArraysConformable)(action, elementSize, a1, a2, allowOverlap); +} + +private template CopyElem(string CopyAction) +{ + const char[] CopyElem = "{\n" ~ q{ + memcpy(&tmp, cast(void*) &dst, elemSize); + } ~ CopyAction ~ q{ + auto elem = cast(Unqual!T*) &tmp; + destroy(*elem); + } ~ "}\n"; +} + +private template CopyArray(bool CanOverlap, string CopyAction) +{ + const char[] CopyArray = CanOverlap ? q{ + if (vFrom.ptr < vTo.ptr && vTo.ptr < vFrom.ptr + elemSize * vFrom.length) + foreach_reverse (i, ref dst; to) + } ~ CopyElem!(CopyAction) ~ q{ + else + foreach (i, ref dst; to) + } ~ CopyElem!(CopyAction) + : q{ + foreach (i, ref dst; to) + } ~ CopyElem!(CopyAction); +} + +private template ArrayAssign(string CopyLogic, string AllowOverLap) +{ + const char[] ArrayAssign = q{ + import core.internal.traits : hasElaborateCopyConstructor, Unqual; + import core.lifetime : copyEmplace; + import core.stdc.string : memcpy; + + void[] vFrom = (cast(void*) from.ptr)[0 .. from.length]; + void[] vTo = (cast(void*) to.ptr)[0 .. to.length]; + enum elemSize = T.sizeof; + + enforceRawArraysConformable("copy", elemSize, vFrom, vTo, } ~ AllowOverLap ~ q{); + + void[elemSize] tmp = void; + + } ~ CopyLogic ~ q{ + + return to; + }; +} + +/** + * Does array assignment (not construction) from another array of the same + * element type. Handles overlapping copies. Assumes the right hand side is an + * lvalue, + * + * Used for static array assignment with non-POD element types: + * --- + * struct S + * { + * ~this() {} // destructor, so not Plain Old Data + * } + * + * void main() + * { + * S[3] arr; + * S[3] lvalue; + * + * arr = lvalue; + * // Generates: + * // _d_arrayassign_l(arr[], lvalue[]), arr; + * } + * --- + * + * Params: + * to = destination array + * from = source array + * Returns: + * `to` + */ +Tarr _d_arrayassign_l(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + mixin(ArrayAssign!(q{ + static if (hasElaborateCopyConstructor!T) + } ~ CopyArray!(true, "copyEmplace(from[i], dst);") ~ q{ + else + } ~ CopyArray!(true, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), + "true")); +} + +@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_arrayassign_l(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_arrayassign_l(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_arrayassign_l(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_arrayassign_l(a[], b[]); + } + catch (Exception) + { + didThrow = false; + } + assert(!didThrow); + assert(counter == 4); +} + +/** + * Does array assignment (not construction) from another array of the same + * element type. Does not support overlapping copies. Assumes the right hand + * side is an rvalue, + * + * Used for static array assignment with non-POD element types: + * --- + * struct S + * { + * ~this() {} // destructor, so not Plain Old Data + * } + * + * void main() + * { + * S[3] arr; + * S[3] getRvalue() {return lvalue;} + * + * arr = getRvalue(); + * // Generates: + * // (__appendtmp = getRvalue), _d_arrayassign_l(arr[], __appendtmp), arr; + * } + * --- + * + * Params: + * to = destination array + * from = source array + * Returns: + * `to` + */ +Tarr _d_arrayassign_r(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + mixin(ArrayAssign!( + CopyArray!(false, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), + "false")); +} + +@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_arrayassign_r(arr1[], arr2[]); + + assert(counter == 0); + 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_arrayassign_r(arr1[], arr2[]); + + assert(counter == 0); + assert(arr1 == arr2); +} + +@safe nothrow unittest +{ + // Test that `nothrow` works + bool didThrow = false; + int 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_arrayassign_r(a[], b[]); + } + catch (Exception) + { + didThrow = false; + } + assert(!didThrow); + assert(counter == 0); +} diff --git a/druntime/src/object.d b/druntime/src/object.d index 7ffbe396a7..78a83fb218 100644 --- a/druntime/src/object.d +++ b/druntime/src/object.d @@ -4603,6 +4603,8 @@ public import core.internal.array.casting: __ArrayCast; public import core.internal.array.concatenation : _d_arraycatnTXImpl; public import core.internal.array.construction : _d_arrayctor; public import core.internal.array.construction : _d_arraysetctor; +public import core.internal.array.arrayassign : _d_arrayassign_l; +public import core.internal.array.arrayassign : _d_arrayassign_r; public import core.internal.array.capacity: _d_arraysetlengthTImpl; public import core.internal.dassert: _d_assert_fail; diff --git a/druntime/src/rt/arrayassign.d b/druntime/src/rt/arrayassign.d index c9db2fc2c8..c9e2b15025 100644 --- a/druntime/src/rt/arrayassign.d +++ b/druntime/src/rt/arrayassign.d @@ -19,171 +19,6 @@ private debug(PRINTF) import core.stdc.stdio; } -/* - * Superseded array assignment hook. Does not take into account destructors: - * https://issues.dlang.org/show_bug.cgi?id=13661 - * Kept for backward binary compatibility. This function can be removed in the future. - */ -extern (C) void[] _d_arrayassign(TypeInfo ti, void[] from, void[] to) -{ - debug(PRINTF) printf("_d_arrayassign(from = %p,%d, to = %p,%d) size = %d\n", from.ptr, from.length, to.ptr, to.length, ti.tsize); - - immutable elementSize = ti.tsize; - - // Need a temporary buffer tmp[] big enough to hold one element - void[16] buf = void; - void* ptmp = (elementSize > buf.sizeof) ? malloc(elementSize) : buf.ptr; - scope (exit) - { - if (ptmp != buf.ptr) - free(ptmp); - } - return _d_arrayassign_l(ti, from, to, ptmp); -} - -/** -Does array assignment (not construction) from another array of the same -element type. - -Handles overlapping copies. - -The `_d_arrayassign_l` variant assumes the right hand side is an lvalue, -while `_d_arrayassign_r` assumes it's an rvalue, which means it doesn't have to call copy constructors. - -Used for static array assignment with non-POD element types: ---- -struct S -{ - ~this() {} // destructor, so not Plain Old Data -} - -void main() -{ - S[3] arr; - S[3] lvalue; - - arr = lvalue; - // Generates: - // S _tmp; - // _d_arrayassign_l(typeid(S), (cast(void*) lvalue.ptr)[0..lvalue.length], (cast(void*) arr.ptr)[0..arr.length], &_tmp); - - S[3] getRvalue() {return lvalue;} - arr = getRvalue(); - // Similar, but `_d_arrayassign_r` -} ---- - -Params: - ti = `TypeInfo` of the array element type. - dst = target memory. Its `.length` is equal to the element count, not byte length. - src = source memory. Its `.length` is equal to the element count, not byte length. - ptmp = Temporary memory for element swapping, must have capacity of `ti.tsize` bytes. -Returns: `dst` -*/ -extern (C) void[] _d_arrayassign_l(TypeInfo ti, void[] src, void[] dst, void* ptmp) -{ - debug(PRINTF) printf("_d_arrayassign_l(src = %p,%d, dst = %p,%d) size = %d\n", src.ptr, src.length, dst.ptr, dst.length, ti.tsize); - - immutable elementSize = ti.tsize; - - enforceRawArraysConformable("copy", elementSize, src, dst, true); - - if (src.ptr < dst.ptr && dst.ptr < src.ptr + elementSize * src.length) - { - // If dst is in the middle of src memory, use reverse order. - for (auto i = dst.length; i--; ) - { - void* pdst = dst.ptr + i * elementSize; - void* psrc = src.ptr + i * elementSize; - memcpy(ptmp, pdst, elementSize); - memcpy(pdst, psrc, elementSize); - ti.postblit(pdst); - ti.destroy(ptmp); - } - } - else - { - // Otherwise, use normal order. - foreach (i; 0 .. dst.length) - { - void* pdst = dst.ptr + i * elementSize; - void* psrc = src.ptr + i * elementSize; - memcpy(ptmp, pdst, elementSize); - memcpy(pdst, psrc, elementSize); - ti.postblit(pdst); - ti.destroy(ptmp); - } - } - return dst; -} - -unittest // Bugzilla 14024 -{ - string op; - - struct S - { - char x = 'x'; - this(this) { op ~= x-0x20; } // upper case - ~this() { op ~= x; } // lower case - } - - S[4] mem; - ref S[2] slice(int a, int b) { return mem[a .. b][0 .. 2]; } - - op = null; - mem[0].x = 'a'; - mem[1].x = 'b'; - mem[2].x = 'x'; - mem[3].x = 'y'; - slice(0, 2) = slice(2, 4); // [ab] = [xy] - assert(op == "XaYb", op); - - op = null; - mem[0].x = 'x'; - mem[1].x = 'y'; - mem[2].x = 'a'; - mem[3].x = 'b'; - slice(2, 4) = slice(0, 2); // [ab] = [xy] - assert(op == "XaYb", op); - - op = null; - mem[0].x = 'a'; - mem[1].x = 'b'; - mem[2].x = 'c'; - slice(0, 2) = slice(1, 3); // [ab] = [bc] - assert(op == "BaCb", op); - - op = null; - mem[0].x = 'x'; - mem[1].x = 'y'; - mem[2].x = 'z'; - slice(1, 3) = slice(0, 2); // [yz] = [xy] - assert(op == "YzXy", op); -} - -/// ditto -extern (C) void[] _d_arrayassign_r(TypeInfo ti, void[] src, void[] dst, void* ptmp) -{ - debug(PRINTF) printf("_d_arrayassign_r(src = %p,%d, dst = %p,%d) size = %d\n", src.ptr, src.length, dst.ptr, dst.length, ti.tsize); - - immutable elementSize = ti.tsize; - - enforceRawArraysConformable("copy", elementSize, src, dst, false); - - // Always use normal order, because we can assume that - // the rvalue src has no overlapping with dst. - foreach (i; 0 .. dst.length) - { - void* pdst = dst.ptr + i * elementSize; - void* psrc = src.ptr + i * elementSize; - memcpy(ptmp, pdst, elementSize); - memcpy(pdst, psrc, elementSize); - ti.destroy(ptmp); - } - return dst; -} - /** Set all elements of an array to a single value.