I'm currently researching the passing of structs by value. I have following code:
typedef struct s2{
double d1;
double d2;
int a;
}S2;
void f_(S2 s);//Some external function, only used for compiling, so I can see the passing of parameters.
void f(){
S2 s=(S2){0.5,1.0,1};
f_(s);
}
If I compile it with -Og -S -fno-asynchronous-unwind-tables, I get following assembly output:
f:
subq $48, %rsp
movq .LC0(%rip), %rax
movq %rax, 8(%rsp)
movq .LC1(%rip), %rax
movq %rax, 16(%rsp)
movl $1, 24(%rsp)
pushq 24(%rsp)
pushq 24(%rsp)
pushq 24(%rsp)
call f_@PLT
addq $72, %rsp
ret
.size f, .-f
.section .rodata.cst8,"aM",@progbits,8
.align 8
.LC0:
.long 0
.long 1071644672
.align 8
.LC1:
.long 0
.long 1072693248
This assembly code, namely the passing over the stack was quite unexpected. Look into the System V Application Binary Interface. At page 17 following, the parameter passing is outlined.
The classification of the parameters is described on page 18-19.
The classification of aggregate (structures and arrays) and union types works as follows:
- If the size of an object is larger than four eightbytes, or it contains unaligned fields, it has class MEMORY. (
S2has the size2*sizeof(double)+sizeof(int)=24, so less than 32 bytes.)
...Skipping 2, as it refers to C++....
- If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS. (This is true. So we have to classify each one)
Some definitions:
Arguments of types (signed and unsigned) _Bool,char,short,int,long,long long, and pointers are in the INTEGER class.
Arguments of types float,double,_Decimal32,_Decimal64 and __m64 are in class SSE.
Each member classified:
typedef struct s2{
double d1;//SSE
double d2;//SSE
int a;//INTEGER
}S2;
Then how each argument should be passed:
- If the class is INTEGER, the next available register of the sequence %rdi,%rsi,%rdx,%rcx,%r8 and %r9 is used
- If the class is SSE, the next available vector register is used, the registers are taken in the order from %xmm0 to %xmm7.
So my proposal for the register allocation looks like this:
xmm0: d1
xmm1: d2
rsi: a
This is clearly wrong, as shown by the compiler that passes the argument using the stack.
Where is my error? What detail did I miss?