rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.63k stars 12.48k forks source link

E0587 error on packed and aligned structures from C #59154

Open ahomescu opened 5 years ago

ahomescu commented 5 years ago

When converting C structures that are both packed and aligned using either C2Rust or bindgen, such as:

struct xregs_state {
    struct fxregs_state     i387;
    struct xstate_header        header;
    u8              extended_state_area[0];
} __attribute__ ((packed, aligned (64)));

the code that either tool produces looks something like:

#[repr(C, packed(64))]
#[repr(align(64))]
pub struct xregs_state {
    pub i387: fxregs_state,
    pub header: xstate_header,
    pub extended_state_area: __IncompleteArrayField<u8>,
}

This Rust code fails to compile due to error E0587:

error[E0587]: type has conflicting packed and align representation hints
    --> .../out/bindings.rs:3894:1
     |
3894 | / pub struct xregs_state {
3895 | |     pub i387: fxregs_state,
3896 | |     pub header: xstate_header,
3897 | |     pub extended_state_area: __IncompleteArrayField<u8>,
3898 | | }
     | |_^

We can work around this in C2Rust by emitting an aligned outer/packed inner structure pair (I think bindgen could do the same), but I'm wondering if it would be better to fix this on the Rust language/compiler side.

jonas-schievink commented 5 years ago

The error message could be improved to say why these hints are incompatible (that is, if this error shouldn't be removed outright)

TheDan64 commented 5 years ago

Note that the aligned outer/packed inner workaround is still really limited. If the struct contains any struct fields with their own alignment, then you'll still hit this error. (The xregs_state example does have this exact problem, as its i387: fxregs_state field has an alignment of 16)

So I think it makes sense for rust to support this for repr C/FFI purposes at the very least.

Stargateur commented 5 years ago

note that u8 extended_state_area[0]; is not valid C code according to standard and should not be used in 2019, u8 extended_state_area[]; instead.

retep998 commented 5 years ago

Having structs with repr(align(N)) (transitively) inside a struct that is repr(packed(N)) is currently not allowed because gcc and VC++ differ in their behavior. Given:

#[repr(C, align(4))]
struct Foo(u8);
#[repr(C, packed(1))]
struct Bar(Foo);

What is align_of::<Bar>()? If we follow gcc's behavior it is 1, but if we follow VC++'s behavior it is 4. Things get even hairier when you consider MinGW which is gcc and therefore has gcc behavior despite targeting Windows which should obey the C ABI dictated by VC++.

Stargateur commented 5 years ago

@retep998

you consider MinGW which is gcc and therefore has gcc behavior despite targeting Windows which should obey the C ABI dictated by VC++.

Actually no, gcc can do whatever it want even targeting windows, the "only" requirement is that the final program work. Also, a structure defined in user side can only be use by user side, so gcc don't have to follow msvc rule to work on window in the user side. OFC, when gcc need to call Windows API they need to follow their rules, also note I don't talk about msvc rules but those of Windows API (they have probably the same anyway).

All of this to say, C ABI is not a standard thing, __attribute__ ((packed, aligned (64))) is clearly not defined by the standard and compiler are allow to have extension, there are totally implemented behavior.

retep998 commented 5 years ago

Actually no, gcc can do whatever it want even targeting windows, the "only" requirement is that the final program work.

If MinGW compiled binaries want to do anything they have to be able to call into dlls compiled by VC++, which means they have to match the ABI that VC++ uses. Whether the C ABI is standardized or whether the language features in question are even standard is irrelevant. If there is a dll out there that uses #pragma pack in combination with __declspec(align(N)) then you need to be able to call into it regardless of whether you are in the VC++ world or the MinGW world.

Having pc-windows-msvc and pc-windows-gnu differ in their behavior around this would be a massive footgun, but at the same time having pc-windows-gnu differ from all other -gnu targets would also be a footgun. The lang team hasn't decided on a solution to this yet, and so this feature combination remains illegal.

Stargateur commented 5 years ago

Yes, but all of this is implemented side, C doesn't care of all of that, dll is not a thing in C standard, if MinGW want to handle dll produce by VC++ is their problem not C problem. I disagree about Rust trying to bet about what all C compiler should produce to have a FFI interface. C is design to be very flexible on what a compiler can do. So you are hurting a wall. Unless you want something like #[repr(C, gcc, 5.6, linux)], I don't think you can find a magic solution. It's look like you blame gcc for this even if gcc is allowed to do this even on windows and still be a valid C compiler.

The "best guess" is already a very good thing to have in the Rust side. I find that Rust don't have the ressource to handle every case of every implementation of C (specially with Microsoft, MSVC is not a true C compiler)

retep998 commented 5 years ago

(specially with Microsoft, MSVC is not a true C compiler)

What is or isn't a "true" C compiler is not important nor is it relevant.

Rust needs to be able to interface with system libraries, which means that Rust needs to match the ABI used by system libraries. Therefore on Windows Rust has to match the VC++ ABI because that is the ABI used by system libraries. If MinGW differs from this then Rust is in a tight spot because pc-windows-gnu still needs to be able to match the ABI used by system libraries.

In my opinion, having the behavior of mixing repr(packed(N)) and repr(align(N)) differ based on target is a huge footgun due to pc-windows-gnu. Instead I'm in favor of allowing the user to explicitly choose which behavior they want, whether that be through alternative versions of repr(align(N)) and repr(packed(N)) or some other method.

TheDan64 commented 5 years ago

What if repr(packed, align(N)) was allowed to compile on every target except windows? And it just remained an error there until a decision on windows was made? This would at least open it up to the most common use cases while not committing to a windows representation.

retep998 commented 5 years ago

On the contrary I think Windows is the common use case and repr(packed, align(N)) should only be allowed there with VC++ semantics until a decision on other targets is made.

rinon commented 5 years ago

I'm not sure why you think Windows is necessarily the common case? The initial example above is from the Linux kernel headers and is blocking development of Rust kernel modules that use that and other similar structs. We need some way of being ABI compatible with the Linux kernel for the same reason that we need to be ABI compatible with VC++ libraries.

retep998 commented 5 years ago

I'm not sure why you think Windows is necessarily the common case?

For the same reason that some people think non-Windows is the common case.

More seriously, I am opposed to implementing either behavior until the lang team decides on a solution that allows both behaviors to be used. I want this to be resolved just as much as you do, but I'm not willing to throw some platforms under the bus for the sake of other platforms.

rinon commented 5 years ago

I think the point was that there is only an ABI ambiguity on Windows and the ABI behaviour doesn't necessarily need to be consistent across platforms. Defining the same semantics for packed+aligned on both pc-windows-gnu or pc-windows-msvc would be a problem, but leaving those targets undefined as they currently are and defining default semantics for other targets would at least allow this combination to be used in cases where there is no ambiguity in ABI implementations. Windows would be no worse off than currently and other platforms would be able to interface with packed+aligned structs.

All that said, though, it sounds like we do need to start an RFC on defining a syntax for controlling layout for the combination of packed structs and aligned fields.

joshtriplett commented 5 years ago

We discussed this on Discord, and here's a rough sketch of some ideas and next steps:

SamB commented 2 years ago

Did anyone talk to the MinGW and/or GCC people about the ABI issue yet? If so, links to relevant conversations would be good.

bertschingert commented 7 months ago

We've been discussing this issue in the bcachefs IRC recently because it's become a blocker for continuing to integrate Rust more into bcachefs. It seems like the thing holding it back is the difference in behavior between platforms.

From the example given it seems like the difference comes down to whose alignment wins when you have a parent/outer struct with an embedded child struct. One compiler uses the child struct's alignment, the other the parent.

One idea we discussed: what about creating a version of the #[repr(align(N))] attribute to specify which semantics to use? For example, a new attribute: #[repr(align_override_packed(N))] that can be used when the behavior of the child overriding the parent alignment is desired?

#[repr(align(N))] #[repr(packed)] can be used together to get the opposite behavior, parent/outer alignment wins.

The exact names don't matter at this point--just that we can come up with something reasonable to move forward. I'd appreciate feedback on this idea.

Also, I take it this will require an RFC at some point to either introduce a new attribute, or change the behavior of the current attributes? I am willing to try to get that started by initiating a pre-RFC discussion, if that'd be helpful.

RalfJung commented 4 months ago

Note that the aligned outer/packed inner workaround is still really limited. If the struct contains any struct fields with their own alignment, then you'll still hit this error.

They should hit a different error, no? "packed type cannot transitively contain a #[repr(align)] type" is not the same error as "conflicting packed and align representation hints". There's another bug for the "cannot contain" issue: https://github.com/rust-lang/rust/issues/100743.

@bertschingert @ojeda does bcachefs/Rust-for-Linux need #59154 or #100743?