C++ Logo

std-proposals

Advanced search

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

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Mon, 19 Dec 2022 20:59:39 +0100
On Mon, 19 Dec 2022 at 18:55, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
wrote:

> On Mon, Dec 19, 2022 at 12:05 PM Edward Catmur via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On Mon, 19 Dec 2022 at 17:52, Giuseppe D'Angelo via Std-Proposals <
>> std-proposals_at_[hidden]> 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:
>
>
> https://github.com/llvm/llvm-project/commit/cc0addf5ab7f84a1a959addc2d69f6ac38d07e98#diff-bbea28f47b73848b21aca02ff43cea0dd32a070825ce786de3d9566bd7ed4717
>
>
> https://github.com/llvm/llvm-project/commit/cc0addf5ab7f84a1a959addc2d69f6ac38d07e98#diff-2c05a5b3a75a81a99615776824dcdc7e612b081e88f024d389e83e834eb20f1e
>
> 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));
    src->a.~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.

HTH,
> Arthur
>

Received on 2022-12-19 19:59:51