register allocation --- how to utilize and spill the caller saved registers

741 views Asked by At

I have learned that if any of the caller saved registers (rax rdx rcx rsi rdi r8 r9 r10 r11) is used by the callee, then it has to be saved before and restored after a call instruction by the caller.

Through the following example,

int read();
void print(int i);

int main()
{
    int a = read();
    int b = read();
    int c = read();
    int d = read();
    int e = read();
    int f = read();
    int g = read();
    print(a);
    print(b);
    print(c);
    print(d);
    print(e);
    print(f);
    print(g);
}

Note

  1. The variables a - g should use all the callee saved registers (rbp rsp rbx r12 r13 r14 r15). And we cannot use both rbp or rsp, since either has to be used for addressing the stack memory.

  2. The read and print are from some external compilation unit. Thus, we don't really know about their caller save registers usage when we compile the current compilation unit, specifically, during register allocation for the main function.

In godbolt with -O3 it compiles to the following

main:
  pushq %r15
  pushq %r14
  pushq %r13
  pushq %r12
  pushq %rbp
  pushq %rbx
  subq $24, %rsp # spill here
  call read()
  movl %eax, 12(%rsp) # spill here
  call read()
  movl %eax, %ebx
  call read()
  movl %eax, %r15d
  call read()
  movl %eax, %r14d
  call read()
  movl %eax, %r13d
  call read()
  movl %eax, %r12d
  call read()
  movl 12(%rsp), %edi
  movl %eax, %ebp
  call print(int)
  movl %ebx, %edi
  call print(int)
  movl %r15d, %edi
  call print(int)
  movl %r14d, %edi
  call print(int)
  movl %r13d, %edi
  call print(int)
  movl %r12d, %edi
  call print(int)
  movl %ebp, %edi
  call print(int)
  addq $24, %rsp
  xorl %eax, %eax
  popq %rbx
  popq %rbp
  popq %r12
  popq %r13
  popq %r14
  popq %r15
  ret

Note

  1. The variable a is spilled into 12(%rsp).

  2. We don't need to do spill any of the caller saved registers since they are not used at all, which turns out to be more efficient here.

My questions

  1. Look like we don't really need to deal with spilling the caller saved registers, if we don't use them. Thus, when should we use the caller saved registers?

  2. For callees like read and print since we don't know about their register usage, how should we do the spilling for the caller saved registers?

Thanks

2

There are 2 answers

4
Peter Cordes On BEST ANSWER

It looks like the confusing and unintuitive "caller saved / callee saved" terminology has misled you into thinking every register should always be saved by someone somewhere. See What are callee and caller saved registers? - "call preserved" vs. "call clobbered" is more useful both in ease of remembering and as a mental model. It's normal to let values be destroyed, like a function arg.

Look like we don't really need to deal with spilling the caller saved registers, if we don't use them.

Note that your function does use a couple call-clobbered ("caller saved") registers: It uses RDI to pass the arg to print(int), and it zeros RAX as main's return value.

In cases where it has a value in a call-clobbered register that needs to survive across a function call, GCC chose to mov that value to a call-preserved register. e.g. when read() returns, its return value is in EAX, which would be destroyed by the next call. So it uses mov %eax, %ebp or whatever to save it into a call-preserved register, or spills one to 12(%rsp).

(Note that GCC used push/pop to save/restore its caller's values of the call-preserved registers it uses.)

GCC's default code-gen strategy is to save/restore call-preserved registers to hold values across calls, instead of spilling to memory inside this function. That's generally a good thing for less trivial cases, especially for calls inside loops. See Why do compilers insist on using a callee-saved register here? for more about that.

And we cannot use both rbp or rsp, since either has to be used for addressing the stack memory.

Also false: with -fomit-frame-pointer (on at most optimization levels), RBP is just another call-preserved register. Your function uses it to save a read return value. (EBP is the low 32 bits of RBP).

1
Chris Dodd On

I have learned that if any of the caller saved registers (rax rdx rcx rsi rdi r8 r9 r10 r11) is used by the callee, then it has to be saved before and restored after a call instruction by the caller.

should be

I have learned that if any of the caller saved registers (rax rdx rcx rsi rdi r8 r9 r10 r11) is used by the caller, then it has to be saved before and restored after a call instruction by the caller.

Caller save registers are those that might be clobbered by any called function. You don't know for certain whether any given callee uses them, so you have to assume the worst. The caller only needs to save them if the caller is using them, however. If you're not, you don't care that they might be clobbered.