Skip to content

add ppcf128 library API - #164021

Open
folkertdev wants to merge 5 commits into
rust-lang:mainfrom
folkertdev:ppcf128-library-api
Open

folkertdev wants to merge 5 commits into
rust-lang:mainfrom
folkertdev:ppcf128-library-api

Conversation

@folkertdev

@folkertdev folkertdev commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

View all comments

tracking issue: #163323
cc #161787

The constants are from C, though for MIN and MAX the C values are kind of wrong...

#include <float.h>
#include <stdio.h>
#include <math.h>
#include <string.h>

static long double from_components(double hi, double lo)
{
    double parts[2] = { hi, lo };
    long double x;
    memcpy(&x, parts, sizeof x);
    return x;
}

static void print_const(const char *const_name, long double x)
{
    unsigned char bytes[sizeof x];
    memcpy(bytes, &x, sizeof bytes);

    printf("%s: [\n    ", const_name);
    for (size_t i = 0; i < sizeof bytes; i++) {
        if (i == 8) {
            printf("\n    ");
        }
        printf("0x%02x, ", bytes[i]);
    }
    puts("\n]");
}

int main(void)
{
    print_const("MIN", -LDBL_MAX);
    print_const("MAX", LDBL_MAX);
    print_const("NAN", nanl(""));
    print_const("INFINITY", (long double)INFINITY);
    print_const("NEG_INFINITY", -((long double) INFINITY));

    double hi, lo;

    hi = DBL_MAX;
    lo = DBL_MAX * (DBL_EPSILON / 4.0);

    // LDBL_MAX is not right
    volatile long double MORE_MAX = from_components(hi, lo);
    printf("isfinite(ours): %d\n", isfinite(MORE_MAX));
    printf("ours > LDBL_MAX: %d\n", MORE_MAX > LDBL_MAX);

    hi = DBL_MIN;
    lo = DBL_MIN* (DBL_EPSILON / 4.0);

    // LDBL_MIN is not right
    volatile long double MORE_MIN = from_components(hi, lo);
    printf("isfinite(ours): %d\n", isfinite(MORE_MIN));
    printf("ours < LDBL_MIN: %d\n", MORE_MIN < LDBL_MIN);

    return 0;
}

So, LDBL_MAX is not actually the maximum finite value (I think this is a precision issue, they're off by one bit). I opted to have our MAX actually be the biggest finite value, and MIN the smallest.

@folkertdev folkertdev added the F-c_longdouble `#![feature(c_longdouble)]` label Oct 9, 2026
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Oct 9, 2026
@folkertdev
folkertdev force-pushed the ppcf128-library-api branch from 1c271dd to 6a31122 Compare October 9, 2026 21:27
@rust-log-analyzer

This comment has been minimized.

@folkertdev

Copy link
Copy Markdown
Contributor Author

r? tgross35
cc @beetrees

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.

@rustbot

rustbot commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

tgross35 is currently at their maximum review capacity.
They may take a while to respond.

@folkertdev
folkertdev marked this pull request as ready for review October 12, 2026 11:27
@rustbot

rustbot commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

rustc_codegen_cranelift is developed in its own repository. If possible, consider making this change to rust-lang/rustc_codegen_cranelift instead.

cc @bjorn3

stdarch is developed in its own repository. If possible, consider making this change to rust-lang/stdarch instead.

cc @Amanieu, @sayantn

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 12, 2026
Comment thread library/stdarch/crates/core_arch/src/powerpc/mod.rs

@tgross35 tgross35 Oct 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread library/stdarch/crates/core_arch/src/powerpc/mod.rs Outdated
Comment thread library/stdarch/crates/core_arch/src/powerpc/mod.rs Outdated
Comment thread library/stdarch/crates/core_arch/src/powerpc/mod.rs
Comment thread library/stdarch/crates/core_arch/src/powerpc/mod.rs Outdated
#[unstable(feature = "powerpc_ppcf128", issue = "163323")]
pub const unsafe fn from_components_unchecked(large: f64, small: f64) -> Self {
unsafe { core::mem::transmute([large, small]) }
}

@tgross35 tgross35 Oct 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@tgross35 tgross35 Oct 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From<f16, f32, f64> probably also makes sense

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or perhaps a general From<T> where f64: From<T>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread library/stdarch/crates/core_arch/src/powerpc/mod.rs
Comment thread library/coretests/tests/num/ppcf128.rs
Comment thread library/stdarch/crates/core_arch/src/powerpc/mod.rs Outdated
@rust-log-analyzer

This comment has been minimized.

#[unstable(feature = "powerpc_ppcf128", issue = "163323")]
#[rustc_const_unstable(feature = "powerpc_ppcf128", issue = "163323")]
#[inline]
pub const fn to_bits(self) -> u128 {

@beetrees beetrees Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@beetrees beetrees Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rust-log-analyzer

This comment has been minimized.

@tgross35

Copy link
Copy Markdown
Member

Some things were marked as resolved but looks like might not have been pushed

@rust-log-analyzer

This comment has been minimized.

@folkertdev

Copy link
Copy Markdown
Contributor Author

I've pushed everything I have now

@rust-log-analyzer

This comment has been minimized.

Comment on lines +18 to +19
/// large component. Creating a value with components that are not in normal form
/// (e.g. with [`ppcf128::from_components_unchecked`]) is [undefined behavior].

@tgross35 tgross35 Oct 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an open question that has not been answered yet I think -- is this a validity invariant or a safety invariant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@tgross35 tgross35 Oct 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

View changes since the review

///
/// # Examples
///
#[doc = ppcf128_doctest_prelude!()]

@tgross35 tgross35 Oct 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that is kind of annoying. Could something like

#[doc(test(attr(
feature(cfg_target_has_reliable_f16_f128),
allow(internal_features, unused_features)
)))]
work maybe? If it lets you add a cfg.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rustbot

This comment has been minimized.

@rust-bors

This comment has been minimized.

@rustbot

rustbot commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

F-c_longdouble `#![feature(c_longdouble)]` S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants