trivial_abi

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Wed Jan 3 14:12:58 PST 2018


On 2 January 2018 at 20:55, John McCall via cfe-commits <
cfe-commits at lists.llvm.org> wrote:

> On Jan 2, 2018, at 10:43 PM, Richard Smith <richard at metafoo.co.uk> wrote:
>
> On 2 January 2018 at 19:02, John McCall via cfe-commits <
> cfe-commits at lists.llvm.org> wrote:
>
>>
>> On Jan 2, 2018, at 9:15 PM, Akira Hatanaka <ahatanaka at apple.com> wrote:
>>
>>
>>
>> On Jan 2, 2018, at 4:56 PM, Richard Smith via cfe-commits <
>> cfe-commits at lists.llvm.org> wrote:
>>
>> On 2 January 2018 at 15:33, John McCall via cfe-commits <
>> cfe-commits at lists.llvm.org> wrote:
>>
>>> Hey, Richard et al.  Akira and I were talking about the right ABI rule
>>> for deciding can-pass-in-registers-ness for structs in the presence of
>>> trivial_abi, and I think I like Akira's approach but wanted to get your
>>> input.
>>>
>>> The current definition in Itanium is:
>>>
>>>   *non-trivial for the purposes of calls*
>>>
>>> A type is considered non-trivial for the purposes of calls if:
>>>
>>>    - it has a non-trivial copy constructor, move constructor, or
>>>    destructor, or
>>>
>>> I'm assuming we're implicitly excluding deleted functions here. (I'd
>> prefer to make that explicit; this has been the source of a number of ABI
>> mismatches.)
>>
>>>
>>>    - all of its copy and move constructors are deleted.
>>>
>>>
>>> I'd suggest modifying this to:
>>>
>>> A type is considered non-trivial for the purposes of calls if:
>>> - if has a copy constructor, move constructor, or destructor which is
>>> non-trivial for the purposes of calls, or
>>> - all of its copy and move constructors are deleted and it does not have
>>> the trivial_abi attribute.
>>>
>>> A copy/move constructor is considered trivial for the purposes of calls
>>> if:
>>> - it is user-provided and
>>> - the class has the trivial_abi attribute and
>>> - a defaulted definition of the constructor would be trivial for the
>>> purposes of calls; or
>>>
>>
>> We'd need to say what happens if the function in question cannot validly
>> be defaulted for any of the reasons in [dcl.fct.def.default]. Do we try to
>> infer whether it's a copy or move constructor, and use the rules for a
>> defaulted copy or move constructor? Or do we just say that's never trivial
>> for the purposes of calls? Or something else? Eg:
>>
>> struct [[clang::trivial_abi]] A {
>>   A(A && = make());
>> };
>>
>> Here, A::A(A&&) cannot validly be defaulted. Is A trivial for the purpose
>> of calls? Likewise:
>>
>> struct [[clang::trivial_abi]] B {
>>   B(...);
>> };
>> struct C {
>>   volatile B b;
>> };
>>
>> Here, C's copy constructor calls B::B(...). Is C trivial for the purpose
>> of calls? (OK, Clang crashes on that example today. But still...)
>>
>> I'd be uncomfortable making the rules in [dcl.fct.def.default] part of
>> the ABI; they seem to be changing relatively frequently. Perhaps we could
>> say "if the function is a copy constructor ([class.copy.ctor]/1), then
>> consider what an implicitly-declared defaulted copy constructor would do;
>> if it's a move constructor ([class.copy.ctor]/2), then consider what an
>> implicitly-declared defaulted move constructor would do; otherwise, it's
>> not trivial for the purpose of calls". That'd mean A is trivial for the
>> purpose of calls and C is not, which I think is probably the right answer.
>>
>> - it is not user-provided and
>>> - the class has no virtual functions and no virtual base classes, and
>>> - the constructor used to copy/move each direct base class subobject is
>>> trivial for the purposes of calls, and
>>> - for each non-static data member that is of class type (or array
>>> thereof), the constructor selected to copy/move that member is trivial for
>>> the purposes of calls.
>>>
>>> A destructor is considered trivial for the purposes of calls if:
>>> - it is not user-provided or the class has the trivial_abi attribute, and
>>> - the destructor is not virtual, and
>>> - all of the direct base classes of its class have destructors that are
>>> trivial for the purposes of calls, and
>>> - for all of the non-static data members of its class that are of class
>>> type (or array thereof), each such class is trivial for the purposes of
>>> calls.
>>>
>>> These definitions are intended to follow [class.copy.ctor]p11 and
>>> [class.dtor]p6 except for the special rules applicable to trivial_abi
>>> classes.
>>>
>>
>> If I could rephrase: a *tor is considered trivial for for the purposes of
>> calls if it is either defaulted or the class has the trivial_abi attribute,
>> and the defaulted definition would satisfy the language rule for being
>> trivial but with the word "trivial" replaced by "trivial for the purposes
>> of calls". So only effect of the trivial_abi attribute is to "undo" the
>> non-triviality implied by a user-provided *tor when computing triviality
>> for the purpose of calls.
>>
>> I think that's a reasonable rule, if we have a satisfactory notion of
>> "defaulted definition".
>>
>> I'm not sure about the "defaulted definition" rule for copy/move
>>> constructors in trivial_abi classes.  The intent is to allow class
>>> temploids with trivial_abi that are instantiated to contain non-trivial
>>> classes to just silently become non-trivial.  I was thinking at first that
>>> it would be nice to have a general rule that trivial_abi classes only
>>> contain trivial_abi subobjects, but unfortunately that's not consistent
>>> with the standard triviality rule in some silly corner cases: a
>>> trivially-copyable class can have a non-trivially-copyable subobject if it
>>> happens to copy that subobject with a trivial copy constructor.  I couldn't
>>> think of a better way of capturing this than the "defaulted definition"
>>> rule.  I considered using the actual initializers used by the constructor,
>>> but that would introduce a lot of new complexity: suddenly we'd be asking
>>> about triviality for an arbitrary constructor, and copy/move elision make
>>> the question somewhat ambiguous anyway.
>>>
>>
>> Per the above examples, I don't think you can escape asking about
>> triviality for an arbitrary constructor if you take this path.
>>
>> Another option, similar to your general rule, would be to say that a type
>> is considered trivial for the purpose of calls if either: (1) it is trivial
>> for the purpose of calls under the current Itanium ABI rule, or (2) it has
>> the trivial_abi attribute and all members and base classes have types that
>> are trivial for the purpose of calls. That would sidestep the "defaulted
>> definition" complexity entirely, and while it differs from the way that the
>> language computes triviality normally, it doesn't seem fundamentally
>> unreasonable: when we're thinking about triviality for the purpose of
>> calls, there's notionally a call to the trivial copy/move ctor being
>> elided, not a call to an arbitrary ctor selected by overload resolution,
>> and we'd just be pushing that effect from the class itself to its
>> subobjects with this attribute.
>>
>>
>>
>> It sounds like a class containing a member that has a type annotated with
>> “trivial_abi” would not necessarily be considered trivial for the purpose
>> of calls according to rule (2)? For example, S1 would not be trivial for
>> the purpose of calls because it isn’t annotated with “trivial_abi” in the
>> code below:
>>
>> struct [[clang::trivial_abi]] S0 {
>>   // user-provided special functions declared here.
>> };
>>
>> struct S1 {
>>   S0 f0;
>> };
>>
>> I thought we wanted containing classes (S1 in this case) to be trivial
>> for the purpose of calls too?
>>
>>
>> I would like that, yeah.
>>
>
> OK, I think that's fair. Then we probably need the more complex rule.
> Which I think means we're at something equivalent to:
>
> A type is considered non-trivial for the purposes of calls if:
> - if has a copy constructor, move constructor, or destructor *that is not
> deleted and* is non-trivial for the purposes of calls, or
> - all of its copy and move constructors are deleted and it does not have
> the trivial_abi attribute.
>
> Hold on... this final "and it does not have the trivial_abi attribute"
looks wrong to me; it seems to break the "do what I mean"ness of the
attribute. Consider:

