Reputation: 27385
There are only 2 return registers defined in SystemV ABI: rax
and rdx
, but struct
s can have a size much more than 16 bytes and have more than 2 members. So I considered the following example:
struct test{
unsigned long a;
char *b;
unsigned long c;
};
struct test get_struct(void){
return (struct test){.a = 10, .b = "abc", .c = 20};
}
void get_struct2(struct test *tst){
struct test tmp = {.a = 10, .b = "abc", .c = 20};
*tst = tmp;
}
The O3
compiled code with gcc
for these functions looks almost identically:
Dump of assembler code for function get_struct:
0x0000000000000820 <+0>: lea rdx,[rip+0x2f6] # 0xb1d
0x0000000000000827 <+7>: mov rax,rdi
0x000000000000082a <+10>: mov QWORD PTR [rdi],0xa
0x0000000000000831 <+17>: mov QWORD PTR [rdi+0x10],0x14
0x0000000000000839 <+25>: mov QWORD PTR [rdi+0x8],rdx
0x000000000000083d <+29>: ret
End of assembler dump.
and
Dump of assembler code for function get_struct2:
0x0000000000000840 <+0>: lea rax,[rip+0x2d6] # 0xb1d
0x0000000000000847 <+7>: mov QWORD PTR [rdi],0xa
0x000000000000084e <+14>: mov QWORD PTR [rdi+0x10],0x14
0x0000000000000856 <+22>: mov QWORD PTR [rdi+0x8],rax
0x000000000000085a <+26>: ret
End of assembler dump.
So the get_struct
function signature was silently modified to accept a pointer to the struct and return that pointer.
QUESTION: In the example function returning the struct what is the reason behind returning the pointer passed as the first argument and the very first lea rdx,[rip+0x2f6]
that is similar in both of the cases? Is such usage standardized in the ABI or it is compiler dependent?
The lea rdx,[rip+0x2f6]
seems to stand for the loading of the char *
, but its assembly looks a bit confusing to me since it uses the rip
with disposition (I guess this is the issue with displaying the address of elements in the rodata
section.)
In case if the struct contains 2 members that can be put in registers we can see the expected usage of rax
and rdx
.
Upvotes: 4
Views: 1799
Reputation: 44066
Returning the pointer passed in rdi
(an hidden pointer) is a convenience for the caller.
The callee, not being able to return the whole struct in the registers can only return the struct in memory.
However the callee cannot allocate a buffer for the struct as that would be not only inefficient but also problematic from the ownership point of view (how can the caller free a buffer it doesn't know how is allocated?), so it can only return the pointer given by the caller.
This is also useful to pass the value to other functions if compatible (e.g. f(g())
) and the compiler already know how to deal with functions returning pointers to structs (namely, without any special action).
The use of the hidden pointer, as well as returning it in rax
, is documented in the ABI:
Returning of Values
The returning of values is done according to the following algorithm:
- Classify the return type with the classification algorithm.
- 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 argument. 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.
The lea rax,[rip+0x2d6]
is just the pointer to "abc"
, that's what PIEs (to not be confused with PIC) have to do to access their data (read-only or not).
Finally:
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.
The wording is not 100% correct IMO, a better version would be: "the whole argument has class MEMORY". But the effect is the same: a struct smaller than 16B can be passed and returned in the registers.
Upvotes: 8