-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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 for float_minimum_maximum #91079
Comments
Just so it's tracked here: This currently does not compile to the corresponding instruction on WASM (fmin / fmax) and I believe Aarch64 has an instruction for this too (FMIN / FMAX), which it also doesn't compile to. |
I believe there is an LLVM instruction for this. We can teach Rust to emit the appropriate LLVMIR directly, at the cost of adding the weight of an intrinsic. As we have discussed doing so for the portable SIMD API anyways, it might be profitable to do this in general. |
LLVM does have an intrinsic for this, but as the PR that added these showed (https://godbolt.org/z/czz71M9KT), it doesn't actually work yet, which is why rustc isn't using it to implement these. |
Ah, Damn. |
Everything that follows will be stated in terms of the Does the function have to be called |
Nominated at the request of @Urgau. |
The confusion between We'll need some more input: How do other languages and libraries handle this? How are these named in the standard? What are the use cases for |
I agree that having pairs of functions such that Here are some suggestions to add to the brainstorm. Any of these can be
I think that |
To throw some other ideas in the mix: By analogy with the distinction between quiet NaN and signaling NaN, maybe the new minimum (maximum) function should be called I also like IMO |
This might also influence the naming situation: There's more than just these two algorithms. Rust's max is IEEE Std 754-2008 maxNum |
The new one is arguably the "best" one, as it's the one that behaves most similarly to everything else with respect to NANs -- the other binary functions like So one option might be to deprecate (Thought that does have the obvious downside of being different from the |
C/C++/Swift doesn't (yet ?). C#/Python have the IEEE 754-2019
They are named
(I don't really understand the question) but I would say none. Only one should exist, in IEEE 754-2019 they deprecated
+1 Let me know if you need more input. Happy to help. |
I still feel like the standard is being misrepresented here. There's 4 functions now, not 2: One pair does NaN propagation, the other does NaN absorbtion. The 2008 variant turned into the new NaN absorbtion variant, though it does differ in how 0 is being treated. Imo we should just add (Also lmao, I just now see that they literally say that we should have all of them as part of the image I quoted) Also here's a list I made quite a while ago that lists the different instructions and languages: https://gist.github.com/CryZe/30cc76f4629cb0846d5a9b8d13144649
They don't match the semantics, they don't follow either standard. |
When used in isolation as a normal binary function between two calculated values, I'd say that But when considered as a way to collapse a full sequence, both are usable in different cases. You can think of them kinda like
|
Thanks for the clarifications. :) The names
I agree it's a shame that we already have our current
Thanks! It's a great overview, but the data is somewhat depressing. :( |
I'm not sure it's a good idea to deprecate max/min with their existing names given that the segment of code which is likely to care about the difference in semantics between 2008 and 2019 is rather small. It's not exactly an upgrade for people using the API, just different. So lots of downstream churn for no advantage. Seems like it would be better just to flip to the new semantics with an edition bump, if that's possible. Though it will likely break anyone who was relying on the performance of min and max being acceptable on arm compiler explorer. (Won't matter for x86 because you're already forced to write your own min and max to get good codegen there) |
That's "not true", as ARM has instructions for both 2019 behaviors as seen here: https://gist.github.com/CryZe/30cc76f4629cb0846d5a9b8d13144649
|
I'm not sure that gist is really correct. The arm docs for FMINNM state.
"NaNs are handled according to the IEEE 754-2008 standard. If one vector element is numeric and the other is a quiet NaN, the result that is placed in the vector is the numerical value, otherwise the result is identical to FMIN (scalar)."
…-------- Original Message --------
On 10 Sep 2022, 14:22, Christopher Serr wrote:
> Though it will likely break anyone who was relying on the performance of min and max being acceptable on arm
That's "not true", as ARM has instructions for both 2019 behaviors as seen here: https://gist.github.com/CryZe/30cc76f4629cb0846d5a9b8d13144649
What's interesting to me though is that LLVM compiles the current behavior to FMINNM / FMAXNM, even though that's not matching the 2008 standard. The ARM documentation states that these instructions behave differently based on the value in the AH register. I'm currently looking into what exactly the changes are.
—
Reply to this email directly, [view it on GitHub](#91079 (comment)), or [unsubscribe](https://github.com/notifications/unsubscribe-auth/AAAM36NEM7KIIFVNLGUH5LDV5R4PTANCNFSM5INX2B3A).
You are receiving this because you commented.Message ID: ***@***.***>
|
That documentation is correct, but it doesn't disagree with my list. The difference in behavior in the old and new standard is in the handling of -0 and +0.
|
#[inline(never)]
fn hardware_max(a: f32, b: f32) -> f32 {
a.max(b)
}
#[inline(always)]
fn llvm_evaluated_max(a: f32, b: f32) -> f32 {
a.max(b)
}
fn main() {
dbg!(hardware_max(0.0, 0.0).is_sign_positive());
dbg!(hardware_max(0.0, -0.0).is_sign_positive());
dbg!(hardware_max(-0.0, 0.0).is_sign_positive());
dbg!(hardware_max(-0.0, -0.0).is_sign_positive());
println!();
dbg!(llvm_evaluated_max(0.0, 0.0).is_sign_positive());
dbg!(llvm_evaluated_max(0.0, -0.0).is_sign_positive());
dbg!(llvm_evaluated_max(-0.0, 0.0).is_sign_positive());
dbg!(llvm_evaluated_max(-0.0, -0.0).is_sign_positive());
} |
It does not follow the 2019 semantics for NaN though? Or am I mistaken about what 2019 says. e.g it should be returning NaN if either argument is NaN. As opposed to 2008 semantics where it returns the *other* value if either argument is NaN.
…-------- Original Message --------
On 10 Sep 2022, 18:46, Christopher Serr wrote:
I ran it on both qemu and actual hardware to confirm that what I said above is indeed true and FMAXNM is indeed following the 2019 semantics and LLVM emits the wrong instruction:
[https://i.imgur.com/TxCndLb.png](https://camo.githubusercontent.com/18c92e43247cce9059680cd21ac47ca1b33e82502faa219867317bb8ad89692e/68747470733a2f2f692e696d6775722e636f6d2f5478436e644c622e706e67)
#
[
inline
(
never
)
]
fn
hardware_max
(
a
:
f32
,
b
:
f32
)
->
f32
{
a
.
max
(
b
)
}
#
[
inline
(
always
)
]
fn
llvm_evaluated_max
(
a
:
f32
,
b
:
f32
)
->
f32
{
a
.
max
(
b
)
}
fn
main
(
)
{
dbg
!
(
hardware_max
(
0.0
,
0.0
)
.is_sign_positive
(
)
)
;
dbg
!
(
hardware_max
(
0.0
, -
0.0
)
.is_sign_positive
(
)
)
;
dbg
!
(
hardware_max
(
-
0.0
,
0.0
)
.is_sign_positive
(
)
)
;
dbg
!
(
hardware_max
(
-
0.0
, -
0.0
)
.is_sign_positive
(
)
)
;
println
!
(
)
;
dbg
!
(
llvm_evaluated_max
(
0.0
,
0.0
)
.is_sign_positive
(
)
)
;
dbg
!
(
llvm_evaluated_max
(
0.0
, -
0.0
)
.is_sign_positive
(
)
)
;
dbg
!
(
llvm_evaluated_max
(
-
0.0
,
0.0
)
.is_sign_positive
(
)
)
;
dbg
!
(
llvm_evaluated_max
(
-
0.0
, -
0.0
)
.is_sign_positive
(
)
)
;
}
—
Reply to this email directly, [view it on GitHub](#91079 (comment)), or [unsubscribe](https://github.com/notifications/unsubscribe-auth/AAAM36KISOGKRL74LNWQ3RLV5S3OFANCNFSM5INX2B3A).
You are receiving this because you commented.Message ID: ***@***.***>
|
The 2019 standard has both NaN absorbtion ( |
@CryZe I think your comments are causing unnecessary confusion here. IEEE-2008 leaves the behavior for signed zero unspecified (either operand may be returned). An instruction that follows IEEE-2019 signed zero behavior also conforms to IEEE-2008 -- just not the other way around. LLVM is correct to use FMAXNM to lower |
Yeah you are right I was under the impression that it actually specified it to choose either the first or second operand (in particular because the ARM documentation explicitly supports it via the alternate floating point mode, but it looks like I was misled), but it seems like it's implementation defined in the 2008 standard, the C standard, technically in Rust and in LLVM. I adjusted my list to reflect that and updated the wrong comments. So one option would be to upgrade the |
it isn't actually backward compatible because IEEE 754-2008 background on min/max in IEEE 754-2019: https://grouper.ieee.org/groups/msc/ANSI_IEEE-Std-754-2019/background/minNum_maxNum_Removal_Demotion_v3.pdf |
While this is technically true, this comes with the caveat that people don't actually use the IEEE 754-2008 definition of maxNum and instead use "maxNum, but with sNaN treated the same ways as qNaN". So basically, what IEEE 754-2008 maxNum is in practice, is IEEE 754-2019 maximumNumber with unspecified signed zero behavior. Here is Rust's own definition:
And this is LLVM's more extensive definition:
And now we are in the wonderful position where there are actually 5 different notions, all commonly accepted, of what a float maximum is supposed to be :) |
I'd like to add to this issue that I ran into It's a tricky situation, but at the least the documentations should reflect these performance considerations. |
Note that Intel specifically documents
Whereas I don't think we should generally add "but it might be faster, on some platforms, to do something that works differently" to method documentation. ( |
I think the correct place to document this could be the beginners guide or if there's gonna be a chapter in "the book" about portable simd. |
Rather than having alternate names for max and min, why not use attributes instead? This way the attribute would specify how max/min is applied for a certain block of code, with consistent max/min algorithms and a well understood and expected output. This of course assumes different max/min behaviors are seldom intermingled together. Using attributes also allows for adjusting code to different max/min mechanisms for different CPU types and also for different IEEE 754 standards as they’re published and adjusted over time. |
In #130568, I'm making these methods |
Feature gate:
#![feature(float_minimum_maximum)]
This is the tracking issue for the
f{32,64}::{min,max}imum
functions.It provide IEEE 754-2019
minimum
andmaximum
functions to f32/f64.Public API
Steps / History
const fn
#130568Unresolved Questions
minimum
andmaximum
) or something else ? Tracking Issue for float_minimum_maximum #91079 (comment)min
/max
) ?The text was updated successfully, but these errors were encountered: