LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 43907 - Double Signaling NaN converted to float becomes INF
Summary: Double Signaling NaN converted to float becomes INF
Status: RESOLVED FIXED
Alias: None
Product: libraries
Classification: Unclassified
Component: Support Libraries (show other bugs)
Version: 11.0
Hardware: PC Windows NT
: P normal
Assignee: Unassigned LLVM Bugs
URL:
Keywords:
Depends on:
Blocks: 13707 release-11.0.0
  Show dependency tree
 
Reported: 2019-11-05 05:14 PST by Baudouin Feildel
Modified: 2020-09-30 06:28 PDT (History)
8 users (show)

See Also:
Fixed By Commit(s):


Attachments
LLVM IR (2.47 KB, text/plain)
2019-11-05 06:57 PST, Baudouin Feildel
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Baudouin Feildel 2019-11-05 05:14:26 PST
The following piece of code produce incorrect result with clang (and clang-cl) on Windows:

```
#include <cmath>
#include <limits>

int main() {
    const double quiet_nan_double = std::numeric_limits<double>::quiet_NaN();
    const float converted_to_float = float(quiet_nan_double);
    const bool isnan = std::isnan(converted_to_float);
    return isnan ? 1 : 0;
}
```

The program should return 1. It does on Linux with majors compilers. On Windows clang (and clang-cl) returns 0 no matter what compilation flags I tried. Here are some flags I used: 

clang -O3 -Wall -Wextra -g nan_d2f.cpp
clang -O0 -Wall -Wextra -g nan_d2f.cpp
clang -Wall -Wextra -g nan_d2f.cpp
clang-cl /Ox /W4 /EHsc nan_d2f.cpp
clang-cl /W4 /EHsc nan_d2f.cpp

Here is version reported by `clang -v`:

```
clang version 9.0.0 (tags/RELEASE_900/final)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\scoop\apps\llvm\current\bin
```

It was downloaded from here: https://releases.llvm.org/9.0.0/LLVM-9.0.0-win64.exe#/dl.7z

Let me know if you need more info or cannot reproduce.
Comment 1 Baudouin Feildel 2019-11-05 05:16:58 PST
Sorry, the initial shared code is wrong. Here is the correct one:

```
#include <cmath>
#include <limits>

int main() {
    const double signaling_nan_double = std::numeric_limits<double>::signaling_NaN();
    const float converted_to_float = float(signaling_nan_double);
    const bool isnan = std::isnan(converted_to_float);
    return isnan ? 1 : 0;
}
```
Comment 2 Sanjay Patel 2019-11-05 06:48:21 PST
I can't reproduce this on macOS or Linux:
https://godbolt.org/z/2ciTCf

Can you output LLVM IR as shown in the above link and paste it here?
Comment 3 Baudouin Feildel 2019-11-05 06:56:21 PST
It works as expected on macOS and Linux. Only issue is with Windows.

I am attaching the LLVM IR I just generated with the following command line:

clang -g -o nan_d2f_clang.llvm -mllvm --x86-asm-syntax=intel -S -fcolor-diagnostics -fno-crash-diagnostics -O2 -g0 -emit-llvm nan_d2f.cpp
Comment 4 Baudouin Feildel 2019-11-05 06:57:22 PST
Created attachment 22772 [details]
LLVM IR

Command line:

clang -g -o nan_d2f_clang.llvm -mllvm --x86-asm-syntax=intel -S -fcolor-diagnostics -fno-crash-diagnostics -O2 -g0 -emit-llvm nan_d2f.cpp
Comment 5 Sanjay Patel 2019-11-05 07:04:44 PST
(In reply to Baudouin Feildel from comment #3)
> It works as expected on macOS and Linux. Only issue is with Windows.

I don't know much about Windows, so someone else will have to investigate further. Here's the IR that I see in the attachment:

define dso_local i32 @main() local_unnamed_addr #0 {
  %1 = alloca float, align 4
  %2 = bitcast float* %1 to i8*
  call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %2) #3
  store float 0x7FF0000000000000, float* %1, align 4, !tbaa !8
  %3 = call i16 @_fdtest(float* nonnull %1) #3
  call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %2) #3
  %4 = icmp eq i16 %3, 2
  %5 = zext i1 %4 to i32
  ret i32 %5
}

declare dso_local i16 @_fdtest(float*) local_unnamed_addr #1

---------------------------------------------------------------------------------

I've never seen "_fdtest" before...
https://devblogs.microsoft.com/cppblog/improving-the-performance-of-standard-library-functions/
Comment 6 Baudouin Feildel 2019-12-09 02:39:56 PST
I reported the bug to Microsoft as it seems it is related to their STL. They were not able to reproduce the bug so I setup a repository in Azure DevOps to reproduce the bug. If that can help anyone to reproduce it I am sharing it here: https://dev.azure.com/feildel/clang-bug-nan-d2f
Comment 7 Casey Carter 2019-12-13 10:19:35 PST
Minimal repro:

  #include <assert.h>
  #include <math.h>

  int main() {
      assert(isnan(float(__builtin_nans("1"))));
  }

The assertion fires when compiled with clang-cl:

  clang version 9.0.0 (tags/RELEASE_900/final)
  Target: x86_64-pc-windows-msvc
  Thread model: posix

but not for MSVC (or gcc/clang on Ubuntu, for that matter) per https://godbolt.org/z/65wWKG.
Comment 8 Thom Chiovoloni 2020-09-14 07:57:35 PDT
In https://github.com/rust-lang/rust/issues/69532 we've hit this bug, and I'm pretty confident I've figured out the issue.

Here's a minimal example that just operates on IR https://godbolt.org/resetlayout/Zf7HMh , in case there's not one already in this thread.

