Unordered Floating Noint Numbers (and Infinite ones too)

I hope I have the right category.

In trying to implement the fdim() function as

float ramp(const float x, const float y)
{
    return x != x ? x : y != y ? y : x > y ? x - y : (float) 0;
}

I was pleasantly surprised away by how optimally the compiler handled the unordered comparison.

However, the protection in front of the subtraction later on in the statement is basically ignored, which means in the case of both arguments being INFINITY, the routine will raise an unwanted INVALID exception even though it gives the correct floating point result of 0.0 (as if the subtraction had been protected). Very odd.

Clang 11.

The optimizer is being a bit too smart.

The default mode in Clang doesn’t guarantee the exception behavior. To make it, you need to use strict FP options/pragma: Compiler Explorer

It’s also safe to use builtins in your scenario: Compiler Explorer

1 Like

But it is worse than that. How can the compiler evaluate a subtraction before the boolean expression to which it is subject. To make it simpler

float t = x > y ? x - y : (float) 0;

The assembler shows that the expression x - y is evaluated BEFORE x > y. How can the compiler justify doing that?

I ran the earlier code with

-ffp-exception-behavior=strict

and that solved the exception behaviour. Bt it messed with expressions like x != x to the point of needing 4 instructions to do what should have been possible with 2 and it even decided to be overly strict with

(float) 0

and do the conversion at run-time rather than compiler. It seems like massive over-reach that an option which is concerned with exceptions thinks that a coercion of a zero of any flavour has anything to do with exceptions. Is there an option to force the evaluation of an expression known at compile time to be done at compile time?

Thanks.

Thanks for the suggestion about builtins. However, my underlying problem is not fdim itself. My actual problem behaves in the same way as does my recoding of fdim. I had hoped that my use of fdim as the example case meant that the logic needed no explanation and kept things really simple.

Compiler has freedom to do any equivalent transformations. Since compiler assumes exceptions are ignoed, moving x - y ahead x > y is a correct equivalent transformation. The underlying optimization evolving branch elimination and instruction scheduling. Nothing wrong during these steps.
I admit there’re space to improve the strict FP optimizations. There are some discussion about that, but no progress as far as I know.

The assembler shows that the expression x - y is evaluated BEFORE x > y. How can the compiler justify doing that?

If an expression has no side effects, then it can be speculatively executed or reordered with any other instruction. The default floating point model (fp-model=precise) considers that the MXCSR register is set to its default values (namely, default rounding mode and exceptions are suppressed), which allow floating-point instructions to be treated as having no side effects.

It seems like massive over-reach that an option which is concerned with exceptions thinks that a coercion of a zero of any flavour has anything to do with exceptions. Is there an option to force the evaluation of an expression known at compile time to be done at compile time?

Yes, there is one way to force a constant floating-point expression to be evaluated at compile time in strict floating-point execution mode: assign the expression to a static variable. C requires that any constant floating-point expression evaluated in strict execution mode be evaluated at runtime (see section F.8.4 of the C specification).

In theory, the compiler can optimize the constant integer value 0 to floating-point +0.0, since the value is known to be exact, rounding mode doesn’t impact the result, and no exceptions can be thrown. However, LLVM hasn’t yet attempted to do any optimizations of strict floating-point arithmetic.

1 Like

It is not obvious in the documentation about the compiler options that the default fp-model that the compiler assumes that exceptions are ignored. Yes,in the same place there is a table which mentioned this but, it is not obvious. Besides, the executable it produces raises exceptions which seems inconsistent. If we teach students that they can use an if statement to protect execution and then the optimizer (by default and without an explicit request) totally ignores that, something really is not right. The word precise for that umbrella term in the fp-model is a misnomer. I will experiment with the individual options and see if there is a mix which is closer to what I mean The umbrella term strict is strict C standard and seems far too aggressive for what I need. I just want something which is closer to matching the default IEEE 754 behaviour

Is the default fp-model really architecture dependent that it assumes things about an X86-64 MXCSR register? That seems odd. But I do agree that there is a table in the documentation that says that the default exception behaviour is to ignore them, although that table needs to be cross referenced where the fp-model option is explained.

I do know that I can define a variable as static as you say. However, my experience in the past has been that I could not then use that same static variable in the expression defining other static variables. And past versions of various compilers often lost the plot when they encountered static variables. I use constructs like sat

((REAL) 0)
((REAL) 0.5)

as a way to form exact (exception-less) generic floating point constants where REAL is predefined by me to be either float or double or long double (or _FloatNN) at compile time so I really do not want F.8.4 semantics.

The compiler needs an option than forbids out of scope speculative execution, It also needs one that mandates that it respect parentheses. That would solve a plethora of problems with speculative execution. The compiler can still free to complain that it really wants to be more aggressive with its speculation to help the programmer but it should not do it behind his/her back. If a programmer goes to the trouble of using (explicit or implicit) parentheses or braces, such things should be respected. My 2c.

I realise that Clang’s default is -fno-associative-math and that it further may protect-paren. That statement was a bit too general but was meant in the more general context. Maybe extending these to also include braces will solve the problem of overly aggressive predictive behaviour. Just a thought.

By the way, how do I disable trapping math but still get the compiler to respect exceptions which is what I believe is the default in 2019 IEEE 754. I assume that trapping math wants to save things into memory more aggressively than no trapping math.

Is the default fp-model really architecture dependent that it assumes things about an X86-64 MXCSR register?

No. I just use MXCSR as a shorthand for the entire floating-point environment dependency, because it’s shorter than enumerating all of those components (and it includes the reasonably portable concepts of FTZ and DAZ mode bits, which aren’t a feature of IEEE 754 or any of the major languages).

By the way, how do I disable trapping math but still get the compiler to respect exceptions which is what I believe is the default in 2019 IEEE 754. I assume that trapping math wants to save things into memory more aggressively than no trapping math.

Trapping math shouldn’t have an effect on saving things into memory. The only thing it should be telling is informing the compiler that all floating-point operations have a read/write dependency on the MXCSR (see above note). If you disable trapping math, you are telling the compiler that it can assume that all floating-point operations have no dependencies on MXCSR, and therefore that you do not want strict IEEE 754 semantics.

There is ongoing work to optimize strict floating-point, but very few people are working on it. By my count there are as many as three people, but most days less than that. I got the EarlyCSE changes into the tree this year and now I’m working on IPSCCP. InstSimplify gets changes once in a while. Watch for commits with [StrictFP] in the title.

1 Like