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.
I would appreciate it if you could help me with the questions above. Thank you very much!
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.