fn main()
{
let a = 100;
let b = add_one(a);
println!("{}", b);
}
fn add_one(x: i32) -> i32
{
x + 1
}
During the running of the above code snippet basically, two stack frames are created: one for the main function and another for the add_one function. CPU executes the add_one function first, and then the stack frame for it is overwritten by another program. But the main function's stack frame needs to take the return value for the variable b.
My question is how does the main function's stack frame collect the return value from the add_one function's stack frame before it is overwritten by another program?
Why do you think main should receive return value from the stack frame?
Simple return values are returned in registers and complex return values are copied by add_one function (there are additional hidden paramater which points to address of “returned” value).
Because I think main called the add_one function or main need the return value of the add_one for its local variable b: this is where we called the add_one.
I'm not good at reading assembly myself which I'm quite ashamed of, but here's my interpretation of what you see in Godbolt:
mov edi, 100 // We store `a = 100` in the lower 32 bits of the rdi register
call qword ptr [rip + example::add_one@GOTPCREL] // call `add_one` function
mov dword ptr [rsp + 4], eax // store 32bit representation of the contents of the eax register on the stack (rsp is the stack pointer). Think of this as `b`. We offset by 4 here, because we are on a 64bit system, I guess?
lea rax, [rsp + 4] // load `b` from the stack into the rax register
... // print b here using the contents of the rax register
example::add_one:
lea eax, [rdi + 1] // we add 1 to the value stored in the rdi register and store it in the eax register. So eax stores our return value
ret
Note that add_one is so small, it didn't even get a stack frame, as it does not need to store anything on the stack. If we add a print statement to add_one:
it will get its own stack frame which you can see from the leading sub rsp, 72 and trailing add rsp, 72 instructions (found that info here). The added print statement doesn't change the fact that we return x + 1 in the eax register. Godbolt.
Thank you so much for your reply. It is really helpful. Your comments on the assembly code do make sense.
So b uses the eax register to save its value which will be returned from after calling the add_one? The memory address of eax register is also the memory address of b?
I believe having answers to these two questions will be enough for clearing my confusion because I keep thinking that data from the eax register will be copied into the memory address of the b(assuming that b uses a different memory location other than the eax register)
Registers are "global" (if we disregard threads, processes, and other forms of concurrency that might be supported directly by the hardware). If some function sets a register to a particular value, then that register continues to have that value as long as it's not changed by something else, thus the caller can read it later. No stack frames involved whatsoever.
Values that don't fit into a small number of registers will mostly be returned by pre-allocating space in the frame of the caller and passing a pointer to that buffer to the called function, and the called function will then directly write/copy the return value into the frame of the caller.
The EAX register does not have a memory address - it's a CPU register.
To really understand what's going on, you need enough understanding of how assembly language works to understand an ABI document, like the x86-64 psABI or the ARM AAPCS, which explains how parameters and return values are passed around once you have machine code.
A stack frame is itself an abstraction, and functions are not guaranteed to have a stack frame once optimized, unless the ABI document requires one; for example, add_one does not necessarily need a stack frame, since its input value is passed in a register, and its return value is also passed in a register.