Repository navigation
add ppcf128 library API - #164021
add ppcf128 library API#164021folkertdev wants to merge 5 commits into
ppcf128 library API#164021Conversation
1c271dd to
6a31122
Compare
This comment has been minimized.
This comment has been minimized.
6a31122 to
355c9be
Compare
|
r? tgross35 We can debate what should be added for this type (and x87 f80 soon). Conversions to/from components and bytes seem useful to me. Equality and ordering are easy to provide and may come in handy. The constants are just kinda because we can. The main concern here is really that for macros it is convenient to have an API surface that matches other floats (as much as possible). I don't anticipate these methods and values getting a lot of use on their own. |
|
|
|
cc @bjorn3
|
There was a problem hiding this comment.
The docs for ppcf128 should be improved in this PR, especially since we're adding invariants but I'm not exactly sure what they are. And we should tell the user what to expect for component and bitwise conversions.
E.g. the term "normal form" needs to be defined, maybe with a table of how +0.0, -0.0, infinity, qNaN etc are represented. Also something about NaNs similar to a significantly simplified version of https://doc.rust-lang.org/std/primitive.f32.html#nan-bit-patterns (or just link that and note any differences).
There was a problem hiding this comment.
hmm, what should that look like. +0.0 can be either ppcf128::from_components(0.0, 0.0) or ppcf128::from_components(0.0, -0.0).
For NaN any value where the first component is NaN is a NaN. We don't have any operations that would clear NaN at the moment I believe, and we don't plan on adding any.
There was a problem hiding this comment.
You could probably just put it into a table like this:
| Value | Preferred Representation | Allowed Representations | Notes |
|---|---|---|---|
| +0.0 | (+0.0, +0.0) | (+0.0, ±0.0) | Nonzero second components are not allowed |
| -0.0 | (-0.0, -0.0) | (-0.0, ±0.0) | Nonzero second components are not allowed |
| +∞ | (+∞, +0.0) | (+∞, ±0.0) | Nonzero second components are not allowed |
| -∞ | (-∞, -0.0) | (-∞, ±0.0) | Nonzero second components are not allowed |
| NaN | (NaN, +0.0) | (NaN, *) | Any NaN is allowed in the first component, any representation is allowed in the second component |
| x ∈ ℝ | (x₁, x₂) | N/A | x₁ + x₂ must round to x₁ |
Where "Preferred representation" is what the constants are and probably what we get from operations, and "Allowed Representations" are what you can have without it being UB.
| #[unstable(feature = "powerpc_ppcf128", issue = "163323")] | ||
| pub const unsafe fn from_components_unchecked(large: f64, small: f64) -> Self { | ||
| unsafe { core::mem::transmute([large, small]) } | ||
| } |
There was a problem hiding this comment.
So this does add the precondition even though that's not what is currently in https://github.com/rust-lang/rfcs/pull/4003/changes#diff-62de97c7dda87de848b9cc9e60b093d8da9493c25b42ef407537ad0c5e29ae82R144? That will need to be rectified at some point, probably worth a FIXME.
There was a problem hiding this comment.
We sort of realized that LLVM does not clearly specify the assumptions, and it is very likely that we will run into miscompilations if we don't enforce normalizaton.
There was a problem hiding this comment.
From<f16, f32, f64> probably also makes sense
There was a problem hiding this comment.
Or perhaps a general From<T> where f64: From<T>
There was a problem hiding this comment.
I think doing separate impls is more consistent with other float types: for example, we don't impl From<T> for f128 where f64: From<T>.
There was a problem hiding this comment.
Those require separate bodies, whereas every From<T> for ppcf128 has to go via f64 anyway. I can't really think of any reasonable exception we would have in std.
But I suppose it doesn't hurt to be consistent. Could be a from_via_f64! macro and cover the integers as well.
There was a problem hiding this comment.
Just the three From implementations seem fine to me. We can add more later, but I don't think the ergonomics will be a big deal for this type.
This comment has been minimized.
This comment has been minimized.
2b4f5be to
ae02207
Compare
| #[unstable(feature = "powerpc_ppcf128", issue = "163323")] | ||
| #[rustc_const_unstable(feature = "powerpc_ppcf128", issue = "163323")] | ||
| #[inline] | ||
| pub const fn to_bits(self) -> u128 { |
There was a problem hiding this comment.
Given the endianess-dependency footgun, I'm not sure to_bits is worthwhile having - I'm struggling to think of any use cases that wouldn't be better served by to_{ne,le,be}_bytes() or to_components().{0,1}.to_bits(). Thoughts?
There was a problem hiding this comment.
I do think there is value overall in having a consistent interface across float types (e.g. for macros, but also for humans so that there are fewer edge cases to remember).
It also (and this is really why I added it now) serves as a good place to document the endianness behavior.
There was a problem hiding this comment.
I feel like having consistency here would hide the fact that ppcf128 is not like other float types - due to the endianness problem storing the bits would be a footgun (unlike other float types where the bits are target independent - to_{ne,le,be}_bytes provide consistent behaviour across types here so are better for this use case), and manipulating the bits from to_bits like you would another float type for soft-float routines would also likely be incorrect as it is actually two floats in an endian-dependent order (to_components is much less error-prone for this use case). I'm having difficulty thinking of a useful macro example where calling to_bits and performing the same operations on it as would be done on another float type (e.g. as part of a macro) wouldn't be a logic error.
There was a problem hiding this comment.
Code using to_bits often has a from_bits, which would have to be unsafe here. I think that makes the generic code argument less convincing.
There was a problem hiding this comment.
Sure, it's more a general principle, that we can deviate from if there is good reason.
I'll remove the method and try to put the doc example somewhere because I do think it illustrates the endianness problem well.
This comment has been minimized.
This comment has been minimized.
|
Some things were marked as resolved but looks like might not have been pushed |
ae02207 to
24792b3
Compare
This comment has been minimized.
This comment has been minimized.
05ef1fc to
df84afb
Compare
|
I've pushed everything I have now |
This comment has been minimized.
This comment has been minimized.
| /// large component. Creating a value with components that are not in normal form | ||
| /// (e.g. with [`ppcf128::from_components_unchecked`]) is [undefined behavior]. |
There was a problem hiding this comment.
I wonder if this should say "using a value created with" rather than "creating a value with"? I think this would be more consistent with behavior elsewhere, where it's the use of something invalid rather than the existence of something invalid that is unsound.
Cc @RalfJung
There was a problem hiding this comment.
This is an open question that has not been answered yet I think -- is this a validity invariant or a safety invariant.
There was a problem hiding this comment.
This is an open question that has not been answered yet I think -- is this a validity invariant or a safety invariant.
This is tricky. We're really trying to guard against the optimizer doing something weird. We deduce that LLVM optimizes based on the value being in normal form, so if that is ever not the case, the program might miscompile. As far as we're aware, LLVM never formally states that it requires the value to be in normal form though, it just falls out of the logic.
So, I think that means it's a validity invariant?
There was a problem hiding this comment.
Not necessarily. If it's only UB to perform an operation like fadd on a bad value, that could be a safety invariant. It's a validity invariant if LLVM is allowed to speculatively execute fadd though. So it's probably safer to say that it is a validity invariant.
That means Miri has to check it. (Though that can be a separate PR.)
LLVM never formally states that it requires the value to be in normal form though, it just falls out of the logic.
Could you file an issue on the LLVM side with an example of the misbehavior you found? This should be documented in the LangRef.
| /// Combining a large `f64` with a small one provides meaningful extra precision, while | ||
| /// being able to use existing `f64` hardware instructions. | ||
| /// | ||
| /// # Normal form |
There was a problem hiding this comment.
Is "normal form" a term from somewhere? If not, perhaps uses of the term should be "valid representations" because it's usable vs. invalid. Rather than something like x87_f80 where there's a preferred form and pseudo forms, but you can use either without things breaking.
| /// | ||
| /// # Examples | ||
| /// | ||
| #[doc = ppcf128_doctest_prelude!()] |
There was a problem hiding this comment.
Nit: I'd move the ``` out of this macro and just put it before, since I know some syntax highlighters naively look for code fences in the docs which would break.
There was a problem hiding this comment.
I know, but we need to emit
/// ```only-powerpc
for doc builds. I think that all needs to be on one line, so I don't really see another way than having it just be part of the macro.
There was a problem hiding this comment.
Ah that is kind of annoying. Could something like
rust/library/core/src/num/f128.rs
Lines 153 to 156 in 4ebd258
cfg.
There was a problem hiding this comment.
found a workaround with all(doc, not(doctest)), so the examples are shown in the docs, but don't actually get run by doctest on non-powerpc targets.
So now the triple backtics are no longer inside the macro, and overall the examples get a bit simpler.
| #[unstable(feature = "powerpc_ppcf128", issue = "163323")] | ||
| #[rustc_const_unstable(feature = "powerpc_ppcf128", issue = "163323")] | ||
| #[inline] | ||
| pub const fn to_bits(self) -> u128 { |
There was a problem hiding this comment.
Code using to_bits often has a from_bits, which would have to be unsafe here. I think that makes the generic code argument less convincing.
df84afb to
d1f8c30
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
d1f8c30 to
7f937e4
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
View all comments
tracking issue: #163323
cc #161787
The constants are from C, though for
MINandMAXthe C values are kind of wrong...So,
LDBL_MAXis not actually the maximum finite value (I think this is a precision issue, they're off by one bit). I opted to have ourMAXactually be the biggest finite value, andMINthe smallest.