From 70d7f87be00aa1a372c856759948fd62666be295 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Thu, 10 Feb 2022 13:29:48 -0700 Subject: [PATCH 1/6] Fix up sign handling and add arbitrary-length integer support to @bitCast() --- lib/std/math/big/int.zig | 174 +++++++++++++++++++++++++++------- lib/std/math/big/int_test.zig | 25 +++++ src/value.zig | 5 +- test/behavior/bitcast.zig | 107 ++++++++++++++++++++- 4 files changed, 270 insertions(+), 41 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 87a62bf66c..1c6404fb3c 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -1,4 +1,5 @@ const std = @import("../../std.zig"); +const builtin = @import("builtin"); const math = std.math; const Limb = std.math.big.Limb; const limb_bits = @typeInfo(Limb).Int.bits; @@ -14,6 +15,7 @@ const minInt = std.math.minInt; const assert = std.debug.assert; const Endian = std.builtin.Endian; const Signedness = std.builtin.Signedness; +const native_endian = builtin.cpu.arch.endian(); const debug_safety = false; @@ -1621,6 +1623,15 @@ pub const Mutable = struct { } } + /// Read the value of `x` from `buffer` + /// Asserts that `buffer` and `bit_count` are large enough to store the value. + /// + /// For integers with a well-defined layout (e.g. all power-of-two integers), this function + /// reads from `buffer` as if it were the contents of @ptrCast([]const u8, &x), where the + /// slice length is taken to be @sizeOf(std.meta.Int(signedness, )) + /// + /// For integers with a non-well-defined layout, `buffer` must have been created by + /// writeTwosComplement. pub fn readTwosComplement( x: *Mutable, buffer: []const u8, @@ -1634,26 +1645,77 @@ pub const Mutable = struct { x.positive = true; return; } - // zig fmt: off - switch (signedness) { - .signed => { - if (bit_count <= 8) return x.set(mem.readInt( i8, buffer[0.. 1], endian)); - if (bit_count <= 16) return x.set(mem.readInt( i16, buffer[0.. 2], endian)); - if (bit_count <= 32) return x.set(mem.readInt( i32, buffer[0.. 4], endian)); - if (bit_count <= 64) return x.set(mem.readInt( i64, buffer[0.. 8], endian)); - if (bit_count <= 128) return x.set(mem.readInt(i128, buffer[0..16], endian)); - }, - .unsigned => { - if (bit_count <= 8) return x.set(mem.readInt( u8, buffer[0.. 1], endian)); - if (bit_count <= 16) return x.set(mem.readInt( u16, buffer[0.. 2], endian)); - if (bit_count <= 32) return x.set(mem.readInt( u32, buffer[0.. 4], endian)); - if (bit_count <= 64) return x.set(mem.readInt( u64, buffer[0.. 8], endian)); - if (bit_count <= 128) return x.set(mem.readInt(u128, buffer[0..16], endian)); - }, - } - // zig fmt: on - @panic("TODO implement std lib big int readTwosComplement"); + // byte_count is the total amount of bytes to read from buffer + var byte_count = @sizeOf(Limb) * (bit_count / @bitSizeOf(Limb)); + if (bit_count % @bitSizeOf(Limb) != 0) { // Round up to a power-of-two integer <= Limb + byte_count += (std.math.ceilPowerOfTwoAssert(usize, bit_count % @bitSizeOf(Limb)) + 7) / 8; + } + + const limb_count = calcTwosCompLimbCount(8 * byte_count); + + // Check whether the input is negative + var positive = true; + if (signedness == .signed) { + var last_byte = switch (endian) { + .Little => ((bit_count + 7) / 8) - 1, + .Big => byte_count - ((bit_count + 7) / 8), + }; + + const sign_bit = @as(u8, 1) << @intCast(u3, (bit_count - 1) % 8); + positive = ((buffer[last_byte] & sign_bit) == 0); + } + + // Copy all complete limbs + var carry: u1 = if (positive) 0 else 1; + var limb_index: usize = 0; + while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) { + var buf_index = switch (endian) { + .Little => @sizeOf(Limb) * limb_index, + .Big => byte_count - (limb_index + 1) * @sizeOf(Limb), + }; + + const limb_buf = @ptrCast(*const [@sizeOf(Limb)]u8, buffer[buf_index..]); + var limb = mem.readInt(Limb, limb_buf, endian); + + // 2's complement (bitwise not, then add carry bit) + if (!positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb)); + x.limbs[limb_index] = limb; + } + + // Copy any remaining bytes, using the nearest power-of-two integer that is large enough + const bits_left = @intCast(Log2Limb, bit_count % @bitSizeOf(Limb)); + if (bits_left != 0) { + const bytes_read = limb_index * @sizeOf(Limb); + const bytes_left = byte_count - bytes_read; + var buffer_left = switch (endian) { + .Little => buffer[bytes_read..], + .Big => buffer[0..], + }; + + var limb = @intCast(Limb, blk: { + // zig fmt: off + if (bytes_left == 1) break :blk mem.readInt( u8, buffer_left[0.. 1], endian); + if (bytes_left == 2) break :blk mem.readInt( u16, buffer_left[0.. 2], endian); + if (bytes_left == 4) break :blk mem.readInt( u32, buffer_left[0.. 4], endian); + if (bytes_left == 8) break :blk mem.readInt( u64, buffer_left[0.. 8], endian); + if (bytes_left == 16) break :blk mem.readInt(u128, buffer_left[0..16], endian); + // zig fmt: on + unreachable; + }); + + // 2's complement (bitwise not, then add carry bit) + if (!positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb); + + // Mask off any unused bits + const mask = (@as(Limb, 1) << bits_left) -% 1; // 0b0..01..1 with (bits_left) trailing ones + limb &= mask; + + x.limbs[limb_count - 1] = limb; + } + x.positive = positive; + x.len = limb_count; + x.normalize(x.len); } /// Normalize a possible sequence of leading zeros. @@ -1806,7 +1868,7 @@ pub const Const = struct { .Int => |info| { const UT = std.meta.Int(.unsigned, info.bits); - if (self.bitCountTwosComp() > info.bits) { + if (!self.fitsInTwosComp(info.signedness, info.bits)) { return error.TargetTooSmall; } @@ -2013,27 +2075,69 @@ pub const Const = struct { return s.len; } + /// Write the value of `x` into `buffer` /// Asserts that `buffer` and `bit_count` are large enough to store the value. + /// + /// For integers with a well-defined layout (e.g. all power-of-two integers), this function + /// can be thought of as writing to `buffer` the contents of @ptrCast([]const u8, &x), + /// where the slice length is taken to be @sizeOf(std.meta.Int(_,)) + /// + /// For integers with a non-well-defined layout, the only requirement is that readTwosComplement + /// on the same buffer creates an equivalent big integer. pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, endian: Endian) void { if (bit_count == 0) return; - // zig fmt: off - if (x.positive) { - if (bit_count <= 8) return mem.writeInt( u8, buffer[0.. 1], x.to( u8) catch unreachable, endian); - if (bit_count <= 16) return mem.writeInt( u16, buffer[0.. 2], x.to( u16) catch unreachable, endian); - if (bit_count <= 32) return mem.writeInt( u32, buffer[0.. 4], x.to( u32) catch unreachable, endian); - if (bit_count <= 64) return mem.writeInt( u64, buffer[0.. 8], x.to( u64) catch unreachable, endian); - if (bit_count <= 128) return mem.writeInt(u128, buffer[0..16], x.to(u128) catch unreachable, endian); - } else { - if (bit_count <= 8) return mem.writeInt( i8, buffer[0.. 1], x.to( i8) catch unreachable, endian); - if (bit_count <= 16) return mem.writeInt( i16, buffer[0.. 2], x.to( i16) catch unreachable, endian); - if (bit_count <= 32) return mem.writeInt( i32, buffer[0.. 4], x.to( i32) catch unreachable, endian); - if (bit_count <= 64) return mem.writeInt( i64, buffer[0.. 8], x.to( i64) catch unreachable, endian); - if (bit_count <= 128) return mem.writeInt(i128, buffer[0..16], x.to(i128) catch unreachable, endian); + var byte_count = @sizeOf(Limb) * (bit_count / @bitSizeOf(Limb)); + if (bit_count % @bitSizeOf(Limb) != 0) { + byte_count += (std.math.ceilPowerOfTwoAssert(usize, bit_count % @bitSizeOf(Limb)) + 7) / 8; } - // zig fmt: on + assert(buffer.len >= byte_count); + assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count)); - @panic("TODO implement std lib big int writeTwosComplement for larger than 128 bits"); + // Copy all complete limbs + var carry: u1 = if (x.positive) 0 else 1; + var limb_index: usize = 0; + while (limb_index < byte_count / @sizeOf(Limb)) : (limb_index += 1) { + var buf_index = switch (endian) { + .Little => @sizeOf(Limb) * limb_index, + .Big => byte_count - (limb_index + 1) * @sizeOf(Limb), + }; + + var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; + // 2's complement (bitwise not, then add carry bit) + if (!x.positive) carry = @boolToInt(@addWithOverflow(Limb, ~limb, carry, &limb)); + + var limb_buf = @ptrCast(*[@sizeOf(Limb)]u8, buffer[buf_index..]); + mem.writeInt(Limb, limb_buf, limb, endian); + } + + // Copy any remaining bytes + if (byte_count % @sizeOf(Limb) != 0) { + const bytes_read = limb_index * @sizeOf(Limb); + const bytes_left = byte_count - bytes_read; + var buffer_left = switch (endian) { + .Little => buffer[bytes_read..], + .Big => buffer[0..], + }; + + var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; + // 2's complement (bitwise not, then add carry bit) + if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb); + + if (bytes_left == 1) { + mem.writeInt(u8, buffer_left[0..1], @truncate(u8, limb), endian); + } else if (@sizeOf(Limb) > 1 and bytes_left == 2) { + mem.writeInt(u16, buffer_left[0..2], @truncate(u16, limb), endian); + } else if (@sizeOf(Limb) > 2 and bytes_left == 4) { + mem.writeInt(u32, buffer_left[0..4], @truncate(u32, limb), endian); + } else if (@sizeOf(Limb) > 4 and bytes_left == 8) { + mem.writeInt(u64, buffer_left[0..8], @truncate(u64, limb), endian); + } else if (@sizeOf(Limb) > 8 and bytes_left == 16) { + mem.writeInt(u128, buffer_left[0..16], @truncate(u128, limb), endian); + } else if (@sizeOf(Limb) > 16) { + @compileError("@sizeOf(Limb) exceeded supported range"); + } else unreachable; + } } /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 70a9b97a38..f6f210f56c 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -2486,3 +2486,28 @@ test "big int popcount" { try testing.expect(a.toConst().orderAgainstScalar(16) == .eq); } + +test "big int conversion read/write twos complement" { + var a = try Managed.initSet(testing.allocator, (1 << 493) - 1); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, (1 << 493) - 1); + defer b.deinit(); + var m = b.toMutable(); + + var buffer1 = try testing.allocator.alloc(u8, 64); + defer testing.allocator.free(buffer1); + + const endians = [_]std.builtin.Endian{ .Little, .Big }; + + for (endians) |endian| { + // Writing to buffer and back should not change anything + a.toConst().writeTwosComplement(buffer1, 493, endian); + m.readTwosComplement(buffer1, 493, endian, .unsigned); + try testing.expect(m.toConst().order(a.toConst()) == .eq); + + // Equivalent to @bitCast(i493, @as(u493, intMax(u493)) + a.toConst().writeTwosComplement(buffer1, 493, endian); + m.readTwosComplement(buffer1, 493, endian, .signed); + try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq); + } +} diff --git a/src/value.zig b/src/value.zig index 33a75e08bb..2018eb3df3 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1093,8 +1093,9 @@ pub const Value = extern union { .Int => { const int_info = ty.intInfo(target); const endian = target.cpu.arch.endian(); - // TODO use a correct amount of limbs - const limbs_buffer = try arena.alloc(std.math.big.Limb, 2); + const Limb = std.math.big.Limb; + const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb); + const limbs_buffer = try arena.alloc(Limb, limb_count); var bigint = BigIntMutable.init(limbs_buffer, 0); bigint.readTwosComplement(buffer, int_info.bits, endian, int_info.signedness); return fromBigInt(arena, bigint.toConst()); diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index d56e3c1c53..43d6524a4e 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const maxInt = std.math.maxInt; +const minInt = std.math.minInt; const native_endian = builtin.target.cpu.arch.endian(); test "@bitCast i32 -> u32" { @@ -11,21 +12,119 @@ test "@bitCast i32 -> u32" { } fn testBitCast_i32_u32() !void { - try expect(conv(-1) == maxInt(u32)); - try expect(conv2(maxInt(u32)) == -1); + try expect(conv_i32(-1) == maxInt(u32)); + try expect(conv_u32(maxInt(u32)) == -1); + try expect(conv_u32(0x8000_0000) == minInt(i32)); + try expect(conv_i32(minInt(i32)) == 0x8000_0000); } -fn conv(x: i32) u32 { +fn conv_i32(x: i32) u32 { return @bitCast(u32, x); } -fn conv2(x: u32) i32 { +fn conv_u32(x: u32) i32 { return @bitCast(i32, x); } +test "@bitCast i48 -> u48" { + try testBitCast_i48_u48(); + comptime try testBitCast_i48_u48(); +} + +fn testBitCast_i48_u48() !void { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + + try expect(conv_i48(-1) == maxInt(u48)); + try expect(conv_u48(maxInt(u48)) == -1); + try expect(conv_u48(0x8000_0000_0000) == minInt(i48)); + try expect(conv_i48(minInt(i48)) == 0x8000_0000_0000); +} + +fn conv_i48(x: i48) u48 { + return @bitCast(u48, x); +} + +fn conv_u48(x: u48) i48 { + return @bitCast(i48, x); +} + +test "@bitCast i27 -> u27" { + try testBitCast_i27_u27(); + comptime try testBitCast_i27_u27(); +} + +fn testBitCast_i27_u27() !void { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + + try expect(conv_i27(-1) == maxInt(u27)); + try expect(conv_u27(maxInt(u27)) == -1); + try expect(conv_u27(0x400_0000) == minInt(i27)); + try expect(conv_i27(minInt(i27)) == 0x400_0000); +} + +fn conv_i27(x: i27) u27 { + return @bitCast(u27, x); +} + +fn conv_u27(x: u27) i27 { + return @bitCast(i27, x); +} + +test "@bitCast i512 -> u512" { + try testBitCast_i512_u512(); + comptime try testBitCast_i512_u512(); +} + +fn testBitCast_i512_u512() !void { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + + try expect(conv_i512(-1) == maxInt(u512)); + try expect(conv_u512(maxInt(u512)) == -1); + try expect(conv_u512(@as(u512, 1) << 511) == minInt(i512)); + try expect(conv_i512(minInt(i512)) == (@as(u512, 1) << 511)); +} + +fn conv_i512(x: i512) u512 { + return @bitCast(u512, x); +} + +fn conv_u512(x: u512) i512 { + return @bitCast(i512, x); +} + test "bitcast result to _" { _ = @bitCast(u8, @as(i8, 1)); } +test "@bitCast i493 -> u493" { + try testBitCast_i493_u493(); + comptime try testBitCast_i493_u493(); +} + +fn testBitCast_i493_u493() !void { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + + try expect(conv_i493(-1) == maxInt(u493)); + try expect(conv_u493(maxInt(u493)) == -1); + try expect(conv_u493(@as(u493, 1) << 492) == minInt(i493)); + try expect(conv_i493(minInt(i493)) == (@as(u493, 1) << 492)); +} + +fn conv_i493(x: i493) u493 { + return @bitCast(u493, x); +} + +fn conv_u493(x: u493) i493 { + return @bitCast(i493, x); +} + test "nested bitcast" { const S = struct { fn moo(x: isize) !void { From eeb043f5833db03ac3250f9943ba8be0b518432f Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 11 Feb 2022 08:46:01 -0700 Subject: [PATCH 2/6] Fix big-endian handling in stage1 bigint_write_twos_complement --- src/stage1/bigint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stage1/bigint.cpp b/src/stage1/bigint.cpp index eab0f037cf..e10af08bc4 100644 --- a/src/stage1/bigint.cpp +++ b/src/stage1/bigint.cpp @@ -313,12 +313,12 @@ void bigint_write_twos_complement(const BigInt *big_int, uint8_t *buf, size_t bi } if (digit_index == 0) break; - digit_index -= 1; if (digit_index == last_digit_index) { buf_index += bytes_in_last_digit; } else { buf_index += 8; } + digit_index -= 1; } } else { size_t digit_count = (bit_count + 63) / 64; From 7b72fc6bbc5554643bc27933310899e32783b81b Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Sun, 13 Feb 2022 11:54:37 -0700 Subject: [PATCH 3/6] Add `abi_size` parameter to read/writeTwosComplement Big-int functions were updated to respect the provided abi_size, rather than inferring a potentially incorrect abi_size implicitly. In combination with the convention that any required padding bits are added on the MSB end, this means that exotic integers can potentially have a well-defined memory layout. --- lib/std/math/big/int.zig | 141 +++++++++++++++++----------------- lib/std/math/big/int_test.zig | 95 ++++++++++++++++++++++- src/value.zig | 9 ++- 3 files changed, 166 insertions(+), 79 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 1c6404fb3c..731f4b5456 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -1624,18 +1624,16 @@ pub const Mutable = struct { } /// Read the value of `x` from `buffer` - /// Asserts that `buffer` and `bit_count` are large enough to store the value. + /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value. /// - /// For integers with a well-defined layout (e.g. all power-of-two integers), this function - /// reads from `buffer` as if it were the contents of @ptrCast([]const u8, &x), where the - /// slice length is taken to be @sizeOf(std.meta.Int(signedness, )) - /// - /// For integers with a non-well-defined layout, `buffer` must have been created by - /// writeTwosComplement. + /// The contents of `buffer` are interpreted as if they were the contents of + /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian` + /// and any required padding bits are expected on the MSB end. pub fn readTwosComplement( x: *Mutable, buffer: []const u8, bit_count: usize, + abi_size: usize, endian: Endian, signedness: Signedness, ) void { @@ -1646,20 +1644,18 @@ pub const Mutable = struct { return; } - // byte_count is the total amount of bytes to read from buffer - var byte_count = @sizeOf(Limb) * (bit_count / @bitSizeOf(Limb)); - if (bit_count % @bitSizeOf(Limb) != 0) { // Round up to a power-of-two integer <= Limb - byte_count += (std.math.ceilPowerOfTwoAssert(usize, bit_count % @bitSizeOf(Limb)) + 7) / 8; - } - - const limb_count = calcTwosCompLimbCount(8 * byte_count); + // byte_count is our total read size: it cannot exceed abi_size, + // but may be less as long as it includes the required bits + const limb_count = calcTwosCompLimbCount(bit_count); + const byte_count = std.math.min(abi_size, @sizeOf(Limb) * limb_count); + assert(8 * byte_count >= bit_count); // Check whether the input is negative var positive = true; if (signedness == .signed) { var last_byte = switch (endian) { .Little => ((bit_count + 7) / 8) - 1, - .Big => byte_count - ((bit_count + 7) / 8), + .Big => abi_size - ((bit_count + 7) / 8), }; const sign_bit = @as(u8, 1) << @intCast(u3, (bit_count - 1) % 8); @@ -1672,7 +1668,7 @@ pub const Mutable = struct { while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) { var buf_index = switch (endian) { .Little => @sizeOf(Limb) * limb_index, - .Big => byte_count - (limb_index + 1) * @sizeOf(Limb), + .Big => abi_size - (limb_index + 1) * @sizeOf(Limb), }; const limb_buf = @ptrCast(*const [@sizeOf(Limb)]u8, buffer[buf_index..]); @@ -1683,32 +1679,34 @@ pub const Mutable = struct { x.limbs[limb_index] = limb; } - // Copy any remaining bytes, using the nearest power-of-two integer that is large enough - const bits_left = @intCast(Log2Limb, bit_count % @bitSizeOf(Limb)); - if (bits_left != 0) { - const bytes_read = limb_index * @sizeOf(Limb); - const bytes_left = byte_count - bytes_read; - var buffer_left = switch (endian) { - .Little => buffer[bytes_read..], - .Big => buffer[0..], - }; + // Copy the remaining N bytes (N <= @sizeOf(Limb)) + var bytes_read = limb_index * @sizeOf(Limb); + if (bytes_read != byte_count) { + var limb: Limb = 0; - var limb = @intCast(Limb, blk: { - // zig fmt: off - if (bytes_left == 1) break :blk mem.readInt( u8, buffer_left[0.. 1], endian); - if (bytes_left == 2) break :blk mem.readInt( u16, buffer_left[0.. 2], endian); - if (bytes_left == 4) break :blk mem.readInt( u32, buffer_left[0.. 4], endian); - if (bytes_left == 8) break :blk mem.readInt( u64, buffer_left[0.. 8], endian); - if (bytes_left == 16) break :blk mem.readInt(u128, buffer_left[0..16], endian); - // zig fmt: on - unreachable; - }); + while (bytes_read != byte_count) { + const read_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_read); + var int_buffer = switch (endian) { + .Little => buffer[bytes_read..], + .Big => buffer[(abi_size - bytes_read - read_size)..], + }; + limb |= @intCast(Limb, switch (read_size) { + 1 => mem.readInt(u8, int_buffer[0..1], endian), + 2 => mem.readInt(u16, int_buffer[0..2], endian), + 4 => mem.readInt(u32, int_buffer[0..4], endian), + 8 => mem.readInt(u64, int_buffer[0..8], endian), + 16 => mem.readInt(u128, int_buffer[0..16], endian), + else => unreachable, + }) << @intCast(Log2Limb, 8 * (bytes_read % @sizeOf(Limb))); + bytes_read += read_size; + } // 2's complement (bitwise not, then add carry bit) if (!positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb); // Mask off any unused bits - const mask = (@as(Limb, 1) << bits_left) -% 1; // 0b0..01..1 with (bits_left) trailing ones + const valid_bits = @intCast(Log2Limb, bit_count % @bitSizeOf(Limb)); + const mask = (@as(Limb, 1) << valid_bits) -% 1; // 0b0..01..1 with (valid_bits_in_limb) trailing ones limb &= mask; x.limbs[limb_count - 1] = limb; @@ -2076,21 +2074,16 @@ pub const Const = struct { } /// Write the value of `x` into `buffer` - /// Asserts that `buffer` and `bit_count` are large enough to store the value. + /// Asserts that `buffer`, `abi_size`, and `bit_count` are large enough to store the value. /// - /// For integers with a well-defined layout (e.g. all power-of-two integers), this function - /// can be thought of as writing to `buffer` the contents of @ptrCast([]const u8, &x), - /// where the slice length is taken to be @sizeOf(std.meta.Int(_,)) - /// - /// For integers with a non-well-defined layout, the only requirement is that readTwosComplement - /// on the same buffer creates an equivalent big integer. - pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, endian: Endian) void { - if (bit_count == 0) return; + /// `buffer` is filled so that its contents match what would be observed via + /// @ptrCast(*[abi_size]const u8, &x). Byte ordering is determined by `endian`, + /// and any required padding bits are added on the MSB end. + pub fn writeTwosComplement(x: Const, buffer: []u8, bit_count: usize, abi_size: usize, endian: Endian) void { - var byte_count = @sizeOf(Limb) * (bit_count / @bitSizeOf(Limb)); - if (bit_count % @bitSizeOf(Limb) != 0) { - byte_count += (std.math.ceilPowerOfTwoAssert(usize, bit_count % @bitSizeOf(Limb)) + 7) / 8; - } + // byte_count is our total write size + const byte_count = abi_size; + assert(8 * byte_count >= bit_count); assert(buffer.len >= byte_count); assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count)); @@ -2100,7 +2093,7 @@ pub const Const = struct { while (limb_index < byte_count / @sizeOf(Limb)) : (limb_index += 1) { var buf_index = switch (endian) { .Little => @sizeOf(Limb) * limb_index, - .Big => byte_count - (limb_index + 1) * @sizeOf(Limb), + .Big => abi_size - (limb_index + 1) * @sizeOf(Limb), }; var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; @@ -2111,32 +2104,36 @@ pub const Const = struct { mem.writeInt(Limb, limb_buf, limb, endian); } - // Copy any remaining bytes - if (byte_count % @sizeOf(Limb) != 0) { - const bytes_read = limb_index * @sizeOf(Limb); - const bytes_left = byte_count - bytes_read; - var buffer_left = switch (endian) { - .Little => buffer[bytes_read..], - .Big => buffer[0..], - }; - + // Copy the remaining N bytes (N < @sizeOf(Limb)) + var bytes_written = limb_index * @sizeOf(Limb); + if (bytes_written != byte_count) { var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; // 2's complement (bitwise not, then add carry bit) if (!x.positive) _ = @addWithOverflow(Limb, ~limb, carry, &limb); - if (bytes_left == 1) { - mem.writeInt(u8, buffer_left[0..1], @truncate(u8, limb), endian); - } else if (@sizeOf(Limb) > 1 and bytes_left == 2) { - mem.writeInt(u16, buffer_left[0..2], @truncate(u16, limb), endian); - } else if (@sizeOf(Limb) > 2 and bytes_left == 4) { - mem.writeInt(u32, buffer_left[0..4], @truncate(u32, limb), endian); - } else if (@sizeOf(Limb) > 4 and bytes_left == 8) { - mem.writeInt(u64, buffer_left[0..8], @truncate(u64, limb), endian); - } else if (@sizeOf(Limb) > 8 and bytes_left == 16) { - mem.writeInt(u128, buffer_left[0..16], @truncate(u128, limb), endian); - } else if (@sizeOf(Limb) > 16) { - @compileError("@sizeOf(Limb) exceeded supported range"); - } else unreachable; + while (bytes_written != byte_count) { + const write_size = std.math.floorPowerOfTwo(usize, byte_count - bytes_written); + var int_buffer = switch (endian) { + .Little => buffer[bytes_written..], + .Big => buffer[(abi_size - bytes_written - write_size)..], + }; + + if (write_size == 1) { + mem.writeInt(u8, int_buffer[0..1], @truncate(u8, limb), endian); + } else if (@sizeOf(Limb) >= 2 and write_size == 2) { + mem.writeInt(u16, int_buffer[0..2], @truncate(u16, limb), endian); + } else if (@sizeOf(Limb) >= 4 and write_size == 4) { + mem.writeInt(u32, int_buffer[0..4], @truncate(u32, limb), endian); + } else if (@sizeOf(Limb) >= 8 and write_size == 8) { + mem.writeInt(u64, int_buffer[0..8], @truncate(u64, limb), endian); + } else if (@sizeOf(Limb) >= 16 and write_size == 16) { + mem.writeInt(u128, int_buffer[0..16], @truncate(u128, limb), endian); + } else if (@sizeOf(Limb) >= 32) { + @compileError("@sizeOf(Limb) exceeded supported range"); + } else unreachable; + limb >>= @intCast(Log2Limb, 8 * write_size); + bytes_written += write_size; + } } } diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index f6f210f56c..69600425a4 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -2498,16 +2498,103 @@ test "big int conversion read/write twos complement" { defer testing.allocator.free(buffer1); const endians = [_]std.builtin.Endian{ .Little, .Big }; + const abi_size = 64; for (endians) |endian| { // Writing to buffer and back should not change anything - a.toConst().writeTwosComplement(buffer1, 493, endian); - m.readTwosComplement(buffer1, 493, endian, .unsigned); + a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian); + m.readTwosComplement(buffer1, 493, abi_size, endian, .unsigned); try testing.expect(m.toConst().order(a.toConst()) == .eq); // Equivalent to @bitCast(i493, @as(u493, intMax(u493)) - a.toConst().writeTwosComplement(buffer1, 493, endian); - m.readTwosComplement(buffer1, 493, endian, .signed); + a.toConst().writeTwosComplement(buffer1, 493, abi_size, endian); + m.readTwosComplement(buffer1, 493, abi_size, endian, .signed); try testing.expect(m.toConst().orderAgainstScalar(-1) == .eq); } } + +test "big int conversion read twos complement with padding" { + var a = try Managed.initSet(testing.allocator, 0x01_02030405_06070809_0a0b0c0d); + defer a.deinit(); + + var buffer1 = try testing.allocator.alloc(u8, 16); + defer testing.allocator.free(buffer1); + @memset(buffer1.ptr, 0xaa, buffer1.len); + + // writeTwosComplement: + // (1) should not write beyond buffer[0..abi_size] + // (2) should correctly order bytes based on the provided endianness + // (3) should sign-extend any bits from bit_count to 8 * abi_size + + var bit_count: usize = 12 * 8 + 1; + a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); + 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 })); + a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); + 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 })); + a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); + 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 })); + a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); + 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 })); + + @memset(buffer1.ptr, 0xaa, buffer1.len); + try a.set(-0x01_02030405_06070809_0a0b0c0d); + bit_count = 12 * 8 + 2; + + a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); + 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 })); + a.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); + 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 })); + a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); + 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 })); + a.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); + 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 })); +} + +test "big int conversion write twos complement with padding" { + var a = try Managed.initSet(testing.allocator, 0x01_ffffffff_ffffffff_ffffffff); + defer a.deinit(); + + var m = a.toMutable(); + + // readTwosComplement: + // (1) should not read beyond buffer[0..abi_size] + // (2) should correctly interpret bytes based on the provided endianness + // (3) should ignore any bits from bit_count to 8 * abi_size + + var bit_count: usize = 12 * 8 + 1; + var buffer: []const u8 = undefined; + + buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xb }; + m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); + + buffer = &[_]u8{ 0xb, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd }; + m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); + + buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xab, 0xaa, 0xaa, 0xaa }; + m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); + + buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0xab, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd }; + m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); + + bit_count = 12 * 8 + 2; + + buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02 }; + m.readTwosComplement(buffer, bit_count, 13, .Little, .signed); + try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); + + buffer = &[_]u8{ 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 }; + m.readTwosComplement(buffer, bit_count, 13, .Big, .signed); + try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); + + buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02, 0xaa, 0xaa, 0xaa }; + m.readTwosComplement(buffer, bit_count, 16, .Little, .signed); + try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); + + buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 }; + m.readTwosComplement(buffer, bit_count, 16, .Big, .signed); + try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); +} diff --git a/src/value.zig b/src/value.zig index 2018eb3df3..a3dd6501e4 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1046,7 +1046,8 @@ pub const Value = extern union { var bigint_buffer: BigIntSpace = undefined; const bigint = val.toBigInt(&bigint_buffer); const bits = ty.intInfo(target).bits; - bigint.writeTwosComplement(buffer, bits, target.cpu.arch.endian()); + const abi_size = ty.abiSize(target); + bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian()); }, .Enum => { var enum_buffer: Payload.U64 = undefined; @@ -1054,7 +1055,8 @@ pub const Value = extern union { var bigint_buffer: BigIntSpace = undefined; const bigint = int_val.toBigInt(&bigint_buffer); const bits = ty.intInfo(target).bits; - bigint.writeTwosComplement(buffer, bits, target.cpu.arch.endian()); + const abi_size = ty.abiSize(target); + bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian()); }, .Float => switch (ty.floatBits(target)) { 16 => return floatWriteToMemory(f16, val.toFloat(f16), target, buffer), @@ -1096,8 +1098,9 @@ pub const Value = extern union { const Limb = std.math.big.Limb; const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb); const limbs_buffer = try arena.alloc(Limb, limb_count); + const abi_size = ty.abiSize(target); var bigint = BigIntMutable.init(limbs_buffer, 0); - bigint.readTwosComplement(buffer, int_info.bits, endian, int_info.signedness); + bigint.readTwosComplement(buffer, int_info.bits, abi_size, endian, int_info.signedness); return fromBigInt(arena, bigint.toConst()); }, .Float => switch (ty.floatBits(target)) { From c586e3ba1bbc7784aee22bf96f69b8003611112a Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Sun, 13 Feb 2022 11:59:14 -0700 Subject: [PATCH 4/6] Add additional tests for `@bitCast` --- lib/std/math/big/int_test.zig | 91 ++++++++++++++++++++++ test/behavior/bitcast.zig | 140 +++++++++------------------------- 2 files changed, 126 insertions(+), 105 deletions(-) diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 69600425a4..e7469326e4 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -2550,6 +2550,43 @@ test "big int conversion read twos complement with padding" { 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 })); } +test "big int write twos complement +/- zero" { + var a = try Managed.initSet(testing.allocator, 0x0); + defer a.deinit(); + var m = a.toMutable(); + + var buffer1 = try testing.allocator.alloc(u8, 16); + defer testing.allocator.free(buffer1); + @memset(buffer1.ptr, 0xaa, buffer1.len); + + var bit_count: usize = 0; + + // Test zero + + m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); + m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); + + @memset(buffer1.ptr, 0xaa, buffer1.len); + m.positive = false; + + // Test negative zero + + m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Little); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + m.toConst().writeTwosComplement(buffer1, bit_count, 13, .Big); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Little); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); + m.toConst().writeTwosComplement(buffer1, bit_count, 16, .Big); + try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); +} + test "big int conversion write twos complement with padding" { var a = try Managed.initSet(testing.allocator, 0x01_ffffffff_ffffffff_ffffffff); defer a.deinit(); @@ -2564,6 +2601,8 @@ test "big int conversion write twos complement with padding" { var bit_count: usize = 12 * 8 + 1; var buffer: []const u8 = undefined; + // Test 0x01_02030405_06070809_0a0b0c0d + buffer = &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0xb }; m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); try testing.expect(m.toConst().orderAgainstScalar(0x01_02030405_06070809_0a0b0c0d) == .eq); @@ -2582,6 +2621,8 @@ test "big int conversion write twos complement with padding" { bit_count = 12 * 8 + 2; + // Test -0x01_02030405_06070809_0a0b0c0d + buffer = &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0x02 }; m.readTwosComplement(buffer, bit_count, 13, .Little, .signed); try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); @@ -2597,4 +2638,54 @@ test "big int conversion write twos complement with padding" { buffer = &[_]u8{ 0xaa, 0xaa, 0xaa, 0x02, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 }; m.readTwosComplement(buffer, bit_count, 16, .Big, .signed); try testing.expect(m.toConst().orderAgainstScalar(-0x01_02030405_06070809_0a0b0c0d) == .eq); + + // Test 0 + + buffer = &([_]u8{0} ** 16); + m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + + bit_count = 0; + buffer = &([_]u8{0xaa} ** 16); + m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); +} + +test "big int conversion write twos complement zero" { + var a = try Managed.initSet(testing.allocator, 0x01_ffffffff_ffffffff_ffffffff); + defer a.deinit(); + + var m = a.toMutable(); + + // readTwosComplement: + // (1) should not read beyond buffer[0..abi_size] + // (2) should correctly interpret bytes based on the provided endianness + // (3) should ignore any bits from bit_count to 8 * abi_size + + var bit_count: usize = 12 * 8 + 1; + var buffer: []const u8 = undefined; + + buffer = &([_]u8{0} ** 13); + m.readTwosComplement(buffer, bit_count, 13, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 13, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + + buffer = &([_]u8{0} ** 16); + m.readTwosComplement(buffer, bit_count, 16, .Little, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); + m.readTwosComplement(buffer, bit_count, 16, .Big, .unsigned); + try testing.expect(m.toConst().orderAgainstScalar(0x0) == .eq); } diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index 43d6524a4e..59a16c5fc9 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -6,123 +6,53 @@ const maxInt = std.math.maxInt; const minInt = std.math.minInt; const native_endian = builtin.target.cpu.arch.endian(); -test "@bitCast i32 -> u32" { - try testBitCast_i32_u32(); - comptime try testBitCast_i32_u32(); +test "@bitCast iX -> uX" { + const bit_values = [_]usize{ 8, 16, 32, 64 }; + + inline for (bit_values) |bits| { + try testBitCast(bits); + comptime try testBitCast(bits); + } } -fn testBitCast_i32_u32() !void { - try expect(conv_i32(-1) == maxInt(u32)); - try expect(conv_u32(maxInt(u32)) == -1); - try expect(conv_u32(0x8000_0000) == minInt(i32)); - try expect(conv_i32(minInt(i32)) == 0x8000_0000); -} - -fn conv_i32(x: i32) u32 { - return @bitCast(u32, x); -} -fn conv_u32(x: u32) i32 { - return @bitCast(i32, x); -} - -test "@bitCast i48 -> u48" { - try testBitCast_i48_u48(); - comptime try testBitCast_i48_u48(); -} - -fn testBitCast_i48_u48() !void { +test "@bitCast iX -> uX exotic integers" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; - try expect(conv_i48(-1) == maxInt(u48)); - try expect(conv_u48(maxInt(u48)) == -1); - try expect(conv_u48(0x8000_0000_0000) == minInt(i48)); - try expect(conv_i48(minInt(i48)) == 0x8000_0000_0000); + const bit_values = [_]usize{ 1, 48, 27, 512, 493, 293, 125, 204, 112 }; + + inline for (bit_values) |bits| { + try testBitCast(bits); + comptime try testBitCast(bits); + } } -fn conv_i48(x: i48) u48 { - return @bitCast(u48, x); +fn testBitCast(comptime N: usize) !void { + const iN = std.meta.Int(.signed, N); + const uN = std.meta.Int(.unsigned, N); + + try expect(conv_iN(N, -1) == maxInt(uN)); + try expect(conv_uN(N, maxInt(uN)) == -1); + + try expect(conv_iN(N, maxInt(iN)) == maxInt(iN)); + try expect(conv_uN(N, maxInt(iN)) == maxInt(iN)); + + try expect(conv_uN(N, 1 << (N - 1)) == minInt(iN)); + try expect(conv_iN(N, minInt(iN)) == (1 << (N - 1))); + + try expect(conv_uN(N, 0) == 0); + try expect(conv_iN(N, 0) == 0); + + try expect(conv_iN(N, -0) == 0); } -fn conv_u48(x: u48) i48 { - return @bitCast(i48, x); +fn conv_iN(comptime N: usize, x: std.meta.Int(.signed, N)) std.meta.Int(.unsigned, N) { + return @bitCast(std.meta.Int(.unsigned, N), x); } -test "@bitCast i27 -> u27" { - try testBitCast_i27_u27(); - comptime try testBitCast_i27_u27(); -} - -fn testBitCast_i27_u27() !void { - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; - - try expect(conv_i27(-1) == maxInt(u27)); - try expect(conv_u27(maxInt(u27)) == -1); - try expect(conv_u27(0x400_0000) == minInt(i27)); - try expect(conv_i27(minInt(i27)) == 0x400_0000); -} - -fn conv_i27(x: i27) u27 { - return @bitCast(u27, x); -} - -fn conv_u27(x: u27) i27 { - return @bitCast(i27, x); -} - -test "@bitCast i512 -> u512" { - try testBitCast_i512_u512(); - comptime try testBitCast_i512_u512(); -} - -fn testBitCast_i512_u512() !void { - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; - - try expect(conv_i512(-1) == maxInt(u512)); - try expect(conv_u512(maxInt(u512)) == -1); - try expect(conv_u512(@as(u512, 1) << 511) == minInt(i512)); - try expect(conv_i512(minInt(i512)) == (@as(u512, 1) << 511)); -} - -fn conv_i512(x: i512) u512 { - return @bitCast(u512, x); -} - -fn conv_u512(x: u512) i512 { - return @bitCast(i512, x); -} - -test "bitcast result to _" { - _ = @bitCast(u8, @as(i8, 1)); -} - -test "@bitCast i493 -> u493" { - try testBitCast_i493_u493(); - comptime try testBitCast_i493_u493(); -} - -fn testBitCast_i493_u493() !void { - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; - - try expect(conv_i493(-1) == maxInt(u493)); - try expect(conv_u493(maxInt(u493)) == -1); - try expect(conv_u493(@as(u493, 1) << 492) == minInt(i493)); - try expect(conv_i493(minInt(i493)) == (@as(u493, 1) << 492)); -} - -fn conv_i493(x: i493) u493 { - return @bitCast(u493, x); -} - -fn conv_u493(x: u493) i493 { - return @bitCast(i493, x); +fn conv_uN(comptime N: usize, x: std.meta.Int(.unsigned, N)) std.meta.Int(.signed, N) { + return @bitCast(std.meta.Int(.signed, N), x); } test "nested bitcast" { From 45aed7171c5cafb06671714ac1b15fdcf056d040 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Sun, 13 Feb 2022 13:01:55 -0700 Subject: [PATCH 5/6] Skip 8/16-bit `@bitCast` test for wasm --- test/behavior/bitcast.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index 59a16c5fc9..1bf33af57b 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -6,8 +6,18 @@ const maxInt = std.math.maxInt; const minInt = std.math.minInt; const native_endian = builtin.target.cpu.arch.endian(); -test "@bitCast iX -> uX" { - const bit_values = [_]usize{ 8, 16, 32, 64 }; +test "@bitCast iX -> uX (32, 64)" { + const bit_values = [_]usize{ 32, 64 }; + + inline for (bit_values) |bits| { + try testBitCast(bits); + comptime try testBitCast(bits); + } +} + +test "@bitCast iX -> uX (8, 16, 128)" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + const bit_values = [_]usize{ 8, 16, 128 }; inline for (bit_values) |bits| { try testBitCast(bits); From 7edf3d9f2d53f5b1c5e31ee1294ff9b52337760b Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Sun, 13 Feb 2022 14:16:40 -0700 Subject: [PATCH 6/6] Cast abi_size to usize --- src/value.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/value.zig b/src/value.zig index a3dd6501e4..d07179359f 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1046,7 +1046,7 @@ pub const Value = extern union { var bigint_buffer: BigIntSpace = undefined; const bigint = val.toBigInt(&bigint_buffer); const bits = ty.intInfo(target).bits; - const abi_size = ty.abiSize(target); + const abi_size = @intCast(usize, ty.abiSize(target)); bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian()); }, .Enum => { @@ -1055,7 +1055,7 @@ pub const Value = extern union { var bigint_buffer: BigIntSpace = undefined; const bigint = int_val.toBigInt(&bigint_buffer); const bits = ty.intInfo(target).bits; - const abi_size = ty.abiSize(target); + const abi_size = @intCast(usize, ty.abiSize(target)); bigint.writeTwosComplement(buffer, bits, abi_size, target.cpu.arch.endian()); }, .Float => switch (ty.floatBits(target)) { @@ -1098,7 +1098,7 @@ pub const Value = extern union { const Limb = std.math.big.Limb; const limb_count = (buffer.len + @sizeOf(Limb) - 1) / @sizeOf(Limb); const limbs_buffer = try arena.alloc(Limb, limb_count); - const abi_size = ty.abiSize(target); + const abi_size = @intCast(usize, ty.abiSize(target)); var bigint = BigIntMutable.init(limbs_buffer, 0); bigint.readTwosComplement(buffer, int_info.bits, abi_size, endian, int_info.signedness); return fromBigInt(arena, bigint.toConst());