On Mon, 19 Dec 2022 at 23:58, Sébastien Bini <sebastien.bini@gmail.com> wrote:
Hello again,

I briefly read through your comments and will answer tomorrow.

I just wanted to quickly share another approach on the ABI issue. I've given a lot of thoughts in the last hours, and it's really headachy: I see no satisfying solution if we put the opt-out attribute on the class / relocation ctor level.

My suggestion is that declaring a relocation constructor is not enough to trigger the ABI change. Instead, only if the type of the parameter is relocate-only (no copy, no move ctor, only a reloc ctor) that the ABI is enforced to be callee-destroy.

That way, there is simply no ABI break as there are no such types today. And for the types that really need this feature (relocate-only types), then it's guaranteed to be there. Also, we would no longer need the opt-out attribute.

The only downside is that move instead of relocation will likely happen all the time when reloc is used on a function parameter, with incompatible ABI. But I doubt it would be a real problem, and removing the ABI dilemma will surely help this proposal move forward.

Yes, that's another option. Really, it's up to the implementation what they want to do about ABI:

* full break, use callee-destroy for all relocatable types (for those who don't care about ABI)
* break with opt-out - an attribute to mark opt-out, and propagation mechanisms (these could be standardized at a later date)
* no break, but opt-in - an improved [[trivial_abi]] that actually checks that the type is trivially relocatable
* no break, use callee-destroy only for relocate-only types.
* and those that are callee-destroy already don't need to do anything!

And more or less orthogonal to that is whether to use trivial ABI for trivially-relocatable nontrivial types passed as callee-destroy, or to always pass such types on the stack. An implementation could also choose something more complex such as passing trivially-relocatable types with trivial ABI, but non-relocate-only nontrivially-relocatable types as caller-destroy.

Given that there are so many options (and maybe more that I've missed), maybe it would be best just to list them as a sign that we've considered ABI as an issue. If one of them gets settled on across the current caller-destroy platforms (are there any other than Itanium?), that can be standardized (along with propagation mechanisms, if appropriate) at a later date.


On Mon, Dec 19, 2022, 20:59 Edward Catmur <ecatmur@googlemail.com> wrote:

On Mon, 19 Dec 2022 at 18:55, Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
On Mon, Dec 19, 2022 at 12:05 PM Edward Catmur via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Mon, 19 Dec 2022 at 17:52, Giuseppe D'Angelo via Std-Proposals <std-proposals@lists.isocpp.org> wrote:

I'm not really sure without reading something concrete, but this seems
to be falling the same bucket of synthesizing copies / moves /
relocations (P1144) (/ [[trivial_abi]]), i.e. things compose
"automatically" by default or you've got type traits to allow you to
choose a composition strategy.

Yes, I agree on that part, if you mean that they should compose automatically for defaulted special member functions (including for aggregates).

The most recent syntax
optional(optional) [[no_parameter_reloc(is_no_parameter_reloc<T>)]];
variant(variant) [[no_parameter_reloc(is_no_parameter_reloc<Types> or ...)]];
certainly seems to be gravitating toward the P1144 propagation solution, as seen in my libc++ fork here for example:

The difference I see between this and P1144 is that this is conflating trivial_abi with trivial relocatability (even though there are types which are trivially relocatable that we probably don't want to be trivial_abi, such as `std::array<int, 1000>`

I'm not sure I understand your position here. std::array<int, 1000> *is* trivial, so it certainly has trivial ABI. And that's fine; ABI says that a class type that is too large to be passed in (some small number of) registers is passed by copying from some stack location to another. Trivial ABI just says to, constructors and destructors notwithstanding, pass the object in the manner that ABI prescribes for a trivial class type of the same layout.

) and even with non-trivial relocatability.

I see it as allowing to express the fine-grained distinction between several possibilities, some of which already exist of course:

* trivial
* trivial ABI
* trivially relocatable but not trivial for the purposes of ABI
* non-trivially relocatable
* relocatable via copy/move+destroy
* immovable

It's almost like a "customizable trivial_abi" at this point — "take this type and pass it by trivial_abi, except that each time the object teleports from one memory address through a register to another memory address, run this code."
And the problem I see with that is that the "teleport" operation is really two operations: "slurp from memory into a register" on the caller's side, and "blat from register back into memory" on the callee's side. For types that are trivially relocatable, both operations are trivial.  For types that aren't trivially relocatable, it's unclear what these two operations should actually do, and in particular whether the in-register representation can be used directly at all. 

Ah; no, that isn't how a nontrivial relocating constructor works. It doesn't place the object representation into registers like a trivial relocation (of a small enough class) would; it will in all likelihood have the same ABI as a copy or move constructor, i.e. taking two pointers, to the source and destination objects. (Although it has the additional effect of ending the lifetime of the source object.)

And collapsing them both into `T(T)` (however it's ultimately spelled) feels like a problem.

Well, the former is spelled `T(T) = default`. Those extra 2 keywords do a lot of work, here and elsewhere,

Have you tried "manually implementing" this idea, by showing some simple examples of `T(T)` and then showing the machine code generated from them?
Assume that nothing is inlined or optimized — so you're not allowed to "cheat" by eliding any operations — just show exactly how the `T(T)` constructor would be codegenned, and then what a call to it would look like, and so on.

It would perhaps be more illuminating to show the equivalent C++ code. Given a type A that is relocatable through move+destroy, and a type T that contains an instance of A:

struct A { A(A&&); ~A(); };
struct T { A a; int i; };

The automatically-generated (implicitly defaulted) T::T(T) would, modulo assumptions of ABI, be equivalent to the following (omitting exception handling, which Sébastien is certainly mindful of):

void T_T_T(T* dest, T* src) {
    std::construct_at(&dest->a, std::move(src->a));
    dest->i = src->i;

So there's really no need to compile down to machine code; the code executed by the relocating constructor is fully expressible in today's C++ - though I should point out that I'm very much not advocating specifying it via source translation.