2

Suppose we have a C (or C++) function with the following signature:

void foo(int64_t a, double b, int64_t c, double d);

When compiled on Linux, Mac, or any OS that uses the System V ABI (x86_64), a and c get passed in the rdi and rsi registers, and b and d get passed in xmm0 and xmm1. Okay, nothing wrong with that. But then I do the same in Windows (x86_64), and it looks like it skips some registers. a and c get passed in rcx and r8 (rdx skipped), and b and d get passed in xmm1 and xmm3 (xmm0 and xmm2 skipped). Why does the Win64 do this instead of "compacting" the arguments like System V? With System V, I imagine being able to pass, say, 4 qwords and 4 doubles without needing to pass anything on the stack, whereas Win64, as I'm guessing it would, would pass anything past the 4th argument on the stack.

I am aware of the differences in the order of the registers in argument passing in Win64 vs SysV, but order shouldn't be relevant. I'm just curious as to why Win64 would skip over registers, especially when it only has 4 for non-stack argument passing.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Mona the Monad
  • 2,265
  • 3
  • 19
  • 30
  • Totally different abi, I think I think the old win abi was called fastcall don't know about 64 bit... I just know it is different... – Grady Player Aug 15 '17 at 18:25
  • 1
    It's a bit hard to answer, we could speculate but in the end only designers *really* know their reasons – harold Aug 15 '17 at 18:25
  • Note that the Win64 ABI passes at most four integer / pointer arguments in registers, whereas the System V ABI passes up to six in registers (see https://en.wikipedia.org/wiki/X86_calling_conventions). The four used for the purpose in Win64 are all among the six used in System V. I don't think it's quite appropriate to say that the Win64 ABI *skips* registers, but certainly designates fewer of them for the use. As for why it does so, however, I doubt you'll get an explanation here. – John Bollinger Aug 15 '17 at 18:51
  • That wasn't what I meant by skipping. – Mona the Monad Aug 15 '17 at 21:26
  • 1
    Related: https://stackoverflow.com/questions/4429398/why-does-windows64-use-a-different-calling-convention-from-all-other-oses-on-x86. One interesting point is that at the time, Microsoft and Intel were backing IA-64, so MS's AMD64 support was low-effort, which may explain why they went for an ABI that was similar to 32-bit `__fastcall`, and easy to implement variadic functions, rather than a new design for high performance with "normal" (non-variadic) functions. (Working on an answer to this question, will post later). – Peter Cordes Aug 19 '17 at 05:03

1 Answers1

2

Microsoft's documentation

https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160

states that they pass at most 4 parameters in registers. If one parameter does not fit in a specific register, that register is just skipped.

"Floating-point and double-precision arguments are passed in XMM0 - XMM3 (up to 4) with the integer slot (RCX, RDX, R8, and R9) that would normally be used for that cardinal slot being ignored (see example) and vice versa."

Example 3 on the linked page is exactly your example, and explains the effect you have seen:

func3(int a, double b, int c, float d);    
// a in RCX, b in XMM1, c in R8, d in XMM3  

So they are using up to 4 registers for parameters, first parameter in either RCX or XMM0, second parameter in either RDX or XMM1, etc.

So why doing it this way? Perhaps the idea of passing 8 register parameters to functions didn't seem like an important use case.

Olsonist
  • 2,051
  • 1
  • 20
  • 35
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • I'm just curious if this register skipping behaviour has a negligible effect on performance or something, as Windows is certainly no joke of an operating system. – Mona the Monad Aug 15 '17 at 21:49
  • 3
    It would mean a variable amount of home space for parameters, and make variadic functions much harder to implement. – Raymond Chen Aug 15 '17 at 23:29