Skip to content

Representation of bool, integers and floating points #9

Closed

Description

@nikomatsakis

This issue is to discuss the memory layout for integral and floating point types:

  • bool
  • u8..u128, i8..i128
  • usize, isize
  • f32, f64

For the most part, these are relatively uncontroversial. However, there are some interesting things worth discussing:

  • Unlike other types, there are no #[repr(C)] vs #[repr(Rust)] variants here. The size is always fixed and well-defined across FFI boundaries. The types map to their corresponding integral types in the surrounding C ABI.
  • Prior discussions ([#46156][], [#46176][]) documented bool as a single byte that is either 0 or 1.
  • How is usize intended to be defined on various platforms?
  • Rust currently states that the maximum size of any single value must fit in with isize
    - Can we say a bit more about why? (e.g., ensuring that "pointer diff" is representable
  • Do we want to discuss signaling NaN at all? Specifically: why is it potentially of concern, and are there things that unsafe authors or other folks need to be aware of? (@gankro, for example, wrote that "NaN masking is unnecessary from LLVM's perspective", but I don't really know what that means. =)

Activity

  1. added
    A-layoutTopic: Related to data structure layout (`#[repr]`)
    on Aug 30, 2018
  2. joshtriplett commented on Aug 30, 2018

    @joshtriplett
    Member

    Don't forget u128 and i128.

    Another topic: Does FFI code need to use types like libc::uint64_t, or is it safe to just use u64?

  3. nikomatsakis commented on Aug 30, 2018

    @nikomatsakis
    ContributorAuthor

    @joshtriplett

    Don't forget u128 and i128.

    Edited, thanks!

    Another topic: Does FFI code need to use types like libc::uint64_t, or is it safe to just use u64?

    Good question! I presume it does not, but I'd be curious if there is another side to the discussion.

    *Related note: clearly usize and C's unsized are not equivalent, but it's worth stating this explicitly.

  4. Gankra commented on Aug 30, 2018

    @Gankra

    Another topic: Does FFI code need to use types like c_uint64, or is it safe to just use u64?

    Much of the rust ecosystem (e.g. webrender and therefore firefox) assumes uint64_t and u64 are the same ABI-wise, and I'm unaware of any reason to prevent that assumption.

    How is usize intended to be defined on various platforms?

    I believe pointer size is the correct definition but I haven't read the link, so grain of salt

    Rust currently states that the maximum size of any single value must fit in with isize

    There's no good reason, it's just because llvm has a quirky definition of in-bounds pointer calculations

    Do we want to discuss signaling NaN at all? Specifically: why is it potentially of concern, and are there things that unsafe authors or other folks need to be aware of?

    Signaling NaNs only merit discussion insofar as the IEEE spec defines some random operations to act differently on them. (e.g. max(sNaN, x) != max(qNaN, x), although iirc this example is regarded as a mistake and is intended to be changed)

    Signaling itself is, I think, largely a failed experiment and worth ignoring (soft cc on @stephentyrone in case i'm misremembering)

  5. hanna-kruppe commented on Aug 30, 2018

    @hanna-kruppe

    Wrt signaling NaNs, it's more of a question for the (now deferred, cc #8) discussion of valid values. There's a persistent rumor (including, at times, among LLVM contributors) that handling an sNaN or doing certain operations on it will cause a trap or is undefined behavior in LLVM. This is not the case, but I've encountered enough people thinking it's true that I think it would be best explicitly state that signaling NaN are perfectly fine, and thus that all bit patterns are valid floats.

  6. gnzlbg commented on Aug 30, 2018

    @gnzlbg
    Contributor

    +1 From the point-of-view of just layout, SNaNs are not really that interesting and the easiest thing is to just allow them. AFAIK f32::from_bits(u32) is safe and stable and works for all bit patterns, so we can't really do much about this anyways without potentially breaking some code.

  7. added
    A-layoutTopic: Related to data structure layout (`#[repr]`)
    and removed
    A-layoutTopic: Related to data structure layout (`#[repr]`)
    on Aug 31, 2018
  8. nikomatsakis commented on Aug 31, 2018

    @nikomatsakis
    ContributorAuthor

    @gankro

    I believe pointer size is the correct definition but I haven't read the link, so grain of salt

    The link is to a comment from @gnzlbg and states:

    C++ says that usize is an unsigned integer type that can store the maximum size (as returned by mem::size_of<T>/size_of_val/etc.) of a theoretically possible object of any type (including arrays). A type whose size cannot be represented by usize is ill-formed. On many platforms (an exception is systems with segmented addressing) usize can safely store the value of any non-member pointer. In those platforms, usize is a type capable of holding a pointer.

  9. nikomatsakis commented on Aug 31, 2018

    @nikomatsakis
    ContributorAuthor

    @gankro

    There's no good reason, it's just because llvm has a quirky definition of in-bounds pointer calculations

    Do you think we should write down that this is something that is presently true but which may be changed in the future (so unsafe code should not rely on it being true)? It seems like it might affect quite a bit how one writes code.

  10. nikomatsakis commented on Aug 31, 2018

    @nikomatsakis
    ContributorAuthor

    (In particular, it seems to imply that it is safe to use isize for "pointer offset" within any one value, which is otherwise not necessarily true, right?)

  11. Gankra commented on Aug 31, 2018

    @Gankra

    I don't think we can ever change it since it's baked into ptr::offset. If we did it would be in a way where negative offsets were a valid very-large-positive offset, so isize would still "work" but be weird.

  12. Gankra commented on Aug 31, 2018

    @Gankra

    also fwiw I think gcc also gets sad with huge offsets

  13. gnzlbg commented on Aug 31, 2018

    @gnzlbg
    Contributor

    Another topic: Does FFI code need to use types like libc::uint64_t, or is it safe to just use u64?

    I think that the bare minimal guarantee here is that the Rust extern "C" function declarations need to use types that match in size and alignment with the types of the C function declaration.

    That is, if C uses uint64_t, then you can use u64, libc::uint64_t, or even i64. In particular, libc types are not special. When C uses unsigned, then you need to use a 32 or 64 bit type (or something else) depending on the platform you are targeting. The libc::uint type does this correctly for you, but you don't have to use that.

    That would be the bare minimum, and I think that would already be ok since we are just passing bags of bytes here and it is all unsafe anyways. @mw might know whether this can result in any issues due to, e.g., cross-language inlining.

    If we wanted to extend this minimum, we could map the C types to the Rust types, e.g. saying that if a C's function declaration uses uint64_t then Rust extern "C" declaration must use a 64-bit wide unsigned integer type. Or if it uses unsigned that the Rust extern "C" declaration must use an unsigned integer type of the same width. But this opens many questions, e.g., is struct A(u64) a 64-bit unsigned integer type that I can use where C uses uint64_t ? What if I apply repr(transparent) to it? I'd rather avoid all this.

    I think if we can get by with only the size and alignment requirements, we should. If someone then uses a &T as the return type of an extern "C" function, and the function happens to create an invalid value, then that's UB but that would be covered by a different part of the unsafe code guidelines.

  14. 36 remaining items

  15. avadacatavra commented on Oct 18, 2018

    @avadacatavra
    Contributor

    should this discussion deal with all scalar types (aka should we include characters in this discussion?)

  16. strega-nil commented on Oct 18, 2018

    @strega-nil

    @avadacatavra char [C++] is equivalent to either i8/u8 (although you can use either for ABI compat); char [Rust] is not ABI compatible with anything (although I'd argue it'd be useful to be ABI compatible with char32_t) (note: that type only exists in C++).

    People have argued that they shouldn't be ABI compatible, since char32_t doesn't have the correctness guarantees Rust's char does; I would argue that it's the same idea as C-like enums in Rust vs enums in C++.

  17. briansmith commented on Oct 21, 2018

    @briansmith

    I wrote this big thing detailing what I believe to be true about layouts and ABIs in rust: https://gankro.github.io/blah/rust-layouts-and-abis/

    In https://gankro.github.io/blah/rust-layouts-and-abis/#the-layoutsabis-of-builtins, it would be useful to define the ABI correspondence for function parameters x: &[T; n] and C T x[static n] and T *x. ring is one crate that depends on this correspondence.

  18. Gankra commented on Oct 21, 2018

    @Gankra

    @briansmith I believe that is implicit in pointer ABI matching and array layout matching. I'm not aware of any system under-which the ABI of a pointer depends on the pointee's type, and array types in function parameters are just sugar for pointers.

  19. gnzlbg commented on Nov 2, 2018

    @gnzlbg
    Contributor

    How is usize intended to be defined on various platforms?

    • the native size of a pointer?
    • the max of various other considerations?
    • other edge cases to consider?

    Summarizing the discussion about usize/isize so far, we have already committed to these having the same size as a native pointer and changing that at this point would be a big breaking change.

    The representation of usize determines many things, like:

    • how much a pointer of a certain type can be offseted,
    • the maximum size of Rust objects (because size_of/size_of_val return usize),
    • the maximum number of elements in an array ([T; N: usize]),
    • usize/isize in C FFI are compatible with C's uintptr_t / intptr_t.

    etc. We should document these, but they don't change usize's representation so we don't have to document all of these things right now.

    This definition would also limit the problematic platforms to those that either do not have a native pointer size (can't think of any) or those that have multiple native pointer sizes (near and far pointers in segmented architectures). I'd say it's ok to worry about them when someone tries to add support for them (for segmented archs one could pick one of the pointer types as "native" and add newer types for the rest).

  20. gnzlbg commented on Nov 4, 2018

    @gnzlbg
    Contributor

    @sfackler

    We do guarantee bool is C-compatible - see rust-lang/rust#46176 and rust-lang/rust#46156.

    The merged PR specifies bool to be of size 1 which is not the same as being C-compatible [0] . The intent seems to have been to guarantee compatibility with C's _Bool type, e.g., @withoutboats called this out in the discussion here:

    • bool has the same representation as the platform's _Bool type.

    • We document this, and also document that on every platform we currently support, this means that the size of bool is 1.

    and with more rationale here:

    People could come to the conclusion that they need a c_bool type for their FFI to be forward compatible with platforms we don't yet support. I think defining it as the same representation as _Bool / C++ bool makes it the least likely someone does something painful to avoid entirely hypothetical problems.

    We have to decide whether we want bool to have the same representation as C's _Bool type (C FFI safe), or whether we want to make bool have size 1 (C FFI unsafe?). We could also make bool have size 1 and be C FFI safe by trading out support for platforms in which _Bool does not have size 1.


    [0] The MSVC2012 docs mention that MSVC <= 4.2 bool type is 4 bytes wide - link .

  21. gnzlbg commented on Nov 5, 2018

    @gnzlbg
    Contributor

    We could also make bool have size 1 and be C FFI safe by trading out support for platforms in which _Bool does not have size 1.

    FWIW, this is what @gankro 's document documents (https://gankro.github.io/blah/rust-layouts-and-abis/):

    For Rust to support a platform at all, its standard C dialect must: [...]

    • Have a boolean be a byte, where true = 1 and false = 0
  22. Gankra commented on Nov 5, 2018

    @Gankra

    Just to add more context:

    MSVC 5.0 fixed the sizeof bool in 1997, meaning this was fixed before windows 98 was released.

    Considering windows xp is a tier 3 (cross-compile only) platform because it's so janky and officially unsupported by the vendor, I find it dubious that we have interest in supporting compatibility with pre-windows-98 systems.

  23. Gankra commented on Nov 7, 2018

    @Gankra

    Great presentation by JF Bastien on efforts to standardize int/bool repr in C++20:

    talk: https://www.youtube.com/watch?v=JhUxIVf1qok
    paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html

    TL;DR:
    screen shot 2018-11-07 at 1 06 11 pm

  24. changed the title [-]Representation of integers and floating points[/-] [+]Representation of bool, integers and floating points[/+] on Nov 15, 2018
  25. gnzlbg commented on Mar 14, 2019

    @gnzlbg
    Contributor

    I've went through the reference and all the comments:

    If you feel I missed anything, it would be better to open an specific issue to discuss that.

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

Metadata

Metadata

Assignees

Labels

A-layoutTopic: Related to data structure layout (`#[repr]`)S-writeup-assignedStatus: Ready for a writeup and someone is assigned to it

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions