From fc1c83a363d32e749b9061044db63852ab7255d0 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Wed, 6 May 2026 11:01:16 +0100 Subject: [PATCH] Air: fix legalization of packed struct init with OPV field I have verified that this fixes *both* of the reproductions given in https://codeberg.org/ziglang/zig/issues/31837 (they were the same bug). Resolves: https://codeberg.org/ziglang/zig/issues/31837 --- src/Air/Legalize.zig | 32 ++++++++++++++++++++++---------- test/behavior/bitcast.zig | 16 ++++++++++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig index e449835ce856ac7061f07abe33329fc4963f4fcf..23956038f83cc6ab258f040eb56ba4a52fe84a22 100644 --- a/src/Air/Legalize.zig +++ b/src/Air/Legalize.zig @@ -853,14 +853,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .@"union" => unreachable, .@"struct" => switch (agg_ty.containerLayout(zcu)) { .auto, .@"extern" => {}, - .@"packed" => switch (agg_ty.structFieldCount(zcu)) { - 0 => unreachable, - // An `aggregate_init` of a packed struct with 1 field is just a fancy bitcast. - 1 => continue :inst l.replaceInst(inst, .bitcast, .{ .ty_op = .{ - .ty = .fromType(agg_ty), - .operand = @enumFromInt(l.air_extra.items[ty_pl.payload]), - } }), - else => continue :inst l.replaceInst(inst, .block, try l.packedAggregateInitBlockPayload(inst)), + .@"packed" => { + // If any field accounts for the full bit size of the struct, this init + // is just equivalent to a bitcast of that field. This usually means the + // field count is 1, but not always, as there could be zero-bit fields. + const struct_bits = agg_ty.bitSize(zcu); + for (0..agg_ty.structFieldCount(zcu)) |field_index| { + const field_bits = agg_ty.fieldType(field_index, zcu).bitSize(zcu); + if (field_bits == struct_bits) { + // Just bitcast this field. + continue :inst l.replaceInst(inst, .bitcast, .{ .ty_op = .{ + .ty = .fromType(agg_ty), + .operand = @enumFromInt(l.air_extra.items[ty_pl.payload + field_index]), + } }); + } + } + // Otherwise, we will need to use a sequence of bitcasts and shifts to + // combine multiple values' bits. + continue :inst l.replaceInst(inst, .block, try l.packedAggregateInitBlockPayload(inst)); }, }, } @@ -2513,8 +2523,10 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro while (field_idx > 0) { field_idx -= 1; const field_ty = agg_ty.fieldType(field_idx, zcu); - const field_uint_ty = try pt.intType(.unsigned, @intCast(field_ty.bitSize(zcu))); - const field_bit_size_ref: Air.Inst.Ref = .fromValue(try pt.intValue(shift_ty, field_ty.bitSize(zcu))); + const field_bits: u16 = @intCast(field_ty.bitSize(zcu)); + assert(field_bits < num_bits); + const field_uint_ty = try pt.intType(.unsigned, field_bits); + const field_bit_size_ref: Air.Inst.Ref = .fromValue(try pt.intValue(shift_ty, field_bits)); const field_val: Air.Inst.Ref = @enumFromInt(l.air_extra.items[orig_ty_pl.payload + field_idx]); const shifted = main_block.addBinOp(l, .shl_exact, cur_uint, field_bit_size_ref).toRef(); diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index fa9ebfa92b74bdd19e5186712ac20a8a6aa8140b..eb1ebe17b54dcae1ee658a15ab87007ba185e310 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -591,3 +591,19 @@ test "@bitCast of float to extern struct" { try S.doTheTest(); try comptime S.doTheTest(); } + +test "@bitCast of packed struct with void field to integer" { + const S = packed struct(u8) { + v: void, + x: u8, + + fn doTheTest(x: u8) !void { + // Intentionally using `@as` to avoid RLS which masks the bug + const foo = @as(@This(), .{ .v = {}, .x = x }); + const as_int: u8 = @bitCast(foo); + try expect(as_int == x); + } + }; + try S.doTheTest(123); + try comptime S.doTheTest(123); +} -- 2.54.0