authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-02-18 13:49:00-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-02-18 13:49:00-05:00
log736b9dcdd60b83c07bfbdb69c62facfbe10611f1
tree7cf77d45cd80adb7c56adb74a9f15e12727e2a0f
parentab43c045edc16d90679810d0384096c859f51a9f
parent7edf3d9f2d53f5b1c5e31ee1294ff9b52337760b
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #10858 from topolarity/stage2-bitcast

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 @@
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,10 +1623,17 @@ pub const Mutable = struct {...@@ -1621,10 +1623,17 @@ pub const Mutable = struct {
1621 }1623 }
1622 }1624 }
16231625
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: off1646
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
16551681
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 }
16581718
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);
18081868
1809 if (self.bitCountTwosComp() > info.bits) {1869 if (!self.fitsInTwosComp(info.signedness, info.bits)) {
1810 return error.TargetTooSmall;1870 return error.TargetTooSmall;
1811 }1871 }
18121872
...@@ -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 }
20152075
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));
20192089
2020 // zig fmt: off2090 // 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
20352106
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 }
20382139
2039 /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if2140 /// 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" {
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 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
2516test "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
2553test "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
2590test "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
2666test "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 }
314314
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 limbs1098 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");
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 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
18test "@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}
1227
13fn testBitCast_i32_u32() !void {28test "@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}
1740
18fn conv(x: i32) u32 {41fn 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}
21fn conv2(x: u32) i32 {59
22 return @bitCast(i32, x);60fn 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}
2463
25test "bitcast result to _" {64fn 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}
2867
29test "nested bitcast" {68test "nested bitcast" {