Quuxplusone added a comment.

In D92361#2433190 <https://reviews.llvm.org/D92361#2433190>, @rjmccall wrote:

> There is no such thing as an object "teleporting" in C++.  Objects are always 
> observed in memory with a specific address.  When an argument is passed in 
> registers, its address can be observed to be different on both sides, and 
> that is not permitted; there must be some operation that creates the object 
> at the new address and destroys it at the old.

That's where you're wrong (about C++). You might be right about C or 
Objective-C, I don't know. In C++, that "teleporting" happens //without// any 
call to a special member — there is no move happening, and no destroy 
happening. You can actually observe this: https://godbolt.org/z/zojooc The 
object is simply "bitwise-teleported" from one place to another. Standard C++17 
says that this is a "guaranteed-copy-elision" context; there is indeed only one 
C++ "object" here. It just happens to blit around in memory //beyond// what the 
C++ code is doing to it.

> That operation is a destructive move (which I certainly did not originate as 
> a term for this), or what your proposal calls a relocation (which may be a 
> more palatable term for the C++ committee).

D50119 <https://reviews.llvm.org/D50119> "relocation" is different; it's a way 
for the programmer to warrant an invariant about the behavior of the 
//C++-language-level// operations which you called "copy" and "destroy," and 
say that these operations can be //replaced// by the compiler as long as 
they're replaced in matching pairs. (A move and a destroy always end up 
"teleporting" the object from one place to another as a side effect; 
`[[trivially_relocatable]]` warrants that that pair of operations has no 
//other// side-effects worth preserving.) But D50119 
<https://reviews.llvm.org/D50119> "relocation" doesn't permit the compiler to 
introduce new "teleports" in places where the programmer hasn't written any 
operations at all. https://godbolt.org/z/zojooc is an example of code where the 
programmer hasn't written any operations at all — we have just one object — and 
yet, the compiler teleports it from place to place.

> Your trait should certainly consider a `trivial_abi` type to be trivially 
> relocatable.

The two attributes are essentially orthogonal, from the compiler's POV. One 
says it's okay to replace [move+destroy] with [teleport] but you must not 
introduce any unrequested teleports; the other says it's okay to introduce 
unrequested teleports but you must fully execute all moves and destroys. 
https://godbolt.org/z/EKbznG  (Of course the other major difference in practice 
is that `[[trivially_relocatable]]` is all libc++ magic that manually replaces 
[move+destroy] pairs only in //library// code. So my use of libc++ `std::swap` 
in that demo is deliberate; you wouldn't see the [move+destroy] pairs getting 
removed by the compiler if you had written out swap's 
move-assign-assign-destroy dance by hand.)

---

However, I see there's yet //another// way to think about it, which is highly 
relevant to this PR! In P0593 "implicit-lifetime objects" 
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html> 
conceptually spring into existence via the implementation secretly calling one 
of their constructors at an unspecified point; but this call is unobservable 
because the compiler magically selects a //trivial// constructor to call. Types 
with no trivial constructors can't be implicit-lifetime (IIUC). You could apply 
the same trick here — you could say that when a `[[trivial_abi]]` object 
"bitwise-teleports" from one place to another, it's not //actually// 
teleporting — the compiler is secretly inserting calls to [move+destroy] and 
then replacing the pair with a teleport. But this trick works only if the type 
is in fact movable and/or copyable! If the type //has// no move or copy 
operations, then you can't use this trick to explain what's going on.

Here's an example using Clang trunk `[[trivial_abi]]`: 
https://godbolt.org/z/jox9Er
`U0` is trivial-abi, movable, non-copyable. `U1` is trivial-abi, copyable, 
non-movable. `U2` has a `U0` data member, so it's movable and non-copyable, and 
it inherits trivial-abi. `U3` has a `U1` data member, so it's copyable and 
non-movable, and //it// inherits trivial-abi. But `U4` has both `U0` and `U1` 
members; it wants to inherit trivial-abi, but it's neither movable nor 
copyable. If we're using the trick of saying that "the compiler is not 
inserting bitwise-teleports, it's inserting C++-language-level operations and 
then replacing them with teleports," then we can't use that trick here, because 
`U4` //has// no language-level operations to secretly insert — in the same way 
that a type with no constructors can't be implicit-lifetime (IIUC).

So I think Zoe's PR here is basically forcing us to choose a model: is 
`[[trivial_abi]]` giving the compiler permission to insert unrequested 
bitwise-teleports //directly at the ABI level// (as the name implies), or is it 
inserting unrequested operations at the language level and then (á là 
`[[trivially_relocatable]]`) optimizing them back down to memcpy?  If the 
former, the PR is good; if the latter, the PR is bad.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D92361/new/

https://reviews.llvm.org/D92361

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to