authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-02-10 13:29:48-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-02-11 08:49:19-07:00
log70d7f87be00aa1a372c856759948fd62666be295
treed2c2a9256366f9d68074d99e39a93c9b96ddeadc
parente1a535360fb9ed08fc48018571b9702ab12a5876

Fix up sign handling and add arbitrary-length integer support to @bitCast()


4 files changed, 269 insertions(+), 40 deletions(-)

lib/std/math/big/int.zig+138-34
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../../std.zig");1const std = @import("../../std.zig");
2const builtin = @import("builtin");
2const math = std.math;3const math = std.math;
3const Limb = std.math.big.Limb;4const Limb = std.math.big.Limb;
4const limb_bits = @typeInfo(Limb).Int.bits;5const limb_bits = @typeInfo(Limb).Int.bits;
...@@ -14,6 +15,7 @@ const minInt = std.math.minInt;...@@ -14,6 +15,7 @@ const minInt = std.math.minInt;
14const assert = std.debug.assert;15const assert = std.debug.assert;
15const Endian = std.builtin.Endian;16const Endian = std.builtin.Endian;
16const Signedness = std.builtin.Signedness;17const Signedness = std.builtin.Signedness;
18const native_endian = builtin.cpu.arch.endian();
1719
18const debug_safety = false;20const debug_safety = false;
1921
...@@ -1621,6 +1623,15 @@ pub const Mutable = struct {...@@ -1621,6 +1623,15 @@ pub const Mutable = struct {
1621 }1623 }
1622 }1624 }
16231625
1626 /// Read the value of `x` from `buffer`
1627 /// Asserts that `buffer` and `bit_count` are large enough to store the value.
1628 ///
1629 /// For integers with a well-defined layout (e.g. all power-of-two integers), this function
1630 /// reads from `buffer` as if it were the contents of @ptrCast([]const u8, &x), where the
1631 /// slice length is taken to be @sizeOf(std.meta.Int(signedness, <bit_count>))
1632 ///
1633 /// For integers with a non-well-defined layout, `buffer` must have been created by
1634 /// writeTwosComplement.
1624 pub fn readTwosComplement(1635 pub fn readTwosComplement(
1625 x: *Mutable,1636 x: *Mutable,
1626 buffer: []const u8,1637 buffer: []const u8,
...@@ -1634,26 +1645,77 @@ pub const Mutable = struct {...@@ -1634,26 +1645,77 @@ pub const Mutable = struct {
1634 x.positive = true;1645 x.positive = true;
1635 return;1646 return;
1636 }1647 }
1637 // zig fmt: off1648
1638 switch (signedness) {1649 // byte_count is the total amount of bytes to read from buffer
1639 .signed => {1650 var byte_count = @sizeOf(Limb) * (bit_count / @bitSizeOf(Limb));
1640 if (bit_count <= 8) return x.set(mem.readInt( i8, buffer[0.. 1], endian));1651 if (bit_count % @bitSizeOf(Limb) != 0) { // Round up to a power-of-two integer <= Limb
1641 if (bit_count <= 16) return x.set(mem.readInt( i16, buffer[0.. 2], endian));1652 byte_count += (std.math.ceilPowerOfTwoAssert(usize, bit_count % @bitSizeOf(Limb)) + 7) / 8;
1642 if (bit_count <= 32) return x.set(mem.readInt( i32, buffer[0.. 4], endian));1653 }
1643 if (bit_count <= 64) return x.set(mem.readInt( i64, buffer[0.. 8], endian));1654
1644 if (bit_count <= 128) return x.set(mem.readInt(i128, buffer[0..16], endian));1655 const limb_count = calcTwosCompLimbCount(8 * byte_count);
1645 },1656
1646 .unsigned => {1657 // Check whether the input is negative
1647 if (bit_count <= 8) return x.set(mem.readInt( u8, buffer[0.. 1], endian));1658 var positive = true;
1648 if (bit_count <= 16) return x.set(mem.readInt( u16, buffer[0.. 2], endian));1659 if (signedness == .signed) {
1649 if (bit_count <= 32) return x.set(mem.readInt( u32, buffer[0.. 4], endian));1660 var last_byte = switch (endian) {
1650 if (bit_count <= 64) return x.set(mem.readInt( u64, buffer[0.. 8], endian));1661 .Little => ((bit_count + 7) / 8) - 1,
1651 if (bit_count <= 128) return x.set(mem.readInt(u128, buffer[0..16], endian));1662 .Big => byte_count - ((bit_count + 7) / 8),
1652 },1663 };
1664
1665 const sign_bit = @as(u8, 1) << @intCast(u3, (bit_count - 1) % 8);
1666 positive = ((buffer[last_byte] & sign_bit) == 0);
1667 }
1668
1669 // Copy all complete limbs
1670 var carry: u1 = if (positive) 0 else 1;
1671 var limb_index: usize = 0;
1672 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {
1673 var buf_index = switch (endian) {
1674 .Little => @sizeOf(Limb) * limb_index,
1675 .Big => byte_count - (limb_index + 1) * @sizeOf(Limb),
1676 };
1677
1678 const limb_buf = @ptrCast(*const [@sizeOf(Limb)]u8, buffer[buf_index..]);
1679 var limb = mem.readInt(Limb, limb_buf, endian);
1680
1681 // 2's complement (bitwise not, then add carry bit)
1682 if (!positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
1683 x.limbs[limb_index] = limb;
1653 }1684 }
1654 // zig fmt: on
16551685
1656 @panic("TODO implement std lib big int readTwosComplement");1686 // Copy any remaining bytes, using the nearest power-of-two integer that is large enough
1687 const bits_left = @intCast(Log2Limb, bit_count % @bitSizeOf(Limb));
1688 if (bits_left != 0) {
1689 const bytes_read = limb_index * @sizeOf(Limb);
1690 const bytes_left = byte_count - bytes_read;
1691 var buffer_left = switch (endian) {
1692 .Little => buffer[bytes_read..],
1693 .Big => buffer[0..],
1694 };
1695
1696 var limb = @intCast(Limb, blk: {
1697 // zig fmt: off
1698 if (bytes_left == 1) break :blk mem.readInt( u8, buffer_left[0.. 1], endian);
1699 if (bytes_left == 2) break :blk mem.readInt( u16, buffer_left[0.. 2], endian);
1700 if (bytes_left == 4) break :blk mem.readInt( u32, buffer_left[0.. 4], endian);
1701 if (bytes_left == 8) break :blk mem.readInt( u64, buffer_left[0.. 8], endian);
1702 if (bytes_left == 16) break :blk mem.readInt(u128, buffer_left[0..16], endian);
1703 // zig fmt: on
1704 unreachable;
1705 });
1706
1707 // 2's complement (bitwise not, then add carry bit)
1708 if (!positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);
1709
1710 // Mask off any unused bits
1711 const mask = (@as(Limb, 1) << bits_left) -% 1; // 0b0..01..1 with (bits_left) trailing ones
1712 limb &= mask;
1713
1714 x.limbs[limb_count - 1] = limb;
1715 }
1716 x.positive = positive;
1717 x.len = limb_count;
1718 x.normalize(x.len);
1657 }1719 }
16581720
1659 /// Normalize a possible sequence of leading zeros.1721 /// Normalize a possible sequence of leading zeros.
...@@ -1806,7 +1868,7 @@ pub const Const = struct {...@@ -1806,7 +1868,7 @@ pub const Const = struct {
1806 .Int => |info| {1868 .Int => |info| {
1807 const UT = std.meta.Int(.unsigned, info.bits);1869 const UT = std.meta.Int(.unsigned, info.bits);
18081870
1809 if (self.bitCountTwosComp() > info.bits) {1871 if (!self.fitsInTwosComp(info.signedness, info.bits)) {
1810 return error.TargetTooSmall;1872 return error.TargetTooSmall;
1811 }1873 }
18121874
...@@ -2013,27 +2075,69 @@ pub const Const = struct {...@@ -2013,27 +2075,69 @@ pub const Const = struct {
2013 return s.len;2075 return s.len;
2014 }2076 }
20152077
2078 /// Write the value of `x` into `buffer`
2016 /// Asserts that `buffer` and `bit_count` are large enough to store the value.2079 /// Asserts that `buffer` and `bit_count` are large enough to store the value.
2080 ///
2081 /// For integers with a well-defined layout (e.g. all power-of-two integers), this function
2082 /// can be thought of as writing to `buffer` the contents of @ptrCast([]const u8, &x),
2083 /// where the slice length is taken to be @sizeOf(std.meta.Int(_,<bit_count>))
2084 ///
2085 /// For integers with a non-well-defined layout, the only requirement is that readTwosComplement
2086 /// on the same buffer creates an equivalent big integer.
2017 pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, endian: Endian) void {2087 pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, endian: Endian) void {
2018 if (bit_count == 0) return;2088 if (bit_count == 0) return;
20192089
2020 // zig fmt: off2090 var byte_count = @sizeOf(Limb) * (bit_count / @bitSizeOf(Limb));
2021 if (x.positive) {2091 if (bit_count % @bitSizeOf(Limb) != 0) {
2022 if (bit_count <= 8) return mem.writeInt( u8, buffer[0.. 1], x.to( u8) catch unreachable, endian);2092 byte_count += (std.math.ceilPowerOfTwoAssert(usize, bit_count % @bitSizeOf(Limb)) + 7) / 8;
2023 if (bit_count <= 16) return mem.writeInt( u16, buffer[0.. 2], x.to( u16) catch unreachable, endian);
2024 if (bit_count <= 32) return mem.writeInt( u32, buffer[0.. 4], x.to( u32) catch unreachable, endian);
2025 if (bit_count <= 64) return mem.writeInt( u64, buffer[0.. 8], x.to( u64) catch unreachable, endian);
2026 if (bit_count <= 128) return mem.writeInt(u128, buffer[0..16], x.to(u128) catch unreachable, endian);
2027 } else {
2028 if (bit_count <= 8) return mem.writeInt( i8, buffer[0.. 1], x.to( i8) catch unreachable, endian);
2029 if (bit_count <= 16) return mem.writeInt( i16, buffer[0.. 2], x.to( i16) catch unreachable, endian);
2030 if (bit_count <= 32) return mem.writeInt( i32, buffer[0.. 4], x.to( i32) catch unreachable, endian);
2031 if (bit_count <= 64) return mem.writeInt( i64, buffer[0.. 8], x.to( i64) catch unreachable, endian);
2032 if (bit_count <= 128) return mem.writeInt(i128, buffer[0..16], x.to(i128) catch unreachable, endian);
2033 }2093 }
2034 // zig fmt: on2094 assert(buffer.len >= byte_count);
2095 assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count));
2096
2097 // Copy all complete limbs
2098 var carry: u1 = if (x.positive) 0 else 1;
2099 var limb_index: usize = 0;
2100 while (limb_index < byte_count / @sizeOf(Limb)) : (limb_index += 1) {
2101 var buf_index = switch (endian) {
2102 .Little => @sizeOf(Limb) * limb_index,
2103 .Big => byte_count - (limb_index + 1) * @sizeOf(Limb),
2104 };
20352105
2036 @panic("TODO implement std lib big int writeTwosComplement for larger than 128 bits");2106 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2107 // 2's complement (bitwise not, then add carry bit)
2108 if (!x.positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
2109
2110 var limb_buf = @ptrCast(*[@sizeOf(Limb)]u8, buffer[buf_index..]);
2111 mem.writeInt(Limb, limb_buf, limb, endian);
2112 }
2113
2114 // Copy any remaining bytes
2115 if (byte_count % @sizeOf(Limb) != 0) {
2116 const bytes_read = limb_index * @sizeOf(Limb);
2117 const bytes_left = byte_count - bytes_read;
2118 var buffer_left = switch (endian) {
2119 .Little => buffer[bytes_read..],
2120 .Big => buffer[0..],
2121 };
2122
2123 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2124 // 2's complement (bitwise not, then add carry bit)
2125 if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);
2126
2127 if (bytes_left == 1) {
2128 mem.writeInt(u8, buffer_left[0..1], @truncate(u8, limb), endian);
2129 } else if (@sizeOf(Limb) > 1 and bytes_left == 2) {
2130 mem.writeInt(u16, buffer_left[0..2], @truncate(u16, limb), endian);
2131 } else if (@sizeOf(Limb) > 2 and bytes_left == 4) {
2132 mem.writeInt(u32, buffer_left[0..4], @truncate(u32, limb), endian);
2133 } else if (@sizeOf(Limb) > 4 and bytes_left == 8) {
2134 mem.writeInt(u64, buffer_left[0..8], @truncate(u64, limb), endian);
2135 } else if (@sizeOf(Limb) > 8 and bytes_left == 16) {
2136 mem.writeInt(u128, buffer_left[0..16], @truncate(u128, limb), endian);
2137 } else if (@sizeOf(Limb) > 16) {
2138 @compileError("@sizeOf(Limb) exceeded supported range");
2139 } else unreachable;
2140 }
2037 }2141 }
20382142
2039 /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if2143 /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if
lib/std/math/big/int_test.zig+25
...@@ -2486,3 +2486,28 @@ test "big int popcount" {...@@ -2486,3 +2486,28 @@ test "big int popcount" {
24862486
2487 try testing.expect(a.toConst().orderAgainstScalar(16) == .eq);2487 try testing.expect(a.toConst().orderAgainstScalar(16) == .eq);
2488}2488}
2489
2490test "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
2502 for (endians) |endian| {
2503 // Writing to buffer and back should not change anything
2504 a.toConst().writeTwosComplement(buffer1, 493, endian);
2505 m.readTwosComplement(buffer1, 493, endian, .unsigned);
2506 try testing.expect(m.toConst().order(a.toConst()) == .eq);
2507
2508 // Equivalent to @bitCast(i493, @as(u493, intMax(u493))
2509 a.toConst().writeTwosComplement(buffer1, 493, endian);
2510 m.readTwosComplement(buffer1, 493, endian, .signed);
2511 try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq);
2512 }
2513}
src/value.zig+3-2
...@@ -1093,8 +1093,9 @@ pub const Value = extern union {...@@ -1093,8 +1093,9 @@ pub const Value = extern union {
1093 .Int => {1093 .Int => {
1094 const int_info = ty.intInfo(target);1094 const int_info = ty.intInfo(target);
1095 const endian = target.cpu.arch.endian();1095 const endian = target.cpu.arch.endian();
1096 // TODO use a correct amount of limbs1096 const Limb = std.math.big.Limb;
1097 const limbs_buffer = try arena.alloc(std.math.big.Limb, 2);1097 const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1098 const limbs_buffer = try arena.alloc(Limb, limb_count);
1098 var bigint = BigIntMutable.init(limbs_buffer, 0);1099 var bigint = BigIntMutable.init(limbs_buffer, 0);
1099 bigint.readTwosComplement(buffer, int_info.bits, endian, int_info.signedness);1100 bigint.readTwosComplement(buffer, int_info.bits, endian, int_info.signedness);
1100 return fromBigInt(arena, bigint.toConst());1101 return fromBigInt(arena, bigint.toConst());
test/behavior/bitcast.zig+103-4
...@@ -3,6 +3,7 @@ const builtin = @import("builtin");...@@ -3,6 +3,7 @@ const builtin = @import("builtin");
3const expect = std.testing.expect;3const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;4const expectEqual = std.testing.expectEqual;
5const maxInt = std.math.maxInt;5const maxInt = std.math.maxInt;
6const minInt = std.math.minInt;
6const native_endian = builtin.target.cpu.arch.endian();7const native_endian = builtin.target.cpu.arch.endian();
78
8test "@bitCast i32 -> u32" {9test "@bitCast i32 -> u32" {
...@@ -11,21 +12,119 @@ test "@bitCast i32 -> u32" {...@@ -11,21 +12,119 @@ test "@bitCast i32 -> u32" {
11}12}
1213
13fn testBitCast_i32_u32() !void {14fn testBitCast_i32_u32() !void {
14 try expect(conv(-1) == maxInt(u32));15 try expect(conv_i32(-1) == maxInt(u32));
15 try expect(conv2(maxInt(u32)) == -1);16 try expect(conv_u32(maxInt(u32)) == -1);
17 try expect(conv_u32(0x8000_0000) == minInt(i32));
18 try expect(conv_i32(minInt(i32)) == 0x8000_0000);
16}19}
1720
18fn conv(x: i32) u32 {21fn conv_i32(x: i32) u32 {
19 return @bitCast(u32, x);22 return @bitCast(u32, x);
20}23}
21fn conv2(x: u32) i32 {24fn conv_u32(x: u32) i32 {
22 return @bitCast(i32, x);25 return @bitCast(i32, x);
23}26}
2427
28test "@bitCast i48 -> u48" {
29 try testBitCast_i48_u48();
30 comptime try testBitCast_i48_u48();
31}
32
33fn testBitCast_i48_u48() !void {
34 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
35 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
36 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
37
38 try expect(conv_i48(-1) == maxInt(u48));
39 try expect(conv_u48(maxInt(u48)) == -1);
40 try expect(conv_u48(0x8000_0000_0000) == minInt(i48));
41 try expect(conv_i48(minInt(i48)) == 0x8000_0000_0000);
42}
43
44fn conv_i48(x: i48) u48 {
45 return @bitCast(u48, x);
46}
47
48fn conv_u48(x: u48) i48 {
49 return @bitCast(i48, x);
50}
51
52test "@bitCast i27 -> u27" {
53 try testBitCast_i27_u27();
54 comptime try testBitCast_i27_u27();
55}
56
57fn testBitCast_i27_u27() !void {
58 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
59 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
60 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
61
62 try expect(conv_i27(-1) == maxInt(u27));
63 try expect(conv_u27(maxInt(u27)) == -1);
64 try expect(conv_u27(0x400_0000) == minInt(i27));
65 try expect(conv_i27(minInt(i27)) == 0x400_0000);
66}
67
68fn conv_i27(x: i27) u27 {
69 return @bitCast(u27, x);
70}
71
72fn conv_u27(x: u27) i27 {
73 return @bitCast(i27, x);
74}
75
76test "@bitCast i512 -> u512" {
77 try testBitCast_i512_u512();
78 comptime try testBitCast_i512_u512();
79}
80
81fn testBitCast_i512_u512() !void {
82 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
83 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
84 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
85
86 try expect(conv_i512(-1) == maxInt(u512));
87 try expect(conv_u512(maxInt(u512)) == -1);
88 try expect(conv_u512(@as(u512, 1) << 511) == minInt(i512));
89 try expect(conv_i512(minInt(i512)) == (@as(u512, 1) << 511));
90}
91
92fn conv_i512(x: i512) u512 {
93 return @bitCast(u512, x);
94}
95
96fn conv_u512(x: u512) i512 {
97 return @bitCast(i512, x);
98}
99
25test "bitcast result to _" {100test "bitcast result to _" {
26 _ = @bitCast(u8, @as(i8, 1));101 _ = @bitCast(u8, @as(i8, 1));
27}102}
28103
104test "@bitCast i493 -> u493" {
105 try testBitCast_i493_u493();
106 comptime try testBitCast_i493_u493();
107}
108
109fn testBitCast_i493_u493() !void {
110 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
111 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
112 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
113
114 try expect(conv_i493(-1) == maxInt(u493));
115 try expect(conv_u493(maxInt(u493)) == -1);
116 try expect(conv_u493(@as(u493, 1) << 492) == minInt(i493));
117 try expect(conv_i493(minInt(i493)) == (@as(u493, 1) << 492));
118}
119
120fn conv_i493(x: i493) u493 {
121 return @bitCast(u493, x);
122}
123
124fn conv_u493(x: u493) i493 {
125 return @bitCast(i493, x);
126}
127
29test "nested bitcast" {128test "nested bitcast" {
30 const S = struct {129 const S = struct {
31 fn moo(x: isize) !void {130 fn moo(x: isize) !void {