www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Move semantics, D vs. C++, ABI details

reply kinke <noone nowhere.com> writes:
This is an attempt to clarify some of the recent confusion wrt. 
DIP 1014, Walter's statement that DMD wouldn't move structs etc.

My understanding of the terminology:

* D moving: copy bits to another memory location, skipping 
postblit for the
   moved-to object & skipping destruction of the moved-from object
* C++ moving: moved-to object constructed via special constructor 
hijacking
   the moved-from object's data and resetting the moved-from 
object for safe
   destruction (no double-free etc.)


More interesting are the following low-level ABI differences. No 
guarantees for completeness/absolute correctness from my side 
(but I've worked on LDC's ABI implementations).


C++11:

1) Non-POD by-value arguments are passed by reference, 
low-level-wise,
    and never on the stack or in registers.
2) The argument/parameter is allocated on the caller's stack and 
destructed
    by the caller after the call.

struct S
{
     S(int) {}      // ctor
     S(const S&) {} // copy ctor
     S(S&&) {}      // move ctor
     ~S() {}        // dtor
};

void foo(S);

void bar()
{
     // 1) passing an rvalue
     foo(S(123));
     /* =>
      * S tmp(123); // construct temporary
      * foo(&tmp);  // pass the temporary directly by ref, no move 
ctor involved
      * tmp.~S();   // destruct it after the call
      */

     // 2) passing an lvalue
     S lval(456);
     foo(lval);
     /* =>
      * S tmp(lval); // construct temporary via copy ctor
      * foo(&tmp);   // pass the temporary directly by ref
      * tmp.~S();    // destruct it after the call
      */

     // 3) using std::move
     foo(std::move(lval));
     /* =>
      * S tmp(std::move(lval)); // construct temporary via move 
ctor (possibly mutating lval)
      * foo(&tmp);              // pass the temporary by ref
      * tmp.~S();               // destruct it after the call
      */
}

====================

D:

1) Non-POD by-value arguments are passed by value, i.e., on the 
stack
    [or in registers].
2) The callee destructs the parameter; the caller doesn't perform 
any
    cleanup/destruction after the call.

struct S
{
     this(int) {}  // ctor
     this(this) {} // postblit
     ~this() {}    // dtor
}

void foo(S);

void bar()
{
     // 1) passing an rvalue
     foo(S(123));
     /* =>
      * S tmp = S(123); // construct temporary
      * foo(tmp);       // pass the temporary by value, i.e., move
      *                 // to foo params stack (copy bits, no 
postblit call)
      * // foo() will destruct its param (moved-to object)
      * // destruction of tmp is skipped (no need to reset to 
S.init)
      */

     // 2) passing an lvalue
     S lval = S(456);
     foo(lval);
     /* =>
      * S tmp = lval; // construct temporary by bitcopy + postblit 
call
      * foo(tmp);     // pass the temporary by value, see rvalue 
case
      * // foo() will destruct its param & tmp's dtor is disabled 
(in the AST)
      */
}


D's rvalue case (explicit temporary + move to params stack) is 
how LDC handles it to satisfy LLVM IR (before optimization); 
other compilers might emplace the argument directly in the 
parameters stack.

For DIP 1014, we (at least LDC) would most likely need to adopt 
the C++ ABI in this regard, i.e., always pass non-PODs by 
reference - AFAIK, the LLVM IR doesn't provide the means to get 
the final address of the (moved-to) parameter in the callee's 
parameter stack, inside the caller's scope (required for the 
proposed postmove call), with whatever that entails (don't 
disable dtor for lvalue copies in AST, destruct the temporaries 
after the call in a finally block if the callee potentially 
throws etc.).
Oct 03 2018
parent kinke <noone nowhere.com> writes:
On Wednesday, 3 October 2018 at 20:57:39 UTC, kinke wrote:
 For DIP 1014, we (at least LDC) would most likely need to adopt 
 the C++ ABI in this regard, i.e., always pass non-PODs by 
 reference
Which would also help with C++ interop of course - while LDC's extern(C++) ABI was fixed wrt. passing all non-PODs by reference, the different destruction rules are still an issue (no destruction when calling a C++ function from D, and double destruction when calling an extern(C++) D function from C++).
Oct 03 2018