Code change:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried this code: This runs correctly (i.e. doesn't assert) when run with rust nightly, but fails at when run with miri on nightly (2020-02-20). It seems like miri just truncated the f64 to get the f32 and therefore converted the nan2 to a floating point zero instead of a floating point NaN
(Miri-the-tool has its own bugtracker at , where I see things earlier than when they are burried here.) Cc this could also be an apfloat issue as that's what Miri uses to implement casts. I don't think we are doing any truncation . Would be interesting to see the bit patterns involved.
for returns . So, this doesn't look like just a truncated to me, it looks like apfloat did something but that something is not correct (or at least, not nan-preserving -- I am not sure if that is a guarantee).
cc
Sorry, I should have said truncated the fraction there. The is the floating point exponent being set to all ones, the (plus the empty bottom 3 bits in the 8) is a truncation of the original fraction in the f64. By removing all the non-zero bits in the fraction it converted the NaN to an infinity. For reference for returns where the (plus the empty bottom 3 bits in the 8) is a truncation of the original fraction from 52 to 23 bits Note that the normal Rust compiler seems to always set the MSB in the fraction of a NaN during its conversion to avoid this problem. There is and is
Assuming that f64-to-f32 on NaN has fully defined semantics, we should probably have a test asserting these exact bit patterns for Miri. But what you wrote makes it sound even more like the bug is in apfloat, and not in the Miri code that feeds data to/from apfloat. FWIW, the float casting code is .
Yes, it does sound to me like an apfloat issue rather than something unique in Miri. Reading that casting method I can't see anything that is special casing the fraction truncation for NaNs, only for an 80bit extended precision type that has different NaN semantics. Sorry, I've not dealt with the bug workflow on Rust before - is it possible to move this bug to apfloat? Would you like me to create a separate bug on apfloat?
apfloat does not have a separate issue tracker, no need to move anything. However, if I understood correctly then apfloat is a direct Rust port of LLVM's apfloat -- so likely they have the same bug, and a fix would have to happen upstream as well.
Yes, rustcapfloat is a port of LLVM's APFloat, but (also in older versions, back to before started the Rust port). Note that the MSB of the significand is set, so this is indeed a NaN. It seems LLVM does not have the same bug. Something strange is up.
Hm, that's still not the same as the that we see at run-time though?
I think the LLVM C++ code doing the same cast is .
Sure, but that's intentional. Optimizations need to preserve whether something is a NaN but (by default) the payload bits, quiet/signaling distinction and sign bit are not generally preserved or guaranteed to match between compile time evaluation and run time evaluation.
So the semantics would be something like "if the result is a NaN, non-deterministically pick any legal NaN representation"?
That sounds about right. Kind of off-topic here, though -- if you want to discuss further, we have
The category should still be , but it looks like we don't enforce the significand is non-: <hr/ Wait, I just looked at your link and LLVM prints - that's a bitpattern, not a , lol. But with a to integer, it matches : (if you hover over the decimal value, helpfully shows it as hex)
Note that converting floats to double bit patterns happens when printing textual IR: I'm reasonably sure the constant folding produces a proper float (). But perhaps the non-zero bits in its significand are in bit positions 32? Then the bitcast code would silently drop them.
Just remembered I wanted to look at assembly or machine code to see what the LLVM lowers to and show that it's buggy in LLVM even outside of constant-folded bitcasts.
Also isn't a +Infinity, showing the had already truncated the significand?
. And I just realized that while the high half of is a f32 NaN, the whole bit string as an f64 is in fact a NaN and setting just one bit more (e.g. ) does make llc (and presumably bitcast folding) produce (an f32 NaN). So I guess that I interpreted incorrectly and the fptrunc folding is at fault after all.
So... what does this mean? Is it an LLVM bug, and do we have enough information to report it upstream?
Yes, either truncation needs to result in a non- (e.g. becoming when losing lower bits could be "rounded up" by setting it to ), or extracting the IEEE representation from should handle the situation by setting some bit. cc (I don't know who does upstream LLVM bug reports nowadays most often)
actually I misremembered what this bug is about. This is not just "surprising NaN payload", indeed the result here is not a NaN when it should be. So that is quite clearly a bug in LLVM's APfloat, or in the Rust port of it (but we seem confident the problem is upstream). It would be great if we could report this against LLVM, but I have no idea how to reproduce the problem with LLVM. Do we need to write a C++ program using APfloat, or can we somehow make this happen just with the right LLVM IR? was previously posted as an example, does this show an incorrect compilation? These bit patterns mean nothing to me.^^
Wow! That's extremely wrong! It turns a NaN into positive infinity! Poking it gives a bit more insight into the issue: shows that all bits in the NaN payload below the 29th bit are truncated here. Mercifully, I can repro this in rustcapfloat, which is easier for me to experiment with than LLVM's (okay, I also remember what a headache it was trying to make changes to LLVM in the past, and it's now not something I'm interested in doing in my spare time) That is, it reports has , but then turns it into . Doing some further digging shows points me to , where we call with a number that comes from the difference between (bits of significand) of the source and target, e.g. 53 - 22, or 29 (which all-but-confirms that this is the issue). (The branch where there's the sig::shiftleft is wrong too, FWIW). It's also pretty wrong — IEEE wants you to truncate the payload here, not shift it out. e.g. I think what you want to do is something like (this code intentionally doesn't compile, and is assuming is the target type and that things are beautifully simple enough that there's just a function which returns something I can bitand...): That said, this is possibly hopelessly wrong? I don't thing so but I don't have a copy of IEEE754 around to verify. ... ... Okay I tried to make it compile, and if you drop this beast in before the shifts (e.g. line 1488 as of commit ) the tests pass (Sorry, I foolishly did this in a github download of rustcapfloat, since my rustc is in the middle of other things, and so I can't easily just give you a patch) That said it could still have other issues. And the fix could be extremely wrong for other reasons, it's the result of not-very-long spent debugging. Someone who understands the code better should audit it for this issue ( It says you wrote ). Ditto for LLVM's version which I can hardly follow since my C++ is so rusty. That said, LLVM is a popular enough project that someone hopefully could take it from here? E.g. Turning a NaN into an infinity is extremely bad — obviously so. Masking away the bottom bits like this is an obvious bug (nothing anywhere does that), and so even if they don't care about preserving NaNs specifically I suspect they'd want to fix this. Edit: I wrote this without carefully reading the rest of this issue. I think you uncovered some of this? Maybe? I don't know. Maybe this helped, sorry if not.
Yeah that sounds wrong. :D I'm afraid I cannot comment on the internals of apfloat that you are getting into for the rest of your post. That stuff is way beyond me.^^ I understand you won't work on fixing this in LLVM, but could you make this a bugreport for them, in their bugzilla? I feel a bit awkward making a bugreport that basically says "look I don't know what I am talking about but I have it on good authority that what LLVM is doing is wrong". Would be better if someone who knows what they are talking about does that I think. ;) Chances are, they'll have someone who can fix it.
says I'll send them an email, but...
Argh. :/ I think this worked fine for me though. If they don't respond in a few days, we can also use me as a proxy.^^
That sounds fine. In the meantime it maybe be good for someone more familiar with the rustc_apfloat code to take a look at what I wrote anyway...
There was already a bug, but I a comment for it:
There's a fix on the LLVM side now, somewhere in -- there are multiple reviews, we should figure out which one actually landed. Anyone up for porting this back to the Rust apfloat?
I guess would be the page to check for what happened in apfloat upstream.
Done:
The fix on the LLVM side is the minimal possible fix to avoid the linked miscompilation here: It doesn't fix this bug in the general case.
is a more correct fix. But note that it's not just the change to APFloat that makes it correct (in fact, that change alone is arguably wrong), but at usage sites changing things to be (for example): I think we should probably do this inside our function, otherwise it would be incorrect too. This is unfortunate as it causes more divergence to upstream APFloat
are you referring to this segment of your comment above? Can you quote the part of the IEEE 754 standard which mandates this? Or alternatively, come up with an example like the one at the top where there is a mismatch between what happens at runtime vs during miri evaluation? Apparently hardware implementations shift out payloads as well: prints:
Larger example that compares runtime and const eval (as of nightly master): prints: Edit: apparently on the hardware it always unconditionally sets the highest bit of the mantissa when a NaN truncation is performed to work around the issue. Edit2: which is nothing other than turning the sNaN into a qNaN.
IEEE754-2019 6.2.3 (NaN Propagation) states that This seems impossible if the implementation shifts the payload out. But I guess this only applies to qNaN, although in my testing earlier it seemed to happen on sNaN on my machines. Anyway, I'm a bit busy at the moment, or I'd dig further into this.
I knew there were two patches but I have no idea which one landed... so far the plan was to only port things that have landed on the LLVM side. If there is still a bug left there, that seems worth reporting?
If the implementation shifts to the left on conversion to the wider format and back to the right on conversion to the narrower format, then the NaN payload will be the same. So I think that shifting is conformant with that rule of the standard. Otherwise hardware would be nonconformant as well. But maybe there's still some rule in the standard that LLVM apfloat violates.
Hmm, fair enough. Unfortunately, I don't think I'm going to have time to dig further into this for at least another week, but sounds good to me. Sorry for any trouble.
No trouble caused, thanks for looking into this. :)
Yes your comments were very helpful. I wouldn't have found or thought of looking for that LLVM MR for example.
This issue will be fixed by but that there are some other interesting test-cases that would not have been fixed by a more naive approach... would be great if we could get those testcases into our test suite as well :)

Related discussion:
text: <ins> // run-pass #![feature(const_fn_transmute)] const fn make_nans() -> (f64, f64, f32, f32) { let nan1: f64 = unsafe { std::mem::transmute(0x7FF0_0001_0000_0001u64) }; let nan2: f64 = unsafe { std::mem::transmute(0x7FF0_0000_0000_0001u64) }; let nan1_32 = nan1 as f32; let nan2_32 = nan2 as f32; (nan1, nan2, nan1_32, nan2_32) } static NANS: (f64, f64, f32, f32) = make_nans(); fn main() { let (nan1, nan2, nan1_32, nan2_32) = NANS; assert!(nan1.is_nan()); assert!(nan2.is_nan()); assert!(nan1_32.is_nan()); assert!(nan2_32.is_nan()); } </ins>