What's So Difficult About `uint64_t`? (Conversion Assembly From `float`)
Asked Answered
K

1

11

I am in a situation where I need to compute something like size_t s=(size_t)floorf(f);. That is, the argument is a float, but it has an integer value (assume floorf(f) is small enough to be represented exactly). While optimizing this, I discovered something interesting.

Here are some conversions from float to integers (GCC 5.2.0 -O3). For clarity, the conversion given is the return value of a test function.

Here's int32_t x=(int32_t)f:

    cvttss2si   eax, xmm0
    ret

Here's uint32_t x=(uint32_t)f:

    cvttss2si   rax, xmm0
    ret

Here's int64_t x=(int64_t)f:

    cvttss2si   rax, xmm0
    ret

Last, here's uint64_t x=(uint64_t)f;:

    ucomiss xmm0, DWORD PTR .LC2[rip]
    jnb .L4
    cvttss2si   rax, xmm0
    ret
.L4:
    subss   xmm0, DWORD PTR .LC2[rip]
    movabs  rdx, -9223372036854775808
    cvttss2si   rax, xmm0
    xor rax, rdx
    ret

.LC2:
    .long   1593835520

This last one is much more complex than the others. Moreover, Clang and MSVC behave similarly. For your convenience, I've translated it into pseudo-C:

float lc2 = (float)(/* 2^63 - 1 */);
if (f<lc2) {
    return (uint64_t)f;
} else {
    f -= lc2;
    uint64_t temp = (uint64_t)f;
    temp ^= /* 2^63 */; //Toggle highest bit
    return temp;
}

This looks like it is trying to compute the first overflow mod 64 correctly. That seems kindof bogus, since the documentation for cvttss2si tells me that if an overflow happens (at 2^32, not 2^64), "the indefinite integer value (80000000H) is returned".

My questions:

  1. What is this really doing, and why?
  2. Why wasn't something similar done for the other integer types?
  3. How can I change the conversion so as to produce similar code (only output lines 3 and 4) (again, assume that the value is exactly representable)?
Kerakerala answered 21/9, 2015 at 0:2 Comment(1)
This blog post and this comment in particular are related to your question: blog.frama-c.com/index.php?post/2013/10/09/…Dictatorial
I
12

Since cvttss2si does a signed conversion, it will consider the numbers in the interval [2^63, 2^64) to be out of range, when in fact they are in range for unsigned. Hence, this case is detected and mapped to the low half in the float, and a correction is applied after conversion.

As for the other cases, notice that the uint32_t conversion still uses a 64 bit destination which will work for the full range of the uint32_t and further truncation is implicit by using the low 32 bits of the result according to calling convention.

In terms of avoiding the extra code, it depends on whether your input may fall into the above mentioned range. If it can, there is no way around it. Otherwise, a double cast first to signed then to unsigned could work, ie. (uint64_t)(int64_t)f.

Interoffice answered 21/9, 2015 at 0:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.