Say we are on x64 (aka x86-64, x86_64, AMD64, Intel 64, IA32e, and EM64T). To make sure that I understand the System V psABI correctly, I compiled the following piece of C on Compiler Explorer:
#include <stdint.h>
struct A { char *p; uint32_t a; };
struct B { char *p; uint32_t a, b; };
extern fa(struct A);
extern fb(struct B);
void gaa(struct A x) { x.a = 1; fa(x); }
void gba(struct B x) { x.a = 1; fb(x); }
void gbb(struct B x) { x.b = 1; fb(x); }
This is what gcc 15.2 produces at -O2 or -O3:
gaa:
movabs rcx, 0xFFFF'FFFF'0000'0000
and rsi, rcx
or rsi, 1
jmp fa
gba:
movabs rcx, 0xFFFF'FFFF'0000'0000
and rsi, rcx
or rsi, 1
jmp fb
gbb:
mov rax, rsi
mov rsi, rdi
mov eax, eax
bts rax, 32
mov rdi, rax
mov rax, rsi
mov rsi, rdi
mov rdi, rax
jmp fb
And this is what clang 21.1.0 produces at -O2 or -O3:
gaa:
mov esi, 1
jmp fa@PLT
gba:
movabs rax, 0xFFFF'FFFF'0000'0000
and rsi, rax
inc rsi
jmp fb@PLT
gbb:
mov eax, esi
movabs rsi, 0x0000'0001'0000'0000
or rsi, rax
jmp fb@PLT
Both generate the same code for gba,
which says a is stored in the lower half of rsi and b in the upper half.
Note that clang zeroes the upper half of rsi in gaa.
I find it surprising that gcc preserves it, and did not find any relevant information.
I think this kind of detail is not well specified and/or documented because very few people care, which is sad.
Also, the code gcc generates for gbb seems very odd.
I do not know why it shuffles registers around when it can simply do mov esi, esi; bts rsi, 32.
Actually, clang does this at -Os or -Oz.
Compilers are smart yet confusing.