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 {...@@ -1762,16 +1762,32 @@ pub const Mutable = struct {
1762 }1762 }
17631763
1764 /// Read the value of `x` from `buffer`1764 /// 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`.
1766 ///1766 ///
1767 /// The contents of `buffer` are interpreted as if they were the contents of1767 /// 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`
1769 /// and any required padding bits are expected on the MSB end.1769 /// and any required padding bits are expected on the MSB end.
1770 pub fn readTwosComplement(1770 pub fn readTwosComplement(
1771 x: *Mutable,1771 x: *Mutable,
1772 buffer: []const u8,1772 buffer: []const u8,
1773 bit_count: usize,1773 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,
1775 endian: Endian,1791 endian: Endian,
1776 signedness: Signedness,1792 signedness: Signedness,
1777 ) void {1793 ) void {
...@@ -1782,75 +1798,54 @@ pub const Mutable = struct {...@@ -1782,75 +1798,54 @@ pub const Mutable = struct {
1782 return;1798 return;
1783 }1799 }
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
1791 // Check whether the input is negative1801 // Check whether the input is negative
1792 var positive = true;1802 var positive = true;
1793 if (signedness == .signed) {1803 if (signedness == .signed) {
1804 const total_bits = bit_offset + bit_count;
1794 var last_byte = switch (endian) {1805 var last_byte = switch (endian) {
1795 .Little => ((bit_count + 7) / 8) - 1,1806 .Little => ((total_bits + 7) / 8) - 1,
1796 .Big => abi_size - ((bit_count + 7) / 8),1807 .Big => bytes.len - ((total_bits + 7) / 8),
1797 };1808 };
17981809
1799 const sign_bit = @as(u8, 1) << @intCast(u3, (bit_count - 1) % 8);1810 const sign_bit = @as(u8, 1) << @intCast(u3, (total_bits - 1) % 8);
1800 positive = ((buffer[last_byte] & sign_bit) == 0);1811 positive = ((bytes[last_byte] & sign_bit) == 0);
1801 }1812 }
18021813
1803 // Copy all complete limbs1814 // Copy all complete limbs
1804 var carry: u1 = if (positive) 0 else 1;1815 var carry: u1 = 1;
1805 var limb_index: usize = 0;1816 var limb_index: usize = 0;
1817 var bit_index: usize = 0;
1806 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {1818 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {
1807 var buf_index = switch (endian) {1819 // Read one Limb of bits
1808 .Little => @sizeOf(Limb) * limb_index,1820 var limb = mem.readPackedInt(Limb, bytes, bit_index + bit_offset, endian);
1809 .Big => abi_size - (limb_index + 1) * @sizeOf(Limb),1821 bit_index += @bitSizeOf(Limb);
1810 };
1811
1812 const limb_buf = @ptrCast(*const [@sizeOf(Limb)]u8, buffer[buf_index..]);
1813 var limb = mem.readInt(Limb, limb_buf, endian);
18141822
1815 // 2's complement (bitwise not, then add carry bit)1823 // 2's complement (bitwise not, then add carry bit)
1816 if (!positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));1824 if (!positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
1817 x.limbs[limb_index] = limb;1825 x.limbs[limb_index] = limb;
1818 }1826 }
18191827
1820 // Copy the remaining N bytes (N <= @sizeOf(Limb))1828 // Copy the remaining bits
1821 var bytes_read = limb_index * @sizeOf(Limb);1829 if (bit_count != bit_index) {
1822 if (bytes_read != byte_count) {1830 // Read all remaining bits
1823 var limb: Limb = 0;1831 var limb = switch (signedness) {
18241832 .unsigned => mem.readVarPackedInt(Limb, bytes, bit_index + bit_offset, bit_count - bit_index, endian, .unsigned),
1825 while (bytes_read != byte_count) {1833 .signed => b: {
1826 const read_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_read);1834 const SLimb = std.meta.Int(.signed, @bitSizeOf(Limb));
1827 var int_buffer = switch (endian) {1835 const limb = mem.readVarPackedInt(SLimb, bytes, bit_index + bit_offset, bit_count - bit_index, endian, .signed);
1828 .Little => buffer[bytes_read..],1836 break :b @bitCast(Limb, limb);
1829 .Big => buffer[(abi_size - bytes_read - read_size)..],1837 },
1830 };1838 };
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 }
18411839
1842 // 2's complement (bitwise not, then add carry bit)1840 // 2's complement (bitwise not, then add carry bit)
1843 if (!positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);1841 if (!positive) assert(!@addWithOverflow(Limb, ~limb, carry, &limb));
18441842 x.limbs[limb_index] = limb;
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;
18491843
1850 x.limbs[limb_count - 1] = limb;1844 limb_index += 1;
1851 }1845 }
1846
1852 x.positive = positive;1847 x.positive = positive;
1853 x.len = limb_count;1848 x.len = limb_index;
1854 x.normalize(x.len);1849 x.normalize(x.len);
1855 }1850 }
18561851
...@@ -2212,66 +2207,48 @@ pub const Const = struct {...@@ -2212,66 +2207,48 @@ pub const Const = struct {
2212 }2207 }
22132208
2214 /// Write the value of `x` into `buffer`2209 /// 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.
2216 ///2211 ///
2217 /// `buffer` is filled so that its contents match what would be observed via2212 /// `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`,
2219 /// and any required padding bits are added on the MSB end.2214 /// 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 size2219 /// Write the value of `x` to a packed memory `buffer`.
2223 const byte_count = abi_size;2220 /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count`
2224 assert(8 * byte_count >= bit_count);2221 /// at offset `bit_offset`.
2225 assert(buffer.len >= byte_count);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 {
2226 assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count));2226 assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count));
22272227
2228 // Copy all complete limbs2228 // Copy all complete limbs
2229 var carry: u1 = if (x.positive) 0 else 1;2229 var carry: u1 = 1;
2230 var limb_index: usize = 0;2230 var limb_index: usize = 0;
2231 while (limb_index < byte_count / @sizeOf(Limb)) : (limb_index += 1) {2231 var bit_index: usize = 0;
2232 var buf_index = switch (endian) {2232 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {
2233 .Little => @sizeOf(Limb) * limb_index,
2234 .Big => abi_size - (limb_index + 1) * @sizeOf(Limb),
2235 };
2236
2237 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;2233 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2234
2238 // 2's complement (bitwise not, then add carry bit)2235 // 2's complement (bitwise not, then add carry bit)
2239 if (!x.positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));2236 if (!x.positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
22402237
2241 var limb_buf = @ptrCast(*[@sizeOf(Limb)]u8, buffer[buf_index..]);2238 // Write one Limb of bits
2242 mem.writeInt(Limb, limb_buf, limb, endian);2239 mem.writePackedInt(Limb, bytes, bit_index + bit_offset, limb, endian);
2240 bit_index += @bitSizeOf(Limb);
2243 }2241 }
22442242
2245 // Copy the remaining N bytes (N < @sizeOf(Limb))2243 // Copy the remaining bits
2246 var bytes_written = limb_index * @sizeOf(Limb);2244 if (bit_count != bit_index) {
2247 if (bytes_written != byte_count) {
2248 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;2245 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2246
2249 // 2's complement (bitwise not, then add carry bit)2247 // 2's complement (bitwise not, then add carry bit)
2250 if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);2248 if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);
22512249
2252 while (bytes_written != byte_count) {2250 // Write all remaining bits
2253 const write_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_written);2251 mem.writeVarPackedInt(bytes, bit_index + bit_offset, bit_count - bit_index, limb, endian);
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 }
2275 }2252 }
2276 }2253 }
22772254
lib/std/math/big/int_test.zig+60-42
...@@ -2603,13 +2603,13 @@ test "big int conversion read/write twos complement" {...@@ -2603,13 +2603,13 @@ test "big int conversion read/write twos complement" {
26032603
2604 for (endians) |endian| {2604 for (endians) |endian| {
2605 // Writing to buffer and back should not change anything2605 // Writing to buffer and back should not change anything
2606 a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian);2606 a.toConst().writeTwosComplement(buffer1[0..abi_size], endian);
2607 m.readTwosComplement(buffer1, 493, abi_size, endian, .unsigned);2607 m.readTwosComplement(buffer1[0..abi_size], 493, endian, .unsigned);
2608 try testing.expect(m.toConst().order(a.toConst()) == .eq);2608 try testing.expect(m.toConst().order(a.toConst()) == .eq);
26092609
2610 // Equivalent to @bitCast(i493, @as(u493, intMax(u493))2610 // Equivalent to @bitCast(i493, @as(u493, intMax(u493))
2611 a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian);2611 a.toConst().writeTwosComplement(buffer1[0..abi_size], endian);
2612 m.readTwosComplement(buffer1, 493, abi_size, endian, .signed);2612 m.readTwosComplement(buffer1[0..abi_size], 493, endian, .signed);
2613 try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq);2613 try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq);
2614 }2614 }
2615}2615}
...@@ -2628,26 +2628,26 @@ test "big int conversion read twos complement with padding" {...@@ -2628,26 +2628,26 @@ test "big int conversion read twos complement with padding" {
2628 // (3) should sign-extend any bits from bit_count to 8 * abi_size2628 // (3) should sign-extend any bits from bit_count to 8 * abi_size
26292629
2630 var bit_count: usize = 12 * 8 + 1;2630 var bit_count: usize = 12 * 8 + 1;
2631 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);2631 a.toConst().writeTwosComplement(buffer1[0..13], .Little);
2632 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 }));2632 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);
2634 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 }));2634 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);
2636 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 }));2636 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);
2638 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 }));2638 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
2640 @memset(buffer1.ptr, 0xaa, buffer1.len);2640 @memset(buffer1.ptr, 0xaa, buffer1.len);
2641 try a.set(-0x01_02030405_06070809_0a0b0c0d);2641 try a.set(-0x01_02030405_06070809_0a0b0c0d);
2642 bit_count = 12 * 8 + 2;2642 bit_count = 12 * 8 + 2;
26432643
2644 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);2644 a.toConst().writeTwosComplement(buffer1[0..13], .Little);
2645 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 }));2645 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);
2647 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 }));2647 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);
2649 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 }));2649 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);
2651 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 }));2651 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 }));
2652}2652}
26532653
...@@ -2660,17 +2660,15 @@ test "big int write twos complement +/- zero" {...@@ -2660,17 +2660,15 @@ test "big int write twos complement +/- zero" {
2660 defer testing.allocator.free(buffer1);2660 defer testing.allocator.free(buffer1);
2661 @memset(buffer1.ptr, 0xaa, buffer1.len);2661 @memset(buffer1.ptr, 0xaa, buffer1.len);
26622662
2663 var bit_count: usize = 0;
2664
2665 // Test zero2663 // Test zero
26662664
2667 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);2665 m.toConst().writeTwosComplement(buffer1[0..13], .Little);
2668 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));2666 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);
2670 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));2668 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);
2672 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));2670 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);
2674 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));2672 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
26752673
2676 @memset(buffer1.ptr, 0xaa, buffer1.len);2674 @memset(buffer1.ptr, 0xaa, buffer1.len);
...@@ -2678,13 +2676,13 @@ test "big int write twos complement +/- zero" {...@@ -2678,13 +2676,13 @@ test "big int write twos complement +/- zero" {
26782676
2679 // Test negative zero2677 // Test negative zero
26802678
2681 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);2679 m.toConst().writeTwosComplement(buffer1[0..13], .Little);
2682 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));2680 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);
2684 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));2682 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);
2686 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));2684 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);
2688 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));2686 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
2689}2687}
26902688
...@@ -2705,62 +2703,82 @@ test "big int conversion write twos complement with padding" {...@@ -2705,62 +2703,82 @@ test "big int conversion write twos complement with padding" {
2705 // Test 0x01_02030405_06070809_0a0b0c0d2703 // Test 0x01_02030405_06070809_0a0b0c0d
27062704
2707 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xb };2705 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);
2709 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);2707 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27102708
2711 buffer = &[_]u8{ 0xb, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };2709 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);
2713 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);2711 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27142712
2715 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xab, 0xaa, 0xaa, 0xaa };2713 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);
2717 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);2715 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27182716
2719 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0xab, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };2717 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);
2721 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);2719 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
2723 bit_count = 12 * 8 + 2;2741 bit_count = 12 * 8 + 2;
27242742
2725 // Test -0x01_02030405_06070809_0a0b0c0d2743 // Test -0x01_02030405_06070809_0a0b0c0d
27262744
2727 buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02 };2745 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);
2729 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);2747 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27302748
2731 buffer = &[_]u8{ 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 };2749 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);
2733 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);2751 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27342752
2735 buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02, 0xaa, 0xaa, 0xaa };2753 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);
2737 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);2755 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27382756
2739 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 };2757 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);
2741 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);2759 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27422760
2743 // Test 02761 // Test 0
27442762
2745 buffer = &([_]u8{0} ** 16);2763 buffer = &([_]u8{0} ** 16);
2746 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);2764 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
2747 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2765 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);
2749 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2767 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);
2751 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2769 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);
2753 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2771 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27542772
2755 bit_count = 0;2773 bit_count = 0;
2756 buffer = &([_]u8{0xaa} ** 16);2774 buffer = &([_]u8{0xaa} ** 16);
2757 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);2775 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
2758 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2776 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);
2760 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2778 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);
2762 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2780 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);
2764 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2782 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2765}2783}
27662784
...@@ -2779,15 +2797,15 @@ test "big int conversion write twos complement zero" {...@@ -2779,15 +2797,15 @@ test "big int conversion write twos complement zero" {
2779 var buffer: []const u8 = undefined;2797 var buffer: []const u8 = undefined;
27802798
2781 buffer = &([_]u8{0} ** 13);2799 buffer = &([_]u8{0} ** 13);
2782 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);2800 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
2783 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2801 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);
2785 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2803 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27862804
2787 buffer = &([_]u8{0} ** 16);2805 buffer = &([_]u8{0} ** 16);
2788 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);2806 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
2789 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2807 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);
2791 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);2809 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2792}2810}
27932811
lib/std/mem.zig+467
...@@ -1299,6 +1299,76 @@ pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: Endian)...@@ -1299,6 +1299,76 @@ pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: Endian)
1299 return result;1299 return result;
1300}1300}
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
1302/// Reads an integer from memory with bit count specified by T.1372/// Reads an integer from memory with bit count specified by T.
1303/// The bit count of T must be evenly divisible by 8.1373/// The bit count of T must be evenly divisible by 8.
1304/// This function cannot fail and cannot cause undefined behavior.1374/// 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,...@@ -1366,6 +1436,84 @@ pub fn readInt(comptime T: type, bytes: *const [@divExact(@typeInfo(T).Int.bits,
1366 }1436 }
1367}1437}
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
1369/// Asserts that bytes.len >= @typeInfo(T).Int.bits / 8. Reads the integer starting from index 01517/// Asserts that bytes.len >= @typeInfo(T).Int.bits / 8. Reads the integer starting from index 0
1370/// and ignores extra bytes.1518/// and ignores extra bytes.
1371/// The bit count of T must be evenly divisible by 8.1519/// 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)]...@@ -1448,6 +1596,100 @@ pub fn writeInt(comptime T: type, buffer: *[@divExact(@typeInfo(T).Int.bits, 8)]
1448 }1596 }
1449}1597}
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
1451/// Writes a twos-complement little-endian integer to memory.1693/// Writes a twos-complement little-endian integer to memory.
1452/// Asserts that buf.len >= @typeInfo(T).Int.bits / 8.1694/// Asserts that buf.len >= @typeInfo(T).Int.bits / 8.
1453/// The bit count of T must be divisible by 8.1695/// 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...@@ -1524,6 +1766,69 @@ pub fn writeIntSlice(comptime T: type, buffer: []u8, value: T, endian: Endian) v
1524 };1766 };
1525}1767}
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
1527test "writeIntBig and writeIntLittle" {1832test "writeIntBig and writeIntLittle" {
1528 var buf0: [0]u8 = undefined;1833 var buf0: [0]u8 = undefined;
1529 var buf1: [1]u8 = undefined;1834 var buf1: [1]u8 = undefined;
...@@ -3394,3 +3699,165 @@ pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice...@@ -3394,3 +3699,165 @@ pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice
3394 const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]);3699 const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]);
3395 return @alignCast(new_alignment, aligned_slice);3700 return @alignCast(new_alignment, aligned_slice);
3396}3701}
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(...@@ -26490,48 +26490,6 @@ fn bitCastVal(
26490 const target = sema.mod.getTarget();26490 const target = sema.mod.getTarget();
26491 if (old_ty.eql(new_ty, sema.mod)) return val;26491 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
26535 // For types with well-defined memory layouts, we serialize them a byte buffer,26493 // For types with well-defined memory layouts, we serialize them a byte buffer,
26536 // then deserialize to the new type.26494 // then deserialize to the new type.
26537 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target));26495 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target));
src/codegen.zig+1-1
...@@ -470,7 +470,7 @@ pub fn generateSymbol(...@@ -470,7 +470,7 @@ pub fn generateSymbol(
470 const abi_size = math.cast(usize, typed_value.ty.abiSize(target)) orelse return error.Overflow;470 const abi_size = math.cast(usize, typed_value.ty.abiSize(target)) orelse return error.Overflow;
471 const start = code.items.len;471 const start = code.items.len;
472 try code.resize(start + abi_size);472 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);
474 return Result{ .appended = {} };474 return Result{ .appended = {} };
475 }475 }
476 switch (info.signedness) {476 switch (info.signedness) {
src/value.zig+245-244
...@@ -1206,8 +1206,13 @@ pub const Value = extern union {...@@ -1206,8 +1206,13 @@ pub const Value = extern union {
1206 };1206 };
1207 }1207 }
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.
1209 pub fn writeToMemory(val: Value, ty: Type, mod: *Module, buffer: []u8) void {1213 pub fn writeToMemory(val: Value, ty: Type, mod: *Module, buffer: []u8) void {
1210 const target = mod.getTarget();1214 const target = mod.getTarget();
1215 const endian = target.cpu.arch.endian();
1211 if (val.isUndef()) {1216 if (val.isUndef()) {
1212 const size = @intCast(usize, ty.abiSize(target));1217 const size = @intCast(usize, ty.abiSize(target));
1213 std.mem.set(u8, buffer[0..size], 0xaa);1218 std.mem.set(u8, buffer[0..size], 0xaa);
...@@ -1218,31 +1223,41 @@ pub const Value = extern union {...@@ -1218,31 +1223,41 @@ pub const Value = extern union {
1218 .Bool => {1223 .Bool => {
1219 buffer[0] = @boolToInt(val.toBool());1224 buffer[0] = @boolToInt(val.toBool());
1220 },1225 },
1221 .Int => {1226 .Int, .Enum => {
1222 var bigint_buffer: BigIntSpace = undefined;1227 const int_info = ty.intInfo(target);
1223 const bigint = val.toBigInt(&bigint_buffer, target);1228 const bits = int_info.bits;
1224 const bits = ty.intInfo(target).bits;1229 const byte_count = (bits + 7) / 8;
1225 const abi_size = @intCast(usize, ty.abiSize(target));1230
1226 bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian());
1227 },
1228 .Enum => {
1229 var enum_buffer: Payload.U64 = undefined;1231 var enum_buffer: Payload.U64 = undefined;
1230 const int_val = val.enumToInt(ty, &enum_buffer);1232 const int_val = val.enumToInt(ty, &enum_buffer);
1231 var bigint_buffer: BigIntSpace = undefined;1233
1232 const bigint = int_val.toBigInt(&bigint_buffer, target);1234 if (byte_count <= @sizeOf(u64)) {
1233 const bits = ty.intInfo(target).bits;1235 const int: u64 = switch (int_val.tag()) {
1234 const abi_size = @intCast(usize, ty.abiSize(target));1236 .zero => 0,
1235 bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian());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 }
1236 },1251 },
1237 .Float => switch (ty.floatBits(target)) {1252 .Float => switch (ty.floatBits(target)) {
1238 16 => return floatWriteToMemory(f16, val.toFloat(f16), target, buffer),1253 16 => std.mem.writeInt(u16, buffer[0..2], @bitCast(u16, val.toFloat(f16)), endian),
1239 32 => return floatWriteToMemory(f32, val.toFloat(f32), target, buffer),1254 32 => std.mem.writeInt(u32, buffer[0..4], @bitCast(u32, val.toFloat(f32)), endian),
1240 64 => return floatWriteToMemory(f64, val.toFloat(f64), target, buffer),1255 64 => std.mem.writeInt(u64, buffer[0..8], @bitCast(u64, val.toFloat(f64)), endian),
1241 80 => return floatWriteToMemory(f80, val.toFloat(f80), target, buffer),1256 80 => std.mem.writeInt(u80, buffer[0..10], @bitCast(u80, val.toFloat(f80)), endian),
1242 128 => return floatWriteToMemory(f128, val.toFloat(f128), target, buffer),1257 128 => std.mem.writeInt(u128, buffer[0..16], @bitCast(u128, val.toFloat(f128)), endian),
1243 else => unreachable,1258 else => unreachable,
1244 },1259 },
1245 .Array, .Vector => {1260 .Array => {
1246 const len = ty.arrayLen();1261 const len = ty.arrayLen();
1247 const elem_ty = ty.childType();1262 const elem_ty = ty.childType();
1248 const elem_size = @intCast(usize, elem_ty.abiSize(target));1263 const elem_size = @intCast(usize, elem_ty.abiSize(target));
...@@ -1251,10 +1266,16 @@ pub const Value = extern union {...@@ -1251,10 +1266,16 @@ pub const Value = extern union {
1251 var buf_off: usize = 0;1266 var buf_off: usize = 0;
1252 while (elem_i < len) : (elem_i += 1) {1267 while (elem_i < len) : (elem_i += 1) {
1253 const elem_val = val.elemValueBuffer(mod, elem_i, &elem_value_buf);1268 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..]);
1255 buf_off += elem_size;1270 buf_off += elem_size;
1256 }1271 }
1257 },1272 },
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 },
1258 .Struct => switch (ty.containerLayout()) {1279 .Struct => switch (ty.containerLayout()) {
1259 .Auto => unreachable, // Sema is supposed to have emitted a compile error already1280 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
1260 .Extern => {1281 .Extern => {
...@@ -1266,122 +1287,113 @@ pub const Value = extern union {...@@ -1266,122 +1287,113 @@ pub const Value = extern union {
1266 }1287 }
1267 },1288 },
1268 .Packed => {1289 .Packed => {
1269 // TODO allocate enough heap space instead of using this buffer1290 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1270 // on the stack.1291 writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
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());
1276 },1292 },
1277 },1293 },
1278 .ErrorSet => {1294 .ErrorSet => {
1279 // TODO revisit this when we have the concept of the error tag type1295 // TODO revisit this when we have the concept of the error tag type
1280 const Int = u16;1296 const Int = u16;
1281 const int = mod.global_error_set.get(val.castTag(.@"error").?.data.name).?;1297 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);
1283 },1299 },
1284 else => @panic("TODO implement writeToMemory for more types"),1300 else => @panic("TODO implement writeToMemory for more types"),
1285 }1301 }
1286 }1302 }
12871303
1288 fn packedStructToInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {1304 /// Write a Value's contents to `buffer`.
1289 var bigint = BigIntMutable.init(buf, 0);1305 ///
1290 const fields = ty.structFields().values();1306 /// Both the start and the end of the provided buffer must be tight, since
1291 const field_vals = val.castTag(.aggregate).?.data;1307 /// big-endian packed memory layouts start at the end of the buffer.
1292 var bits: u16 = 0;1308 pub fn writeToPackedMemory(val: Value, ty: Type, mod: *Module, buffer: []u8, bit_offset: usize) void {
1293 // TODO allocate enough heap space instead of using this buffer1309 const target = mod.getTarget();
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 {
1332 const endian = target.cpu.arch.endian();1310 const endian = target.cpu.arch.endian();
1333 var vec_bitint = BigIntMutable.init(buf, 0);1311 if (val.isUndef()) {
1334 const vec_len = @intCast(usize, ty.arrayLen());1312 const bit_size = @intCast(usize, ty.bitSize(target));
1335 const elem_ty = ty.childType();1313 std.mem.writeVarPackedInt(buffer, bit_offset, bit_size, @as(u1, 0), endian);
1336 const elem_size = @intCast(usize, elem_ty.bitSize(target));1314 return;
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());
1355 }1315 }
1356 return vec_bitint.toConst();1316 switch (ty.zigTypeTag()) {
1357 }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 {1333 var enum_buffer: Payload.U64 = undefined;
1360 var enum_buf: Payload.U64 = undefined;1334 const int_val = val.enumToInt(ty, &enum_buffer);
1361 const int_val = val.enumToInt(ty, &enum_buf);
1362 return int_val.toBigInt(space, target);
1363 }
13641335
1365 fn floatToBigInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {1336 if (abi_size <= @sizeOf(u64)) {
1366 return switch (ty.floatBits(target)) {1337 const int: u64 = switch (int_val.tag()) {
1367 16 => bitcastFloatToBigInt(f16, val.toFloat(f16), buf),1338 .zero => 0,
1368 32 => bitcastFloatToBigInt(f32, val.toFloat(f32), buf),1339 .one => 1,
1369 64 => bitcastFloatToBigInt(f64, val.toFloat(f64), buf),1340 .int_u64 => int_val.castTag(.int_u64).?.data,
1370 80 => bitcastFloatToBigInt(f80, val.toFloat(f80), buf),1341 .int_i64 => @bitCast(u64, int_val.castTag(.int_i64).?.data),
1371 128 => bitcastFloatToBigInt(f128, val.toFloat(f128), buf),1342 else => unreachable,
1372 else => unreachable,1343 };
1373 };1344 std.mem.writeVarPackedInt(buffer, bit_offset, bits, int, endian);
1374 }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 {1364 var bits: u16 = 0;
1377 const Int = @Type(.{ .Int = .{1365 var elem_i: usize = 0;
1378 .signedness = .unsigned,1366 var elem_value_buf: ElemValueBuffer = undefined;
1379 .bits = @typeInfo(F).Float.bits,1367 while (elem_i < len) : (elem_i += 1) {
1380 } });1368 // On big-endian systems, LLVM reverses the element order of vectors by default
1381 const int = @bitCast(Int, f);1369 const tgt_elem_i = if (endian == .Big) len - elem_i - 1 else elem_i;
1382 return BigIntMutable.init(buf, int).toConst();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 }
1383 }1391 }
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.
1385 pub fn readFromMemory(1397 pub fn readFromMemory(
1386 ty: Type,1398 ty: Type,
1387 mod: *Module,1399 mod: *Module,
...@@ -1389,6 +1401,7 @@ pub const Value = extern union {...@@ -1389,6 +1401,7 @@ pub const Value = extern union {
1389 arena: Allocator,1401 arena: Allocator,
1390 ) Allocator.Error!Value {1402 ) Allocator.Error!Value {
1391 const target = mod.getTarget();1403 const target = mod.getTarget();
1404 const endian = target.cpu.arch.endian();
1392 switch (ty.zigTypeTag()) {1405 switch (ty.zigTypeTag()) {
1393 .Void => return Value.@"void",1406 .Void => return Value.@"void",
1394 .Bool => {1407 .Bool => {
...@@ -1398,27 +1411,40 @@ pub const Value = extern union {...@@ -1398,27 +1411,40 @@ pub const Value = extern union {
1398 return Value.@"true";1411 return Value.@"true";
1399 }1412 }
1400 },1413 },
1401 .Int => {1414 .Int, .Enum => {
1402 if (buffer.len == 0) return Value.zero;
1403 const int_info = ty.intInfo(target);1415 const int_info = ty.intInfo(target);
1404 const endian = target.cpu.arch.endian();1416 const bits = int_info.bits;
1405 const Limb = std.math.big.Limb;1417 const byte_count = (bits + 7) / 8;
1406 const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb);1418 if (bits == 0 or buffer.len == 0) return Value.zero;
1407 const limbs_buffer = try arena.alloc(Limb, limb_count);1419
1408 const abi_size = @intCast(usize, ty.abiSize(target));1420 if (bits <= 64) switch (int_info.signedness) { // Fast path for integers <= u64
1409 var bigint = BigIntMutable.init(limbs_buffer, 0);1421 .signed => {
1410 bigint.readTwosComplement(buffer, int_info.bits, abi_size, endian, int_info.signedness);1422 const val = std.mem.readVarInt(i64, buffer[0..byte_count], endian);
1411 return fromBigInt(arena, bigint.toConst());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 }
1412 },1438 },
1413 .Float => switch (ty.floatBits(target)) {1439 .Float => switch (ty.floatBits(target)) {
1414 16 => return Value.Tag.float_16.create(arena, floatReadFromMemory(f16, target, buffer)),1440 16 => return Value.Tag.float_16.create(arena, @bitCast(f16, std.mem.readInt(u16, buffer[0..2], endian))),
1415 32 => return Value.Tag.float_32.create(arena, floatReadFromMemory(f32, target, buffer)),1441 32 => return Value.Tag.float_32.create(arena, @bitCast(f32, std.mem.readInt(u32, buffer[0..4], endian))),
1416 64 => return Value.Tag.float_64.create(arena, floatReadFromMemory(f64, target, buffer)),1442 64 => return Value.Tag.float_64.create(arena, @bitCast(f64, std.mem.readInt(u64, buffer[0..8], endian))),
1417 80 => return Value.Tag.float_80.create(arena, floatReadFromMemory(f80, target, buffer)),1443 80 => return Value.Tag.float_80.create(arena, @bitCast(f80, std.mem.readInt(u80, buffer[0..10], endian))),
1418 128 => return Value.Tag.float_128.create(arena, floatReadFromMemory(f128, target, buffer)),1444 128 => return Value.Tag.float_128.create(arena, @bitCast(f128, std.mem.readInt(u128, buffer[0..16], endian))),
1419 else => unreachable,1445 else => unreachable,
1420 },1446 },
1421 .Array, .Vector => {1447 .Array => {
1422 const elem_ty = ty.childType();1448 const elem_ty = ty.childType();
1423 const elem_size = elem_ty.abiSize(target);1449 const elem_size = elem_ty.abiSize(target);
1424 const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen()));1450 const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen()));
...@@ -1429,6 +1455,12 @@ pub const Value = extern union {...@@ -1429,6 +1455,12 @@ pub const Value = extern union {
1429 }1455 }
1430 return Tag.aggregate.create(arena, elems);1456 return Tag.aggregate.create(arena, elems);
1431 },1457 },
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 },
1432 .Struct => switch (ty.containerLayout()) {1464 .Struct => switch (ty.containerLayout()) {
1433 .Auto => unreachable, // Sema is supposed to have emitted a compile error already1465 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
1434 .Extern => {1466 .Extern => {
...@@ -1436,26 +1468,20 @@ pub const Value = extern union {...@@ -1436,26 +1468,20 @@ pub const Value = extern union {
1436 const field_vals = try arena.alloc(Value, fields.len);1468 const field_vals = try arena.alloc(Value, fields.len);
1437 for (fields) |field, i| {1469 for (fields) |field, i| {
1438 const off = @intCast(usize, ty.structFieldOffset(i, target));1470 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);
1440 }1473 }
1441 return Tag.aggregate.create(arena, field_vals);1474 return Tag.aggregate.create(arena, field_vals);
1442 },1475 },
1443 .Packed => {1476 .Packed => {
1444 const endian = target.cpu.arch.endian();1477 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1445 const Limb = std.math.big.Limb;1478 return readFromPackedMemory(ty, mod, buffer[0..byte_count], 0, arena);
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);
1453 },1479 },
1454 },1480 },
1455 .ErrorSet => {1481 .ErrorSet => {
1456 // TODO revisit this when we have the concept of the error tag type1482 // TODO revisit this when we have the concept of the error tag type
1457 const Int = u16;1483 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
1460 const payload = try arena.create(Value.Payload.Error);1486 const payload = try arena.create(Value.Payload.Error);
1461 payload.* = .{1487 payload.* = .{
...@@ -1468,115 +1494,90 @@ pub const Value = extern union {...@@ -1468,115 +1494,90 @@ pub const Value = extern union {
1468 }1494 }
1469 }1495 }
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(
1472 ty: Type,1502 ty: Type,
1473 target: Target,1503 mod: *Module,
1474 bigint: BigIntConst,1504 buffer: []const u8,
1505 bit_offset: usize,
1475 arena: Allocator,1506 arena: Allocator,
1476 ) Allocator.Error!Value {1507 ) Allocator.Error!Value {
1477 const limbs_buffer = try arena.alloc(std.math.big.Limb, bigint.limbs.len);1508 const target = mod.getTarget();
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 {
1529 const endian = target.cpu.arch.endian();1509 const endian = target.cpu.arch.endian();
1530 if (F == f80) {1510 switch (ty.zigTypeTag()) {
1531 const repr = std.math.break_f80(f);1511 .Void => return Value.@"void",
1532 std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian);1512 .Bool => {
1533 std.mem.writeInt(u16, buffer[8..10], repr.exp, endian);1513 const byte = switch (endian) {
1534 std.mem.set(u8, buffer[10..], 0);1514 .Big => buffer[buffer.len - bit_offset / 8 - 1],
1535 return;1515 .Little => buffer[bit_offset / 8],
1536 }1516 };
1537 const Int = @Type(.{ .Int = .{1517 if (((byte >> @intCast(u3, bit_offset % 8)) & 1) == 0) {
1538 .signedness = .unsigned,1518 return Value.@"false";
1539 .bits = @typeInfo(F).Float.bits,1519 } else {
1540 } });1520 return Value.@"true";
1541 const int = @bitCast(Int, f);1521 }
1542 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], int, endian);1522 },
1543 }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 {1528 const bits = int_info.bits;
1546 const endian = target.cpu.arch.endian();1529 if (bits <= 64) switch (int_info.signedness) { // Fast path for integers <= u64
1547 if (F == f80) {1530 .signed => return Value.Tag.int_i64.create(arena, std.mem.readVarPackedInt(i64, buffer, bit_offset, bits, endian, .signed)),
1548 return std.math.make_f80(.{1531 .unsigned => return Value.Tag.int_u64.create(arena, std.mem.readVarPackedInt(u64, buffer, bit_offset, bits, endian, .unsigned)),
1549 .fraction = readInt(u64, buffer[0..8], endian),1532 } else { // Slow path, we have to construct a big-int
1550 .exp = readInt(u16, buffer[8..10], endian),1533 const Limb = std.math.big.Limb;
1551 });1534 const limb_count = (abi_size + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1552 }1535 const limbs_buffer = try arena.alloc(Limb, limb_count);
1553 const Int = @Type(.{ .Int = .{1536
1554 .signedness = .unsigned,1537 var bigint = BigIntMutable.init(limbs_buffer, 0);
1555 .bits = @typeInfo(F).Float.bits,1538 bigint.readPackedTwosComplement(buffer, bit_offset, bits, endian, int_info.signedness);
1556 } });1539 return fromBigInt(arena, bigint.toConst());
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;
1568 }1540 }
1569 },1541 },
1570 .Little => {1542 .Float => switch (ty.floatBits(target)) {
1571 var i: usize = buffer.len;1543 16 => return Value.Tag.float_16.create(arena, @bitCast(f16, std.mem.readPackedInt(u16, buffer, bit_offset, endian))),
1572 while (i != 0) {1544 32 => return Value.Tag.float_32.create(arena, @bitCast(f32, std.mem.readPackedInt(u32, buffer, bit_offset, endian))),
1573 i -= 1;1545 64 => return Value.Tag.float_64.create(arena, @bitCast(f64, std.mem.readPackedInt(u64, buffer, bit_offset, endian))),
1574 result <<= 8;1546 80 => return Value.Tag.float_80.create(arena, @bitCast(f80, std.mem.readPackedInt(u80, buffer, bit_offset, endian))),
1575 result |= buffer[i];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;
1576 }1561 }
1562 return Tag.aggregate.create(arena, elems);
1577 },1563 },
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"),
1578 }1580 }
1579 return result;
1580 }1581 }
15811582
1582 /// Asserts that the value is a float or an integer.1583 /// 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 {...@@ -63,6 +63,10 @@ fn testBitCast(comptime N: usize) !void {
63 try expect(conv_iN(N, 0) == 0);63 try expect(conv_iN(N, 0) == 0);
6464
65 try expect(conv_iN(N, -0) == 0);65 try expect(conv_iN(N, -0) == 0);
66
67 if (N > 24) {
68 try expect(conv_uN(N, 0xf23456) == 0xf23456);
69 }
66}70}
6771
68fn conv_iN(comptime N: usize, x: std.meta.Int(.signed, N)) std.meta.Int(.unsigned, N) {72fn 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...@@ -73,6 +77,55 @@ fn conv_uN(comptime N: usize, x: std.meta.Int(.unsigned, N)) std.meta.Int(.signe
73 return @bitCast(std.meta.Int(.signed, N), x);77 return @bitCast(std.meta.Int(.signed, N), x);
74}78}
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
76test "nested bitcast" {129test "nested bitcast" {
77 const S = struct {130 const S = struct {
78 fn moo(x: isize) !void {131 fn moo(x: isize) !void {
...@@ -283,7 +336,8 @@ test "@bitCast packed struct of floats" {...@@ -283,7 +336,8 @@ test "@bitCast packed struct of floats" {
283 comptime try S.doTheTest();336 comptime try S.doTheTest();
284}337}
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;
287 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;341 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
288 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;342 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
289 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;343 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
...@@ -304,6 +358,44 @@ test "comptime @bitCast packed struct to int" {...@@ -304,6 +358,44 @@ test "comptime @bitCast packed struct to int" {
304 vectorf: @Vector(2, f16) = .{ 3.14, 2.71 },358 vectorf: @Vector(2, f16) = .{ 3.14, 2.71 },
305 };359 };
306 const Int = @typeInfo(S).Struct.backing_integer.?;360 const Int = @typeInfo(S).Struct.backing_integer.?;
361
362 // S -> Int
307 var s: S = .{};363 var s: S = .{};
308 try expectEqual(@bitCast(Int, s), comptime @bitCast(Int, S{}));364 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)));
309}401}