9

Why is AsDouble1 much more straightforward than AsDouble0?

// AsDouble0(unsigned long):                          # @AsDouble0(unsigned long)
//         movq    xmm1, rdi
//         punpckldq       xmm1, xmmword ptr [rip + .LCPI0_0] # xmm1 = xmm1[0],mem[0],xmm1[1],mem[1]
//         subpd   xmm1, xmmword ptr [rip + .LCPI0_1]
//         movapd  xmm0, xmm1
//         unpckhpd        xmm0, xmm1                      # xmm0 = xmm0[1],xmm1[1]
//         addsd   xmm0, xmm1
//         addsd   xmm0, xmm0
//         ret
double AsDouble0(uint64_t x) { return x * 2.0; }

// AsDouble1(unsigned long):                          # @AsDouble1(unsigned long)
//         shr     rdi
//         cvtsi2sd        xmm0, rdi
//         addsd   xmm0, xmm0
//         ret
double AsDouble1(uint64_t x) { return (x >> 1) * 2.0; }

Code available at: https://godbolt.org/z/dKc6Pe6M1

Lundin
  • 195,001
  • 40
  • 254
  • 396
Etienne M
  • 604
  • 3
  • 11
  • 1
    FYI, you don't need to include a `main()` on Godbolt if you just want to look at the asm. Also semi-related [Are there unsigned equivalents of the x87 FILD and SSE CVTSI2SD instructions?](https://stackoverflow.com/a/69233028) except that's the reverse direction. [How to efficiently perform double/int64 conversions with SSE/AVX?](https://stackoverflow.com/q/41144668) covers packed conversions. – Peter Cordes Jul 05 '22 at 08:23
  • [What's So Difficult About \`uint64\_t\`? (Conversion Assembly From \`float\`)](https://stackoverflow.com/q/32685576) is specifically about compiler-generated code for `(uint64_t)float`. – Peter Cordes Jul 05 '22 at 08:28
  • @PeterCordes re. main(), is an artifact of my other tests – Etienne M Jul 05 '22 at 09:18

2 Answers2

8

x86 has an instruction to convert between signed integers and floats. Unsigned integer conversion is (I think) supported by AVX512, which most compilers don't assume by default. If you shift right a uint64_t once, the sign bit is gone, so you can interpret it as a signed integer and have the same result.

xiver77
  • 2,162
  • 1
  • 2
  • 12
  • 2
    Correct, [`vcvtsd2usi`](https://www.felixcloutier.com/x86/vcvtsd2usi) is new with AVX-512, along with packed conversions to unsigned 32 and 64-bit integers. (Packed conversion to 64-bit was also new). – Peter Cordes Jul 05 '22 at 08:21
  • 2
    @Peter Wouldn't it be `vcvtusi2sd` to convert *from* an unsinged int to a double? But I guess we all know what you mean. :-) – Adrian Mole Jul 05 '22 at 09:48
  • 1
    @AdrianMole: Oops, yes, got the direction backwards, including in my comments under the question. – Peter Cordes Jul 05 '22 at 09:51
6

The cvtsi2sd instruction takes, as its source operand, a signed integer (either 32- or 64-bits wide). However, your functions take unsigned arguments.

Thus, in the first case, the compiler cannot directly use the cvtsi2sd instruction, because the value in the given argument may not be representable as a same-size signed integer – so it generates code that does the conversion to double the "long way" (but safely).

However, in your second function, the initial right shift by one bit guarantees that the sign bit will be clear; thus, the resultant value will be identical, whether it is interpreted as signed or unsigned … so the compiler can safely use that (modified) value as the source for the cvtsi2sd operation.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83