Fixture 173

float int conversions

C · 7 functions · 4 lanes · 15 of 28 function-lanes behave identically

4 of 4 lanes have a function that returns a different result after decompilation: clang-O2 (3/7), clang-O0 (4/7), gcc-O0 (4/7), gcc-O2 (4/7).

Conversions in both directions between integers and IEEE binary32/binary64.

Float-to-integer TRUNCATES TOWARD ZERO — -2.75 becomes -2, not -3 — and is UNDEFINED when the value does not fit the destination, so every function here range-checks before converting. The predicate is spelled !(in range) rather than out of range so that a NaN, which compares false against everything, takes the reject path instead of falling through it.

The reverse direction is exact for small magnitudes and ROUNDS for large ones: (float)INT32_MAX is 2147483648.0f, one above the value it came from. int32_round_trip_delta measures precisely that, which is why a recovery that drops the conversion, or performs it at the wrong width, cannot fake it.

tests/decompiler_fixtures/src/173_float_int_conversions.c source
#include <stdint.h>

/* Conversions in both directions between integers and IEEE binary32/binary64.
 *
 * Float-to-integer TRUNCATES TOWARD ZERO — `-2.75` becomes `-2`, not `-3` — and
 * is UNDEFINED when the value does not fit the destination, so every function
 * here range-checks before converting.  The predicate is spelled `!(in range)`
 * rather than `out of range` so that a NaN, which compares false against
 * everything, takes the reject path instead of falling through it.
 *
 * The reverse direction is exact for small magnitudes and ROUNDS for large
 * ones: `(float)INT32_MAX` is 2147483648.0f, one above the value it came from.
 * `int32_round_trip_delta` measures precisely that, which is why a recovery
 * that drops the conversion, or performs it at the wrong width, cannot fake it.
 */

#define FP173_INT32_LIMIT 2147483648.0f          /* 2^31, exact in binary32 */
#define FP173_UINT32_LIMIT 4294967296.0f         /* 2^32, exact in binary32 */
#define FP173_INT64_LIMIT 9223372036854775808.0  /* 2^63, exact in binary64 */
#define FP173_ROUND_LIMIT 1000000.0f

/* Shared by the two binary32 -> int32 paths so both reject identically. */
static int fp173_in_int32_range(float value) {
    return value >= -FP173_INT32_LIMIT && value < FP173_INT32_LIMIT;
}

__attribute__((noinline)) int32_t truncate_toward_zero(float value) {
    if (!fp173_in_int32_range(value)) {
        return 0;
    }
    return (int32_t)value;
}

__attribute__((noinline)) int64_t truncate_double_to_i64(double value) {
    if (!(value >= -FP173_INT64_LIMIT && value < FP173_INT64_LIMIT)) {
        return 0;
    }
    return (int64_t)value;
}

/* Unsigned truncation is a different instruction sequence from the signed one
 * (the destination has no sign bit to borrow), and a recovery that types the
 * result signed disagrees for everything above 2^31. */
__attribute__((noinline)) uint32_t truncate_to_unsigned(float value) {
    if (!(value >= 0.0f && value < FP173_UINT32_LIMIT)) {
        return 0u;
    }
    return (uint32_t)value;
}

/* Rounding half away from zero, built out of truncation.  Bounded well inside
 * the int32 range so the `+/- 0.5f` cannot push the value out of it. */
__attribute__((noinline)) int32_t round_half_away_from_zero(float value) {
    if (!(value >= -FP173_ROUND_LIMIT && value <= FP173_ROUND_LIMIT)) {
        return 0;
    }
    return (int32_t)(value >= 0.0f ? value + 0.5f : value - 0.5f);
}

__attribute__((noinline)) float widen_int_to_float(int32_t value) {
    return (float)value;
}

__attribute__((noinline)) double widen_long_to_double(int64_t value) {
    return (double)value;
}

/* How far one int32 -> binary32 -> int32 round trip moved the value.  The
 * subtraction is performed at 64 bits: the two operands are within 128 of each
 * other, but computing the difference in `int32_t` would still be reasoning
 * about overflow that does not need to exist. */
__attribute__((noinline)) int32_t int32_round_trip_delta(int32_t value) {
    float widened = (float)value;
    if (!fp173_in_int32_range(widened)) {
        return 0;
    }
    return (int32_t)((int64_t)(int32_t)widened - (int64_t)value);
}

Recovered C

Generated by glaurung decompile --style decbench at b47f6b43. baseline.json records the result after recompiling the C and calling it beside the original with seeded inputs.

clang -O2

