Date: Mon, 17 Apr 2023 12:09:25 +0100
Lately I've been messing around trying to write assembler thunks for
invoking lambdas without the hidden 'this' paramater, and I had to
consider the complications of multi-threading, and also of recursive
function calls.
Let me stick to single-threaded programs for now. I will use the term
'caller' to describe a recursive function which contains a lambda,
so something like:
extern void SomeLibraryFunc( void(*)(void) );
void Func(int const arg)
{
if ( -1 == arg ) return;
auto mylambda = [arg](void)->void { /* do something */ };
SomeLibraryFunc( thunk(mylambda) );
Func(arg - 1);
}
In order to write an efficient implementation of the thunk generator to
accommodate recursive calls, I was thinking it would be cool if I could
somehow get the thunk to correctly identify whether the caller has
been re-entered since the thunk was generated. Then I realised that this
could be ascertained from the stack pointer (i.e. the RSP register on
x86_64, or the x31sp register on aarch64). As a function is executed
though, it might push and pop and push and pop, and so the stack pointer
would be going up and down by 8 bytes here and there, it would be
fluctuating a little. But if the frame pointer is used instead (i.e. the
RBP register on x86_64, or the x29FP register on aarch64), then it could
be used to reliably check if re-entry has occurred.
What if the Standard library were to have:
namespace std {
void *frame_pointer(void) noexcept;
void *stack_pointer(void) noexcept;
}
The contents of "frame_and_stack_pointer.S" on x86_64 could be:
frame_pointer:
mov rax,rbp
ret
stack_pointer:
mov rax,rsp
add rax,8 ; disregard the return address on top of stack
ret
Also maybe it would be helpful if the return address were available too,
something like:
namespace std {
void (*return_address(void))(void) noexcept;
}
The contents of "return_address.S" on x86_64:
return_address:
mov rax,[rsp] ; return address is at top of stack
ret
Nine times out of ten, the return address will just be the 8 bytes at
the top of the stack, but it won't be if the function has supernumerary
parameters or a very big return type, so the compiler would have to
be a bit smarter.
If the function "std::frame_pointer" is invoked inside a function that
was compiled with a compiler flag to prevent the use of a frame pointer
(e.g. '-fomit-frame-pointer' on the GNU g++ compiler), or if it is a very
simply function that doesn't use the stack and so doesn't bother with
a frame pointer, then it should return a nullptr.
invoking lambdas without the hidden 'this' paramater, and I had to
consider the complications of multi-threading, and also of recursive
function calls.
Let me stick to single-threaded programs for now. I will use the term
'caller' to describe a recursive function which contains a lambda,
so something like:
extern void SomeLibraryFunc( void(*)(void) );
void Func(int const arg)
{
if ( -1 == arg ) return;
auto mylambda = [arg](void)->void { /* do something */ };
SomeLibraryFunc( thunk(mylambda) );
Func(arg - 1);
}
In order to write an efficient implementation of the thunk generator to
accommodate recursive calls, I was thinking it would be cool if I could
somehow get the thunk to correctly identify whether the caller has
been re-entered since the thunk was generated. Then I realised that this
could be ascertained from the stack pointer (i.e. the RSP register on
x86_64, or the x31sp register on aarch64). As a function is executed
though, it might push and pop and push and pop, and so the stack pointer
would be going up and down by 8 bytes here and there, it would be
fluctuating a little. But if the frame pointer is used instead (i.e. the
RBP register on x86_64, or the x29FP register on aarch64), then it could
be used to reliably check if re-entry has occurred.
What if the Standard library were to have:
namespace std {
void *frame_pointer(void) noexcept;
void *stack_pointer(void) noexcept;
}
The contents of "frame_and_stack_pointer.S" on x86_64 could be:
frame_pointer:
mov rax,rbp
ret
stack_pointer:
mov rax,rsp
add rax,8 ; disregard the return address on top of stack
ret
Also maybe it would be helpful if the return address were available too,
something like:
namespace std {
void (*return_address(void))(void) noexcept;
}
The contents of "return_address.S" on x86_64:
return_address:
mov rax,[rsp] ; return address is at top of stack
ret
Nine times out of ten, the return address will just be the 8 bytes at
the top of the stack, but it won't be if the function has supernumerary
parameters or a very big return type, so the compiler would have to
be a bit smarter.
If the function "std::frame_pointer" is invoked inside a function that
was compiled with a compiler flag to prevent the use of a frame pointer
(e.g. '-fomit-frame-pointer' on the GNU g++ compiler), or if it is a very
simply function that doesn't use the stack and so doesn't bother with
a frame pointer, then it should return a nullptr.
Received on 2023-04-17 11:09:37