Intel x86 Assembly Language & Microarchitecture Calling Conventions 32-bit, cdecl — Dealing with Floating Point

Help us to keep this website almost Ad Free! It takes only 10 seconds of your time:
> Step 1: Go view our video on YouTube: EF Core Bulk Extensions
> Step 2: And Like the video. BONUS: You can also share it!


As parameters (float, double)

Floats are 32 bits in size, they are passed naturally on the stack.
Doubles are 64 bits in size, they are passed, on the stack, respecting the Little Endian convention1, pushing first the upper 32 bits and than the lower ones.

//C prototype of callee
double foo(double a, float b);

foo(3.1457, 0.241);

;Assembly call

;3.1457 is 0x40092A64C2F837B5ULL
;0.241 is 0x3e76c8b4

push DWORD  3e76c8b4h        ;b, is 32 bits, nothing special here
push DWORD 0c2f837b5h        ;a, is 64 bits, Higher part of 3.1457
push DWORD  40092a64h        ;a, is 64 bits, Lower part of 3.1457
call foo
add esp, 0ch

;Call, using the FPU
;ST(0) = a, ST(1) = b
sub esp, 0ch
fstp QWORD PTR [esp]        ;Storing a as a QWORD on the stack
fstp DWORD PTR [esp+08h]    ;Storing b as a DWORD on the stack
call foo
add esp, 0ch

As parameters (long double)

Long doubles are 80 bits2 wide, while on the stack a TBYTE could be stored with two 32 bits pushes and one 16 bit push (for 4 + 4 + 2 = 10), to keep the stack aligned on 4 bytes, it ends occupying 12 bytes, thus using three 32 bits pushes.
Respecting Little Endian convention, bits 79-64 are pushed first3, then bits 63-32 followed by bits 31-0.

//C prototype of the callee
void  __attribute__((cdecl)) foo(long double a);


;Call to foo in assembly
;3.1457 is 0x4000c9532617c1bda800

push DWORD 4000h        ;Bits 79-64, as 32 bits push
push DWORD 0c9532617h        ;Bits 63-32
push DWORD 0c1bda800h        ;Bits 31-0
call foo
add esp, 0ch

;Call to foo, using the FPU
;ST(0) = a

sub esp, 0ch
fstp TBYTE PTR [esp]        ;Store a as ten byte on the stack
call foo
add esp, 0ch

As return value

A floating point values, whatever its size, is returned in ST(0)4.

float one() { return 1; }

fld1            ;ST(0) = 1

double zero() { return 0; }

fldz            ;ST(0) = 0

long double pi() { return PI; }

fldpi            ;ST(0) = PI

1 Lower DWORD at lower address.

2 Known as TBYTE, from Ten Bytes.

3 Using a full width push with any extension, higher WORD is not used.

4 Which is TBYE wide, note that contrary to the integers, FP are always returned with more precision that it is required.

Got any Intel x86 Assembly Language & Microarchitecture Question?