Difference between compile-time and runtime float precision on 32-bit x86 without SSE can cause miscompilation leading to segfault · Issue #89885 · llvm/llvm-project (original) (raw)

The following program (based on the Rust version from here, which is based on @comex's example from a different issue; they gave an explanation of how they found it here)

#include <stdio.h> #include <stddef.h>

void print_vals(float x, size_t i, int vals_i) { printf("x=%f i=%zu vals[i]=%i\n", x, i, vals_i); }

void out_of_bounds(float x, size_t i) { printf("x=%f i=%zu vals[i]=out of bounds\n", x, i); }

void evil(int vals[300]) { float x = 0.0; size_t i = 0;

while (x != 90.0) {
    // At compile time, LLVM will brute-force the loop and discover that it
    // terminates after 90 iterations, with `i` always less than 300. This bounds
    // check therefore gets optimised out.
    if (i < 300) {
        print_vals(x, i, vals[i]);
    } else {
        out_of_bounds(x, i);
    }
    x += 1.0;
    // This addition is too small to have any effect on the value of `x` with
    // regular `float` precision, which is what LLVM uses at compile-time.
    // However, with the extra precision offered by x87 floating point registers,
    // the value of `x` will change slightly, meaning it will never hit exactly
    // 90.0 and therefore the loop will never terminate at runtime.
    x += 2.9802322387695312e-08;
    i += 2;
}

}

int main() { int vals[300]; for (int i = 0; i < 300; i++) { vals[i] = i; } evil(vals); }

compiled with clang -O3 --target=i686-unknown-linux-gnu -mno-sse code.c will segfault at runtime. This is due to LLVM evaluating floats at standard float precision at compile-time, but outputting machine code that uses x87 extended precision at runtime. Specifically, llvm/lib/Analysis/ScalarEvolution.cpp will brute force the loop using compile-time semantics, causing the bounds check to be optimised out; however the extra precision of x87 extended precision floats will mean that the loop termination condition is never hit at runtime.

The LangRef appears to imply that the compile-time semantics are correct, so this is a bug in the x86 backend.

Related to #44218.