C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Relocation in C++

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Wed, 18 May 2022 02:16:14 +0100
On Tue, 17 May 2022 at 17:24, Sébastien Bini <sebastien.bini_at_[hidden]>
wrote:

> If reloc is called on a function parameter, then relocation can be
> performed using (1), (2) or (3), in that order of preference. (1) or (2)
> are no longer enforced as ABI restrictions may prevent from prematurely
> ending the lifetime of relocated parameters. Which one is picked is left at
> the discretion of compiler vendors.
>

Actually, can we rethink this? One of the success criteria here is that we
should be able to pass std::unique_ptr (or equivalent) in registers. So
that means that we're accepting at least some level of ABI breakage.

Any class that is trivially relocatable or has an accessible relocate
operator must either have been trivial already (in which case there is no
ABI impact) or have been explicitly opted in to relocation, by having a
relocator declared or defaulted. Since this is under the control of the
library author and/or user, I don't see it as an issue to say that
functions with parameters having trivial or user-defined relocate have a
different ABI (callee-destroy, or if necessary having automatic
accompanying flags in registers or on the stack). We accept that adding a
user-defined destructor, or virtual functions/bases, or changing data
members changes ABI, so why shouldn't defaulting/declaring a relocator?

(Yes, aggregate-style types ("plain structs") are also affected - but
again, we accept that their ABI is at the mercy of the types they compose.)

So (1) and (2) always apply; ABI is dependent on whether a class is
trivially or user relocatable (and the language on callee-destroy will need
amending). Since you're specifying that classes with synthesized relocate
(from move+destroy) are destroyed at the end of scope, the behavior for
function parameters will be uniform with that for variables.

Then a constructor attribute and reloc_wrapper are no longer necessary.


> *C. Relocator member function*
>
> The relocator member function is the special function that is invoked by
> operator reloc (2). The operator reloc behaves like a constructor: it
> constructs a new instance and maybe allows for base or member
> initialisation.
> This special function is only needed if the object is not trivially
> relocatable. The suggested syntaxes are:
>
> struct T: B
> {
> // data members
> D data;
> T* self;
>
> // syntax A
> operator reloc() { self = this; }
> // syntax B
> operator reloc(T&& rhs) : self{this} {}
> // syntax C (P0023R0)
> >>T(T& rhs) : >>B{rhs}, >>data{rhs.data}, self{this} {}
> };
>

Yes, I agree with syntax B.

I would argue that (except for already trivial classes, where it is
unnecessary) the syntax for declaring a class to be trivially relocatable
should be precisely to default the relocator special member function:

operator reloc(T&& rhs) = default;

This would perform memberwise relocate of bases-and-members, and be trivial
if all those relocate operations are trivial. That is, it would have
identical behavior to a relocator with empty base-and-member list and body:

operator reloc(T&& rhs) {}

However, the latter is never trivial and is not nothrow unless otherwise
specified.

As you will have seen on a previous message, I think that there needs to be
a mechanism to prevent leaks if a base-or-member relocator throws, and
suggest a function-try-block with initializer:

operator reloc(T&& rhs) /* noexcept(false) */
try (auto p = rhs.y)
    /* base-or-member-initializers */
{
    /* relocator function-body */
}
catch (...)
{
    delete p;
}

Received on 2022-05-18 01:16:26