Here's a more illustrative test case that demonstrates what's happening: https://godbolt.org/z/c59E4E . It seems that the bottom 29 bits of the NaN payload are being discarded.

I believe the issue is here: https://github.com/llvm/llvm-project/blob/9868ea764f31b0fd4ec250867807aa0ad7958abf/llvm/lib/Support/APFloat.cpp#L2203-L2205 (I also believe L2229-L2232 (shifting right) very likely suffers from a similar issue)

When `convert`ing from binary64 to binary32, the `-shift` value will evaluate to 29, which explains why the bottom 29 bits are gone.

I think LLVM has leeway with what to do precisely, but:

- IMO APFloat should properly preserve NaN payloads where possible, *at least* at compile time.
- Even if not, compiling NaN => Inf should be fixed.

So, for the first part, I don't think IEEE 754 comes out an explicitly says that you should truncate the most significant bits when going from a higher-precision format to a lower precision one (I could be wrong), but it seems to imply it, at least for qNaN (sNaN is obviously implementation defined but it's hard for me to see why it should be treated very much differently to qNaN here):

> Conversion of a quiet NaN from a narrower format to a wider format in
> the same radix, and then back to thesame narrower format, should not
> change the quiet NaN payload in any way except to make it canonical.
>
> - 6.2.3 NaN propagation (in 754-2008 anyway, I don't have 754-2019)

That is, binary32 => binary64 => binary32 should be unchanged, which it would be for truncating the most significant bits, but not for shifting, or any other obvious method of discarding a subset of the bits.

That said, this might just move your issue to the high bits unless you also ensure the output is a NaN if the input is one. I think this just needs to be an explicit check.

I provided some rust code that does these things for rustc's port of APFloat in https://github.com/rust-lang/rust/issues/69532#issuecomment-691988800 (it could be very wrong but probably gets the idea across — someone who works more regularly/deeply with IEEE 754 should take a look and possibly correct my mistakes — that's very welcome!).

---

TL; DR:

- APFloat does wrong things with NaN payloads here: https://github.com/llvm/llvm-project/blob/9868ea764f31b0fd4ec250867807aa0ad7958abf/llvm/lib/Support/APFloat.cpp#L2203-L2205 (and probably elsewhere in this function, and maybe elsewhere in APFloat).
- This can turn NaNs into infinities (and cause APFloats to have `categories` that do not match the categories of their bitpatterns).
- Easy fix is just to ensure that NaN in => NaN out.
- Slightly harder fix is to ensure (at least in `convert`) that it's the upper bits of the NaN's payload that are discarded (not lower).
- https://github.com/rust-lang/rust/issues/69532#issuecomment-691988800 has some rust code that performs an equivalent operation for Rust's APFloat port, but it's slapdash and may have bugs. Seems to work tho.

P.S. I was filing this as a new issue when I noticed this at the last minute, which is why it's phrased like it would be a new issue.
Comment 9 Ralf Jung 2020-09-17 00:31:18 PDT
Is there some APFloat component that this can be assigned to? Would be good to get the APFloat maintainer(s) to look at this.
Comment 10 Sanjay Patel 2020-09-17 08:39:04 PDT
I don't know if anyone is an APFloat maintainer/authority at this point, but I created patch proposal here:
https://reviews.llvm.org/D87835
Comment 11 Sanjay Patel 2020-09-24 12:40:53 PDT
APFloat minimally fixed here:
https://reviews.llvm.org/rGe34bd1e0b03d

Can someone verify that the code on Windows behaves as expected now?

We will hopefully fix this better with:
https://reviews.llvm.org/D88238
Comment 12 Baudouin Feildel 2020-09-25 04:12:21 PDT
The reported issue is fixed on Windows using clang & clang-cl built from commit e34bd1e0b03d20a506ada156d87e1b3a96d82fa2.

Thanks for your work!
Comment 13 Baudouin Feildel 2020-09-29 00:48:19 PDT
Will this fix make it to LLVM 11.0? Or shall we wait for LLVM 12?

Also, I don't know if I should switch the status of the bug to resolved, or if some people are responsible for this.
Comment 14 Sanjay Patel 2020-09-29 05:28:52 PDT
(In reply to Baudouin Feildel from comment #13)
> Will this fix make it to LLVM 11.0? Or shall we wait for LLVM 12?
> 
> Also, I don't know if I should switch the status of the bug to resolved, or
> if some people are responsible for this.

IIUC, this was not a recent regression, and the 11.0 release is close to done, but there's still a chance...
cc'ing Hans and marking as blocker.
Comment 15 Hans Wennborg 2020-09-29 05:49:37 PDT
(In reply to Sanjay Patel from comment #14)
> (In reply to Baudouin Feildel from comment #13)
> > Will this fix make it to LLVM 11.0? Or shall we wait for LLVM 12?
> > 
> > Also, I don't know if I should switch the status of the bug to resolved, or
> > if some people are responsible for this.
> 
> IIUC, this was not a recent regression, and the 11.0 release is close to
> done, but there's still a chance...
> cc'ing Hans and marking as blocker.

If we have to spin another release candidate (rc5) we can pick up this fix, but otherwise it will have to wait for 11.0.1.

I've made a note, but will remove it from the blockers list for now.
Comment 16 Hans Wennborg 2020-09-30 04:36:59 PDT
There will be an rc5, so I've pushed this to 11.x as 60a25202a7dd1e00067fcfce512086ebf3788537.
Comment 17 Sanjay Patel 2020-09-30 06:28:41 PDT
(In reply to Hans Wennborg from comment #16)
> There will be an rc5, so I've pushed this to 11.x as
> 60a25202a7dd1e00067fcfce512086ebf3788537.

Great - thank you!