Search code examples
linuxassemblyx86x86-64abi

Which registers to use as temporaries when writing AMD64 SysV assembly?


I'm implementing a function using cpuid in assembly according to AMD64 SysV ABI. I need 2 temporary registers to be used in the function itself: the first one to accumulate return value and the second one as a counter.

My function currently looks as:

;zero argument function
some_cpuid_fun:
  push rbx

  xor r10d, r10d ;counter initial value
  xor r11d, r11d ;return value accumulator initial value
  some_cpuid_fun_loop:
  ;...
  cpuid
  ;update r10d and r11d according to the result


  mov eax, r11d
  pop rbx
  ret

Since cpuid clobbers eax, ebx, ecx, edx I cannot use them across different cpuid executions. As documented in the AMD64 SysV ABI:

r10    temporary register, used for passing a function’s
       static chain pointer

r11    temporary register

There is only one strictly temporary register r11, r10 seems to have a different purpose (and its usage as a loop counter is not one, I obviously did not pass any static chain pointers).

QUESTION: Is the some_cpuid_fun function implementation AMD64 SysV ABI compatible? If no how to rewrite it to stay compatible with the ABI?


Solution

  • After you have identified all the registers used for the arguments, none in this case, all you have to care about is if a register is volatile (not preserved across calls) or not.

    In short, looks at the last column of the Figure 3.4 (where the usage for r10 come from): It is not preserved, so you can use it without restoring it.
    The Usage column only tells you where to look for the arguments if expected and where to put the return value.
    If you don't take a static chain pointer in input, you can overwrite r10 as soon as needed.

    So yes, using r10 as a temporary register is ABI compatible.

    For reference:

    This subsection discusses usage of each register. Registers %rbp, %rbx and %r12 through %r15 “belong” to the calling function and the called function is required to preserve their values. In other words, a called function must preserve these registers’ values for its caller. Remaining registers “belong” to the called function. 5 If a calling function wants to preserve such a register value across a function call, it must save the value in its local stack frame.