template<typename T, typename U> struct [[clang::trivial_abi]] pair { ... };

std::pair<ContainsPointerToSelf, int> f(); // returned indirect
std::pair<ContainsPointerToSelf, NonCopyable> g(); // returned in registers
because all copy/move ctors deleted

That seems like a bug. Can we just strike that addition, or does one of
your intended use cases need it?

> A copy/move constructor is considered trivial for the purposes of calls if:
> - it is user-provided and
> - the class has the trivial_abi attribute and
> - *a defaulted definition of a constructor with the signature of the
> implicit copy/move constructor for the class would be trivial for the
> purposes of calls*; or
>
> One other concern here: what if the defaulted definition would be deleted?
I think in that case the constructor we're considering should also be
treated as if it were deleted. And that applies recursively: if the
implicit copy/move constructor would itself have been deleted, we want to
treat the original member of the type we're checking as being deleted. And
likewise, if a defaulted copy/move constructor invokes a copy/move
constructor of a trivial_abi class, and a defaulted copy/move constructor
for that class would have been deleted, we want to act as if the original
defaulted copy/move constructor was deleted. That seems awkward to specify
in the fashion we've been using until now, since the result of a triviality
calculation is now "deleted", "non-trivial", or "trivial", and deletedness
can change in either direction as a result of the attribute.

