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.
619 lines
20 KiB
Zig
619 lines
20 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const endian = builtin.cpu.arch.endian();
|
|
const testing = @import("std").testing;
|
|
const ptr_size = @sizeOf(usize);
|
|
|
|
test "type pun signed and unsigned as single pointer" {
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as(*i32, @ptrCast(&x));
|
|
y.* = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as many pointer" {
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as([*]i32, @ptrCast(&x));
|
|
y[0] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as array pointer" {
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as(*[1]i32, @ptrCast(&x));
|
|
y[0] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as offset many pointer" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var x: u32 = 0;
|
|
var y = @as([*]i32, @ptrCast(&x));
|
|
y -= 10;
|
|
y[10] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun signed and unsigned as array pointer with pointer arithemtic" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var x: u32 = 0;
|
|
const y = @as([*]i32, @ptrCast(&x)) - 10;
|
|
const z: *[15]i32 = y[0..15];
|
|
z[10] = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), x);
|
|
}
|
|
}
|
|
|
|
test "type pun value and struct" {
|
|
comptime {
|
|
const StructOfU32 = extern struct { x: u32 };
|
|
var inst: StructOfU32 = .{ .x = 0 };
|
|
@as(*i32, @ptrCast(&inst.x)).* = -1;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFF), inst.x);
|
|
@as(*i32, @ptrCast(&inst)).* = -2;
|
|
try testing.expectEqual(@as(u32, 0xFFFFFFFE), inst.x);
|
|
}
|
|
}
|
|
|
|
fn bigToNativeEndian(comptime T: type, v: T) T {
|
|
return if (endian == .Big) v else @byteSwap(v);
|
|
}
|
|
test "type pun endianness" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
comptime {
|
|
const StructOfBytes = extern struct { x: [4]u8 };
|
|
var inst: StructOfBytes = .{ .x = [4]u8{ 0, 0, 0, 0 } };
|
|
const structPtr = @as(*align(1) u32, @ptrCast(&inst));
|
|
const arrayPtr = @as(*align(1) u32, @ptrCast(&inst.x));
|
|
inst.x[0] = 0xFE;
|
|
inst.x[2] = 0xBE;
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), structPtr.*);
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xFE00BE00), arrayPtr.*);
|
|
structPtr.* = bigToNativeEndian(u32, 0xDEADF00D);
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), structPtr.*);
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xDEADF00D), arrayPtr.*);
|
|
try testing.expectEqual(@as(u8, 0xDE), inst.x[0]);
|
|
try testing.expectEqual(@as(u8, 0xAD), inst.x[1]);
|
|
try testing.expectEqual(@as(u8, 0xF0), inst.x[2]);
|
|
try testing.expectEqual(@as(u8, 0x0D), inst.x[3]);
|
|
}
|
|
}
|
|
|
|
const Bits = packed struct {
|
|
// Note: This struct has only single byte words so it
|
|
// doesn't need to be byte swapped.
|
|
p0: u1,
|
|
p1: u4,
|
|
p2: u3,
|
|
p3: u2,
|
|
p4: u6,
|
|
p5: u8,
|
|
p6: u7,
|
|
p7: u1,
|
|
};
|
|
const ShuffledBits = packed struct {
|
|
p1: u4,
|
|
p3: u2,
|
|
p7: u1,
|
|
p0: u1,
|
|
p5: u8,
|
|
p2: u3,
|
|
p6: u7,
|
|
p4: u6,
|
|
};
|
|
fn shuffle(ptr: usize, comptime From: type, comptime To: type) usize {
|
|
if (@sizeOf(From) != @sizeOf(To))
|
|
@compileError("Mismatched sizes! " ++ @typeName(From) ++ " and " ++ @typeName(To) ++ " must have the same size!");
|
|
const array_len = @divExact(ptr_size, @sizeOf(From));
|
|
var result: usize = 0;
|
|
const pSource = @as(*align(1) const [array_len]From, @ptrCast(&ptr));
|
|
const pResult = @as(*align(1) [array_len]To, @ptrCast(&result));
|
|
var i: usize = 0;
|
|
while (i < array_len) : (i += 1) {
|
|
inline for (@typeInfo(To).Struct.fields) |f| {
|
|
@field(pResult[i], f.name) = @field(pSource[i], f.name);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn doTypePunBitsTest(as_bits: *Bits) !void {
|
|
const as_u32 = @as(*align(1) u32, @ptrCast(as_bits));
|
|
const as_bytes = @as(*[4]u8, @ptrCast(as_bits));
|
|
as_u32.* = bigToNativeEndian(u32, 0xB0A7DEED);
|
|
try testing.expectEqual(@as(u1, 0x00), as_bits.p0);
|
|
try testing.expectEqual(@as(u4, 0x08), as_bits.p1);
|
|
try testing.expectEqual(@as(u3, 0x05), as_bits.p2);
|
|
try testing.expectEqual(@as(u2, 0x03), as_bits.p3);
|
|
try testing.expectEqual(@as(u6, 0x29), as_bits.p4);
|
|
try testing.expectEqual(@as(u8, 0xDE), as_bits.p5);
|
|
try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
|
|
try testing.expectEqual(@as(u1, 0x01), as_bits.p7);
|
|
|
|
as_bits.p6 = 0x2D;
|
|
as_bits.p1 = 0x0F;
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAD), as_u32.*);
|
|
|
|
// clobbering one bit doesn't clobber the word
|
|
as_bits.p7 = undefined;
|
|
try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
|
|
// even when read as a whole
|
|
const u = as_u32.*;
|
|
_ = u; // u is undefined
|
|
try testing.expectEqual(@as(u7, 0x2D), as_bits.p6);
|
|
// or if a field which shares the byte is modified
|
|
as_bits.p6 = 0x6D;
|
|
try testing.expectEqual(@as(u7, 0x6D), as_bits.p6);
|
|
|
|
// but overwriting the undefined will clear it
|
|
as_bytes[3] = 0xAF;
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0xBEA7DEAF), as_u32.*);
|
|
}
|
|
|
|
test "type pun bits" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var v: u32 = undefined;
|
|
try doTypePunBitsTest(@as(*Bits, @ptrCast(&v)));
|
|
}
|
|
}
|
|
|
|
const imports = struct {
|
|
var global_u32: u32 = 0;
|
|
};
|
|
|
|
// Make sure lazy values work on their own, before getting into more complex tests
|
|
test "basic pointer preservation" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const lazy_address = @intFromPtr(&imports.global_u32);
|
|
try testing.expectEqual(@intFromPtr(&imports.global_u32), lazy_address);
|
|
try testing.expectEqual(&imports.global_u32, @as(*u32, @ptrFromInt(lazy_address)));
|
|
}
|
|
}
|
|
|
|
test "byte copy preserves linker value" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const ct_value = comptime blk: {
|
|
const lazy = &imports.global_u32;
|
|
var result: *u32 = undefined;
|
|
const pSource = @as(*const [ptr_size]u8, @ptrCast(&lazy));
|
|
const pResult = @as(*[ptr_size]u8, @ptrCast(&result));
|
|
var i: usize = 0;
|
|
while (i < ptr_size) : (i += 1) {
|
|
pResult[i] = pSource[i];
|
|
try testing.expectEqual(pSource[i], pResult[i]);
|
|
}
|
|
try testing.expectEqual(&imports.global_u32, result);
|
|
break :blk result;
|
|
};
|
|
|
|
try testing.expectEqual(&imports.global_u32, ct_value);
|
|
}
|
|
|
|
test "unordered byte copy preserves linker value" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const ct_value = comptime blk: {
|
|
const lazy = &imports.global_u32;
|
|
var result: *u32 = undefined;
|
|
const pSource = @as(*const [ptr_size]u8, @ptrCast(&lazy));
|
|
const pResult = @as(*[ptr_size]u8, @ptrCast(&result));
|
|
if (ptr_size > 8) @compileError("This array needs to be expanded for platform with very big pointers");
|
|
const shuffled_indices = [_]usize{ 4, 5, 2, 6, 1, 3, 0, 7 };
|
|
for (shuffled_indices) |i| {
|
|
pResult[i] = pSource[i];
|
|
try testing.expectEqual(pSource[i], pResult[i]);
|
|
}
|
|
try testing.expectEqual(&imports.global_u32, result);
|
|
break :blk result;
|
|
};
|
|
|
|
try testing.expectEqual(&imports.global_u32, ct_value);
|
|
}
|
|
|
|
test "shuffle chunks of linker value" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
const lazy_address = @intFromPtr(&imports.global_u32);
|
|
const shuffled1_rt = shuffle(lazy_address, Bits, ShuffledBits);
|
|
const unshuffled1_rt = shuffle(shuffled1_rt, ShuffledBits, Bits);
|
|
try testing.expectEqual(lazy_address, unshuffled1_rt);
|
|
const shuffled1_ct = comptime shuffle(lazy_address, Bits, ShuffledBits);
|
|
const shuffled1_ct_2 = comptime shuffle(lazy_address, Bits, ShuffledBits);
|
|
try comptime testing.expectEqual(shuffled1_ct, shuffled1_ct_2);
|
|
const unshuffled1_ct = comptime shuffle(shuffled1_ct, ShuffledBits, Bits);
|
|
try comptime testing.expectEqual(lazy_address, unshuffled1_ct);
|
|
try testing.expectEqual(shuffled1_ct, shuffled1_rt);
|
|
}
|
|
|
|
test "dance on linker values" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
var arr: [2]usize = undefined;
|
|
arr[0] = @intFromPtr(&imports.global_u32);
|
|
arr[1] = @intFromPtr(&imports.global_u32);
|
|
|
|
const weird_ptr = @as([*]Bits, @ptrCast(@as([*]u8, @ptrCast(&arr)) + @sizeOf(usize) - 3));
|
|
try doTypePunBitsTest(&weird_ptr[0]);
|
|
if (ptr_size > @sizeOf(Bits))
|
|
try doTypePunBitsTest(&weird_ptr[1]);
|
|
|
|
var arr_bytes = @as(*[2][ptr_size]u8, @ptrCast(&arr));
|
|
|
|
var rebuilt_bytes: [ptr_size]u8 = undefined;
|
|
var i: usize = 0;
|
|
while (i < ptr_size - 3) : (i += 1) {
|
|
rebuilt_bytes[i] = arr_bytes[0][i];
|
|
}
|
|
while (i < ptr_size) : (i += 1) {
|
|
rebuilt_bytes[i] = arr_bytes[1][i];
|
|
}
|
|
|
|
try testing.expectEqual(&imports.global_u32, @as(*u32, @ptrFromInt(@as(usize, @bitCast(rebuilt_bytes)))));
|
|
}
|
|
}
|
|
|
|
test "offset array ptr by element size" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const VirtualStruct = struct { x: u32 };
|
|
var arr: [4]VirtualStruct = .{
|
|
.{ .x = bigToNativeEndian(u32, 0x0004080c) },
|
|
.{ .x = bigToNativeEndian(u32, 0x0105090d) },
|
|
.{ .x = bigToNativeEndian(u32, 0x02060a0e) },
|
|
.{ .x = bigToNativeEndian(u32, 0x03070b0f) },
|
|
};
|
|
|
|
const address = @intFromPtr(&arr);
|
|
try testing.expectEqual(@intFromPtr(&arr[0]), address);
|
|
try testing.expectEqual(@intFromPtr(&arr[0]) + 10, address + 10);
|
|
try testing.expectEqual(@intFromPtr(&arr[1]), address + @sizeOf(VirtualStruct));
|
|
try testing.expectEqual(@intFromPtr(&arr[2]), address + 2 * @sizeOf(VirtualStruct));
|
|
try testing.expectEqual(@intFromPtr(&arr[3]), address + @sizeOf(VirtualStruct) * 3);
|
|
|
|
const secondElement = @as(*VirtualStruct, @ptrFromInt(@intFromPtr(&arr[0]) + 2 * @sizeOf(VirtualStruct)));
|
|
try testing.expectEqual(bigToNativeEndian(u32, 0x02060a0e), secondElement.x);
|
|
}
|
|
}
|
|
|
|
test "offset instance by field size" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const VirtualStruct = struct { x: u32, y: u32, z: u32, w: u32 };
|
|
var inst = VirtualStruct{ .x = 0, .y = 1, .z = 2, .w = 3 };
|
|
|
|
var ptr = @intFromPtr(&inst);
|
|
ptr -= 4;
|
|
ptr += @offsetOf(VirtualStruct, "x");
|
|
try testing.expectEqual(@as(u32, 0), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr -= @offsetOf(VirtualStruct, "x");
|
|
ptr += @offsetOf(VirtualStruct, "y");
|
|
try testing.expectEqual(@as(u32, 1), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr = ptr - @offsetOf(VirtualStruct, "y") + @offsetOf(VirtualStruct, "z");
|
|
try testing.expectEqual(@as(u32, 2), @as([*]u32, @ptrFromInt(ptr))[1]);
|
|
ptr = @intFromPtr(&inst.z) - 4 - @offsetOf(VirtualStruct, "z");
|
|
ptr += @offsetOf(VirtualStruct, "w");
|
|
try testing.expectEqual(@as(u32, 3), @as(*u32, @ptrFromInt(ptr + 4)).*);
|
|
}
|
|
}
|
|
|
|
test "offset field ptr by enclosing array element size" {
|
|
if (true) {
|
|
// TODO https://github.com/ziglang/zig/issues/9646
|
|
return error.SkipZigTest;
|
|
}
|
|
|
|
comptime {
|
|
const VirtualStruct = struct { x: u32 };
|
|
var arr: [4]VirtualStruct = .{
|
|
.{ .x = bigToNativeEndian(u32, 0x0004080c) },
|
|
.{ .x = bigToNativeEndian(u32, 0x0105090d) },
|
|
.{ .x = bigToNativeEndian(u32, 0x02060a0e) },
|
|
.{ .x = bigToNativeEndian(u32, 0x03070b0f) },
|
|
};
|
|
|
|
var i: usize = 0;
|
|
while (i < 4) : (i += 1) {
|
|
var ptr: [*]u8 = @as([*]u8, @ptrCast(&arr[0]));
|
|
ptr += i;
|
|
ptr += @offsetOf(VirtualStruct, "x");
|
|
var j: usize = 0;
|
|
while (j < 4) : (j += 1) {
|
|
const base = ptr + j * @sizeOf(VirtualStruct);
|
|
try testing.expectEqual(@as(u8, @intCast(i * 4 + j)), base[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
test "accessing reinterpreted memory of parent object" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
const S = extern struct {
|
|
a: f32,
|
|
b: [4]u8,
|
|
c: f32,
|
|
};
|
|
const expected = if (endian == .Little) 102 else 38;
|
|
|
|
comptime {
|
|
const x = S{
|
|
.a = 1.5,
|
|
.b = [_]u8{ 1, 2, 3, 4 },
|
|
.c = 2.6,
|
|
};
|
|
const ptr = &x.b[0];
|
|
const b = @as([*c]const u8, @ptrCast(ptr))[5];
|
|
try testing.expect(b == expected);
|
|
}
|
|
}
|
|
|
|
test "bitcast packed union to integer" {
|
|
const U = packed union {
|
|
x: u1,
|
|
y: u2,
|
|
};
|
|
|
|
comptime {
|
|
const a = U{ .x = 1 };
|
|
const b = U{ .y = 2 };
|
|
const cast_a = @as(u2, @bitCast(a));
|
|
const cast_b = @as(u2, @bitCast(b));
|
|
|
|
// truncated because the upper bit is garbage memory that we don't care about
|
|
try testing.expectEqual(@as(u1, 1), @as(u1, @truncate(cast_a)));
|
|
try testing.expectEqual(@as(u2, 2), cast_b);
|
|
}
|
|
}
|
|
|
|
test "mutate entire slice at comptime" {
|
|
comptime {
|
|
var buf: [3]u8 = undefined;
|
|
const x: [2]u8 = .{ 1, 2 }; // Avoid RLS
|
|
buf[1..3].* = x;
|
|
}
|
|
}
|
|
|
|
test "dereference undefined pointer to zero-bit type" {
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const p0: *void = undefined;
|
|
try testing.expectEqual({}, p0.*);
|
|
|
|
const p1: *[0]u32 = undefined;
|
|
try testing.expect(p1.*.len == 0);
|
|
}
|
|
|
|
test "type pun extern struct" {
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const S = extern struct { f: u8 };
|
|
comptime var s = S{ .f = 123 };
|
|
@as(*u8, @ptrCast(&s)).* = 72;
|
|
try testing.expectEqual(@as(u8, 72), s.f);
|
|
}
|
|
|
|
test "type pun @ptrFromInt" {
|
|
const p: *u8 = @ptrFromInt(42);
|
|
// note that expectEqual hides the bug
|
|
try testing.expect(@as(*const [*]u8, @ptrCast(&p)).* == @as([*]u8, @ptrFromInt(42)));
|
|
}
|
|
|
|
test "type pun null pointer-like optional" {
|
|
const p: ?*u8 = null;
|
|
// note that expectEqual hides the bug
|
|
try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null);
|
|
}
|
|
|
|
test "reinterpret extern union" {
|
|
const U = extern union {
|
|
foo: u8,
|
|
baz: u32 align(8),
|
|
bar: u32,
|
|
};
|
|
|
|
comptime {
|
|
{
|
|
// Undefined initialization
|
|
const u = blk: {
|
|
var u: U = undefined;
|
|
@memset(std.mem.asBytes(&u), 0);
|
|
u.bar = 0xbbbbbbbb;
|
|
u.foo = 0x2a;
|
|
break :blk u;
|
|
};
|
|
try testing.expectEqual(@as(u8, 0x2a), u.foo);
|
|
try testing.expectEqual(@as(u32, 0xbbbbbb2a), u.bar);
|
|
try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz);
|
|
}
|
|
|
|
{
|
|
// Union initialization
|
|
var u: U = .{
|
|
.foo = 0x2a,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0x2a), u.foo);
|
|
try testing.expectEqual(@as(u32, 0x2a), u.bar & 0xff);
|
|
try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff);
|
|
|
|
// Writing to a larger field
|
|
u = .{
|
|
.baz = 0xbbbbbbbb,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0xbb), u.foo);
|
|
try testing.expectEqual(@as(u32, 0xbbbbbbbb), u.bar);
|
|
try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
|
|
|
|
// Writing to the same field
|
|
u = .{
|
|
.baz = 0xcccccccc,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0xcc), u.foo);
|
|
try testing.expectEqual(@as(u32, 0xcccccccc), u.bar);
|
|
try testing.expectEqual(@as(u64, 0xcccccccc), u.baz);
|
|
|
|
// Writing to a smaller field
|
|
u = .{
|
|
.foo = 0xdd,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0xdd), u.foo);
|
|
try testing.expectEqual(@as(u32, 0xccccccdd), u.bar);
|
|
try testing.expectEqual(@as(u64, 0xccccccdd), u.baz);
|
|
}
|
|
}
|
|
}
|
|
|
|
test "reinterpret packed union" {
|
|
{
|
|
const U = packed union {
|
|
a: u32,
|
|
b: u8 align(8),
|
|
};
|
|
|
|
comptime {
|
|
var u: U = undefined;
|
|
@memset(std.mem.asBytes(&u), 42);
|
|
try testing.expect(0x2a2a2a2a == u.a);
|
|
try testing.expect(0x2a == u.b);
|
|
try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);
|
|
try testing.expectEqual(0x2a, u.b);
|
|
}
|
|
}
|
|
|
|
{
|
|
const U = packed union {
|
|
a: u7,
|
|
b: u1,
|
|
};
|
|
|
|
const S = packed struct {
|
|
lsb: U,
|
|
msb: U,
|
|
};
|
|
|
|
comptime {
|
|
var s: S = undefined;
|
|
@memset(std.mem.asBytes(&s), 0x55);
|
|
try testing.expectEqual(@as(u7, 0x55), s.lsb.a);
|
|
try testing.expectEqual(@as(u1, 1), s.lsb.b);
|
|
try testing.expectEqual(@as(u7, 0x2a), s.msb.a);
|
|
try testing.expectEqual(@as(u1, 0), s.msb.b);
|
|
|
|
s.lsb.b = 0;
|
|
try testing.expectEqual(@as(u7, 0x54), s.lsb.a);
|
|
try testing.expectEqual(@as(u1, 0), s.lsb.b);
|
|
s.msb.b = 1;
|
|
try testing.expectEqual(@as(u7, 0x2b), s.msb.a);
|
|
try testing.expectEqual(@as(u1, 1), s.msb.b);
|
|
}
|
|
}
|
|
|
|
{
|
|
const U = packed union {
|
|
foo: u8,
|
|
bar: u29,
|
|
baz: u64,
|
|
};
|
|
|
|
comptime {
|
|
{
|
|
const u = blk: {
|
|
var u: U = undefined;
|
|
@memset(std.mem.asBytes(&u), 0);
|
|
u.baz = 0xbbbbbbbb;
|
|
u.foo = 0x2a;
|
|
break :blk u;
|
|
};
|
|
try testing.expectEqual(@as(u8, 0x2a), u.foo);
|
|
try testing.expectEqual(@as(u29, 0x1bbbbb2a), u.bar);
|
|
try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz);
|
|
}
|
|
|
|
{
|
|
// Union initialization
|
|
var u: U = .{
|
|
.foo = 0x2a,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0x2a), u.foo);
|
|
try testing.expectEqual(@as(u29, 0x2a), u.bar & 0xff);
|
|
try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff);
|
|
|
|
// Writing to a larger field
|
|
u = .{
|
|
.baz = 0xbbbbbbbb,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0xbb), u.foo);
|
|
try testing.expectEqual(@as(u29, 0x1bbbbbbb), u.bar);
|
|
try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
|
|
|
|
// Writing to the same field
|
|
u = .{
|
|
.baz = 0xcccccccc,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0xcc), u.foo);
|
|
try testing.expectEqual(@as(u29, 0x0ccccccc), u.bar);
|
|
try testing.expectEqual(@as(u64, 0xcccccccc), u.baz);
|
|
|
|
// Writing to a smaller field
|
|
u = .{
|
|
.foo = 0xdd,
|
|
};
|
|
try testing.expectEqual(@as(u8, 0xdd), u.foo);
|
|
try testing.expectEqual(@as(u29, 0x0cccccdd), u.bar);
|
|
try testing.expectEqual(@as(u64, 0xccccccdd), u.baz);
|
|
}
|
|
}
|
|
}
|
|
}
|