Skip to content

ABI mismatch on powerpc64 (elfv1) for union containing floats #162011

Description

@folkertdev

with

https://godbolt.org/z/EYxKxTs4f

// --target=powerpc64-unknown-linux-gnu -Copt-level=3
#[repr(C)] pub union TwoFloats { a: f32, b: f32 }

#[unsafe(no_mangle)]
extern "C" fn foo(x: TwoFloats) -> f32 {
    unsafe { x.a }
}

rustc emits

foo:
        .quad   .Lfunc_begin0
        .quad   .TOC.@tocbase
        .quad   0
.Lfunc_begin0:
        blr

I.e. the argument is already in a float register, and it stays there for the return. But with GCC and Clang the equivalent

https://godbolt.org/z/Mx8GEzdvn

typedef union { float a; float b; } TwoFloats;

float foo(TwoFloats x) {
    return x.a;
}

Has an explicit move from GPR to memory to FPR

foo:
        .quad   .Lfunc_begin0
        .quad   .TOC.@tocbase
        .quad   0
.Lfunc_begin0:
        stw 3, -4(1)
        lfs 1, -4(1)
        blr

You can see this difference in the LLVM IR emitted by rustc and clang too:

; rust 
define noundef float @foo(float returned %0) unnamed_addr {
start:
  ret float %0
}

; clang
define dso_local float @foo(i32 %x.coerce) local_unnamed_addr {
entry:
  %0 = bitcast i32 %x.coerce to float
  ret float %0
}

This came up in relation to #161987.

