Skip to content

add c_longdouble RFC - #3

Closed
folkertdev wants to merge 7 commits into
masterfrom
c-longdouble
Closed

add c_longdouble RFC#3
folkertdev wants to merge 7 commits into
masterfrom
c-longdouble

Conversation

@folkertdev

@folkertdev folkertdev commented Aug 18, 2026

Copy link
Copy Markdown
Owner

NOTE: this is a PR against my own fork, meant as a preview.

Please use inline comments rather than comments on the PR to keep different threads separate.


Summary

Add core::ffi::c_longdouble, to match C long double, and its prerequisites.

use core::ffi::c_longdouble;

extern "C" fn f(x: c_longdouble) -> c_longdouble {
    x
}

This type is highly platform-specific, A long double can be:

  • f32, e.g. on AVR
  • f64, e.g. on many 32-bit targets and some 64-bit target using MUSL
  • f128, e.g. on aarch64 and riscv

So far so good, but on some platforms this type corresponds to a type that Rust cannot currently express:

  • X87 f80 on x86 and x86_64, an IEEE (ish) 80-bit floating point type
  • IBM f128 on powerpc and powerpc64, a "double double"

This RFC proposes the addition of these two types, with extremely minimal APIs, so that a portable core::ffi::c_longdouble can be defined.

Rendered

