Commit fb33bc99e1
Changed files (3)
src
test
behavior
src/Sema.zig
@@ -27251,7 +27251,7 @@ fn unionFieldVal(
return sema.failWithOwnedErrorMsg(block, msg);
}
},
- .Packed, .Extern => {
+ .Packed, .Extern => |layout| {
if (tag_matches) {
return Air.internedToRef(un.val);
} else {
@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;
- if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty)) |new_val| {
+ if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
@@ -30751,26 +30751,50 @@ fn bitCastUnionFieldVal(
val: Value,
old_ty: Type,
field_ty: Type,
+ layout: std.builtin.Type.ContainerLayout,
) !?Value {
const mod = sema.mod;
if (old_ty.eql(field_ty, mod)) return val;
const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));
const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod));
+ const endian = mod.getTarget().cpu.arch.endian();
const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size));
defer sema.gpa.free(buffer);
- val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.ReinterpretDeclRef => return null,
- error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
- error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}),
- };
- // Reading a larger value means we need to reinterpret from undefined bytes
- if (field_size > old_size) @memset(buffer[old_size..], 0xaa);
+ // Reading a larger value means we need to reinterpret from undefined bytes.
+ const offset = switch (layout) {
+ .Extern => offset: {
+ if (field_size > old_size) @memset(buffer[old_size..], 0xaa);
+ val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.ReinterpretDeclRef => return null,
+ error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
+ error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}),
+ };
+ break :offset 0;
+ },
+ .Packed => offset: {
+ if (field_size > old_size) {
+ const min_size = @max(old_size, 1);
+ switch (endian) {
+ .Little => @memset(buffer[min_size - 1 ..], 0xaa),
+ .Big => @memset(buffer[0 .. buffer.len - min_size + 1], 0xaa),
+ }
+ }
+
+ val.writeToPackedMemory(old_ty, mod, buffer, 0) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.ReinterpretDeclRef => return null,
+ };
+
+ break :offset if (endian == .Big) buffer.len - field_size else 0;
+ },
+ .Auto => unreachable,
+ };
- return Value.readFromMemory(field_ty, mod, buffer[0..], sema.arena) catch |err| switch (err) {
+ return Value.readFromMemory(field_ty, mod, buffer[offset..], sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}),
test/behavior/comptime_memory.zig
@@ -455,164 +455,3 @@ test "type pun null pointer-like optional" {
// 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);
- }
- }
- }
-}
test/behavior/union.zig
@@ -1,5 +1,6 @@
const builtin = @import("builtin");
const std = @import("std");
+const endian = builtin.cpu.arch.endian();
const expect = std.testing.expect;
const assert = std.debug.assert;
const expectEqual = std.testing.expectEqual;
@@ -1660,15 +1661,219 @@ test "union with 128 bit integer" {
}
}
-test "memset extern union at comptime" {
+test "memset extern union" {
const U = extern union {
foo: u8,
+ bar: u32,
+ };
+
+ const S = struct {
+ fn doTheTest() !void {
+ var u: U = undefined;
+ @memset(std.mem.asBytes(&u), 0);
+ try expectEqual(@as(u8, 0), u.foo);
+ try expectEqual(@as(u32, 0), u.bar);
+ }
+ };
+
+ try comptime S.doTheTest();
+ try S.doTheTest();
+}
+
+test "memset packed union" {
+ const U = packed union {
+ a: u32,
+ b: u8,
+ };
+
+ const S = struct {
+ fn doTheTest() !void {
+ var u: U = undefined;
+ @memset(std.mem.asBytes(&u), 42);
+ try expectEqual(@as(u32, 0x2a2a2a2a), u.a);
+ try expectEqual(@as(u8, 0x2a), u.b);
+ }
+ };
+
+ try comptime S.doTheTest();
+
+ if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO
+ try S.doTheTest();
+}
+
+fn littleToNativeEndian(comptime T: type, v: T) T {
+ return if (endian == .Little) v else @byteSwap(v);
+}
+
+test "reinterpret extern union" {
+ const U = extern union {
+ foo: u8,
+ baz: u32 align(8),
+ bar: u32,
+ };
+
+ const S = struct {
+ fn doTheTest() !void {
+ {
+ // 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 expectEqual(@as(u8, 0x2a), u.foo);
+ try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.bar);
+ try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.baz);
+ }
+
+ {
+ // Union initialization
+ var u: U = .{
+ .foo = 0x2a,
+ };
+
+ {
+ const expected, const mask = switch (endian) {
+ .Little => .{ 0x2a, 0xff },
+ .Big => .{ 0x2a000000, 0xff000000 },
+ };
+
+ try expectEqual(@as(u8, 0x2a), u.foo);
+ try expectEqual(@as(u32, expected), u.bar & mask);
+ try expectEqual(@as(u32, expected), u.baz & mask);
+ }
+
+ // Writing to a larger field
+ u.baz = 0xbbbbbbbb;
+ try expectEqual(@as(u8, 0xbb), u.foo);
+ try expectEqual(@as(u32, 0xbbbbbbbb), u.bar);
+ try expectEqual(@as(u32, 0xbbbbbbbb), u.baz);
+
+ // Writing to the same field
+ u.baz = 0xcccccccc;
+ try expectEqual(@as(u8, 0xcc), u.foo);
+ try expectEqual(@as(u32, 0xcccccccc), u.bar);
+ try expectEqual(@as(u32, 0xcccccccc), u.baz);
+
+ // Writing to a smaller field
+ u.foo = 0xdd;
+ try expectEqual(@as(u8, 0xdd), u.foo);
+ try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.bar);
+ try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.baz);
+ }
+ }
+ };
+
+ try comptime S.doTheTest();
+
+ if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+ try S.doTheTest();
+}
+
+test "reinterpret packed union" {
+ const U = packed union {
+ foo: u8,
+ bar: u29,
+ baz: u64,
+ qux: u12,
+ };
+
+ const S = struct {
+ fn doTheTest() !void {
+ {
+ const u = blk: {
+ var u: U = undefined;
+ @memset(std.mem.asBytes(&u), 0);
+ u.baz = 0xbbbbbbbb;
+ u.qux = 0xe2a;
+ break :blk u;
+ };
+
+ try expectEqual(@as(u8, 0x2a), u.foo);
+ try expectEqual(@as(u12, 0xe2a), u.qux);
+
+ // https://github.com/ziglang/zig/issues/17360
+ if (@inComptime()) {
+ try expectEqual(@as(u29, 0x1bbbbe2a), u.bar);
+ try expectEqual(@as(u64, 0xbbbbbe2a), u.baz);
+ }
+ }
+
+ {
+ // Union initialization
+ var u: U = .{
+ .qux = 0xe2a,
+ };
+ try expectEqual(@as(u8, 0x2a), u.foo);
+ try expectEqual(@as(u12, 0xe2a), u.qux);
+ try expectEqual(@as(u29, 0xe2a), u.bar & 0xfff);
+ try expectEqual(@as(u64, 0xe2a), u.baz & 0xfff);
+
+ // Writing to a larger field
+ u.baz = 0xbbbbbbbb;
+ try expectEqual(@as(u8, 0xbb), u.foo);
+ try expectEqual(@as(u12, 0xbbb), u.qux);
+ try expectEqual(@as(u29, 0x1bbbbbbb), u.bar);
+ try expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
+
+ // Writing to the same field
+ u.baz = 0xcccccccc;
+ try expectEqual(@as(u8, 0xcc), u.foo);
+ try expectEqual(@as(u12, 0xccc), u.qux);
+ try expectEqual(@as(u29, 0x0ccccccc), u.bar);
+ try expectEqual(@as(u64, 0xcccccccc), u.baz);
+
+ // Writing to a smaller field
+ u.foo = 0xdd;
+ try expectEqual(@as(u8, 0xdd), u.foo);
+ try expectEqual(@as(u12, 0xcdd), u.qux);
+ try expectEqual(@as(u29, 0x0cccccdd), u.bar);
+ try expectEqual(@as(u64, 0xccccccdd), u.baz);
+ }
+ }
};
- const u = comptime blk: {
- var u: U = undefined;
- @memset(std.mem.asBytes(&u), 0);
- u.foo = 0;
- break :blk u;
+
+ try comptime S.doTheTest();
+
+ if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+ if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO
+ try S.doTheTest();
+}
+
+test "reinterpret packed union inside packed struct" {
+ const U = packed union {
+ a: u7,
+ b: u1,
+ };
+
+ const V = packed struct {
+ lo: U,
+ hi: U,
};
- try expect(u.foo == 0);
+
+ const S = struct {
+ fn doTheTest() !void {
+ var v: V = undefined;
+ @memset(std.mem.asBytes(&v), 0x55);
+ try expectEqual(@as(u7, 0x55), v.lo.a);
+ try expectEqual(@as(u1, 1), v.lo.b);
+ try expectEqual(@as(u7, 0x2a), v.hi.a);
+ try expectEqual(@as(u1, 0), v.hi.b);
+
+ v.lo.b = 0;
+ try expectEqual(@as(u7, 0x54), v.lo.a);
+ try expectEqual(@as(u1, 0), v.lo.b);
+ v.hi.b = 1;
+ try expectEqual(@as(u7, 0x2b), v.hi.a);
+ try expectEqual(@as(u1, 1), v.hi.b);
+ }
+ };
+
+ try comptime S.doTheTest();
+
+ if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+ try S.doTheTest();
}