0

Given the following code:

typedef struct tagRECT {
  int left;
  int top;
  int right;
  int bottom;
} RECT;

extern int Func(RECT *a, int b, char *c, int d, char e, long f, int g, int h, int i, int j);

int main() {

}

void gui() {
    RECT x = {4, 5, 6, 7};
    Func(&x, 1, 0, 3, 4, 5, 6, 7, 8, 9);
}

This is the assembly generated gcc x86_64 presumably on linux (I used compiler explorer).

main:
  mov eax, 0
  ret
gui:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  ; RECT x assignment
  mov DWORD PTR [rbp-16], 4
  mov DWORD PTR [rbp-12], 5
  mov DWORD PTR [rbp-8], 6
  mov DWORD PTR [rbp-4], 7

  ; parameters
  lea rax, [rbp-16]
  push 9
  push 8
  push 7
  push 6
  mov r9d, 5
  mov r8d, 4
  mov ecx, 3
  mov edx, 0
  mov esi, 1
  mov rdi, rax
  call Func
  add rsp, 32
  nop
  leave
  ret

It can be seen that the int in the struct are aligned by 4 bytes. But the last 4 parameters to the function, all int are pushd to the stack which means they were aligned by 8 bytes. Why this inconsistency?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
wingerse
  • 3,670
  • 1
  • 29
  • 61
  • 1
    Have you read [the ABI document](https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf) for amd64 Linux? It explains exactly how arguments to functions works. – fuz Jul 27 '18 at 19:22
  • @fuz yes I just read it and it does mention that parameters are to be 8 byte aligned but I still don't understand why struct is not.. – wingerse Jul 27 '18 at 20:26
  • Intuitively, because this way, compilers can simply push registers on the stack. Only 8 bytes can be pushed at a time on amd64. – fuz Jul 27 '18 at 20:36

1 Answers1

3

stack slots are 8 bytes in x86-64 calling conventions like the x86-64 System V calling convention you're using, because 32-bit push/pop is impossible, and to make it easier to keep it 16-byte aligned. See What are the calling conventions for UNIX & Linux system calls on i386 and x86-64 (it also covers function-calling conventions, as well as system-calling conventions. Where is the x86-64 System V ABI documented?.

mov works just fine, though, so it would have been a valid design to make 4 bytes the minimum unit for stack args. (Unlike x86-16 where SP-relative addressing modes were impossible). But unless you introduce padding rules, then you could have misaligned 8-byte args. So giving every arg at least 8-byte alignment was probably part of the motivation. (Although there are padding rules to guarantee that __m128 args have 16-byte alignment, and __m256 have 32-byte, etc. And presumably also for over-aligned structs, like struct { alignas(64) char b[256]; };.

Only 4-byte slots would break more easily for functions without prototypes, and maybe make variadic functions more complex, but x86-64 System V already passes larger objects by value on the stack, so a stack arg may take more than one 8-byte "stack slot".

(Unlike Windows x64 which passes by hidden reference so every arg is exactly one stack slot. It even reserves 32 bytes of shadow space so a variadic function can spill its register args into the shadow space and create a full array of all the args.)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847