Skip to content

"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows ABI #552

Closed

Description

@RalfJung

So apparently ZST return types are actually returned via a return pointer on windows-gnu targets -- except of course when the return type is (). At the same time we promise "Any two types with size 0 and alignment 1 are ABI-compatible". These two things are in direct contradiction with each other. We have to either add something like "except on platforms with a ridiculous ABI, such as windows, then you are just on your own" to the ABI docs, or decide we do not follow the likely accidental ad-hoc ABI of windows-gnu, or emit a hard error for types that the ABI simply does not support.

Note that the reason for this rule of any two 1-ZST being ABI compatible is that for types like #[repr(transparent)] ZST([u8; 0]; ()), we promise that the type is ABI-compatible with the field that is being transparently wrapped, and that could be either field. So the only way to always deliver on our repr(transparent) ABI-compatibility promise is to make all 1-ZST ABI-compatible.

This came up in rust-lang/rust#135204.
Cc @programmerjake @ChrisDenton @eddyb

Activity

  1. changed the title [-]"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows API[/-] [+]"Any two types with size 0 and alignment 1 are ABI-compatible" vs the Windows ABI[/+] on Jan 10, 2025
  2. programmerjake commented on Jan 10, 2025

    @programmerjake
    Member

    could we make the function return type syntax meaningful here? so extern "win64" fn() means no sret pointer but extern "win64" fn() -> () means there is a sret pointer since the return type is explicit? or is this too big of a change?

  3. bjorn3 commented on Jan 10, 2025

    @bjorn3
    Member

    extern "win64" fn() is an alias for extern "win64" fn() -> () by definition:

    https://doc.rust-lang.org/stable/reference/items/functions.html#r-items.fn.implicit-return.

    If the output type is not explicitly stated, it is the unit type.

    Also extern "win64" fn foo() -> () is the literal translation of void foo(void) on windows. extern "win64" fn foo() is just a convenience shorthand.

  4. programmerjake commented on Jan 10, 2025

    @programmerjake
    Member

    extern "win64" fn() is an alias for extern "win64" fn() -> () by definition:

    yeah, I was proposing thinking about changing that...

    another probably better option would be to have #[repr(transparent)] always pick a particular field to duplicate the ABI of, e.g. it could pick the generic field, or if there isn't one it'd pick an annotated field or the first field, and a future edition could require ambiguous #[repr(transparent)] structs to annotate one field.
    that way you'd have the following ABI lowering:

    #[repr(C)]
    struct Z([u8; 0]);
    
    #[repr(transparent)]
    struct S1 {
        a: Z,
        b: (),
    }
    
    // define dso_local void @f(ptr sret(%struct.Z) %0) local_unnamed_addr #0 {
    extern "win64" f() -> S1 {
        S1 { a: Z([]), b: () }
    }
    
    #[repr(transparent)]
    struct S2 {
        a: (),
        b: Z,
    }
    
    // define dso_local void @g() local_unnamed_addr #0 {
    extern "win64" g() -> S2 {
        S2 { a: (), b: Z([]) }
    }
  5. RalfJung commented on Jan 10, 2025

    @RalfJung
    MemberAuthor

    I don't think the quirks of this... special... ABI should affect what we guarantee for other, more reasonable, ABIs.

  6. Diggsey commented on Jan 10, 2025

    @Diggsey

    My understanding is that ZSTs are a non-standard extension to C by clang/gcc, hence why they don't exist under MSVC.

    In other words, we don't necessarily have to follow the same ABI - our ABI would be "just as correct" for windows. The question is whether that would cause too many issues for interop with C.

    We could have an ABI-modifying attribute to apply at a function level which makes the return value be returned via pointer, to handle that edgecase.

  7. RalfJung commented on Jan 10, 2025

    @RalfJung
    MemberAuthor

    @rust-lang/lang is there any appetite for a post-monomorphization lint or error rejecting argument types that are not supported by a particular ABI? The context here is passing ZST across extern "C" on Windows targets; MSVC does not have such types so there is no official ABI and the de-facto ABI implemented by gcc and clang is... odd.

    This could be seen as similar to the lint that we already have rejecting SIMD vectors passed via extern "C" without the required target features.

    We could have an ABI-modifying attribute to apply at a function level which makes the return value be returned via pointer, to handle that edgecase.

    This would have to be a separate extern "ABI" string, since otherwise function pointers are unsound.

  8. bjorn3 commented on Jan 10, 2025

    @bjorn3
    Member

    is there any appetite for a post-monomorphization lint or error rejecting argument types that are not supported by a particular ABI?

    I would love to have that. And also to have it reject any #[repr(Rust)] type for which we don't guarantee a specific ABI.

  9. RalfJung commented on Jan 10, 2025

    @RalfJung
    MemberAuthor

    And also to have it reject any #[repr(Rust)] type for which we don't guarantee a specific ABI.

    In the interest of minimizing breaking changes, I'd like to make a difference between "we don't guarantee this type's ABI" and "this type just can't be passed via this ABI".

    Please don't expand the scope of this issue further than is absolutely necessary.

  10. chorman0773 commented on Jan 10, 2025

    @chorman0773
    Contributor

    I think the answer ought to be just "All return 1-ZSTs match c void" (not the same as c_void), and we just don't match C ZSTs in return position on windows-gnu.

  11. chorman0773 commented on Jan 10, 2025

    @chorman0773
    Contributor

    Although, now that I think about it, we should check other ABIs.
    Something tells me, aggregate ZSTs might be returned in memory on some other platforms (ARM32 maybe)?

  12. Diggsey commented on Jan 11, 2025

    @Diggsey

    I think there's a more general problem here where Rust is making guarantees that are not strictly within its remit.

    A platform's C ABI can be arbitrarily "odd", and yet we would still want to be able to interoperate with it via extern "C" functions.

    Can we relax the guarantees we're making in cases where it's outside our remit. For example, #[repr(transparent)] would still guarantee that the representation of the outer vs inner types in memory and in extern "Rust" functions is identical, but would only be "best effort" as far as how the type is passed to or returned from an extern "C" function.

    At monomorphisation time we should check that an extern "C" function signature unambiguously corresponds to a valid C function definition for the target architecture.

    We would return an error or warning if either of the following:

    • There are multiple "equally correct" translations to C which could be valid, with different ABIs. This would occur on windows-gnu for a function that tries to return the following type:

      #[repr(transparent)]
      struct CantReturn {
          a: (),
          b: [i32; 0]
      }
    • There is no "correct" translation to C. This would occur on windows-msvc for the following type:

      #[repr(C)]
      struct CantReturn {};

      ie. we should avoid "extending" the ABI, since it's not within our remit to do so and could cause backwards compatibility problems in future.

    (These are just examples, but there might be other cases)

  13. chorman0773 commented on Jan 11, 2025

    @chorman0773
    Contributor

    BTW, if 1-ZSTs don't all have the same ABI, what's the ABI of the following?

    #[repr(transparent)]
    struct Transparent6;
  14. Lokathor commented on Jan 11, 2025

    @Lokathor
    Contributor

    If there isn't a single correct answer we should be at least warning about this during compilation, if not deny by default.

  15. zachs18 commented on Jan 12, 2025

    @zachs18

    BTW, if 1-ZSTs don't all have the same ABI, what's the ABI of the following?

    #[repr(transparent)]
    struct Transparent6;

    I would expect Transparent61 would be ABI-compatible with () (unit), since the Reference currently states that, for #[repr(transparent)]

    Structs and enums with this representation have the same layout and ABI as the only non-size 0 non-alignment 1 field, if present, or unit otherwise.

    (source, emphasis mine). However, this does give the wrinkle that, by a strict reading, if there are 1-ZSTs that are not ABI-compatible with () (say Other1Zst), then #[repr(transparent)] struct Bar(Other1Zst); is ABI-compatible with () and not with Other1Zst.2

    The meaning for #[repr(transparent)] on a struct without a non-1-ZST field was T-lang FCP'd (here), but IIUC under the assumption that all 1-ZSTs are ABI-compatible: "[...] The real question here is whether there exists some "special" 1-ZST that would be treated differently by the ABI than any other 1-ZST. I do not believe that is the case, which is why () is kind of "as good as any other". [...]" (src).

    Footnotes

    1. and any #[repr(transparent)] struct with no non-1-ZST field ↩

    2. Would #[repr(transparent)] struct _Bar<T>(T); type Bar = _Bar<Other1Zst>; be ABI-compatible with Other1Zst or () in that case? I assume Other1Zst since AFAICT we treat generic types "as if" they are non-1-ZST, but that is a fragile assumption. ↩

  16. 33 remaining items

  17. Jules-Bertholet commented on Jun 22, 2026

    @Jules-Bertholet

    Should this have been closed? Until rust-lang/rust#157973 and rust-lang/rust#156112 are merged, the issue still exists

  18. CAD97 commented on Jun 23, 2026

    @CAD97
    Contributor

    Those seem relevant enough to keep this open, I think.

  19. reopened this on Jun 23, 2026
  20. added a commit that references this issue on Oct 2, 2026
    3bcfbad
  21. added a commit that references this issue on Oct 3, 2026
    67108d5
  22. added 2 commits that reference this issue on Oct 5, 2026
    af858cc
    adc9f4a
  23. added a commit that references this issue on Oct 5, 2026
    18068c2
  24. added 2 commits that reference this issue on Oct 7, 2026
  25. added a commit that references this issue on Oct 8, 2026
  26. RalfJung commented on Oct 9, 2026

    @RalfJung
    MemberAuthor
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions