[TUHS] pcc struct return quirk

Alexander Monakov amonakov at ispras.ru
Sat Mar 12 18:00:54 AEST 2022


Hello,

I'm trying to understand a quirk in 32-bit x86 code generation conventions:
on Linux, when returning a structure from a function by value:

struct S {
	int i, j;
};

struct S f(int x)
{
	return (struct S){x, sizeof(void*)};
}

the caller reserves space for the returned structure and passes a pointer
to that space as an 'invisible' extra argument, which is prepended to
the argument list; the callee returns this pointer (although the caller
knows it anyway); it's as if the function is transformed to

struct S *f(struct S *ret, int x)
{
	ret->i = x;
	ret->j = sizeof(void*);
	return ret;
}

with one essential difference: the function 'f' itself is responsible for
popping the extra argument off the stack (the 'x' argument is popped by
its caller).

This necessitates using the return-with-immediate ('ret 4') instruction
instead of the normal 'ret'; this is the only instance where this variant of
the 'ret' instruction is used in code generation for C programs on Linux
normally.

I wonder how this exception came to be.

Early versions of GCC (e.g. gcc-1.27) did not implement this convention, i.e.
the caller was responsible for popping the invisible pointer similar to the
normal arguments. The "callee-pops" variant was implemented later for
"compatibility with other compilers", and the option that controls this is
called -fpcc-struct-return, which also disables returning small structures in
registers (in the example above GCC would return the value in EDX:EAX register
pair).

Operating systems differ on following this convention. For example, FreeBSD
and OpenBSD do not, and neither does Windows.

Looking at 386BSD tree in unix-history-repo, I see that it used GCC, not PCC.
Where can I look at the history of x86 code generation development in PCC?
Do I understand correctly that i386 code generation in PCC evolved in parallel
with GCC, and at some point GCC was convinced to adopt the (less efficient)
PCC calling convention by default?

Did PCC prefer to do this stack adjustment for the invisible pointer on the
callee side for some other platform, and the behavior was merely carried over
to the i386 port?

Thank you.
Alexander


More information about the TUHS mailing list