Date: Wed, 23 Aug 2023 14:43:58 +0100
On Wed, Aug 23, 2023 at 2:11 PM Sarah Kernighan wrote:
>
> Could it be used for any function? Or is it exclusively for constructor and assignment operators?
For the time being I'm just talking about constructors and the
assignment operator. Maybe in the future we can discuss other uses but
let's keep it simple for now. I'm going to focus on the assignment
operator in this post, and my implementation will be for System V
x86_64 (which is used by every x86_64 operating system except for
MS-Windows).
Here is some sample code that makes use of a PR-assignment operator:
extern string SomeFunc(void);
extern mutex FuncThatReturnsMutex(int, int, int, int, string const &);
optional<mutex> om;
void Work(void)
{
om = FuncThatReturnsMutex( 1, 2, 3, 4, SomeFunc() );
}
int main(void)
{
Work();
}
So inside the function "Work", the very first thing that will happen
is that the 5 arguments will be evaluated, meaning that 'SomeFunc'
will be called. After the arguments have been evaluated, they'll be
pushed onto the stack right-to-left, with the address of the allocated
space for the return value put in RDI, and the address of the thunk
put in RSI, so the assembler for 'Work' will look something like as
follows:
Work:
sub rsp, 32 // Allocate space on stack for string
mov rdi, rsp // Set return value address for SomeFunc
call SomeFunc // Returns a string by value
push rsp // Push 5th argument onto the stack
push 4 // Push 4th argument onto the stack
push 3 // Push 3rd argument onto the stack
push 2 // Push 2nd argument onto the stack
push 1 // Push 1st argument onto the stack
mov rdi, myglobal // Set the 'this' pointer to address
of 'myglobal' in RDI
mov rsi, thunk // Put the address of thunk in RSI
call optional::operator=(T^^)
add rsp, 40 // Pop 5 arguments off the stack (5*8 = 40)
mov rdi, rsp // Set the 'this' pointer to the
address of the string
call string::~string // Destroy the string
add rsp, 32 // Deallocate stack space for the string
ret
The C++ code for the PR-assignment operator is as follows:
optional &operator=(T ^^arg) : buf(arg)
{
this->reset();
__emplace;
this->bool_has_value = true;
return *this;
}
Note the use of the '__emplace' keyword which is where you tell the
compiler that it's now time to generate the PRvalue. The PRvalue is
generated by a thunk.
The purpose of the thunk is to copy all the arguments from the stack
to the parameter-passing registers (i.e. rdi,rsi,rdx,rcx,r8,r9) and
then to invoke 'FuncThatReturnsMutex' as follows:
thunk:
// rdi should contain the address of allocated space for mutex
mov rsi, [rsp+24] // Put 1st argument in register
mov rdx, [rsp+32] // Put 2nd argument in register
mov rcx, [rsp+40] // Put 3rd argument in register
mov r8, [rsp+48] // Put 4th argument in register
mov r9, [rsp+56] // Put 5th argument in register
push r12
call FuncThatReturnsMutex // Invoke the function that returns mutex
pop r12
ret
The thunk will be invoked by optional::operator=(T^^), the assembler
for which will be something like:
optional::operator=(T^^):
// rdi should contain the 'this' pointer
push rdi // Save to restore later
call optional::reset
mov rdi, [rsp] // Restore RDI after call to reset
call thunk // Call the 'thunk' function
pop rdi // Restore RDI after call to thunk
mov byte ptr [rdi+40], 1 // bool_has_value = true
mov rax, rdi // return *this
ret
I've put this all together and gotten it working up on GodBolt:
https://godbolt.org/z/fMrGjoWGs
I really think we could do with having PR-construction and
PR-assignment in C++26.
>
> Could it be used for any function? Or is it exclusively for constructor and assignment operators?
For the time being I'm just talking about constructors and the
assignment operator. Maybe in the future we can discuss other uses but
let's keep it simple for now. I'm going to focus on the assignment
operator in this post, and my implementation will be for System V
x86_64 (which is used by every x86_64 operating system except for
MS-Windows).
Here is some sample code that makes use of a PR-assignment operator:
extern string SomeFunc(void);
extern mutex FuncThatReturnsMutex(int, int, int, int, string const &);
optional<mutex> om;
void Work(void)
{
om = FuncThatReturnsMutex( 1, 2, 3, 4, SomeFunc() );
}
int main(void)
{
Work();
}
So inside the function "Work", the very first thing that will happen
is that the 5 arguments will be evaluated, meaning that 'SomeFunc'
will be called. After the arguments have been evaluated, they'll be
pushed onto the stack right-to-left, with the address of the allocated
space for the return value put in RDI, and the address of the thunk
put in RSI, so the assembler for 'Work' will look something like as
follows:
Work:
sub rsp, 32 // Allocate space on stack for string
mov rdi, rsp // Set return value address for SomeFunc
call SomeFunc // Returns a string by value
push rsp // Push 5th argument onto the stack
push 4 // Push 4th argument onto the stack
push 3 // Push 3rd argument onto the stack
push 2 // Push 2nd argument onto the stack
push 1 // Push 1st argument onto the stack
mov rdi, myglobal // Set the 'this' pointer to address
of 'myglobal' in RDI
mov rsi, thunk // Put the address of thunk in RSI
call optional::operator=(T^^)
add rsp, 40 // Pop 5 arguments off the stack (5*8 = 40)
mov rdi, rsp // Set the 'this' pointer to the
address of the string
call string::~string // Destroy the string
add rsp, 32 // Deallocate stack space for the string
ret
The C++ code for the PR-assignment operator is as follows:
optional &operator=(T ^^arg) : buf(arg)
{
this->reset();
__emplace;
this->bool_has_value = true;
return *this;
}
Note the use of the '__emplace' keyword which is where you tell the
compiler that it's now time to generate the PRvalue. The PRvalue is
generated by a thunk.
The purpose of the thunk is to copy all the arguments from the stack
to the parameter-passing registers (i.e. rdi,rsi,rdx,rcx,r8,r9) and
then to invoke 'FuncThatReturnsMutex' as follows:
thunk:
// rdi should contain the address of allocated space for mutex
mov rsi, [rsp+24] // Put 1st argument in register
mov rdx, [rsp+32] // Put 2nd argument in register
mov rcx, [rsp+40] // Put 3rd argument in register
mov r8, [rsp+48] // Put 4th argument in register
mov r9, [rsp+56] // Put 5th argument in register
push r12
call FuncThatReturnsMutex // Invoke the function that returns mutex
pop r12
ret
The thunk will be invoked by optional::operator=(T^^), the assembler
for which will be something like:
optional::operator=(T^^):
// rdi should contain the 'this' pointer
push rdi // Save to restore later
call optional::reset
mov rdi, [rsp] // Restore RDI after call to reset
call thunk // Call the 'thunk' function
pop rdi // Restore RDI after call to thunk
mov byte ptr [rdi+40], 1 // bool_has_value = true
mov rax, rdi // return *this
ret
I've put this all together and gotten it working up on GodBolt:
https://godbolt.org/z/fMrGjoWGs
I really think we could do with having PR-construction and
PR-assignment in C++26.
Received on 2023-08-23 13:44:10