3/7
int32_round_trip_delta fail 7 lines
// glaurung: int32_round_trip_delta @ 0x11d0
int32_t int32_round_trip_delta(int32_t arg0) {
    float widened;
    long ret;
    ret = (unsigned long)((unsigned int)(((int)((float)((int)(arg0))) - arg0)));
    return ((((0x4f000000 < ret) | (ret != ret)) | ((0x4f000000 == ret) | (ret != ret))) ? 0 : ret);
}
round_half_away_from_zero fail 7 lines
// glaurung: round_half_away_from_zero @ 0x1170
int32_t round_half_away_from_zero(float arg0) {
    long ret;
    ret = (((arg0 < 0) | (arg0 != arg0)) == 0);
    ret = (((0x49742400 < arg0) | (arg0 != arg0)) ? 0 : (int)((*(float *)((0x2028 + (ret * 4))) + arg0)));
    return (((arg0 < 0xc9742400) | (arg0 != arg0)) ? 0 : ret);
}
truncate_double_to_i64 fail 8 lines
// glaurung: truncate_double_to_i64 @ 0x1120
static unsigned char glaurung_global_2020[16] __attribute__((aligned(16)));
int64_t truncate_double_to_i64(double arg0) {
    extern unsigned char glaurung_global_2020[16];
    long t14;
    t14 = *(long *)(&glaurung_global_2020[0]);
    return (((arg0 < t14) | ((arg0 != arg0) | (t14 != t14))) ? 0 : ((((0x43e0000000000000 < arg0) | (arg0 != arg0)) | ((0x43e0000000000000 == arg0) | (arg0 != arg0))) ? 0 : (long long)(arg0)));
}
truncate_to_unsigned pass 4 lines
// glaurung: truncate_to_unsigned @ 0x1150
uint32_t truncate_to_unsigned(float arg0) {
    return (((arg0 < 0) | (arg0 != arg0)) ? 0 : ((((0x4f800000 < arg0) | (arg0 != arg0)) | ((0x4f800000 == arg0) | (arg0 != arg0))) ? 0 : (long long)(arg0)));
}
truncate_toward_zero fail 4 lines
// glaurung: truncate_toward_zero @ 0x1100
int32_t truncate_toward_zero(float arg0) {
    return (((arg0 < 0xcf000000) | (arg0 != arg0)) ? 0 : ((((0x4f000000 < arg0) | (arg0 != arg0)) | ((0x4f000000 == arg0) | (arg0 != arg0))) ? 0 : (int)(arg0)));
}
widen_int_to_float pass 4 lines
// glaurung: widen_int_to_float @ 0x11b0
float widen_int_to_float(int32_t arg0) {
    return (float)((int)(arg0));
}
widen_long_to_double pass 4 lines
// glaurung: widen_long_to_double @ 0x11c0
double widen_long_to_double(int64_t arg0) {
    return (double)((long long)(arg0));
}

clang -O0

