| author | |
| committer | |
| log | 736b9dcdd60b83c07bfbdb69c62facfbe10611f1 |
| tree | 7cf77d45cd80adb7c56adb74a9f15e12727e2a0f |
| parent | ab43c045edc16d90679810d0384096c859f51a9f |
| parent | 7edf3d9f2d53f5b1c5e31ee1294ff9b52337760b |
| signature |
stage2 sema: Fix sign handling of exotic integers in `@bitCast`5 files changed, 402 insertions(+), 55 deletions(-)
lib/std/math/big/int.zig+138-37| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | const std = @import("../../std.zig"); | 1 | const std = @import("../../std.zig"); |
| 2 | const builtin = @import("builtin"); | ||
| 2 | const math = std.math; | 3 | const math = std.math; |
| 3 | const Limb = std.math.big.Limb; | 4 | const Limb = std.math.big.Limb; |
| 4 | const limb_bits = @typeInfo(Limb).Int.bits; | 5 | const limb_bits = @typeInfo(Limb).Int.bits; |
| ... | @@ -14,6 +15,7 @@ const minInt = std.math.minInt; | ... | @@ -14,6 +15,7 @@ const minInt = std.math.minInt; |
| 14 | const assert = std.debug.assert; | 15 | const assert = std.debug.assert; |
| 15 | const Endian = std.builtin.Endian; | 16 | const Endian = std.builtin.Endian; |
| 16 | const Signedness = std.builtin.Signedness; | 17 | const Signedness = std.builtin.Signedness; |
| 18 | const native_endian = builtin.cpu.arch.endian(); | ||
| 17 | 19 | ||
| 18 | const debug_safety = false; | 20 | const debug_safety = false; |
| 19 | 21 | ||
| ... | @@ -1621,10 +1623,17 @@ pub const Mutable = struct { | ... | @@ -1621,10 +1623,17 @@ pub const Mutable = struct { |
| 1621 | } | 1623 | } |
| 1622 | } | 1624 | } |
| 1623 | 1625 | ||
| 1626 | /// Read the value of `x` from `buffer` | ||
| 1627 | /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value. | ||
| 1628 | /// | ||
| 1629 | /// The contents of `buffer` are interpreted as if they were the contents of | ||
| 1630 | /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian` | ||
| 1631 | /// and any required padding bits are expected on the MSB end. | ||
| 1624 | pub fn readTwosComplement( | 1632 | pub fn readTwosComplement( |
| 1625 | x: *Mutable, | 1633 | x: *Mutable, |
| 1626 | buffer: []const u8, | 1634 | buffer: []const u8, |
| 1627 | bit_count: usize, | 1635 | bit_count: usize, |
| 1636 | abi_size: usize, | ||
| 1628 | endian: Endian, | 1637 | endian: Endian, |
| 1629 | signedness: Signedness, | 1638 | signedness: Signedness, |
| 1630 | ) void { | 1639 | ) void { |
| ... | @@ -1634,26 +1643,77 @@ pub const Mutable = struct { | ... | @@ -1634,26 +1643,77 @@ pub const Mutable = struct { |
| 1634 | x.positive = true; | 1643 | x.positive = true; |
| 1635 | return; | 1644 | return; |
| 1636 | } | 1645 | } |
| 1637 | // zig fmt: off | 1646 | |
| 1638 | switch (signedness) { | 1647 | // byte_count is our total read size: it cannot exceed abi_size, |
| 1639 | .signed => { | 1648 | // but may be less as long as it includes the required bits |
| 1640 | if (bit_count <= 8) return x.set(mem.readInt( i8, buffer[0.. 1], endian)); | 1649 | const limb_count = calcTwosCompLimbCount(bit_count); |
| 1641 | if (bit_count <= 16) return x.set(mem.readInt( i16, buffer[0.. 2], endian)); | 1650 | const byte_count = std.math.min(abi_size, @sizeOf(Limb) * limb_count); |
| 1642 | if (bit_count <= 32) return x.set(mem.readInt( i32, buffer[0.. 4], endian)); | 1651 | assert(8 * byte_count >= bit_count); |
| 1643 | if (bit_count <= 64) return x.set(mem.readInt( i64, buffer[0.. 8], endian)); | 1652 | |
| 1644 | if (bit_count <= 128) return x.set(mem.readInt(i128, buffer[0..16], endian)); | 1653 | // Check whether the input is negative |
| 1645 | }, | 1654 | var positive = true; |
| 1646 | .unsigned => { | 1655 | if (signedness == .signed) { |
| 1647 | if (bit_count <= 8) return x.set(mem.readInt( u8, buffer[0.. 1], endian)); | 1656 | var last_byte = switch (endian) { |
| 1648 | if (bit_count <= 16) return x.set(mem.readInt( u16, buffer[0.. 2], endian)); | 1657 | .Little => ((bit_count + 7) / 8) - 1, |
| 1649 | if (bit_count <= 32) return x.set(mem.readInt( u32, buffer[0.. 4], endian)); | 1658 | .Big => abi_size - ((bit_count + 7) / 8), |
| 1650 | if (bit_count <= 64) return x.set(mem.readInt( u64, buffer[0.. 8], endian)); | 1659 | }; |
| 1651 | if (bit_count <= 128) return x.set(mem.readInt(u128, buffer[0..16], endian)); | 1660 | |
| 1652 | }, | 1661 | const sign_bit = @as(u8, 1) << @intCast(u3, (bit_count - 1) % 8); |
| 1662 | positive = ((buffer[last_byte] & sign_bit) == 0); | ||
| 1663 | } | ||
| 1664 | |||
| 1665 | // Copy all complete limbs | ||
| 1666 | var carry: u1 = if (positive) 0 else 1; | ||
| 1667 | var limb_index: usize = 0; | ||
| 1668 | while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) { | ||
| 1669 | var buf_index = switch (endian) { | ||
| 1670 | .Little => @sizeOf(Limb) * limb_index, | ||
| 1671 | .Big => abi_size - (limb_index + 1) * @sizeOf(Limb), | ||
| 1672 | }; | ||
| 1673 | |||
| 1674 | const limb_buf = @ptrCast(*const [@sizeOf(Limb)]u8, buffer[buf_index..]); | ||
| 1675 | var limb = mem.readInt(Limb, limb_buf, endian); | ||
| 1676 | |||
| 1677 | // 2's complement (bitwise not, then add carry bit) | ||
| 1678 | if (!positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb)); | ||
| 1679 | x.limbs[limb_index] = limb; | ||
| 1653 | } | 1680 | } |
| 1654 | // zig fmt: on | ||
| 1655 | 1681 | ||
| 1656 | @panic("TODO implement std lib big int readTwosComplement"); | 1682 | // Copy the remaining N bytes (N <= @sizeOf(Limb)) |
| 1683 | var bytes_read = limb_index * @sizeOf(Limb); | ||
| 1684 | if (bytes_read != byte_count) { | ||
| 1685 | var limb: Limb = 0; | ||
| 1686 | |||
| 1687 | while (bytes_read != byte_count) { | ||
| 1688 | const read_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_read); | ||
| 1689 | var int_buffer = switch (endian) { | ||
| 1690 | .Little => buffer[bytes_read..], | ||
| 1691 | .Big => buffer[(abi_size - bytes_read - read_size)..], | ||
| 1692 | }; | ||
| 1693 | limb |= @intCast(Limb, switch (read_size) { | ||
| 1694 | 1 => mem.readInt(u8, int_buffer[0..1], endian), | ||
| 1695 | 2 => mem.readInt(u16, int_buffer[0..2], endian), | ||
| 1696 | 4 => mem.readInt(u32, int_buffer[0..4], endian), | ||
| 1697 | 8 => mem.readInt(u64, int_buffer[0..8], endian), | ||
| 1698 | 16 => mem.readInt(u128, int_buffer[0..16], endian), | ||
| 1699 | else => unreachable, | ||
| 1700 | }) << @intCast(Log2Limb, 8 * (bytes_read % @sizeOf(Limb))); | ||
| 1701 | bytes_read += read_size; | ||
| 1702 | } | ||
| 1703 | |||
| 1704 | // 2's complement (bitwise not, then add carry bit) | ||
| 1705 | if (!positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb); | ||
| 1706 | |||
| 1707 | // Mask off any unused bits | ||
| 1708 | const valid_bits = @intCast(Log2Limb, bit_count % @bitSizeOf(Limb)); | ||
| 1709 | const mask = (@as(Limb, 1) << valid_bits) -% 1; // 0b0..01..1 with (valid_bits_in_limb) trailing ones | ||
| 1710 | limb &= mask; | ||
| 1711 | |||
| 1712 | x.limbs[limb_count - 1] = limb; | ||
| 1713 | } | ||
| 1714 | x.positive = positive; | ||
| 1715 | x.len = limb_count; | ||
| 1716 | x.normalize(x.len); | ||
| 1657 | } | 1717 | } |
| 1658 | 1718 | ||
| 1659 | /// Normalize a possible sequence of leading zeros. | 1719 | /// Normalize a possible sequence of leading zeros. |
| ... | @@ -1806,7 +1866,7 @@ pub const Const = struct { | ... | @@ -1806,7 +1866,7 @@ pub const Const = struct { |
| 1806 | .Int => |info| { | 1866 | .Int => |info| { |
| 1807 | const UT = std.meta.Int(.unsigned, info.bits); | 1867 | const UT = std.meta.Int(.unsigned, info.bits); |
| 1808 | 1868 | ||
| 1809 | if (self.bitCountTwosComp() > info.bits) { | 1869 | if (!self.fitsInTwosComp(info.signedness, info.bits)) { |
| 1810 | return error.TargetTooSmall; | 1870 | return error.TargetTooSmall; |
| 1811 | } | 1871 | } |
| 1812 | 1872 | ||
| ... | @@ -2013,27 +2073,68 @@ pub const Const = struct { | ... | @@ -2013,27 +2073,68 @@ pub const Const = struct { |
| 2013 | return s.len; | 2073 | return s.len; |
| 2014 | } | 2074 | } |
| 2015 | 2075 | ||
| 2016 | /// Asserts that `buffer` and `bit_count` are large enough to store the value. | 2076 | /// Write the value of `x` into `buffer` |
| 2017 | pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, endian: Endian) void { | 2077 | /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value. |
| 2018 | if (bit_count == 0) return; | 2078 | /// |
| 2079 | /// `buffer` is filled so that its contents match what would be observed via | ||
| 2080 | /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian`, | ||
| 2081 | /// and any required padding bits are added on the MSB end. | ||
| 2082 | pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, abi_size: usize, endian: Endian) void { | ||
| 2083 | |||
| 2084 | // byte_count is our total write size | ||
| 2085 | const byte_count = abi_size; | ||
| 2086 | assert(8 * byte_count >= bit_count); | ||
| 2087 | assert(buffer.len >= byte_count); | ||
| 2088 | assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count)); | ||
| 2019 | 2089 | ||
| 2020 | // zig fmt: off | 2090 | // Copy all complete limbs |
| 2021 | if (x.positive) { | 2091 | var carry: u1 = if (x.positive) 0 else 1; |
| 2022 | if (bit_count <= 8) return mem.writeInt( u8, buffer[0.. 1], x.to( u8) catch unreachable, endian); | 2092 | var limb_index: usize = 0; |
| 2023 | if (bit_count <= 16) return mem.writeInt( u16, buffer[0.. 2], x.to( u16) catch unreachable, endian); | 2093 | while (limb_index < byte_count / @sizeOf(Limb)) : (limb_index += 1) { |
| 2024 | if (bit_count <= 32) return mem.writeInt( u32, buffer[0.. 4], x.to( u32) catch unreachable, endian); | 2094 | var buf_index = switch (endian) { |
| 2025 | if (bit_count <= 64) return mem.writeInt( u64, buffer[0.. 8], x.to( u64) catch unreachable, endian); | 2095 | .Little => @sizeOf(Limb) * limb_index, |
| 2026 | if (bit_count <= 128) return mem.writeInt(u128, buffer[0..16], x.to(u128) catch unreachable, endian); | 2096 | .Big => abi_size - (limb_index + 1) * @sizeOf(Limb), |
| 2027 | } else { | 2097 | }; |
| 2028 | if (bit_count <= 8) return mem.writeInt( i8, buffer[0.. 1], x.to( i8) catch unreachable, endian); | 2098 | |
| 2029 | if (bit_count <= 16) return mem.writeInt( i16, buffer[0.. 2], x.to( i16) catch unreachable, endian); | 2099 | var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; |
| 2030 | if (bit_count <= 32) return mem.writeInt( i32, buffer[0.. 4], x.to( i32) catch unreachable, endian); | 2100 | // 2's complement (bitwise not, then add carry bit) |
| 2031 | if (bit_count <= 64) return mem.writeInt( i64, buffer[0.. 8], x.to( i64) catch unreachable, endian); | 2101 | if (!x.positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb)); |
| 2032 | if (bit_count <= 128) return mem.writeInt(i128, buffer[0..16], x.to(i128) catch unreachable, endian); | 2102 | |
| 2103 | var limb_buf = @ptrCast(*[@sizeOf(Limb)]u8, buffer[buf_index..]); | ||
| 2104 | mem.writeInt(Limb, limb_buf, limb, endian); | ||
| 2033 | } | 2105 | } |
| 2034 | // zig fmt: on | ||
| 2035 | 2106 | ||
| 2036 | @panic("TODO implement std lib big int writeTwosComplement for larger than 128 bits"); | 2107 | // Copy the remaining N bytes (N < @sizeOf(Limb)) |
| 2108 | var bytes_written = limb_index * @sizeOf(Limb); | ||
| 2109 | if (bytes_written != byte_count) { | ||
| 2110 | var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; | ||
| 2111 | // 2's complement (bitwise not, then add carry bit) | ||
| 2112 | if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb); | ||
| 2113 | |||
| 2114 | while (bytes_written != byte_count) { | ||
| 2115 | const write_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_written); | ||
| 2116 | var int_buffer = switch (endian) { | ||
| 2117 | .Little => buffer[bytes_written..], | ||
| 2118 | .Big => buffer[(abi_size - bytes_written - write_size)..], | ||
| 2119 | }; | ||
| 2120 | |||
| 2121 | if (write_size == 1) { | ||
| 2122 | mem.writeInt(u8, int_buffer[0..1], @truncate(u8, limb), endian); | ||
| 2123 | } else if (@sizeOf(Limb) >= 2 and write_size == 2) { | ||
| 2124 | mem.writeInt(u16, int_buffer[0..2], @truncate(u16, limb), endian); | ||
| 2125 | } else if (@sizeOf(Limb) >= 4 and write_size == 4) { | ||
| 2126 | mem.writeInt(u32, int_buffer[0..4], @truncate(u32, limb), endian); | ||
| 2127 | } else if (@sizeOf(Limb) >= 8 and write_size == 8) { | ||
| 2128 | mem.writeInt(u64, int_buffer[0..8], @truncate(u64, limb), endian); | ||
| 2129 | } else if (@sizeOf(Limb) >= 16 and write_size == 16) { | ||
| 2130 | mem.writeInt(u128, int_buffer[0..16], @truncate(u128, limb), endian); | ||
| 2131 | } else if (@sizeOf(Limb) >= 32) { | ||
| 2132 | @compileError("@sizeOf(Limb) exceeded supported range"); | ||
| 2133 | } else unreachable; | ||
| 2134 | limb >>= @intCast(Log2Limb, 8 * write_size); | ||
| 2135 | bytes_written += write_size; | ||
| 2136 | } | ||
| 2137 | } | ||
| 2037 | } | 2138 | } |
| 2038 | 2139 | ||
| 2039 | /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if | 2140 | /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if |
lib/std/math/big/int_test.zig+203| ... | @@ -2486,3 +2486,206 @@ test "big int popcount" { | ... | @@ -2486,3 +2486,206 @@ test "big int popcount" { |
| 2486 | 2486 | ||
| 2487 | try testing.expect(a.toConst().orderAgainstScalar(16) == .eq); | 2487 | try testing.expect(a.toConst().orderAgainstScalar(16) == .eq); |
| 2488 | } | 2488 | } |
| 2489 | |||
| 2490 | test "big int conversion read/write twos complement" { | ||
| 2491 | var a = try Managed.initSet(testing.allocator, (1 << 493) - 1); | ||
| 2492 | defer a.deinit(); | ||
| 2493 | var b = try Managed.initSet(testing.allocator, (1 << 493) - 1); | ||
| 2494 | defer b.deinit(); | ||
| 2495 | var m = b.toMutable(); | ||
| 2496 | |||
| 2497 | var buffer1 = try testing.allocator.alloc(u8, 64); | ||
| 2498 | defer testing.allocator.free(buffer1); | ||
| 2499 | |||
| 2500 | const endians = [_]std.builtin.Endian{ .Little, .Big }; | ||
| 2501 | const abi_size = 64; | ||
| 2502 | |||
| 2503 | for (endians) |endian| { | ||
| 2504 | // Writing to buffer and back should not change anything | ||
| 2505 | a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian); | ||
| 2506 | m.readTwosComplement(buffer1, 493, abi_size, endian, .unsigned); | ||
| 2507 | try testing.expect(m.toConst().order(a.toConst()) == .eq); | ||
| 2508 | |||
| 2509 | // Equivalent to @bitCast(i493, @as(u493, intMax(u493)) | ||
| 2510 | a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian); | ||
| 2511 | m.readTwosComplement(buffer1, 493, abi_size, endian, .signed); | ||
| 2512 | try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq); | ||
| 2513 | } | ||
| 2514 | } | ||
| 2515 | |||
| 2516 | test "big int conversion read twos complement with padding" { | ||
| 2517 | var a = try Managed.initSet(testing.allocator, 0x01_02030405_06070809_0a0b0c0d); | ||
| 2518 | defer a.deinit(); | ||
| 2519 | |||
| 2520 | var buffer1 = try testing.allocator.alloc(u8, 16); | ||
| 2521 | defer testing.allocator.free(buffer1); | ||
| 2522 | @memset(buffer1.ptr, 0xaa, buffer1.len); | ||
| 2523 | |||
| 2524 | // writeTwosComplement: | ||
| 2525 | // (1) should not write beyond buffer[0..abi_size] | ||
| 2526 | // (2) should correctly order bytes based on the provided endianness | ||
| 2527 | // (3) should sign-extend any bits from bit_count to 8 * abi_size | ||
| 2528 | |||
| 2529 | var bit_count: usize = 12 * 8 + 1; | ||
| 2530 | a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); | ||
| 2531 | 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 })); | ||
| 2532 | a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); | ||
| 2533 | 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 })); | ||
| 2534 | a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); | ||
| 2535 | 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 })); | ||
| 2536 | a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); | ||
| 2537 | 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 })); | ||
| 2538 | |||
| 2539 | @memset(buffer1.ptr, 0xaa, buffer1.len); | ||
| 2540 | try a.set(-0x01_02030405_06070809_0a0b0c0d); | ||
| 2541 | bit_count = 12 * 8 + 2; | ||
| 2542 | |||
| 2543 | a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); | ||
| 2544 | 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 })); | ||
| 2545 | a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); | ||
| 2546 | 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 })); | ||
| 2547 | a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); | ||
| 2548 | 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 })); | ||
| 2549 | a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); | ||
| 2550 | 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 })); | ||
| 2551 | } | ||
| 2552 | |||
| 2553 | test "big int write twos complement +/- zero" { | ||
| 2554 | var a = try Managed.initSet(testing.allocator, 0x0); | ||
| 2555 | defer a.deinit(); | ||
| 2556 | var m = a.toMutable(); | ||
| 2557 | |||
| 2558 | var buffer1 = try testing.allocator.alloc(u8, 16); | ||
| 2559 | defer testing.allocator.free(buffer1); | ||
| 2560 | @memset(buffer1.ptr, 0xaa, buffer1.len); | ||
| 2561 | |||
| 2562 | var bit_count: usize = 0; | ||
| 2563 | |||
| 2564 | // Test zero | ||
| 2565 | |||
| 2566 | m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); | ||
| 2567 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); | ||
| 2568 | m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); | ||
| 2569 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); | ||
| 2570 | m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); | ||
| 2571 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); | ||
| 2572 | m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); | ||
| 2573 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); | ||
| 2574 | |||
| 2575 | @memset(buffer1.ptr, 0xaa, buffer1.len); | ||
| 2576 | m.positive = false; | ||
| 2577 | |||
| 2578 | // Test negative zero | ||
| 2579 | |||
| 2580 | m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); | ||
| 2581 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); | ||
| 2582 | m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); | ||
| 2583 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); | ||
| 2584 | m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); | ||
| 2585 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); | ||
| 2586 | m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); | ||
| 2587 | try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); | ||
| 2588 | } | ||
| 2589 | |||
| 2590 | test "big int conversion write twos complement with padding" { | ||
| 2591 | var a = try Managed.initSet(testing.allocator, 0x01_ffffffff_ffffffff_ffffffff); | ||
| 2592 | defer a.deinit(); | ||
| 2593 | |||
| 2594 | var m = a.toMutable(); | ||
| 2595 | |||
| 2596 | // readTwosComplement: | ||
| 2597 | // (1) should not read beyond buffer[0..abi_size] | ||
| 2598 | // (2) should correctly interpret bytes based on the provided endianness | ||
| 2599 | // (3) should ignore any bits from bit_count to 8 * abi_size | ||
| 2600 | |||
| 2601 | var bit_count: usize = 12 * 8 + 1; | ||
| 2602 | var buffer: []const u8 = undefined; | ||
| 2603 | |||
| 2604 | // Test 0x01_02030405_06070809_0a0b0c0d | ||
| 2605 | |||
| 2606 | buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xb }; | ||
| 2607 | m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); | ||
| 2608 | try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2609 | |||
| 2610 | buffer = &[_]u8{ 0xb, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd }; | ||
| 2611 | m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); | ||
| 2612 | try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2613 | |||
| 2614 | buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xab, 0xaa, 0xaa, 0xaa }; | ||
| 2615 | m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); | ||
| 2616 | try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2617 | |||
| 2618 | buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0xab, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd }; | ||
| 2619 | m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); | ||
| 2620 | try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2621 | |||
| 2622 | bit_count = 12 * 8 + 2; | ||
| 2623 | |||
| 2624 | // Test -0x01_02030405_06070809_0a0b0c0d | ||
| 2625 | |||
| 2626 | buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02 }; | ||
| 2627 | m.readTwosComplement(buffer, bit_count, 13, .Little, .signed); | ||
| 2628 | try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2629 | |||
| 2630 | buffer = &[_]u8{ 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 }; | ||
| 2631 | m.readTwosComplement(buffer, bit_count, 13, .Big, .signed); | ||
| 2632 | try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2633 | |||
| 2634 | buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02, 0xaa, 0xaa, 0xaa }; | ||
| 2635 | m.readTwosComplement(buffer, bit_count, 16, .Little, .signed); | ||
| 2636 | try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2637 | |||
| 2638 | buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 }; | ||
| 2639 | m.readTwosComplement(buffer, bit_count, 16, .Big, .signed); | ||
| 2640 | try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); | ||
| 2641 | |||
| 2642 | // Test 0 | ||
| 2643 | |||
| 2644 | buffer = &([_]u8{0} ** 16); | ||
| 2645 | m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); | ||
| 2646 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2647 | m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); | ||
| 2648 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2649 | m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); | ||
| 2650 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2651 | m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); | ||
| 2652 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2653 | |||
| 2654 | bit_count = 0; | ||
| 2655 | buffer = &([_]u8{0xaa} ** 16); | ||
| 2656 | m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); | ||
| 2657 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2658 | m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); | ||
| 2659 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2660 | m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); | ||
| 2661 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2662 | m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); | ||
| 2663 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2664 | } | ||
| 2665 | |||
| 2666 | test "big int conversion write twos complement zero" { | ||
| 2667 | var a = try Managed.initSet(testing.allocator, 0x01_ffffffff_ffffffff_ffffffff); | ||
| 2668 | defer a.deinit(); | ||
| 2669 | |||
| 2670 | var m = a.toMutable(); | ||
| 2671 | |||
| 2672 | // readTwosComplement: | ||
| 2673 | // (1) should not read beyond buffer[0..abi_size] | ||
| 2674 | // (2) should correctly interpret bytes based on the provided endianness | ||
| 2675 | // (3) should ignore any bits from bit_count to 8 * abi_size | ||
| 2676 | |||
| 2677 | var bit_count: usize = 12 * 8 + 1; | ||
| 2678 | var buffer: []const u8 = undefined; | ||
| 2679 | |||
| 2680 | buffer = &([_]u8{0} ** 13); | ||
| 2681 | m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); | ||
| 2682 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2683 | m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); | ||
| 2684 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2685 | |||
| 2686 | buffer = &([_]u8{0} ** 16); | ||
| 2687 | m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); | ||
| 2688 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2689 | m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); | ||
| 2690 | try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); | ||
| 2691 | } |
src/stage1/bigint.cpp+1-1| ... | @@ -313,12 +313,12 @@ void bigint_write_twos_complement(const BigInt *big_int, uint8_t *buf, size_t bi | ... | @@ -313,12 +313,12 @@ void bigint_write_twos_complement(const BigInt *big_int, uint8_t *buf, size_t bi |
| 313 | } | 313 | } |
| 314 | 314 | ||
| 315 | if (digit_index == 0) break; | 315 | if (digit_index == 0) break; |
| 316 | digit_index -= 1; | ||
| 317 | if (digit_index == last_digit_index) { | 316 | if (digit_index == last_digit_index) { |
| 318 | buf_index += bytes_in_last_digit; | 317 | buf_index += bytes_in_last_digit; |
| 319 | } else { | 318 | } else { |
| 320 | buf_index += 8; | 319 | buf_index += 8; |
| 321 | } | 320 | } |
| 321 | digit_index -= 1; | ||
| 322 | } | 322 | } |
| 323 | } else { | 323 | } else { |
| 324 | size_t digit_count = (bit_count + 63) / 64; | 324 | size_t digit_count = (bit_count + 63) / 64; |
src/value.zig+9-5| ... | @@ -1046,7 +1046,8 @@ pub const Value = extern union { | ... | @@ -1046,7 +1046,8 @@ pub const Value = extern union { |
| 1046 | var bigint_buffer: BigIntSpace = undefined; | 1046 | var bigint_buffer: BigIntSpace = undefined; |
| 1047 | const bigint = val.toBigInt(&bigint_buffer); | 1047 | const bigint = val.toBigInt(&bigint_buffer); |
| 1048 | const bits = ty.intInfo(target).bits; | 1048 | const bits = ty.intInfo(target).bits; |
| 1049 | bigint.writeTwosComplement(buffer, bits, target.cpu.arch.endian()); | 1049 | const abi_size = @intCast(usize, ty.abiSize(target)); |
| 1050 | bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian()); | ||
| 1050 | }, | 1051 | }, |
| 1051 | .Enum => { | 1052 | .Enum => { |
| 1052 | var enum_buffer: Payload.U64 = undefined; | 1053 | var enum_buffer: Payload.U64 = undefined; |
| ... | @@ -1054,7 +1055,8 @@ pub const Value = extern union { | ... | @@ -1054,7 +1055,8 @@ pub const Value = extern union { |
| 1054 | var bigint_buffer: BigIntSpace = undefined; | 1055 | var bigint_buffer: BigIntSpace = undefined; |
| 1055 | const bigint = int_val.toBigInt(&bigint_buffer); | 1056 | const bigint = int_val.toBigInt(&bigint_buffer); |
| 1056 | const bits = ty.intInfo(target).bits; | 1057 | const bits = ty.intInfo(target).bits; |
| 1057 | bigint.writeTwosComplement(buffer, bits, target.cpu.arch.endian()); | 1058 | const abi_size = @intCast(usize, ty.abiSize(target)); |
| 1059 | bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian()); | ||
| 1058 | }, | 1060 | }, |
| 1059 | .Float => switch (ty.floatBits(target)) { | 1061 | .Float => switch (ty.floatBits(target)) { |
| 1060 | 16 => return floatWriteToMemory(f16, val.toFloat(f16), target, buffer), | 1062 | 16 => return floatWriteToMemory(f16, val.toFloat(f16), target, buffer), |
| ... | @@ -1093,10 +1095,12 @@ pub const Value = extern union { | ... | @@ -1093,10 +1095,12 @@ pub const Value = extern union { |
| 1093 | .Int => { | 1095 | .Int => { |
| 1094 | const int_info = ty.intInfo(target); | 1096 | const int_info = ty.intInfo(target); |
| 1095 | const endian = target.cpu.arch.endian(); | 1097 | const endian = target.cpu.arch.endian(); |
| 1096 | // TODO use a correct amount of limbs | 1098 | const Limb = std.math.big.Limb; |
| 1097 | const limbs_buffer = try arena.alloc(std.math.big.Limb, 2); | 1099 | const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb); |
| 1100 | const limbs_buffer = try arena.alloc(Limb, limb_count); | ||
| 1101 | const abi_size = @intCast(usize, ty.abiSize(target)); | ||
| 1098 | var bigint = BigIntMutable.init(limbs_buffer, 0); | 1102 | var bigint = BigIntMutable.init(limbs_buffer, 0); |
| 1099 | bigint.readTwosComplement(buffer, int_info.bits, endian, int_info.signedness); | 1103 | bigint.readTwosComplement(buffer, int_info.bits, abi_size, endian, int_info.signedness); |
| 1100 | return fromBigInt(arena, bigint.toConst()); | 1104 | return fromBigInt(arena, bigint.toConst()); |
| 1101 | }, | 1105 | }, |
| 1102 | .Float => switch (ty.floatBits(target)) { | 1106 | .Float => switch (ty.floatBits(target)) { |
test/behavior/bitcast.zig+51-12| ... | @@ -3,27 +3,66 @@ const builtin = @import("builtin"); | ... | @@ -3,27 +3,66 @@ const builtin = @import("builtin"); |
| 3 | const expect = std.testing.expect; | 3 | const expect = std.testing.expect; |
| 4 | const expectEqual = std.testing.expectEqual; | 4 | const expectEqual = std.testing.expectEqual; |
| 5 | const maxInt = std.math.maxInt; | 5 | const maxInt = std.math.maxInt; |
| 6 | const minInt = std.math.minInt; | ||
| 6 | const native_endian = builtin.target.cpu.arch.endian(); | 7 | const native_endian = builtin.target.cpu.arch.endian(); |
| 7 | 8 | ||
| 8 | test "@bitCast i32 -> u32" { | 9 | test "@bitCast iX -> uX (32, 64)" { |
| 9 | try testBitCast_i32_u32(); | 10 | const bit_values = [_]usize{ 32, 64 }; |
| 10 | comptime try testBitCast_i32_u32(); | 11 | |
| 12 | inline for (bit_values) |bits| { | ||
| 13 | try testBitCast(bits); | ||
| 14 | comptime try testBitCast(bits); | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | test "@bitCast iX -> uX (8, 16, 128)" { | ||
| 19 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | ||
| 20 | const bit_values = [_]usize{ 8, 16, 128 }; | ||
| 21 | |||
| 22 | inline for (bit_values) |bits| { | ||
| 23 | try testBitCast(bits); | ||
| 24 | comptime try testBitCast(bits); | ||
| 25 | } | ||
| 11 | } | 26 | } |
| 12 | 27 | ||
| 13 | fn testBitCast_i32_u32() !void { | 28 | test "@bitCast iX -> uX exotic integers" { |
| 14 | try expect(conv(-1) == maxInt(u32)); | 29 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; |
| 15 | try expect(conv2(maxInt(u32)) == -1); | 30 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| 31 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; | ||
| 32 | |||
| 33 | const bit_values = [_]usize{ 1, 48, 27, 512, 493, 293, 125, 204, 112 }; | ||
| 34 | |||
| 35 | inline for (bit_values) |bits| { | ||
| 36 | try testBitCast(bits); | ||
| 37 | comptime try testBitCast(bits); | ||
| 38 | } | ||
| 16 | } | 39 | } |
| 17 | 40 | ||
| 18 | fn conv(x: i32) u32 { | 41 | fn testBitCast(comptime N: usize) !void { |
| 19 | return @bitCast(u32, x); | 42 | const iN = std.meta.Int(.signed, N); |
| 43 | const uN = std.meta.Int(.unsigned, N); | ||
| 44 | |||
| 45 | try expect(conv_iN(N, -1) == maxInt(uN)); | ||
| 46 | try expect(conv_uN(N, maxInt(uN)) == -1); | ||
| 47 | |||
| 48 | try expect(conv_iN(N, maxInt(iN)) == maxInt(iN)); | ||
| 49 | try expect(conv_uN(N, maxInt(iN)) == maxInt(iN)); | ||
| 50 | |||
| 51 | try expect(conv_uN(N, 1 << (N - 1)) == minInt(iN)); | ||
| 52 | try expect(conv_iN(N, minInt(iN)) == (1 << (N - 1))); | ||
| 53 | |||
| 54 | try expect(conv_uN(N, 0) == 0); | ||
| 55 | try expect(conv_iN(N, 0) == 0); | ||
| 56 | |||
| 57 | try expect(conv_iN(N, -0) == 0); | ||
| 20 | } | 58 | } |
| 21 | fn conv2(x: u32) i32 { | 59 | |
| 22 | return @bitCast(i32, x); | 60 | fn conv_iN(comptime N: usize, x: std.meta.Int(.signed, N)) std.meta.Int(.unsigned, N) { |
| 61 | return @bitCast(std.meta.Int(.unsigned, N), x); | ||
| 23 | } | 62 | } |
| 24 | 63 | ||
| 25 | test "bitcast result to _" { | 64 | fn conv_uN(comptime N: usize, x: std.meta.Int(.unsigned, N)) std.meta.Int(.signed, N) { |
| 26 | _ = @bitCast(u8, @as(i8, 1)); | 65 | return @bitCast(std.meta.Int(.signed, N), x); |
| 27 | } | 66 | } |
| 28 | 67 | ||
| 29 | test "nested bitcast" { | 68 | test "nested bitcast" { |