Bug 19904 – move semantics fail through the `emplace` pipeline

Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P1
Component
druntime
Product
D
Version
D2
Platform
All
OS
All
Creation time
2019-05-26T17:54:58Z
Last change time
2021-08-04T20:53:22Z
Assigned to
No Owner
Creator
Manu

Comments

Comment #0 by turkeyman — 2019-05-26T17:54:58Z
Consider the emplace function: T* emplace(T, Args...)(T* chunk, auto ref Args args) if (is(T == struct) || Args.length == 1) { import core.internal.lifetime : emplaceRef; emplaceRef!T(*chunk, args); return chunk; } Note there is no `forward!args` then it is passed to `emplaceRef`. This causes the arguments to be unnecessarily copied, potentially executing a bunch of copy code. Now looking into `emplaceRef` (which I won't paste here because it's a monster!), you'll see many calls to: p.__ctor(args); Note a similar lack of `forward!args` at all the constructor calls, which leads to a second layer of unnecessary copying. TL;DR, move semantics in D are woefully broken!
Comment #1 by kinke — 2019-05-27T20:04:27Z
(In reply to Manu from comment #0) > Note there is no `forward!args` then it is passed to `emplaceRef`. This > causes the arguments to be unnecessarily copied, potentially executing a > bunch of copy code. No copy code should be executed. The `auto ref` param will be a ref for an lvalue arg, and a moved bitcopy for an rvalue arg (D move semantics - arg isn't destructed and param's postblit is skipped, but a memcpy is performed). emplaceRef uses `auto ref Args` too. So when calling it in emplace() with its (lvalue) parameters as arguments, they are passed by reference.
Comment #2 by kinke — 2019-05-27T20:24:14Z
This is what I mean: import core.stdc.stdio; struct S { this(this) { printf("postblit\n"); } ~this() { printf("dtor\n"); } } void foo(S s) { printf("rvalue\n"); } void foo(ref S s) { printf("lvalue\n"); } void forward(Args...)(auto ref Args args) { foo(args); } void main() { printf("directly:\n"); foo(S()); printf("forward:\n"); forward(S()); } => directly: rvalue dtor forward: lvalue dtor The rvalue-ness is lost when forwarding `auto ref` params this way (but still detectible via isRef trait).
Comment #3 by kinke — 2019-05-27T20:37:39Z
Ah, due to this rval->lval conversion when calling an `auto ref` function, the ctor is called with an lvalue arg, even if the emplace-arg was originally an rvalue, and that then leads to a superfluous copy (and later destruction) instead of a move at that end of the chain.
Comment #4 by turkeyman — 2019-05-27T21:35:38Z
Right, you unwrapped my example. You need another layer to see it all go wrong.
Comment #5 by andrei — 2019-05-27T22:07:06Z
First, we have a problem with identifying the ref vs the non-ref instantiation. This should compile and doesn't: void fun(T)(auto ref T x) { static if (is(x == ref)) { ... } } This does compile, but prints the wrong thing (non ref for both cases): import std.stdio; void fun(T)(auto ref T x) { pragma(msg, __PRETTY_FUNCTION__); static if (is(__traits(isRef, x))) { writeln("ref: ", x); } else { writeln("non ref: ", x); } } void main() { int a; fun(a); fun(42); } Should definitely be its own bug, so I just submitted https://issues.dlang.org/show_bug.cgi?id=19906. This doesn't compile: void fun(T)(auto ref T x) { pragma(msg, __PRETTY_FUNCTION__); static if (__traits(getParameterStorageClasses, fun, 0)[0] == "ref") { writeln("ref: ", x); } else { writeln("non ref: ", x); } } The error message suggests you can't get access to the function name from within the function definition. This doesn't same to compile for the same reason: void fun(T)(auto ref T x) { import std.traits; pragma(msg, __PRETTY_FUNCTION__); static if (ParameterStorageClassTuple!fun[0] == ParameterStorageClass.ref_) { writeln("ref: ", x); } else { writeln("non ref: ", x); } } Detecting ref-ness should be easily doable from within the instantiation. That's the first problem to tackle.
Comment #6 by atila.neves — 2019-05-28T08:09:44Z
This compiles and works as intended though: import std.stdio; void fun(T)(auto ref T x) { static if(__traits(isRef, x)) writeln("x: ", x, " is a ref"); else writeln("x: ", x, " is NOT a ref"); } void main() { fun(42); int i; fun(i); }
Comment #7 by andrei — 2019-05-28T13:46:09Z
Yes, my bad... copy and paste strikes again!
Comment #8 by kinke — 2021-02-18T08:14:49Z
I completely forgot about this; it should be fixed now. The struct case was already handled, the class case was recently fixed with https://github.com/dlang/druntime/pull/3352.