Here's a terse summary of the rule I'm considering:

"""
For the determination of triviality for the purposes of calls, a modified
form of the program is considered. In this modified form, each copy or move
constructor or destructor of a class with the trivial_abi attribute is
replaced by a defaulted copy or move constructor or destructor (with the
signature of an implicit such declaration), and calls to the former are
transformed into calls to the latter within the implicit definitions of
defaulted special member functions. A function is deleted for the purposes
of calls in the original program if the corresponding function is deleted
in the modified program, and is otherwise trivial for the purposes of calls
in the original program if the corresponding function is trivial in the
modified program.

A type is considered non-trivial for the purposes of calls if:
- if has a copy constructor, move constructor, or destructor that is
non-deleted and non-trivial for the purposes of calls, or
- all of its copy and move constructors are deleted for purposes of calls.
"""

> - it is not user-provided and
> - the class has no virtual functions and no virtual base classes, and
> - the constructor used to copy/move each direct base class subobject is
> trivial for the purposes of calls, and
> - for each non-static data member that is of class type (or array
> thereof), the constructor selected to copy/move that member is trivial for
> the purposes of calls.
> *A constructor that is neither a copy constructor nor a move constructor
> is considered non-trivial for the purposes of calls*.
>
>
> This clause is there to handle constructors that are copy/move
> constructors only because of defaulted arguments?  I wonder if this is
> necessary; I think the allocator-like use cases would prefer that we just
> ignore the non-initial arguments, wouldn't they?
>

This doesn't affect the default argument case: if a constructor has a first
parameter of type T / cv T& / cv T&&, and all further parameters (if any)
have default arguments, it is still a copy or move constructor. Rather, we
reach this clause in any case where "the constructor used/selected to
copy/move [...]" has some other first parameter type or is X::X(...); such
a constructor is only selected when there is no viable copy/move
constructor.

> A destructor is considered trivial for the purposes of calls if:
> - it is not user-provided or the class has the trivial_abi attribute, and
> - the destructor is not virtual, and
> - all of the direct base classes of its class have destructors that are
> trivial for the purposes of calls, and
> - for all of the non-static data members of its class that are of class
> type (or array thereof), each such class is trivial for the purposes of
> calls.
>
> Bolded phrases are changed from John's initial email.
>
>
> Thank you for the revision; this is much improved.
>

I'm concerned about the level of complexity we've discovered to be
necessary here, and in particular the necessity of having a side-notion of
"trivial for the purpose of calls" for all copy/move ctors and dtors, even
in classes that do not directly use the trivial_abi attribute. But I
suppose that's fundamental if we want to pass struct S1 (above) directly.
I'd like a simpler rule, but I'm not convinced there is one.


> John.
>
>
>
>> John.
>>
>>
>> I'm also not sure about the right rules about virtual methods.  Should we
>>> allow polymorphic classes to be made trivial by application of the
>>> attribute?
>>>
>>
>> I think that it probably doesn't make much sense to pass dynamic classes
>> indirectly unless we can avoid passing the vptr; otherwise I'd expect we'd
>> use too many registers for it to be worthwhile. Perhaps as a compromise, we
>> could make the attribute ill-formed if used on a class definition that
>> introduces any virtual bases or explicitly declares any member functions as
>> 'virtual'. That gives us the room to make this decision later if we find we
>> want to.
>> _______________________________________________
>> cfe-commits mailing list
>> cfe-commits at lists.llvm.org
>> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>>
>>
>>
>>
>> _______________________________________________
>> cfe-commits mailing list
>> cfe-commits at lists.llvm.org
>> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>>
>>
>
>
> _______________________________________________
> cfe-commits mailing list
> cfe-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20180103/35c3bb6d/attachment-0001.html>


More information about the cfe-commits mailing list