Skip to content

Difference between compile-time and runtime float precision on 32-bit x86 without SSE can cause miscompilation leading to segfault #89885

Open
@beetrees

Description

@beetrees

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions