authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-18 11:33:24-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-28 08:41:04-07:00
logc639c225444c9252515949786e139494fb728861
tree117b6952ad8f19a20b4bbbbca8ece0ec7c519229
parentf28e4e03eeb622d1cfd391cf9f0c7e45f4d80681

std.mem: Add readPackedInt, writePackedInt, etc.

These utility functions allow reading from (stage2) packed memory at runtime-known offsets.

1 files changed, 460 insertions(+), 0 deletions(-)

lib/std/mem.zig+460
...@@ -1298,6 +1298,76 @@ pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: Endian)...@@ -1298,6 +1298,76 @@ pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: Endian)
1298 return result;1298 return result;
1299}1299}
13001300
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///
1309pub 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/// Reads an integer from memory with bit count specified by T.1371/// Reads an integer from memory with bit count specified by T.
1302/// The bit count of T must be evenly divisible by 8.1372/// The bit count of T must be evenly divisible by 8.
1303/// This function cannot fail and cannot cause undefined behavior.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,6 +1435,84 @@ pub fn readInt(comptime T: type, bytes: *const [@divExact(@typeInfo(T).Int.bits,
1365 }1435 }
1366}1436}
13671437
1438fn 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
1464fn 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
1491pub const readPackedIntNative = switch (native_endian) {
1492 .Little => readPackedIntLittle,
1493 .Big => readPackedIntBig,
1494};
1495
1496pub 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///
1509pub 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/// Asserts that bytes.len >= @typeInfo(T).Int.bits / 8. Reads the integer starting from index 01516/// Asserts that bytes.len >= @typeInfo(T).Int.bits / 8. Reads the integer starting from index 0
1369/// and ignores extra bytes.1517/// and ignores extra bytes.
1370/// The bit count of T must be evenly divisible by 8.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,6 +1595,100 @@ pub fn writeInt(comptime T: type, buffer: *[@divExact(@typeInfo(T).Int.bits, 8)]
1447 }1595 }
1448}1596}
14491597
1598pub 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
1631pub 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
1666pub const writePackedIntNative = switch (native_endian) {
1667 .Little => writePackedIntLittle,
1668 .Big => writePackedIntBig,
1669};
1670
1671pub 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///
1685pub 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/// Writes a twos-complement little-endian integer to memory.1692/// Writes a twos-complement little-endian integer to memory.
1451/// Asserts that buf.len >= @typeInfo(T).Int.bits / 8.1693/// Asserts that buf.len >= @typeInfo(T).Int.bits / 8.
1452/// The bit count of T must be divisible by 8.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,6 +1765,69 @@ pub fn writeIntSlice(comptime T: type, buffer: []u8, value: T, endian: Endian) v
1523 };1765 };
1524}1766}
15251767
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///
1778pub 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
1526test "writeIntBig and writeIntLittle" {1831test "writeIntBig and writeIntLittle" {
1527 var buf0: [0]u8 = undefined;1832 var buf0: [0]u8 = undefined;
1528 var buf1: [1]u8 = undefined;1833 var buf1: [1]u8 = undefined;
...@@ -3393,3 +3698,158 @@ pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice...@@ -3393,3 +3698,158 @@ pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice
3393 const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]);3698 const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]);
3394 return @alignCast(new_alignment, aligned_slice);3699 return @alignCast(new_alignment, aligned_slice);
3395}3700}
3701
3702test "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}