Comment thread text/0000-c_longdouble.md
};
```

LLVM now defines what `long double` resolves to for each target triple ([source](https://github.com/llvm/llvm-project/blob/f6b3f9399e98ef8b79388176e94c910d67669e1f/llvm/lib/TargetParser/Triple.cpp#L2541-L2597)).

@Gelbpunkt Gelbpunkt Aug 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I didn't really know where in this file to open the thread (here or later under "Availability"), so sorry in advance if this is the wrong place.

While this does indeed return the default type of long double for a triple, Clang can be configured to use different defaults, which is quite common on powerpc64le-unknown-linux-gnu.

The type of long double on this target is configured to an IEEE quad instead of the IBM double-double by many distributions via Clang's PPC_LINUX_DEFAULT_IEEELONGDOUBLE define (source).

While GCC and LLVM default to IBM double-double, the latest releases of Debian (Trixie and newer, ref), Fedora (36 and newer, ref) and Ubuntu (since 25.10, don't have a ref at hand) all use IEEE quads. Big-endian glibc ppc64 distributions and targets are unaffected because at least GCC wants VSX for -mabi=ieeelongdouble, which is way above their baseline (VSX is POWER7+, virtually all big endian distributions target POWER4 or POWER6 as a baseline).

This makes it hard to actually determine the correct type for long double to resolve to (it's impossible via current cfg options). All of the aforementioned distributions and those that use IBM double-double (like RHEL8/9, Debian bookworm, bullseye and e.g. Ubuntu 24.04 LTS) currently use the same powerpc64le-unknown-linux-gnu target. Resolving long double to f128ppc for this target would be incorrect for the aforementioned distributions and result in wrong behavior.

One could argue that this doesn't matter because distributions will be patching the toolchain accordingly, but with rustup we ship a bundled LLVM that is configured for IBM double-double by default and I don't see Rust guessing this at runtime (though feel free to correct me if that's trivial to do).

This means we would probably have to split the powerpc64le-unknown-linux-gnu target into two: one with IBM double-double and one with IEEE quad long double. I don't think anyone, especially the LLVM folks, would be particularly happy about this. I'm unsure what to do here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Target modifiers (rust-lang/rust#136966) seem like they could be part of the solution here - that way we could allow both options without having an entire new target. It's also definitely worth discussing what we want the default for the 64-bit PowerPC targets (which have vector support) to be - we could have it default to having actual f128 as long double and only require the target modifier for using f128ppc. This would however make it inconsistent with the 32-bit PowerPC targets, which can't default to f128 as they don't have altivec enabled by default.

@Gelbpunkt Gelbpunkt Aug 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm aware that target modifiers exist, and this definitely should be a target modifier, but there's some issues with only having a single powerpc64le-unknown-linux-gnu target and an additional target modifier. Keeping a single triple would mean that someone could download a build for it via rustup and compile code without setting the target modifier explicitly on e.g. RHEL8 and Fedora 44 and they would end up with code that's ABI incompatible with C on one of the two.

I see three options here:

  1. Split it into two targets. This is essentially two ABIs using the same target triple in C compilers and we have precedent in Rust for creating new targets for this (see e.g. powerpc64-unknown-linux-gnuelfv2 and powerpc64-unknown-linux-gnu). If we do this, I would recommend resolving c_longdouble to f128 in powerpc64le-unknown-linux-gnu since this is clearly the future and being adopted by distributions and adding a new target with ibm128 (or whatever the name ends up at) as c_longdouble

  2. Consider ibm128 completely out of scope for powerpc64le-unknown-linux-gnu and require c_longdouble to be f128 on the target. Existing code prior to the introduction of the c_longdouble type will still work fine when built for an ibm128 distro, but using the type requires a compatible toolchain. This is the easiest way forward if it is acceptable

  3. Figure out a way to at compile time detect the type of c_longdouble that the target we're building for is using and resolve it to this type. This is tricky because core is prebuilt for one of the two and a way to detect this is not straightforward to me. Also, anything distributed via rustup must then not make use of c_longdouble at FFI boundary to system libraries since then it wouldn't run on one of the two ABIs anymore (this is trivial).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It's my opinion that the compiler should have control of all these aliases anyway, there have been a few times we've wanted this (e.g. a flag to make c_char unsigned). Could just be a builtin macro allowing type c_longdouble = c_type!(c_longdouble);.

But for the initial work just having the aliases is fine.

@beetrees beetrees Aug 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[...] I see three options here: [...]

I see your point. In that case, personally, I think having c_longdouble = f128 on targets which have sufficient features and allowing for separate targets with c_longdouble = ibm128, similar to powerpc64-unknown-linux-gnuelfv2, seems like a reasonable way forward. This RFC doesn't necessarily have to create those targets itself IMO - they could also be proposed separately by target maintainers if they have an interest before the feature is stabilised (I imagine not all PowerPC targets will have the need for both variants). Ultimately target maintainers (such as yourself) should definitely be involved in working out which type their target(s) should use.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tangibly related to this: FreeBSD 16 switches powerpc64le-unknown-freebsd from f64 to f128 for c_longdouble (ref). The current FreeBSD baseline in Rust is 12. We might have to do some FreeBSD version detection or raise the baseline, maybe only for this specific type (?).

Comment thread text/0000-c_longdouble.md

So far so good, but on some platforms this type corresponds to a type that Rust cannot currently express:

- X87 f80 on `x86` and `x86_64`, an IEEE (ish) 80-bit floating point type

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

f80x87 is also used on m68k by GCC: Clang currently seems to use f64. AFAICT GCC's is correct according to the ABI, although GCC seems to use the "Linux/GCC" ABI whereas Clang/Rust seem to use the "SystemV" ABI, based on their respective type alignments. It's probably worth asking the target maintainers about this.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I thought that m68k f80 was different, but apparently it's the same scheme just represented in memory slightly differently.

Anyway, I don't think LLVM even really builds m68k in practice? Part of the pitch of rustc_codegen_gcc is that it could more reliably compile rust for the target.

I'll add a note to future possibilities at least.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Clang/LLVM can definitely compile things m68k (C example).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Well std doesn't build for the target for sure, I'm not sure about core but I believe it has issues too.

Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md
Comment thread text/0000-c_longdouble.md
- `ppcf128` or `ppc_f128`
- `ibmf128` or `ibm_f128`
- `doubledouble` or `DoubleDouble`
- `f64f64` or `F64F64`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Personally I'd be inclined to go for __ibm128 (possibly without the underscores) to match C - this is the convention already used for SIMD types.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'd second this, the convention for naming this type is to include IBM in there, alternatively I'm also okay with some variation of doubledouble. ppcf128 somewhat implies that this is f128 on ppc which isn't the case, they're distinct types (IEEE quad vs IBM double-double)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Sure, I'll add it.

One consideration with f16b is that there is chance that we do want literals in the future, and then 1.0f16b looks more consistent than 1.0bf16. I believe there are also grammar/parser reasons to prefer a leading f.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

the convention for naming this type is to include IBM in there

When naming bf16 there was some real dislike for the enshrining of (Google) brain in "brain float". I don't think IBM will get more love.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't think IBM will get more love.

That's fair. I think ibm128 and doubledouble are both clear in what the type does and resolve the ambiguity that ppcf128 has. It could probably be a doc alias if we opt against ibm128 to help people familiar with C with finding the type.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Yes we'll probably doc alias all serious options (like what was already done in the f16b PR)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If we want to avoid the IBM in __ibm128 then I think doubledouble (DoubleDouble is probably better) is a good choice.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think that if we were making it cross-platform then we'd want a name like doubledouble (or twofloat). But if it's something that only exists in the PowerPC modules then I don't think __ibm128 is likely to be a problem - PowerPC is already a brand name and they own that trademark, after all, and there's already the target_vendor="ibm" config.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't want to pre-bikeshed the name discussion that will come up in the final RFC too much, but I believe there is a leaning towards not having __ prefixes for intrinsics and types anymore, like e.g. the loongarch64 vector stdarch code does not have the prefix that is added in the C intrinsics and types.

Since the PowerPC intrinsics don't have an __ prefix anyways I think it should be just omitted and called ibm128 if this is the name chosen.

Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md
Comment thread text/0000-c_longdouble.md
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md
};
```

