diff --git a/compiler/src/dmd/aggregate.h b/compiler/src/dmd/aggregate.h index c972f0a6631..010da524b6e 100644 --- a/compiler/src/dmd/aggregate.h +++ b/compiler/src/dmd/aggregate.h @@ -177,6 +177,8 @@ class StructDeclaration : public AggregateDeclaration bool zeroInit(bool v); bool hasIdentityAssign() const; // true if has identity opAssign bool hasIdentityAssign(bool v); + bool hasMoveAssign() const; // true if has identity opAssign + bool hasMoveAssign(bool v); bool hasBlitAssign() const; // true if opAssign is a blit bool hasBlitAssign(bool v); bool hasIdentityEquals() const; // true if has identity opEquals @@ -185,6 +187,8 @@ class StructDeclaration : public AggregateDeclaration bool hasNoFields(bool v); bool hasCopyCtor() const; // copy constructor bool hasCopyCtor(bool v); + bool hasMoveCtor() const; // copy constructor + bool hasMoveCtor(bool v); // Even if struct is defined as non-root symbol, some built-in operations // (e.g. TypeidExp, NewExp, ArrayLiteralExp, etc) request its TypeInfo. // For those, today TypeInfo_Struct is generated in COMDAT. diff --git a/compiler/src/dmd/backend/debugprint.d b/compiler/src/dmd/backend/debugprint.d index 6ed1943c13a..a5848d48ed3 100644 --- a/compiler/src/dmd/backend/debugprint.d +++ b/compiler/src/dmd/backend/debugprint.d @@ -509,7 +509,7 @@ void numberBlocks(block *startblock) @trusted void WRfunc(const char* msg, Symbol* sfunc, block* startblock) { - printf("............%s...%s().............\n", msg, sfunc.Sident.ptr); + printf("............%s...%s()\n", msg, sfunc.Sident.ptr); numberBlocks(startblock); for (block *b = startblock; b; b = b.Bnext) WRblock(b); diff --git a/compiler/src/dmd/backend/dout.d b/compiler/src/dmd/backend/dout.d index 5f6f8c2aacb..5cf3a04d447 100644 --- a/compiler/src/dmd/backend/dout.d +++ b/compiler/src/dmd/backend/dout.d @@ -862,7 +862,7 @@ private void writefunc2(Symbol *sfunc, ref GlobalOptimizer go) { func_t *f = sfunc.Sfunc; - //printf("writefunc(%s)\n",sfunc.Sident.ptr); + debugb && printf("===================== writefunc %s =================\n",sfunc.Sident.ptr); //symbol_print(sfunc); debug debugy && printf("writefunc(%s)\n",sfunc.Sident.ptr); diff --git a/compiler/src/dmd/clone.d b/compiler/src/dmd/clone.d index d67167e716c..af05dd3eedd 100644 --- a/compiler/src/dmd/clone.d +++ b/compiler/src/dmd/clone.d @@ -1533,6 +1533,9 @@ FuncDeclaration buildPostBlit(StructDeclaration sd, Scope* sc) return xpostblit; } +/* ===================================== Copy Constructor ========================== */ +static if (1) { + /** * Generates a copy constructor declaration with the specified storage * class for the parameter and the function. @@ -1736,3 +1739,209 @@ bool buildCopyCtor(StructDeclaration sd, Scope* sc) } return true; } + +} + +/* ===================================== Move Constructor ========================== */ +static if (1) { + +/** + * Generates a move constructor declaration with the specified storage + * class for the parameter and the function. + * + * Params: + * sd = the `struct` that contains the move constructor + * paramStc = the storage class of the move constructor parameter + * funcStc = the storage class for the move constructor declaration + * + * Returns: + * The move constructor declaration for struct `sd`. + */ +private CtorDeclaration generateMoveCtorDeclaration(StructDeclaration sd, const StorageClass paramStc, const StorageClass funcStc) +{ + /* Although the move constructor is declared as `this(S s) { ... }`, + * it is implemented as `this(ref S s) { ... }` + */ + return generateCopyCtorDeclaration(sd, paramStc, funcStc); +} + +/** + * Generates a trivial move constructor body that simply does memberwise + * initialization: + * + * this.field1 = rhs.field1; + * this.field2 = rhs.field2; + * ... + * + * Params: + * sd = the `struct` declaration that contains the copy constructor + * + * Returns: + * A `CompoundStatement` containing the body of the copy constructor. + */ +private Statement generateMoveCtorBody(StructDeclaration sd) +{ + Loc loc; + Expression e; + foreach (v; sd.fields) + { + auto ec = new AssignExp(loc, + new DotVarExp(loc, new ThisExp(loc), v), + new DotVarExp(loc, new IdentifierExp(loc, Id.p), v)); + e = Expression.combine(e, ec); + //printf("e.toChars = %s\n", e.toChars()); + } + Statement s1 = new ExpStatement(loc, e); + return new CompoundStatement(loc, s1); +} + +/** + * Determine if a move constructor is needed for struct sd, + * if the following conditions are met: + * + * 1. sd does not define a move constructor + * 2. at least one field of sd defines a move constructor + * + * Params: + * sd = the `struct` for which the move constructor is generated + * hasMoveCtor = set to true if a move constructor is already present + * + * Returns: + * `true` if one needs to be generated + * `false` otherwise + */ +bool needMoveCtor(StructDeclaration sd, out bool hasMoveCtor) +{ + if (global.errors) + return false; + + auto ctor = sd.search(sd.loc, Id.ctor); + if (ctor) + { + if (ctor.isOverloadSet()) + return false; + if (auto td = ctor.isTemplateDeclaration()) + ctor = td.funcroot; + } + + CtorDeclaration moveCtor; + CtorDeclaration rvalueCtor; + + if (!ctor) + goto LcheckFields; + + overloadApply(ctor, (Dsymbol s) + { + if (s.isTemplateDeclaration()) + return 0; + auto ctorDecl = s.isCtorDeclaration(); + assert(ctorDecl); + if (ctorDecl.isMoveCtor) + { + if (!moveCtor) + moveCtor = ctorDecl; + return 0; + } + + if (isRvalueConstructor(sd, ctorDecl)) + rvalueCtor = ctorDecl; + return 0; + }); + + if (moveCtor) + { + if (rvalueCtor) + { + .error(sd.loc, "`struct %s` may not define both a rvalue constructor and a move constructor", sd.toChars()); + errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here"); + errorSupplemental(moveCtor.loc, "move constructor defined here"); + } + hasMoveCtor = true; + return false; + } + +LcheckFields: + VarDeclaration fieldWithMoveCtor; + // see if any struct members define a copy constructor + foreach (v; sd.fields) + { + if (v.storage_class & STC.ref_) + continue; + if (v.overlapped) + continue; + + auto ts = v.type.baseElemOf().isTypeStruct(); + if (!ts) + continue; + if (ts.sym.hasMoveCtor) + { + fieldWithMoveCtor = v; + break; + } + } + + if (fieldWithMoveCtor && rvalueCtor) + { + .error(sd.loc, "`struct %s` may not define a rvalue constructor and have fields with move constructors", sd.toChars()); + errorSupplemental(rvalueCtor.loc,"rvalue constructor defined here"); + errorSupplemental(fieldWithMoveCtor.loc, "field with move constructor defined here"); + return false; + } + else if (!fieldWithMoveCtor) + return false; + return true; +} + +/** + * Generates a move constructor if needMoveCtor() returns true. + * The generated move constructor will be of the form: + * this(ref return scope inout(S) rhs) inout + * { + * this.field1 = rhs.field1; + * this.field2 = rhs.field2; + * ... + * } + * + * Params: + * sd = the `struct` for which the copy constructor is generated + * sc = the scope where the copy constructor is generated + * + * Returns: + * `true` if `struct` sd defines a copy constructor (explicitly or generated), + * `false` otherwise. + * References: + * https://dlang.org/spec/struct.html#struct-copy-constructor + */ +bool buildMoveCtor(StructDeclaration sd, Scope* sc) +{ + //printf("buildMoveCtor() %s\n", sd.toChars()); + bool hasMoveCtor; + if (!needMoveCtor(sd, hasMoveCtor)) + return hasMoveCtor; + + //printf("generating move constructor for %s\n", sd.toChars()); + const MOD paramMod = MODFlags.wild; + const MOD funcMod = MODFlags.wild; + auto ccd = generateMoveCtorDeclaration(sd, ModToStc(paramMod), ModToStc(funcMod)); + auto moveCtorBody = generateMoveCtorBody(sd); + ccd.fbody = moveCtorBody; + sd.members.push(ccd); + ccd.addMember(sc, sd); + const errors = global.startGagging(); + Scope* sc2 = sc.push(); + sc2.stc = 0; + sc2.linkage = LINK.d; + ccd.dsymbolSemantic(sc2); + ccd.semantic2(sc2); + ccd.semantic3(sc2); + //printf("ccd semantic: %s\n", ccd.type.toChars()); + sc2.pop(); + if (global.endGagging(errors) || sd.isUnionDeclaration()) + { + ccd.storage_class |= STC.disable; + ccd.fbody = null; + } + return true; +} + +} diff --git a/compiler/src/dmd/dstruct.d b/compiler/src/dmd/dstruct.d index 8b4ac7d8846..1c9d01d7efc 100644 --- a/compiler/src/dmd/dstruct.d +++ b/compiler/src/dmd/dstruct.d @@ -218,9 +218,11 @@ extern (C++) class StructDeclaration : AggregateDeclaration bool zeroInit; // !=0 if initialize with 0 fill bool hasIdentityAssign; // true if has identity opAssign bool hasBlitAssign; // true if opAssign is a blit + bool hasMoveAssign; // true if move assignment bool hasIdentityEquals; // true if has identity opEquals bool hasNoFields; // has no fields bool hasCopyCtor; // copy constructor + bool hasMoveCtor; // move constructor bool hasPointerField; // members with indirections bool hasVoidInitPointers; // void-initialized unsafe fields bool hasUnsafeBitpatterns; // @system members, pointers, bool @@ -417,7 +419,7 @@ extern (C++) class StructDeclaration : AggregateDeclaration * POD is defined as: * $(OL * $(LI not nested) - * $(LI no postblits, destructors, or assignment operators) + * $(LI no postblits, copy constructors, move constructors, destructors, or assignment operators) * $(LI no `ref` fields or fields that are themselves non-POD) * ) * The idea being these are compatible with C structs. @@ -436,10 +438,14 @@ extern (C++) class StructDeclaration : AggregateDeclaration bool hasCpCtorLocal; needCopyCtor(this, hasCpCtorLocal); + bool hasMoveCtorLocal; + needMoveCtor(this, hasMoveCtorLocal); + if (enclosing || // is nested search(this, loc, Id.postblit) || // has postblit search(this, loc, Id.dtor) || // has destructor - hasCpCtorLocal) // has copy constructor + hasCpCtorLocal || // has copy constructor + hasMoveCtorLocal) // has move constructor { ispod = ThreeState.no; return false; diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 2ccecab6019..a81d0880ad3 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -281,7 +281,7 @@ bool isRvalueConstructor(StructDeclaration sd, CtorDeclaration ctor) { auto tf = ctor.type.isTypeFunction(); const dim = tf.parameterList.length; - if (dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg)) + if (dim > 1 && tf.parameterList[1].defaultArg) { auto param = tf.parameterList[0]; if (!(param.storageClass & STC.ref_) && param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) @@ -554,6 +554,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor override void visit(VarDeclaration dsym) { + //printf("VarDeclaration %s\n", dsym.toChars()); version (none) { printf("VarDeclaration::semantic('%s', parent = '%s') sem = %d\n", @@ -2480,14 +2481,22 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor .error(ctd.loc, "%s `%s` all parameters have default arguments, "~ "but structs cannot have default constructors.", ctd.kind, ctd.toPrettyChars); } - else if ((dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg))) + else if (dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg)) { //printf("tf: %s\n", tf.toChars()); auto param = tf.parameterList[0]; - if (param.storageClass & STC.ref_ && param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) + if (param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf()) { - //printf("copy constructor\n"); - ctd.isCpCtor = true; + if (param.storageClass & STC.ref_) + { + //printf("found copy constructor\n"); + ctd.isCpCtor = true; + } + else + { + //printf("found move constructor\n"); + ctd.isMoveCtor = true; + } } } } @@ -2979,6 +2988,7 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor buildDtors(sd, sc2); sd.hasCopyCtor = buildCopyCtor(sd, sc2); + sd.hasMoveCtor = buildMoveCtor(sd, sc2); sd.postblit = buildPostBlit(sd, sc2); buildOpAssign(sd, sc2); diff --git a/compiler/src/dmd/e2ir.d b/compiler/src/dmd/e2ir.d index 187f0b180be..29a822f550a 100644 --- a/compiler/src/dmd/e2ir.d +++ b/compiler/src/dmd/e2ir.d @@ -5509,6 +5509,12 @@ elem *callfunc(const ref Loc loc, bool copy = !(v && v.isArgDtorVar); // copy unless the destructor is going to be run on it // then assume the frontend took care of the copying and pass it by ref + if (ea.Eoper == OPind && ea.E1.Eoper == OPcall && arg.type.isTypeStruct()) + { + if (auto ctor = fd.isCtorDeclaration()) + copy = !ctor.isMoveCtor; + } + elems[i] = addressElem(ea, arg.type, copy); continue; } diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 2998685a3e7..c511a7c1627 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -819,6 +819,7 @@ extern(D) bool arrayExpressionSemantic( */ extern (D) Expression doCopyOrMove(Scope *sc, Expression e, Type t = null) { + //printf("doCopyOrMove() %s\n", toChars(e)); if (auto ce = e.isCondExp()) { ce.e1 = doCopyOrMove(sc, ce.e1); @@ -842,6 +843,7 @@ extern (D) Expression doCopyOrMove(Scope *sc, Expression e, Type t = null) */ private Expression callCpCtor(Scope* sc, Expression e, Type destinationType) { + //printf("callCpCtor()\n"); auto ts = e.type.baseElemOf().isTypeStruct(); if (!ts) @@ -884,6 +886,7 @@ private Expression callCpCtor(Scope* sc, Expression e, Type destinationType) */ Expression valueNoDtor(Expression e) { + //printf("valueNoDtor()\n"); auto ex = lastComma(e); if (auto ce = ex.isCallExp()) @@ -10888,6 +10891,21 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return; } } + else if (sd.hasMoveCtor) + { + /* Rewrite as: + * e1.moveCtor(e2); + */ + Expression e; + e = new DotIdExp(exp.loc, e1x, Id.ctor); + e = new CallExp(exp.loc, e, e2x); + + //printf("e: %s\n", e.toChars()); + + result = e.expressionSemantic(sc); + printf("result: %s\n", e.toChars()); + return; + } else { /* The struct value returned from the function is transferred diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index 268b4617fa2..9ecd79f6c5f 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -3814,6 +3814,7 @@ class CtorDeclaration final : public FuncDeclaration { public: bool isCpCtor; + bool isMoveCtor; CtorDeclaration* syntaxCopy(Dsymbol* s) override; const char* kind() const override; const char* toChars() const override; @@ -7293,12 +7294,16 @@ class StructDeclaration : public AggregateDeclaration bool hasIdentityAssign(bool v); bool hasBlitAssign() const; bool hasBlitAssign(bool v); + bool hasMoveAssign() const; + bool hasMoveAssign(bool v); bool hasIdentityEquals() const; bool hasIdentityEquals(bool v); bool hasNoFields() const; bool hasNoFields(bool v); bool hasCopyCtor() const; bool hasCopyCtor(bool v); + bool hasMoveCtor() const; + bool hasMoveCtor(bool v); bool hasPointerField() const; bool hasPointerField(bool v); bool hasVoidInitPointers() const; diff --git a/compiler/src/dmd/func.d b/compiler/src/dmd/func.d index 9d59fc99104..6868323c71e 100644 --- a/compiler/src/dmd/func.d +++ b/compiler/src/dmd/func.d @@ -1546,7 +1546,9 @@ extern (C++) final class FuncLiteralDeclaration : FuncDeclaration */ extern (C++) final class CtorDeclaration : FuncDeclaration { - bool isCpCtor; + bool isCpCtor; // it's a copy constructor + bool isMoveCtor; // it's a move constructor + extern (D) this(const ref Loc loc, const ref Loc endloc, StorageClass stc, Type type, bool isCpCtor = false) { super(loc, endloc, Id.ctor, stc, type); diff --git a/compiler/src/dmd/parse.d b/compiler/src/dmd/parse.d index bb2411825fa..33eab2ed709 100644 --- a/compiler/src/dmd/parse.d +++ b/compiler/src/dmd/parse.d @@ -2493,7 +2493,7 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer AST.Expression constraint = tpl ? parseConstraint() : null; - AST.Type tf = new AST.TypeFunction(parameterList, null, linkage, stc); // RetrunType -> auto + AST.Type tf = new AST.TypeFunction(parameterList, null, linkage, stc); // ReturnType -> auto tf = tf.addSTC(stc); auto f = new AST.CtorDeclaration(loc, Loc.initial, stc, tf); diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index 99108ba4e23..deed9b0c9e3 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -882,6 +882,7 @@ extern (D) MATCH callMatch(TypeFunction tf, Type tthis, ArgumentList argumentLis private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, Expression arg, Type tprm, Scope* sc, const(char)** pMessage) { + //printf("isCopyConstructorCallable() %s, %s, %s,\n", argStruct.toChars(), toChars(arg), toChars(tprm)); auto tmp = new VarDeclaration(arg.loc, tprm, Identifier.generateId("__copytmp"), null); tmp.storage_class = STC.rvalue | STC.temp | STC.ctfe; tmp.dsymbolSemantic(sc); @@ -972,7 +973,7 @@ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, * p = The parameter of `tf` being matched * arg = Argument being passed (bound) to `p` * wildmatch = Wild (`inout`) matching level, derived from the full argument list - * flag = A non-zero value means we're doing a partial ordering check + * flag = A non-zero value means we are doing a partial ordering check * (no value semantic check) * sc = Scope we are in * pMessage = A buffer to write the error in, or `null` @@ -982,11 +983,15 @@ private extern(D) bool isCopyConstructorCallable (StructDeclaration argStruct, private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, Expression arg, ubyte wildmatch, int flag, Scope* sc, const(char)** pMessage) { - //printf("arg: %s, type: %s\n", arg.toChars(), arg.type.toChars()); + //printf("argumentMatchParameter() flag: %d arg: %s, type: %s\n", flag, arg.toChars(), (arg.type ? arg.type.toChars() : "null".ptr)); MATCH m; Type targ = arg.type; Type tprm = wildmatch ? p.type.substWildTo(wildmatch) : p.type; + bool hasMoveCtor; + if (auto ts = targ.isTypeStruct()) + hasMoveCtor = ts.sym.hasMoveCtor; + if (p.isLazy() && tprm.ty == Tvoid && targ.ty != Tvoid) m = MATCH.convert; else if (flag) @@ -1010,9 +1015,10 @@ private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, // check if the copy constructor may be called to copy the argument if (argStruct && argStruct == prmStruct && argStruct.hasCopyCtor) { - if (!isCopyConstructorCallable(argStruct, arg, tprm, sc, pMessage)) + if (hasMoveCtor || isCopyConstructorCallable(argStruct, arg, tprm, sc, pMessage)) + m = MATCH.exact; + else return MATCH.nomatch; - m = MATCH.exact; } else { @@ -1071,20 +1077,21 @@ private extern(D) MATCH argumentMatchParameter (TypeFunction tf, Parameter p, // Need to make this a rvalue through a temporary m = MATCH.convert; } - else if (global.params.rvalueRefParam != FeatureState.enabled || - p.storageClass & STC.out_ || - !arg.type.isCopyable()) // can't copy to temp for ref parameter - { - if (pMessage) *pMessage = tf.getParamError(arg, p); - return MATCH.nomatch; - } - else + else if (global.params.rvalueRefParam == FeatureState.enabled && + !hasMoveCtor && + arg.type.isCopyable()) { /* in functionParameters() we'll convert this * rvalue into a temporary */ m = MATCH.convert; } + else + { + // can't copy to temp for ref parameter + if (pMessage) *pMessage = tf.getParamError(arg, p); + return MATCH.nomatch; + } } /* If the match is not already perfect or if the arg