I'm briefly studying the System V ABI for amd64 / x86-64 architecture, and am curious how it handles return values over 128 bits, where rax
and rdx
aren't enough.
I wrote the following C code on Ubuntu 18.04 64-bit (more generally, any amd64 POSIX-compliant system):
struct big {
long long a, b, c, d;
};
struct big bigfunc(void) {
struct big r = {12, 34, 56, 78};
return r;
}
Compiled it as gcc -S -masm=intel t.c
, and inspected t.s
:
.file "t.c"
.intel_syntax noprefix
.text
.globl bigfunc
.type bigfunc, @function
bigfunc:
.LFB0:
.cfi_startproc
mov QWORD PTR -40[rsp], rdi
mov QWORD PTR -32[rsp], 12
mov QWORD PTR -24[rsp], 34
mov QWORD PTR -16[rsp], 56
mov QWORD PTR -8[rsp], 78
mov rcx, QWORD PTR -40[rsp]
mov rax, QWORD PTR -32[rsp]
mov rdx, QWORD PTR -24[rsp]
mov QWORD PTR [rcx], rax
mov QWORD PTR 8[rcx], rdx
mov rax, QWORD PTR -16[rsp]
mov rdx, QWORD PTR -8[rsp]
mov QWORD PTR 16[rcx], rax
mov QWORD PTR 24[rcx], rdx
mov rax, QWORD PTR -40[rsp]
ret
.cfi_endproc
.LFE0:
.size bigfunc, .-bigfunc
.ident "GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0"
.section .note.GNU-stack,"",@progbits
No surprise that the struct definition doesn't compile into any instructions, so the output only contains function bigfunc
. The output assembly looks pretty straightforward, allocating memory from stack for struct big r
and assign initial values, and returning it.
If I am understanding correctly, before ret
is executed, register rax
contains the value of rdi
at the beginning of the function call (from QWORD PTR -40[rbp]
). According to SysV, rdi
is the first argument supplied to the function, which is impossible because the function accepts no arguments. So I have a few questions here:
rdi
when the function bigfunc
takes no arguments?rax
(as the register that contains return value), when rdx
is not touched in this function?According to the ABI (1) ,page 22
If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first argument to the function. In effect, this address becomes a “hidden” first ar- gument. This storage must not overlap any data visible to the callee through other names than this argument. On return %rax will contain the address that has been passed in by the caller in %rdi
Page 17, 18 and 19 describes the classifications, I beliveve
the following on page 19 is the clause designating your struct big
as a MEMORY class.
(c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument is passed in memory.
i.e. the caller have to allocate memory for the return value, and pass a pointer to that memory in %rdi (and the called function returns that same address in %rax)
(1) there's newer offical versions of the ABI at https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI , though the links arn't currently working properly.