[PATCH] D92361: [trivial-abi] Support types without a copy or move constructor.

Arthur O'Dwyer via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 4 07:56:38 PST 2020


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



More information about the cfe-commits mailing list