| author | |
| committer | |
| log | c36eb4ede9b0ba65f275f0d230fdbee2b71e88df |
| tree | 6214d598a0cc7f67f29676bd4be5dc5ccd82a54a |
| parent | c66d3f6bf6be62d565a444792390655f4db3bd7a |
| parent | 40b7792a4c815868bafe882cc77d89a67c08571b |
| signature |
Introduce `std.mem.readPackedInt` and improve bitcasting of packed memory layouts7 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 | } |
| 1763 | 1763 | ||
| 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 of | 1767 | /// 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 | } |
| 1784 | 1800 | ||
| 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 negative | 1801 | // 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 | }; |
| 1798 | 1809 | ||
| 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 | } |
| 1802 | 1813 | ||
| 1803 | // Copy all complete limbs | 1814 | // 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); | ||
| 1814 | 1822 | ||
| 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 | } |
| 1819 | 1827 | ||
| 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) { |
| 1824 | 1832 | .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 | } | ||
| 1841 | 1839 | ||
| 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)); |
| 1844 | 1842 | 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; | ||
| 1849 | 1843 | ||
| 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 | } |
| 1856 | 1851 | ||
| ... | @@ -2212,66 +2207,48 @@ pub const Const = struct { | ... | @@ -2212,66 +2207,48 @@ pub const Const = struct { |
| 2212 | } | 2207 | } |
| 2213 | 2208 | ||
| 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 via | 2212 | /// `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 | } | ||
| 2221 | 2218 | ||
| 2222 | // byte_count is our total write size | 2219 | /// 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)); |
| 2227 | 2227 | ||
| 2228 | // Copy all complete limbs | 2228 | // 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)); |
| 2240 | 2237 | ||
| 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 | } |
| 2244 | 2242 | ||
| 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); |
| 2251 | 2249 | ||
| 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 | } |
| 2277 | 2254 |
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" { |
| 2603 | 2603 | ||
| 2604 | for (endians) |endian| { | 2604 | for (endians) |endian| { |
| 2605 | // Writing to buffer and back should not change anything | 2605 | // 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); |
| 2609 | 2609 | ||
| 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_size | 2628 | // (3) should sign-extend any bits from bit_count to 8 * abi_size |
| 2629 | 2629 | ||
| 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 })); |
| 2639 | 2639 | ||
| 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; |
| 2643 | 2643 | ||
| 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 | } |
| 2653 | 2653 | ||
| ... | @@ -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); |
| 2662 | 2662 | ||
| 2663 | var bit_count: usize = 0; | ||
| 2664 | |||
| 2665 | // Test zero | 2663 | // Test zero |
| 2666 | 2664 | ||
| 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)))); |
| 2675 | 2673 | ||
| 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" { |
| 2678 | 2676 | ||
| 2679 | // Test negative zero | 2677 | // Test negative zero |
| 2680 | 2678 | ||
| 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 | } |
| 2690 | 2688 | ||
| ... | @@ -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_0a0b0c0d | 2703 | // Test 0x01_02030405_06070809_0a0b0c0d |
| 2706 | 2704 | ||
| 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); |
| 2710 | 2708 | ||
| 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); |
| 2714 | 2712 | ||
| 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); |
| 2718 | 2716 | ||
| 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); |
| 2722 | 2720 | ||
| 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; |
| 2724 | 2742 | ||
| 2725 | // Test -0x01_02030405_06070809_0a0b0c0d | 2743 | // Test -0x01_02030405_06070809_0a0b0c0d |
| 2726 | 2744 | ||
| 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); |
| 2730 | 2748 | ||
| 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); |
| 2734 | 2752 | ||
| 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); |
| 2738 | 2756 | ||
| 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); |
| 2742 | 2760 | ||
| 2743 | // Test 0 | 2761 | // Test 0 |
| 2744 | 2762 | ||
| 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); |
| 2754 | 2772 | ||
| 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 | } |
| 2766 | 2784 | ||
| ... | @@ -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; |
| 2780 | 2798 | ||
| 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); |
| 2786 | 2804 | ||
| 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 | } |
| 2793 | 2811 |
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 | } |
| 1301 | 1301 | ||
| 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 | /// | ||
| 1310 | pub 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 | } |
| 1368 | 1438 | ||
| 1439 | fn 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 | |||
| 1465 | fn 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 | |||
| 1492 | pub const readPackedIntNative = switch (native_endian) { | ||
| 1493 | .Little => readPackedIntLittle, | ||
| 1494 | .Big => readPackedIntBig, | ||
| 1495 | }; | ||
| 1496 | |||
| 1497 | pub 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 | /// | ||
| 1510 | pub 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 0 | 1517 | /// 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 | } |
| 1450 | 1598 | ||
| 1599 | pub 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 | |||
| 1632 | pub 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 | |||
| 1667 | pub const writePackedIntNative = switch (native_endian) { | ||
| 1668 | .Little => writePackedIntLittle, | ||
| 1669 | .Big => writePackedIntBig, | ||
| 1670 | }; | ||
| 1671 | |||
| 1672 | pub 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 | /// | ||
| 1686 | pub 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 | } |
| 1526 | 1768 | ||
| 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 | /// | ||
| 1779 | pub 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 | |||
| 1527 | test "writeIntBig and writeIntLittle" { | 1832 | test "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 | |||
| 3703 | test "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; |
| 26492 | 26492 | ||
| 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 | } |
| 1208 | 1208 | ||
| 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 already | 1280 | .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 buffer | 1290 | 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 type | 1295 | // 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 | } |
| 1287 | 1303 | ||
| 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 buffer | 1309 | 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)); | ||
| 1358 | 1332 | ||
| 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 | } | ||
| 1364 | 1335 | ||
| 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()); | ||
| 1375 | 1363 | ||
| 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 | } |
| 1384 | 1392 | ||
| 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 already | 1465 | .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 type | 1482 | // 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); |
| 1459 | 1485 | ||
| 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 | } |
| 1470 | 1496 | ||
| 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)); | ||
| 1544 | 1527 | ||
| 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 | } |
| 1581 | 1582 | ||
| 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); |
| 64 | 64 | ||
| 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 | } |
| 67 | 71 | ||
| 68 | fn conv_iN(comptime N: usize, x: std.meta.Int(.signed, N)) std.meta.Int(.unsigned, N) { | 72 | fn 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 | } |
| 75 | 79 | ||
| 80 | test "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 | |||
| 94 | fn 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 | |||
| 76 | test "nested bitcast" { | 129 | test "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 | } |
| 285 | 338 | ||
| 286 | test "comptime @bitCast packed struct to int" { | 339 | test "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 | |||
| 378 | test "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 | |||
| 393 | test "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 | } |