authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-18 11:37:43-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-28 08:41:04-07:00
log3295fee9116789f144e6406493116c451aee7c57
tree71f10d7a5b987b956d0811d925424fea57fddd09
parentc639c225444c9252515949786e139494fb728861

stage2: Use mem.readPackedInt etc. for packed bitcasts

Packed memory has a well-defined layout that doesn't require conversion from an integer to read from. Let's use it :-) This change means that for bitcasting to/from a packed value that is N layers deep, we no longer have to create N temporary big-ints and perform N copies. Other miscellaneous improvements: - Adds support for casting to packed enums and vectors - Fixes bitcasting to/from vectors outside of a packed struct - Adds a fast path for bitcasting <= u/i64 - Fixes bug when bitcasting f80 which would clear following fields This also changes the bitcast memory layout of exotic integers on big-endian systems to match what's empirically observed on our targets. Technically, this layout is not guaranteed by LLVM so we should probably ban bitcasts that reveal these padding bits, but for now this is an improvement.

6 files changed, 461 insertions(+), 422 deletions(-)

lib/std/math/big/int.zig+69-92
......@@ -1762,16 +1762,32 @@ pub const Mutable = struct {
17621762 }
17631763
17641764 /// Read the value of `x` from `buffer`
1765 /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value.
1765 /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count`.
17661766 ///
17671767 /// The contents of `buffer` are interpreted as if they were the contents of
1768 /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian`
1768 /// @ptrCast(*[buffer.len]const u8, &x). Byte ordering is determined by `endian`
17691769 /// and any required padding bits are expected on the MSB end.
17701770 pub fn readTwosComplement(
17711771 x: *Mutable,
17721772 buffer: []const u8,
17731773 bit_count: usize,
1774 abi_size: usize,
1774 endian: Endian,
1775 signedness: Signedness,
1776 ) void {
1777 return readPackedTwosComplement(x, buffer, 0, bit_count, endian, signedness);
1778 }
1779
1780 /// Read the value of `x` from a packed memory `buffer`.
1781 /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count`
1782 /// at offset `bit_offset`.
1783 ///
1784 /// This is equivalent to loading the value of an integer with `bit_count` bits as
1785 /// if it were a field in packed memory at the provided bit offset.
1786 pub fn readPackedTwosComplement(
1787 x: *Mutable,
1788 bytes: []const u8,
1789 bit_offset: usize,
1790 bit_count: usize,
17751791 endian: Endian,
17761792 signedness: Signedness,
17771793 ) void {
......@@ -1782,75 +1798,54 @@ pub const Mutable = struct {
17821798 return;
17831799 }
17841800
1785 // byte_count is our total read size: it cannot exceed abi_size,
1786 // but may be less as long as it includes the required bits
1787 const limb_count = calcTwosCompLimbCount(bit_count);
1788 const byte_count = std.math.min(abi_size, @sizeOf(Limb) * limb_count);
1789 assert(8 * byte_count >= bit_count);
1790
17911801 // Check whether the input is negative
17921802 var positive = true;
17931803 if (signedness == .signed) {
1804 const total_bits = bit_offset + bit_count;
17941805 var last_byte = switch (endian) {
1795 .Little => ((bit_count + 7) / 8) - 1,
1796 .Big => abi_size - ((bit_count + 7) / 8),
1806 .Little => ((total_bits + 7) / 8) - 1,
1807 .Big => bytes.len - ((total_bits + 7) / 8),
17971808 };
17981809
1799 const sign_bit = @as(u8, 1) << @intCast(u3, (bit_count - 1) % 8);
1800 positive = ((buffer[last_byte] & sign_bit) == 0);
1810 const sign_bit = @as(u8, 1) << @intCast(u3, (total_bits - 1) % 8);
1811 positive = ((bytes[last_byte] & sign_bit) == 0);
18011812 }
18021813
18031814 // Copy all complete limbs
1804 var carry: u1 = if (positive) 0 else 1;
1815 var carry: u1 = 1;
18051816 var limb_index: usize = 0;
1817 var bit_index: usize = 0;
18061818 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {
1807 var buf_index = switch (endian) {
1808 .Little => @sizeOf(Limb) * limb_index,
1809 .Big => abi_size - (limb_index + 1) * @sizeOf(Limb),
1810 };
1811
1812 const limb_buf = @ptrCast(*const [@sizeOf(Limb)]u8, buffer[buf_index..]);
1813 var limb = mem.readInt(Limb, limb_buf, endian);
1819 // Read one Limb of bits
1820 var limb = mem.readPackedInt(Limb, bytes, bit_index + bit_offset, endian);
1821 bit_index += @bitSizeOf(Limb);
18141822
18151823 // 2's complement (bitwise not, then add carry bit)
18161824 if (!positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
18171825 x.limbs[limb_index] = limb;
18181826 }
18191827
1820 // Copy the remaining N bytes (N <= @sizeOf(Limb))
1821 var bytes_read = limb_index * @sizeOf(Limb);
1822 if (bytes_read != byte_count) {
1823 var limb: Limb = 0;
1824
1825 while (bytes_read != byte_count) {
1826 const read_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_read);
1827 var int_buffer = switch (endian) {
1828 .Little => buffer[bytes_read..],
1829 .Big => buffer[(abi_size - bytes_read - read_size)..],
1830 };
1831 limb |= @intCast(Limb, switch (read_size) {
1832 1 => mem.readInt(u8, int_buffer[0..1], endian),
1833 2 => mem.readInt(u16, int_buffer[0..2], endian),
1834 4 => mem.readInt(u32, int_buffer[0..4], endian),
1835 8 => mem.readInt(u64, int_buffer[0..8], endian),
1836 16 => mem.readInt(u128, int_buffer[0..16], endian),
1837 else => unreachable,
1838 }) << @intCast(Log2Limb, 8 * (bytes_read % @sizeOf(Limb)));
1839 bytes_read += read_size;
1840 }
1828 // Copy the remaining bits
1829 if (bit_count != bit_index) {
1830 // Read all remaining bits
1831 var limb = switch (signedness) {
1832 .unsigned => mem.readVarPackedInt(Limb, bytes, bit_index + bit_offset, bit_count - bit_index, endian, .unsigned),
1833 .signed => b: {
1834 const SLimb = std.meta.Int(.signed, @bitSizeOf(Limb));
1835 const limb = mem.readVarPackedInt(SLimb, bytes, bit_index + bit_offset, bit_count - bit_index, endian, .signed);
1836 break :b @bitCast(Limb, limb);
1837 },
1838 };
18411839
18421840 // 2's complement (bitwise not, then add carry bit)
1843 if (!positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);
1844
1845 // Mask off any unused bits
1846 const valid_bits = @intCast(Log2Limb, bit_count % @bitSizeOf(Limb));
1847 const mask = (@as(Limb, 1) << valid_bits) -% 1; // 0b0..01..1 with (valid_bits_in_limb) trailing ones
1848 limb &= mask;
1841 if (!positive) assert(!@addWithOverflow(Limb, ~limb, carry, &limb));
1842 x.limbs[limb_index] = limb;
18491843
1850 x.limbs[limb_count - 1] = limb;
1844 limb_index += 1;
18511845 }
1846
18521847 x.positive = positive;
1853 x.len = limb_count;
1848 x.len = limb_index;
18541849 x.normalize(x.len);
18551850 }
18561851
......@@ -2212,66 +2207,48 @@ pub const Const = struct {
22122207 }
22132208
22142209 /// Write the value of `x` into `buffer`
2215 /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value.
2210 /// Asserts that `buffer` is large enough to store the value.
22162211 ///
22172212 /// `buffer` is filled so that its contents match what would be observed via
2218 /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian`,
2213 /// @ptrCast(*[buffer.len]const u8, &x). Byte ordering is determined by `endian`,
22192214 /// and any required padding bits are added on the MSB end.
2220 pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, abi_size: usize, endian: Endian) void {
2215 pub fn writeTwosComplement(x: Const, buffer: []u8, endian: Endian) void {
2216 return writePackedTwosComplement(x, buffer, 0, 8 * buffer.len, endian);
2217 }
22212218
2222 // byte_count is our total write size
2223 const byte_count = abi_size;
2224 assert(8 * byte_count >= bit_count);
2225 assert(buffer.len >= byte_count);
2219 /// Write the value of `x` to a packed memory `buffer`.
2220 /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count`
2221 /// at offset `bit_offset`.
2222 ///
2223 /// This is equivalent to storing the value of an integer with `bit_count` bits as
2224 /// if it were a field in packed memory at the provided bit offset.
2225 pub fn writePackedTwosComplement(x: Const, bytes: []u8, bit_offset: usize, bit_count: usize, endian: Endian) void {
22262226 assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count));
22272227
22282228 // Copy all complete limbs
2229 var carry: u1 = if (x.positive) 0 else 1;
2229 var carry: u1 = 1;
22302230 var limb_index: usize = 0;
2231 while (limb_index < byte_count / @sizeOf(Limb)) : (limb_index += 1) {
2232 var buf_index = switch (endian) {
2233 .Little => @sizeOf(Limb) * limb_index,
2234 .Big => abi_size - (limb_index + 1) * @sizeOf(Limb),
2235 };
2236
2231 var bit_index: usize = 0;
2232 while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) {
22372233 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2234
22382235 // 2's complement (bitwise not, then add carry bit)
22392236 if (!x.positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb));
22402237
2241 var limb_buf = @ptrCast(*[@sizeOf(Limb)]u8, buffer[buf_index..]);
2242 mem.writeInt(Limb, limb_buf, limb, endian);
2238 // Write one Limb of bits
2239 mem.writePackedInt(Limb, bytes, bit_index + bit_offset, limb, endian);
2240 bit_index += @bitSizeOf(Limb);
22432241 }
22442242
2245 // Copy the remaining N bytes (N < @sizeOf(Limb))
2246 var bytes_written = limb_index * @sizeOf(Limb);
2247 if (bytes_written != byte_count) {
2243 // Copy the remaining bits
2244 if (bit_count != bit_index) {
22482245 var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0;
2246
22492247 // 2's complement (bitwise not, then add carry bit)
22502248 if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb);
22512249
2252 while (bytes_written != byte_count) {
2253 const write_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_written);
2254 var int_buffer = switch (endian) {
2255 .Little => buffer[bytes_written..],
2256 .Big => buffer[(abi_size - bytes_written - write_size)..],
2257 };
2258
2259 if (write_size == 1) {
2260 mem.writeInt(u8, int_buffer[0..1], @truncate(u8, limb), endian);
2261 } else if (@sizeOf(Limb) >= 2 and write_size == 2) {
2262 mem.writeInt(u16, int_buffer[0..2], @truncate(u16, limb), endian);
2263 } else if (@sizeOf(Limb) >= 4 and write_size == 4) {
2264 mem.writeInt(u32, int_buffer[0..4], @truncate(u32, limb), endian);
2265 } else if (@sizeOf(Limb) >= 8 and write_size == 8) {
2266 mem.writeInt(u64, int_buffer[0..8], @truncate(u64, limb), endian);
2267 } else if (@sizeOf(Limb) >= 16 and write_size == 16) {
2268 mem.writeInt(u128, int_buffer[0..16], @truncate(u128, limb), endian);
2269 } else if (@sizeOf(Limb) >= 32) {
2270 @compileError("@sizeOf(Limb) exceeded supported range");
2271 } else unreachable;
2272 limb >>= @intCast(Log2Limb, 8 * write_size);
2273 bytes_written += write_size;
2274 }
2250 // Write all remaining bits
2251 mem.writeVarPackedInt(bytes, bit_index + bit_offset, bit_count - bit_index, limb, endian);
22752252 }
22762253 }
22772254
lib/std/math/big/int_test.zig+60-42
......@@ -2603,13 +2603,13 @@ test "big int conversion read/write twos complement" {
26032603
26042604 for (endians) |endian| {
26052605 // Writing to buffer and back should not change anything
2606 a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian);
2607 m.readTwosComplement(buffer1, 493, abi_size, endian, .unsigned);
2606 a.toConst().writeTwosComplement(buffer1[0..abi_size], endian);
2607 m.readTwosComplement(buffer1[0..abi_size], 493, endian, .unsigned);
26082608 try testing.expect(m.toConst().order(a.toConst()) == .eq);
26092609
26102610 // Equivalent to @bitCast(i493, @as(u493, intMax(u493))
2611 a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian);
2612 m.readTwosComplement(buffer1, 493, abi_size, endian, .signed);
2611 a.toConst().writeTwosComplement(buffer1[0..abi_size], endian);
2612 m.readTwosComplement(buffer1[0..abi_size], 493, endian, .signed);
26132613 try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq);
26142614 }
26152615}
......@@ -2628,26 +2628,26 @@ test "big int conversion read twos complement with padding" {
26282628 // (3) should sign-extend any bits from bit_count to 8 * abi_size
26292629
26302630 var bit_count: usize = 12 * 8 + 1;
2631 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2631 a.toConst().writeTwosComplement(buffer1[0..13], .Little);
26322632 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 }));
2633 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2633 a.toConst().writeTwosComplement(buffer1[0..13], .Big);
26342634 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 }));
2635 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2635 a.toConst().writeTwosComplement(buffer1[0..16], .Little);
26362636 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 }));
2637 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2637 a.toConst().writeTwosComplement(buffer1[0..16], .Big);
26382638 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 }));
26392639
26402640 @memset(buffer1.ptr, 0xaa, buffer1.len);
26412641 try a.set(-0x01_02030405_06070809_0a0b0c0d);
26422642 bit_count = 12 * 8 + 2;
26432643
2644 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2644 a.toConst().writeTwosComplement(buffer1[0..13], .Little);
26452645 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 }));
2646 a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2646 a.toConst().writeTwosComplement(buffer1[0..13], .Big);
26472647 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 }));
2648 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2648 a.toConst().writeTwosComplement(buffer1[0..16], .Little);
26492649 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 }));
2650 a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2650 a.toConst().writeTwosComplement(buffer1[0..16], .Big);
26512651 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 }));
26522652}
26532653
......@@ -2660,17 +2660,15 @@ test "big int write twos complement +/- zero" {
26602660 defer testing.allocator.free(buffer1);
26612661 @memset(buffer1.ptr, 0xaa, buffer1.len);
26622662
2663 var bit_count: usize = 0;
2664
26652663 // Test zero
26662664
2667 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2665 m.toConst().writeTwosComplement(buffer1[0..13], .Little);
26682666 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2669 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2667 m.toConst().writeTwosComplement(buffer1[0..13], .Big);
26702668 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2671 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2669 m.toConst().writeTwosComplement(buffer1[0..16], .Little);
26722670 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
2673 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2671 m.toConst().writeTwosComplement(buffer1[0..16], .Big);
26742672 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
26752673
26762674 @memset(buffer1.ptr, 0xaa, buffer1.len);
......@@ -2678,13 +2676,13 @@ test "big int write twos complement +/- zero" {
26782676
26792677 // Test negative zero
26802678
2681 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little);
2679 m.toConst().writeTwosComplement(buffer1[0..13], .Little);
26822680 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2683 m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big);
2681 m.toConst().writeTwosComplement(buffer1[0..13], .Big);
26842682 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3))));
2685 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little);
2683 m.toConst().writeTwosComplement(buffer1[0..16], .Little);
26862684 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
2687 m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big);
2685 m.toConst().writeTwosComplement(buffer1[0..16], .Big);
26882686 try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16))));
26892687}
26902688
......@@ -2705,62 +2703,82 @@ test "big int conversion write twos complement with padding" {
27052703 // Test 0x01_02030405_06070809_0a0b0c0d
27062704
27072705 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xb };
2708 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2706 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27092707 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27102708
27112709 buffer = &[_]u8{ 0xb, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2712 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2710 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27132711 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27142712
27152713 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xab, 0xaa, 0xaa, 0xaa };
2716 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2714 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27172715 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27182716
27192717 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0xab, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2720 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2718 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27212719 try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq);
27222720
2721 bit_count = @sizeOf(Limb) * 8;
2722
2723 // Test 0x0a0a0a0a_02030405_06070809_0a0b0c0d
2724
2725 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xaa };
2726 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
2727 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaa_02030405_06070809_0a0b0c0d)) == .eq);
2728
2729 buffer = &[_]u8{ 0xaa, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2730 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
2731 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaa_02030405_06070809_0a0b0c0d)) == .eq);
2732
2733 buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xaa, 0xaa, 0xaa, 0xaa };
2734 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
2735 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaaaaaaaa_02030405_06070809_0a0b0c0d)) == .eq);
2736
2737 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0xaa, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd };
2738 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
2739 try testing.expect(m.toConst().orderAgainstScalar(@truncate(Limb, 0xaaaaaaaa_02030405_06070809_0a0b0c0d)) == .eq);
2740
27232741 bit_count = 12 * 8 + 2;
27242742
27252743 // Test -0x01_02030405_06070809_0a0b0c0d
27262744
27272745 buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02 };
2728 m.readTwosComplement(buffer, bit_count, 13, .Little, .signed);
2746 m.readTwosComplement(buffer[0..13], bit_count, .Little, .signed);
27292747 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27302748
27312749 buffer = &[_]u8{ 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 };
2732 m.readTwosComplement(buffer, bit_count, 13, .Big, .signed);
2750 m.readTwosComplement(buffer[0..13], bit_count, .Big, .signed);
27332751 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27342752
27352753 buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02, 0xaa, 0xaa, 0xaa };
2736 m.readTwosComplement(buffer, bit_count, 16, .Little, .signed);
2754 m.readTwosComplement(buffer[0..16], bit_count, .Little, .signed);
27372755 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27382756
27392757 buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 };
2740 m.readTwosComplement(buffer, bit_count, 16, .Big, .signed);
2758 m.readTwosComplement(buffer[0..16], bit_count, .Big, .signed);
27412759 try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq);
27422760
27432761 // Test 0
27442762
27452763 buffer = &([_]u8{0} ** 16);
2746 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2764 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27472765 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2748 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2766 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27492767 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2750 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2768 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27512769 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2752 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2770 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27532771 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27542772
27552773 bit_count = 0;
27562774 buffer = &([_]u8{0xaa} ** 16);
2757 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2775 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27582776 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2759 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2777 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27602778 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2761 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2779 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27622780 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2763 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2781 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27642782 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27652783}
27662784
......@@ -2779,15 +2797,15 @@ test "big int conversion write twos complement zero" {
27792797 var buffer: []const u8 = undefined;
27802798
27812799 buffer = &([_]u8{0} ** 13);
2782 m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned);
2800 m.readTwosComplement(buffer[0..13], bit_count, .Little, .unsigned);
27832801 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2784 m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned);
2802 m.readTwosComplement(buffer[0..13], bit_count, .Big, .unsigned);
27852803 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27862804
27872805 buffer = &([_]u8{0} ** 16);
2788 m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned);
2806 m.readTwosComplement(buffer[0..16], bit_count, .Little, .unsigned);
27892807 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
2790 m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned);
2808 m.readTwosComplement(buffer[0..16], bit_count, .Big, .unsigned);
27912809 try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq);
27922810}
27932811
src/Sema.zig-42
......@@ -26445,48 +26445,6 @@ fn bitCastVal(
2644526445 const target = sema.mod.getTarget();
2644626446 if (old_ty.eql(new_ty, sema.mod)) return val;
2644726447
26448 // Some conversions have a bitwise definition that ignores in-memory layout,
26449 // such as converting between f80 and u80.
26450
26451 if (old_ty.eql(Type.f80, sema.mod) and new_ty.isAbiInt()) {
26452 const float = val.toFloat(f80);
26453 switch (new_ty.intInfo(target).signedness) {
26454 .signed => {
26455 const int = @bitCast(i80, float);
26456 const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
26457 const big_int = std.math.big.int.Mutable.init(limbs, int);
26458 return Value.fromBigInt(sema.arena, big_int.toConst());
26459 },
26460 .unsigned => {
26461 const int = @bitCast(u80, float);
26462 const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
26463 const big_int = std.math.big.int.Mutable.init(limbs, int);
26464 return Value.fromBigInt(sema.arena, big_int.toConst());
26465 },
26466 }
26467 }
26468
26469 if (new_ty.eql(Type.f80, sema.mod) and old_ty.isAbiInt()) {
26470 var bigint_space: Value.BigIntSpace = undefined;
26471 var bigint = try val.toBigIntAdvanced(&bigint_space, target, sema.kit(block, src));
26472 switch (old_ty.intInfo(target).signedness) {
26473 .signed => {
26474 // This conversion cannot fail because we already checked bit size before
26475 // calling bitCastVal.
26476 const int = bigint.to(i80) catch unreachable;
26477 const float = @bitCast(f80, int);
26478 return Value.Tag.float_80.create(sema.arena, float);
26479 },
26480 .unsigned => {
26481 // This conversion cannot fail because we already checked bit size before
26482 // calling bitCastVal.
26483 const int = bigint.to(u80) catch unreachable;
26484 const float = @bitCast(f80, int);
26485 return Value.Tag.float_80.create(sema.arena, float);
26486 },
26487 }
26488 }
26489
2649026448 // For types with well-defined memory layouts, we serialize them a byte buffer,
2649126449 // then deserialize to the new type.
2649226450 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target));
src/codegen.zig+1-1
......@@ -470,7 +470,7 @@ pub fn generateSymbol(
470470 const abi_size = math.cast(usize, typed_value.ty.abiSize(target)) orelse return error.Overflow;
471471 const start = code.items.len;
472472 try code.resize(start + abi_size);
473 bigint.writeTwosComplement(code.items[start..][0..abi_size], info.bits, abi_size, endian);
473 bigint.writeTwosComplement(code.items[start..][0..abi_size], endian);
474474 return Result{ .appended = {} };
475475 }
476476 switch (info.signedness) {
src/value.zig+245-244
......@@ -1206,8 +1206,13 @@ pub const Value = extern union {
12061206 };
12071207 }
12081208
1209 /// Write a Value's contents to `buffer`.
1210 ///
1211 /// Asserts that buffer.len >= ty.abiSize(). The buffer is allowed to extend past
1212 /// the end of the value in memory.
12091213 pub fn writeToMemory(val: Value, ty: Type, mod: *Module, buffer: []u8) void {
12101214 const target = mod.getTarget();
1215 const endian = target.cpu.arch.endian();
12111216 if (val.isUndef()) {
12121217 const size = @intCast(usize, ty.abiSize(target));
12131218 std.mem.set(u8, buffer[0..size], 0xaa);
......@@ -1218,31 +1223,41 @@ pub const Value = extern union {
12181223 .Bool => {
12191224 buffer[0] = @boolToInt(val.toBool());
12201225 },
1221 .Int => {
1222 var bigint_buffer: BigIntSpace = undefined;
1223 const bigint = val.toBigInt(&bigint_buffer, target);
1224 const bits = ty.intInfo(target).bits;
1225 const abi_size = @intCast(usize, ty.abiSize(target));
1226 bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian());
1227 },
1228 .Enum => {
1226 .Int, .Enum => {
1227 const int_info = ty.intInfo(target);
1228 const bits = int_info.bits;
1229 const byte_count = (bits + 7) / 8;
1230
12291231 var enum_buffer: Payload.U64 = undefined;
12301232 const int_val = val.enumToInt(ty, &enum_buffer);
1231 var bigint_buffer: BigIntSpace = undefined;
1232 const bigint = int_val.toBigInt(&bigint_buffer, target);
1233 const bits = ty.intInfo(target).bits;
1234 const abi_size = @intCast(usize, ty.abiSize(target));
1235 bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian());
1233
1234 if (byte_count <= @sizeOf(u64)) {
1235 const int: u64 = switch (int_val.tag()) {
1236 .zero => 0,
1237 .one => 1,
1238 .int_u64 => int_val.castTag(.int_u64).?.data,
1239 .int_i64 => @bitCast(u64, int_val.castTag(.int_i64).?.data),
1240 else => unreachable,
1241 };
1242 for (buffer[0..byte_count]) |_, i| switch (endian) {
1243 .Little => buffer[i] = @truncate(u8, (int >> @intCast(u6, (8 * i)))),
1244 .Big => buffer[byte_count - i - 1] = @truncate(u8, (int >> @intCast(u6, (8 * i)))),
1245 };
1246 } else {
1247 var bigint_buffer: BigIntSpace = undefined;
1248 const bigint = int_val.toBigInt(&bigint_buffer, target);
1249 bigint.writeTwosComplement(buffer[0..byte_count], endian);
1250 }
12361251 },
12371252 .Float => switch (ty.floatBits(target)) {
1238 16 => return floatWriteToMemory(f16, val.toFloat(f16), target, buffer),
1239 32 => return floatWriteToMemory(f32, val.toFloat(f32), target, buffer),
1240 64 => return floatWriteToMemory(f64, val.toFloat(f64), target, buffer),
1241 80 => return floatWriteToMemory(f80, val.toFloat(f80), target, buffer),
1242 128 => return floatWriteToMemory(f128, val.toFloat(f128), target, buffer),
1253 16 => std.mem.writeInt(u16, buffer[0..2], @bitCast(u16, val.toFloat(f16)), endian),
1254 32 => std.mem.writeInt(u32, buffer[0..4], @bitCast(u32, val.toFloat(f32)), endian),
1255 64 => std.mem.writeInt(u64, buffer[0..8], @bitCast(u64, val.toFloat(f64)), endian),
1256 80 => std.mem.writeInt(u80, buffer[0..10], @bitCast(u80, val.toFloat(f80)), endian),
1257 128 => std.mem.writeInt(u128, buffer[0..16], @bitCast(u128, val.toFloat(f128)), endian),
12431258 else => unreachable,
12441259 },
1245 .Array, .Vector => {
1260 .Array => {
12461261 const len = ty.arrayLen();
12471262 const elem_ty = ty.childType();
12481263 const elem_size = @intCast(usize, elem_ty.abiSize(target));
......@@ -1251,10 +1266,16 @@ pub const Value = extern union {
12511266 var buf_off: usize = 0;
12521267 while (elem_i < len) : (elem_i += 1) {
12531268 const elem_val = val.elemValueBuffer(mod, elem_i, &elem_value_buf);
1254 writeToMemory(elem_val, elem_ty, mod, buffer[buf_off..]);
1269 elem_val.writeToMemory(elem_ty, mod, buffer[buf_off..]);
12551270 buf_off += elem_size;
12561271 }
12571272 },
1273 .Vector => {
1274 // We use byte_count instead of abi_size here, so that any padding bytes
1275 // follow the data bytes, on both big- and little-endian systems.
1276 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1277 writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
1278 },
12581279 .Struct => switch (ty.containerLayout()) {
12591280 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
12601281 .Extern => {
......@@ -1266,122 +1287,113 @@ pub const Value = extern union {
12661287 }
12671288 },
12681289 .Packed => {
1269 // TODO allocate enough heap space instead of using this buffer
1270 // on the stack.
1271 var buf: [16]std.math.big.Limb = undefined;
1272 const host_int = packedStructToInt(val, ty, target, &buf);
1273 const abi_size = @intCast(usize, ty.abiSize(target));
1274 const bit_size = @intCast(usize, ty.bitSize(target));
1275 host_int.writeTwosComplement(buffer, bit_size, abi_size, target.cpu.arch.endian());
1290 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1291 writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
12761292 },
12771293 },
12781294 .ErrorSet => {
12791295 // TODO revisit this when we have the concept of the error tag type
12801296 const Int = u16;
12811297 const int = mod.global_error_set.get(val.castTag(.@"error").?.data.name).?;
1282 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], @intCast(Int, int), target.cpu.arch.endian());
1298 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], @intCast(Int, int), endian);
12831299 },
12841300 else => @panic("TODO implement writeToMemory for more types"),
12851301 }
12861302 }
12871303
1288 fn packedStructToInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {
1289 var bigint = BigIntMutable.init(buf, 0);
1290 const fields = ty.structFields().values();
1291 const field_vals = val.castTag(.aggregate).?.data;
1292 var bits: u16 = 0;
1293 // TODO allocate enough heap space instead of using this buffer
1294 // on the stack.
1295 var field_buf: [16]std.math.big.Limb = undefined;
1296 var field_space: BigIntSpace = undefined;
1297 var field_buf2: [16]std.math.big.Limb = undefined;
1298 for (fields) |field, i| {
1299 const field_val = field_vals[i];
1300 const field_bigint_const = switch (field.ty.zigTypeTag()) {
1301 .Void => continue,
1302 .Float => floatToBigInt(field_val, field.ty, target, &field_buf),
1303 .Int, .Bool => intOrBoolToBigInt(field_val, field.ty, target, &field_buf, &field_space),
1304 .Struct => switch (field.ty.containerLayout()) {
1305 .Auto, .Extern => unreachable, // Sema should have error'd before this.
1306 .Packed => packedStructToInt(field_val, field.ty, target, &field_buf),
1307 },
1308 .Vector => vectorToBigInt(field_val, field.ty, target, &field_buf),
1309 .Enum => enumToBigInt(field_val, field.ty, target, &field_space),
1310 .Union => unreachable, // TODO: packed structs support packed unions
1311 else => unreachable,
1312 };
1313 var field_bigint = BigIntMutable.init(&field_buf2, 0);
1314 field_bigint.shiftLeft(field_bigint_const, bits);
1315 bits += @intCast(u16, field.ty.bitSize(target));
1316 bigint.bitOr(bigint.toConst(), field_bigint.toConst());
1317 }
1318 return bigint.toConst();
1319 }
1320
1321 fn intOrBoolToBigInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb, space: *BigIntSpace) BigIntConst {
1322 const big_int_const = val.toBigInt(space, target);
1323 if (big_int_const.positive) return big_int_const;
1324
1325 var big_int = BigIntMutable.init(buf, 0);
1326 big_int.bitNotWrap(big_int_const.negate(), .unsigned, @intCast(u32, ty.bitSize(target)));
1327 big_int.addScalar(big_int.toConst(), 1);
1328 return big_int.toConst();
1329 }
1330
1331 fn vectorToBigInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {
1304 /// Write a Value's contents to `buffer`.
1305 ///
1306 /// Both the start and the end of the provided buffer must be tight, since
1307 /// big-endian packed memory layouts start at the end of the buffer.
1308 pub fn writeToPackedMemory(val: Value, ty: Type, mod: *Module, buffer: []u8, bit_offset: usize) void {
1309 const target = mod.getTarget();
13321310 const endian = target.cpu.arch.endian();
1333 var vec_bitint = BigIntMutable.init(buf, 0);
1334 const vec_len = @intCast(usize, ty.arrayLen());
1335 const elem_ty = ty.childType();
1336 const elem_size = @intCast(usize, elem_ty.bitSize(target));
1337
1338 var elem_buf: [16]std.math.big.Limb = undefined;
1339 var elem_space: BigIntSpace = undefined;
1340 var elem_buf2: [16]std.math.big.Limb = undefined;
1341
1342 var elem_i: usize = 0;
1343 while (elem_i < vec_len) : (elem_i += 1) {
1344 const elem_i_target = if (endian == .Big) vec_len - elem_i - 1 else elem_i;
1345 const elem_val = val.indexVectorlike(elem_i_target);
1346 const elem_bigint_const = switch (elem_ty.zigTypeTag()) {
1347 .Int, .Bool => intOrBoolToBigInt(elem_val, elem_ty, target, &elem_buf, &elem_space),
1348 .Float => floatToBigInt(elem_val, elem_ty, target, &elem_buf),
1349 .Pointer => unreachable, // TODO
1350 else => unreachable, // Sema should not let this happen
1351 };
1352 var elem_bitint = BigIntMutable.init(&elem_buf2, 0);
1353 elem_bitint.shiftLeft(elem_bigint_const, elem_size * elem_i);
1354 vec_bitint.bitOr(vec_bitint.toConst(), elem_bitint.toConst());
1311 if (val.isUndef()) {
1312 const bit_size = @intCast(usize, ty.bitSize(target));
1313 std.mem.writeVarPackedInt(buffer, bit_offset, bit_size, @as(u1, 0), endian);
1314 return;
13551315 }
1356 return vec_bitint.toConst();
1357 }
1316 switch (ty.zigTypeTag()) {
1317 .Void => {},
1318 .Bool => {
1319 const byte_index = switch (endian) {
1320 .Little => bit_offset / 8,
1321 .Big => buffer.len - bit_offset / 8 - 1,
1322 };
1323 if (val.toBool()) {
1324 buffer[byte_index] |= (@as(u8, 1) << @intCast(u3, bit_offset % 8));
1325 } else {
1326 buffer[byte_index] &= ~(@as(u8, 1) << @intCast(u3, bit_offset % 8));
1327 }
1328 },
1329 .Int, .Enum => {
1330 const bits = ty.intInfo(target).bits;
1331 const abi_size = @intCast(usize, ty.abiSize(target));
13581332
1359 fn enumToBigInt(val: Value, ty: Type, target: Target, space: *BigIntSpace) BigIntConst {
1360 var enum_buf: Payload.U64 = undefined;
1361 const int_val = val.enumToInt(ty, &enum_buf);
1362 return int_val.toBigInt(space, target);
1363 }
1333 var enum_buffer: Payload.U64 = undefined;
1334 const int_val = val.enumToInt(ty, &enum_buffer);
13641335
1365 fn floatToBigInt(val: Value, ty: Type, target: Target, buf: []std.math.big.Limb) BigIntConst {
1366 return switch (ty.floatBits(target)) {
1367 16 => bitcastFloatToBigInt(f16, val.toFloat(f16), buf),
1368 32 => bitcastFloatToBigInt(f32, val.toFloat(f32), buf),
1369 64 => bitcastFloatToBigInt(f64, val.toFloat(f64), buf),
1370 80 => bitcastFloatToBigInt(f80, val.toFloat(f80), buf),
1371 128 => bitcastFloatToBigInt(f128, val.toFloat(f128), buf),
1372 else => unreachable,
1373 };
1374 }
1336 if (abi_size <= @sizeOf(u64)) {
1337 const int: u64 = switch (int_val.tag()) {
1338 .zero => 0,
1339 .one => 1,
1340 .int_u64 => int_val.castTag(.int_u64).?.data,
1341 .int_i64 => @bitCast(u64, int_val.castTag(.int_i64).?.data),
1342 else => unreachable,
1343 };
1344 std.mem.writeVarPackedInt(buffer, bit_offset, bits, int, endian);
1345 } else {
1346 var bigint_buffer: BigIntSpace = undefined;
1347 const bigint = int_val.toBigInt(&bigint_buffer, target);
1348 bigint.writePackedTwosComplement(buffer, bit_offset, bits, endian);
1349 }
1350 },
1351 .Float => switch (ty.floatBits(target)) {
1352 16 => std.mem.writePackedInt(u16, buffer, bit_offset, @bitCast(u16, val.toFloat(f16)), endian),
1353 32 => std.mem.writePackedInt(u32, buffer, bit_offset, @bitCast(u32, val.toFloat(f32)), endian),
1354 64 => std.mem.writePackedInt(u64, buffer, bit_offset, @bitCast(u64, val.toFloat(f64)), endian),
1355 80 => std.mem.writePackedInt(u80, buffer, bit_offset, @bitCast(u80, val.toFloat(f80)), endian),
1356 128 => std.mem.writePackedInt(u128, buffer, bit_offset, @bitCast(u128, val.toFloat(f128)), endian),
1357 else => unreachable,
1358 },
1359 .Vector => {
1360 const len = ty.arrayLen();
1361 const elem_ty = ty.childType();
1362 const elem_bit_size = @intCast(u16, elem_ty.bitSize(target));
13751363
1376 fn bitcastFloatToBigInt(comptime F: type, f: F, buf: []std.math.big.Limb) BigIntConst {
1377 const Int = @Type(.{ .Int = .{
1378 .signedness = .unsigned,
1379 .bits = @typeInfo(F).Float.bits,
1380 } });
1381 const int = @bitCast(Int, f);
1382 return BigIntMutable.init(buf, int).toConst();
1364 var bits: u16 = 0;
1365 var elem_i: usize = 0;
1366 var elem_value_buf: ElemValueBuffer = undefined;
1367 while (elem_i < len) : (elem_i += 1) {
1368 // On big-endian systems, LLVM reverses the element order of vectors by default
1369 const tgt_elem_i = if (endian == .Big) len - elem_i - 1 else elem_i;
1370 const elem_val = val.elemValueBuffer(mod, tgt_elem_i, &elem_value_buf);
1371 elem_val.writeToPackedMemory(elem_ty, mod, buffer, bit_offset + bits);
1372 bits += elem_bit_size;
1373 }
1374 },
1375 .Struct => switch (ty.containerLayout()) {
1376 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
1377 .Extern => unreachable, // Handled in non-packed writeToMemory
1378 .Packed => {
1379 var bits: u16 = 0;
1380 const fields = ty.structFields().values();
1381 const field_vals = val.castTag(.aggregate).?.data;
1382 for (fields) |field, i| {
1383 const field_bits = @intCast(u16, field.ty.bitSize(target));
1384 field_vals[i].writeToPackedMemory(field.ty, mod, buffer, bit_offset + bits);
1385 bits += field_bits;
1386 }
1387 },
1388 },
1389 else => @panic("TODO implement writeToPackedMemory for more types"),
1390 }
13831391 }
13841392
1393 /// Load a Value from the contents of `buffer`.
1394 ///
1395 /// Asserts that buffer.len >= ty.abiSize(). The buffer is allowed to extend past
1396 /// the end of the value in memory.
13851397 pub fn readFromMemory(
13861398 ty: Type,
13871399 mod: *Module,
......@@ -1389,6 +1401,7 @@ pub const Value = extern union {
13891401 arena: Allocator,
13901402 ) Allocator.Error!Value {
13911403 const target = mod.getTarget();
1404 const endian = target.cpu.arch.endian();
13921405 switch (ty.zigTypeTag()) {
13931406 .Void => return Value.@"void",
13941407 .Bool => {
......@@ -1398,27 +1411,40 @@ pub const Value = extern union {
13981411 return Value.@"true";
13991412 }
14001413 },
1401 .Int => {
1402 if (buffer.len == 0) return Value.zero;
1414 .Int, .Enum => {
14031415 const int_info = ty.intInfo(target);
1404 const endian = target.cpu.arch.endian();
1405 const Limb = std.math.big.Limb;
1406 const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1407 const limbs_buffer = try arena.alloc(Limb, limb_count);
1408 const abi_size = @intCast(usize, ty.abiSize(target));
1409 var bigint = BigIntMutable.init(limbs_buffer, 0);
1410 bigint.readTwosComplement(buffer, int_info.bits, abi_size, endian, int_info.signedness);
1411 return fromBigInt(arena, bigint.toConst());
1416 const bits = int_info.bits;
1417 const byte_count = (bits + 7) / 8;
1418 if (bits == 0 or buffer.len == 0) return Value.zero;
1419
1420 if (bits <= 64) switch (int_info.signedness) { // Fast path for integers <= u64
1421 .signed => {
1422 const val = std.mem.readVarInt(i64, buffer[0..byte_count], endian);
1423 return Value.Tag.int_i64.create(arena, (val << @intCast(u6, 64 - bits)) >> @intCast(u6, 64 - bits));
1424 },
1425 .unsigned => {
1426 const val = std.mem.readVarInt(u64, buffer[0..byte_count], endian);
1427 return Value.Tag.int_u64.create(arena, (val << @intCast(u6, 64 - bits)) >> @intCast(u6, 64 - bits));
1428 },
1429 } else { // Slow path, we have to construct a big-int
1430 const Limb = std.math.big.Limb;
1431 const limb_count = (byte_count + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1432 const limbs_buffer = try arena.alloc(Limb, limb_count);
1433
1434 var bigint = BigIntMutable.init(limbs_buffer, 0);
1435 bigint.readTwosComplement(buffer[0..byte_count], bits, endian, int_info.signedness);
1436 return fromBigInt(arena, bigint.toConst());
1437 }
14121438 },
14131439 .Float => switch (ty.floatBits(target)) {
1414 16 => return Value.Tag.float_16.create(arena, floatReadFromMemory(f16, target, buffer)),
1415 32 => return Value.Tag.float_32.create(arena, floatReadFromMemory(f32, target, buffer)),
1416 64 => return Value.Tag.float_64.create(arena, floatReadFromMemory(f64, target, buffer)),
1417 80 => return Value.Tag.float_80.create(arena, floatReadFromMemory(f80, target, buffer)),
1418 128 => return Value.Tag.float_128.create(arena, floatReadFromMemory(f128, target, buffer)),
1440 16 => return Value.Tag.float_16.create(arena, @bitCast(f16, std.mem.readInt(u16, buffer[0..2], endian))),
1441 32 => return Value.Tag.float_32.create(arena, @bitCast(f32, std.mem.readInt(u32, buffer[0..4], endian))),
1442 64 => return Value.Tag.float_64.create(arena, @bitCast(f64, std.mem.readInt(u64, buffer[0..8], endian))),
1443 80 => return Value.Tag.float_80.create(arena, @bitCast(f80, std.mem.readInt(u80, buffer[0..10], endian))),
1444 128 => return Value.Tag.float_128.create(arena, @bitCast(f128, std.mem.readInt(u128, buffer[0..16], endian))),
14191445 else => unreachable,
14201446 },
1421 .Array, .Vector => {
1447 .Array => {
14221448 const elem_ty = ty.childType();
14231449 const elem_size = elem_ty.abiSize(target);
14241450 const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen()));
......@@ -1429,6 +1455,12 @@ pub const Value = extern union {
14291455 }
14301456 return Tag.aggregate.create(arena, elems);
14311457 },
1458 .Vector => {
1459 // We use byte_count instead of abi_size here, so that any padding bytes
1460 // follow the data bytes, on both big- and little-endian systems.
1461 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1462 return readFromPackedMemory(ty, mod, buffer[0..byte_count], 0, arena);
1463 },
14321464 .Struct => switch (ty.containerLayout()) {
14331465 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
14341466 .Extern => {
......@@ -1436,26 +1468,20 @@ pub const Value = extern union {
14361468 const field_vals = try arena.alloc(Value, fields.len);
14371469 for (fields) |field, i| {
14381470 const off = @intCast(usize, ty.structFieldOffset(i, target));
1439 field_vals[i] = try readFromMemory(field.ty, mod, buffer[off..], arena);
1471 const sz = @intCast(usize, ty.structFieldType(i).abiSize(target));
1472 field_vals[i] = try readFromMemory(field.ty, mod, buffer[off..(off + sz)], arena);
14401473 }
14411474 return Tag.aggregate.create(arena, field_vals);
14421475 },
14431476 .Packed => {
1444 const endian = target.cpu.arch.endian();
1445 const Limb = std.math.big.Limb;
1446 const abi_size = @intCast(usize, ty.abiSize(target));
1447 const bit_size = @intCast(usize, ty.bitSize(target));
1448 const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1449 const limbs_buffer = try arena.alloc(Limb, limb_count);
1450 var bigint = BigIntMutable.init(limbs_buffer, 0);
1451 bigint.readTwosComplement(buffer, bit_size, abi_size, endian, .unsigned);
1452 return intToPackedStruct(ty, target, bigint.toConst(), arena);
1477 const byte_count = (@intCast(usize, ty.bitSize(target)) + 7) / 8;
1478 return readFromPackedMemory(ty, mod, buffer[0..byte_count], 0, arena);
14531479 },
14541480 },
14551481 .ErrorSet => {
14561482 // TODO revisit this when we have the concept of the error tag type
14571483 const Int = u16;
1458 const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian());
1484 const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], endian);
14591485
14601486 const payload = try arena.create(Value.Payload.Error);
14611487 payload.* = .{
......@@ -1468,115 +1494,90 @@ pub const Value = extern union {
14681494 }
14691495 }
14701496
1471 fn intToPackedStruct(
1497 /// Load a Value from the contents of `buffer`.
1498 ///
1499 /// Both the start and the end of the provided buffer must be tight, since
1500 /// big-endian packed memory layouts start at the end of the buffer.
1501 pub fn readFromPackedMemory(
14721502 ty: Type,
1473 target: Target,
1474 bigint: BigIntConst,
1503 mod: *Module,
1504 buffer: []const u8,
1505 bit_offset: usize,
14751506 arena: Allocator,
14761507 ) Allocator.Error!Value {
1477 const limbs_buffer = try arena.alloc(std.math.big.Limb, bigint.limbs.len);
1478 var bigint_mut = bigint.toMutable(limbs_buffer);
1479 const fields = ty.structFields().values();
1480 const field_vals = try arena.alloc(Value, fields.len);
1481 var bits: u16 = 0;
1482 for (fields) |field, i| {
1483 const field_bits = @intCast(u16, field.ty.bitSize(target));
1484 bigint_mut.shiftRight(bigint, bits);
1485 bigint_mut.truncate(bigint_mut.toConst(), .unsigned, field_bits);
1486 bits += field_bits;
1487 const field_bigint = bigint_mut.toConst();
1488
1489 field_vals[i] = switch (field.ty.zigTypeTag()) {
1490 .Float => switch (field.ty.floatBits(target)) {
1491 16 => try bitCastBigIntToFloat(f16, .float_16, field_bigint, arena),
1492 32 => try bitCastBigIntToFloat(f32, .float_32, field_bigint, arena),
1493 64 => try bitCastBigIntToFloat(f64, .float_64, field_bigint, arena),
1494 80 => try bitCastBigIntToFloat(f80, .float_80, field_bigint, arena),
1495 128 => try bitCastBigIntToFloat(f128, .float_128, field_bigint, arena),
1496 else => unreachable,
1497 },
1498 .Bool => makeBool(!field_bigint.eqZero()),
1499 .Int => try Tag.int_big_positive.create(
1500 arena,
1501 try arena.dupe(std.math.big.Limb, field_bigint.limbs),
1502 ),
1503 .Struct => try intToPackedStruct(field.ty, target, field_bigint, arena),
1504 else => unreachable,
1505 };
1506 }
1507 return Tag.aggregate.create(arena, field_vals);
1508 }
1509
1510 fn bitCastBigIntToFloat(
1511 comptime F: type,
1512 comptime float_tag: Tag,
1513 bigint: BigIntConst,
1514 arena: Allocator,
1515 ) !Value {
1516 const Int = @Type(.{ .Int = .{
1517 .signedness = .unsigned,
1518 .bits = @typeInfo(F).Float.bits,
1519 } });
1520 const int = bigint.to(Int) catch |err| switch (err) {
1521 error.NegativeIntoUnsigned => unreachable,
1522 error.TargetTooSmall => unreachable,
1523 };
1524 const f = @bitCast(F, int);
1525 return float_tag.create(arena, f);
1526 }
1527
1528 fn floatWriteToMemory(comptime F: type, f: F, target: Target, buffer: []u8) void {
1508 const target = mod.getTarget();
15291509 const endian = target.cpu.arch.endian();
1530 if (F == f80) {
1531 const repr = std.math.break_f80(f);
1532 std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian);
1533 std.mem.writeInt(u16, buffer[8..10], repr.exp, endian);
1534 std.mem.set(u8, buffer[10..], 0);
1535 return;
1536 }
1537 const Int = @Type(.{ .Int = .{
1538 .signedness = .unsigned,
1539 .bits = @typeInfo(F).Float.bits,
1540 } });
1541 const int = @bitCast(Int, f);
1542 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], int, endian);
1543 }
1510 switch (ty.zigTypeTag()) {
1511 .Void => return Value.@"void",
1512 .Bool => {
1513 const byte = switch (endian) {
1514 .Big => buffer[buffer.len - bit_offset / 8 - 1],
1515 .Little => buffer[bit_offset / 8],
1516 };
1517 if (((byte >> @intCast(u3, bit_offset % 8)) & 1) == 0) {
1518 return Value.@"false";
1519 } else {
1520 return Value.@"true";
1521 }
1522 },
1523 .Int, .Enum => {
1524 if (buffer.len == 0) return Value.zero;
1525 const int_info = ty.intInfo(target);
1526 const abi_size = @intCast(usize, ty.abiSize(target));
15441527
1545 fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F {
1546 const endian = target.cpu.arch.endian();
1547 if (F == f80) {
1548 return std.math.make_f80(.{
1549 .fraction = readInt(u64, buffer[0..8], endian),
1550 .exp = readInt(u16, buffer[8..10], endian),
1551 });
1552 }
1553 const Int = @Type(.{ .Int = .{
1554 .signedness = .unsigned,
1555 .bits = @typeInfo(F).Float.bits,
1556 } });
1557 const int = readInt(Int, buffer[0..@sizeOf(Int)], endian);
1558 return @bitCast(F, int);
1559 }
1560
1561 fn readInt(comptime Int: type, buffer: *const [@sizeOf(Int)]u8, endian: std.builtin.Endian) Int {
1562 var result: Int = 0;
1563 switch (endian) {
1564 .Big => {
1565 for (buffer) |byte| {
1566 result <<= 8;
1567 result |= byte;
1528 const bits = int_info.bits;
1529 if (bits <= 64) switch (int_info.signedness) { // Fast path for integers <= u64
1530 .signed => return Value.Tag.int_i64.create(arena, std.mem.readVarPackedInt(i64, buffer, bit_offset, bits, endian, .signed)),
1531 .unsigned => return Value.Tag.int_u64.create(arena, std.mem.readVarPackedInt(u64, buffer, bit_offset, bits, endian, .unsigned)),
1532 } else { // Slow path, we have to construct a big-int
1533 const Limb = std.math.big.Limb;
1534 const limb_count = (abi_size + @sizeOf(Limb) - 1) / @sizeOf(Limb);
1535 const limbs_buffer = try arena.alloc(Limb, limb_count);
1536
1537 var bigint = BigIntMutable.init(limbs_buffer, 0);
1538 bigint.readPackedTwosComplement(buffer, bit_offset, bits, endian, int_info.signedness);
1539 return fromBigInt(arena, bigint.toConst());
15681540 }
15691541 },
1570 .Little => {
1571 var i: usize = buffer.len;
1572 while (i != 0) {
1573 i -= 1;
1574 result <<= 8;
1575 result |= buffer[i];
1542 .Float => switch (ty.floatBits(target)) {
1543 16 => return Value.Tag.float_16.create(arena, @bitCast(f16, std.mem.readPackedInt(u16, buffer, bit_offset, endian))),
1544 32 => return Value.Tag.float_32.create(arena, @bitCast(f32, std.mem.readPackedInt(u32, buffer, bit_offset, endian))),
1545 64 => return Value.Tag.float_64.create(arena, @bitCast(f64, std.mem.readPackedInt(u64, buffer, bit_offset, endian))),
1546 80 => return Value.Tag.float_80.create(arena, @bitCast(f80, std.mem.readPackedInt(u80, buffer, bit_offset, endian))),
1547 128 => return Value.Tag.float_128.create(arena, @bitCast(f128, std.mem.readPackedInt(u128, buffer, bit_offset, endian))),
1548 else => unreachable,
1549 },
1550 .Vector => {
1551 const elem_ty = ty.childType();
1552 const elems = try arena.alloc(Value, @intCast(usize, ty.arrayLen()));
1553
1554 var bits: u16 = 0;
1555 const elem_bit_size = @intCast(u16, elem_ty.bitSize(target));
1556 for (elems) |_, i| {
1557 // On big-endian systems, LLVM reverses the element order of vectors by default
1558 const tgt_elem_i = if (endian == .Big) elems.len - i - 1 else i;
1559 elems[tgt_elem_i] = try readFromPackedMemory(elem_ty, mod, buffer, bit_offset + bits, arena);
1560 bits += elem_bit_size;
15761561 }
1562 return Tag.aggregate.create(arena, elems);
15771563 },
1564 .Struct => switch (ty.containerLayout()) {
1565 .Auto => unreachable, // Sema is supposed to have emitted a compile error already
1566 .Extern => unreachable, // Handled by non-packed readFromMemory
1567 .Packed => {
1568 var bits: u16 = 0;
1569 const fields = ty.structFields().values();
1570 const field_vals = try arena.alloc(Value, fields.len);
1571 for (fields) |field, i| {
1572 const field_bits = @intCast(u16, field.ty.bitSize(target));
1573 field_vals[i] = try readFromPackedMemory(field.ty, mod, buffer, bit_offset + bits, arena);
1574 bits += field_bits;
1575 }
1576 return Tag.aggregate.create(arena, field_vals);
1577 },
1578 },
1579 else => @panic("TODO implement readFromPackedMemory for more types"),
15781580 }
1579 return result;
15801581 }
15811582
15821583 /// Asserts that the value is a float or an integer.
test/behavior/bitcast.zig+86-1
......@@ -63,6 +63,10 @@ fn testBitCast(comptime N: usize) !void {
6363 try expect(conv_iN(N, 0) == 0);
6464
6565 try expect(conv_iN(N, -0) == 0);
66
67 if (N > 24) {
68 try expect(conv_uN(N, 0xf23456) == 0xf23456);
69 }
6670}
6771
6872fn conv_iN(comptime N: usize, x: std.meta.Int(.signed, N)) std.meta.Int(.unsigned, N) {
......@@ -73,6 +77,55 @@ fn conv_uN(comptime N: usize, x: std.meta.Int(.unsigned, N)) std.meta.Int(.signe
7377 return @bitCast(std.meta.Int(.signed, N), x);
7478}
7579
80test "bitcast uX to bytes" {
81 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
82 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
83 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
84 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
85 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
86
87 const bit_values = [_]usize{ 1, 48, 27, 512, 493, 293, 125, 204, 112 };
88 inline for (bit_values) |bits| {
89 try testBitCast(bits);
90 comptime try testBitCast(bits);
91 }
92}
93
94fn testBitCastuXToBytes(comptime N: usize) !void {
95
96 // The location of padding bits in these layouts are technically not defined
97 // by LLVM, but we currently allow exotic integers to be cast (at comptime)
98 // to types that expose their padding bits anyway.
99 //
100 // This test at least makes sure those bits are matched by the runtime behavior
101 // on the platforms we target. If the above behavior is restricted after all,
102 // this test should be deleted.
103
104 const T = std.meta.Int(.unsigned, N);
105 for ([_]T{ 0, ~@as(T, 0) }) |init_value| {
106 var x: T = init_value;
107 const bytes = std.mem.asBytes(&x);
108
109 const byte_count = (N + 7) / 8;
110 switch (builtin.cpu.arch.endian()) {
111 .Little => {
112 var byte_i = 0;
113 while (byte_i < (byte_count - 1)) : (byte_i += 1) {
114 try expect(bytes[byte_i] == 0xff);
115 }
116 try expect(((bytes[byte_i] ^ 0xff) << -%@truncate(u3, N)) == 0);
117 },
118 .Big => {
119 var byte_i = byte_count - 1;
120 while (byte_i > 0) : (byte_i -= 1) {
121 try expect(bytes[byte_i] == 0xff);
122 }
123 try expect(((bytes[byte_i] ^ 0xff) << -%@truncate(u3, N)) == 0);
124 },
125 }
126 }
127}
128
76129test "nested bitcast" {
77130 const S = struct {
78131 fn moo(x: isize) !void {
......@@ -283,7 +336,8 @@ test "@bitCast packed struct of floats" {
283336 comptime try S.doTheTest();
284337}
285338
286test "comptime @bitCast packed struct to int" {
339test "comptime @bitCast packed struct to int and back" {
340 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
287341 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
288342 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
289343 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
......@@ -304,6 +358,37 @@ test "comptime @bitCast packed struct to int" {
304358 vectorf: @Vector(2, f16) = .{ 3.14, 2.71 },
305359 };
306360 const Int = @typeInfo(S).Struct.backing_integer.?;
361
362 // S -> Int
307363 var s: S = .{};
308364 try expectEqual(@bitCast(Int, s), comptime @bitCast(Int, S{}));
365
366 // Int -> S
367 var i: Int = 0;
368 const rt_cast = @bitCast(S, i);
369 const ct_cast = comptime @bitCast(S, @as(Int, 0));
370 inline for (@typeInfo(S).Struct.fields) |field| {
371 if (@typeInfo(field.field_type) == .Vector)
372 continue; //TODO: https://github.com/ziglang/zig/issues/13201
373
374 try expectEqual(@field(rt_cast, field.name), @field(ct_cast, field.name));
375 }
376}
377
378test "comptime bitcast with fields following a float" {
379 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO: https://github.com/ziglang/zig/issues/13214
380
381 const FloatT = extern struct { f: f80, x: u128 };
382 var x: FloatT = .{ .f = 0.5, .x = 123 };
383 try expect(@bitCast(u256, x) == comptime @bitCast(u256, @as(FloatT, .{ .f = 0.5, .x = 123 })));
384}
385
386test "bitcast vector to integer and back" {
387 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO: https://github.com/ziglang/zig/issues/13220
388 if (builtin.zig_backend == .stage1) return error.SkipZigTest; // stage1 gets the comptime cast wrong
389
390 const arr: [16]bool = [_]bool{ true, false } ++ [_]bool{true} ** 14;
391 var x = @splat(16, true);
392 x[1] = false;
393 try expect(@bitCast(u16, x) == comptime @bitCast(u16, @as(@Vector(16, bool), arr)));
309394}