Activity

  1. added
    O-PowerPCTarget: PowerPC processors
    C-bugCategory: This is a bug.
    A-ABIArea: Concerning the application binary interface (ABI)
    on Aug 30, 2026
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Aug 30, 2026
  3. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Aug 31, 2026
  4. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Aug 31, 2026
  5. added
    I-miscompileIssue: Correct Rust code lowers to incorrect machine code
    and removed
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on Aug 31, 2026
  6. removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Aug 31, 2026
  7. rustbot commented on Aug 31, 2026

    @rustbot
    Collaborator

    Assigning P-high (discussion on Zulip).

  8. folkertdev commented on Sep 3, 2026

    @folkertdev
    ContributorAuthor
  9. beetrees commented on Sep 3, 2026

    @beetrees
    Contributor

    The ELFv1 specification says "Floating point registers f1 through f13 are used consecutively to pass [...] one member aggregates passed by value containing a floating point value" and the specification defines aggregates at the first use of the term as "Aggregates (structures and arrays)", so GCC and Clang are correct.

  10. 3 remaining items

  11. glaubitz commented on Sep 9, 2026

    @glaubitz
    Contributor

    Yes, we're still using it. Losing host tools would be unfortunate as it it has quite some enthusiasts still.

    Not much we can do, unfortunately. Rust upstream is extremely strict and insist on two maintainers per target which smaller downstream projects can't really provide.

    Luckily, gccrs is in the works and if Rust upstream ever decides to completely kill off older PowerPC ports, we will still have GCC.

  12. tgross35 commented on Sep 9, 2026

    @tgross35
    Member

    Please note that the barrier for being a target maintainer is actually rather low, it certainly doesn't come with any time commitment. Our docs say:

    A tier 2 target must have a designated team of developers (the "target maintainers") available to consult on target-specific build-breaking issues, or if necessary to develop target-specific language or library implementation details. This team must have at least 2 developers.

    All that's really needed is somebody who knows about some target specifics, and knows who to get in touch with for issues that they themselves can't address. If they can work on the issues that's of course better, but it could also be e.g. a GCC/LLVM contributor who dabbles in Rust and doesn't mind some ./x build work to help figure out the occasional issue.

    I have to imagine it's possible to dig up two people like that for the target - please do reach out and point them at #113739 (comment) if so! If there really aren't two people like that around then it is not a good sign of this target's health.

  13. tgross35 commented on Sep 9, 2026

    @tgross35
    Member

    I'll also note that this issue is not at all cause for demotion on its own. However, the target has accumulated a number of broken edge cases like this. Three years have gone by since #113739 was opened and in that time, nobody has expressed enough interest in keeping the target alive to sign up. Demotion would only be a reflection of that, and is completely reversible if that situation changes.

    Fwiw I don't think we should demote now. But if at some point this target starts blocking something (breaks CI or blocks a new feature - IIRC this is problematic with tail calls) and we still don't have a target maintainer, I expect it would be demoted very rapidly.

  14. mattst88 commented on Sep 9, 2026

    @mattst88
    Contributor

    From the TwoFloats example in the initial report, clang pass the float in a GPR and rust passes it in an FPR.

    This bit of code seems likely to be involved: https://github.com/rust-lang/rust/blob/main/compiler/rustc_target/src/callconv/powerpc64.rs#L27-L44

  15. glaubitz commented on Sep 10, 2026

    @glaubitz
    Contributor

    I'll also note that this issue is not at all cause for demotion on its own. However, the target has accumulated a number of broken edge cases like this. Three years have gone by since #113739 was opened and in that time, nobody has expressed enough interest in keeping the target alive to sign up. Demotion would only be a reflection of that, and is completely reversible if that situation changes.

    It's a communications problem. Not every downstream developer can be aware of every upstream issue of every upstream project.

    Upstream developers often make announcements about changes that affect downstream within their upstream channels only and then they're surprised that they're not getting any feedback. But we can't just be everywhere. Downstream developers from distributions like Debian and Gentoo try to contribute as much as they can, but we don't have the manpower to be everywhere.

    What frustrates me is that IBM engineers could perfectly take care of the other PowerPC ports besides ppc64el like they already do in GCC and the kernel. But apparently the management side has decided that older PowerPC targets deserve no attention, so these ports and the people maintaining them downstream end up getting under pressure.

    Fwiw I don't think we should demote now. But if at some point this target starts blocking something (breaks CI or blocks a new feature - IIRC this is problematic with tail calls) and we still don't have a target maintainer, I expect it would be demoted very rapidly.

    Well, the first message that reached downstream regarding this issue was the threat of demotion and not the question to ask for help resolving these issues. So, maybe you can understand the frustration a bit.

  16. apiraino commented on Sep 10, 2026

    @apiraino
    Contributor

    Fwiw I don't think we should demote now. But if at some point this target starts blocking something (breaks CI or blocks a new feature - IIRC this is problematic with tail calls) and we still don't have a target maintainer, I expect it would be demoted very rapidly.

    Well, the first message that reached downstream regarding this issue was the threat of demotion and not the question to ask for help resolving these issues. So, maybe you can understand the frustration a bit.

    I was the one that took the initiative to talk about demoting this target (in rust-lang/compiler-team#1035) so I am in part responsible for the confusion, for which I apologize.

    As discussed on Zulip we (Rust project) try to balance maintenance reality and downstream expectations. I think we have enough evidence that demoting the target would create concrete inconveniences downstream, so I will retract the proposal.

    Last but not least, thank you @glaubitz for your hard work in the Debian project and for taking the time to interface with us as well!

  17. mattst88 commented on Sep 14, 2026

    @mattst88
    Contributor

    I think I have a fix. I'll try to clean it up and add some tests.

  18. tgross35 commented on Sep 15, 2026

    @tgross35
    Member

    I think I have a fix. I'll try to clean it up and add some tests.

    It looks like you filed a Clang fix (thank you!), but per #162011 (comment) isn't Clang already doing the right thing?

  19. beetrees commented on Sep 15, 2026

    @beetrees
    Contributor

    Clang seems to have this bug only with unions containing a single element (e.g. union { float a; }). This original bug report example is a union with two elements (e.g. union { float a; float b; }), which Rust doesn't handle correctly but Clang does.

  20. mattst88 commented on Sep 15, 2026

    @mattst88
    Contributor

    It looks like you filed a Clang fix (thank you!), but per #162011 (comment) isn't Clang already doing the right thing?

    For the reported case, yes. Clang gets single-member unions wrong, though.

    The spec lists unions separately from aggregates everywhere ("Aggregates and Unions" in 3.1.6, "aggregates and unions passed by value" in 3.2.3), and the FPR rule only mentions "one member aggregates". So a union should never be passed in an FPR, even if it has one field. GCC does this. Clang unwraps single-member unions:

    ELFv1 GCC Clang 22 rustc
    union { float a; float b; } GPR GPR FPR
    struct { union { float a; float b; } u; } GPR GPR FPR
    union { float a; } GPR FPR FPR
    struct { union { float a; } u; } GPR FPR FPR
    union { struct { float a; } s; } GPR FPR FPR

    It's the same with double, long double and 128-bit vector members. In Clang this comes from isSingleElementStruct accepting unions; llvm/llvm-project#223481 fixes that for ELFv1.

    My rustc change never unwraps a union on ELFv1, so it matches GCC in all of these cases (and Clang once that PR lands). It does change rustc's ABI for single-member unions.

  21. folkertdev commented on Sep 15, 2026

    @folkertdev
    ContributorAuthor

    My rustc change never unwraps a union on ELFv1

    I look forward to seeing this. Does that mean you don't use homogenous_aggregate at all on elfv1? That function is causing issues on some other targets (where an empty array should disqualify an aggregate) so it needs some work.

  22. mattst88 commented on Sep 20, 2026

    @mattst88
    Contributor

    Sorry, took a while to get the tests done.

  23. added a commit that references this issue on Sep 25, 2026
    b48bdb1
  24. added a commit that references this issue on Sep 25, 2026
    e885f2f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ABIArea: Concerning the application binary interface (ABI)C-bugCategory: This is a bug.I-miscompileIssue: Correct Rust code lowers to incorrect machine codeO-PowerPCTarget: PowerPC processorsP-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions