Zig Version
3edaef9e01
Steps to Reproduce and Observed Behavior
#31403 introduced some new assertions (via zig_static_assert) emitted by the CBE to verify that the C compiler agrees with the size / alignment of generated structs.
There was some discussion about this already on zulip, which I'll duplicate here.
When building from source, these assertions are tripped, ie:
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(12881,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(12887,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(12900,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(19580,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
...
One specific example:
zig_static_assert(sizeof (struct Zcu_LazySrcLoc_Offset_51837) == 16, "incorrect size");
I inspected the actual values by smuggling them into error messages like this:
#define PRINT_STRUCT_INFO(t) void print_info_##t(char (*size)[sizeof(struct t)], char (*align)[_Alignof(struct t)]) { __pragma(message(__FUNCSIG__)); }
PRINT_STRUCT_INFO(Zcu_LazySrcLoc_Offset_51837);
Which results in:
void __cdecl print_info_Zcu_LazySrcLoc_Offset_51837(char (*)[24],char (*)[8])
So, cl.exe is choosing to do something unexpected with the zig_packed, here is a reduction:
typedef struct {
uint32_t a;
uint32_t b;
uint32_t c;
} S;
#pragma pack(1)
typedef union {
S s;
} U;
#pragma pack()
typedef struct {
zig_align(8) U u;
uint8_t flag;
} Foo;
typedef struct {
zig_align(8) zig_packed(union { S s; }) U;
uint8_t flag;
} FooInline;
In this case, FooInline is 24 bytes and Foo is 16 bytes, under cl.exe. They are both 16 bytes via clang.
Jacob pointed out that if we swap the ordering of zig_packed and zig_align, ie. zig_packed(union { S s; }) zig_align(8), this resolves the immediate issue, ie. the sizes match.
Based on this, I came up with this diff:
diff --git a/lib/zig.h b/lib/zig.h
index dc5666d894..e3594252c8 100644
--- a/lib/zig.h
+++ b/lib/zig.h
@@ -274,6 +274,12 @@
#define zig_packed(definition) zig_packed_unavailable
#endif
+#if defined(zig_msvc)
+#define zig_packed_align(align, definition) zig_packed(definition) zig_align(align)
+#else
+#define zig_packed_align(align, definition) zig_align(align) zig_packed(definition)
+#endif
+
#if zig_has_attribute(section) || defined(zig_tinyc)
#define zig_linksection(name) __attribute__((section(name)))
#define zig_linksection_fn zig_linksection
diff --git a/src/codegen/c/type/render_defs.zig b/src/codegen/c/type/render_defs.zig
index 52bf573064..3ab0f2f5a0 100644
--- a/src/codegen/c/type/render_defs.zig
+++ b/src/codegen/c/type/render_defs.zig
@@ -559,11 +559,14 @@ fn defineUnionAuto(
});
if (payload_has_bits) {
try w.writeByte(' ');
- if (overalign) {
+ if (pack and overalign) {
+ try w.print("zig_packed_align({d}, ", .{union_type.alignment.toByteUnits().?});
+ } else if (overalign) {
// Specify the alignment of `union { ... } payload;` to align the union's `struct`.
try w.print("zig_align({d}) ", .{union_type.alignment.toByteUnits().?});
+ } else if (pack) {
+ try w.writeAll("zig_packed(");
}
- if (pack) try w.writeAll("zig_packed(");
try w.writeAll("union {\n");
for (0..enum_tag_ty.enumFieldCount(zcu)) |field_index| {
const field_ty = ty.fieldType(field_index, zcu);
diff --git a/stage1/zig.h b/stage1/zig.h
index 0b9c6e58ca..0891618013 100644
--- a/stage1/zig.h
+++ b/stage1/zig.h
@@ -274,6 +274,12 @@
#define zig_packed(definition) zig_packed_unavailable
#endif
+#if defined(zig_msvc)
+#define zig_packed_align(align, definition) zig_packed(definition) zig_align(align)
+#else
+#define zig_packed_align(align, definition) zig_align(align) zig_packed(definition)
+#endif
+
#if zig_has_attribute(section) || defined(zig_tinyc)
#define zig_linksection(name) __attribute__((section(name)))
#define zig_linksection_fn zig_linksection
However, as Jacob also pointed out, there are further issues:
struct Foo { int x; };
struct Bar { __declspec(align(4)) int y; };
#pragma pack(1)
struct WithFoo { __declspec(align(1)) struct Foo foo; char after; };
struct WithBar { __declspec(align(1)) struct Bar bar; char after; };
int sizeof_with_foo = sizeof(struct WithFoo);
int sizeof_with_bar = sizeof(struct WithBar);
Here, sizeof_with_foo is 5 and sizeof_with_bar is 8, under cl.exe.
This problem affects the Zcu_LazySrcLoc_Offset_51837 case, zig_packed union has a struct field that itself uses zig_align(8):
struct Zcu_LazySrcLoc_Offset_TracedOffset_51840 { /* Zcu.LazySrcLoc.Offset.TracedOffset */
zig_align(8) enum__zig_Ast_Node_Offset_11866 x;
};
I'm not sure how to solve this in the general case, as there doesn't seem to be a way to force cl.exe to pack these unions / structs the way we want it to.
Perhaps we could have the CBE notice it is emitting this pattern (an aligned struct field inside a type we are going to later wrap with zig_packed), and emit instead (using the above as an example) a version of Zcu_LazySrcLoc_Offset_TracedOffset_51840 that doesn't specify zig_align on it's first field. If the affected struct was going to be used in both a non-packed parent struct / union and a packed one, we would have to emit two versions (one without the align attribute and one with) and select the correct one based on the context.
My focus for this issue was specifically the ability to build the zig compiler itself from source on Windows, as this is the workflow I use to follow master branch.
Expected Behavior
CBE output should be compatible with MSVC.
### Zig Version
3edaef9e011ac500f66c9ee0ba3ea24be905bcde
### Steps to Reproduce and Observed Behavior
https://codeberg.org/ziglang/zig/pulls/31403 introduced some new assertions (via `zig_static_assert`) emitted by the CBE to verify that the C compiler agrees with the size / alignment of generated structs.
There was some discussion about this already on [zulip](https://zsf.zulipchat.com/#narrow/channel/454360-compiler/topic/type.20resolution/near/578767693), which I'll duplicate here.
When building from source, these assertions are tripped, ie:
```
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(12881,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(12887,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(12900,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.c(19580,1): error C2466: cannot allocate an array of constant size 0 [C:\cygwin64\home\kcbanner\kit\zig\build-release\zig2.vcxproj]
...
```
One specific example:
```
zig_static_assert(sizeof (struct Zcu_LazySrcLoc_Offset_51837) == 16, "incorrect size");
```
I inspected the actual values by smuggling them into error messages like this:
```
#define PRINT_STRUCT_INFO(t) void print_info_##t(char (*size)[sizeof(struct t)], char (*align)[_Alignof(struct t)]) { __pragma(message(__FUNCSIG__)); }
PRINT_STRUCT_INFO(Zcu_LazySrcLoc_Offset_51837);
```
Which results in:
```
void __cdecl print_info_Zcu_LazySrcLoc_Offset_51837(char (*)[24],char (*)[8])
```
So, cl.exe is choosing to do something unexpected with the `zig_packed`, here is a reduction:
```c
typedef struct {
uint32_t a;
uint32_t b;
uint32_t c;
} S;
#pragma pack(1)
typedef union {
S s;
} U;
#pragma pack()
typedef struct {
zig_align(8) U u;
uint8_t flag;
} Foo;
typedef struct {
zig_align(8) zig_packed(union { S s; }) U;
uint8_t flag;
} FooInline;
```
In this case, `FooInline` is 24 bytes and `Foo` is 16 bytes, under cl.exe. They are both 16 bytes via clang.
Jacob pointed out that if we swap the ordering of `zig_packed` and `zig_align`, ie. `zig_packed(union { S s; }) zig_align(8)`, this resolves the immediate issue, ie. the sizes match.
Based on this, I came up with this diff:
```diff
diff --git a/lib/zig.h b/lib/zig.h
index dc5666d894..e3594252c8 100644
--- a/lib/zig.h
+++ b/lib/zig.h
@@ -274,6 +274,12 @@
#define zig_packed(definition) zig_packed_unavailable
#endif
+#if defined(zig_msvc)
+#define zig_packed_align(align, definition) zig_packed(definition) zig_align(align)
+#else
+#define zig_packed_align(align, definition) zig_align(align) zig_packed(definition)
+#endif
+
#if zig_has_attribute(section) || defined(zig_tinyc)
#define zig_linksection(name) __attribute__((section(name)))
#define zig_linksection_fn zig_linksection
diff --git a/src/codegen/c/type/render_defs.zig b/src/codegen/c/type/render_defs.zig
index 52bf573064..3ab0f2f5a0 100644
--- a/src/codegen/c/type/render_defs.zig
+++ b/src/codegen/c/type/render_defs.zig
@@ -559,11 +559,14 @@ fn defineUnionAuto(
});
if (payload_has_bits) {
try w.writeByte(' ');
- if (overalign) {
+ if (pack and overalign) {
+ try w.print("zig_packed_align({d}, ", .{union_type.alignment.toByteUnits().?});
+ } else if (overalign) {
// Specify the alignment of `union { ... } payload;` to align the union's `struct`.
try w.print("zig_align({d}) ", .{union_type.alignment.toByteUnits().?});
+ } else if (pack) {
+ try w.writeAll("zig_packed(");
}
- if (pack) try w.writeAll("zig_packed(");
try w.writeAll("union {\n");
for (0..enum_tag_ty.enumFieldCount(zcu)) |field_index| {
const field_ty = ty.fieldType(field_index, zcu);
diff --git a/stage1/zig.h b/stage1/zig.h
index 0b9c6e58ca..0891618013 100644
--- a/stage1/zig.h
+++ b/stage1/zig.h
@@ -274,6 +274,12 @@
#define zig_packed(definition) zig_packed_unavailable
#endif
+#if defined(zig_msvc)
+#define zig_packed_align(align, definition) zig_packed(definition) zig_align(align)
+#else
+#define zig_packed_align(align, definition) zig_align(align) zig_packed(definition)
+#endif
+
#if zig_has_attribute(section) || defined(zig_tinyc)
#define zig_linksection(name) __attribute__((section(name)))
#define zig_linksection_fn zig_linksection
```
However, as Jacob also pointed out, there are further issues:
```
struct Foo { int x; };
struct Bar { __declspec(align(4)) int y; };
#pragma pack(1)
struct WithFoo { __declspec(align(1)) struct Foo foo; char after; };
struct WithBar { __declspec(align(1)) struct Bar bar; char after; };
int sizeof_with_foo = sizeof(struct WithFoo);
int sizeof_with_bar = sizeof(struct WithBar);
```
Here, `sizeof_with_foo` is 5 and `sizeof_with_bar` is 8, under cl.exe.
This problem affects the `Zcu_LazySrcLoc_Offset_51837` case, `zig_packed` union has a struct field that itself uses `zig_align(8)`:
```c
struct Zcu_LazySrcLoc_Offset_TracedOffset_51840 { /* Zcu.LazySrcLoc.Offset.TracedOffset */
zig_align(8) enum__zig_Ast_Node_Offset_11866 x;
};
```
I'm not sure how to solve this in the general case, as there doesn't seem to be a way to force cl.exe to pack these unions / structs the way we want it to.
Perhaps we could have the CBE notice it is emitting this pattern (an aligned struct field inside a type we are going to later wrap with `zig_packed`), and emit instead (using the above as an example) a version of `Zcu_LazySrcLoc_Offset_TracedOffset_51840` that doesn't specify `zig_align` on it's first field. If the affected struct was going to be used in both a non-packed parent struct / union and a packed one, we would have to emit two versions (one without the align attribute and one with) and select the correct one based on the context.
My focus for this issue was specifically the ability to build the zig compiler itself from source on Windows, as this is the workflow I use to follow master branch.
### Expected Behavior
CBE output should be compatible with MSVC.