How main function's stack frame takes the return value from the other function's stack frame?

Here is the code snippet:

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?

Thanks.

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).

The return value is copied to some register. Godbolt.

I am new to programming so I think you can safely assume that I am not familiar with assembly.

What happens after when the return value is copied to register? How does the main stack frame have it?

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:

pub fn add_one(x: i32) -> i32
{
    println!("{}", x);
    x + 1
}

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.

1 Like

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.

3 Likes

eax does not have a memory address. It is a register that only exist in the CPU. You write the contents of eax to memory here (on the stack):

3 Likes

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.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.