Converting unsigned 64-bit integer to double gives strange assembly code

409 Views Asked by At

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.

0

There are 0 best solutions below