authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-24 23:21:24+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-24 23:21:24+02:00
logbca1cc3eef57f71ccfbc98d9dfe973431f9f237e
treee044c5b7b36ed0fb9dda34730ad20d3a74af75dc
parent3423b14e83f29817cc01bf15382d2253cd5d2cce
parentf356324325dc3637b93859d7b53f98f4e66009f5

Merge pull request 'llvm: fix bugs related on padding of struct and unions' (#37347) from pavelverigo/zig:i37335 into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37347 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

4 files changed, 77 insertions(+), 15 deletions(-)

src/codegen.zig+4-4
......@@ -617,6 +617,7 @@ pub fn generateSymbol(
617617 else => unreachable,
618618 },
619619 .un => |un| {
620 const union_begin = w.end;
620621 const layout = ty.unionGetLayout(zcu);
621622
622623 if (layout.payload_size == 0) {
......@@ -648,11 +649,10 @@ pub fn generateSymbol(
648649
649650 if (layout.tag_size > 0 and layout.tag_align.compare(.lt, layout.payload_align)) {
650651 try generateSymbol(bin_file, pt, Value.fromInterned(un.tag), w, reloc_parent);
651
652 if (layout.padding > 0) {
653 try w.splatByteAll(0, layout.padding);
654 }
655652 }
653
654 const padding: usize = @intCast(union_begin + layout.abi_size - w.end);
655 if (padding > 0) try w.splatByteAll(0, padding);
656656 },
657657 .bitpack => |bitpack| try generateSymbol(bin_file, pt, .fromInterned(bitpack.backing_int_val), w, reloc_parent),
658658 .memoized_call => unreachable,
src/codegen/llvm.zig+21-11
......@@ -2933,8 +2933,8 @@ pub const Object = struct {
29332933 o.gpa,
29342934 try o.builder.arrayType(padding_len, .i8),
29352935 );
2936 if (@ctz(offset) < max_field_ty_align.toLog2Units()) {
2937 struct_kind = .@"packed"; // prevent unexpected trailing padding
2936 if (struct_type.alignment.compareStrict(.lt, max_field_ty_align)) {
2937 struct_kind = .@"packed"; // prevent wrong alignment
29382938 }
29392939 }
29402940
......@@ -3013,14 +3013,22 @@ pub const Object = struct {
30133013 const aligned_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[layout.most_aligned_field]);
30143014 const aligned_field_llvm_ty = try o.lowerType(aligned_field_ty, repr);
30153015
3016 const payload_ty = ty: {
3017 if (layout.most_aligned_field_size == layout.payload_size) {
3018 break :ty aligned_field_llvm_ty;
3016 const struct_kind: Builder.Type.Structure.Kind = for (union_obj.field_types.get(ip)) |field_ty_ip| {
3017 const field_ty = Type.fromInterned(field_ty_ip);
3018 if (!field_ty.hasRuntimeBits(zcu)) continue;
3019 if (layout.payload_align.compareStrict(.lt, field_ty.abiAlignment(zcu))) {
3020 break .@"packed"; // prevent wrong alignment
30193021 }
3022 } else .normal;
3023
3024 const payload_ty = ty: {
30203025 const padding_len = if (layout.tag_size == 0)
30213026 layout.abi_size - layout.most_aligned_field_size
30223027 else
30233028 layout.payload_size - layout.most_aligned_field_size;
3029 if (padding_len == 0) {
3030 break :ty aligned_field_llvm_ty;
3031 }
30243032 break :ty try o.builder.structType(.@"packed", &.{
30253033 aligned_field_llvm_ty,
30263034 try o.builder.arrayType(padding_len, .i8),
......@@ -3033,7 +3041,7 @@ pub const Object = struct {
30333041
30343042 o.builder.namedTypeSetBody(
30353043 ty,
3036 try o.builder.structType(.normal, &.{payload_ty}),
3044 try o.builder.structType(struct_kind, &.{payload_ty}),
30373045 );
30383046 return ty;
30393047 }
......@@ -3061,7 +3069,7 @@ pub const Object = struct {
30613069
30623070 o.builder.namedTypeSetBody(
30633071 ty,
3064 try o.builder.structType(.normal, llvm_fields[0..llvm_fields_len]),
3072 try o.builder.structType(struct_kind, llvm_fields[0..llvm_fields_len]),
30653073 );
30663074 return ty;
30673075 },
......@@ -3683,6 +3691,11 @@ pub const Object = struct {
36833691 const payload = if (un.tag != .none) p: {
36843692 const field_index = zcu.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?;
36853693 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]);
3694 const field_size = field_ty.abiSize(zcu);
3695 const padding_len = if (layout.tag_size == 0)
3696 layout.abi_size - field_size
3697 else
3698 layout.payload_size - field_size;
36863699
36873700 // Sometimes we must make an unnamed struct because LLVM does
36883701 // not support bitcasting our payload struct to the true union payload type.
......@@ -3691,7 +3704,6 @@ pub const Object = struct {
36913704 need_unnamed = layout.most_aligned_field != field_index;
36923705
36933706 if (!field_ty.hasRuntimeBits(zcu)) {
3694 const padding_len = layout.payload_size;
36953707 break :p try o.builder.undefConst(try o.builder.arrayType(padding_len, .i8));
36963708 }
36973709 const payload = try o.lowerValue(un.val, repr);
......@@ -3699,9 +3711,7 @@ pub const Object = struct {
36993711 if (payload_ty != union_ty.structFields(&o.builder)[
37003712 @intFromBool(layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align))
37013713 ]) need_unnamed = true;
3702 const field_size = field_ty.abiSize(zcu);
3703 if (field_size == layout.payload_size) break :p payload;
3704 const padding_len = layout.payload_size - field_size;
3714 if (padding_len == 0) break :p payload;
37053715 const padding_ty = try o.builder.arrayType(padding_len, .i8);
37063716 break :p try o.builder.structConst(
37073717 try o.builder.structType(.@"packed", &.{ payload_ty, padding_ty }),
test/behavior/align.zig+36
......@@ -499,6 +499,42 @@ test "struct field explicit alignment" {
499499 try expect(@intFromPtr(&node.massive_byte) % 64 == 0);
500500}
501501
502test "nested struct field alignment" {
503 const S = struct {
504 fn doTheTest() !void {
505 const T = struct {
506 a: u8,
507 b: struct { c: u16 align(1) },
508 };
509 const init: T = .{ .a = 0, .b = .{ .c = ~@as(u16, 0) } };
510 var t: T = init; // copy constant aggregate
511 _ = &t;
512 try expect(t.b.c == ~@as(u16, 0));
513 }
514 };
515
516 try S.doTheTest();
517 try comptime S.doTheTest();
518}
519
520test "nested union field alignment" {
521 const S = struct {
522 fn doTheTest() !void {
523 const T = struct {
524 a: u8,
525 b: union { c: u16 align(1) },
526 };
527 const init: T = .{ .a = 0, .b = .{ .c = ~@as(u16, 0) } };
528 var t: T = init; // copy constant aggregate
529 _ = &t;
530 try expect(t.b.c == ~@as(u16, 0));
531 }
532 };
533
534 try S.doTheTest();
535 try comptime S.doTheTest();
536}
537
502538test "align(N) on functions" {
503539 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
504540 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
test/behavior/union.zig+16
......@@ -1190,6 +1190,22 @@ test "extern union most-aligned field is smaller" {
11901190 try expect(a != null);
11911191}
11921192
1193test "union constant trailing padding" {
1194 const S = struct {
1195 fn doTheTest() !void {
1196 const U = extern union { x: u32, y: [5]u8 };
1197 var u = [2]U{ .{ .x = 0 }, .{ .y = @splat(0xff) } };
1198 _ = &u;
1199 try expect(u[0].x == 0);
1200 try expect(u[1].y[0] == 0xff);
1201 try expect(u[1].y[4] == 0xff);
1202 }
1203 };
1204
1205 try S.doTheTest();
1206 try comptime S.doTheTest();
1207}
1208
11931209test "return an extern union from C calling convention" {
11941210 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
11951211 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO