0

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

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Lin
  • 1,547
  • 2
  • 12
  • 26

2 Answers2

1

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

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks for answering. I admit I got misled by the naming convention a little. Based on your explanation the `call clobbered registers` should only be used for holding temporary values across the calls, whereas the `call preserved registers` are meant to hold something like variables. – Lin Oct 02 '20 at 14:34
  • Some followups. 1. Thus, while doing register allocations, i.e., mapping variables to registers, we should only use `call preserved registers`? 2. And when we call procedures from another compilation unit, I believe we don't need to concern save/restore the call clobbered registers. 3. If that is the case, then when we design an architecture, we should naturally choose to have more `call preserved registers` for more possible number of variables? – Lin Oct 02 '20 at 14:35
  • @Lin: Go read [What are callee and caller saved registers?](https://stackoverflow.com/a/56178078) - I gave more examples and details there. 1. no, in a leaf function, use the call-clobbered registers first, hopefully not needing to save/restore anything or allocate any stack space. Or if you have a variable that's only live *between* two function calls, like a loop counter for a loop that doesn't contain any calls, use a call clobbered register for it. – Peter Cordes Oct 02 '20 at 22:39
  • 3. no, call-clobbered registers are valuable, too. And it's not a feature of an ISA; designing a calling convention is separate from the hardware. For example, x86-64 has two major ones, x86-64 System V and Windows x64. See [Why does Windows64 use a different calling convention from all other OSes on x86-64?](//stackoverflow.com/a/35619528) for some links to mailing list archive messages about how / why it was designed that way. See also [Why not store function parameters in XMM vector registers?](//stackoverflow.com/q/33707228) for more about the tradeoff between volatile / non-volatile regs – Peter Cordes Oct 02 '20 at 22:42
0

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.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Well, you don't have to save and restore the register per se; only if it contains a value that you need to keep. And ideally, you can design your compiler so that values that need to be preserved across a function call are never placed in such registers, but instead in callee-saved registers or in memory. – Nate Eldredge Oct 02 '20 at 00:06