Search code examples
assemblyx86-64cpu-registerscalling-convention

How do callee-saved registers work? Who should push the original value onto the stack?


I'm learning callee- and caller-saved registers from CSAPP (Third Edition) on pg251, where I am aware that as for callee-saved registers:

Procedure Q (callee) can preserve a register value by either not changing it at all or by pushing the original value on the stack, altering it, and then popping the old value from the stack before returning.

Additionally, I've learned from this post that:

Callee-saved registers (AKA non-volatile registers, or call-preserved) are used to hold long-lived values that should be preserved across calls.

However, I am still confused when trying to understand an example in CSAPP (Third Edition) on pg252. The example is partially stated below:

long P(long x, long y) {
    long u = Q(y); // The detail of Q is irrelevant here
    long v = Q(x);
    return u + v;
}

The corresponding assembly is generated by gcc-11 as below:

_P:
LFB2:
    pushq   %rbp
LCFI2:
    pushq   %rbx
LCFI3:
    subq    $8, %rsp
LCFI4:
    movq    %rdi, %rbp
    movq    %rsi, %rdi
    call    _Q
    movq    %rax, %rbx
    movq    %rbp, %rdi
    call    _Q
    addq    %rbx, %rax
    addq    $8, %rsp
LCFI5:
    popq    %rbx
LCFI6:
    popq    %rbp
LCFI7:
    ret

It is clear that %rbp and %rbx are callee-saved registers, and we could see that procedure P (caller) pushes %rbp and %rbx onto the stack.

  • Does this mean that caller P (instead of callee Q) actually saves the values of the two registers?
  • If so, does it contradict the role of callee-saved registers?

I would appreciate it if you could help me with the questions above. Thank you very much!


Solution

  • You have to keep in mind that P is not only a caller but also a callee.

    _P:
    LFB2:
        pushq   %rbp
    LCFI2:
        pushq   %rbx
    

    Here we can see that P saves rbp and rbx to save its callers values.


    These two lines are interesting.

    call    _Q
    addq    %rbx, %rax
    

    We can see that P calls Q and then adds rbx to its result (stored in rax). This implies rbx was not modified by Q which means Q must have preserved its value, i.e. it's callee saved.