Hello all,
This post is mainly for the brave ones that followed the discussion about relocation with the putative reloc keyword. I am in the process of writing the proposal, and I find that the ABI stability section is the weakest point.
For everyone to follow, here is a brief recap: we want to provide full relocation support in the language. relocation happens through its own constructor, which destructively steals the resources of the source object. The destructor on the source object is then never called.
Problem is enabling relocation from function parameters passed by value. For this to work, we need the ABI of the function to be callee-destroy (the function itself is responsible for destroying its parameters). Otherwise if the ABI were caller-destroy, then the function could not avoid the call to the destructor on its input parameters, should some be passed to a relocation constructor.
The ABI break is planned to be opt-in and opt-out. Declaring a relocation constructor on a class-type will force each function that takes that class as value parameter to be callee-destroy (potential ABI break, depending on compilers).
class T { T(T); /* reloc ctor declaration */ };
void foo(T); //< now forcibly callee-destroy
It can also be opted-out, by adding a new implementation-defined attribute on the class definition, so that despite the fact that a relocation constructor may be declared, the ABI of functions are not impacted:
class [[no_parameter_reloc]] T { T(T); /* reloc ctor declaration */ };
void foo(T); //< callee-destroy constraint no longer applies
I agree with the need for the attribute, but wouldn't it be better to attach it to the relocating constructor?
class T { public: T(T) [[no_parameter_reloc]]; };
That way code that wants to be backwards compatible has fewer places where a feature-test macro check is required.
This works well for many cases. Library vendors migrating to this proposal can simply carefully pick which classes they choose to declare a relocation ctor in, and then also add the opt-out attribute in the class definition if they wish to provide ABI stability.
The problem I foresee is that this has terrible composability. If I declare a relocation ctor to my MyString class-type, and opt-out of the ABI break, what happens to all the functions where std::pair<MyString, int> is passed by value? We have many good reasons not to add the opt-out parameter to std::pair. Hence, with the given rules, that would mean that there is no way of ensuring ABI stability on such functions.
Good spot, I hadn't thought of this. However, it's only a problem for non-aggregate class templates that place the contained value(s) directly within the storage of the class, right? pair and tuple, optional, expected and variant; are there any others in the library?
Aggregates should be fine since the attribute can propagate to the class automatically. For example, std::array should propagate the attribute from T, since it has a single non-static data member of type T[N].
I'm thinking of tweaking the rules a little bit. First, we keep the opt-out attribute on the class definition, but it is no longer implementation-defined. Second, the opt-out attribute also provides an (auto) syntax: class [[no_parameter_reloc(auto)]] that will inspect the class subobjects (direct or virtual bases and non-static data-members) and decide accordingly. Here are the deduction rules:
- If one of the class subobjects hasn't opted-out, and (maybe implicitly) declares a relocation constructor and is not trivial (trivial implies destructor is a no-op) then the class opts-in for the ABI break ;
- Otherwise if one of the class subobjects opted-out, and that it (maybe implicitly) declares a relocation constructor, and it is not trivial, then the class opts-out of the ABI break ;
- Otherwise the class opts-in by default.
With the updated rules, if std::pair has the [[no_parameter_reloc(auto)]] attribute, then std::pair<MyString,int> opts-out, while std::pair<MyString,UserDefinedOptInType> opts-in.
It's not clear that `auto` would be sufficient. For example, an implementation of optional/variant that uses a byte buffer instead of a union would not contain a non-static data member manifest subobject of type T.
Instead, I think it would be better to provide a way to query the attribute, and let library code compute propagation appropriately:
optional(optional) [[no_parameter_reloc(is_no_parameter_reloc<T>)]];
variant(variant) [[no_parameter_reloc(is_no_parameter_reloc<Types> or ...)]];
How much third-party code do you think would make use of this attribute? Boost, abseil, folly, sure; who else is writing utility components that need to honor ABI like this?
I am also thinking of allowing a function attribute to opt-out on function level for tricky cases.
Such as?
Anyone have some thoughts on the matter?
Regards,
Sébastien Bini