authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-10-01 00:07:21-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-10-02 13:15:28-04:00
logd657b6c0e2ab7c47f5416dc4df1abb2bfbecd4b6
tree1c28a341c2b8094468bac5121a3dc955f16f8582
parent53775b0999527ced0550f3abb32c4a4a715af74e

sema: support reinterpreting extern/packed unions at comptime via field access

My previous change for reading / writing to unions at comptime did not handle union field read/writes correctly in all cases. Previously, if a field was written to a union, it would overwrite the entire value. This is problematic when a field of a larger size is subsequently read, because the value would not be long enough, causing a panic. Additionally, the writing behaviour itself was incorrect. Writing to a field of a packed or extern union should only overwrite the bits corresponding to that field, allowing for memory reintepretation via field writes / reads. I addressed these problems as follows: Add the concept of a "backing type" for extern / packed unions (`Type.unionBackingType`). For extern unions, this is a `u8` array, for packed unions it's an integer matching the `bitSize` of the union. Whenever union memory is read at comptime, it's read as this type. When union memory is written at comptime, the tag may still be known. If so, the memory is written using the tagged type. If the tag is unknown (because this union had previously been read from memory), it's simply written back out as the backing type. I added `write_packed` to the `reinterpret` field of `ComptimePtrMutationKit`. This causes writes of the operand to be packed - which is necessary when writing to a field of a packed union. Without this, writing a value to a `u1` field would overwrite the entire byte it occupied. The final case to address was reading a different (potentially larger) field from a union when it was written with a known tag. To handle this, a new kind of bitcast was introduced (`bitCastUnionFieldVal`) which supports reading a larger field by using a backing buffer that has the unwritten bits set to undefined. The reason to support this (vs always just writing the union as it's backing type), is that no reads to larger fields ever occur at comptime, it would be strictly worse to have spent time writing the full backing type.

6 files changed, 271 insertions(+), 73 deletions(-)

src/Module.zig+1
...@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in...@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
6607 return field_ty.abiAlignment(mod);6607 return field_ty.abiAlignment(mod);
6608}6608}
66096609
6610/// Returns the index of the active field, given the current tag value
6610pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {6611pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
6611 const ip = &mod.intern_pool;6612 const ip = &mod.intern_pool;
6612 if (enum_tag.toIntern() == .none) return null;6613 if (enum_tag.toIntern() == .none) return null;
src/Sema.zig+85-21
...@@ -27260,7 +27260,7 @@ fn unionFieldVal(...@@ -27260,7 +27260,7 @@ fn unionFieldVal(
27260 else27260 else
27261 union_ty.unionFieldType(un.tag.toValue(), mod).?;27261 union_ty.unionFieldType(un.tag.toValue(), mod).?;
2726227262
27263 if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {27263 if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty)) |new_val| {
27264 return Air.internedToRef(new_val.toIntern());27264 return Air.internedToRef(new_val.toIntern());
27265 }27265 }
27266 }27266 }
...@@ -29781,13 +29781,19 @@ fn storePtrVal(...@@ -29781,13 +29781,19 @@ fn storePtrVal(
29781 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already29781 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
29782 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),29782 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
29783 };29783 };
29784 operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {29784 if (reinterpret.write_packed) {
29785 error.OutOfMemory => return error.OutOfMemory,29785 operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
29786 error.ReinterpretDeclRef => unreachable,29786 error.OutOfMemory => return error.OutOfMemory,
29787 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already29787 error.ReinterpretDeclRef => unreachable,
29788 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),29788 };
29789 };29789 } else {
2979029790 operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
29791 error.OutOfMemory => return error.OutOfMemory,
29792 error.ReinterpretDeclRef => unreachable,
29793 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
29794 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
29795 };
29796 }
29791 const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {29797 const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
29792 error.OutOfMemory => return error.OutOfMemory,29798 error.OutOfMemory => return error.OutOfMemory,
29793 error.IllDefinedMemoryLayout => unreachable,29799 error.IllDefinedMemoryLayout => unreachable,
...@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {...@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
29819 reinterpret: struct {29825 reinterpret: struct {
29820 val_ptr: *Value,29826 val_ptr: *Value,
29821 byte_offset: usize,29827 byte_offset: usize,
29828 /// If set, write the operand to packed memory
29829 write_packed: bool = false,
29822 },29830 },
29823 /// If the root decl could not be used as parent, this means `ty` is the type that29831 /// If the root decl could not be used as parent, this means `ty` is the type that
29824 /// caused that by not having a well-defined layout.29832 /// caused that by not having a well-defined layout.
...@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(...@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
30182 );30190 );
30183 },30191 },
30184 .@"union" => {30192 .@"union" => {
30185 // We need to set the active field of the union.
30186 const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);
30187
30188 const payload = &val_ptr.castTag(.@"union").?.data;30193 const payload = &val_ptr.castTag(.@"union").?.data;
30189 payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);30194 const layout = base_child_ty.containerLayout(mod);
3019030195
30191 return beginComptimePtrMutationInner(30196 const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
30192 sema,30197 const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
30193 block,30198 if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
30194 src,30199 // We need to set the active field of the union.
30195 parent.ty.structFieldType(field_index, mod),30200 payload.tag = hypothetical_tag;
30196 &payload.val,30201
30197 ptr_elem_ty,30202 const field_ty = parent.ty.structFieldType(field_index, mod);
30198 parent.mut_decl,30203 return beginComptimePtrMutationInner(
30199 );30204 sema,
30205 block,
30206 src,
30207 field_ty,
30208 &payload.val,
30209 ptr_elem_ty,
30210 parent.mut_decl,
30211 );
30212 } else {
30213 // Writing to a different field (a different or unknown tag is active) requires reinterpreting
30214 // memory of the entire union, which requires knowing its abiSize.
30215 try sema.resolveTypeLayout(parent.ty);
30216
30217 // This union value no longer has a well-defined tag type.
30218 // The reinterpretation will read it back out as .none.
30219 payload.val = try payload.val.unintern(sema.arena, mod);
30220 return ComptimePtrMutationKit{
30221 .mut_decl = parent.mut_decl,
30222 .pointee = .{ .reinterpret = .{
30223 .val_ptr = val_ptr,
30224 .byte_offset = 0,
30225 .write_packed = layout == .Packed,
30226 } },
30227 .ty = parent.ty,
30228 };
30229 }
30200 },30230 },
30201 .slice => switch (field_index) {30231 .slice => switch (field_index) {
30202 Value.slice_ptr_index => return beginComptimePtrMutationInner(30232 Value.slice_ptr_index => return beginComptimePtrMutationInner(
...@@ -30697,6 +30727,7 @@ fn bitCastVal(...@@ -30697,6 +30727,7 @@ fn bitCastVal(
30697 // For types with well-defined memory layouts, we serialize them a byte buffer,30727 // For types with well-defined memory layouts, we serialize them a byte buffer,
30698 // then deserialize to the new type.30728 // then deserialize to the new type.
30699 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));30729 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));
30730
30700 const buffer = try sema.gpa.alloc(u8, abi_size);30731 const buffer = try sema.gpa.alloc(u8, abi_size);
30701 defer sema.gpa.free(buffer);30732 defer sema.gpa.free(buffer);
30702 val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {30733 val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
...@@ -30713,6 +30744,39 @@ fn bitCastVal(...@@ -30713,6 +30744,39 @@ fn bitCastVal(
30713 };30744 };
30714}30745}
3071530746
30747fn bitCastUnionFieldVal(
30748 sema: *Sema,
30749 block: *Block,
30750 src: LazySrcLoc,
30751 val: Value,
30752 old_ty: Type,
30753 field_ty: Type,
30754) !?Value {
30755 const mod = sema.mod;
30756 if (old_ty.eql(field_ty, mod)) return val;
30757
30758 const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));
30759 const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod));
30760
30761 const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size));
30762 defer sema.gpa.free(buffer);
30763 val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
30764 error.OutOfMemory => return error.OutOfMemory,
30765 error.ReinterpretDeclRef => return null,
30766 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
30767 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}),
30768 };
30769
30770 // Reading a larger value means we need to reinterpret from undefined bytes
30771 if (field_size > old_size) @memset(buffer[old_size..], 0xaa);
30772
30773 return Value.readFromMemory(field_ty, mod, buffer[0..], sema.arena) catch |err| switch (err) {
30774 error.OutOfMemory => return error.OutOfMemory,
30775 error.IllDefinedMemoryLayout => unreachable,
30776 error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}),
30777 };
30778}
30779
30716fn coerceArrayPtrToSlice(30780fn coerceArrayPtrToSlice(
30717 sema: *Sema,30781 sema: *Sema,
30718 block: *Block,30782 block: *Block,
src/TypedValue.zig+17-7
...@@ -84,22 +84,27 @@ pub fn print(...@@ -84,22 +84,27 @@ pub fn print(
84 if (level == 0) {84 if (level == 0) {
85 return writer.writeAll(".{ ... }");85 return writer.writeAll(".{ ... }");
86 }86 }
87 const union_val = val.castTag(.@"union").?.data;87 const payload = val.castTag(.@"union").?.data;
88 try writer.writeAll(".{ ");88 try writer.writeAll(".{ ");
8989
90 if (union_val.tag.toIntern() != .none) {90 if (payload.tag) |tag| {
91 try print(.{91 try print(.{
92 .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),92 .ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
93 .val = union_val.tag,93 .val = tag,
94 }, writer, level - 1, mod);94 }, writer, level - 1, mod);
95 try writer.writeAll(" = ");95 try writer.writeAll(" = ");
96 const field_ty = ty.unionFieldType(union_val.tag, mod).?;96 const field_ty = ty.unionFieldType(tag, mod).?;
97 try print(.{97 try print(.{
98 .ty = field_ty,98 .ty = field_ty,
99 .val = union_val.val,99 .val = payload.val,
100 }, writer, level - 1, mod);100 }, writer, level - 1, mod);
101 } else {101 } else {
102 return writer.writeAll("(unknown tag)");102 try writer.writeAll("(unknown tag) = ");
103 const backing_ty = try ty.unionBackingType(mod);
104 try print(.{
105 .ty = backing_ty,
106 .val = payload.val,
107 }, writer, level - 1, mod);
103 }108 }
104109
105 return writer.writeAll(" }");110 return writer.writeAll(" }");
...@@ -421,7 +426,12 @@ pub fn print(...@@ -421,7 +426,12 @@ pub fn print(
421 .val = un.val.toValue(),426 .val = un.val.toValue(),
422 }, writer, level - 1, mod);427 }, writer, level - 1, mod);
423 } else {428 } else {
424 try writer.writeAll("(unknown tag)");429 try writer.writeAll("(unknown tag) = ");
430 const backing_ty = try ty.unionBackingType(mod);
431 try print(.{
432 .ty = backing_ty,
433 .val = un.val.toValue(),
434 }, writer, level - 1, mod);
425 }435 }
426 } else try writer.writeAll("...");436 } else try writer.writeAll("...");
427 return writer.writeAll(" }");437 return writer.writeAll(" }");
src/type.zig+10
...@@ -1954,6 +1954,16 @@ pub const Type = struct {...@@ -1954,6 +1954,16 @@ pub const Type = struct {
1954 return true;1954 return true;
1955 }1955 }
19561956
1957 /// Returns the type used for backing storage of this union during comptime operations.
1958 /// Asserts the type is either an extern or packed union.
1959 pub fn unionBackingType(ty: Type, mod: *Module) !Type {
1960 return switch (ty.containerLayout(mod)) {
1961 .Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
1962 .Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
1963 .Auto => unreachable,
1964 };
1965 }
1966
1957 pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {1967 pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
1958 const ip = &mod.intern_pool;1968 const ip = &mod.intern_pool;
1959 const union_type = ip.indexToKey(ty.toIntern()).union_type;1969 const union_type = ip.indexToKey(ty.toIntern()).union_type;
src/value.zig+25-22
...@@ -327,11 +327,19 @@ pub const Value = struct {...@@ -327,11 +327,19 @@ pub const Value = struct {
327 },327 },
328 .@"union" => {328 .@"union" => {
329 const pl = val.castTag(.@"union").?.data;329 const pl = val.castTag(.@"union").?.data;
330 return mod.intern(.{ .un = .{330 if (pl.tag) |pl_tag| {
331 .ty = ty.toIntern(),331 return mod.intern(.{ .un = .{
332 .tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),332 .ty = ty.toIntern(),
333 .val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),333 .tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
334 } });334 .val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
335 } });
336 } else {
337 return mod.intern(.{ .un = .{
338 .ty = ty.toIntern(),
339 .tag = .none,
340 .val = try pl.val.intern(try ty.unionBackingType(mod), mod),
341 } });
342 }
335 },343 },
336 }344 }
337 }345 }
...@@ -399,10 +407,7 @@ pub const Value = struct {...@@ -399,10 +407,7 @@ pub const Value = struct {
399407
400 .un => |un| Tag.@"union".create(arena, .{408 .un => |un| Tag.@"union".create(arena, .{
401 // toValue asserts that the value cannot be .none which is valid on unions.409 // toValue asserts that the value cannot be .none which is valid on unions.
402 .tag = .{410 .tag = if (un.tag == .none) null else un.tag.toValue(),
403 .ip_index = un.tag,
404 .legacy = undefined,
405 },
406 .val = un.val.toValue(),411 .val = un.val.toValue(),
407 }),412 }),
408413
...@@ -709,21 +714,22 @@ pub const Value = struct {...@@ -709,21 +714,22 @@ pub const Value = struct {
709 .Union => switch (ty.containerLayout(mod)) {714 .Union => switch (ty.containerLayout(mod)) {
710 .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already715 .Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
711 .Extern => {716 .Extern => {
712 const union_obj = mod.typeToUnion(ty).?;
713 if (val.unionTag(mod)) |union_tag| {717 if (val.unionTag(mod)) |union_tag| {
718 const union_obj = mod.typeToUnion(ty).?;
714 const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;719 const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
715 const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();720 const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
716 const field_val = try val.fieldValue(mod, field_index);721 const field_val = try val.fieldValue(mod, field_index);
717 const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));722 const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
718 return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);723 return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
719 } else {724 } else {
720 const union_size = ty.abiSize(mod);725 const backing_ty = try ty.unionBackingType(mod);
721 const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });726 const byte_count: usize = @intCast(backing_ty.abiSize(mod));
722 return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);727 return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
723 }728 }
724 },729 },
725 .Packed => {730 .Packed => {
726 const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;731 const backing_ty = try ty.unionBackingType(mod);
732 const byte_count: usize = @intCast(backing_ty.abiSize(mod));
727 return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);733 return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
728 },734 },
729 },735 },
...@@ -842,9 +848,8 @@ pub const Value = struct {...@@ -842,9 +848,8 @@ pub const Value = struct {
842 const field_val = try val.fieldValue(mod, field_index);848 const field_val = try val.fieldValue(mod, field_index);
843 return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);849 return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
844 } else {850 } else {
845 const union_bits: u16 = @intCast(ty.bitSize(mod));851 const backing_ty = try ty.unionBackingType(mod);
846 const int_ty = try mod.intType(.unsigned, union_bits);852 return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
847 return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
848 }853 }
849 },854 },
850 }855 }
...@@ -1146,10 +1151,8 @@ pub const Value = struct {...@@ -1146,10 +1151,8 @@ pub const Value = struct {
1146 .Union => switch (ty.containerLayout(mod)) {1151 .Union => switch (ty.containerLayout(mod)) {
1147 .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory1152 .Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
1148 .Packed => {1153 .Packed => {
1149 const union_bits: u16 = @intCast(ty.bitSize(mod));1154 const backing_ty = try ty.unionBackingType(mod);
1150 assert(union_bits != 0);1155 const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
1151 const int_ty = try mod.intType(.unsigned, union_bits);
1152 const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
1153 return (try mod.intern(.{ .un = .{1156 return (try mod.intern(.{ .un = .{
1154 .ty = ty.toIntern(),1157 .ty = ty.toIntern(),
1155 .tag = .none,1158 .tag = .none,
...@@ -4017,7 +4020,7 @@ pub const Value = struct {...@@ -4017,7 +4020,7 @@ pub const Value = struct {
4017 data: Data,4020 data: Data,
40184021
4019 pub const Data = struct {4022 pub const Data = struct {
4020 tag: Value,4023 tag: ?Value,
4021 val: Value,4024 val: Value,
4022 };4025 };
4023 };4026 };
test/behavior/comptime_memory.zig+133-23
...@@ -457,18 +457,60 @@ test "type pun null pointer-like optional" {...@@ -457,18 +457,60 @@ test "type pun null pointer-like optional" {
457}457}
458458
459test "reinterpret extern union" {459test "reinterpret extern union" {
460 {460 const U = extern union {
461 const U = extern union {461 foo: u8,
462 a: u32,462 baz: u32 align(8),
463 b: u8 align(8),463 bar: u32,
464 };464 };
465465
466 comptime var u: U = undefined;466 comptime {
467 comptime @memset(std.mem.asBytes(&u), 42);467 {
468 try comptime testing.expect(0x2a2a2a2a == u.a);468 // Undefined initialization
469 try comptime testing.expect(42 == u.b);469 const u = blk: {
470 try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);470 var u: U = undefined;
471 try testing.expectEqual(42, u.b);471 @memset(std.mem.asBytes(&u), 0);
472 u.bar = 0xbbbbbbbb;
473 u.foo = 0x2a;
474 break :blk u;
475 };
476 try testing.expectEqual(@as(u8, 0x2a), u.foo);
477 try testing.expectEqual(@as(u32, 0xbbbbbb2a), u.bar);
478 try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz);
479 }
480
481 {
482 // Union initialization
483 var u: U = .{
484 .foo = 0x2a,
485 };
486 try testing.expectEqual(@as(u8, 0x2a), u.foo);
487 try testing.expectEqual(@as(u32, 0x2a), u.bar & 0xff);
488 try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff);
489
490 // Writing to a larger field
491 u = .{
492 .baz = 0xbbbbbbbb,
493 };
494 try testing.expectEqual(@as(u8, 0xbb), u.foo);
495 try testing.expectEqual(@as(u32, 0xbbbbbbbb), u.bar);
496 try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
497
498 // Writing to the same field
499 u = .{
500 .baz = 0xcccccccc,
501 };
502 try testing.expectEqual(@as(u8, 0xcc), u.foo);
503 try testing.expectEqual(@as(u32, 0xcccccccc), u.bar);
504 try testing.expectEqual(@as(u64, 0xcccccccc), u.baz);
505
506 // Writing to a smaller field
507 u = .{
508 .foo = 0xdd,
509 };
510 try testing.expectEqual(@as(u8, 0xdd), u.foo);
511 try testing.expectEqual(@as(u32, 0xccccccdd), u.bar);
512 try testing.expectEqual(@as(u64, 0xccccccdd), u.baz);
513 }
472 }514 }
473}515}
474516
...@@ -479,12 +521,14 @@ test "reinterpret packed union" {...@@ -479,12 +521,14 @@ test "reinterpret packed union" {
479 b: u8 align(8),521 b: u8 align(8),
480 };522 };
481523
482 comptime var u: U = undefined;524 comptime {
483 comptime @memset(std.mem.asBytes(&u), 42);525 var u: U = undefined;
484 try comptime testing.expect(0x2a2a2a2a == u.a);526 @memset(std.mem.asBytes(&u), 42);
485 try comptime testing.expect(0x2a == u.b);527 try testing.expect(0x2a2a2a2a == u.a);
486 try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);528 try testing.expect(0x2a == u.b);
487 try testing.expectEqual(0x2a, u.b);529 try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);
530 try testing.expectEqual(0x2a, u.b);
531 }
488 }532 }
489533
490 {534 {
...@@ -498,11 +542,77 @@ test "reinterpret packed union" {...@@ -498,11 +542,77 @@ test "reinterpret packed union" {
498 msb: U,542 msb: U,
499 };543 };
500544
501 comptime var s: S = undefined;545 comptime {
502 comptime @memset(std.mem.asBytes(&s), 0xaa);546 var s: S = undefined;
503 try comptime testing.expectEqual(@as(u7, 0x2a), s.lsb.a);547 @memset(std.mem.asBytes(&s), 0x55);
504 try comptime testing.expectEqual(@as(u1, 0), s.lsb.b);548 try testing.expectEqual(@as(u7, 0x55), s.lsb.a);
505 try comptime testing.expectEqual(@as(u7, 0x55), s.msb.a);549 try testing.expectEqual(@as(u1, 1), s.lsb.b);
506 try comptime testing.expectEqual(@as(u1, 1), s.msb.b);550 try testing.expectEqual(@as(u7, 0x2a), s.msb.a);
551 try testing.expectEqual(@as(u1, 0), s.msb.b);
552
553 s.lsb.b = 0;
554 try testing.expectEqual(@as(u7, 0x54), s.lsb.a);
555 try testing.expectEqual(@as(u1, 0), s.lsb.b);
556 s.msb.b = 1;
557 try testing.expectEqual(@as(u7, 0x2b), s.msb.a);
558 try testing.expectEqual(@as(u1, 1), s.msb.b);
559 }
560 }
561
562 {
563 const U = packed union {
564 foo: u8,
565 bar: u29,
566 baz: u64,
567 };
568
569 comptime {
570 {
571 const u = blk: {
572 var u: U = undefined;
573 @memset(std.mem.asBytes(&u), 0);
574 u.baz = 0xbbbbbbbb;
575 u.foo = 0x2a;
576 break :blk u;
577 };
578 try testing.expectEqual(@as(u8, 0x2a), u.foo);
579 try testing.expectEqual(@as(u29, 0x1bbbbb2a), u.bar);
580 try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz);
581 }
582
583 {
584 // Union initialization
585 var u: U = .{
586 .foo = 0x2a,
587 };
588 try testing.expectEqual(@as(u8, 0x2a), u.foo);
589 try testing.expectEqual(@as(u29, 0x2a), u.bar & 0xff);
590 try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff);
591
592 // Writing to a larger field
593 u = .{
594 .baz = 0xbbbbbbbb,
595 };
596 try testing.expectEqual(@as(u8, 0xbb), u.foo);
597 try testing.expectEqual(@as(u29, 0x1bbbbbbb), u.bar);
598 try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
599
600 // Writing to the same field
601 u = .{
602 .baz = 0xcccccccc,
603 };
604 try testing.expectEqual(@as(u8, 0xcc), u.foo);
605 try testing.expectEqual(@as(u29, 0x0ccccccc), u.bar);
606 try testing.expectEqual(@as(u64, 0xcccccccc), u.baz);
607
608 // Writing to a smaller field
609 u = .{
610 .foo = 0xdd,
611 };
612 try testing.expectEqual(@as(u8, 0xdd), u.foo);
613 try testing.expectEqual(@as(u29, 0x0cccccdd), u.bar);
614 try testing.expectEqual(@as(u64, 0xccccccdd), u.baz);
615 }
616 }
507 }617 }
508}618}