Floats on x86 - Examples
Whats available?
A staggeringly wide number of instructions, especially if operating on data in parallel. I’ll not cover them.
As basic, these SSE mov variants are specific for floats. They move between XMM registers and load/unload memory locations. Note they can not move between two memory locations, or accept immediates. When handling registers, they place in an initial place (‘low doubleword’),
MOVSS
32‐bit (‘single precision’)
MOVSD
64‐bit (‘double precision’)
These MMX mov variants can be used. They also move between XMM (later XMM) registers and load/unload memory locations, will not move between memory locations, and will not accept immediates,
MOVD
32‐bit (doubleword, ‘single precision’)
MOVQ
64‐bit (‘quadword’, ‘double precision’)
As far as I know, the SSE instructions are different because they do a zero fill. I’ve seen both of these instruction sets used a lot, in examples and generated code.
As usual mov has uses (the instruction moves bits, has no understanding of what it moves, so will not corrupt float values when sizing matches). Variants can move between general registers, memory and handle immediates,
MOV
32‐bit (doubleword, ‘single precision’). Other sizes if provoked
Out there I’ve also seen this SSE2 instruction, which is starting to delve into the madness. Works on XMM and memory locations, with a move,
CVTSS2SD
Single to double precision,
What do these instructions do?
In general, they load and unload values into the float registers. Later on, the instruction sets could run more and more maths operations, much the same as the general resisters. There are also some float/integer conversion operations.
Things to beware of,
Early versions couldn’t reorder numbers within a register
Only came in with SSE2
Immediates can’t be loaded
Which I can’t explain, but has remained true. To load a constant value to a floating point register, the value must be saved in memory (stack, setion, heap, etc.) then moved. Or load a general register then transfer to a float register.
Examples
These examples may not be the best way to do things—for all I know, movss/movsd may be more efficient or robust than movd/movq, or preferred in some situations etc. In defence, you won’t find this information, or with great difficulty, elsewhere on the web. I’ll update if I find out more.
You may also query the examples do not step into the world of SSE streaming—packing numbers into registers. But I give a start that works, yes? Maybe later.
All examples given with a proposed call to a print function, but that’s not needed. Still, that gives the examples a extra real code move to XMM0. Also, these are minimal snippets—I assume you’ll frame with code to compile, establish a main function, set stack and so forth.
How do I get floats into a register?
Section memory to xmm
.rodata
numFloat64ToPrint: dq 64.987
...
.text
; also works with movq
movsd xmm0, [numFloat64ToPrint]
call [printlnFloat64 wrt ..got]
Section .text to xmm
.text
numFloat64ToPrint:
dq 64.987
...
; also works with movq
movsd xmm0, [numFloat64ToPrint]
call [printlnFloat64 wrt ..got]
Immediate via. general register to xmm register
I would like to say like this, with the NASM macros. However, use of the __?float64?__() macro is dependant on knowing it must be loaded into a general register (from online commentary I thought it was only RAX but suspicious GCC code and testing proved otherwise). Otherwise, it acts as if it’s broken,
.text
mov eax, __?float32?__(32.987)
movd xmm0, eax
call [printFloat32 wrt ..got]
; 64-bit immediates must go to a general register before memory
mov rax, __?float64?__(64.987)
movq xmm0, rax
call [printFloat64 wrt ..got]
About 64‐bit immediates: x74 code gets fussy with 64‐bit immediates. There is no support for 64‐bit immediates into anything but a general register. Not only with floats but integers also. This is by report because the underlying addressing system, even in 64‐bit modes, is 32‐bit. What that has to do with immediates I don’t know (and forcing qword on effective addressing doesn’t work). Still can’t argue that GCC seems to output this kind of code, seemingly meaningless moves to RAX or RDX—maybe because it’s the only way possible.
Tidy the above with a macro package
Those macro calls are in standard NASM macro form. The ‘fp’ macro package, a builtin standard in NASM, can make them neater,
; make float immediates tidier
%use fp
.text
mov eax, float32(32.987)
movd xmm0, eax
call [printFloat32 wrt ..got]
; 64-bit immediates must go to a general register before memory
mov rax, float64(64.987)
movq xmm0, rax
call [printFloat64 wrt ..got]
Float immediates direct
Switching to another immediate representation, use a website utility or other code to create the hex for float literal/immediates (language compilers may well create assembly code that looks like this),
.text
mov eax, 0x4203F2B0
movd xmm0, eax
call [printFloat32 wrt ..got]
mov rax, 0x40503F2B020C49BA
movq xmm0, rax
call [printFloat64 wrt ..got]
Immediate via stack to xmm register
Move float immediates to stack memory first. Examples offset by one item so the use of offsetting can be seen,
mov dword [rbp - 4], __?float32?__(32.987)
movd xmm0, [rbp - 4]
call [printFloat32 wrt ..got]
; 64-bit immediates must go to a general register before memory
mov rax, __?float64?__(64.987)
mov qword [rbp - 8], rax
movq xmm0, [rbp - 8]
call [printFloat64 wrt ..got]
Immediate via heap memory to xmm register
Move float immediates to some place in malloced heap memory first.
extern malloc
extern free
...
.text
...
; alloc some malloc space, assign the space pointer to R15
mov rdi, 8
call [malloc wrt ..got]
; final code should have a malloc fail test here
mov qword r15, rax
...
mov dword [r15], __?float32?__(32.987)
movd xmm0, [r15]
call [printFloat32 wrt ..got]
; 64-bit immediates must go to a general register before memory
mov rax, __?float64?__(64.987)
mov qword [r15], rax
movq xmm0, [r15]
call [printFloat64 wrt ..got]
...
; free malloc space
mov rdi, r15
call [free wrt ..got]
So I can stash floats in memory or general registers?
You may wonder, why ask this question? I had an interest quickly, and maybe you have too. The reasons may be a few. For creation of constants (which will not load directly as immediates). Perhaps you are using registers a lot, and want a float value out of the way. Perhaps you are making calls, and need somewhere to put the float—in the System V call convention, all xmm registers are classed volatile. Maybe you have a patch of memory on hand.
You can do this. Assembly only moves bits. The thing to beware of is the banal statement ‘it’s only ones and zeros’. It’s not—layers of encoding turn ‘1s and 0s’ into something meaningful. So instructions that work on general registers are all integer‐intended, and will do little meaningful for a float value. But you can use memory to stash the values, no problem. In fact, GCC output of very simple float code will stash float numbers both in registers and stack memory. So don’t be concerned about this.
References
An online float converter,
NASM manual, Floating point constants, mostly about creating constants,
NASM manual, floating point macros,