To do FP optimization correctly the compiler needs quite detailed knowledge
of the FP model, that goes beyond just "safe/unsafe".
For example as Chris pointed out, x + 0.0 -> x is not a valid rewrite if
the FP model has signed zeroes (i.e. zeroes whose sign is observable by
well-defined operations in the language). It is valid in Standard C because
the sign of zero is not observable in Standard C. On the other hand
your example needs reassociation, to change 1 + x + 1 to x + (1 + 1),
and reassociation generally isn't valid even in Standard C, because it
changes the numeric value of the result.
Other examples: to optimize away a voided computation you need to know
"does the FP model trap or set cumulative exception bits". To rewrite
x + y as y + x you need to know "does the FP model trap". There are other
optimizations whose validity depends on whether the rounding mode can be
changed at runtime.
Overall the FP model needs to be able to answer at least these queries:
- does it trap (i.e. invoke user-defined trap handler with the original
operands in the original order)
- does it set cumulative exception bits
- can the rounding mode be changed at runtime
- does it have Inf and NaN
- are denormals flushed to zero (and if so how)
Some of the flags tend to imply others, e.g. you don't in practice have the
ability to trap without the ability to set cumulative exception bits.
To support Standard C/C++ you only need a minimal model; to claim full
IEEE support you need pretty much everything. The "Java numerics" model
is somewhere in between.
All of these are within the space of "safe" models, the difference is the
set of values and observable events.
As well as this you have a concept of "unsafe" (or "fast") modes which aren't
really models at all - they are modes where the compiler doesn't implement a
well-defined predictable model of FP arithmetic and where numeric results can
vary between different optimization levels, different programming styles (e.g.
inline functions vs. macros) and from one day's build of the compiler to the next.
And within the space of fast modes there are some choices for varying the
heuristics - e.g. replacing division-by-constant by multiply-by-reciprocal
is generally numerically "safer" than reassociation.
At the very least (I believe) a compiler for serious FP work needs a
fast/unsafe mode, a minimal Standard C mode, and an IEEE mode. Attaching
a safe/unsafe bit to each FP operation is only sufficient if there is only
one safe model in effect at any time. Even then, exactly what the safe
model is, ought to be configurable.