-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking issue: 32bit x86 targets without SSE2 have unsound floating point behavior #114479
Comments
Can we just drop support for x86 without SSE2 or fall back to software floating point? |
We currently have the following targets in that category:
They are all tier 2. I assume people added them for a reason so I doubt they will be happy about having them dropped. Not sure to what extent floating point support is needed on those targets, but I don't think we have a concept of "target without FP support".
In theory of course we can, not sure if that would be any easier than following the Java approach. |
it should be much easier since LLVM already supports that, unlike the Java scheme (afaik) |
FWIW f32 on x86-32-noSSE should actually be fine, since double-rounding is okay as long as the precision gap between the two modes is big enough. Only f64 has a problem since it is "too close" to the 80bit-precision of the x87 FPU. On another note, we even have code in the standard library that temporarily alters the x87 FPU control word to ensure exact 64bit precision... |
I wonder if there's something that could be done about the fact that this also affects tier 1 targets with custom (stable) flags such as |
@RalfJung What about forcing non-SSE2 targets to software floating point? That must be supported anyway because of kernel code. |
Yeah that's an option listed above, since you already proposed it before. I have no idea how feasible it is. People are very concerned about the softfloat support for f16/f128 leading to code bloat and whatnot, so the same concerns would likely also apply here. AFAIK the kernel code just doesn't use floats, I don't think they have softfloats? |
…bilee add notes about non-compliant FP behavior on 32bit x86 targets Based on ton of prior discussion (see all the issues linked from rust-lang/unsafe-code-guidelines#237), the consensus seems to be that these targets are simply cursed and we cannot implement the desired semantics for them. I hope I properly understood what exactly the extent of the curse is here, let's make sure people with more in-depth FP knowledge take a close look! In particular for the tier 3 targets I have no clue which target is affected by which particular variant of the x86_32 FP curse. I assumed that `i686` meant SSE is used so the "floating point return value" is the only problem, while everything lower (`i586`, `i386`) meant x87 is used. I opened rust-lang#114479 to concisely describe and track the issue. Cc `@workingjubilee` `@thomcc` `@chorman0773` `@rust-lang/opsem` Fixes rust-lang#73288 Fixes rust-lang#72327
Rollup merge of rust-lang#113053 - RalfJung:x86_32-float, r=workingjubilee add notes about non-compliant FP behavior on 32bit x86 targets Based on ton of prior discussion (see all the issues linked from rust-lang/unsafe-code-guidelines#237), the consensus seems to be that these targets are simply cursed and we cannot implement the desired semantics for them. I hope I properly understood what exactly the extent of the curse is here, let's make sure people with more in-depth FP knowledge take a close look! In particular for the tier 3 targets I have no clue which target is affected by which particular variant of the x86_32 FP curse. I assumed that `i686` meant SSE is used so the "floating point return value" is the only problem, while everything lower (`i586`, `i386`) meant x87 is used. I opened rust-lang#114479 to concisely describe and track the issue. Cc `@workingjubilee` `@thomcc` `@chorman0773` `@rust-lang/opsem` Fixes rust-lang#73288 Fixes rust-lang#72327
A few specific tests fail under i586 due to its inherent floating point inaccuracy issues (rust-lang/rust#114479), so ignore these tests if certain are met. We have specific integration tests elsewhere in fish to check that even under i586 we get mostly sane results, so this is OK. I tried to modify the assert macros to check for a loose string match (up to one character difference) or an f64 abs diff of less than epsilon, but it was a lot of code with little value and increased the friction to contributing to the tests. Also, let's just acknowledge the fact that all of i686, let alone i586 specifically, is a dead end and not worth investing such time and effort into so long as it more or less "works". Closes fish-shell#10474.
Due to the inherent floating point accuracy issues under i586 described in #10474 and at rust-lang/rust#114479, we need to add a workaround to our littlecheck math tests to perform less stringent comparisons when fish was built for x86 without SSE2 support. This commit addresses the littlecheck issues that caused #10474 to be re-opened, but I still have to reproduce the cargo test failures for `negative_precision_width`, `test_float`, `test_float_g`, and `test_locale`.
A few specific tests fail under i586 due to its inherent floating point inaccuracy issues (rust-lang/rust#114479), so ignore these tests if certain are met. We have specific integration tests elsewhere in fish to check that even under i586 we get mostly sane results, so this is OK. I tried to modify the assert macros to check for a loose string match (up to one character difference) or an f64 abs diff of less than epsilon, but it was a lot of code with little value and increased the friction to contributing to the tests. Also, let's just acknowledge the fact that all of i686, let alone i586 specifically, is a dead end and not worth investing such time and effort into so long as it more or less "works". Closes #10474.
Does Rust expose the difference between representation and evaluation method? In C, FLT_EVAL_METHOD` is used by applications to detect platforms on which the computations are performed at a higher precision than the underlying value representations. This doesn't completely solve the problem as 8087 ABI also does representation conversion across function call boundaries so passing (or returning) a sNaN will have that transformed into qNaN and an exception. It's a stretch to claim that the 8087 FPU has 'unsound' floating point behavior; it's all quite compliant with the IEEE 754 specification, if you squint hard enough. Changing the ABI to pass float/double without a representation change would resolve the worst of the problems I've found, and that's not a hardware issue. |
In Rust, all operations are individually evaluated at the precision of the type being used (which I believe is the equivalent of The unsoundness is not just theoretical; the LLVM IR Rust compiles Because this is a miscompilation at the LLVM IR -> machine code stage, as opposed to the Rust -> LLVM IR stage, miscompilations can occur in other programming languages that use LLVM as a codegen backend. For example, llvm/llvm-project#89885 contains an example of a miscompilation from C. Ultimately what matters are the semantics of the LLVM IR; not everything that is permitted by the IEEE 754 specification is permitted by LLVM IR (and vice versa). The return value ABI issue is tracked separately in #115567, and affects all 32-bit x86 targets, not just those with SSE/SSE2 disabled. It is possible to manually load/store a |
Yeah, it sounds like rust cannot support other values for Having Rust produce results that depend on the underlying hardware seems antithetical to some pretty fundamental language goals. But, that would mean abandoning the x87 FPU entirely, and that doesn't seem feasible even though sse2, which provides the necessary native 32/64 binary format support is nearly old enough to serve in the US house of representatives. I'd encourage someone to figure out how to tell applications that the underlying hardware doesn't work the way they expect. Mirroring some version of FLT_EVAL_METHOD from C would at least be easy and sufficient for this hardware. |
That's not the claim (so if it sounds like that is our claim we should fix that). The claim is that the behavior of LLVM's optimizer and backend in combination with that of the x87 FPU is unsound. Having hardware-dependent behavior in an operation that is specified to be portable and deterministic would also still be unsound, but could conceivably be fixed by changing the spec. But an internally inconsistent backend is wrong for every reasonable spec. (Whether we'd really want such a target-specific spec is a different question. Rust code is pretty portable by default. Do we really want to require all Rust code to have to deal with non-standard precision float arithmetic? Or should code have some explicit opt-in / opt-out for niche targets with non-standard behavior? For this thread the question is rather moot as reliably implementing consistent non-standard behavior would at best be a lot of work [probably involving using LLVM's
As @beetrees explained, it's not just that the underlying hardware works differently and that bleeds into language semantics. It's that Rust's primary backend, LLVM, assumes the underlying hardware to work the standard way -- the examples @beetrees referenced demonstrate that there is no reliable way to program against this hardware in any compiler that uses LLVM as its backend. (At least not if the compiler uses the standard LLVM float types and operations.) To my knowledge, nobody on the LLVM side really cares about this. So until that changes it is unlikely that we'll be able to improve things in Rust here. Telling programmers "on this hardware your program may randomly explode for no fault of your own" is not very useful. (I mean, it is a useful errata of course, but it doesn't make sense to make this the spec.)
For f32, double-rounding does not affect results. For f64, it's possible to get around that using the approach Java used, IIUC. It does cost some performance but it avoids the semantic inconsistencies. We could also say that Rust code expects the FPU to be set to 64bit precision on such targets. That seems a lot easier... |
There are legitimate use-cases for running with the FPU in flush-to-zero + denormals-are-zero mode, even on mainstream targets like x86-64. Processing denormals outside of FTZ+DAZ mode requires a microcode assist that causes ~100 cycles IIRC. In this application, denormal numbers correspond to sounds that are (IIUC) below the noise floor, so flushing them to zero is harmless. Failing to meet deadlines because of the microcode assist is a bug. That’s separate but related to this issue. |
This is only true for some operations, and then only if a single step is performed. It is not true for addition/subtraction, and it is not true even for other operations if multiple operations are performed at 80-bit precision.
Yes, but LLVM's support for anything except standard IEEE floating point arithmetic is broken or absent, so Rust can't hope to support this until LLVM does. |
For |
Flushing denormals isn't even permitted by the IEEE standard. So while I am not doubting that that is something people may want to do, supporting such non-standards-compliant hardware requires inline assembly or new language features (and, depending on the constraints, work on the LLVM side). This is related to supporting floating point exception flags and non-default rounding modes, which is part of the standard but not supported in Rust. Please take that to another thread.
Indeed the rounding down to |
WG-prioritization assigning priority (see Zulip discussion for the related issue #129880). @rustbot label -I-prioritize +P-medium |
…ilee enable const-float-classify test, and test_next_up/down on 32bit x86 The test_next_up/down tests have been disabled on all 32bit x86 targets, which goes too far -- they should definitely work on our (tier 1) i686 target, it is only without SSE that we might run into trouble due to rust-lang#114479. However, I cannot reproduce that trouble any more -- maybe that got fixed by rust-lang#123351? The const-float-classify test relied on const traits "because we can", and got disabled when const traits got removed. That's an unfortunate reduction in test coverage of our float functionality, so let's restore the test in a way that does not rely on const traits. The const-float tests are actually testing runtime behavior as well, and I don't think that runtime behavior is covered anywhere else. Probably they shouldn't be called "const-float", but we don't have a `tests/ui/float` folder... should I create one and move them there? Are there any other ui tests that should be moved there? I also removed some FIXME referring to not use x87 for Rust-to-Rust-calls -- that has happened in rust-lang#123351 so this got fixed indeed. Does that mean we can simplify all that float code again? I am not sure how to test it. Is running the test suite with an i586 target enough? Cc `@tgross35` `@workingjubilee`
…ilee enable const-float-classify test, and test_next_up/down on 32bit x86 The test_next_up/down tests have been disabled on all 32bit x86 targets, which goes too far -- they should definitely work on our (tier 1) i686 target, it is only without SSE that we might run into trouble due to rust-lang#114479. However, I cannot reproduce that trouble any more -- maybe that got fixed by rust-lang#123351? The const-float-classify test relied on const traits "because we can", and got disabled when const traits got removed. That's an unfortunate reduction in test coverage of our float functionality, so let's restore the test in a way that does not rely on const traits. The const-float tests are actually testing runtime behavior as well, and I don't think that runtime behavior is covered anywhere else. Probably they shouldn't be called "const-float", but we don't have a `tests/ui/float` folder... should I create one and move them there? Are there any other ui tests that should be moved there? I also removed some FIXME referring to not use x87 for Rust-to-Rust-calls -- that has happened in rust-lang#123351 so this got fixed indeed. Does that mean we can simplify all that float code again? I am not sure how to test it. Is running the test suite with an i586 target enough? Cc `@tgross35` `@workingjubilee`
…ilee enable const-float-classify test, and test_next_up/down on 32bit x86 The test_next_up/down tests have been disabled on all 32bit x86 targets, which goes too far -- they should definitely work on our (tier 1) i686 target, it is only without SSE that we might run into trouble due to rust-lang#114479. However, I cannot reproduce that trouble any more -- maybe that got fixed by rust-lang#123351? The const-float-classify test relied on const traits "because we can", and got disabled when const traits got removed. That's an unfortunate reduction in test coverage of our float functionality, so let's restore the test in a way that does not rely on const traits. The const-float tests are actually testing runtime behavior as well, and I don't think that runtime behavior is covered anywhere else. Probably they shouldn't be called "const-float", but we don't have a `tests/ui/float` folder... should I create one and move them there? Are there any other ui tests that should be moved there? I also removed some FIXME referring to not use x87 for Rust-to-Rust-calls -- that has happened in rust-lang#123351 so this got fixed indeed. Does that mean we can simplify all that float code again? I am not sure how to test it. Is running the test suite with an i586 target enough? Cc ``@tgross35`` ``@workingjubilee``
…ilee enable const-float-classify test, and test_next_up/down on 32bit x86 The test_next_up/down tests have been disabled on all 32bit x86 targets, which goes too far -- they should definitely work on our (tier 1) i686 target, it is only without SSE that we might run into trouble due to rust-lang#114479. However, I cannot reproduce that trouble any more -- maybe that got fixed by rust-lang#123351? The const-float-classify test relied on const traits "because we can", and got disabled when const traits got removed. That's an unfortunate reduction in test coverage of our float functionality, so let's restore the test in a way that does not rely on const traits. The const-float tests are actually testing runtime behavior as well, and I don't think that runtime behavior is covered anywhere else. Probably they shouldn't be called "const-float", but we don't have a `tests/ui/float` folder... should I create one and move them there? Are there any other ui tests that should be moved there? I also removed some FIXME referring to not use x87 for Rust-to-Rust-calls -- that has happened in rust-lang#123351 so this got fixed indeed. Does that mean we can simplify all that float code again? I am not sure how to test it. Is running the test suite with an i586 target enough? Cc ```@tgross35``` ```@workingjubilee```
Rollup merge of rust-lang#129835 - RalfJung:float-tests, r=workingjubilee enable const-float-classify test, and test_next_up/down on 32bit x86 The test_next_up/down tests have been disabled on all 32bit x86 targets, which goes too far -- they should definitely work on our (tier 1) i686 target, it is only without SSE that we might run into trouble due to rust-lang#114479. However, I cannot reproduce that trouble any more -- maybe that got fixed by rust-lang#123351? The const-float-classify test relied on const traits "because we can", and got disabled when const traits got removed. That's an unfortunate reduction in test coverage of our float functionality, so let's restore the test in a way that does not rely on const traits. The const-float tests are actually testing runtime behavior as well, and I don't think that runtime behavior is covered anywhere else. Probably they shouldn't be called "const-float", but we don't have a `tests/ui/float` folder... should I create one and move them there? Are there any other ui tests that should be moved there? I also removed some FIXME referring to not use x87 for Rust-to-Rust-calls -- that has happened in rust-lang#123351 so this got fixed indeed. Does that mean we can simplify all that float code again? I am not sure how to test it. Is running the test suite with an i586 target enough? Cc ```@tgross35``` ```@workingjubilee```
enable const-float-classify test, and test_next_up/down on 32bit x86 The test_next_up/down tests have been disabled on all 32bit x86 targets, which goes too far -- they should definitely work on our (tier 1) i686 target, it is only without SSE that we might run into trouble due to rust-lang/rust#114479. However, I cannot reproduce that trouble any more -- maybe that got fixed by rust-lang/rust#123351? The const-float-classify test relied on const traits "because we can", and got disabled when const traits got removed. That's an unfortunate reduction in test coverage of our float functionality, so let's restore the test in a way that does not rely on const traits. The const-float tests are actually testing runtime behavior as well, and I don't think that runtime behavior is covered anywhere else. Probably they shouldn't be called "const-float", but we don't have a `tests/ui/float` folder... should I create one and move them there? Are there any other ui tests that should be moved there? I also removed some FIXME referring to not use x87 for Rust-to-Rust-calls -- that has happened in #123351 so this got fixed indeed. Does that mean we can simplify all that float code again? I am not sure how to test it. Is running the test suite with an i586 target enough? Cc ```@tgross35``` ```@workingjubilee```
Upstream Rust always requires SSE2 for x86. But back in 2017[^1][^2] we patched lang/rust to disable SSE2 for i386. At the time, it was reported that some people were still using non-SSE2 capable hardware. More recently, LLVM bugs have been discovered[^3][^4] that can result in rounding bugs and reduced accuracy when using f64 on non-SSE hardware. In weird cases, they can even cause wilder unpredictable behavior, like segfaults. Revert our change for the sake of Pentium 4 (and later) users. But add an SSE2 option. Disabling it will allow the port to be used on Pentium 3 and older CPUs. [^1]: d65b288 [^2]: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=223415 [^3]: rust-lang/rust#114479 [^4]: llvm/llvm-project#44218 Reviewed by: emaste Differential Revision: https://reviews.freebsd.org/D47227
On x86 (32bit) targets that cannot use SSE2 instructions (this includes the tier 1 i686 targets with flags that disable SSE2 support, such as
-C target-cpu=pentium
), floating-point operation can return results that are rounded in different ways than they should, and results can be "inconsistent": depending on whether const-propagation happened, the same computation can produce different results, leading to a program that seemingly contradicts itself. This is caused by using x87 instructions to perform floating-point arithmetic, which do not accurately implement IEEE floating-point semantics (not with the right precision, anyway). The testtests/ui/numbers-arithmetic/issue-105626.rs
has an example of such a problem.Worse, LLVM can use x87 register to store values it thinks are floats, which resets the signaling bit and thus alters the value -- leading to miscompilations.
This is an LLVM bug: rustc is generating LLVM IR with the intended semantics, but LLVM does not compile that code in the way that the LLVM LangRef describes. This is a known and long-standing problem, and very hard to fix. The affected targets are so niche these days that that is nobody's priority. The purpose of this issue mostly is to document its existence and to give it a URL that can be referenced.
Some ideas that have been floated for fixing this problem:
We could set the FPU control register to 64bit precision for Rust programs, and require other code to set the register in that way before calling into a Rust library.this does not workRelated issues:
Prior issues:
The text was updated successfully, but these errors were encountered: