New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
translate-c: Allow translating packed C structs iff ABI-sized #12735
translate-c: Allow translating packed C structs iff ABI-sized #12735
Conversation
bd59aa5
to
eb1eea0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Packed structs are not ABI compatible with C structs but with ints. Ints and structs with only one pointer sized int seem to be always compatible so if you add a check for there only being one field then I think this is fine until we get a better solution.
I think even this specific case of packed structs with a single integer is incompatible as the alignment is different. It seems that the entire reason these structures use I believe the correct translation of this example would be an extern struct with struct _OSUnalignedU32 {
uint32_t __val;
} __attribute__((__packed__)); pub const _OSUnalignedU32 = extern struct {
val: u32 align(1),
}; I've left the |
This relaxes the restriction added in c7884af and fixes ziglang#12733 Right now, the compiler only considers packed structs to be C compatible (extern) if the structs are "ABI sized". See also b83c037 for details on the extern restrictions on `packed struct` This is an issue on all platforms, but is of particular importance on aarch64-macos (seeissue for details) Before this commit, a type like `` struct example { int val; } __attribute__((__packed__)); `` would fail to translate and turn into `` pub const example = @CompileError("cannot translate packed record union"); `` This fixes this issue for ABI-sized structs, making the translate-c rules consistent with the regular compiler. However, we still reject packed structs that are not "ABI-sized", giving the same `@compileError` message So `` struct example { long a, b, c } __attribute__((__packed__)); `` will still fail (in the sense that it will emit a `@compileError`) **Implementation details**: For the purposes of translate-c, a packed struct is considered "ABI-sized" when sizeof(target_struct) <= sizeof(void*) Unfortunately, right now the translate-c `*Context` structure (on the Zig side) doesn't directly have any information on the target we're translating into. However, clang has this target information, and keeps it in clang::ASTContext. I added a helper function to extract the `clang::TargetInfo` from this, then query the pointer size. This is potentially controversial, since it technically makes the output target-dependent. The decision of whether to accept/reject a packed struct varies depending on the target's pointer size. However, the translator already asks for (integer) field offsets elsewhere, so I'm pretty sure target info already impacts translation. We also add getSize() and getDataSize() functions to *clang.RecordDecl. These return padded and unpadded sizes respectively. For a packed struct, they should be the same. The overall effect is 4 more functions added to zig_clang.cpp, in order to extract target info & query record sizes.
eb1eea0
to
dcac7a7
Compare
I have added in this restriction (and rebased onto master). However we may want to look deeper into why the ABIs are incompatible. It may have something to do with @ifreund 's comment on alignment.
Agreed. I think you are correct that First looking at the Common Type attributes page: The final line of the documentation for
This implies the
Then I went to go look at the documentation for Common Variable Attributes:
Combining this means that I think this is (yet another) independent bug. It is present even in stage1 translate-c. We need to change |
This is relevant to the fix in ziglang#12735 , but also fixes stage1. This comes from a careful reading of the GCC docs for type & variable attributes. The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], does not directly state this relationship with alignment. However, it *does* say that the 'packed' attribute is applied recursively to each member. > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. (NOTE: Somewhat confusingly, field attributes are documented under "variables") This means that a C structure with __attribute__((packed)) automatically implies align(1) on each of its member. Unfortunately, the current implementation of `translate-c` does not reflect this. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` does not apply any alignment attributes to any fields except d. Sometimes __attribute__((packed)) is used only for alignment purposes. It is a semi-portable way to do unaligned stores. In particular, the aarch64-macos.12 headers do this in stdlib.h This was noted by @ifreund in the discussion of PR ziglang#12735 The structures in the MacOS headers were being used chiefly for their alignment properties, not for padding purposes (they only had one field). After applying this change, the translated structures have align(1) explicitly applied to all of their fields (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = packed struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = packed struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` This is closely related with PR ziglang#12735 Since the last stable release (0.9.1), there was a **change in the language spec** related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer So now `translate-c` definitely needs to specify align(1) for every field (unless the spec is overriden).
This is relevant to the fix in ziglang#12735 , but also fixes stage1. This comes from a careful reading of the GCC docs for type & variable attributes. The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], does not directly state this relationship with alignment. However, it *does* say that the 'packed' attribute is applied recursively to each member. > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. (NOTE: Somewhat confusingly, field attributes are documented under "variables") This means that a C structure with __attribute__((packed)) automatically implies align(1) on each of its member. Unfortunately, the current implementation of `translate-c` does not reflect this. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` does not apply any alignment attributes to any fields except d. Sometimes __attribute__((packed)) is used only for alignment purposes. It is a semi-portable way to do unaligned stores. In particular, the aarch64-macos.12 headers do this in stdlib.h This was noted by @ifreund in the discussion of PR ziglang#12735 The structures in the MacOS headers were being used chiefly for their alignment properties, not for padding purposes (they only had one field). After applying this change, the translated structures have align(1) explicitly applied to all of their fields (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = packed struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = packed struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` This is closely related with PR ziglang#12735 Since the last stable release (0.9.1), there was a **change in the language spec** related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer So now `translate-c` definitely needs to specify align(1) for every field (unless the spec is overriden).
This is relevant to the fix in ziglang#12735 , but also fixes stage1. This comes from a careful reading of the GCC docs for type & variable attributes. The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], does not directly state this relationship with alignment. However, it *does* say that the 'packed' attribute is applied recursively to each member. > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. (NOTE: Somewhat confusingly, field attributes are documented under "variables") This means that a C structure with __attribute__((packed)) automatically implies align(1) on each of its member. Unfortunately, the current implementation of `translate-c` does not reflect this. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` does not apply any alignment attributes to any fields except d. Sometimes __attribute__((packed)) is used only for alignment purposes. It is a semi-portable way to do unaligned stores. In particular, the aarch64-macos.12 headers do this in stdlib.h This was noted by @ifreund in the discussion of PR ziglang#12735 The structures in the MacOS headers were being used chiefly for their alignment properties, not for padding purposes (they only had one field). After applying this change, the translated structures have align(1) explicitly applied to all of their fields (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = packed struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = packed struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` This is closely related with PR ziglang#12735 Since the last stable release (0.9.1), there was a **change in the language spec** related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer So now `translate-c` definitely needs to specify align(1) for every field (unless the spec is overriden).
This PR technically works, but only by accident. In Zig , a I plan to amend my other PR tomorrow (#12745) to fix this. @Vexu has pointed this out a couple times, but it took a while to sink in 😉 Here is the code that (currently) lowers packed structs directly LLVM types: Lines 3541 to 3572 in e323cf1
|
Superceeds PR ziglang#12735 (now supporting all packed structs in GNU C) Fixes issue ziglang#12733 This stops translating C packed struct as a Zig packed struct. Instead use a regular `extern struct` with `align(1)`. This is because (as @Vexu explained) Zig packed structs are really just integers (not structs). Alignment issue is more complicated. I think @ifreund was the first to notice it in his comment on PR ziglang#12735 Justification of my interpretion of the C(lang) behavior comes from a careful reading of the GCC docs for type & variable attributes: (clang emulates gnu's packed attribute here) The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], states: > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields (wierdly under "variables" section)]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. Furthermore, alignment is the only effect of the packed attribute mentioned in the GCC docs (for "common" architecture). Based on this, it seems safe to completely substitute C 'packed' with Zig 'align(1)'. Target-specific or undocumented behavior potentially changes this. Unfortunately, the current implementation of `translate-c` translates as `packed struct` without alignment info. Because Zig packed structs are really integers (as mentioned above), they are the wrong interpretation and we should be using 'extern struct'. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` Previously used a 'packed struct' (which was not FFI-safe on stage1). After applying this change, the translated structures have align(1) explicitly applied to all of their fields AS EXPECTED (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = extern struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = extern struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` Also note for reference: Since the last stable release (0.9.1), there was a change in the language spec related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer Suggsestive both to the change to an integer-backed representation which is incompatible with C's notation.
Superceeds PR ziglang#12735 (now supporting all packed structs in GNU C) Fixes issue ziglang#12733 This stops translating C packed struct as a Zig packed struct. Instead use a regular `extern struct` with `align(1)`. This is because (as @Vexu explained) Zig packed structs are really just integers (not structs). Alignment issue is more complicated. I think @ifreund was the first to notice it in his comment on PR ziglang#12735 Justification of my interpretion of the C(lang) behavior comes from a careful reading of the GCC docs for type & variable attributes: (clang emulates gnu's packed attribute here) The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], states: > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields (wierdly under "variables" section)]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. Furthermore, alignment is the only effect of the packed attribute mentioned in the GCC docs (for "common" architecture). Based on this, it seems safe to completely substitute C 'packed' with Zig 'align(1)'. Target-specific or undocumented behavior potentially changes this. Unfortunately, the current implementation of `translate-c` translates as `packed struct` without alignment info. Because Zig packed structs are really integers (as mentioned above), they are the wrong interpretation and we should be using 'extern struct'. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` Previously used a 'packed struct' (which was not FFI-safe on stage1). After applying this change, the translated structures have align(1) explicitly applied to all of their fields AS EXPECTED (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = extern struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = extern struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` Also note for reference: Since the last stable release (0.9.1), there was a change in the language spec related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer Suggsestive both to the change to an integer-backed representation which is incompatible with C's notation.
Superceeds PR ziglang#12735 (now supporting all packed structs in GNU C) Fixes issue ziglang#12733 This stops translating C packed struct as a Zig packed struct. Instead use a regular `extern struct` with `align(1)`. This is because (as @Vexu explained) Zig packed structs are really just integers (not structs). Alignment issue is more complicated. I think @ifreund was the first to notice it in his comment on PR ziglang#12735 Justification of my interpretion of the C(lang) behavior comes from a careful reading of the GCC docs for type & variable attributes: (clang emulates gnu's packed attribute here) The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], states: > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields (wierdly under "variables" section)]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. Furthermore, alignment is the only effect of the packed attribute mentioned in the GCC docs (for "common" architecture). Based on this, it seems safe to completely substitute C 'packed' with Zig 'align(1)'. Target-specific or undocumented behavior potentially changes this. Unfortunately, the current implementation of `translate-c` translates as `packed struct` without alignment info. Because Zig packed structs are really integers (as mentioned above), they are the wrong interpretation and we should be using 'extern struct'. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` Previously used a 'packed struct' (which was not FFI-safe on stage1). After applying this change, the translated structures have align(1) explicitly applied to all of their fields AS EXPECTED (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = extern struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = extern struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` Also note for reference: Since the last stable release (0.9.1), there was a change in the language spec related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer Suggsestive both to the change to an integer-backed representation which is incompatible with C's notation.
Superceeds PR ziglang#12735 (now supporting all packed structs in GNU C) Fixes issue ziglang#12733 This stops translating C packed struct as a Zig packed struct. Instead use a regular `extern struct` with `align(1)`. This is because (as @Vexu explained) Zig packed structs are really just integers (not structs). Alignment issue is more complicated. I think @ifreund was the first to notice it in his comment on PR ziglang#12735 Justification of my interpretion of the C(lang) behavior comes from a careful reading of the GCC docs for type & variable attributes: (clang emulates gnu's packed attribute here) The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], states: > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields (wierdly under "variables" section)]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. Furthermore, alignment is the only effect of the packed attribute mentioned in the GCC docs (for "common" architecture). Based on this, it seems safe to completely substitute C 'packed' with Zig 'align(1)'. Target-specific or undocumented behavior potentially changes this. Unfortunately, the current implementation of `translate-c` translates as `packed struct` without alignment info. Because Zig packed structs are really integers (as mentioned above), they are the wrong interpretation and we should be using 'extern struct'. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` Previously used a 'packed struct' (which was not FFI-safe on stage1). After applying this change, the translated structures have align(1) explicitly applied to all of their fields AS EXPECTED (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = extern struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = extern struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` Also note for reference: Since the last stable release (0.9.1), there was a change in the language spec related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer Suggsestive both to the change to an integer-backed representation which is incompatible with C's notation.
Superceeds PR ziglang#12735 (now supporting all packed structs in GNU C) Fixes issue ziglang#12733 This stops translating C packed struct as a Zig packed struct. Instead use a regular `extern struct` with `align(1)`. This is because (as @Vexu explained) Zig packed structs are really just integers (not structs). Alignment issue is more complicated. I think @ifreund was the first to notice it in his comment on PR ziglang#12735 Justification of my interpretion of the C(lang) behavior comes from a careful reading of the GCC docs for type & variable attributes: (clang emulates gnu's packed attribute here) The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], states: > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields (wierdly under "variables" section)]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. Furthermore, alignment is the only effect of the packed attribute mentioned in the GCC docs (for "common" architecture). Based on this, it seems safe to completely substitute C 'packed' with Zig 'align(1)'. Target-specific or undocumented behavior potentially changes this. Unfortunately, the current implementation of `translate-c` translates as `packed struct` without alignment info. Because Zig packed structs are really integers (as mentioned above), they are the wrong interpretation and we should be using 'extern struct'. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` Previously used a 'packed struct' (which was not FFI-safe on stage1). After applying this change, the translated structures have align(1) explicitly applied to all of their fields AS EXPECTED (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = extern struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = extern struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` Also note for reference: Since the last stable release (0.9.1), there was a change in the language spec related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer Suggsestive both to the change to an integer-backed representation which is incompatible with C's notation.
This relaxes the restriction added in c7884af
It fixes issue #12733
Right now, the compiler only considered packed structs to be
C compatible (extern) if the structs are "ABI sized".
See also b83c037 for details on
the extern restrictions on
packed struct
This is an issue on all platforms, but is of particular
importance on aarch64-macos (see my other issue for details)
Previously a type like
would fail to translate and turn into
This fixes this issue for ABI-sized struct, making the translate-c
rules consistent with the regular compiler.
However, we still keep the restriction on structs that are not "ABI-sized"
So
will still fail with the appropriate error (in the sense it will emit
@compileError
)Implementation details
For the purposes of translate-c, a packed struct is considered
"ABI-sized" when sizeof(target_struct) <= sizeof(void*)
Unfortunately, right now the translate-c
*Context
structure (on the Zig side)doesn't directly have any information on the target we're translating into.
However, clang has this target information, and keeps it in clang::ASTContext.
I added a helper function to extract the
clang::TargetInfo
from this,then query the pointer size.
This is potentially controversial, since it technically makes the
output target-dependent.
The decision of whether to accept/reject a packed struct varies depending
on the target's pointer size.
Howevee, the translator already asks for (integer) field offsets elsewhere,
so I'm pretty sure target info already impacts translation.
We also add getSize() and getDataSize() functions to *clang.RecordDecl.
These return padded and unpadded sizes respectively.
For a packed struct, they should be the same.
The overall effect is 4 more functions added to
zig_clang.cpp
used toextract target info & query sizes.
Overall Effect on M1 Macs
As I mentioned in the issue,
aarch64-macos.12
uses (ABI-sized) packed structs instdlib.h
. Even an empty file fails to compile without support. This makes resolving the underlying problem (#12733) crucial.After fixing the bug (and applying #12730 ), I can get
build test-run-translated-c
to finally work!Before, it just gave a nasty error:
Anyways, combining these two PRs seems to fix the most serious stage3 compiler and tranlsate-c bugs that I've been encountering on my M1 Mac.
In addition to fixing
test-run-translated-c
, it also fixes my own libraries. In particular the test suite of zig-mpack finally passes!With stage3, most of the remaining issues in my main codebase seem to be bugs with my project that stage1 didn't catch. For example, I had two conflicting definitions of
const debug = .....
. It was defined twice in my root module.Hopefully these fixes mean I can finally get back to doing real programming with stage3 and Zig as my backend. Big thanks of course to @Vexu and the whole Zig team 🎉
Remaining (possible) questions
packed struct
in stage3. Ideally wouldn't we support all of them?Please note, I have homework due (that I've been dodging) so I may not be super responsive (maybe just amend the PR?)