You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Undefined Behavior: Changing MXCSR to _MM_ROUND_TOWARD_ZERO on x86 and x86_64. #776
In Rust, modifying the rounding mode in x86 and x86_64 by modifying the MXCSR register (through _mm_setcsr or inline assembly) is considered immediate undefined behavior.
The key element of using inline assembly is that you can't ever leave an inline assembly block without restoring MXCSR to the default state, or you hit the same UB problem.
You'd have to do the entire round-toward-zero operation using only inline assembly.
This "flush to zero" leads to important performance improvements.
Brainstorming further:
Could the entire round-toward-zero operation can be summarized as a function call, so the assembly would end up quite small (function declaration, arguments prep, function call) ?
You cannot leave flush to zero active while executing rust code, it's simply not allowed.
So you can't have functions to toggle the effect on/off, and you also can't have a function that takes a fn pointer to run with flushing set which changes the state and calls back into rust while the state is changed.
This applies equally to all floating point environment settings, though flush to zero is usually the only one people try to change.
This "flush to zero" leads to important performance improvements.
Can you elaborate on these? From @Lokathor's comments, it sounds like these just aren't available in sound Rust today, and it might be best to do without.
There is no benchmark specifically for subnormal floating point values for Rapier. And I am not sure whether it will be easy to construct one or not.
However, I made a generic benchmark with just a random arithmetic operations: https://github.com/im-0/rust-denormals-bench
There is a 50x performance degradation on modern Intel CPUs when subnormal values are encountered.
I totally forgot about this. So a large portion of the issue is the fact that Rust guarantees that all of its floating point arithmetic matches that of IEE-754-whatever-the-current-number-is. There is no sound way, in stable Rust anyways, to get the behavior of -ffast-math without there being a risk of UB. This is because, well, the compiler makes this assumption, everywhere.
Potential solutions? None, really, besides writing these routines utilizing inline assembly. With the recent addition of... Raw functions I think they're called, this may not be so bad, but still, it is an optimization barrier, and error prone.
Now, if you're willing to utilize intrinsics in nightly, this could be solved I think potentially with those. See fadd_fast for one such example. It would be nice if these intrinsics were provided with a clean API on stable, but currently they're not, and this is the only likely to be sound way to achieve this behavior.
I've been meaning to contribute to the standard library for quite some time now, so perhaps this is the first thing I'll try to propose: making these APIs accessible, safely, or, safer.
Though that may require some, additional analysis on behalf of rustc... Maybe... I'll have to check godbolt.
Starting to believe that behavior detailing the behavior of some instruction should probably not be defined by some global mutable state anyone can modify... Oh well. Thanks x86!
@herabit the usage of MXCSR in Rapier has nothing to do with rounding. Current code enables "Flush [denormals] to Zero" and "Denormals Are Zero" flags (still UB in Rust).
And... could you please elaborate on how -ffast-math is relevant here?
@im-0 I made this issue nearly a year ago. It may not have to do with rounding, or I may have copied the wrong flag as I was trying to figure out how to use MXCSR in Rust properly, was reading the standard library, and then came across its problematic usage here while searching GitHub for examples on how to do it correctly.
Point is, currently, there is zero way of changing these flags without incurring undefined behavior.
This is related to -ffast-math, because one of the major features of -ffast-math, at least on x86 and x86_64, is flushing denormals to zero. Handling denormals properly according to IEEE-754 is extremely expensive, even in hardware, so the "flush to zero" flag is a part of -ffast-math.
If there was safe-r ways of accessing fast-math arithmetic within Rust, that would be the preferred way of accomplishing these tasks, without requiring the utilization of inline assembly.
@sebcrozet sorry for the ping, but it seems it was you who introduced these calls in #909 .
i have been looking at the problem for the last couple days and the prospects for a fix do not seem great.
i have no clue how to measure this reliably, so given you made the PR and the blogpost i was wondering if you had insight on how much this specific guard impacts performance on the relevant architectures.
At this point it'd just be better as an unstable feature flag, that's opt-in. If you really need the performance and do not give half a shit about, well, soundness... It's a good medium until these things are, well, able to be made more sound.
Activity
ThierryBerger commentedon Dec 20, 2024
Thanks for the issue ; I admit I'm not sure how to research "using inline assembly instead".
Lokathor commentedon Dec 20, 2024
The key element of using inline assembly is that you can't ever leave an inline assembly block without restoring MXCSR to the default state, or you hit the same UB problem.
You'd have to do the entire round-toward-zero operation using only inline assembly.
ThierryBerger commentedon Jan 6, 2025
A bit more context: this operation occurs when using
enable_flush_to_zero, for example:rapier/src/pipeline/physics_pipeline.rs
Line 270 in cf77b5b
This "flush to zero" leads to important performance improvements.
Brainstorming further:
Could the entire round-toward-zero operation can be summarized as a function call, so the assembly would end up quite small (function declaration, arguments prep, function call) ?
Lokathor commentedon Jan 6, 2025
You cannot leave flush to zero active while executing rust code, it's simply not allowed.
So you can't have functions to toggle the effect on/off, and you also can't have a function that takes a fn pointer to run with flushing set which changes the state and calls back into rust while the state is changed.
This applies equally to all floating point environment settings, though flush to zero is usually the only one people try to change.
Ralith commentedon Feb 15, 2025
Can you elaborate on these? From @Lokathor's comments, it sounds like these just aren't available in sound Rust today, and it might be best to do without.
im-0 commentedon Sep 20, 2025
There is no benchmark specifically for subnormal floating point values for Rapier. And I am not sure whether it will be easy to construct one or not.
However, I made a generic benchmark with just a random arithmetic operations: https://github.com/im-0/rust-denormals-bench
There is a 50x performance degradation on modern Intel CPUs when subnormal values are encountered.
herabit commentedon Sep 21, 2025
I totally forgot about this. So a large portion of the issue is the fact that Rust guarantees that all of its floating point arithmetic matches that of IEE-754-whatever-the-current-number-is. There is no sound way, in stable Rust anyways, to get the behavior of
-ffast-mathwithout there being a risk of UB. This is because, well, the compiler makes this assumption, everywhere.Potential solutions? None, really, besides writing these routines utilizing inline assembly. With the recent addition of... Raw functions I think they're called, this may not be so bad, but still, it is an optimization barrier, and error prone.
Now, if you're willing to utilize intrinsics in nightly, this could be solved I think potentially with those. See fadd_fast for one such example. It would be nice if these intrinsics were provided with a clean API on stable, but currently they're not, and this is the only likely to be sound way to achieve this behavior.
I've been meaning to contribute to the standard library for quite some time now, so perhaps this is the first thing I'll try to propose: making these APIs accessible, safely, or, safer.
Though that may require some, additional analysis on behalf of
rustc... Maybe... I'll have to check godbolt.herabit commentedon Sep 21, 2025
Oh, yeah, I forgot about this blog post Someone's Been Messing With My Subnormals! that, introduces a related issue.
Starting to believe that behavior detailing the behavior of some instruction should probably not be defined by some global mutable state anyone can modify... Oh well. Thanks
x86!herabit commentedon Sep 21, 2025
Or, just... Avoid
-ffast-math(see here)... Why is this shit so hard??????Guess that explains why there's no stable API for this... It's just fucked.
im-0 commentedon Sep 21, 2025
@herabit the usage of MXCSR in Rapier has nothing to do with rounding. Current code enables "Flush [denormals] to Zero" and "Denormals Are Zero" flags (still UB in Rust).
And... could you please elaborate on how
-ffast-mathis relevant here?herabit commentedon Sep 21, 2025
@im-0 I made this issue nearly a year ago. It may not have to do with rounding, or I may have copied the wrong flag as I was trying to figure out how to use
MXCSRin Rust properly, was reading the standard library, and then came across its problematic usage here while searching GitHub for examples on how to do it correctly.Point is, currently, there is zero way of changing these flags without incurring undefined behavior.
This is related to
-ffast-math, because one of the major features of-ffast-math, at least onx86andx86_64, is flushing denormals to zero. Handling denormals properly according to IEEE-754 is extremely expensive, even in hardware, so the "flush to zero" flag is a part of-ffast-math.If there was safe-r ways of accessing
fast-matharithmetic within Rust, that would be the preferred way of accomplishing these tasks, without requiring the utilization of inline assembly.I hope this makes, sense.
Morgane55440 commentedon Jun 8, 2026
@sebcrozet sorry for the ping, but it seems it was you who introduced these calls in #909 .
i have been looking at the problem for the last couple days and the prospects for a fix do not seem great.
i have no clue how to measure this reliably, so given you made the PR and the blogpost i was wondering if you had insight on how much this specific guard impacts performance on the relevant architectures.
herabit commentedon Jun 21, 2026
At this point it'd just be better as an unstable feature flag, that's opt-in. If you really need the performance and do not give half a shit about, well, soundness... It's a good medium until these things are, well, able to be made more sound.
Lokathor commentedon Jun 24, 2026
I think it would be a poor idea to have a crate feature to enable unsoundness in the code
sebcrozet commentedon Aug 7, 2026
The MXCSR register modifications have been removed entirely in the latest version of rapier.