From 43a091081f1fad7e4d1f2bf2d2b229fb82b65c11 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 24 Oct 2026 23:21:24 +0200 Subject: [PATCH] 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 --- src/codegen.zig | 8 ++++---- src/codegen/llvm.zig | 32 +++++++++++++++++++++----------- test/behavior/align.zig | 36 ++++++++++++++++++++++++++++++++++++ test/behavior/union.zig | 16 ++++++++++++++++ 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index d4828f4fc73027aa4e854bf55e002f855ccfe9b0..cf2018a5c0d2eea2cc08fe347defcd77ce53f0bb 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -617,6 +617,7 @@ pub fn generateSymbol( else => unreachable, }, .un => |un| { + const union_begin = w.end; const layout = ty.unionGetLayout(zcu); if (layout.payload_size == 0) { @@ -648,11 +649,10 @@ pub fn generateSymbol( if (layout.tag_size > 0 and layout.tag_align.compare(.lt, layout.payload_align)) { try generateSymbol(bin_file, pt, Value.fromInterned(un.tag), w, reloc_parent); - - if (layout.padding > 0) { - try w.splatByteAll(0, layout.padding); - } } + + const padding: usize = @intCast(union_begin + layout.abi_size - w.end); + if (padding > 0) try w.splatByteAll(0, padding); }, .bitpack => |bitpack| try generateSymbol(bin_file, pt, .fromInterned(bitpack.backing_int_val), w, reloc_parent), .memoized_call => unreachable, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 9b7dc14533385fcf29d7d0367ea468c1e3e52efe..24253e0c066936883fceb3e098d52c88e9d0c201 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2898,8 +2898,8 @@ pub const Object = struct { o.gpa, try o.builder.arrayType(padding_len, .i8), ); - if (@ctz(offset) < max_field_ty_align.toLog2Units()) { - struct_kind = .@"packed"; // prevent unexpected trailing padding + if (struct_type.alignment.compareStrict(.lt, max_field_ty_align)) { + struct_kind = .@"packed"; // prevent wrong alignment } } @@ -2978,14 +2978,22 @@ pub const Object = struct { const aligned_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[layout.most_aligned_field]); const aligned_field_llvm_ty = try o.lowerType(aligned_field_ty, repr); + const struct_kind: Builder.Type.Structure.Kind = for (union_obj.field_types.get(ip)) |field_ty_ip| { + const field_ty = Type.fromInterned(field_ty_ip); + if (!field_ty.hasRuntimeBits(zcu)) continue; + if (layout.payload_align.compareStrict(.lt, field_ty.abiAlignment(zcu))) { + break .@"packed"; // prevent wrong alignment + } + } else .normal; + const payload_ty = ty: { - if (layout.most_aligned_field_size == layout.payload_size) { - break :ty aligned_field_llvm_ty; - } const padding_len = if (layout.tag_size == 0) layout.abi_size - layout.most_aligned_field_size else layout.payload_size - layout.most_aligned_field_size; + if (padding_len == 0) { + break :ty aligned_field_llvm_ty; + } break :ty try o.builder.structType(.@"packed", &.{ aligned_field_llvm_ty, try o.builder.arrayType(padding_len, .i8), @@ -2998,7 +3006,7 @@ pub const Object = struct { o.builder.namedTypeSetBody( ty, - try o.builder.structType(.normal, &.{payload_ty}), + try o.builder.structType(struct_kind, &.{payload_ty}), ); return ty; } @@ -3026,7 +3034,7 @@ pub const Object = struct { o.builder.namedTypeSetBody( ty, - try o.builder.structType(.normal, llvm_fields[0..llvm_fields_len]), + try o.builder.structType(struct_kind, llvm_fields[0..llvm_fields_len]), ); return ty; }, @@ -3579,6 +3587,11 @@ pub const Object = struct { const payload = if (un.tag != .none) p: { const field_index = zcu.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?; const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]); + const field_size = field_ty.abiSize(zcu); + const padding_len = if (layout.tag_size == 0) + layout.abi_size - field_size + else + layout.payload_size - field_size; // Sometimes we must make an unnamed struct because LLVM does // not support bitcasting our payload struct to the true union payload type. @@ -3587,7 +3600,6 @@ pub const Object = struct { need_unnamed = layout.most_aligned_field != field_index; if (!field_ty.hasRuntimeBits(zcu)) { - const padding_len = layout.payload_size; break :p try o.builder.undefConst(try o.builder.arrayType(padding_len, .i8)); } const payload = try o.lowerValue(un.val, repr); @@ -3595,9 +3607,7 @@ pub const Object = struct { if (payload_ty != union_ty.structFields(&o.builder)[ @intFromBool(layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align)) ]) need_unnamed = true; - const field_size = field_ty.abiSize(zcu); - if (field_size == layout.payload_size) break :p payload; - const padding_len = layout.payload_size - field_size; + if (padding_len == 0) break :p payload; const padding_ty = try o.builder.arrayType(padding_len, .i8); break :p try o.builder.structConst( try o.builder.structType(.@"packed", &.{ payload_ty, padding_ty }), diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 3ef1249e78a49c1abf71d2a42e9c4d130acf4ef3..7d075325f695a3082e08eb299e8aa77c6ad02b2e 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -495,6 +495,42 @@ test "struct field explicit alignment" { try expect(@intFromPtr(&node.massive_byte) % 64 == 0); } +test "nested struct field alignment" { + const S = struct { + fn doTheTest() !void { + const T = struct { + a: u8, + b: struct { c: u16 align(1) }, + }; + const init: T = .{ .a = 0, .b = .{ .c = ~@as(u16, 0) } }; + var t: T = init; // copy constant aggregate + _ = &t; + try expect(t.b.c == ~@as(u16, 0)); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "nested union field alignment" { + const S = struct { + fn doTheTest() !void { + const T = struct { + a: u8, + b: union { c: u16 align(1) }, + }; + const init: T = .{ .a = 0, .b = .{ .c = ~@as(u16, 0) } }; + var t: T = init; // copy constant aggregate + _ = &t; + try expect(t.b.c == ~@as(u16, 0)); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + test "align(N) on functions" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/union.zig b/test/behavior/union.zig index e0f294557def1e340f6ffc2d7f1f9e8cfbec2856..879cbe418596939a8cb8c0d8afaa3d4e4d000e50 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1162,6 +1162,22 @@ test "extern union most-aligned field is smaller" { try expect(a != null); } +test "union constant trailing padding" { + const S = struct { + fn doTheTest() !void { + const U = extern union { x: u32, y: [5]u8 }; + var u = [2]U{ .{ .x = 0 }, .{ .y = @splat(0xff) } }; + _ = &u; + try expect(u[0].x == 0); + try expect(u[1].y[0] == 0xff); + try expect(u[1].y[4] == 0xff); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + test "return an extern union from C calling convention" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO -- 2.54.0