fast-math, clang-6+ and floating point exceptions

I'm working on scientific code, that explicitly catches and crashes
on floating point exceptions (I've seen several such projects in the
past in the scientific domain do this). *Everything* is compiled with
-ffast-math and -O3. However, we've got this code in a module:

    float w = std::log(x);
    if (x > 3) {
        w -= std::log(w);
    }

Godbolt: https://godbolt.org/z/yvGqjl

Basically, if I'm reading the assembly output properly, it looks like
clang > 6 optimizes this into assuming that the second log is almost
always run (not unreasonably), always calls log twice, and eliminates
the branch. However, in our code, the second log causes an FPE which
jumps to a crash handler.

My questions are:
- Am I correct in my interpretation of what's going on here?
- Is this a reasonable thing for clang to do - it's not implied by any
of the cases on
https://clang.llvm.org/docs/UsersManual.html#cmdoption-ffast-math ,
although to be fair that does say it's a non-inclusive list. GCC
_doesn't_ seem to make this transform, though obviously that doesn't
mean that it's a bad one
- Can I prevent this from happening, even with -ffast-math enabled?
- Is this sort of configuration sensible or insane?

Thanks,

Nick

From: cfe-dev [mailto:cfe-dev-bounces@lists.llvm.org] On Behalf Of
Nicholas Devenish via cfe-dev
Sent: Tuesday, July 30, 2019 6:28 PM
To: cfe-dev@lists.llvm.org
Subject: [cfe-dev] fast-math, clang-6+ and floating point exceptions

I'm working on scientific code, that explicitly catches and crashes
on floating point exceptions (I've seen several such projects in the
past in the scientific domain do this). *Everything* is compiled with
-ffast-math and -O3. However, we've got this code in a module:

    float w = std::log(x);
    if (x > 3) {
        w -= std::log(w);
    }

Godbolt: https://godbolt.org/z/yvGqjl

Basically, if I'm reading the assembly output properly, it looks like
clang > 6 optimizes this into assuming that the second log is almost
always run (not unreasonably), always calls log twice, and eliminates
the branch. However, in our code, the second log causes an FPE which
jumps to a crash handler.

My questions are:
- Am I correct in my interpretation of what's going on here?
- Is this a reasonable thing for clang to do - it's not implied by any
of the cases on
https://clang.llvm.org/docs/UsersManual.html#cmdoption-ffast-math ,
although to be fair that does say it's a non-inclusive list. GCC
_doesn't_ seem to make this transform, though obviously that doesn't
mean that it's a bad one

I'm far from an expert, but we've run into this kind of thing, and
basically (a) LLVM assumes there are no exceptions, (b) it is willing
to speculate FP operations sometimes; this combo can go boom.

- Can I prevent this from happening, even with -ffast-math enabled?

We do the equivalent of `-mllvm -speculate-one-expensive-inst=false`
and it helps.

- Is this sort of configuration sensible or insane?

In the golden land of perfect understanding of FP exceptions, it would
be controllable in a more complete and principled way. LLVM is a long
way off from that.
--paulr

> - Can I prevent this from happening, even with -ffast-math enabled?

We do the equivalent of `-mllvm -speculate-one-expensive-inst=false`
and it helps.

Someone has put a noop redundant function call in the middle and that
tricks the optimizer into not doing this transform, but it's not
exactly an elegant or obvious solution.

I was also surprised to see that /both directions/ of __builtin_expect
caused it to not apply the transform - even though I'd naively expect
the "True" case to result in exactly the same optimization
assumptions.

> - Is this sort of configuration sensible or insane?

In the golden land of perfect understanding of FP exceptions, it would
be controllable in a more complete and principled way. LLVM is a long
way off from that.

Was more asking about, this approach of catching/crashing on all FPE's
- I've seen it before but not completely understood (or believed) the
reasons why.

Nick

- Can I prevent this from happening, even with -ffast-math enabled?

We do the equivalent of `-mllvm -speculate-one-expensive-inst=false`
and it helps.

Someone has put a noop redundant function call in the middle and that
tricks the optimizer into not doing this transform, but it's not
exactly an elegant or obvious solution.

I was also surprised to see that /both directions/ of __builtin_expect
caused it to not apply the transform - even though I'd naively expect
the "True" case to result in exactly the same optimization
assumptions.

- Is this sort of configuration sensible or insane?

In the golden land of perfect understanding of FP exceptions, it would
be controllable in a more complete and principled way. LLVM is a long
way off from that.

We're not that far off. There's an active effort in this area and it's
pretty far along (see, e.g.,
http://llvm.org/docs/LangRef.html#constrained-floating-point-intrinsics).

Was more asking about, this approach of catching/crashing on all FPE's
- I've seen it before but not completely understood (or believed) the
reasons why.

The primary use case I've seen is that you don't want you application,
which is supposed to be performing some numerical simulation, to run for
an extended period of time simply computing NaNs. And, moreover, if
something does go around and you generate a NaN, you want to know
exactly where that happened. LLVM doesn't really support this yet, but I
think that it's a perfectly-reasonable use case.

-Hal