Search code examples
assemblyposixreturn-valuex86-64abi

How does System V amd64 handle very long return values?


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:

  1. What is rdi when the function bigfunc takes no arguments?
  2. What is rax (as the register that contains return value), when rdx is not touched in this function?
  3. How does the function return this 256-bit C structure?

Solution

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