LLVM now defines what `long double` resolves to for each target triple ([source](https://github.com/llvm/llvm-project/blob/f6b3f9399e98ef8b79388176e94c910d67669e1f/llvm/lib/TargetParser/Triple.cpp#L2541-L2597)).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It's my opinion that the compiler should have control of all these aliases anyway, there have been a few times we've wanted this (e.g. a flag to make c_char unsigned). Could just be a builtin macro allowing type c_longdouble = c_type!(c_longdouble);.

But for the initial work just having the aliases is fine.

Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md Outdated
Comment thread text/0000-c_longdouble.md

Conversions to and from `f32` and `f64` have hardware support, for `f16` and `f128` libcalls are needed.

## `f128ppc`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It would be good to mention how we treat values when they are "invalid" (not sure what the correct word is here), which AFAIK can be summed up as when larger + smaller != larger. The two options I think seem most promising are: handling it the way C de-facto does, which AFAICT is "it's not undefined behaviour, but operations might give nonsensical results", or we check that the f128ppc is "valid" before any operation and treat it as NaN if it is not.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Worth noting the treating "invalid" inputs as NaN option does have the additional question of whether .is_nan() should return true or false for them (and similarly for other float classification functions).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

it's not undefined behaviour

Having said that, we should probably check whether LLVM makes any assumptions that would lead this to be undefined behaviour.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LLVM appears to optimise presuming that long double is "valid". For instance:

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

__attribute__((optnone)) void black_box(void* x) {
}

struct S { double a; int b; };

__attribute__((noinline)) struct S foobar(long double x) {
    double y = x;
    return (struct S){.a=y,.b=x < 0 && y < 0};              
}

void print_it(long double x) {
    struct S r = foobar(x);
    printf("y=%e x<0&&y<0=%d\n", r.a, r.b);
}

int main() {
    double parts[2] = {0, -1};
    black_box(&parts);
    long double ld;
    memcpy(&ld, &parts, sizeof(ld));
    black_box(&ld);
    print_it(ld);
}

Will print:

y=0.000000e+00 x<0&&y<0=1

As LLVM folds the two comparisons together into a single ppc_fp128 comparison.

Comment thread text/0000-c_longdouble.md Outdated
Because `rustup` downloads a pre-built `core`, we can't detect the right type. Fundamentally, picking a different `long double` type is a different ABI.

We can solve this problem with special target tuples, e.g. by having the standard target use IEEE f128 and introducing a legacy target tuple for IBM f128 compatibility.
Most distributions for big-endian `powerpc{64}` have a baseline without vector registers (specifically, without the `vsx` target feature) and hence don't define an IEEE f128 ABI.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The vector registers required for the f128 ABI are actually added by the altivec (which adds the "Vector Facility") feature, as opposed to the vsx (which adds the "Vector-Scalar Extension Facility") (the 32 altivec registers overlap with the upper 32 of the 64 vsx registers). LLVM, however, currently only uses the vector ABI when the vsx feature is enabled.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I believe GCC does the same. So in practice vsx is the relevant feature for interop with C, right?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

C doesn't support f128 at all without vsx so mostly yes (although for targets with altivec it could still be relevant when linking against code that has been compiled with -mvsx/#[target_feature(enable = "vsx")]).

Most distributions for big-endian powerpc{64} have a baseline without vector registers

All Rust 64-bit PowerPC targets have altivec support.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: the text now reads

For big-endian powerpc{64} the baseline only includes altivec

which seems to imply 32-bit PowerPC targets also have a baseline including altivec, which no 32-bit targets currently have.

@Gelbpunkt Gelbpunkt Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

So in practice vsx is the relevant feature for interop with C, right?

Yes, vsx is required for the IEEE f128 type, see https://gcc.gnu.org/onlinedocs/gcc/RS_002f6000-and-PowerPC-Options.html#index-mfloat128.

All Rust 64-bit PowerPC targets have altivec support.

Clang, Rust and LLVM have a baseline of a PPC970 as in the Apple G5 by default. Only GCC defaults to POWER4 unless configured otherwise. I believe most powerpc64 big-endian distributions in practice will be targeting a G5 and above (i.e. PPC970 or POWER6 with AltiVec required).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(Parallel discussion llvm/llvm-project#112885 (comment))

Comment thread text/0000-c_longdouble.md
- on x86 the alignment is 4 bytes and the size is 12 bytes
- on x86_64 the alignment is 8 bytes and the size is 16 bytes.

This RFC deliberately does not propose support for arithmetic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Even if we only support conversions, LLVM will happily optimise in f80x87 arithmetic (compiler explorer):

__float128 half(__float128 x) {
    return x / 2;
}

long double half_ld(long double x) {
    return (long double) half((__float128) x);
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

That seems fine though if the precision requirements of the cast allow it?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

My main reason to leave it out is to cut scope and really focus on the ABI-relevant parts right now. Extensions to the API should then mostly be a T-libs decision that can de added on later.

@beetrees beetrees Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The problem here is that on Windows the default precision of all f80x87 operations is 53 bits, meaning AFAICT this will result in a miscompilation as LLVM doesn't change the precision before using fmul.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

That looks like an LLVM bug then, GCC keeps the f128 arithmetic https://godbolt.org/z/3hj588Wf6.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Indeed. The proper fix would be to fix LLVM's codegen for x86_fp80 to set the rounding precision as needed, a fix for just what's required by this RFC would probably be to just stop applying the optimisation when x86_fp80 is involved.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

We could try and core::hint::black_box in the conversion functions. It's not a good solution but might work for us for now.

Is there an existing LLVM issue for this problem? If not, can you open one?

Comment thread text/0000-c_longdouble.md

## Distributions configuring `long double`

Some distributions (e.g. RHEL8 and Fedora 44 powerpc) configure a non-standard `long double` in their system C compiler. This is a problem when

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry if this was not clear from my example, but I listed RHEL8 and Fedora 44 ppc64le explicitly because these two have the two different long double types. Fedora from 36 onwards has the IEEE quad long double, but RHEL8 for example is based on Fedora 28 and therefore has the IBM double-double long double.

If all you need here is some examples for distributions that use IEEE quad long double, Fedora 44 and Ubuntu 26.04 are fitting.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

My bad, fixed now, thanks!

@folkertdev folkertdev left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I think the remaining items are discussions that we can/should have on the actual RFC PR, so I'll open that soon.

Thanks everyone for helping out here!

Comment thread text/0000-c_longdouble.md
- on x86 the alignment is 4 bytes and the size is 12 bytes
- on x86_64 the alignment is 8 bytes and the size is 16 bytes.

This RFC deliberately does not propose support for arithmetic.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

We could try and core::hint::black_box in the conversion functions. It's not a good solution but might work for us for now.

Is there an existing LLVM issue for this problem? If not, can you open one?

Comment thread text/0000-c_longdouble.md

## Distributions configuring `long double`

Some distributions (e.g. RHEL8 and Fedora 44 powerpc) configure a non-standard `long double` in their system C compiler. This is a problem when

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

My bad, fixed now, thanks!

@folkertdev

Copy link
Copy Markdown
Owner Author

See rust-lang#4003

@folkertdev folkertdev closed this Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants