0

After doing a bit of reading, I have a vague understanding of the use of a stack in calling functions when one function calls another, where the arguments are placed on the stack for the called function to retrieve them.

So, in the following example, calling the function add, would place the values 5 and 6 on the stack, then go to the start of the add function which would pop the values off the stack for each of its arguments.

function add(num1, num2) {
    return num1 + num2;
}

function doit() {
    add(5, 6);
}

However, what happens when a function call takes place in the arguments, to another function, for example:

function subtract(num1, num2) {
    return num1 - num2;
}

function add(num1, num2) {
    return num1 + num2;
}

function doit() {
    add(subtract(9, 7), 6);
}

In this example, the first operand is actually a function call (the call to subtract). So what is placed on the stack in this case in the call to add? How is the function call to subtract resolved before actually executing add?

2

The compiler will put 7 and 9 on the stack, call subtract, put the result and 6 on the stack, and call add.
Just imagine splitting it in two lines, saving the subtract result to a temp variable.

1

The best way to see what is happening under the hood with the stack would be to see what the assembly is doing. Let's translate your example into a simple C program:

int add(int, int);
int subtract(int, int);

int main(void) {
    add(subtract(9, 7), 6);
}

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

We use GCC to generate the assembly for that program: gcc -O0 -S -o test.s test.c1 On an x64 machine, this generates a file called test.s containing the assembly. Here's what I got, cleaned up a bit:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $7, %esi
    movl    $9, %edi
    call    subtract
    movl    $6, %esi
    movl    %eax, %edi
    call    add
    movl    $0, %eax
    popq    %rbp
    ret
add:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -4(%rbp), %edx
    movl    -8(%rbp), %eax
    addl    %edx, %eax
    popq    %rbp
    ret
subtract:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -4(%rbp), %eax
    subl    -8(%rbp), %eax
    popq    %rbp
    ret

The instructions are broken down by function. Each function starts with the block

pushq   %rbp
movq    %rsp, %rbp

The current base pointer is pushed onto the stack. Then the current stack pointer is copied into the base pointer. The stack pointer tracks the location of the entire stack. The base pointer tracks the start of the segment of the stack for this call, called the stack frame. These two lines initialize the stack frame, and store the location to return to when the function call is done.

Next in the main function, we see:

    movl    $7, %esi
    movl    $9, %edi
    call    subtract

The two arguments we passed to the subtract function are set in the %esi and %edi registers. They are available to the subtract call via those registers. Finally, subtract is called. Note that the subtract call happens before the add call is evaluated. The parser saw a function call that needs evaluated first, and pulled it out.

In the subtract call are the lines to initialize the stack frame, then:

movl    %edi, -4(%rbp)
movl    %esi, -8(%rbp)
movl    -4(%rbp), %eax
subl    -8(%rbp), %eax

The first two movls move the arguments from the two registers pushes them onto the stack in this stack frame. The first argument is set at 4 bytes on top of the base pointer, and the second at 8 bytes on top of the base pointer. The third movl moves the first argument into the %eax register. The subl subtracts the second argument from the first, stored in %eax, and stores the result into %eax. The %eax register is where the return value of a function must be stored.

Finally, we see:

    popq    %rbp
    ret

The stack is popped off and the return location in the main function is put back into the base pointer register. The ret call returns execution to that location.

The add function mostly looks the same.

Back in the main function, we see:

    movl    $6, %esi
    movl    %eax, %edi
    call    add

This is the call to add. The second argument is set in the %esi register again. The first argument, the %edi register, is set to the value in %eax. This was the return value. We call the add function with those two arguments.

Finally:

    movl    $0, %eax
    popq    %rbp
    ret

The return value, the %eax register, is set for the main function to 0 (an implicit return added by the compiler).

We can see that the nested call has been converted into two sequential calls, and the return value from the first is passed to the second. With more complicated nesting, the return value might have to be stored temporarily on the stack in the calling function's stack frame.


1 In this invocation, we call gcc, with the -O0 flag to disable all optimizations, the -S flag to generate assembly instead of compiled code, and the -o test.s flag to output the assembly to a file called test.s.

0

What you're asking about is called nested function calls.  It depends on the language, (the instruction set architecture, the compiler) and the calling convention.

If the calling convention has arguments passed in registers, then the compiler will have to flatten nested calls — so it will look like two separate calls, with some copies and temporary locations used in between.

If pushing onto the stack is used to pass arguments (and it is C style calling convention) then we can use the stack as temporary space, and the machine code can have nested calls like the source — part of the outer call will be done (e.g. passing 6), and the inner call will happen, and then the rest of the outer call.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.