Here is a C program that takes a 64 bit integer and divides it by the maximum number a 64 bit integer can hold, to get a double in [0, 1] (technically undefined behaviour as conversions are implementation defined and very large 64-bit integers cannot be held by a double). Compiling
#include <stdint.h>
double convert(uint64_t num)
{
return (double) num / 0xFFFFFFFFFFFFFFFF;
}
with gcc 10.3.0 for the x86_64 architecture with either the -O3 or -O2 flag gives the following output, and I don't understand it. So I would appreciate it if someone could walk me through it.
convert:
test rdi, rdi
js .L2
pxor xmm0, xmm0
cvtsi2sd xmm0, rdi
mulsd xmm0, QWORD PTR .LC0[rip]
ret
.L2:
mov rax, rdi
and edi, 1
pxor xmm0, xmm0
shr rax
or rax, rdi
cvtsi2sd xmm0, rax
addsd xmm0, xmm0
mulsd xmm0, QWORD PTR .LC0[rip]
ret
.LC0:
.long 0
.long 1005584384
Here are some specific things I do not understand, but now that I have written it out, it is basically everything except for the cvtsi2sd instructions and setting the xmm0 register to 0.
The first thing I find strange is the .L2 label. As the contents of rdi is equal to itself, the first line will always set the sign flag to 1, meaning the jump of js will always occur right? So then why not remove the first two lines and put the contents of .L2 there?
Then I also don't understand why we put 1 in the edi register, and I can't find anywhere what the shr operations does when there is no second argument.
Then we have some xmulsd xmm0, QWORD PTR .LC0[rip]. I don't understand why we use the instruction pointer, and 10005584384 looks like a magic number to me.