Commit fb33bc99e1

kcbanner <kcbanner@gmail.com>
2023-10-02 08:06:37
sema: handle big-endian when bitcasting between different-sized union fields
Updated the tests to also run at runtime, and moved them to union.zig
1 parent d657b6c
Changed files (3)
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();
 }