Date: Fri, 20 May 2022 18:43:26 -0700
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.
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?
[1] https://github.com/itanium-cxx-abi/cxx-abi
[2] https://gcc.godbolt.org/z/z5adK1Tzo
> 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.
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?
[1] https://github.com/itanium-cxx-abi/cxx-abi
[2] https://gcc.godbolt.org/z/z5adK1Tzo
-- Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org Software Architect - Intel DPG Cloud Engineering
Received on 2022-05-21 01:43:28