translate-c: packed struct implies align(1) on every field

Superceeds PR #12735 (now supporting all packed structs in GNU C)
Fixes issue #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 #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 commit is contained in:
Techcable
2022-09-04 17:16:26 -07:00
parent 34835bbbcf
commit 5b689d389f
6 changed files with 106 additions and 50 deletions

View File

@@ -250,20 +250,18 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
\\}
, "");
if (@import("builtin").zig_backend == .stage1) {
cases.add("struct initializer - packed",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdint.h>
\\#include <stdlib.h>
\\struct s {uint8_t x,y;
\\ uint32_t z;} __attribute__((packed)) s0 = {1, 2};
\\int main() {
\\ /* sizeof nor offsetof currently supported */
\\ if (((intptr_t)&s0.z - (intptr_t)&s0.x) != 2) abort();
\\ return 0;
\\}
, "");
}
cases.add("struct initializer - packed",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdint.h>
\\#include <stdlib.h>
\\struct s {uint8_t x,y;
\\ uint32_t z;} __attribute__((packed)) s0 = {1, 2};
\\int main() {
\\ /* sizeof nor offsetof currently supported */
\\ if (((intptr_t)&s0.z - (intptr_t)&s0.x) != 2) abort();
\\ return 0;
\\}
, "");
cases.add("cast signed array index to unsigned",
\\#include <stdlib.h>