Date: Sun, 22 May 2022 16:52:45 -0600
On Fri, 20 May 2022 at 19:43, Thiago Macieira via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Friday, 20 May 2022 17:51:58 PDT Thiago Macieira via Std-Proposals
> wrote:
> > I don't think that's sufficient. The change needs to be opt-in, not
> opt-out.
> > Otherwise, we're going to have silent breakages because of some libraries
> > recompiled and others not.
>
> There may be a way not to have a break at all, but it comes at a cost.
> This
> discussion probably needs to go to the cxx-abi group[1] . There are also
> problems I haven't seen yet and there's one that I have and don't have a
> solution for.
>
> Let's say a library currently has a function:
> void func(Type t1);
>
> That gets mangled as _Z4func4Type. If Type is not trivially destructible,
> then
> it's up to the caller today to call Type::~Type on the t1 object, but that
> makes it impossible for func() to relocate from it. This is the central
> point
> of the discussion.
>
> So what if we split the difference?
>
> For every call site where that function is called, instead of:
> call _Z4func4Type
> leaq 15(%rsp), %rdi ; reload saved address of t1
> call _ZN4TypeD1Ev
>
> The compiler emits instead:
> call _ZTr4func4Type
>
> This "Tr" marker indicates that the function will take charge of
> destroying
> any parameters before returning. When compiler the call site, the compiler
> emits this function as a weak thunk, which does exactly what the code used
> to
> do until now. As a weak symbol, it will only be emitted into the binary if
> it
> doesn't already exist elsewhere as a non-weak one. Even if it is emitted
> into
> the binary, at runtime if the non-weak version is present, it won't get
> used.
>
> If the library providing func() hasn't been recompiled, then it doesn't
> have
> the new symbol, meaning the thunk gets used. But since it hasn't been
> recompiled, that means it can't be relocating from the any of its
> parameters,
> so the thunk will be doing its job correctly. But this is the extra cost:
> for
> every out-of-line function that takes any non-trivial parameter by value,
> there's an extra stack frame between caller and callee. Total code size is
> roughly the same, because the amount of work is the same, including that
> of
> stack unwinding due to an exception.
>
> If the library *has* been recompiled, then the compiler may emit both
> symbols.
> If the function is not relocating from any of its parameters, the new
> symbol
> is optional. Whether the compiler emits two completely separate function
> bodies (one that destroys the parameters and one that doesn't, like it
> does
> for constructors and destructors, see [2]) or merges the two in some way
> is an
> implementation question. That's probably something the optimiser should
> decide. Either way, it makes no difference in behaviour.
>
> If the library has been recompiled and the function is relocating any of
> its
> parameters, then obviously the old function symbol is unusable. The ABI
> folks
> should find a way so that the symbol remains in existence but any attempt
> to
> directly call it results in linker error.
>
There is an alternative to making it unusable; per Sébastien, as long as
the parameter is movable, a second function body can be emitted that uses
move to relocate. But perhaps this is too much of a behavioral change to
have dependent on how a function is called.
> It needs to retain the old symbol because of function pointers. Code that
> has
> been recompiled and code that hasn't must still agree that the address of
> that
> function is the same, however the psABI materialises pointers. But that
> brings
> me to the problem I haven't solved yet: just how does one make an indirect
> call through a function pointer?
>
It's a great mechanism, but I think that function pointers are something of
a sticking point; since you don't know who will be invoking a
function pointer, those will always have to use the old ABI, which means
this is a penalty even for code compiled entirely to the new ABI (unless
the compiler provides a flag to disable it, for use in standalone builds).
And also you won't be able to form a function pointer to a function that
performs relocation on its parameters, unless you emit a version (as above)
using move constructor to relocate.
Still, it's good that we've been able to come up with two solutions to the
ABI problem; this gives me confidence that it shouldn't be a blocker.
std-proposals_at_[hidden]> wrote:
> On Friday, 20 May 2022 17:51:58 PDT Thiago Macieira via Std-Proposals
> wrote:
> > I don't think that's sufficient. The change needs to be opt-in, not
> opt-out.
> > Otherwise, we're going to have silent breakages because of some libraries
> > recompiled and others not.
>
> There may be a way not to have a break at all, but it comes at a cost.
> This
> discussion probably needs to go to the cxx-abi group[1] . There are also
> problems I haven't seen yet and there's one that I have and don't have a
> solution for.
>
> Let's say a library currently has a function:
> void func(Type t1);
>
> That gets mangled as _Z4func4Type. If Type is not trivially destructible,
> then
> it's up to the caller today to call Type::~Type on the t1 object, but that
> makes it impossible for func() to relocate from it. This is the central
> point
> of the discussion.
>
> So what if we split the difference?
>
> For every call site where that function is called, instead of:
> call _Z4func4Type
> leaq 15(%rsp), %rdi ; reload saved address of t1
> call _ZN4TypeD1Ev
>
> The compiler emits instead:
> call _ZTr4func4Type
>
> This "Tr" marker indicates that the function will take charge of
> destroying
> any parameters before returning. When compiler the call site, the compiler
> emits this function as a weak thunk, which does exactly what the code used
> to
> do until now. As a weak symbol, it will only be emitted into the binary if
> it
> doesn't already exist elsewhere as a non-weak one. Even if it is emitted
> into
> the binary, at runtime if the non-weak version is present, it won't get
> used.
>
> If the library providing func() hasn't been recompiled, then it doesn't
> have
> the new symbol, meaning the thunk gets used. But since it hasn't been
> recompiled, that means it can't be relocating from the any of its
> parameters,
> so the thunk will be doing its job correctly. But this is the extra cost:
> for
> every out-of-line function that takes any non-trivial parameter by value,
> there's an extra stack frame between caller and callee. Total code size is
> roughly the same, because the amount of work is the same, including that
> of
> stack unwinding due to an exception.
>
> If the library *has* been recompiled, then the compiler may emit both
> symbols.
> If the function is not relocating from any of its parameters, the new
> symbol
> is optional. Whether the compiler emits two completely separate function
> bodies (one that destroys the parameters and one that doesn't, like it
> does
> for constructors and destructors, see [2]) or merges the two in some way
> is an
> implementation question. That's probably something the optimiser should
> decide. Either way, it makes no difference in behaviour.
>
> If the library has been recompiled and the function is relocating any of
> its
> parameters, then obviously the old function symbol is unusable. The ABI
> folks
> should find a way so that the symbol remains in existence but any attempt
> to
> directly call it results in linker error.
>
There is an alternative to making it unusable; per Sébastien, as long as
the parameter is movable, a second function body can be emitted that uses
move to relocate. But perhaps this is too much of a behavioral change to
have dependent on how a function is called.
> It needs to retain the old symbol because of function pointers. Code that
> has
> been recompiled and code that hasn't must still agree that the address of
> that
> function is the same, however the psABI materialises pointers. But that
> brings
> me to the problem I haven't solved yet: just how does one make an indirect
> call through a function pointer?
>
It's a great mechanism, but I think that function pointers are something of
a sticking point; since you don't know who will be invoking a
function pointer, those will always have to use the old ABI, which means
this is a penalty even for code compiled entirely to the new ABI (unless
the compiler provides a flag to disable it, for use in standalone builds).
And also you won't be able to form a function pointer to a function that
performs relocation on its parameters, unless you emit a version (as above)
using move constructor to relocate.
Still, it's good that we've been able to come up with two solutions to the
ABI problem; this gives me confidence that it shouldn't be a blocker.
Received on 2022-05-22 22:52:58