authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-10-28 21:15:16-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-10-28 21:15:16-04:00
logc36eb4ede9b0ba65f275f0d230fdbee2b71e88df
tree6214d598a0cc7f67f29676bd4be5dc5ccd82a54a
parentc66d3f6bf6be62d565a444792390655f4db3bd7a
parent40b7792a4c815868bafe882cc77d89a67c08571b
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #13221 from topolarity/packed-mem

Introduce `std.mem.readPackedInt` and improve bitcasting of packed memory layouts

7 files changed, 935 insertions(+), 422 deletions(-)

lib/std/math/big/int.zig+69-92
......@@ -1762,16 +1762,32 @@ pub const Mutable = struct {
17621762 }
17631763
17641764 /// Read the value of `x` from `buffer`
1765 /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value.
1765 /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count`.
17661766 ///
17671767 /// The contents of `buffer` are interpreted as if they were the contents of
1768 /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian`
1768 /// @ptrCast(*[buffer.len]const u8, &x). Byte ordering is determined by `endian`
17691769 /// and any required padding bits are expected on the MSB end.
17701770 pub fn readTwosComplement(
17711771 x: *Mutable,
17721772 buffer: []const u8,
17731773 bit_count: usize,
1774 abi_size: usize,
1774 endian: Endian,
1775 signedness: Signedness,
1776 ) void {
1777 return readPackedTwosComplement(x, buffer, 0, bit_count, endian, signedness);
1778 }
1779
1780 /// Read the value of `x` from a packed memory `buffer`.
1781 /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count`
1782 /// at offset `bit_offset`.
1783 ///
1784 /// This is equivalent to loading the value of an integer with `bit_count` bits as
1785 /// if it were a field in packed memory at the provided bit offset.
1786 pub fn readPackedTwosComplement(
1787 x: *Mutable,
1788 bytes: []const u8,
1789 bit_offset: usize,
1790 bit_count: usize,
17751791 endian: Endian,
17761792 signedness: Signedness,
17771793 ) void {
......@@ -1782,75 +1798,54 @@ pub const Mutable = struct {
17821798 return;
17831799 }
17841800
1785 // byte_count is our total read size: it cannot exceed abi_size,
1786 // but may be less as long as it includes the required bits
1787 const limb_count = calcTwosCompLimbCount(bit_count);
1788 const byte_count = std.math.min(abi_size, @sizeOf(Limb) * limb_count);
1789 assert(8 * byte_count >= bit_count);
1790
17911801 // Check whether the input is negative
17921802 var positive = true;
17931803 if (signedness == .signed) {
1804 const total_bits = bit_offset + bit_count;
17941805 var last_byte = switch (endian) {
1795 .Little => ((bit_count + 7) / 8) - 1,
1796 .Big => abi_size - ((bit_count + 7) / 8),
1806 .Little => ((total_bits + 7) / 8) - 1,
1807 .Big => bytes.len - ((total_bits + 7) / 8),
17971808 };
17981809
1799 const sign_bit = @as(u8, 1) << @intCast(u3, (bit_count - 1) % 8);
1800 positive = ((buffer[last_byte] & sign_bit) == 0);
1810 const sign_bit = @as(u8, 1) << @intCast(u3, (total_bits - 1) % 8);
1811 positive = ((bytes[last_byte] & sign_bit) == 0);
18011812 }
18021813
18031814 // Copy all complete limbs
1804 var carry: u1 = if (positive) 0 else 1;
1815 var carry: u1 = 1;
18051816 var limb_index: usize = 0;
1817 var bit_index: usize = 0;
18061818 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {
1807 var buf_index = switch (endian) {
1808 .Little => @sizeOf(Limb) * limb_index,
1809 .Big => abi_size - (limb_index + 1) * @sizeOf(Limb),
1810 };
1811
1812 const limb_buf = @ptrCast(*const [@sizeOf(Limb)]u8, buffer[buf_index..]);
1813 var limb = mem.readInt(Limb, limb_buf, endian);
1819 // Read one Limb of bits
1820 var limb = mem.readPackedInt(Limb, bytes, bit_index + bit_offset, endian);
1821 bit_index += @bitSizeOf(Limb);
18141822
18151823 // 2's complement (bitwise not, then add carry bit)
18161824 if (!positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
18171825 x.limbs[limb_index] = limb;
18181826 }
18191827
1820 // Copy the remaining N bytes (N <= @sizeOf(Limb))
1821 var bytes_read = limb_index * @sizeOf(Limb);
1822 if (bytes_read != byte_count) {
1823 var limb: Limb = 0;
1824
1825 while (bytes_read != byte_count) {
1826 const read_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_read);
1827 var int_buffer = switch (endian) {
1828 .Little => buffer[bytes_read..],
1829 .Big => buffer[(abi_size - bytes_read - read_size)..],
1830 };
1831 limb |= @intCast(Limb, switch (read_size) {
1832 1 => mem.readInt(u8, int_buffer[0..1], endian),
1833 2 => mem.readInt(u16, int_buffer[0..2], endian),
1834 4 => mem.readInt(u32, int_buffer[0..4], endian),
1835 8 => mem.readInt(u64, int_buffer[0..8], endian),
1836 16 => mem.readInt(u128, int_buffer[0..16], endian),
1837 else => unreachable,
1838 }) << @intCast(Log2Limb, 8 * (bytes_read % @sizeOf(Limb)));
1839 bytes_read += read_size;
1840 }
1828 // Copy the remaining bits
1829 if (bit_count != bit_index) {
1830 // Read all remaining bits
1831 var limb = switch (signedness) {
1832 .unsigned => mem.readVarPackedInt(Limb, bytes, bit_index + bit_offset, bit_count - bit_index, endian, .unsigned),
1833 .signed => b: {
1834 const SLimb = std.meta.Int(.signed, @bitSizeOf(Limb));
1835 const limb = mem.readVarPackedInt(SLimb, bytes, bit_index + bit_offset, bit_count - bit_index, endian, .signed);
1836 break :b @bitCast(Limb, limb);
1837 },
1838 };
18411839
18421840 // 2's complement (bitwise not, then add carry bit)
1843 if (!positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);
1844
1845 // Mask off any unused bits
1846 const valid_bits = @intCast(Log2Limb, bit_count % @bitSizeOf(Limb));
1847 const mask = (@as(Limb, 1) << valid_bits) -% 1; // 0b0..01..1 with (valid_bits_in_limb) trailing ones
1848 limb &= mask;
1841 if (!positive) assert(!@addWithOverflow(Limb, ~limb, carry, &limb));
1842 x.limbs[limb_index] = limb;
18491843
1850 x.limbs[limb_count - 1] = limb;
1844 limb_index += 1;
18511845 }
1846
18521847 x.positive = positive;
1853 x.len = limb_count;
1848 x.len = limb_index;
18541849 x.normalize(x.len);
18551850 }
18561851
......@@ -2212,66 +2207,48 @@ pub const Const = struct {
22122207 }
22132208
22142209 /// Write the value of `x` into `buffer`
2215 /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value.
2210 /// Asserts that `buffer` is large enough to store the value.
22162211 ///
22172212 /// `buffer` is filled so that its contents match what would be observed via
2218 /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian`,
2213 /// @ptrCast(*[buffer.len]const u8, &x). Byte ordering is determined by `endian`,
22192214 /// and any required padding bits are added on the MSB end.
2220 pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, abi_size: usize, endian: Endian) void {
2215 pub fn writeTwosComplement(x: Const, buffer: []u8, endian: Endian) void {
2216 return writePackedTwosComplement(x, buffer, 0, 8 * buffer.len, endian);
2217 }
22212218
2222 // byte_count is our total write size
2223 const byte_count = abi_size;
2224 assert(8 * byte_count >= bit_count);
2225 assert(buffer.len >= byte_count);
2219 /// Write the value of `x` to a packed memory `buffer`.
2220 /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count`
2221 /// at offset `bit_offset`.
2222 ///
2223 /// This is equivalent to storing the value of an integer with `bit_count` bits as
2224 /// if it were a field in packed memory at the provided bit offset.
2225 pub fn writePackedTwosComplement(x: Const, bytes: []u8, bit_offset: usize, bit_count: usize, endian: Endian) void {
22262226 assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count));
22272227
22282228 // Copy all complete limbs
2229 var carry: u1 = if (x.positive) 0 else 1;
2229 var carry: u1 = 1;
22302230 var limb_index: usize = 0;
2231 while (limb_index < byte_count / @sizeOf(Limb)) : (limb_index += 1) {
2232 var buf_index = switch (endian) {
2233 .Little => @sizeOf(Limb) * limb_index,
2234 .Big => abi_size - (limb_index + 1) * @sizeOf(Limb),
2235 };
2236
2231 var bit_index: usize = 0;
2232 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {
22372233 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2234
22382235 // 2's complement (bitwise not, then add carry bit)
22392236 if (!x.positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
22402237
2241 var limb_buf = @ptrCast(*[@sizeOf(Limb)]u8, buffer[buf_index..]);
2242 mem.writeInt(Limb, limb_buf, limb, endian);
2238 // Write one Limb of bits
2239 mem.writePackedInt(Limb, bytes, bit_index + bit_offset, limb, endian);
2240 bit_index += @bitSizeOf(Limb);
22432241 }
22442242
2245 // Copy the remaining N bytes (N < @sizeOf(Limb))
2246 var bytes_written = limb_index * @sizeOf(Limb);
2247 if (bytes_written != byte_count) {
2243 // Copy the remaining bits
2244 if (bit_count != bit_index) {
22482245 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2246
22492247 // 2's complement (bitwise not, then add carry bit)
22502248 if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);
22512249
2252 while (bytes_written != byte_count) {
2253 const write_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_written);
2254 var int_buffer = switch (endian) {
2255 .Little => buffer[bytes_written..],
2256 .Big => buffer[(abi_size - bytes_written - write_size)..],
2257 };
2258
2259 if (write_size == 1) {
2260 mem.writeInt(u8, int_buffer[0..1], @truncate(u8, limb), endian);
2261 } else if (@sizeOf(Limb) >= 2 and write_size == 2) {
2262 mem.writeInt(u16, int_buffer[0..2], @truncate(u16, limb), endian);
2263 } else if (@sizeOf(Limb) >= 4 and write_size == 4) {
2264 mem.writeInt(u32, int_buffer[0..4], @truncate(u32, limb), endian);
2265 } else if (@sizeOf(Limb) >= 8 and write_size == 8) {
2266 mem.writeInt(u64, int_buffer[0..8], @truncate(u64, limb), endian);
2267 } else if (@sizeOf(Limb) >= 16 and write_size == 16) {
2268 mem.writeInt(u128, int_buffer[0..16], @truncate(u128, limb), endian);
2269 } else if (@sizeOf(Limb) >= 32) {
2270 @compileError("@sizeOf(Limb) exceeded supported range");
2271 } else unreachable;
2272 limb >>= @intCast(Log2Limb, 8 * write_size);
2273 bytes_written += write_size;
2274 }
2250 // Write all remaining bits
2251 mem.writeVarPackedInt(bytes, bit_index + bit_offset, bit_count - bit_index, limb, endian);
22752252 }
22762253 }
22772254
lib/std/math/big/int_test.zig+60-42
......@@ -2603,13 +2603,13 @@ test "big int conversion read/write twos complement" {
26032603
26042604 for (endians) |endian| {
26052605 // Writing to buffer and back should not change anything
2606 a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian);
2607 m.readTwosComplement(buffer1, 493, abi_size, endian, .unsigned);
2606 a.toConst().writeTwosComplement(buffer1[0..abi_size], endian);
2607 m.readTwosComplement(buffer1[0..abi_size], 493, endian, .unsigned);
26082608 try testing.expect(m.toConst().order(a.toConst()) == .eq);
26092609
26102610 // Equivalent to @bitCast(i493, @as(u493, intMax(u493))
2611 a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian);
2612 m.readTwosComplement(buffer1, 493, abi_size, endian, .signed);
2611 a.toConst().writeTwosComplement(buffer1[0..abi_size], endian);
2612 m.readTwosComplement(buffer1[0..abi_size], 493, endian, .signed);
26132613 try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq);
26142614 }
26152615}
......@@ -2628,26 +2628,26 @@ test "big int conversion read twos complement with padding" {
26282628 // (3) should sign-extend any bits from bit_count to 8 * abi_size
26292629
26302630 var bit_count: usize = 12 * 8 + 1;
2631 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2631 a.toConst().writeTwosComplement(buffer1[0..13], .Little);
26322632 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0xaa, 0xaa, 0xaa }));
2633 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2633 a.toConst().writeTwosComplement(buffer1[0..13], .Big);
26342634 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xaa, 0xaa, 0xaa }));
2635 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2635 a.toConst().writeTwosComplement(buffer1[0..16], .Little);
26362636 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0, 0x0, 0x0 }));
2637 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2637 a.toConst().writeTwosComplement(buffer1[0..16], .Big);
26382638 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd }));
26392639
26402640 @memset(buffer1.ptr, 0xaa, buffer1.len);
26412641 try a.set(-0x01_02030405_06070809_0a0b0c0d);
26422642 bit_count = 12 * 8 + 2;
26432643
2644 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2644 a.toConst().writeTwosComplement(buffer1[0..13], .Little);
26452645 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xaa, 0xaa, 0xaa }));
2646 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2646 a.toConst().writeTwosComplement(buffer1[0..13], .Big);
26472647 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3, 0xaa, 0xaa, 0xaa }));
2648 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2648 a.toConst().writeTwosComplement(buffer1[0..16], .Little);
26492649 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff }));
2650 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2650 a.toConst().writeTwosComplement(buffer1[0..16], .Big);
26512651 try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 }));
26522652}
26532653
......@@ -2660,17 +2660,15 @@ test "big int write twos complement +/- zero" {
26602660 defer testing.allocator.free(buffer1);
26612661 @memset(buffer1.ptr, 0xaa, buffer1.len);
26622662
2663 var bit_count: usize = 0;
2664
26652663 // Test zero
26662664
2667 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2665 m.toConst().writeTwosComplement(buffer1[0..13], .Little);
26682666 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2669 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2667 m.toConst().writeTwosComplement(buffer1[0..13], .Big);
26702668 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2671 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2669 m.toConst().writeTwosComplement(buffer1[0..16], .Little);
26722670 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
2673 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2671 m.toConst().writeTwosComplement(buffer1[0..16], .Big);
26742672 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
26752673
26762674 @memset(buffer1.ptr, 0xaa, buffer1.len);
......@@ -2678,13 +2676,13 @@ test "big int write twos complement +/- zero" {
26782676
26792677 // Test negative zero
26802678
2681 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2679 m.toConst().writeTwosComplement(buffer1[0..13], .Little);
26822680 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2683 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2681 m.toConst().writeTwosComplement(buffer1[0..13], .Big);
26842682 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2685 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2683 m.toConst().writeTwosComplement(buffer1[0..16], .Little);
26862684 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
2687 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2685 m.toConst().writeTwosComplement(buffer1[0..16], .Big);
26882686 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
26892687}
26902688
......@@ -2705,62 +2703,82 @@ test "big int conversion write twos complement with padding" {
27052703 // Test 0x01_02030405_06070809_0a0b0c0d
27062704
27072705 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xb };
2708 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2706 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27092707 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27102708
27112709 buffer = &[_]u8{ 0xb, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2712 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2710 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27132711 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27142712
27152713 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xab, 0xaa, 0xaa, 0xaa };
2716 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2714 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27172715 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27182716
27192717 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0xab, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2720 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2718 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27212719 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27222720
2721 bit_count = @sizeOf(Limb) * 8;
2722
2723 // Test 0x0a0a0a0a_02030405_06070809_0a0b0c0d
2724
2725 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xaa };
2726 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
2727 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaa_02030405_06070809_0a0b0c0d)) == .eq);
2728
2729 buffer = &[_]u8{ 0xaa, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2730 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
2731 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaa_02030405_06070809_0a0b0c0d)) == .eq);
2732
2733 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xaa, 0xaa, 0xaa, 0xaa };
2734 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
2735 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaaaaaaaa_02030405_06070809_0a0b0c0d)) == .eq);
2736
2737 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0xaa, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2738 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
2739 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaaaaaaaa_02030405_06070809_0a0b0c0d)) == .eq);
2740
27232741 bit_count = 12 * 8 + 2;
27242742
27252743 // Test -0x01_02030405_06070809_0a0b0c0d
27262744
27272745 buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02 };
2728 m.readTwosComplement(buffer, bit_count, 13, .Little, .signed);
2746 m.readTwosComplement(buffer[0..13], bit_count, .Little, .signed);
27292747 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27302748
27312749 buffer = &[_]u8{ 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 };
2732 m.readTwosComplement(buffer, bit_count, 13, .Big, .signed);
2750 m.readTwosComplement(buffer[0..13], bit_count, .Big, .signed);
27332751 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27342752
27352753 buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02, 0xaa, 0xaa, 0xaa };
2736 m.readTwosComplement(buffer, bit_count, 16, .Little, .signed);
2754 m.readTwosComplement(buffer[0..16], bit_count, .Little, .signed);
27372755 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27382756
27392757 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 };
2740 m.readTwosComplement(buffer, bit_count, 16, .Big, .signed);
2758 m.readTwosComplement(buffer[0..16], bit_count, .Big, .signed);
27412759 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27422760
27432761 // Test 0
27442762
27452763 buffer = &([_]u8{0} ** 16);
2746 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2764 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27472765 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2748 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2766 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27492767 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2750 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2768 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27512769 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2752 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2770 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27532771 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27542772
27552773 bit_count = 0;
27562774 buffer = &([_]u8{0xaa} ** 16);
2757 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2775 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27582776 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2759 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2777 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27602778 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2761 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2779 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27622780 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2763 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2781 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27642782 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27652783}
27662784
......@@ -2779,15 +2797,15 @@ test "big int conversion write twos complement zero" {
27792797 var buffer: []const u8 = undefined;
27802798
27812799 buffer = &([_]u8{0} ** 13);
2782 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2800 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27832801 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2784 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2802 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27852803 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27862804
27872805 buffer = &([_]u8{0} ** 16);
2788 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2806 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27892807 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2790 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2808 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27912809 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27922810}
27932811
lib/std/mem.zig+467
......@@ -1299,6 +1299,76 @@ pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: Endian)
12991299 return result;
13001300}
13011301
1302/// Loads an integer from packed memory with provided bit_count, bit_offset, and signedness.
1303/// Asserts that T is large enough to store the read value.
1304///
1305/// Example:
1306/// const T = packed struct(u16){ a: u3, b: u7, c: u6 };
1307/// var st = T{ .a = 1, .b = 2, .c = 4 };
1308/// const b_field = readVarPackedInt(u64, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, builtin.cpu.arch.endian(), .unsigned);
1309///
1310pub fn readVarPackedInt(
1311 comptime T: type,
1312 bytes: []const u8,
1313 bit_offset: usize,
1314 bit_count: usize,
1315 endian: std.builtin.Endian,
1316 signedness: std.builtin.Signedness,
1317) T {
1318 const uN = std.meta.Int(.unsigned, @bitSizeOf(T));
1319 const iN = std.meta.Int(.signed, @bitSizeOf(T));
1320 const Log2N = std.math.Log2Int(T);
1321
1322 const read_size = (bit_count + (bit_offset % 8) + 7) / 8;
1323 const bit_shift = @intCast(u3, bit_offset % 8);
1324 const pad = @intCast(Log2N, @bitSizeOf(T) - bit_count);
1325
1326 const lowest_byte = switch (endian) {
1327 .Big => bytes.len - (bit_offset / 8) - read_size,
1328 .Little => bit_offset / 8,
1329 };
1330 const read_bytes = bytes[lowest_byte..][0..read_size];
1331
1332 if (@bitSizeOf(T) <= 8) {
1333 // These are the same shifts/masks we perform below, but adds `@truncate`/`@intCast`
1334 // where needed since int is smaller than a byte.
1335 const value = if (read_size == 1) b: {
1336 break :b @truncate(uN, read_bytes[0] >> bit_shift);
1337 } else b: {
1338 const i: u1 = @boolToInt(endian == .Big);
1339 const head = @truncate(uN, read_bytes[i] >> bit_shift);
1340 const tail_shift = @intCast(Log2N, @as(u4, 8) - bit_shift);
1341 const tail = @truncate(uN, read_bytes[1 - i]);
1342 break :b (tail << tail_shift) | head;
1343 };
1344 switch (signedness) {
1345 .signed => return @intCast(T, (@bitCast(iN, value) << pad) >> pad),
1346 .unsigned => return @intCast(T, (@bitCast(uN, value) << pad) >> pad),
1347 }
1348 }
1349
1350 // Copy the value out (respecting endianness), accounting for bit_shift
1351 var int: uN = 0;
1352 switch (endian) {
1353 .Big => {
1354 for (read_bytes[0 .. read_size - 1]) |elem| {
1355 int = elem | (int << 8);
1356 }
1357 int = (read_bytes[read_size - 1] >> bit_shift) | (int << (@as(u4, 8) - bit_shift));
1358 },
1359 .Little => {
1360 int = read_bytes[0] >> bit_shift;
1361 for (read_bytes[1..]) |elem, i| {
1362 int |= (@as(uN, elem) << @intCast(Log2N, (8 * (i + 1) - bit_shift)));
1363 }
1364 },
1365 }
1366 switch (signedness) {
1367 .signed => return @intCast(T, (@bitCast(iN, int) << pad) >> pad),
1368 .unsigned => return @intCast(T, (@bitCast(uN, int) << pad) >> pad),
1369 }
1370}
1371
13021372/// Reads an integer from memory with bit count specified by T.
13031373/// The bit count of T must be evenly divisible by 8.
13041374/// This function cannot fail and cannot cause undefined behavior.
......@@ -1366,6 +1436,84 @@ pub fn readInt(comptime T: type, bytes: *const [@divExact(@typeInfo(T).Int.bits,
13661436 }
13671437}
13681438
1439fn readPackedIntLittle(comptime T: type, bytes: []const u8, bit_offset: usize) T {
1440 const uN = std.meta.Int(.unsigned, @bitSizeOf(T));
1441 const Log2N = std.math.Log2Int(T);
1442
1443 const bit_count = @as(usize, @bitSizeOf(T));
1444 const bit_shift = @intCast(u3, bit_offset % 8);
1445
1446 const load_size = (bit_count + 7) / 8;
1447 const load_tail_bits = @intCast(u3, (load_size * 8) - bit_count);
1448 const LoadInt = std.meta.Int(.unsigned, load_size * 8);
1449
1450 if (bit_count == 0)
1451 return 0;
1452
1453 // Read by loading a LoadInt, and then follow it up with a 1-byte read
1454 // of the tail if bit_offset pushed us over a byte boundary.
1455 const read_bytes = bytes[bit_offset / 8 ..];
1456 const val = @truncate(uN, readIntLittle(LoadInt, read_bytes[0..load_size]) >> bit_shift);
1457 if (bit_shift > load_tail_bits) {
1458 const tail_bits = @intCast(Log2N, bit_shift - load_tail_bits);
1459 const tail_byte = read_bytes[load_size];
1460 const tail_truncated = if (bit_count < 8) @truncate(uN, tail_byte) else @as(uN, tail_byte);
1461 return @bitCast(T, val | (tail_truncated << (@truncate(Log2N, bit_count) -% tail_bits)));
1462 } else return @bitCast(T, val);
1463}
1464
1465fn readPackedIntBig(comptime T: type, bytes: []const u8, bit_offset: usize) T {
1466 const uN = std.meta.Int(.unsigned, @bitSizeOf(T));
1467 const Log2N = std.math.Log2Int(T);
1468
1469 const bit_count = @as(usize, @bitSizeOf(T));
1470 const bit_shift = @intCast(u3, bit_offset % 8);
1471 const byte_count = (@as(usize, bit_shift) + bit_count + 7) / 8;
1472
1473 const load_size = (bit_count + 7) / 8;
1474 const load_tail_bits = @intCast(u3, (load_size * 8) - bit_count);
1475 const LoadInt = std.meta.Int(.unsigned, load_size * 8);
1476
1477 if (bit_count == 0)
1478 return 0;
1479
1480 // Read by loading a LoadInt, and then follow it up with a 1-byte read
1481 // of the tail if bit_offset pushed us over a byte boundary.
1482 const end = bytes.len - (bit_offset / 8);
1483 const read_bytes = bytes[(end - byte_count)..end];
1484 const val = @truncate(uN, readIntBig(LoadInt, bytes[(end - load_size)..end][0..load_size]) >> bit_shift);
1485 if (bit_shift > load_tail_bits) {
1486 const tail_bits = @intCast(Log2N, bit_shift - load_tail_bits);
1487 const tail_byte = if (bit_count < 8) @truncate(uN, read_bytes[0]) else @as(uN, read_bytes[0]);
1488 return @bitCast(T, val | (tail_byte << (@truncate(Log2N, bit_count) -% tail_bits)));
1489 } else return @bitCast(T, val);
1490}
1491
1492pub const readPackedIntNative = switch (native_endian) {
1493 .Little => readPackedIntLittle,
1494 .Big => readPackedIntBig,
1495};
1496
1497pub const readPackedIntForeign = switch (native_endian) {
1498 .Little => readPackedIntBig,
1499 .Big => readPackedIntLittle,
1500};
1501
1502/// Loads an integer from packed memory.
1503/// Asserts that buffer contains at least bit_offset + @bitSizeOf(T) bits.
1504///
1505/// Example:
1506/// const T = packed struct(u16){ a: u3, b: u7, c: u6 };
1507/// var st = T{ .a = 1, .b = 2, .c = 4 };
1508/// const b_field = readPackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), builtin.cpu.arch.endian());
1509///
1510pub fn readPackedInt(comptime T: type, bytes: []const u8, bit_offset: usize, endian: Endian) T {
1511 switch (endian) {
1512 .Little => return readPackedIntLittle(T, bytes, bit_offset),
1513 .Big => return readPackedIntBig(T, bytes, bit_offset),
1514 }
1515}
1516
13691517/// Asserts that bytes.len >= @typeInfo(T).Int.bits / 8. Reads the integer starting from index 0
13701518/// and ignores extra bytes.
13711519/// The bit count of T must be evenly divisible by 8.
......@@ -1448,6 +1596,100 @@ pub fn writeInt(comptime T: type, buffer: *[@divExact(@typeInfo(T).Int.bits, 8)]
14481596 }
14491597}
14501598
1599pub fn writePackedIntLittle(comptime T: type, bytes: []u8, bit_offset: usize, value: T) void {
1600 const uN = std.meta.Int(.unsigned, @bitSizeOf(T));
1601 const Log2N = std.math.Log2Int(T);
1602
1603 const bit_count = @as(usize, @bitSizeOf(T));
1604 const bit_shift = @intCast(u3, bit_offset % 8);
1605
1606 const store_size = (@bitSizeOf(T) + 7) / 8;
1607 const store_tail_bits = @intCast(u3, (store_size * 8) - bit_count);
1608 const StoreInt = std.meta.Int(.unsigned, store_size * 8);
1609
1610 if (bit_count == 0)
1611 return;
1612
1613 // Write by storing a StoreInt, and then follow it up with a 1-byte tail
1614 // if bit_offset pushed us over a byte boundary.
1615 const write_bytes = bytes[bit_offset / 8 ..];
1616 const head = write_bytes[0] & ((@as(u8, 1) << bit_shift) - 1);
1617
1618 var write_value = (@as(StoreInt, @bitCast(uN, value)) << bit_shift) | @intCast(StoreInt, head);
1619 if (bit_shift > store_tail_bits) {
1620 const tail_len = @intCast(Log2N, bit_shift - store_tail_bits);
1621 write_bytes[store_size] &= ~((@as(u8, 1) << @intCast(u3, tail_len)) - 1);
1622 write_bytes[store_size] |= @intCast(u8, (@bitCast(uN, value) >> (@truncate(Log2N, bit_count) -% tail_len)));
1623 } else if (bit_shift < store_tail_bits) {
1624 const tail_len = store_tail_bits - bit_shift;
1625 const tail = write_bytes[store_size - 1] & (@as(u8, 0xfe) << (7 - tail_len));
1626 write_value |= @as(StoreInt, tail) << (8 * (store_size - 1));
1627 }
1628
1629 writeIntLittle(StoreInt, write_bytes[0..store_size], write_value);
1630}
1631
1632pub fn writePackedIntBig(comptime T: type, bytes: []u8, bit_offset: usize, value: T) void {
1633 const uN = std.meta.Int(.unsigned, @bitSizeOf(T));
1634 const Log2N = std.math.Log2Int(T);
1635
1636 const bit_count = @as(usize, @bitSizeOf(T));
1637 const bit_shift = @intCast(u3, bit_offset % 8);
1638 const byte_count = (bit_shift + bit_count + 7) / 8;
1639
1640 const store_size = (@bitSizeOf(T) + 7) / 8;
1641 const store_tail_bits = @intCast(u3, (store_size * 8) - bit_count);
1642 const StoreInt = std.meta.Int(.unsigned, store_size * 8);
1643
1644 if (bit_count == 0)
1645 return;
1646
1647 // Write by storing a StoreInt, and then follow it up with a 1-byte tail
1648 // if bit_offset pushed us over a byte boundary.
1649 const end = bytes.len - (bit_offset / 8);
1650 const write_bytes = bytes[(end - byte_count)..end];
1651 const head = write_bytes[byte_count - 1] & ((@as(u8, 1) << bit_shift) - 1);
1652
1653 var write_value = (@as(StoreInt, @bitCast(uN, value)) << bit_shift) | @intCast(StoreInt, head);
1654 if (bit_shift > store_tail_bits) {
1655 const tail_len = @intCast(Log2N, bit_shift - store_tail_bits);
1656 write_bytes[0] &= ~((@as(u8, 1) << @intCast(u3, tail_len)) - 1);
1657 write_bytes[0] |= @intCast(u8, (@bitCast(uN, value) >> (@truncate(Log2N, bit_count) -% tail_len)));
1658 } else if (bit_shift < store_tail_bits) {
1659 const tail_len = store_tail_bits - bit_shift;
1660 const tail = write_bytes[0] & (@as(u8, 0xfe) << (7 - tail_len));
1661 write_value |= @as(StoreInt, tail) << (8 * (store_size - 1));
1662 }
1663
1664 writeIntBig(StoreInt, write_bytes[(byte_count - store_size)..][0..store_size], write_value);
1665}
1666
1667pub const writePackedIntNative = switch (native_endian) {
1668 .Little => writePackedIntLittle,
1669 .Big => writePackedIntBig,
1670};
1671
1672pub const writePackedIntForeign = switch (native_endian) {
1673 .Little => writePackedIntBig,
1674 .Big => writePackedIntLittle,
1675};
1676
1677/// Stores an integer to packed memory.
1678/// Asserts that buffer contains at least bit_offset + @bitSizeOf(T) bits.
1679///
1680/// Example:
1681/// const T = packed struct(u16){ a: u3, b: u7, c: u6 };
1682/// var st = T{ .a = 1, .b = 2, .c = 4 };
1683/// // st.b = 0x7f;
1684/// writePackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 0x7f, builtin.cpu.arch.endian());
1685///
1686pub fn writePackedInt(comptime T: type, bytes: []u8, bit_offset: usize, value: T, endian: Endian) void {
1687 switch (endian) {
1688 .Little => writePackedIntLittle(T, bytes, bit_offset, value),
1689 .Big => writePackedIntBig(T, bytes, bit_offset, value),
1690 }
1691}
1692
14511693/// Writes a twos-complement little-endian integer to memory.
14521694/// Asserts that buf.len >= @typeInfo(T).Int.bits / 8.
14531695/// The bit count of T must be divisible by 8.
......@@ -1524,6 +1766,69 @@ pub fn writeIntSlice(comptime T: type, buffer: []u8, value: T, endian: Endian) v
15241766 };
15251767}
15261768
1769/// Stores an integer to packed memory with provided bit_count, bit_offset, and signedness.
1770/// If negative, the written value is sign-extended.
1771///
1772/// Example:
1773/// const T = packed struct(u16){ a: u3, b: u7, c: u6 };
1774/// var st = T{ .a = 1, .b = 2, .c = 4 };
1775/// // st.b = 0x7f;
1776/// var value: u64 = 0x7f;
1777/// writeVarPackedInt(std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, value, builtin.cpu.arch.endian());
1778///
1779pub fn writeVarPackedInt(bytes: []u8, bit_offset: usize, bit_count: usize, value: anytype, endian: std.builtin.Endian) void {
1780 const T = @TypeOf(value);
1781 const uN = std.meta.Int(.unsigned, @bitSizeOf(T));
1782 const Log2N = std.math.Log2Int(T);
1783
1784 const bit_shift = @intCast(u3, bit_offset % 8);
1785 const write_size = (bit_count + bit_shift + 7) / 8;
1786 const lowest_byte = switch (endian) {
1787 .Big => bytes.len - (bit_offset / 8) - write_size,
1788 .Little => bit_offset / 8,
1789 };
1790 const write_bytes = bytes[lowest_byte..][0..write_size];
1791
1792 if (write_size == 1) {
1793 // Single byte writes are handled specially, since we need to mask bits
1794 // on both ends of the byte.
1795 const mask = (@as(u8, 0xff) >> @intCast(u3, 8 - bit_count));
1796 const new_bits = @intCast(u8, @bitCast(uN, value) & mask) << bit_shift;
1797 write_bytes[0] = (write_bytes[0] & ~(mask << bit_shift)) | new_bits;
1798 return;
1799 }
1800
1801 var remaining: T = value;
1802
1803 // Iterate bytes forward for Little-endian, backward for Big-endian
1804 const delta: i2 = if (endian == .Big) -1 else 1;
1805 const start = if (endian == .Big) @intCast(isize, write_bytes.len - 1) else 0;
1806
1807 var i: isize = start; // isize for signed index arithmetic
1808
1809 // Write first byte, using a mask to protects bits preceding bit_offset
1810 const head_mask = @as(u8, 0xff) >> bit_shift;
1811 write_bytes[@intCast(usize, i)] &= ~(head_mask << bit_shift);
1812 write_bytes[@intCast(usize, i)] |= @intCast(u8, @bitCast(uN, remaining) & head_mask) << bit_shift;
1813 remaining >>= @intCast(Log2N, @as(u4, 8) - bit_shift);
1814 i += delta;
1815
1816 // Write bytes[1..bytes.len - 1]
1817 if (@bitSizeOf(T) > 8) {
1818 const loop_end = start + delta * (@intCast(isize, write_size) - 1);
1819 while (i != loop_end) : (i += delta) {
1820 write_bytes[@intCast(usize, i)] = @truncate(u8, @bitCast(uN, remaining));
1821 remaining >>= 8;
1822 }
1823 }
1824
1825 // Write last byte, using a mask to protect bits following bit_offset + bit_count
1826 const following_bits = -%@truncate(u3, bit_shift + bit_count);
1827 const tail_mask = (@as(u8, 0xff) << following_bits) >> following_bits;
1828 write_bytes[@intCast(usize, i)] &= ~tail_mask;
1829 write_bytes[@intCast(usize, i)] |= @intCast(u8, @bitCast(uN, remaining) & tail_mask);
1830}
1831
15271832test "writeIntBig and writeIntLittle" {
15281833 var buf0: [0]u8 = undefined;
15291834 var buf1: [1]u8 = undefined;
......@@ -3394,3 +3699,165 @@ pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice
33943699 const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]);
33953700 return @alignCast(new_alignment, aligned_slice);
33963701}
3702
3703test "read/write(Var)PackedInt" {
3704 switch (builtin.cpu.arch) {
3705 // This test generates too much code to execute on WASI.
3706 // LLVM backend fails with "too many locals: locals exceed maximum"
3707 .wasm32, .wasm64 => return error.SkipZigTest,
3708 else => {},
3709 }
3710
3711 const foreign_endian: Endian = if (native_endian == .Big) .Little else .Big;
3712 const expect = std.testing.expect;
3713 var prng = std.rand.DefaultPrng.init(1234);
3714 const random = prng.random();
3715
3716 @setEvalBranchQuota(10_000);
3717 inline for ([_]type{ u8, u16, u32, u128 }) |BackingType| {
3718 for ([_]BackingType{
3719 @as(BackingType, 0), // all zeros
3720 -%@as(BackingType, 1), // all ones
3721 random.int(BackingType), // random
3722 random.int(BackingType), // random
3723 random.int(BackingType), // random
3724 }) |init_value| {
3725 const uTs = [_]type{ u1, u3, u7, u8, u9, u10, u15, u16, u86 };
3726 const iTs = [_]type{ i1, i3, i7, i8, i9, i10, i15, i16, i86 };
3727 inline for (uTs ++ iTs) |PackedType| {
3728 if (@bitSizeOf(PackedType) > @bitSizeOf(BackingType))
3729 continue;
3730
3731 const iPackedType = std.meta.Int(.signed, @bitSizeOf(PackedType));
3732 const uPackedType = std.meta.Int(.unsigned, @bitSizeOf(PackedType));
3733 const Log2T = std.math.Log2Int(BackingType);
3734
3735 const offset_at_end = @bitSizeOf(BackingType) - @bitSizeOf(PackedType);
3736 for ([_]usize{ 0, 1, 7, 8, 9, 10, 15, 16, 86, offset_at_end }) |offset| {
3737 if (offset > offset_at_end or offset == @bitSizeOf(BackingType))
3738 continue;
3739
3740 for ([_]PackedType{
3741 ~@as(PackedType, 0), // all ones: -1 iN / maxInt uN
3742 @as(PackedType, 0), // all zeros: 0 iN / 0 uN
3743 @bitCast(PackedType, @as(iPackedType, math.maxInt(iPackedType))), // maxInt iN
3744 @bitCast(PackedType, @as(iPackedType, math.minInt(iPackedType))), // maxInt iN
3745 random.int(PackedType), // random
3746 random.int(PackedType), // random
3747 }) |write_value| {
3748 { // Fixed-size Read/Write (Native-endian)
3749
3750 // Initialize Value
3751 var value: BackingType = init_value;
3752
3753 // Read
3754 const read_value1 = readPackedInt(PackedType, asBytes(&value), offset, native_endian);
3755 try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset))));
3756
3757 // Write
3758 writePackedInt(PackedType, asBytes(&value), offset, write_value, native_endian);
3759 try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset))));
3760
3761 // Read again
3762 const read_value2 = readPackedInt(PackedType, asBytes(&value), offset, native_endian);
3763 try expect(read_value2 == write_value);
3764
3765 // Verify bits outside of the target integer are unmodified
3766 const diff_bits = init_value ^ value;
3767 if (offset != offset_at_end)
3768 try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0);
3769 if (offset != 0)
3770 try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0);
3771 }
3772
3773 { // Fixed-size Read/Write (Foreign-endian)
3774
3775 // Initialize Value
3776 var value: BackingType = @byteSwap(init_value);
3777
3778 // Read
3779 const read_value1 = readPackedInt(PackedType, asBytes(&value), offset, foreign_endian);
3780 try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset))));
3781
3782 // Write
3783 writePackedInt(PackedType, asBytes(&value), offset, write_value, foreign_endian);
3784 try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset))));
3785
3786 // Read again
3787 const read_value2 = readPackedInt(PackedType, asBytes(&value), offset, foreign_endian);
3788 try expect(read_value2 == write_value);
3789
3790 // Verify bits outside of the target integer are unmodified
3791 const diff_bits = init_value ^ @byteSwap(value);
3792 if (offset != offset_at_end)
3793 try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0);
3794 if (offset != 0)
3795 try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0);
3796 }
3797
3798 const signedness = @typeInfo(PackedType).Int.signedness;
3799 const NextPowerOfTwoInt = std.meta.Int(signedness, comptime try std.math.ceilPowerOfTwo(u16, @bitSizeOf(PackedType)));
3800 const ui64 = std.meta.Int(signedness, 64);
3801 inline for ([_]type{ PackedType, NextPowerOfTwoInt, ui64 }) |U| {
3802 { // Variable-size Read/Write (Native-endian)
3803
3804 if (@bitSizeOf(U) < @bitSizeOf(PackedType))
3805 continue;
3806
3807 // Initialize Value
3808 var value: BackingType = init_value;
3809
3810 // Read
3811 const read_value1 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), native_endian, signedness);
3812 try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset))));
3813
3814 // Write
3815 writeVarPackedInt(asBytes(&value), offset, @bitSizeOf(PackedType), @as(U, write_value), native_endian);
3816 try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset))));
3817
3818 // Read again
3819 const read_value2 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), native_endian, signedness);
3820 try expect(read_value2 == write_value);
3821
3822 // Verify bits outside of the target integer are unmodified
3823 const diff_bits = init_value ^ value;
3824 if (offset != offset_at_end)
3825 try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0);
3826 if (offset != 0)
3827 try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0);
3828 }
3829
3830 { // Variable-size Read/Write (Foreign-endian)
3831
3832 if (@bitSizeOf(U) < @bitSizeOf(PackedType))
3833 continue;
3834
3835 // Initialize Value
3836 var value: BackingType = @byteSwap(init_value);
3837
3838 // Read
3839 const read_value1 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), foreign_endian, signedness);
3840 try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset))));
3841
3842 // Write
3843 writeVarPackedInt(asBytes(&value), offset, @bitSizeOf(PackedType), @as(U, write_value), foreign_endian);
3844 try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset))));
3845
3846 // Read again
3847 const read_value2 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), foreign_endian, signedness);
3848 try expect(read_value2 == write_value);
3849
3850 // Verify bits outside of the target integer are unmodified
3851 const diff_bits = init_value ^ @byteSwap(value);
3852 if (offset != offset_at_end)
3853 try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0);
3854 if (offset != 0)
3855 try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0);
3856 }
3857 }
3858 }
3859 }
3860 }
3861 }
3862 }
3863}
src/Sema.zig-42
......@@ -26490,48 +26490,6 @@ fn bitCastVal(
2649026490 const target = sema.mod.getTarget();
2649126491 if (old_ty.eql(new_ty, sema.mod)) return val;
2649226492
26493 // Some conversions have a bitwise definition that ignores in-memory layout,
26494 // such as converting between f80 and u80.
26495
26496 if (old_ty.eql(Type.f80, sema.mod) and new_ty.isAbiInt()) {
26497 const float = val.toFloat(f80);
26498 switch (new_ty.intInfo(target).signedness) {
26499 .signed => {
26500 const int = @bitCast(i80, float);
26501 const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
26502 const big_int = std.math.big.int.Mutable.init(limbs, int);
26503 return Value.fromBigInt(sema.arena, big_int.toConst());
26504 },
26505 .unsigned => {
26506 const int = @bitCast(u80, float);
26507 const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
26508 const big_int = std.math.big.int.Mutable.init(limbs, int);
26509 return Value.fromBigInt(sema.arena, big_int.toConst());
26510 },
26511 }
26512 }
26513
26514 if (new_ty.eql(Type.f80, sema.mod) and old_ty.isAbiInt()) {
26515 var bigint_space: Value.BigIntSpace = undefined;
26516 var bigint = try val.toBigIntAdvanced(&bigint_space, target, sema.kit(block, src));
26517 switch (old_ty.intInfo(target).signedness) {
26518 .signed => {
26519 // This conversion cannot fail because we already checked bit size before
26520 // calling bitCastVal.
26521 const int = bigint.to(i80) catch unreachable;
26522 const float = @bitCast(f80, int);
26523 return Value.Tag.float_80.create(sema.arena, float);
26524 },
26525 .unsigned => {
26526 // This conversion cannot fail because we already checked bit size before
26527 // calling bitCastVal.
26528 const int = bigint.to(u80) catch unreachable;
26529 const float = @bitCast(f80, int);
26530 return Value.Tag.float_80.create(sema.arena, float);
26531 },
26532 }
26533 }
26534
2653526493 // For types with well-defined memory layouts, we serialize them a byte buffer,
2653626494 // then deserialize to the new type.
2653726495 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target));
src/codegen.zig+1-1
......@@ -470,7 +470,7 @@ pub fn generateSymbol(
470470 const abi_size = math.cast(usize, typed_value.ty.abiSize(target)) orelse return error.Overflow;
471471 const start = code.items.len;
472472 try code.resize(start + abi_size);
473 bigint.writeTwosComplement(code.items[start..][0..abi_size], info.bits, abi_size, endian);
473 bigint.writeTwosComplement(code.items[start..][0..abi_size], endian);
474474 return Result{ .appended = {} };
475475 }
476476 switch (info.signedness) {
src/value.zig+245-244
......@@ -1206,8 +1206,13 @@ pub const Value = extern union {
12061206 };
12071207 }
12081208
1209 /// Write a Value's contents to `buffer`.
1210 ///
1211 /// Asserts that buffer.len >= ty.abiSize(). The buffer is allowed to extend past
1212 /// the end of the value in memory.
12091213 pub fn writeToMemory(val: Value, ty: Type, mod: *Module, buffer: []u8) void {
12101214 const target = mod.getTarget();
1215 const endian = target.cpu.arch.endian();
12111216 if (val.isUndef()) {
12121217 const size = @intCast(usize, ty.abiSize(target));
12131218 std.mem.set(u8, buffer[0..size], 0xaa);
......@@ -1218,31 +1223,41 @@ pub const Value = extern union {
12181223 .Bool => {
12191224 buffer[0] = @boolToInt(val.toBool());
12201225 },
1221 .Int => {
1222 var bigint_buffer: BigIntSpace = undefined;
1223 const bigint = val.toBigInt(&bigint_buffer, target);
1224 const bits = ty.intInfo(target).bits;
1225 const abi_size = @intCast(usize, ty.abiSize(target));
1226 bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian());
1227 },
1228 .Enum => {
1226 .Int, .Enum => {
1227 const int_info = ty.intInfo(target);
1228 const bits = int_info.bits;
1229 const byte_count = (bits + 7) / 8;
1230
12291231 var enum_buffer: Payload.U64 = undefined;
12301232 const int_val = val.enumToInt(ty, &enum_buffer);
1231 var bigint_buffer: BigIntSpace = undefined;
1232 const bigint = int_val.toBigInt(&bigint_buffer, target);
1233 const bits = ty.intInfo(target).bits;
1234 const abi_size = @intCast(usize, ty.abiSize(target));
1235 bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian());
1233
1234 if (byte_count <= @sizeOf(u64)) {
1235 const int: u64 = switch (int_val.tag()) {
1236 .zero => 0,
1237 .one => 1,
1238 .int_u64 => int_val.castTag(.int_u64).?.data,
1239 .int_i64 => @bitCast(u64, int_val.castTag(.int_i64).?.data),
1240 else => unreachable,
1241 };
1242 for (buffer[0..byte_count]) |_, i| switch (endian) {
1243 .Little => buffer[i] = @truncate(u8, (int >> @intCast(u6, (8 * i)))),
1244 .Big => buffer[byte_count - i - 1] = @truncate(u8, (int >> @intCast(u6, (8 * i)))),
1245 };
1246 } else {
1247 var bigint_buffer: BigIntSpace = undefined;
1248 const bigint = int_val.toBigInt(&bigint_buffer, target);
1249 bigint.writeTwosComplement(buffer[0..byte_count], endian);
1250 }
12361251 },
12371252 .Float => switch (ty.floatBits(target)) {
1238 16 => return floatWriteToMemory(f16, val.toFloat(f16), target, buffer),
1239 32 => return floatWriteToMemory(f32, val.toFloat(f32), target, buffer),
1240 64 => return floatWriteToMemory(f64, val.toFloat(f64), target, buffer),
1241 80 => return floatWriteToMemory(f80, val.toFloat(f80), target, buffer),
1242 128 => return floatWriteToMemory(f128, val.toFloat(f128), target, buffer),
1253 16 => std.mem.writeInt(u16, buffer[0..2], @bitCast(u16, val.toFloat(f16)), endian),
1254 32 => std.mem.writeInt(u32, buffer[0..4], @bitCast(u32, val.toFloat(f32)), endian),
1255 64 => std.mem.writeInt(u64, buffer[0..8], @bitCast(u64, val.toFloat(f64)), endian),
1256 80 => std.mem.writeInt(u80, buffer[0..10], @bitCast(u80, val.toFloat(f80)), endian),
1257 128 => std.mem.writeInt(u128, buffer[0..16], @bitCast(u128, val.toFloat(f128)), endian),
12431258 else => unreachable,
12441259 },
1245 .Array, .Vector => {
1260 .Array => {
12461261 const len = ty.arrayLen();
12471262 const elem_ty = ty.childType();
12481263 const elem_size = @intCast(usize, elem_ty.abiSize(target));
......@@ -1251,10 +1266,16 @@ pub const Value = extern union {
12511266 var buf_off: usize = 0;
12521267 while (elem_i < len) : (elem_i += 1) {
12531268 const elem_val = val.elemValueBuffer(mod, elem_i, &elem_value_buf);
1254 writeToMemory(elem_val, elem_ty, mod, buffer[buf_off..]);
1269 elem_val.writeToMemory(elem_ty, mod, buffer[buf_off..]);
12551270 buf_off += elem_size;
12561271 }
12571272 },
1273 .Vector => {
1274 // We use byte_count instead of abi_size here, so that any padding bytes
1275 // follow the data bytes, on both big- and little-endian systems.
1276 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1277 writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
1278 },
12581279 .Struct => switch (ty.containerLayout()) {
12591280 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
12601281 .Extern => {
......@@ -1266,122 +1287,113 @@ pub const Value = extern union {
12661287 }
12671288 },
12681289 .Packed => {
1269 // TODO allocate enough heap space instead of using this buffer
1270 // on the stack.
1271 var buf: [16]std.math.big.Limb = undefined;
1272 const host_int = packedStructToInt(val, ty, target, &buf);
1273 const abi_size = @intCast(usize, ty.abiSize(target));
1274 const bit_size = @intCast(usize, ty.bitSize(target));
1275 host_int.writeTwosComplement(buffer, bit_size, abi_size, target.cpu.arch.endian());
1290 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1291 writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
12761292 },
12771293 },
12781294 .ErrorSet => {
12791295 // TODO revisit this when we have the concept of the error tag type
12801296 const Int = u16;
12811297 const int = mod.global_error_set.get(val.castTag(.@"error").?.data.name).?;
1282 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], @intCast(Int, int), target.cpu.arch.endian());
1298 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], @intCast(Int, int), endian);
12831299 },
12841300 else => @panic("TODO implement writeToMemory for more types"),
12851301 }
12861302 }
12871303
1288 fn packedStructToInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {
1289 var bigint = BigIntMutable.init(buf, 0);
1290 const fields = ty.structFields().values();
1291 const field_vals = val.castTag(.aggregate).?.data;
1292 var bits: u16 = 0;
1293 // TODO allocate enough heap space instead of using this buffer
1294 // on the stack.
1295 var field_buf: [16]std.math.big.Limb = undefined;
1296 var field_space: BigIntSpace = undefined;
1297 var field_buf2: [16]std.math.big.Limb = undefined;
1298 for (fields) |field, i| {
1299 const field_val = field_vals[i];
1300 const field_bigint_const = switch (field.ty.zigTypeTag()) {
1301 .Void => continue,
1302 .Float => floatToBigInt(field_val, field.ty, target, &field_buf),
1303 .Int, .Bool => intOrBoolToBigInt(field_val, field.ty, target, &field_buf, &field_space),
1304 .Struct => switch (field.ty.containerLayout()) {
1305 .Auto, .Extern => unreachable, // Sema should have error'd before this.
1306 .Packed => packedStructToInt(field_val, field.ty, target, &field_buf),
1307 },
1308 .Vector => vectorToBigInt(field_val, field.ty, target, &field_buf),
1309 .Enum => enumToBigInt(field_val, field.ty, target, &field_space),
1310 .Union => unreachable, // TODO: packed structs support packed unions
1311 else => unreachable,
1312 };
1313 var field_bigint = BigIntMutable.init(&field_buf2, 0);
1314 field_bigint.shiftLeft(field_bigint_const, bits);
1315 bits += @intCast(u16, field.ty.bitSize(target));
1316 bigint.bitOr(bigint.toConst(), field_bigint.toConst());
1317 }
1318 return bigint.toConst();
1319 }
1320
1321 fn intOrBoolToBigInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb, space: *BigIntSpace) BigIntConst {
1322 const big_int_const = val.toBigInt(space, target);
1323 if (big_int_const.positive) return big_int_const;
1324
1325 var big_int = BigIntMutable.init(buf, 0);
1326 big_int.bitNotWrap(big_int_const.negate(), .unsigned, @intCast(u32, ty.bitSize(target)));
1327 big_int.addScalar(big_int.toConst(), 1);
1328 return big_int.toConst();
1329 }
1330
1331 fn vectorToBigInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {
1304 /// Write a Value's contents to `buffer`.
1305 ///
1306 /// Both the start and the end of the provided buffer must be tight, since
1307 /// big-endian packed memory layouts start at the end of the buffer.
1308 pub fn writeToPackedMemory(val: Value, ty: Type, mod: *Module, buffer: []u8, bit_offset: usize) void {
1309 const target = mod.getTarget();
13321310 const endian = target.cpu.arch.endian();
1333 var vec_bitint = BigIntMutable.init(buf, 0);
1334 const vec_len = @intCast(usize, ty.arrayLen());
1335 const elem_ty = ty.childType();
1336 const elem_size = @intCast(usize, elem_ty.bitSize(target));
1337
1338 var elem_buf: [16]std.math.big.Limb = undefined;
1339 var elem_space: BigIntSpace = undefined;
1340 var elem_buf2: [16]std.math.big.Limb = undefined;
1341
1342 var elem_i: usize = 0;
1343 while (elem_i < vec_len) : (elem_i += 1) {
1344 const elem_i_target = if (endian == .Big) vec_len - elem_i - 1 else elem_i;
1345 const elem_val = val.indexVectorlike(elem_i_target);
1346 const elem_bigint_const = switch (elem_ty.zigTypeTag()) {
1347 .Int, .Bool => intOrBoolToBigInt(elem_val, elem_ty, target, &elem_buf, &elem_space),
1348 .Float => floatToBigInt(elem_val, elem_ty, target, &elem_buf),
1349 .Pointer => unreachable, // TODO
1350 else => unreachable, // Sema should not let this happen
1351 };
1352 var elem_bitint = BigIntMutable.init(&elem_buf2, 0);
1353 elem_bitint.shiftLeft(elem_bigint_const, elem_size * elem_i);
1354 vec_bitint.bitOr(vec_bitint.toConst(), elem_bitint.toConst());
1311 if (val.isUndef()) {
1312 const bit_size = @intCast(usize, ty.bitSize(target));
1313 std.mem.writeVarPackedInt(buffer, bit_offset, bit_size, @as(u1, 0), endian);
1314 return;
13551315 }
1356 return vec_bitint.toConst();
1357 }
1316 switch (ty.zigTypeTag()) {
1317 .Void => {},
1318 .Bool => {
1319 const byte_index = switch (endian) {
1320 .Little => bit_offset / 8,
1321 .Big => buffer.len - bit_offset / 8 - 1,
1322 };
1323 if (val.toBool()) {
1324 buffer[byte_index] |= (@as(u8, 1) << @intCast(u3, bit_offset % 8));
1325 } else {
1326 buffer[byte_index] &= ~(@as(u8, 1) << @intCast(u3, bit_offset % 8));
1327 }
1328 },
1329 .Int, .Enum => {
1330 const bits = ty.intInfo(target).bits;
1331 const abi_size = @intCast(usize, ty.abiSize(target));
13581332
1359 fn enumToBigInt(val: Value, ty: Type, target: Target, space: *BigIntSpace) BigIntConst {
1360 var enum_buf: Payload.U64 = undefined;
1361 const int_val = val.enumToInt(ty, &enum_buf);
1362 return int_val.toBigInt(space, target);
1363 }
1333 var enum_buffer: Payload.U64 = undefined;
1334 const int_val = val.enumToInt(ty, &enum_buffer);
13641335
1365 fn floatToBigInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {
1366 return switch (ty.floatBits(target)) {
1367 16 => bitcastFloatToBigInt(f16, val.toFloat(f16), buf),
1368 32 => bitcastFloatToBigInt(f32, val.toFloat(f32), buf),
1369 64 => bitcastFloatToBigInt(f64, val.toFloat(f64), buf),
1370 80 => bitcastFloatToBigInt(f80, val.toFloat(f80), buf),
1371 128 => bitcastFloatToBigInt(f128, val.toFloat(f128), buf),
1372 else => unreachable,
1373 };
1374 }
1336 if (abi_size <= @sizeOf(u64)) {
1337 const int: u64 = switch (int_val.tag()) {
1338 .zero => 0,
1339 .one => 1,
1340 .int_u64 => int_val.castTag(.int_u64).?.data,
1341 .int_i64 => @bitCast(u64, int_val.castTag(.int_i64).?.data),
1342 else => unreachable,
1343 };
1344 std.mem.writeVarPackedInt(buffer, bit_offset, bits, int, endian);
1345 } else {
1346 var bigint_buffer: BigIntSpace = undefined;
1347 const bigint = int_val.toBigInt(&bigint_buffer, target);
1348 bigint.writePackedTwosComplement(buffer, bit_offset, bits, endian);
1349 }
1350 },
1351 .Float => switch (ty.floatBits(target)) {
1352 16 => std.mem.writePackedInt(u16, buffer, bit_offset, @bitCast(u16, val.toFloat(f16)), endian),
1353 32 => std.mem.writePackedInt(u32, buffer, bit_offset, @bitCast(u32, val.toFloat(f32)), endian),
1354 64 => std.mem.writePackedInt(u64, buffer, bit_offset, @bitCast(u64, val.toFloat(f64)), endian),
1355 80 => std.mem.writePackedInt(u80, buffer, bit_offset, @bitCast(u80, val.toFloat(f80)), endian),
1356 128 => std.mem.writePackedInt(u128, buffer, bit_offset, @bitCast(u128, val.toFloat(f128)), endian),
1357 else => unreachable,
1358 },
1359 .Vector => {
1360 const elem_ty = ty.childType();
1361 const elem_bit_size = @intCast(u16, elem_ty.bitSize(target));
1362 const len = @intCast(usize, ty.arrayLen());
13751363
1376 fn bitcastFloatToBigInt(comptime F: type, f: F, buf: []std.math.big.Limb) BigIntConst {
1377 const Int = @Type(.{ .Int = .{
1378 .signedness = .unsigned,
1379 .bits = @typeInfo(F).Float.bits,
1380 } });
1381 const int = @bitCast(Int, f);
1382 return BigIntMutable.init(buf, int).toConst();
1364 var bits: u16 = 0;
1365 var elem_i: usize = 0;
1366 var elem_value_buf: ElemValueBuffer = undefined;
1367 while (elem_i < len) : (elem_i += 1) {
1368 // On big-endian systems, LLVM reverses the element order of vectors by default
1369 const tgt_elem_i = if (endian == .Big) len - elem_i - 1 else elem_i;
1370 const elem_val = val.elemValueBuffer(mod, tgt_elem_i, &elem_value_buf);
1371 elem_val.writeToPackedMemory(elem_ty, mod, buffer, bit_offset + bits);
1372 bits += elem_bit_size;
1373 }
1374 },
1375 .Struct => switch (ty.containerLayout()) {
1376 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
1377 .Extern => unreachable, // Handled in non-packed writeToMemory
1378 .Packed => {
1379 var bits: u16 = 0;
1380 const fields = ty.structFields().values();
1381 const field_vals = val.castTag(.aggregate).?.data;
1382 for (fields) |field, i| {
1383 const field_bits = @intCast(u16, field.ty.bitSize(target));
1384 field_vals[i].writeToPackedMemory(field.ty, mod, buffer, bit_offset + bits);
1385 bits += field_bits;
1386 }
1387 },
1388 },
1389 else => @panic("TODO implement writeToPackedMemory for more types"),
1390 }
13831391 }
13841392
1393 /// Load a Value from the contents of `buffer`.
1394 ///
1395 /// Asserts that buffer.len >= ty.abiSize(). The buffer is allowed to extend past
1396 /// the end of the value in memory.
13851397 pub fn readFromMemory(
13861398 ty: Type,
13871399 mod: *Module,
......@@ -1389,6 +1401,7 @@ pub const Value = extern union {
13891401 arena: Allocator,
13901402 ) Allocator.Error!Value {
13911403 const target = mod.getTarget();
1404 const endian = target.cpu.arch.endian();
13921405 switch (ty.zigTypeTag()) {
13931406 .Void => return Value.@"void",
13941407 .Bool => {
......@@ -1398,27 +1411,40 @@ pub const Value = extern union {
13981411 return Value.@"true";
13991412 }
14001413 },
1401 .Int => {
1402 if (buffer.len == 0) return Value.zero;
1414 .Int, .Enum => {
14031415 const int_info = ty.intInfo(target);
1404 const endian = target.cpu.arch.endian();
1405 const Limb = std.math.big.Limb;
1406 const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1407 const limbs_buffer = try arena.alloc(Limb, limb_count);
1408 const abi_size = @intCast(usize, ty.abiSize(target));
1409 var bigint = BigIntMutable.init(limbs_buffer, 0);
1410 bigint.readTwosComplement(buffer, int_info.bits, abi_size, endian, int_info.signedness);
1411 return fromBigInt(arena, bigint.toConst());
1416 const bits = int_info.bits;
1417 const byte_count = (bits + 7) / 8;
1418 if (bits == 0 or buffer.len == 0) return Value.zero;
1419
1420 if (bits <= 64) switch (int_info.signedness) { // Fast path for integers <= u64
1421 .signed => {
1422 const val = std.mem.readVarInt(i64, buffer[0..byte_count], endian);
1423 return Value.Tag.int_i64.create(arena, (val << @intCast(u6, 64 - bits)) >> @intCast(u6, 64 - bits));
1424 },
1425 .unsigned => {
1426 const val = std.mem.readVarInt(u64, buffer[0..byte_count], endian);
1427 return Value.Tag.int_u64.create(arena, (val << @intCast(u6, 64 - bits)) >> @intCast(u6, 64 - bits));
1428 },
1429 } else { // Slow path, we have to construct a big-int
1430 const Limb = std.math.big.Limb;
1431 const limb_count = (byte_count + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1432 const limbs_buffer = try arena.alloc(Limb, limb_count);
1433
1434 var bigint = BigIntMutable.init(limbs_buffer, 0);
1435 bigint.readTwosComplement(buffer[0..byte_count], bits, endian, int_info.signedness);
1436 return fromBigInt(arena, bigint.toConst());
1437 }
14121438 },
14131439 .Float => switch (ty.floatBits(target)) {
1414 16 => return Value.Tag.float_16.create(arena, floatReadFromMemory(f16, target, buffer)),
1415 32 => return Value.Tag.float_32.create(arena, floatReadFromMemory(f32, target, buffer)),
1416 64 => return Value.Tag.float_64.create(arena, floatReadFromMemory(f64, target, buffer)),
1417 80 => return Value.Tag.float_80.create(arena, floatReadFromMemory(f80, target, buffer)),
1418 128 => return Value.Tag.float_128.create(arena, floatReadFromMemory(f128, target, buffer)),
1440 16 => return Value.Tag.float_16.create(arena, @bitCast(f16, std.mem.readInt(u16, buffer[0..2], endian))),
1441 32 => return Value.Tag.float_32.create(arena, @bitCast(f32, std.mem.readInt(u32, buffer[0..4], endian))),
1442 64 => return Value.Tag.float_64.create(arena, @bitCast(f64, std.mem.readInt(u64, buffer[0..8], endian))),
1443 80 => return Value.Tag.float_80.create(arena, @bitCast(f80, std.mem.readInt(u80, buffer[0..10], endian))),
1444 128 => return Value.Tag.float_128.create(arena, @bitCast(f128, std.mem.readInt(u128, buffer[0..16], endian))),
14191445 else => unreachable,
14201446 },
1421 .Array, .Vector => {
1447 .Array => {
14221448 const elem_ty = ty.childType();
14231449 const elem_size = elem_ty.abiSize(target);
14241450 const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen()));
......@@ -1429,6 +1455,12 @@ pub const Value = extern union {
14291455 }
14301456 return Tag.aggregate.create(arena, elems);
14311457 },
1458 .Vector => {
1459 // We use byte_count instead of abi_size here, so that any padding bytes
1460 // follow the data bytes, on both big- and little-endian systems.
1461 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1462 return readFromPackedMemory(ty, mod, buffer[0..byte_count], 0, arena);
1463 },
14321464 .Struct => switch (ty.containerLayout()) {
14331465 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
14341466 .Extern => {
......@@ -1436,26 +1468,20 @@ pub const Value = extern union {
14361468 const field_vals = try arena.alloc(Value, fields.len);
14371469 for (fields) |field, i| {
14381470 const off = @intCast(usize, ty.structFieldOffset(i, target));
1439 field_vals[i] = try readFromMemory(field.ty, mod, buffer[off..], arena);
1471 const sz = @intCast(usize, ty.structFieldType(i).abiSize(target));
1472 field_vals[i] = try readFromMemory(field.ty, mod, buffer[off..(off + sz)], arena);
14401473 }
14411474 return Tag.aggregate.create(arena, field_vals);
14421475 },
14431476 .Packed => {
1444 const endian = target.cpu.arch.endian();
1445 const Limb = std.math.big.Limb;
1446 const abi_size = @intCast(usize, ty.abiSize(target));
1447 const bit_size = @intCast(usize, ty.bitSize(target));
1448 const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1449 const limbs_buffer = try arena.alloc(Limb, limb_count);
1450 var bigint = BigIntMutable.init(limbs_buffer, 0);
1451 bigint.readTwosComplement(buffer, bit_size, abi_size, endian, .unsigned);
1452 return intToPackedStruct(ty, target, bigint.toConst(), arena);
1477 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1478 return readFromPackedMemory(ty, mod, buffer[0..byte_count], 0, arena);
14531479 },
14541480 },
14551481 .ErrorSet => {
14561482 // TODO revisit this when we have the concept of the error tag type
14571483 const Int = u16;
1458 const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian());
1484 const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], endian);
14591485
14601486 const payload = try arena.create(Value.Payload.Error);
14611487 payload.* = .{
......@@ -1468,115 +1494,90 @@ pub const Value = extern union {
14681494 }
14691495 }
14701496
1471 fn intToPackedStruct(
1497 /// Load a Value from the contents of `buffer`.
1498 ///
1499 /// Both the start and the end of the provided buffer must be tight, since
1500 /// big-endian packed memory layouts start at the end of the buffer.
1501 pub fn readFromPackedMemory(
14721502 ty: Type,
1473 target: Target,
1474 bigint: BigIntConst,
1503 mod: *Module,
1504 buffer: []const u8,
1505 bit_offset: usize,
14751506 arena: Allocator,
14761507 ) Allocator.Error!Value {
1477 const limbs_buffer = try arena.alloc(std.math.big.Limb, bigint.limbs.len);
1478 var bigint_mut = bigint.toMutable(limbs_buffer);
1479 const fields = ty.structFields().values();
1480 const field_vals = try arena.alloc(Value, fields.len);
1481 var bits: u16 = 0;
1482 for (fields) |field, i| {
1483 const field_bits = @intCast(u16, field.ty.bitSize(target));
1484 bigint_mut.shiftRight(bigint, bits);
1485 bigint_mut.truncate(bigint_mut.toConst(), .unsigned, field_bits);
1486 bits += field_bits;
1487 const field_bigint = bigint_mut.toConst();
1488
1489 field_vals[i] = switch (field.ty.zigTypeTag()) {
1490 .Float => switch (field.ty.floatBits(target)) {
1491 16 => try bitCastBigIntToFloat(f16, .float_16, field_bigint, arena),
1492 32 => try bitCastBigIntToFloat(f32, .float_32, field_bigint, arena),
1493 64 => try bitCastBigIntToFloat(f64, .float_64, field_bigint, arena),
1494 80 => try bitCastBigIntToFloat(f80, .float_80, field_bigint, arena),
1495 128 => try bitCastBigIntToFloat(f128, .float_128, field_bigint, arena),
1496 else => unreachable,
1497 },
1498 .Bool => makeBool(!field_bigint.eqZero()),
1499 .Int => try Tag.int_big_positive.create(
1500 arena,
1501 try arena.dupe(std.math.big.Limb, field_bigint.limbs),
1502 ),
1503 .Struct => try intToPackedStruct(field.ty, target, field_bigint, arena),
1504 else => unreachable,
1505 };
1506 }
1507 return Tag.aggregate.create(arena, field_vals);
1508 }
1509
1510 fn bitCastBigIntToFloat(
1511 comptime F: type,
1512 comptime float_tag: Tag,
1513 bigint: BigIntConst,
1514 arena: Allocator,
1515 ) !Value {
1516 const Int = @Type(.{ .Int = .{
1517 .signedness = .unsigned,
1518 .bits = @typeInfo(F).Float.bits,
1519 } });
1520 const int = bigint.to(Int) catch |err| switch (err) {
1521 error.NegativeIntoUnsigned => unreachable,
1522 error.TargetTooSmall => unreachable,
1523 };
1524 const f = @bitCast(F, int);
1525 return float_tag.create(arena, f);
1526 }
1527
1528 fn floatWriteToMemory(comptime F: type, f: F, target: Target, buffer: []u8) void {
1508 const target = mod.getTarget();
15291509 const endian = target.cpu.arch.endian();
1530 if (F == f80) {
1531 const repr = std.math.break_f80(f);
1532 std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian);
1533 std.mem.writeInt(u16, buffer[8..10], repr.exp, endian);
1534 std.mem.set(u8, buffer[10..], 0);
1535 return;
1536 }
1537 const Int = @Type(.{ .Int = .{
1538 .signedness = .unsigned,
1539 .bits = @typeInfo(F).Float.bits,
1540 } });
1541 const int = @bitCast(Int, f);
1542 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], int, endian);
1543 }
1510 switch (ty.zigTypeTag()) {
1511 .Void => return Value.@"void",
1512 .Bool => {
1513 const byte = switch (endian) {
1514 .Big => buffer[buffer.len - bit_offset / 8 - 1],
1515 .Little => buffer[bit_offset / 8],
1516 };
1517 if (((byte >> @intCast(u3, bit_offset % 8)) & 1) == 0) {
1518 return Value.@"false";
1519 } else {
1520 return Value.@"true";
1521 }
1522 },
1523 .Int, .Enum => {
1524 if (buffer.len == 0) return Value.zero;
1525 const int_info = ty.intInfo(target);
1526 const abi_size = @intCast(usize, ty.abiSize(target));
15441527
1545 fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F {
1546 const endian = target.cpu.arch.endian();
1547 if (F == f80) {
1548 return std.math.make_f80(.{
1549 .fraction = readInt(u64, buffer[0..8], endian),
1550 .exp = readInt(u16, buffer[8..10], endian),
1551 });
1552 }
1553 const Int = @Type(.{ .Int = .{
1554 .signedness = .unsigned,
1555 .bits = @typeInfo(F).Float.bits,
1556 } });
1557 const int = readInt(Int, buffer[0..@sizeOf(Int)], endian);
1558 return @bitCast(F, int);
1559 }
1560
1561 fn readInt(comptime Int: type, buffer: *const [@sizeOf(Int)]u8, endian: std.builtin.Endian) Int {
1562 var result: Int = 0;
1563 switch (endian) {
1564 .Big => {
1565 for (buffer) |byte| {
1566 result <<= 8;
1567 result |= byte;
1528 const bits = int_info.bits;
1529 if (bits <= 64) switch (int_info.signedness) { // Fast path for integers <= u64
1530 .signed => return Value.Tag.int_i64.create(arena, std.mem.readVarPackedInt(i64, buffer, bit_offset, bits, endian, .signed)),
1531 .unsigned => return Value.Tag.int_u64.create(arena, std.mem.readVarPackedInt(u64, buffer, bit_offset, bits, endian, .unsigned)),
1532 } else { // Slow path, we have to construct a big-int
1533 const Limb = std.math.big.Limb;
1534 const limb_count = (abi_size + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1535 const limbs_buffer = try arena.alloc(Limb, limb_count);
1536
1537 var bigint = BigIntMutable.init(limbs_buffer, 0);
1538 bigint.readPackedTwosComplement(buffer, bit_offset, bits, endian, int_info.signedness);
1539 return fromBigInt(arena, bigint.toConst());
15681540 }
15691541 },
1570 .Little => {
1571 var i: usize = buffer.len;
1572 while (i != 0) {
1573 i -= 1;
1574 result <<= 8;
1575 result |= buffer[i];
1542 .Float => switch (ty.floatBits(target)) {
1543 16 => return Value.Tag.float_16.create(arena, @bitCast(f16, std.mem.readPackedInt(u16, buffer, bit_offset, endian))),
1544 32 => return Value.Tag.float_32.create(arena, @bitCast(f32, std.mem.readPackedInt(u32, buffer, bit_offset, endian))),
1545 64 => return Value.Tag.float_64.create(arena, @bitCast(f64, std.mem.readPackedInt(u64, buffer, bit_offset, endian))),
1546 80 => return Value.Tag.float_80.create(arena, @bitCast(f80, std.mem.readPackedInt(u80, buffer, bit_offset, endian))),
1547 128 => return Value.Tag.float_128.create(arena, @bitCast(f128, std.mem.readPackedInt(u128, buffer, bit_offset, endian))),
1548 else => unreachable,
1549 },
1550 .Vector => {
1551 const elem_ty = ty.childType();
1552 const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen()));
1553
1554 var bits: u16 = 0;
1555 const elem_bit_size = @intCast(u16, elem_ty.bitSize(target));
1556 for (elems) |_, i| {
1557 // On big-endian systems, LLVM reverses the element order of vectors by default
1558 const tgt_elem_i = if (endian == .Big) elems.len - i - 1 else i;
1559 elems[tgt_elem_i] = try readFromPackedMemory(elem_ty, mod, buffer, bit_offset + bits, arena);
1560 bits += elem_bit_size;
15761561 }
1562 return Tag.aggregate.create(arena, elems);
15771563 },
1564 .Struct => switch (ty.containerLayout()) {
1565 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
1566 .Extern => unreachable, // Handled by non-packed readFromMemory
1567 .Packed => {
1568 var bits: u16 = 0;
1569 const fields = ty.structFields().values();
1570 const field_vals = try arena.alloc(Value, fields.len);
1571 for (fields) |field, i| {
1572 const field_bits = @intCast(u16, field.ty.bitSize(target));
1573 field_vals[i] = try readFromPackedMemory(field.ty, mod, buffer, bit_offset + bits, arena);
1574 bits += field_bits;
1575 }
1576 return Tag.aggregate.create(arena, field_vals);
1577 },
1578 },
1579 else => @panic("TODO implement readFromPackedMemory for more types"),
15781580 }
1579 return result;
15801581 }
15811582
15821583 /// Asserts that the value is a float or an integer.
test/behavior/bitcast.zig+93-1
......@@ -63,6 +63,10 @@ fn testBitCast(comptime N: usize) !void {
6363 try expect(conv_iN(N, 0) == 0);
6464
6565 try expect(conv_iN(N, -0) == 0);
66
67 if (N > 24) {
68 try expect(conv_uN(N, 0xf23456) == 0xf23456);
69 }
6670}
6771
6872fn conv_iN(comptime N: usize, x: std.meta.Int(.signed, N)) std.meta.Int(.unsigned, N) {
......@@ -73,6 +77,55 @@ fn conv_uN(comptime N: usize, x: std.meta.Int(.unsigned, N)) std.meta.Int(.signe
7377 return @bitCast(std.meta.Int(.signed, N), x);
7478}
7579
80test "bitcast uX to bytes" {
81 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
82 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
83 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
84 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
85 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
86
87 const bit_values = [_]usize{ 1, 48, 27, 512, 493, 293, 125, 204, 112 };
88 inline for (bit_values) |bits| {
89 try testBitCast(bits);
90 comptime try testBitCast(bits);
91 }
92}
93
94fn testBitCastuXToBytes(comptime N: usize) !void {
95
96 // The location of padding bits in these layouts are technically not defined
97 // by LLVM, but we currently allow exotic integers to be cast (at comptime)
98 // to types that expose their padding bits anyway.
99 //
100 // This test at least makes sure those bits are matched by the runtime behavior
101 // on the platforms we target. If the above behavior is restricted after all,
102 // this test should be deleted.
103
104 const T = std.meta.Int(.unsigned, N);
105 for ([_]T{ 0, ~@as(T, 0) }) |init_value| {
106 var x: T = init_value;
107 const bytes = std.mem.asBytes(&x);
108
109 const byte_count = (N + 7) / 8;
110 switch (builtin.cpu.arch.endian()) {
111 .Little => {
112 var byte_i = 0;
113 while (byte_i < (byte_count - 1)) : (byte_i += 1) {
114 try expect(bytes[byte_i] == 0xff);
115 }
116 try expect(((bytes[byte_i] ^ 0xff) << -%@truncate(u3, N)) == 0);
117 },
118 .Big => {
119 var byte_i = byte_count - 1;
120 while (byte_i > 0) : (byte_i -= 1) {
121 try expect(bytes[byte_i] == 0xff);
122 }
123 try expect(((bytes[byte_i] ^ 0xff) << -%@truncate(u3, N)) == 0);
124 },
125 }
126 }
127}
128
76129test "nested bitcast" {
77130 const S = struct {
78131 fn moo(x: isize) !void {
......@@ -283,7 +336,8 @@ test "@bitCast packed struct of floats" {
283336 comptime try S.doTheTest();
284337}
285338
286test "comptime @bitCast packed struct to int" {
339test "comptime @bitCast packed struct to int and back" {
340 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
287341 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
288342 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
289343 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
......@@ -304,6 +358,44 @@ test "comptime @bitCast packed struct to int" {
304358 vectorf: @Vector(2, f16) = .{ 3.14, 2.71 },
305359 };
306360 const Int = @typeInfo(S).Struct.backing_integer.?;
361
362 // S -> Int
307363 var s: S = .{};
308364 try expectEqual(@bitCast(Int, s), comptime @bitCast(Int, S{}));
365
366 // Int -> S
367 var i: Int = 0;
368 const rt_cast = @bitCast(S, i);
369 const ct_cast = comptime @bitCast(S, @as(Int, 0));
370 inline for (@typeInfo(S).Struct.fields) |field| {
371 if (@typeInfo(field.field_type) == .Vector)
372 continue; //TODO: https://github.com/ziglang/zig/issues/13201
373
374 try expectEqual(@field(rt_cast, field.name), @field(ct_cast, field.name));
375 }
376}
377
378test "comptime bitcast with fields following f80" {
379 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
380 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
381 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
382 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
383 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
384
385 const FloatT = extern struct { f: f80, x: u128 align(16) };
386 const x: FloatT = .{ .f = 0.5, .x = 123 };
387 var x_as_uint: u256 = comptime @bitCast(u256, x);
388
389 try expect(x.f == @bitCast(FloatT, x_as_uint).f);
390 try expect(x.x == @bitCast(FloatT, x_as_uint).x);
391}
392
393test "bitcast vector to integer and back" {
394 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO: https://github.com/ziglang/zig/issues/13220
395 if (builtin.zig_backend == .stage1) return error.SkipZigTest; // stage1 gets the comptime cast wrong
396
397 const arr: [16]bool = [_]bool{ true, false } ++ [_]bool{true} ** 14;
398 var x = @splat(16, true);
399 x[1] = false;
400 try expect(@bitCast(u16, x) == comptime @bitCast(u16, @as(@Vector(16, bool), arr)));
309401}