authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-10-02 02:06:37-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-10-02 13:28:13-04:00
logfb33bc99e17427b4dc4143b8c2a5745a9d978db2
treee5cbaf8b7af8ddac15eda028892ab02392b76359
parentd657b6c0e2ab7c47f5416dc4df1abb2bfbecd4b6

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

3 files changed, 247 insertions(+), 179 deletions(-)

src/Sema.zig+35-11
...@@ -27251,7 +27251,7 @@ fn unionFieldVal(...@@ -27251,7 +27251,7 @@ fn unionFieldVal(
27251 return sema.failWithOwnedErrorMsg(block, msg);27251 return sema.failWithOwnedErrorMsg(block, msg);
27252 }27252 }
27253 },27253 },
27254 .Packed, .Extern => {27254 .Packed, .Extern => |layout| {
27255 if (tag_matches) {27255 if (tag_matches) {
27256 return Air.internedToRef(un.val);27256 return Air.internedToRef(un.val);
27257 } else {27257 } else {
...@@ -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.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty)) |new_val| {27263 if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
27264 return Air.internedToRef(new_val.toIntern());27264 return Air.internedToRef(new_val.toIntern());
27265 }27265 }
27266 }27266 }
...@@ -30751,26 +30751,50 @@ fn bitCastUnionFieldVal(...@@ -30751,26 +30751,50 @@ fn bitCastUnionFieldVal(
30751 val: Value,30751 val: Value,
30752 old_ty: Type,30752 old_ty: Type,
30753 field_ty: Type,30753 field_ty: Type,
30754 layout: std.builtin.Type.ContainerLayout,
30754) !?Value {30755) !?Value {
30755 const mod = sema.mod;30756 const mod = sema.mod;
30756 if (old_ty.eql(field_ty, mod)) return val;30757 if (old_ty.eql(field_ty, mod)) return val;
3075730758
30758 const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));30759 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 const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod));
30761 const endian = mod.getTarget().cpu.arch.endian();
3076030762
30761 const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size));30763 const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size));
30762 defer sema.gpa.free(buffer);30764 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 };
3076930765
30770 // Reading a larger value means we need to reinterpret from undefined bytes30766 // Reading a larger value means we need to reinterpret from undefined bytes.
30771 if (field_size > old_size) @memset(buffer[old_size..], 0xaa);30767 const offset = switch (layout) {
30768 .Extern => offset: {
30769 if (field_size > old_size) @memset(buffer[old_size..], 0xaa);
30770 val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
30771 error.OutOfMemory => return error.OutOfMemory,
30772 error.ReinterpretDeclRef => return null,
30773 error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
30774 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}),
30775 };
30776 break :offset 0;
30777 },
30778 .Packed => offset: {
30779 if (field_size > old_size) {
30780 const min_size = @max(old_size, 1);
30781 switch (endian) {
30782 .Little => @memset(buffer[min_size - 1 ..], 0xaa),
30783 .Big => @memset(buffer[0 .. buffer.len - min_size + 1], 0xaa),
30784 }
30785 }
30786
30787 val.writeToPackedMemory(old_ty, mod, buffer, 0) catch |err| switch (err) {
30788 error.OutOfMemory => return error.OutOfMemory,
30789 error.ReinterpretDeclRef => return null,
30790 };
30791
30792 break :offset if (endian == .Big) buffer.len - field_size else 0;
30793 },
30794 .Auto => unreachable,
30795 };
3077230796
30773 return Value.readFromMemory(field_ty, mod, buffer[0..], sema.arena) catch |err| switch (err) {30797 return Value.readFromMemory(field_ty, mod, buffer[offset..], sema.arena) catch |err| switch (err) {
30774 error.OutOfMemory => return error.OutOfMemory,30798 error.OutOfMemory => return error.OutOfMemory,
30775 error.IllDefinedMemoryLayout => unreachable,30799 error.IllDefinedMemoryLayout => unreachable,
30776 error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}),30800 error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}),
test/behavior/comptime_memory.zig-161
...@@ -455,164 +455,3 @@ test "type pun null pointer-like optional" {...@@ -455,164 +455,3 @@ test "type pun null pointer-like optional" {
455 // note that expectEqual hides the bug455 // note that expectEqual hides the bug
456 try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null);456 try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null);
457}457}
458
459test "reinterpret extern union" {
460 const U = extern union {
461 foo: u8,
462 baz: u32 align(8),
463 bar: u32,
464 };
465
466 comptime {
467 {
468 // Undefined initialization
469 const u = blk: {
470 var u: U = undefined;
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 }
514 }
515}
516
517test "reinterpret packed union" {
518 {
519 const U = packed union {
520 a: u32,
521 b: u8 align(8),
522 };
523
524 comptime {
525 var u: U = undefined;
526 @memset(std.mem.asBytes(&u), 42);
527 try testing.expect(0x2a2a2a2a == u.a);
528 try testing.expect(0x2a == u.b);
529 try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a);
530 try testing.expectEqual(0x2a, u.b);
531 }
532 }
533
534 {
535 const U = packed union {
536 a: u7,
537 b: u1,
538 };
539
540 const S = packed struct {
541 lsb: U,
542 msb: U,
543 };
544
545 comptime {
546 var s: S = undefined;
547 @memset(std.mem.asBytes(&s), 0x55);
548 try testing.expectEqual(@as(u7, 0x55), s.lsb.a);
549 try testing.expectEqual(@as(u1, 1), s.lsb.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 }
617 }
618}
test/behavior/union.zig+212-7
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std");2const std = @import("std");
3const endian = builtin.cpu.arch.endian();
3const expect = std.testing.expect;4const expect = std.testing.expect;
4const assert = std.debug.assert;5const assert = std.debug.assert;
5const expectEqual = std.testing.expectEqual;6const expectEqual = std.testing.expectEqual;
...@@ -1660,15 +1661,219 @@ test "union with 128 bit integer" {...@@ -1660,15 +1661,219 @@ test "union with 128 bit integer" {
1660 }1661 }
1661}1662}
16621663
1663test "memset extern union at comptime" {1664test "memset extern union" {
1664 const U = extern union {1665 const U = extern union {
1665 foo: u8,1666 foo: u8,
1667 bar: u32,
1668 };
1669
1670 const S = struct {
1671 fn doTheTest() !void {
1672 var u: U = undefined;
1673 @memset(std.mem.asBytes(&u), 0);
1674 try expectEqual(@as(u8, 0), u.foo);
1675 try expectEqual(@as(u32, 0), u.bar);
1676 }
1677 };
1678
1679 try comptime S.doTheTest();
1680 try S.doTheTest();
1681}
1682
1683test "memset packed union" {
1684 const U = packed union {
1685 a: u32,
1686 b: u8,
1687 };
1688
1689 const S = struct {
1690 fn doTheTest() !void {
1691 var u: U = undefined;
1692 @memset(std.mem.asBytes(&u), 42);
1693 try expectEqual(@as(u32, 0x2a2a2a2a), u.a);
1694 try expectEqual(@as(u8, 0x2a), u.b);
1695 }
1696 };
1697
1698 try comptime S.doTheTest();
1699
1700 if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO
1701 try S.doTheTest();
1702}
1703
1704fn littleToNativeEndian(comptime T: type, v: T) T {
1705 return if (endian == .Little) v else @byteSwap(v);
1706}
1707
1708test "reinterpret extern union" {
1709 const U = extern union {
1710 foo: u8,
1711 baz: u32 align(8),
1712 bar: u32,
1713 };
1714
1715 const S = struct {
1716 fn doTheTest() !void {
1717 {
1718 // Undefined initialization
1719 const u = blk: {
1720 var u: U = undefined;
1721 @memset(std.mem.asBytes(&u), 0);
1722 u.bar = 0xbbbbbbbb;
1723 u.foo = 0x2a;
1724 break :blk u;
1725 };
1726
1727 try expectEqual(@as(u8, 0x2a), u.foo);
1728 try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.bar);
1729 try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.baz);
1730 }
1731
1732 {
1733 // Union initialization
1734 var u: U = .{
1735 .foo = 0x2a,
1736 };
1737
1738 {
1739 const expected, const mask = switch (endian) {
1740 .Little => .{ 0x2a, 0xff },
1741 .Big => .{ 0x2a000000, 0xff000000 },
1742 };
1743
1744 try expectEqual(@as(u8, 0x2a), u.foo);
1745 try expectEqual(@as(u32, expected), u.bar & mask);
1746 try expectEqual(@as(u32, expected), u.baz & mask);
1747 }
1748
1749 // Writing to a larger field
1750 u.baz = 0xbbbbbbbb;
1751 try expectEqual(@as(u8, 0xbb), u.foo);
1752 try expectEqual(@as(u32, 0xbbbbbbbb), u.bar);
1753 try expectEqual(@as(u32, 0xbbbbbbbb), u.baz);
1754
1755 // Writing to the same field
1756 u.baz = 0xcccccccc;
1757 try expectEqual(@as(u8, 0xcc), u.foo);
1758 try expectEqual(@as(u32, 0xcccccccc), u.bar);
1759 try expectEqual(@as(u32, 0xcccccccc), u.baz);
1760
1761 // Writing to a smaller field
1762 u.foo = 0xdd;
1763 try expectEqual(@as(u8, 0xdd), u.foo);
1764 try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.bar);
1765 try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.baz);
1766 }
1767 }
1768 };
1769
1770 try comptime S.doTheTest();
1771
1772 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
1773 try S.doTheTest();
1774}
1775
1776test "reinterpret packed union" {
1777 const U = packed union {
1778 foo: u8,
1779 bar: u29,
1780 baz: u64,
1781 qux: u12,
1782 };
1783
1784 const S = struct {
1785 fn doTheTest() !void {
1786 {
1787 const u = blk: {
1788 var u: U = undefined;
1789 @memset(std.mem.asBytes(&u), 0);
1790 u.baz = 0xbbbbbbbb;
1791 u.qux = 0xe2a;
1792 break :blk u;
1793 };
1794
1795 try expectEqual(@as(u8, 0x2a), u.foo);
1796 try expectEqual(@as(u12, 0xe2a), u.qux);
1797
1798 // https://github.com/ziglang/zig/issues/17360
1799 if (@inComptime()) {
1800 try expectEqual(@as(u29, 0x1bbbbe2a), u.bar);
1801 try expectEqual(@as(u64, 0xbbbbbe2a), u.baz);
1802 }
1803 }
1804
1805 {
1806 // Union initialization
1807 var u: U = .{
1808 .qux = 0xe2a,
1809 };
1810 try expectEqual(@as(u8, 0x2a), u.foo);
1811 try expectEqual(@as(u12, 0xe2a), u.qux);
1812 try expectEqual(@as(u29, 0xe2a), u.bar & 0xfff);
1813 try expectEqual(@as(u64, 0xe2a), u.baz & 0xfff);
1814
1815 // Writing to a larger field
1816 u.baz = 0xbbbbbbbb;
1817 try expectEqual(@as(u8, 0xbb), u.foo);
1818 try expectEqual(@as(u12, 0xbbb), u.qux);
1819 try expectEqual(@as(u29, 0x1bbbbbbb), u.bar);
1820 try expectEqual(@as(u64, 0xbbbbbbbb), u.baz);
1821
1822 // Writing to the same field
1823 u.baz = 0xcccccccc;
1824 try expectEqual(@as(u8, 0xcc), u.foo);
1825 try expectEqual(@as(u12, 0xccc), u.qux);
1826 try expectEqual(@as(u29, 0x0ccccccc), u.bar);
1827 try expectEqual(@as(u64, 0xcccccccc), u.baz);
1828
1829 // Writing to a smaller field
1830 u.foo = 0xdd;
1831 try expectEqual(@as(u8, 0xdd), u.foo);
1832 try expectEqual(@as(u12, 0xcdd), u.qux);
1833 try expectEqual(@as(u29, 0x0cccccdd), u.bar);
1834 try expectEqual(@as(u64, 0xccccccdd), u.baz);
1835 }
1836 }
1666 };1837 };
1667 const u = comptime blk: {1838
1668 var u: U = undefined;1839 try comptime S.doTheTest();
1669 @memset(std.mem.asBytes(&u), 0);1840
1670 u.foo = 0;1841 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
1671 break :blk u;1842 if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO
1843 try S.doTheTest();
1844}
1845
1846test "reinterpret packed union inside packed struct" {
1847 const U = packed union {
1848 a: u7,
1849 b: u1,
1850 };
1851
1852 const V = packed struct {
1853 lo: U,
1854 hi: U,
1672 };1855 };
1673 try expect(u.foo == 0);1856
1857 const S = struct {
1858 fn doTheTest() !void {
1859 var v: V = undefined;
1860 @memset(std.mem.asBytes(&v), 0x55);
1861 try expectEqual(@as(u7, 0x55), v.lo.a);
1862 try expectEqual(@as(u1, 1), v.lo.b);
1863 try expectEqual(@as(u7, 0x2a), v.hi.a);
1864 try expectEqual(@as(u1, 0), v.hi.b);
1865
1866 v.lo.b = 0;
1867 try expectEqual(@as(u7, 0x54), v.lo.a);
1868 try expectEqual(@as(u1, 0), v.lo.b);
1869 v.hi.b = 1;
1870 try expectEqual(@as(u7, 0x2b), v.hi.a);
1871 try expectEqual(@as(u1, 1), v.hi.b);
1872 }
1873 };
1874
1875 try comptime S.doTheTest();
1876
1877 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
1878 try S.doTheTest();
1674}1879}