-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Enable Non-determinism of float operations in Miri and change std tests #138062
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
base: master
Are you sure you want to change the base?
Enable Non-determinism of float operations in Miri and change std tests #138062
Conversation
The Miri subtree was changed cc @rust-lang/miri |
The library changes lgtm, for the rest r? @RalfJung |
I should have clarified: the doc tests fail when running |
src/tools/miri/src/intrinsics/mod.rs
Outdated
2, // log2(4) | ||
); | ||
|
||
// Clamp values to the output range defined in IEEE 754 9.1. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We said we're going to follow the C standard, which is more permissive than the IEEE spec. Is there a reference for this in the C standard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and no...
For some operations it specifies it like this:
The atan functions return arctan x in the interval [−π/2, +π/2] radians
But for sin
and cos
like this:
The sin functions return
sin x
If I read "returns sin x", I understand it as "the output is [-1, +1]", but maybe that's just me.
src/tools/miri/src/intrinsics/mod.rs
Outdated
let fixed_res = match (f1.category(), f2.category()) { | ||
// 1^y = 1 for any y even a NaN. | ||
// TODO: C Standard says any NaN, IEEE says not a Signaling NaN | ||
(Category::Normal, _) if f1 == 1.0f32.to_soft() => Some(1.0f32.to_soft()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you avoid using soft floats here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the miri issue you said this:
except that the logic for fixed_res and clamp has to be done with soft-floats.
Did I misunderstand what you meant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh sorry, I meant "avoid using hard floats" :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alright!
src/tools/miri/src/intrinsics/mod.rs
Outdated
let fixed_res = match (f1.category(), f2.category()) { | ||
// 1^y = 1 for any y even a NaN. | ||
// TODO: C says any NaN, IEEE says no a Sign NaN | ||
(Category::Normal, _) if f1 == 1.0f64.to_soft() => Some(1.0f64.to_soft()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not duplicate the same logic 4 times. We were a bit lazy here so far as the boilerplate for making the code generic was bigger than the code, but with all this special-case handling that is no longer the case.
src/tools/miri/tests/pass/float.rs
Outdated
// accept up to 64ULP (16ULP for host floats and 16ULP for miri artificial error and 32 for any rounding errors) | ||
assert_approx_eq!($a, $b, 64); | ||
// accept up to 52ULP (16ULP for host floats, 4ULP for miri artificial error and 32 for any rounding errors) | ||
assert_approx_eq!($a, $b, 52); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really still need 52 ULP? I would have hoped that 32 works now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed 32 works, don't know why I didn't tried it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried lower but got some ULP differences of 27.
src/tools/miri/tests/pass/float.rs
Outdated
// TODO: How to test NaN inputs? f*::NAN is not guaranteed | ||
// to be any specific bit pattern (in std). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is guaranteed to be a NaN though. Why do you want a specific bit pattern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IEEE is not as permissive as C, so it doesn't matter much now, just one case: C standard says for pown
(powi
) the following:
pown(x, 0) returns 1 for all x not a signaling NaN
And since std
doesn't guarantee the specific bit pattern of NaN
, I'm not quite sure how to test it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, since NAN
might be signaling? That'd be quite bad.^^
Please rely on it being not signalling, I'll see if we can fix the docs.
it is failing the tests that use |
library/std/tests/floats/lib.rs
Outdated
macro_rules! assert_approx_eq { | ||
($a:expr, $b:expr) => {{ assert_approx_eq!($a, $b, 1.0e-6) }}; | ||
($a:expr, $b:expr) => {{ assert_approx_eq!($a, $b, 1.0e-4) }}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd rather not change this for all tests, and in particular not for f64
.
An alternative would be to just set this precision with the affected tests in library/std/tests/floats/f32.rs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the precision should be only loosened only with cfg!(miri)
as well. I think it is beneficial to learn which platforms, if any, have worse precision than we expect and address that on a case-by-case basis.
So I:
|
No, a macro is not right. The way to avoid this duplication is to add a function that is generic over the apfloat float type. I'd say just remove |
Yeah Sorry, I was confused with your comment: |
I was confused with your comment: avoid using soft floats.
sorry for that :(
|
No problems! This float stuff it getting to me too :) |
So I extended the There is still some repetitiveness, like applying the error or |
src/tools/miri/src/intrinsics/mod.rs
Outdated
@@ -522,3 +572,121 @@ fn apply_random_float_error_to_imm<'tcx>( | |||
|
|||
interp_ok(ImmTy::from_scalar_int(res, val.layout)) | |||
} | |||
|
|||
// TODO(lorrens): This can be moved to `helpers` when we implement the other intrinsics. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No please only but things in helpers
that are widely useful across Miri. Specific functionality should remain in its specific file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I thought that this would be the case when implementing the foreign_itmes
, since they probably will share this functionality, like asin
and such.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah... we'll figure that out when we get there.
src/tools/miri/src/intrinsics/mod.rs
Outdated
// x^(±0) = 1 for any x, even a NaN | ||
("powf32" | "powf64", [_, exp]) if exp.is_zero() => Some(one), | ||
|
||
// C standard doesn't specify or invalid combination |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a sentence...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, now that I am reading that again... I'll update it, excuse me for my bad English :)
src/tools/miri/src/intrinsics/mod.rs
Outdated
// TODO: not sure about this pattern matching stuff. It's definitly cleaner than if-else chains | ||
// Error code 0158 explains this: https://doc.rust-lang.org/stable/error_codes/E0158.html | ||
// The only reason I did this is to use the same function for powf as for sin/cos/exp/log |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the comment. The code looks nice though :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I should have explained why the error code applies here.
Consider this arm:
// sin(+- 0) = +- 0.
("sinf32" | "sinf64", [input]) if input.is_zero() => Some(*input),
I still have to put an if-guard on it to check that input
is Zero, but in my opinion, it would be a lot nicer if I could do the following:
// sin(+- 0) = +- 0.
("sinf32" | "sinf64", [IeeeFloat::<S>::Zero]) => Some(*input),
If I then were able to make a constant for 1
and maybe other values, I wouldn't need those if-guards. But unfortunately, the compiler doesn't try to see if this can work; it just assumes this to be non-exhaustive, regardless of a _ => ...
arm.
Luckily, you like the code :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't use consts that depend on generics in patterns like that, that is expected.
src/tools/miri/tests/pass/float.rs
Outdated
// TODO: How to test NaN inputs? f*::NAN is not guaranteed | ||
// to be any specific bit pattern (in std). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, since NAN
might be signaling? That'd be quite bad.^^
Please rely on it being not signalling, I'll see if we can fix the docs.
Regarding the library tests, I suggested a plan above for the doc tests. Have you tried that? EDIT: Ah, yes seems you did. :) I assume all tests pass now in the current state of the PR? Also:
I am not convinced we want to do that, so unless @tgross35 asks for it I'd say please stick to the current approach. |
@tgross35 could you have a look at the library diff again? // Miri adds some extra error to float functions, make sure the tests still pass.
const APPROX_DETLA: f32 = if cfg!(miri) { 1e-4 } else { 1e-6 }; |
Yes, they indeed pass now. On the topic of doc and doctests, for example, // part of powf doctest
let x = 2.0_f32;
let abs_difference = (x.powf(2.0) - (x * x)).abs();
assert!(abs_difference <= 8.0 * f32::EPSILON); This is not wrong, but shouldn't it be noted that this only works with small values? |
r? @tgross35 to reflect the current status. Feel free to re-assign to me if the libs changes are fine. |
Let's avoid a conflict with the other in-flight PR, #136457. So let's deal with the algebraic and fast-math operations separately. |
@rustbot author |
☔ The latest upstream changes (presumably #141739) made this pull request unmergeable. Please resolve the merge conflicts. |
bddfc69
to
02f0817
Compare
Alright, so I have updated lots of tests, because Ralf's comment about using env MIRIFLAGS=-Zmiri-many-seeds=0..100 ./x miri --no-doc --no-fail-fast std core coretests -- f32
# and docs
env MIRIFLAGS=-Zmiri-many-seeds=0..100 ./x miri --doc --no-fail-fast std core -- f32 And some tests still failed, especially for So now:
Before pushing, I tested everything again with this command: env MIRIFLAGS=-Zmiri-many-seeds ./x miri --no-fail-fast std core coretests -- f32 f64 And it succeeded (yes, this took some time :)). The float tests from Miri also succeeded with |
/// Miri adds some extra errors to float functions; make sure the tests still pass. | ||
/// These values are purely used as a canary to test against and are thus not a stable guarantee Rust provides. | ||
/// They serve as a way to get an idea of the real precision of floating point operations on different platforms. | ||
const APPROX_DELTA: f32 = if cfg!(miri) { 1e-4 } else { 1e-6 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only use this once in this file, but it's the same reason as in library/std/tests/floats/f32.rs
.
☔ The latest upstream changes (presumably #141753) made this pull request unmergeable. Please resolve the merge conflicts. |
02f0817
to
7a4dd13
Compare
That's fun, I fix conflicts, push, and test. And then 2 more conflicts. Anyway, nothing changed, and the tests were successful. So they should pass now as well. @rustbot ready |
src/tools/miri/tests/pass/float.rs
Outdated
let halve_pi_single = std::f32::consts::FRAC_PI_2; | ||
let halve_pi_double = std::f64::consts::FRAC_PI_2; | ||
let pi_single = std::f32::consts::PI; | ||
let pi_double = std::f64::consts::PI; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't use "single"/"double" terminology, always prefer f32/f64.
Yeah, that was a dumb mistake of mine, during the squash and rebase, I made the mistake of thinking those tests were needed in those files, but they were done in With "clean up the history", do you mean squashing that commit into the previous one? Or just a full squash? |
I mean to squash them until each commit makes independent sense. That may mean a full squash :) |
7a4dd13
to
998f16c
Compare
I think the history is okay now, the first one is the original squash, when my branch was severely outdated. The rest are logical commits I wanted to do. |
Commits 2 and 3 should IMO be merged, there's no reason to have this back-and-forth in the history. Just land the final result immediately. Commit 4 can be merged into the first one. |
@bors2 try |
…<try> Enable Non-determinism of float operations in Miri and change std tests <!-- homu-ignore:start --> <!-- If this PR is related to an unstable feature or an otherwise tracked effort, please link to the relevant tracking issue here. If you don't know of a related tracking issue or there are none, feel free to ignore this. This PR will get automatically assigned to a reviewer. In case you would like a specific user to review your work, you can assign it to them by using r? <reviewer name> --> <!-- homu-ignore:end --> Links to [#4208](rust-lang/miri#4208) and [#3555](rust-lang/miri#3555) in Miri. Non-determinism of floating point operations was disabled in #137594 because it breaks the tests and doc-tests in core/coretests and std. This PR: - enables the float non-determinism but with a lower relative error of 4ULP instead of 16ULP - changes tests that made incorrect assumptions about the operations not to make that assumption anymore (from `assert_eq!` to `assert_approx_eq!`. - changes the `assert_approx_eq!` macro to allow up to 1e-4 to make the tests pass TODO: - I didn't touch the doc tests because I do not know nearly enough to come near them :) - probably change the `assert_approx_eq` to use the same technique as Miri (i.e., using ULP instead of EPSILON) try-job: x86_64-gnu-aux
@bors2 help |
|
change tests in std, core and coretests.
998f16c
to
e9c64a4
Compare
Thanks for the git help, Ralf! |
Links to #4208 and #3555 in Miri.
Non-determinism of floating point operations was disabled in #137594 because it breaks the tests and doc-tests in core/coretests and std.
This PR:
assert_eq!
toassert_approx_eq!
.assert_approx_eq!
macro to allow up to 1e-4 to make the tests passTODO:
assert_approx_eq
to use the same technique as Miri (i.e., using ULP instead of EPSILON)try-job: x86_64-gnu-aux