rust-lang/rust

floating point to floating point casts have undefined behaviour

huonw opened this issue · 20 comments

If the value cannot fit within the destination type, ty2, then the results are undefined.

http://llvm.org/docs/LangRef.html#fptrunc-to-instruction

e.g. 1e300f64 as f32

cc #10184, #10185

Nominating

I think this is not a 1.0 issue. There is no reasonable backwards incompatible language change we can make here to fix it. The only thing to do is to change the LLVM semantics here to not be UB, but rather be an undefined value.

@pcwalton the LLVM semantics here are that the result is an undefined value, not that the instruction exhibits undefined behavior. Otherwise you wouldn't be able to hoist an fptrunc out of a loop.

OK, then I believe this is not a bug, just something to remember to note in the spec.

Assigning P-high, not 1.0 milestone, by the same reasoning as #10184 and #10185.

@zwarich: Rust allows undefined behaviour if you have an undefined value. An undefined value has no live range so it doesn't correspond to a reserved register with a preserved value and can vary between reads. That means something like xs[undef] can do an out-of-bounds index, because there's nothing forcing it to have the same value between the check and the read.

@thestinger That's a good point, but that just makes it like the other bugs that aren't in 1.0.

Programs can be written depending on the value of undefined behaviour, and changing it to either fail or provide a specific result will break those programs. This isn't C where undefined behaviour is a problem in user code.

We may need a UB label for such issues IMO

Can anybody reproduce this undefined behaviour? In

#![crate_type="rlib"]

const f:f64 = 1.0E+300;

pub static ffull:f64 = f;
pub static ftrunc:f32 = f as f32;
pub static f2i:u64 = f as u64;
pub static i2f:f32 = 0xFFFFFFFFFFFFFFFFu64 as f32;

ftrunc is rounded to +inf, while (based on this issue and LLVM documentation) I would have expected an undef. In the actual LLVM code fptrunc seems to be more well-behaved than what is stated in the docs (apparently it tries to respect the IEEE754 standard, with some minor quirks around NaNs).

I asked for additional information on llvm-dev

@ranma42 From what I understood from the reference, the LLVM undef can turn into any value depending on each context. So we cannot complain if it happened to turn into 0x7FF0000000000000. I think the reasoning here is the same with select undef, %X, %Y can be optimized into either of %X or %Y (but not undef.)

(moving this back to P-medium and letting the I-wrong reflect the severity; the conclusion of the lang team was that there are lots of open bugs that reflect wrong behavior, and we are not going to prioritize this one as P-high.)

My thought is to define this as resulting as infinity.

@drbo But doing that could be potentially more expensive than just making the result unspecified but without causing undefined behavior.

I believe this ("if it doesn't fit in the destination type, you get UB/undef") is and always has been FUD caused by individual LLVM developers misunderstanding IEEE 754, just like with signalling NaNs.

LLVM's actual long-standing policy (now finally documented) — and the only policy that makes sense for an optimizing C compiler IMO — is that everything happens in the default floating point environment. This means among other things that rounding is always ties-to-even, the status flags are never inspected, and floating point exception handling always uses the default behavior rather than any custom handling. Consequently, it is clear as day that a floating point truncation that exceeds the range of the target type should result in positive or negative infinity. And indeed that is what LLVM's constant folding will do (specifically, what APFloat will do).

FWIW this operation is a standard operation (formatOf-convertFormat), defined in §5.4.2 of IEEE 754-2008, but it would not really matter if this was a non-standard operation since those follow the same basic rules.

So I recommend:

  • someone with a bugs.llvm.org account files a bug with LLVM to fix this inaccuracy (and it really is just that, it's clearly contradictory with the explicit policy linked earlier)
  • we close this issue
  • we remove this warning

Commit https://reviews.llvm.org/rL329065 removed the quoted statement (and also the stuff about undefined rounding mode) and replaced it with

This instruction is assumed to execute in the default floating-point environment.

Since this was just a doc clarification and in reality LLVM always behaved as we wanted it to, there's no need to wait until an LLVM update to pick up any code changes related to this. So we can close this issue now.

Thanks @rkruppe for clearing this up, however the reference still says this is UB.

@dhardy Thanks for pointing this out, filed a PR to update the reference.