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(...@@ -617,6 +617,7 @@ pub fn generateSymbol(
617 else => unreachable,617 else => unreachable,
618 },618 },
619 .un => |un| {619 .un => |un| {
620 const union_begin = w.end;
620 const layout = ty.unionGetLayout(zcu);621 const layout = ty.unionGetLayout(zcu);
621622
622 if (layout.payload_size == 0) {623 if (layout.payload_size == 0) {
...@@ -648,11 +649,10 @@ pub fn generateSymbol(...@@ -648,11 +649,10 @@ pub fn generateSymbol(
648649
649 if (layout.tag_size > 0 and layout.tag_align.compare(.lt, layout.payload_align)) {650 if (layout.tag_size > 0 and layout.tag_align.compare(.lt, layout.payload_align)) {
650 try generateSymbol(bin_file, pt, Value.fromInterned(un.tag), w, reloc_parent);651 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 }
655 }652 }
653
654 const padding: usize = @intCast(union_begin + layout.abi_size - w.end);
655 if (padding > 0) try w.splatByteAll(0, padding);
656 },656 },
657 .bitpack => |bitpack| try generateSymbol(bin_file, pt, .fromInterned(bitpack.backing_int_val), w, reloc_parent),657 .bitpack => |bitpack| try generateSymbol(bin_file, pt, .fromInterned(bitpack.backing_int_val), w, reloc_parent),
658 .memoized_call => unreachable,658 .memoized_call => unreachable,
src/codegen/llvm.zig+21-11
...@@ -2933,8 +2933,8 @@ pub const Object = struct {...@@ -2933,8 +2933,8 @@ pub const Object = struct {
2933 o.gpa,2933 o.gpa,
2934 try o.builder.arrayType(padding_len, .i8),2934 try o.builder.arrayType(padding_len, .i8),
2935 );2935 );
2936 if (@ctz(offset) < max_field_ty_align.toLog2Units()) {2936 if (struct_type.alignment.compareStrict(.lt, max_field_ty_align)) {
2937 struct_kind = .@"packed"; // prevent unexpected trailing padding2937 struct_kind = .@"packed"; // prevent wrong alignment
2938 }2938 }
2939 }2939 }
29402940
...@@ -3013,14 +3013,22 @@ pub const Object = struct {...@@ -3013,14 +3013,22 @@ pub const Object = struct {
3013 const aligned_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[layout.most_aligned_field]);3013 const aligned_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[layout.most_aligned_field]);
3014 const aligned_field_llvm_ty = try o.lowerType(aligned_field_ty, repr);3014 const aligned_field_llvm_ty = try o.lowerType(aligned_field_ty, repr);
30153015
3016 const payload_ty = ty: {3016 const struct_kind: Builder.Type.Structure.Kind = for (union_obj.field_types.get(ip)) |field_ty_ip| {
3017 if (layout.most_aligned_field_size == layout.payload_size) {3017 const field_ty = Type.fromInterned(field_ty_ip);
3018 break :ty aligned_field_llvm_ty;3018 if (!field_ty.hasRuntimeBits(zcu)) continue;
3019 if (layout.payload_align.compareStrict(.lt, field_ty.abiAlignment(zcu))) {
3020 break .@"packed"; // prevent wrong alignment
3019 }3021 }
3022 } else .normal;
3023
3024 const payload_ty = ty: {
3020 const padding_len = if (layout.tag_size == 0)3025 const padding_len = if (layout.tag_size == 0)
3021 layout.abi_size - layout.most_aligned_field_size3026 layout.abi_size - layout.most_aligned_field_size
3022 else3027 else
3023 layout.payload_size - layout.most_aligned_field_size;3028 layout.payload_size - layout.most_aligned_field_size;
3029 if (padding_len == 0) {
3030 break :ty aligned_field_llvm_ty;
3031 }
3024 break :ty try o.builder.structType(.@"packed", &.{3032 break :ty try o.builder.structType(.@"packed", &.{
3025 aligned_field_llvm_ty,3033 aligned_field_llvm_ty,
3026 try o.builder.arrayType(padding_len, .i8),3034 try o.builder.arrayType(padding_len, .i8),
...@@ -3033,7 +3041,7 @@ pub const Object = struct {...@@ -3033,7 +3041,7 @@ pub const Object = struct {
30333041
3034 o.builder.namedTypeSetBody(3042 o.builder.namedTypeSetBody(
3035 ty,3043 ty,
3036 try o.builder.structType(.normal, &.{payload_ty}),3044 try o.builder.structType(struct_kind, &.{payload_ty}),
3037 );3045 );
3038 return ty;3046 return ty;
3039 }3047 }
...@@ -3061,7 +3069,7 @@ pub const Object = struct {...@@ -3061,7 +3069,7 @@ pub const Object = struct {
30613069
3062 o.builder.namedTypeSetBody(3070 o.builder.namedTypeSetBody(
3063 ty,3071 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]),
3065 );3073 );
3066 return ty;3074 return ty;
3067 },3075 },
...@@ -3683,6 +3691,11 @@ pub const Object = struct {...@@ -3683,6 +3691,11 @@ pub const Object = struct {
3683 const payload = if (un.tag != .none) p: {3691 const payload = if (un.tag != .none) p: {
3684 const field_index = zcu.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?;3692 const field_index = zcu.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?;
3685 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]);3693 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
3687 // Sometimes we must make an unnamed struct because LLVM does3700 // Sometimes we must make an unnamed struct because LLVM does
3688 // not support bitcasting our payload struct to the true union payload type.3701 // not support bitcasting our payload struct to the true union payload type.
...@@ -3691,7 +3704,6 @@ pub const Object = struct {...@@ -3691,7 +3704,6 @@ pub const Object = struct {
3691 need_unnamed = layout.most_aligned_field != field_index;3704 need_unnamed = layout.most_aligned_field != field_index;
36923705
3693 if (!field_ty.hasRuntimeBits(zcu)) {3706 if (!field_ty.hasRuntimeBits(zcu)) {
3694 const padding_len = layout.payload_size;
3695 break :p try o.builder.undefConst(try o.builder.arrayType(padding_len, .i8));3707 break :p try o.builder.undefConst(try o.builder.arrayType(padding_len, .i8));
3696 }3708 }
3697 const payload = try o.lowerValue(un.val, repr);3709 const payload = try o.lowerValue(un.val, repr);
...@@ -3699,9 +3711,7 @@ pub const Object = struct {...@@ -3699,9 +3711,7 @@ pub const Object = struct {
3699 if (payload_ty != union_ty.structFields(&o.builder)[3711 if (payload_ty != union_ty.structFields(&o.builder)[
3700 @intFromBool(layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align))3712 @intFromBool(layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align))
3701 ]) need_unnamed = true;3713 ]) need_unnamed = true;
3702 const field_size = field_ty.abiSize(zcu);3714 if (padding_len == 0) break :p payload;
3703 if (field_size == layout.payload_size) break :p payload;
3704 const padding_len = layout.payload_size - field_size;
3705 const padding_ty = try o.builder.arrayType(padding_len, .i8);3715 const padding_ty = try o.builder.arrayType(padding_len, .i8);
3706 break :p try o.builder.structConst(3716 break :p try o.builder.structConst(
3707 try o.builder.structType(.@"packed", &.{ payload_ty, padding_ty }),3717 try o.builder.structType(.@"packed", &.{ payload_ty, padding_ty }),
test/behavior/align.zig+36
...@@ -499,6 +499,42 @@ test "struct field explicit alignment" {...@@ -499,6 +499,42 @@ test "struct field explicit alignment" {
499 try expect(@intFromPtr(&node.massive_byte) % 64 == 0);499 try expect(@intFromPtr(&node.massive_byte) % 64 == 0);
500}500}
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
502test "align(N) on functions" {538test "align(N) on functions" {
503 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO539 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
504 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO540 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" {...@@ -1190,6 +1190,22 @@ test "extern union most-aligned field is smaller" {
1190 try expect(a != null);1190 try expect(a != null);
1191}1191}
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
1193test "return an extern union from C calling convention" {1209test "return an extern union from C calling convention" {
1194 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1210 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1195 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1211 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO