| ... | ... | @@ -1298,6 +1298,76 @@ pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: Endian) |
| 1298 | 1298 | return result; |
| 1299 | 1299 | } |
| 1300 | 1300 | |
| 1301 | /// Loads an integer from packed memory with provided bit_count, bit_offset, and signedness. |
| 1302 | /// Asserts that T is large enough to store the read value. |
| 1303 | /// |
| 1304 | /// Example: |
| 1305 | /// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; |
| 1306 | /// var st = T{ .a = 1, .b = 2, .c = 4 }; |
| 1307 | /// const b_field = readVarPackedInt(u64, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, builtin.cpu.arch.endian(), .unsigned); |
| 1308 | /// |
| 1309 | pub fn readVarPackedInt( |
| 1310 | comptime T: type, |
| 1311 | bytes: []const u8, |
| 1312 | bit_offset: usize, |
| 1313 | bit_count: usize, |
| 1314 | endian: std.builtin.Endian, |
| 1315 | signedness: std.builtin.Signedness, |
| 1316 | ) T { |
| 1317 | const uN = std.meta.Int(.unsigned, @bitSizeOf(T)); |
| 1318 | const iN = std.meta.Int(.signed, @bitSizeOf(T)); |
| 1319 | const Log2N = std.math.Log2Int(T); |
| 1320 | |
| 1321 | const read_size = (bit_count + (bit_offset % 8) + 7) / 8; |
| 1322 | const bit_shift = @intCast(u3, bit_offset % 8); |
| 1323 | const pad = @intCast(Log2N, @bitSizeOf(T) - bit_count); |
| 1324 | |
| 1325 | const lowest_byte = switch (endian) { |
| 1326 | .Big => bytes.len - (bit_offset / 8) - read_size, |
| 1327 | .Little => bit_offset / 8, |
| 1328 | }; |
| 1329 | const read_bytes = bytes[lowest_byte..][0..read_size]; |
| 1330 | |
| 1331 | if (@bitSizeOf(T) <= 8) { |
| 1332 | // These are the same shifts/masks we perform below, but adds `@truncate`/`@intCast` |
| 1333 | // where needed since int is smaller than a byte. |
| 1334 | const value = if (read_size == 1) b: { |
| 1335 | break :b @truncate(uN, read_bytes[0] >> bit_shift); |
| 1336 | } else b: { |
| 1337 | const i: u1 = @boolToInt(endian == .Big); |
| 1338 | const head = @truncate(uN, read_bytes[i] >> bit_shift); |
| 1339 | const tail_shift = @intCast(Log2N, @as(u4, 8) - bit_shift); |
| 1340 | const tail = @truncate(uN, read_bytes[1 - i]); |
| 1341 | break :b (tail << tail_shift) | head; |
| 1342 | }; |
| 1343 | switch (signedness) { |
| 1344 | .signed => return @intCast(T, (@bitCast(iN, value) << pad) >> pad), |
| 1345 | .unsigned => return @intCast(T, (@bitCast(uN, value) << pad) >> pad), |
| 1346 | } |
| 1347 | } |
| 1348 | |
| 1349 | // Copy the value out (respecting endianness), accounting for bit_shift |
| 1350 | var int: uN = 0; |
| 1351 | switch (endian) { |
| 1352 | .Big => { |
| 1353 | for (read_bytes[0 .. read_size - 1]) |elem| { |
| 1354 | int = elem | (int << 8); |
| 1355 | } |
| 1356 | int = (read_bytes[read_size - 1] >> bit_shift) | (int << (@as(u4, 8) - bit_shift)); |
| 1357 | }, |
| 1358 | .Little => { |
| 1359 | int = read_bytes[0] >> bit_shift; |
| 1360 | for (read_bytes[1..]) |elem, i| { |
| 1361 | int |= (@as(uN, elem) << @intCast(Log2N, (8 * (i + 1) - bit_shift))); |
| 1362 | } |
| 1363 | }, |
| 1364 | } |
| 1365 | switch (signedness) { |
| 1366 | .signed => return @intCast(T, (@bitCast(iN, int) << pad) >> pad), |
| 1367 | .unsigned => return @intCast(T, (@bitCast(uN, int) << pad) >> pad), |
| 1368 | } |
| 1369 | } |
| 1370 | |
| 1301 | 1371 | /// Reads an integer from memory with bit count specified by T. |
| 1302 | 1372 | /// The bit count of T must be evenly divisible by 8. |
| 1303 | 1373 | /// This function cannot fail and cannot cause undefined behavior. |
| ... | ... | @@ -1365,6 +1435,84 @@ pub fn readInt(comptime T: type, bytes: *const [@divExact(@typeInfo(T).Int.bits, |
| 1365 | 1435 | } |
| 1366 | 1436 | } |
| 1367 | 1437 | |
| 1438 | fn readPackedIntLittle(comptime T: type, bytes: []const u8, bit_offset: usize) T { |
| 1439 | const uN = std.meta.Int(.unsigned, @bitSizeOf(T)); |
| 1440 | const Log2N = std.math.Log2Int(T); |
| 1441 | |
| 1442 | const bit_count = @as(usize, @bitSizeOf(T)); |
| 1443 | const bit_shift = @intCast(u3, bit_offset % 8); |
| 1444 | |
| 1445 | const load_size = (bit_count + 7) / 8; |
| 1446 | const load_tail_bits = @intCast(u3, (load_size * 8) - bit_count); |
| 1447 | const LoadInt = std.meta.Int(.unsigned, load_size * 8); |
| 1448 | |
| 1449 | if (bit_count == 0) |
| 1450 | return 0; |
| 1451 | |
| 1452 | // Read by loading a LoadInt, and then follow it up with a 1-byte read |
| 1453 | // of the tail if bit_offset pushed us over a byte boundary. |
| 1454 | const read_bytes = bytes[bit_offset / 8 ..]; |
| 1455 | const val = @truncate(uN, readIntLittle(LoadInt, read_bytes[0..load_size]) >> bit_shift); |
| 1456 | if (bit_shift > load_tail_bits) { |
| 1457 | const tail_bits = @intCast(Log2N, bit_shift - load_tail_bits); |
| 1458 | const tail_byte = read_bytes[load_size]; |
| 1459 | const tail_truncated = if (bit_count < 8) @truncate(uN, tail_byte) else @as(uN, tail_byte); |
| 1460 | return @bitCast(T, val | (tail_truncated << (@truncate(Log2N, bit_count) -% tail_bits))); |
| 1461 | } else return @bitCast(T, val); |
| 1462 | } |
| 1463 | |
| 1464 | fn readPackedIntBig(comptime T: type, bytes: []const u8, bit_offset: usize) T { |
| 1465 | const uN = std.meta.Int(.unsigned, @bitSizeOf(T)); |
| 1466 | const Log2N = std.math.Log2Int(T); |
| 1467 | |
| 1468 | const bit_count = @as(usize, @bitSizeOf(T)); |
| 1469 | const bit_shift = @intCast(u3, bit_offset % 8); |
| 1470 | const byte_count = (@as(usize, bit_shift) + bit_count + 7) / 8; |
| 1471 | |
| 1472 | const load_size = (bit_count + 7) / 8; |
| 1473 | const load_tail_bits = @intCast(u3, (load_size * 8) - bit_count); |
| 1474 | const LoadInt = std.meta.Int(.unsigned, load_size * 8); |
| 1475 | |
| 1476 | if (bit_count == 0) |
| 1477 | return 0; |
| 1478 | |
| 1479 | // Read by loading a LoadInt, and then follow it up with a 1-byte read |
| 1480 | // of the tail if bit_offset pushed us over a byte boundary. |
| 1481 | const end = bytes.len - (bit_offset / 8); |
| 1482 | const read_bytes = bytes[(end - byte_count)..end]; |
| 1483 | const val = @truncate(uN, readIntBig(LoadInt, bytes[(end - load_size)..end][0..load_size]) >> bit_shift); |
| 1484 | if (bit_shift > load_tail_bits) { |
| 1485 | const tail_bits = @intCast(Log2N, bit_shift - load_tail_bits); |
| 1486 | const tail_byte = if (bit_count < 8) @truncate(uN, read_bytes[0]) else @as(uN, read_bytes[0]); |
| 1487 | return @bitCast(T, val | (tail_byte << (@truncate(Log2N, bit_count) -% tail_bits))); |
| 1488 | } else return @bitCast(T, val); |
| 1489 | } |
| 1490 | |
| 1491 | pub const readPackedIntNative = switch (native_endian) { |
| 1492 | .Little => readPackedIntLittle, |
| 1493 | .Big => readPackedIntBig, |
| 1494 | }; |
| 1495 | |
| 1496 | pub const readPackedIntForeign = switch (native_endian) { |
| 1497 | .Little => readPackedIntBig, |
| 1498 | .Big => readPackedIntLittle, |
| 1499 | }; |
| 1500 | |
| 1501 | /// Loads an integer from packed memory. |
| 1502 | /// Asserts that buffer contains at least bit_offset + @bitSizeOf(T) bits. |
| 1503 | /// |
| 1504 | /// Example: |
| 1505 | /// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; |
| 1506 | /// var st = T{ .a = 1, .b = 2, .c = 4 }; |
| 1507 | /// const b_field = readPackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), builtin.cpu.arch.endian()); |
| 1508 | /// |
| 1509 | pub fn readPackedInt(comptime T: type, bytes: []const u8, bit_offset: usize, endian: Endian) T { |
| 1510 | switch (endian) { |
| 1511 | .Little => return readPackedIntLittle(T, bytes, bit_offset), |
| 1512 | .Big => return readPackedIntBig(T, bytes, bit_offset), |
| 1513 | } |
| 1514 | } |
| 1515 | |
| 1368 | 1516 | /// Asserts that bytes.len >= @typeInfo(T).Int.bits / 8. Reads the integer starting from index 0 |
| 1369 | 1517 | /// and ignores extra bytes. |
| 1370 | 1518 | /// The bit count of T must be evenly divisible by 8. |
| ... | ... | @@ -1447,6 +1595,100 @@ pub fn writeInt(comptime T: type, buffer: *[@divExact(@typeInfo(T).Int.bits, 8)] |
| 1447 | 1595 | } |
| 1448 | 1596 | } |
| 1449 | 1597 | |
| 1598 | pub fn writePackedIntLittle(comptime T: type, bytes: []u8, bit_offset: usize, value: T) void { |
| 1599 | const uN = std.meta.Int(.unsigned, @bitSizeOf(T)); |
| 1600 | const Log2N = std.math.Log2Int(T); |
| 1601 | |
| 1602 | const bit_count = @as(usize, @bitSizeOf(T)); |
| 1603 | const bit_shift = @intCast(u3, bit_offset % 8); |
| 1604 | |
| 1605 | const store_size = (@bitSizeOf(T) + 7) / 8; |
| 1606 | const store_tail_bits = @intCast(u3, (store_size * 8) - bit_count); |
| 1607 | const StoreInt = std.meta.Int(.unsigned, store_size * 8); |
| 1608 | |
| 1609 | if (bit_count == 0) |
| 1610 | return; |
| 1611 | |
| 1612 | // Write by storing a StoreInt, and then follow it up with a 1-byte tail |
| 1613 | // if bit_offset pushed us over a byte boundary. |
| 1614 | const write_bytes = bytes[bit_offset / 8 ..]; |
| 1615 | const head = write_bytes[0] & ((@as(u8, 1) << bit_shift) - 1); |
| 1616 | |
| 1617 | var write_value = (@as(StoreInt, @bitCast(uN, value)) << bit_shift) | @intCast(StoreInt, head); |
| 1618 | if (bit_shift > store_tail_bits) { |
| 1619 | const tail_len = @intCast(Log2N, bit_shift - store_tail_bits); |
| 1620 | write_bytes[store_size] &= ~((@as(u8, 1) << @intCast(u3, tail_len)) - 1); |
| 1621 | write_bytes[store_size] |= @intCast(u8, (@bitCast(uN, value) >> (@truncate(Log2N, bit_count) -% tail_len))); |
| 1622 | } else if (bit_shift < store_tail_bits) { |
| 1623 | const tail_len = store_tail_bits - bit_shift; |
| 1624 | const tail = write_bytes[store_size - 1] & (@as(u8, 0xfe) << (7 - tail_len)); |
| 1625 | write_value |= @as(StoreInt, tail) << (8 * (store_size - 1)); |
| 1626 | } |
| 1627 | |
| 1628 | writeIntLittle(StoreInt, write_bytes[0..store_size], write_value); |
| 1629 | } |
| 1630 | |
| 1631 | pub fn writePackedIntBig(comptime T: type, bytes: []u8, bit_offset: usize, value: T) void { |
| 1632 | const uN = std.meta.Int(.unsigned, @bitSizeOf(T)); |
| 1633 | const Log2N = std.math.Log2Int(T); |
| 1634 | |
| 1635 | const bit_count = @as(usize, @bitSizeOf(T)); |
| 1636 | const bit_shift = @intCast(u3, bit_offset % 8); |
| 1637 | const byte_count = (bit_shift + bit_count + 7) / 8; |
| 1638 | |
| 1639 | const store_size = (@bitSizeOf(T) + 7) / 8; |
| 1640 | const store_tail_bits = @intCast(u3, (store_size * 8) - bit_count); |
| 1641 | const StoreInt = std.meta.Int(.unsigned, store_size * 8); |
| 1642 | |
| 1643 | if (bit_count == 0) |
| 1644 | return; |
| 1645 | |
| 1646 | // Write by storing a StoreInt, and then follow it up with a 1-byte tail |
| 1647 | // if bit_offset pushed us over a byte boundary. |
| 1648 | const end = bytes.len - (bit_offset / 8); |
| 1649 | const write_bytes = bytes[(end - byte_count)..end]; |
| 1650 | const head = write_bytes[byte_count - 1] & ((@as(u8, 1) << bit_shift) - 1); |
| 1651 | |
| 1652 | var write_value = (@as(StoreInt, @bitCast(uN, value)) << bit_shift) | @intCast(StoreInt, head); |
| 1653 | if (bit_shift > store_tail_bits) { |
| 1654 | const tail_len = @intCast(Log2N, bit_shift - store_tail_bits); |
| 1655 | write_bytes[0] &= ~((@as(u8, 1) << @intCast(u3, tail_len)) - 1); |
| 1656 | write_bytes[0] |= @intCast(u8, (@bitCast(uN, value) >> (@truncate(Log2N, bit_count) -% tail_len))); |
| 1657 | } else if (bit_shift < store_tail_bits) { |
| 1658 | const tail_len = store_tail_bits - bit_shift; |
| 1659 | const tail = write_bytes[0] & (@as(u8, 0xfe) << (7 - tail_len)); |
| 1660 | write_value |= @as(StoreInt, tail) << (8 * (store_size - 1)); |
| 1661 | } |
| 1662 | |
| 1663 | writeIntBig(StoreInt, write_bytes[(byte_count - store_size)..][0..store_size], write_value); |
| 1664 | } |
| 1665 | |
| 1666 | pub const writePackedIntNative = switch (native_endian) { |
| 1667 | .Little => writePackedIntLittle, |
| 1668 | .Big => writePackedIntBig, |
| 1669 | }; |
| 1670 | |
| 1671 | pub const writePackedIntForeign = switch (native_endian) { |
| 1672 | .Little => writePackedIntBig, |
| 1673 | .Big => writePackedIntLittle, |
| 1674 | }; |
| 1675 | |
| 1676 | /// Stores an integer to packed memory. |
| 1677 | /// Asserts that buffer contains at least bit_offset + @bitSizeOf(T) bits. |
| 1678 | /// |
| 1679 | /// Example: |
| 1680 | /// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; |
| 1681 | /// var st = T{ .a = 1, .b = 2, .c = 4 }; |
| 1682 | /// // st.b = 0x7f; |
| 1683 | /// writePackedInt(u7, std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 0x7f, builtin.cpu.arch.endian()); |
| 1684 | /// |
| 1685 | pub fn writePackedInt(comptime T: type, bytes: []u8, bit_offset: usize, value: T, endian: Endian) void { |
| 1686 | switch (endian) { |
| 1687 | .Little => writePackedIntLittle(T, bytes, bit_offset, value), |
| 1688 | .Big => writePackedIntBig(T, bytes, bit_offset, value), |
| 1689 | } |
| 1690 | } |
| 1691 | |
| 1450 | 1692 | /// Writes a twos-complement little-endian integer to memory. |
| 1451 | 1693 | /// Asserts that buf.len >= @typeInfo(T).Int.bits / 8. |
| 1452 | 1694 | /// The bit count of T must be divisible by 8. |
| ... | ... | @@ -1523,6 +1765,69 @@ pub fn writeIntSlice(comptime T: type, buffer: []u8, value: T, endian: Endian) v |
| 1523 | 1765 | }; |
| 1524 | 1766 | } |
| 1525 | 1767 | |
| 1768 | /// Stores an integer to packed memory with provided bit_count, bit_offset, and signedness. |
| 1769 | /// If negative, the written value is sign-extended. |
| 1770 | /// |
| 1771 | /// Example: |
| 1772 | /// const T = packed struct(u16){ a: u3, b: u7, c: u6 }; |
| 1773 | /// var st = T{ .a = 1, .b = 2, .c = 4 }; |
| 1774 | /// // st.b = 0x7f; |
| 1775 | /// var value: u64 = 0x7f; |
| 1776 | /// writeVarPackedInt(std.mem.asBytes(&st), @bitOffsetOf(T, "b"), 7, value, builtin.cpu.arch.endian()); |
| 1777 | /// |
| 1778 | pub fn writeVarPackedInt(bytes: []u8, bit_offset: usize, bit_count: usize, value: anytype, endian: std.builtin.Endian) void { |
| 1779 | const T = @TypeOf(value); |
| 1780 | const uN = std.meta.Int(.unsigned, @bitSizeOf(T)); |
| 1781 | const Log2N = std.math.Log2Int(T); |
| 1782 | |
| 1783 | const bit_shift = @intCast(u3, bit_offset % 8); |
| 1784 | const write_size = (bit_count + bit_shift + 7) / 8; |
| 1785 | const lowest_byte = switch (endian) { |
| 1786 | .Big => bytes.len - (bit_offset / 8) - write_size, |
| 1787 | .Little => bit_offset / 8, |
| 1788 | }; |
| 1789 | const write_bytes = bytes[lowest_byte..][0..write_size]; |
| 1790 | |
| 1791 | if (write_size == 1) { |
| 1792 | // Single byte writes are handled specially, since we need to mask bits |
| 1793 | // on both ends of the byte. |
| 1794 | const mask = (@as(u8, 0xff) >> @intCast(u3, 8 - bit_count)); |
| 1795 | const new_bits = @intCast(u8, @bitCast(uN, value) & mask) << bit_shift; |
| 1796 | write_bytes[0] = (write_bytes[0] & ~(mask << bit_shift)) | new_bits; |
| 1797 | return; |
| 1798 | } |
| 1799 | |
| 1800 | var remaining: T = value; |
| 1801 | |
| 1802 | // Iterate bytes forward for Little-endian, backward for Big-endian |
| 1803 | const delta: i2 = if (endian == .Big) -1 else 1; |
| 1804 | const start = if (endian == .Big) @intCast(isize, write_bytes.len - 1) else 0; |
| 1805 | |
| 1806 | var i: isize = start; // isize for signed index arithmetic |
| 1807 | |
| 1808 | // Write first byte, using a mask to protects bits preceding bit_offset |
| 1809 | const head_mask = @as(u8, 0xff) >> bit_shift; |
| 1810 | write_bytes[@intCast(usize, i)] &= ~(head_mask << bit_shift); |
| 1811 | write_bytes[@intCast(usize, i)] |= @intCast(u8, @bitCast(uN, remaining) & head_mask) << bit_shift; |
| 1812 | remaining >>= @intCast(Log2N, @as(u4, 8) - bit_shift); |
| 1813 | i += delta; |
| 1814 | |
| 1815 | // Write bytes[1..bytes.len - 1] |
| 1816 | if (@bitSizeOf(T) > 8) { |
| 1817 | const loop_end = start + delta * (@intCast(isize, write_size) - 1); |
| 1818 | while (i != loop_end) : (i += delta) { |
| 1819 | write_bytes[@intCast(usize, i)] = @truncate(u8, @bitCast(uN, remaining)); |
| 1820 | remaining >>= 8; |
| 1821 | } |
| 1822 | } |
| 1823 | |
| 1824 | // Write last byte, using a mask to protect bits following bit_offset + bit_count |
| 1825 | const following_bits = -%@truncate(u3, bit_shift + bit_count); |
| 1826 | const tail_mask = (@as(u8, 0xff) << following_bits) >> following_bits; |
| 1827 | write_bytes[@intCast(usize, i)] &= ~tail_mask; |
| 1828 | write_bytes[@intCast(usize, i)] |= @intCast(u8, @bitCast(uN, remaining) & tail_mask); |
| 1829 | } |
| 1830 | |
| 1526 | 1831 | test "writeIntBig and writeIntLittle" { |
| 1527 | 1832 | var buf0: [0]u8 = undefined; |
| 1528 | 1833 | var buf1: [1]u8 = undefined; |
| ... | ... | @@ -3393,3 +3698,158 @@ pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice |
| 3393 | 3698 | const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]); |
| 3394 | 3699 | return @alignCast(new_alignment, aligned_slice); |
| 3395 | 3700 | } |
| 3701 | |
| 3702 | test "read/write(Var)PackedInt" { |
| 3703 | const foreign_endian: Endian = if (native_endian == .Big) .Little else .Big; |
| 3704 | const expect = std.testing.expect; |
| 3705 | var prng = std.rand.DefaultPrng.init(1234); |
| 3706 | const random = prng.random(); |
| 3707 | |
| 3708 | @setEvalBranchQuota(10_000); |
| 3709 | inline for ([_]type{ u8, u16, u32, u128 }) |BackingType| { |
| 3710 | for ([_]BackingType{ |
| 3711 | @as(BackingType, 0), // all zeros |
| 3712 | -%@as(BackingType, 1), // all ones |
| 3713 | random.int(BackingType), // random |
| 3714 | random.int(BackingType), // random |
| 3715 | random.int(BackingType), // random |
| 3716 | }) |init_value| { |
| 3717 | const uTs = [_]type{ u1, u3, u7, u8, u9, u10, u15, u16, u86 }; |
| 3718 | const iTs = [_]type{ i1, i3, i7, i8, i9, i10, i15, i16, i86 }; |
| 3719 | inline for (uTs ++ iTs) |PackedType| { |
| 3720 | if (@bitSizeOf(PackedType) > @bitSizeOf(BackingType)) |
| 3721 | continue; |
| 3722 | |
| 3723 | const iPackedType = std.meta.Int(.signed, @bitSizeOf(PackedType)); |
| 3724 | const uPackedType = std.meta.Int(.unsigned, @bitSizeOf(PackedType)); |
| 3725 | const Log2T = std.math.Log2Int(BackingType); |
| 3726 | |
| 3727 | const offset_at_end = @bitSizeOf(BackingType) - @bitSizeOf(PackedType); |
| 3728 | for ([_]usize{ 0, 1, 7, 8, 9, 10, 15, 16, 86, offset_at_end }) |offset| { |
| 3729 | if (offset > offset_at_end or offset == @bitSizeOf(BackingType)) |
| 3730 | continue; |
| 3731 | |
| 3732 | for ([_]PackedType{ |
| 3733 | ~@as(PackedType, 0), // all ones: -1 iN / maxInt uN |
| 3734 | @as(PackedType, 0), // all zeros: 0 iN / 0 uN |
| 3735 | @bitCast(PackedType, @as(iPackedType, math.maxInt(iPackedType))), // maxInt iN |
| 3736 | @bitCast(PackedType, @as(iPackedType, math.minInt(iPackedType))), // maxInt iN |
| 3737 | random.int(PackedType), // random |
| 3738 | random.int(PackedType), // random |
| 3739 | }) |write_value| { |
| 3740 | { // Fixed-size Read/Write (Native-endian) |
| 3741 | |
| 3742 | // Initialize Value |
| 3743 | var value: BackingType = init_value; |
| 3744 | |
| 3745 | // Read |
| 3746 | const read_value1 = readPackedInt(PackedType, asBytes(&value), offset, native_endian); |
| 3747 | try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset)))); |
| 3748 | |
| 3749 | // Write |
| 3750 | writePackedInt(PackedType, asBytes(&value), offset, write_value, native_endian); |
| 3751 | try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset)))); |
| 3752 | |
| 3753 | // Read again |
| 3754 | const read_value2 = readPackedInt(PackedType, asBytes(&value), offset, native_endian); |
| 3755 | try expect(read_value2 == write_value); |
| 3756 | |
| 3757 | // Verify bits outside of the target integer are unmodified |
| 3758 | const diff_bits = init_value ^ value; |
| 3759 | if (offset != offset_at_end) |
| 3760 | try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0); |
| 3761 | if (offset != 0) |
| 3762 | try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0); |
| 3763 | } |
| 3764 | |
| 3765 | { // Fixed-size Read/Write (Foreign-endian) |
| 3766 | |
| 3767 | // Initialize Value |
| 3768 | var value: BackingType = @byteSwap(init_value); |
| 3769 | |
| 3770 | // Read |
| 3771 | const read_value1 = readPackedInt(PackedType, asBytes(&value), offset, foreign_endian); |
| 3772 | try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset)))); |
| 3773 | |
| 3774 | // Write |
| 3775 | writePackedInt(PackedType, asBytes(&value), offset, write_value, foreign_endian); |
| 3776 | try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset)))); |
| 3777 | |
| 3778 | // Read again |
| 3779 | const read_value2 = readPackedInt(PackedType, asBytes(&value), offset, foreign_endian); |
| 3780 | try expect(read_value2 == write_value); |
| 3781 | |
| 3782 | // Verify bits outside of the target integer are unmodified |
| 3783 | const diff_bits = init_value ^ @byteSwap(value); |
| 3784 | if (offset != offset_at_end) |
| 3785 | try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0); |
| 3786 | if (offset != 0) |
| 3787 | try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0); |
| 3788 | } |
| 3789 | |
| 3790 | const signedness = @typeInfo(PackedType).Int.signedness; |
| 3791 | const NextPowerOfTwoInt = std.meta.Int(signedness, comptime try std.math.ceilPowerOfTwo(u16, @bitSizeOf(PackedType))); |
| 3792 | const ui64 = std.meta.Int(signedness, 64); |
| 3793 | inline for ([_]type{ PackedType, NextPowerOfTwoInt, ui64 }) |U| { |
| 3794 | { // Variable-size Read/Write (Native-endian) |
| 3795 | |
| 3796 | if (@bitSizeOf(U) < @bitSizeOf(PackedType)) |
| 3797 | continue; |
| 3798 | |
| 3799 | // Initialize Value |
| 3800 | var value: BackingType = init_value; |
| 3801 | |
| 3802 | // Read |
| 3803 | const read_value1 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), native_endian, signedness); |
| 3804 | try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset)))); |
| 3805 | |
| 3806 | // Write |
| 3807 | writeVarPackedInt(asBytes(&value), offset, @bitSizeOf(PackedType), @as(U, write_value), native_endian); |
| 3808 | try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, value >> @intCast(Log2T, offset)))); |
| 3809 | |
| 3810 | // Read again |
| 3811 | const read_value2 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), native_endian, signedness); |
| 3812 | try expect(read_value2 == write_value); |
| 3813 | |
| 3814 | // Verify bits outside of the target integer are unmodified |
| 3815 | const diff_bits = init_value ^ value; |
| 3816 | if (offset != offset_at_end) |
| 3817 | try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0); |
| 3818 | if (offset != 0) |
| 3819 | try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0); |
| 3820 | } |
| 3821 | |
| 3822 | { // Variable-size Read/Write (Foreign-endian) |
| 3823 | |
| 3824 | if (@bitSizeOf(U) < @bitSizeOf(PackedType)) |
| 3825 | continue; |
| 3826 | |
| 3827 | // Initialize Value |
| 3828 | var value: BackingType = @byteSwap(init_value); |
| 3829 | |
| 3830 | // Read |
| 3831 | const read_value1 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), foreign_endian, signedness); |
| 3832 | try expect(read_value1 == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset)))); |
| 3833 | |
| 3834 | // Write |
| 3835 | writeVarPackedInt(asBytes(&value), offset, @bitSizeOf(PackedType), @as(U, write_value), foreign_endian); |
| 3836 | try expect(write_value == @bitCast(PackedType, @truncate(uPackedType, @byteSwap(value) >> @intCast(Log2T, offset)))); |
| 3837 | |
| 3838 | // Read again |
| 3839 | const read_value2 = readVarPackedInt(U, asBytes(&value), offset, @bitSizeOf(PackedType), foreign_endian, signedness); |
| 3840 | try expect(read_value2 == write_value); |
| 3841 | |
| 3842 | // Verify bits outside of the target integer are unmodified |
| 3843 | const diff_bits = init_value ^ @byteSwap(value); |
| 3844 | if (offset != offset_at_end) |
| 3845 | try expect(diff_bits >> @intCast(Log2T, offset + @bitSizeOf(PackedType)) == 0); |
| 3846 | if (offset != 0) |
| 3847 | try expect(diff_bits << @intCast(Log2T, @bitSizeOf(BackingType) - offset) == 0); |
| 3848 | } |
| 3849 | } |
| 3850 | } |
| 3851 | } |
| 3852 | } |
| 3853 | } |
| 3854 | } |
| 3855 | } |