4/7
int32_round_trip_delta pass 14 lines
// glaurung: int32_round_trip_delta @ 0x12d0
int32_t int32_round_trip_delta(int32_t arg0) {
    extern int fp173_in_int32_range(float);
    float widened;
    long var7;
    // x86-64 prologue: save rbp, frame 16 bytes
    widened = (float)((int)(arg0));
    var7 = ((long (*)(float))fp173_in_int32_range)(widened);
    if (((unsigned long)((unsigned int)(var7)) != 0)) {
        return (unsigned int)(((long)((int)((int)(widened))) - (long)(arg0)));
    } else {
        return 0;
    }
}
round_half_away_from_zero fail 18 lines
// glaurung: round_half_away_from_zero @ 0x1220
int32_t round_half_away_from_zero(float arg0) {
    int local_4;
    float local_c;
    // x86-64 prologue: save rbp
    if (((arg0 < 0xc9742400) | (arg0 != arg0))) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    if ((((0x49742400 < arg0) | (arg0 != arg0)) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    local_c = (((arg0 < 0) | (arg0 != arg0)) ? (arg0 - ((union { unsigned int bits; float value; }){ .bits = (unsigned int)(0x3f000000) }).value) : (((union { unsigned int bits; float value; }){ .bits = (unsigned int)(0x3f000000) }).value + arg0));
    local_4 = (int)(local_c);
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}
truncate_double_to_i64 fail 20 lines
// glaurung: truncate_double_to_i64 @ 0x1180
static unsigned char glaurung_global_2018[16] __attribute__((aligned(16)));
int64_t truncate_double_to_i64(double arg0) {
    extern unsigned char glaurung_global_2018[16];
    long local_8;
    long var5;
    // x86-64 prologue: save rbp
    var5 = *(long *)(&glaurung_global_2018[0]);
    if (((arg0 < var5) | ((arg0 != arg0) | (var5 != var5)))) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    if (((((0x43e0000000000000 < arg0) | (arg0 != arg0)) | ((0x43e0000000000000 == arg0) | (arg0 != arg0))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    local_8 = (long long)(arg0);
    // x86-64 epilogue: restore rbp
    return local_8;
}
truncate_to_unsigned pass 16 lines
// glaurung: truncate_to_unsigned @ 0x11d0
uint32_t truncate_to_unsigned(float arg0) {
    int local_4;
    // x86-64 prologue: save rbp
    if (((arg0 < 0) | (arg0 != arg0))) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    if (((((0x4f800000 < arg0) | (arg0 != arg0)) | ((0x4f800000 == arg0) | (arg0 != arg0))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    local_4 = (long long)(arg0);
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}
truncate_toward_zero fail 12 lines
// glaurung: truncate_toward_zero @ 0x1100
int32_t truncate_toward_zero(float arg0) {
    extern int fp173_in_int32_range(float);
    long var5;
    // x86-64 prologue: save rbp, frame 16 bytes
    var5 = ((long (*)(float))fp173_in_int32_range)(arg0);
    if (((unsigned long)((unsigned int)(var5)) != 0)) {
        return (unsigned int)((int)(arg0));
    } else {
        return 0;
    }
}
widen_int_to_float pass 6 lines
// glaurung: widen_int_to_float @ 0x12b0
float widen_int_to_float(int32_t arg0) {
    // x86-64 prologue: save rbp
    // x86-64 epilogue: restore rbp
    return (float)((int)(arg0));
}
widen_long_to_double pass 6 lines
// glaurung: widen_long_to_double @ 0x12c0
double widen_long_to_double(int64_t arg0) {
    // x86-64 prologue: save rbp
    // x86-64 epilogue: restore rbp
    return (double)((long long)(arg0));
}

gcc -O0

4/7
int32_round_trip_delta pass 14 lines
// glaurung: int32_round_trip_delta @ 0x12b1
int32_t int32_round_trip_delta(int32_t arg0) {
    extern int fp173_in_int32_range(float);
    float widened;
    long var12;
    // x86-64 prologue: save rbp, frame 24 bytes
    widened = (float)((int)(arg0));
    var12 = ((long (*)(long))fp173_in_int32_range)((unsigned long)((unsigned int)(((union { unsigned int bits; float value; }){ .value = widened }).bits)));
    if (((unsigned long)((unsigned int)(var12)) != 0)) {
        return (unsigned int)(((int)(widened) - (unsigned long)((unsigned int)(arg0))));
    } else {
        return 0;
    }
}
round_half_away_from_zero fail 17 lines
// glaurung: round_half_away_from_zero @ 0x11fd
int32_t round_half_away_from_zero(float arg0) {
    // x86-64 prologue: save rbp
    if (((unsigned long)((unsigned char)(((unsigned long)((unsigned int)(((((arg0 < 0xc9742400) | (arg0 != arg0)) == 0) ^ 1))) & 255))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    if (((unsigned long)((unsigned char)(((unsigned long)((unsigned int)(((((0x49742400 < arg0) | (arg0 != arg0)) == 0) ^ 1))) & 255))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    if (((arg0 < 0) | (arg0 != arg0))) {
        return (int)((arg0 - ((union { unsigned int bits; float value; }){ .bits = (unsigned int)(0x3f000000) }).value));
    } else {
        return (int)((((union { unsigned int bits; float value; }){ .bits = (unsigned int)(0x3f000000) }).value + arg0));
    }
}
truncate_double_to_i64 fail 18 lines
// glaurung: truncate_double_to_i64 @ 0x1163
static unsigned char glaurung_global_2008[16] __attribute__((aligned(16)));
int64_t truncate_double_to_i64(double arg0) {
    extern unsigned char glaurung_global_2008[16];
    long t14;
    // x86-64 prologue: save rbp
    t14 = *(long *)(&glaurung_global_2008[0]);
    if (((unsigned long)((unsigned char)(((unsigned long)((unsigned int)(((((arg0 < t14) | ((arg0 != arg0) | (t14 != t14))) == 0) ^ 1))) & 255))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    if (((unsigned long)((unsigned char)(((unsigned long)((unsigned int)((((((0x43e0000000000000 < arg0) | (arg0 != arg0)) | ((0x43e0000000000000 == arg0) | (arg0 != arg0))) == 0) ^ 1))) & 255))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    // x86-64 epilogue: restore rbp
    return (long long)(arg0);
}
truncate_to_unsigned pass 14 lines
// glaurung: truncate_to_unsigned @ 0x11b1
uint32_t truncate_to_unsigned(float arg0) {
    // x86-64 prologue: save rbp
    if (((unsigned long)((unsigned char)(((unsigned long)((unsigned int)(((((arg0 < 0) | (arg0 != arg0)) == 0) ^ 1))) & 255))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    if (((unsigned long)((unsigned char)(((unsigned long)((unsigned int)((((((0x4f800000 < arg0) | (arg0 != arg0)) | ((0x4f800000 == arg0) | (arg0 != arg0))) == 0) ^ 1))) & 255))) != 0)) {
        // x86-64 epilogue: restore rbp
        return 0;
    }
    // x86-64 epilogue: restore rbp
    return (long long)(arg0);
}
truncate_toward_zero fail 12 lines
// glaurung: truncate_toward_zero @ 0x1130
int32_t truncate_toward_zero(float arg0) {
    extern int fp173_in_int32_range(float);
    long var5;
    // x86-64 prologue: save rbp, frame 8 bytes
    var5 = ((long (*)(float))fp173_in_int32_range)(arg0);
    if (((unsigned long)((unsigned int)(var5)) != 0)) {
        return (int)(arg0);
    } else {
        return 0;
    }
}
widen_int_to_float pass 6 lines
// glaurung: widen_int_to_float @ 0x1279
float widen_int_to_float(int32_t arg0) {
    // x86-64 prologue: save rbp
    // x86-64 epilogue: restore rbp
    return (float)((int)(arg0));
}
widen_long_to_double pass 6 lines
// glaurung: widen_long_to_double @ 0x128f
double widen_long_to_double(int64_t arg0) {
    // x86-64 prologue: save rbp
    // x86-64 epilogue: restore rbp
    return (double)((long long)(arg0));
}

gcc -O2

4/7
int32_round_trip_delta pass 13 lines
// glaurung: int32_round_trip_delta @ 0x1200
int32_t int32_round_trip_delta(int32_t arg0) {
    float widened;
    float var5;
    var5 = (float)((int)(arg0));
    if (((var5 < 0xcf000000) | (var5 != var5))) {
        return 0;
    }
    if (((((0x4f000000 < var5) | (var5 != var5)) | ((0x4f000000 == var5) | (var5 != var5))) == 0)) {
        return (unsigned int)(((int)(var5) - arg0));
    }
    return 0;
}
round_half_away_from_zero fail 13 lines
// glaurung: round_half_away_from_zero @ 0x1190
int32_t round_half_away_from_zero(float arg0) {
    if (((arg0 < 0xc9742400) | (arg0 != arg0))) {
        return 0;
    }
    if (((0x49742400 < arg0) | (arg0 != arg0))) {
        return 0;
    }
    if (((arg0 < 0) | (arg0 != arg0))) {
        return (int)((arg0 - ((union { unsigned int bits; float value; }){ .bits = (unsigned int)(0x3f000000) }).value));
    }
    return (int)((arg0 + ((union { unsigned int bits; float value; }){ .bits = (unsigned int)(0x3f000000) }).value));
}
truncate_double_to_i64 fail 14 lines
// glaurung: truncate_double_to_i64 @ 0x1130
static unsigned char glaurung_global_2018[16] __attribute__((aligned(16)));
int64_t truncate_double_to_i64(double arg0) {
    extern unsigned char glaurung_global_2018[16];
    long t14;
    t14 = *(long *)(&glaurung_global_2018[0]);
    if (((arg0 < t14) | ((arg0 != arg0) | (t14 != t14)))) {
        return 0;
    }
    if (((((0x43e0000000000000 < arg0) | (arg0 != arg0)) | ((0x43e0000000000000 == arg0) | (arg0 != arg0))) == 0)) {
        return (long long)(arg0);
    }
    return 0;
}
truncate_to_unsigned pass 10 lines
// glaurung: truncate_to_unsigned @ 0x1160
uint32_t truncate_to_unsigned(float arg0) {
    if (((arg0 < 0) | (arg0 != arg0))) {
        return 0;
    }
    if (((((0x4f800000 < arg0) | (arg0 != arg0)) | ((0x4f800000 == arg0) | (arg0 != arg0))) == 0)) {
        return (long long)(arg0);
    }
    return 0;
}
truncate_toward_zero fail 10 lines
// glaurung: truncate_toward_zero @ 0x1100
int32_t truncate_toward_zero(float arg0) {
    if (((arg0 < 0xcf000000) | (arg0 != arg0))) {
        return 0;
    }
    if (((((0x4f000000 < arg0) | (arg0 != arg0)) | ((0x4f000000 == arg0) | (arg0 != arg0))) == 0)) {
        return (int)(arg0);
    }
    return 0;
}
widen_int_to_float pass 4 lines
// glaurung: widen_int_to_float @ 0x11e0
float widen_int_to_float(int32_t arg0) {
    return (float)((int)(arg0));
}
widen_long_to_double pass 4 lines
// glaurung: widen_long_to_double @ 0x11f0
double widen_long_to_double(int64_t arg0) {
    return (double)((long long)(arg0));
}

← 213 fixtures