Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,7 @@ or
{#header_open|packed union#}
<p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible
to be in a {#link|packed struct#}.</p>
<p>All fields in a packed union must have the same {#link|@bitSizeOf#}.</p>
{#header_close#}

{#header_open|Anonymous Union Literals#}
Expand Down
7 changes: 0 additions & 7 deletions lib/std/meta.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1193,13 +1193,6 @@ test hasUniqueRepresentation {

try testing.expect(hasUniqueRepresentation(TestStruct6));

const TestUnion1 = packed union {
a: u32,
b: u16,
};

try testing.expect(!hasUniqueRepresentation(TestUnion1));

const TestUnion2 = extern union {
a: u32,
b: u16,
Expand Down
76 changes: 55 additions & 21 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35614,6 +35614,12 @@ fn unionFields(
if (small.any_aligned_fields)
try field_aligns.ensureTotalCapacityPrecise(sema.arena, fields_len);

var max_bits: u64 = 0;
var min_bits: u64 = std.math.maxInt(u64);
var max_bits_src: LazySrcLoc = undefined;
var min_bits_src: LazySrcLoc = undefined;
var max_bits_ty: Type = undefined;
var min_bits_ty: Type = undefined;
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
Expand All @@ -35622,6 +35628,7 @@ fn unionFields(
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
var last_tag_val: ?Value = null;
const layout = union_type.flagsUnordered(ip).layout;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
Expand Down Expand Up @@ -35773,31 +35780,45 @@ fn unionFields(
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
const layout = union_type.flagsUnordered(ip).layout;
if (layout == .@"extern" and
!try sema.validateExternType(field_ty, .union_field))
{
const msg = msg: {
const msg = try sema.errMsg(type_src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
switch (layout) {
.@"extern" => if (!try sema.validateExternType(field_ty, .union_field)) {
const msg = msg: {
const msg = try sema.errMsg(type_src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);

try sema.explainWhyTypeIsNotExtern(msg, type_src, field_ty, .union_field);
try sema.explainWhyTypeIsNotExtern(msg, type_src, field_ty, .union_field);

try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
} else if (layout == .@"packed" and !try sema.validatePackedType(field_ty)) {
const msg = msg: {
const msg = try sema.errMsg(type_src, "packed unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
},
.@"packed" => {
if (!try sema.validatePackedType(field_ty)) {
const msg = msg: {
const msg = try sema.errMsg(type_src, "packed unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);

try sema.explainWhyTypeIsNotPacked(msg, type_src, field_ty);
try sema.explainWhyTypeIsNotPacked(msg, type_src, field_ty);

try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
const field_bits = try field_ty.bitSizeSema(pt);
if (field_bits >= max_bits) {
max_bits = field_bits;
max_bits_src = type_src;
max_bits_ty = field_ty;
}
if (field_bits <= min_bits) {
min_bits = field_bits;
min_bits_src = type_src;
min_bits_ty = field_ty;
}
},
.auto => {},
}

field_types.appendAssumeCapacity(field_ty.toIntern());
Expand All @@ -35815,6 +35836,19 @@ fn unionFields(
union_type.setFieldTypes(ip, field_types.items);
union_type.setFieldAligns(ip, field_aligns.items);

if (layout == .@"packed" and fields_len != 0 and min_bits != max_bits) {
const msg = msg: {
const msg = try sema.errMsg(src, "packed union has fields with mismatching bit sizes", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(min_bits_src, msg, "{d} bits here", .{min_bits});
try sema.addDeclaredHereNote(msg, min_bits_ty);
try sema.errNote(max_bits_src, msg, "{d} bits here", .{max_bits});
try sema.addDeclaredHereNote(msg, max_bits_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}

if (explicit_tags_seen.len > 0) {
const tag_ty = union_type.tagTypeUnordered(ip);
const tag_info = ip.loadEnumType(tag_ty);
Expand Down
22 changes: 14 additions & 8 deletions test/behavior/cast_int.zig
Original file line number Diff line number Diff line change
Expand Up @@ -225,20 +225,26 @@ test "load non byte-sized value in union" {
// using ptrCast not to depend on unitialised memory state

var union0: packed union {
p: Piece,
p: packed struct(u8) {
a: Piece,
b: u4,
},
int: u8,
} = .{ .int = 0 };
union0.int = 0b11111011;
try expect(union0.p.type == .PAWN);
try expect(union0.p.color == .BLACK);
try expect(union0.p.a.type == .PAWN);
try expect(union0.p.a.color == .BLACK);

var union1: union {
p: Piece,
p: packed struct(u8) {
a: Piece,
b: u4,
},
int: u8,
} = .{ .p = .{ .color = .WHITE, .type = .KING } };
@as(*u8, @ptrCast(&union1.p)).* = 0b11111011;
try expect(union1.p.type == .PAWN);
try expect(union1.p.color == .BLACK);
} = .{ .p = .{ .a = .{ .color = .WHITE, .type = .KING }, .b = 0 } };
@as(*u8, @ptrCast(&union1.p.a)).* = 0b11111011;
try expect(union1.p.a.type == .PAWN);
try expect(union1.p.a.color == .BLACK);

var pieces: [3]Piece = undefined;
@as(*u8, @ptrCast(&pieces[1])).* = 0b11111011;
Expand Down
7 changes: 5 additions & 2 deletions test/behavior/export_keyword.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ const PackedStruct = packed struct {
b: u8,
};
const PackedUnion = packed union {
a: u8,
a: packed struct(u32) {
a: u8,
b: u24 = 0,
},
b: u32,
};

Expand All @@ -29,7 +32,7 @@ test "packed struct, enum, union parameters in extern function" {
testPackedStuff(&(PackedStruct{
.a = 1,
.b = 2,
}), &(PackedUnion{ .a = 1 }));
}), &(PackedUnion{ .a = .{ .a = 1 } }));
}

export fn testPackedStuff(a: *const PackedStruct, b: *const PackedUnion) void {
Expand Down
19 changes: 13 additions & 6 deletions test/behavior/field_parent_ptr.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1755,30 +1755,37 @@ test "@fieldParentPtr extern union" {
test "@fieldParentPtr packed union" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.target.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO

const C = packed union {
a: bool,
a: packed struct(u32) {
a: bool,
b: u31 = 0,
},
b: f32,
c: packed struct { x: u8 },
c: packed struct(u32) {
x: u8,
b: u24 = 0,
},
d: i32,
};

{
const c: C = .{ .a = false };
const c: C = .{ .a = .{ .a = false } };
const pcf = &c.a;
const pc: *const C = @alignCast(@fieldParentPtr("a", pcf));
try expect(pc == &c);
}
{
const c: C = .{ .a = false };
const c: C = .{ .a = .{ .a = false } };
const pcf = &c.a;
var pc: *const C = undefined;
pc = @alignCast(@fieldParentPtr("a", pcf));
try expect(pc == &c);
}
{
const c: C = .{ .a = false };
const c: C = .{ .a = .{ .a = false } };
var pcf: @TypeOf(&c.a) = undefined;
pcf = &c.a;
var pc: *const C = undefined;
Expand All @@ -1787,7 +1794,7 @@ test "@fieldParentPtr packed union" {
}
{
var c: C = undefined;
c = .{ .a = false };
c = .{ .a = .{ .a = false } };
var pcf: @TypeOf(&c.a) = undefined;
pcf = &c.a;
var pc: *C = undefined;
Expand Down
8 changes: 7 additions & 1 deletion test/behavior/packed-struct.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,13 @@ test "load flag from packed struct in union" {
test "bitcasting a packed struct at comptime and using the result" {
comptime {
const Struct = packed struct {
x: packed union { a: u63, b: i32 },
x: packed union {
a: u63,
b: packed struct(u63) {
a: i32,
b: u31 = 0,
},
},
y: u1,

pub fn bitcast(fd: u64) @This() {
Expand Down
17 changes: 10 additions & 7 deletions test/behavior/packed-union.zig
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,17 @@ test "flags in packed union at offset" {

fn testFlagsInPackedUnionAtOffset() !void {
const FlagBits = packed union {
base_flags: packed union {
flags: packed struct(u4) {
enable_1: bool = true,
enable_2: bool = false,
enable_3: bool = false,
enable_4: bool = false,
base_flags: packed struct(u12) {
a: packed union {
flags: packed struct(u4) {
enable_1: bool = true,
enable_2: bool = false,
enable_3: bool = false,
enable_4: bool = false,
},
bits: u4,
},
bits: u4,
pad: u8 = 0,
},
adv_flags: packed struct(u12) {
pad: u8 = 0,
Expand Down
Loading