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

John McCall via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Wed Dec 2 01:31:56 PST 2020


rjmccall added a comment.

In D92361#2427652 <https://reviews.llvm.org/D92361#2427652>, @zoecarver wrote:

>> If it is not possible to copy or move the type at all, it is not possible to copy or move it trivially.
>
> This was a bit confusing to me because I think this changed between C++14 and 17. In C++14, a class was trivially copyable if it had no non-trivial copy/move constructors. But now the standard requires at least one non-trivial move/copy constructor. It looks like <https://godbolt.org/z/47W7qK> the type traits maybe haven't caught up.

Sorry, I know that the standard defines terms in this area, so what I said was unnecessarily confusing.

I'm describing the normative considerations that have gone into the language design of `trivial_abi`, not trying to state the rule in the standard.   It doesn't especially matter what the standard says because this is not a standard feature.  We want to fit in with the standard framework as much as possible because it simplifies the interactions, but ultimately if we don't think the standard permits what we want out of this extension, we'll just diverge.

>> So S0 may representationally depend on its address, and we should ignore/diagnose trivial_abi on aggregates containing an S0.
>
> I guess the question is, does a type with all copy/move constructors deleted //and the trivial_abi attribute// depend on its address? And the whole point of this patch is to make that answer, "no." But, a type //without// the attribute, //does// depend on its address. And therefore a type holding `S0` (without the attribute) also depends on its address because it implicitly has no copy/move constructors. I'm not sure I understand why we need to diagnose aggregates containing an `S0` or why the trivial_abi attribute couldn't be applied to those types, though.

Okay, so, this is the problem with reasoning about this the C++ way.  I apologize if what follows is a little long.

Types have representational needs, and those needs dictate the properties of correctly-implemented value operations.  However, the C++ special members are designed around the C++ language model; we need to talk briefly about value operations on a deeper level.  There are four basic value operations at this deeper level: original initialization, copy, destructive move, and destruction.  Everything else can be thought of as a combination of these basic operations (like copy-assignment combining destruction and copy) or a twist on one (like C++'s non-destructive moves, which end up being a sort of optimized copy).  The basic representational choices of a type dictate the properties of these four basic operations, and they in turn dictate the properties of optimally-implemented special members.

So, for example, types that manage ownership of some resource pretty much necessarily make initialization and destruction non-trivial, and copy must be non-trivial or impossible, but a true destructive move can remain non-trivial.  (This is why C++'s moves are more like a copy than a move at this conceptual level.)  Types with some sort of address sensitivity, like a relative or interior pointer, must make copy and destructive move non-trivial or impossible, but destruction can remain trivial (as can initialization, if you're willing to ignore a possibly invalid value).  Types that register their objects externally must make all the operations either non-trivial or impossible, and so on.

What `trivial_abi` really does is make a statement about the type's destructive move, which is otherwise something that C++ can't talk about at all.  Passing something in registers, or just in general copying its bits from one location to another and abandoning the old location, is a trivial destructive move, and `trivial_abi` says that that's okay for a type irrespective of the type's own special members as long it's okay for all its subobjects.  So we have to look at every subobject and ask if it allows trivial destructive moves, and if it does then we can honor `trivial_abi`.  That query basically turns into asking whether the subobjects are either trivially-copyable or themselves are marked `trivial_abi`.

>> Similarly, if a subobject type has only non-trivial copy/move constructors (but a trivial destructor), we should assume that it representationally depends on its address and prevent aggregates containing it from being passed directly, even if the containing type deletes all its copy/move constructors and uses trivial_abi.
>
> I concur with the first part of this, at least. That is, `S3` should //not// be passed directly because it is non-trivially copyable (because of its member) and does not have the attribute. Similar to above, I don't understand why we couldn't pass it directly if it had the attribute, though.

If you have a type that deletes all its copy/move constructors and does not declare itself `trivial_abi`, it is saying that it cannot be correctly copied/moved at all.  If it deletes all its copy/move constructors and declares itself `trivial_abi`, it is saying that it can only be destructively moved, and furthermore that that operation is trivial.  Making a containing type `trivial_abi` would make it destructively movable, which its subobject doesn't permit.


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