Files
2026-04-18 18:47:29 +02:00

1128 lines
37 KiB
Zig

const std = @import("std");
const testing = std.testing;
const assert = std.debug.assert;
const maxInt = std.math.maxInt;
const minInt = std.math.minInt;
const divCeil = std.math.divCeil;
const builtin = @import("builtin");
const compiler_rt = @import("../compiler_rt.zig");
const symbol = @import("../compiler_rt.zig").symbol;
const endian = builtin.cpu.arch.endian();
inline fn limbGet(limbs: []const u64, i: usize) u64 {
return switch (endian) {
.little => limbs[i],
.big => limbs[limbs.len - 1 - i],
};
}
inline fn limbSet(limbs: []u64, i: usize, value: u64) void {
switch (endian) {
.little => limbs[i] = value,
.big => limbs[limbs.len - 1 - i] = value,
}
}
fn usedLimbCount(bits: u16) u16 {
return divCeil(u16, bits, 64) catch unreachable;
}
fn limbCount(bits: u16) u16 {
return @divExact(std.zig.target.intByteSize(&builtin.target, bits), 8);
}
fn varLimbs(ptr: [*]u64, bits: u16) []u64 {
const limb_cnt = usedLimbCount(bits);
const true_limb_cnt = limbCount(bits);
return switch (endian) {
.little => ptr[0..limb_cnt],
.big => ptr[true_limb_cnt - limb_cnt .. true_limb_cnt],
};
}
fn constLimbs(ptr: [*]const u64, bits: u16) []const u64 {
const limb_cnt = usedLimbCount(bits);
const true_limb_cnt = limbCount(bits);
return switch (endian) {
.little => ptr[0..limb_cnt],
.big => ptr[true_limb_cnt - limb_cnt .. true_limb_cnt],
};
}
fn fixLastLimb(out_ptr: [*]u64, is_signed: bool, bits: u16) void {
const limb_cnt = usedLimbCount(bits);
const true_limb_cnt = limbCount(bits);
if (limb_cnt == true_limb_cnt) return;
const true_out = out_ptr[0..true_limb_cnt];
const ms = limbGet(true_out, limb_cnt - 1);
const sign: u64 = if (!is_signed or @as(i64, @bitCast(ms)) >= 0) 0 else ~@as(u64, 0);
for (limb_cnt..true_limb_cnt) |i| {
limbSet(true_out, i, sign);
}
}
fn Limbs(T: type) type {
const int_info = @typeInfo(T).int;
const limb_cnt = comptime limbCount(int_info.bits);
return [limb_cnt]u64;
}
fn asLimbs(v: anytype) Limbs(@TypeOf(v)) {
const T = @TypeOf(v);
const int_info = @typeInfo(T).int;
const limb_cnt = comptime limbCount(int_info.bits);
const ET = @Int(int_info.signedness, limb_cnt * 64);
return @bitCast(@as(ET, v));
}
fn limbWrap(limb: u64, is_signed: bool, bits: u16) u64 {
assert(bits % 64 != 0);
const pad_bits: u6 = @intCast(64 - bits % 64);
if (!is_signed) {
const s = limb << pad_bits;
return s >> pad_bits;
} else {
const s = @as(i64, @bitCast(limb)) << pad_bits;
return @bitCast(s >> pad_bits);
}
}
comptime {
symbol(&__addo_limb64, "__addo_limb64");
}
fn __addo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
const b = constLimbs(b_ptr, bits);
var carry: u1 = 0;
var i: usize = 0;
while (i < limb_cnt - 1) : (i += 1) {
const s1 = @addWithOverflow(limbGet(a, i), limbGet(b, i));
const s2 = @addWithOverflow(s1[0], carry);
carry = s1[1] | s2[1];
limbSet(out, i, s2[0]);
}
const limb: u64 = b: {
if (!is_signed) {
const s1 = @addWithOverflow(limbGet(a, i), limbGet(b, i));
const s2 = @addWithOverflow(s1[0], carry);
carry = s1[1] | s2[1];
break :b s2[0];
} else {
const as: i64 = @bitCast(limbGet(a, i));
const bs: i64 = @bitCast(limbGet(b, i));
const s1 = @addWithOverflow(as, bs);
const s2 = @addWithOverflow(s1[0], carry);
carry = s1[1] | s2[1];
break :b @bitCast(s2[0]);
}
};
if (bits % 64 == 0) {
limbSet(out, i, limb);
fixLastLimb(out_ptr, is_signed, bits);
return carry != 0;
} else {
assert(carry == 0);
const wrapped_limb = limbWrap(limb, is_signed, bits);
limbSet(out, i, wrapped_limb);
fixLastLimb(out_ptr, is_signed, bits);
return wrapped_limb != limb;
}
}
fn test__addo_limb64(comptime T: type, a: T, b: T, expected: struct { T, bool }) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var b_limbs = asLimbs(b);
var out: Limbs(T) = undefined;
const overflow = __addo_limb64(&out, &a_limbs, &b_limbs, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected[0]);
try testing.expectEqual(expected_limbs, out);
try testing.expectEqual(expected[1], overflow);
}
test __addo_limb64 {
try test__addo_limb64(u64, 1, 2, .{ 3, false });
try test__addo_limb64(u64, maxInt(u64), 2, .{ 1, true });
try test__addo_limb64(u65, maxInt(u65), 2, .{ 1, true });
try test__addo_limb64(u255, 1, 2, .{ 3, false });
try test__addo_limb64(i64, 1, 2, .{ 3, false });
try test__addo_limb64(i64, maxInt(i64), 1, .{ minInt(i64), true });
try test__addo_limb64(i65, maxInt(i65), 1, .{ minInt(i65), true });
try test__addo_limb64(i255, -3, 2, .{ -1, false });
try test__addo_limb64(u150, maxInt(u150), 2, .{ 1, true });
try test__addo_limb64(i150, -3, 2, .{ -1, false });
}
comptime {
symbol(&__subo_limb64, "__subo_limb64");
}
fn __subo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
const b = constLimbs(b_ptr, bits);
var borrow: u1 = 0;
var i: usize = 0;
while (i < limb_cnt - 1) : (i += 1) {
const s1 = @subWithOverflow(limbGet(a, i), limbGet(b, i));
const s2 = @subWithOverflow(s1[0], borrow);
borrow = s1[1] | s2[1];
limbSet(out, i, s2[0]);
}
const limb: u64 = b: {
if (!is_signed) {
const s1 = @subWithOverflow(limbGet(a, i), limbGet(b, i));
const s2 = @subWithOverflow(s1[0], borrow);
borrow = s1[1] | s2[1];
break :b s2[0];
} else {
const as: i64 = @bitCast(limbGet(a, i));
const bs: i64 = @bitCast(limbGet(b, i));
const s1 = @subWithOverflow(as, bs);
const s2 = @subWithOverflow(s1[0], borrow);
borrow = s1[1] | s2[1];
break :b @bitCast(s2[0]);
}
};
if (bits % 64 == 0) {
limbSet(out, i, limb);
fixLastLimb(out_ptr, is_signed, bits);
return borrow != 0;
} else {
const wrapped_limb = limbWrap(limb, is_signed, bits);
limbSet(out, i, wrapped_limb);
fixLastLimb(out_ptr, is_signed, bits);
return borrow != 0 or wrapped_limb != limb;
}
}
fn test__subo_limb64(comptime T: type, a: T, b: T, expected: struct { T, bool }) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var b_limbs = asLimbs(b);
var out: Limbs(T) = undefined;
const overflow = __subo_limb64(&out, &a_limbs, &b_limbs, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected[0]);
try testing.expectEqual(expected_limbs, out);
try testing.expectEqual(expected[1], overflow);
}
test __subo_limb64 {
try test__subo_limb64(u64, 3, 2, .{ 1, false });
try test__subo_limb64(u64, 0, 1, .{ maxInt(u64), true });
try test__subo_limb64(u65, 0, 1, .{ maxInt(u65), true });
try test__subo_limb64(u255, 3, 2, .{ 1, false });
try test__subo_limb64(i64, 1, 2, .{ -1, false });
try test__subo_limb64(i64, minInt(i64), 1, .{ maxInt(i64), true });
try test__subo_limb64(i65, minInt(i65), 1, .{ maxInt(i65), true });
try test__subo_limb64(i255, -1, 2, .{ -3, false });
try test__subo_limb64(u150, 2, maxInt(u150), .{ 3, true });
try test__subo_limb64(i150, -3, 2, .{ -5, false });
}
comptime {
symbol(&__cmp_limb64, "__cmp_limb64");
}
// a < b -> -1
// a == b -> 0
// a > b -> 1
fn __cmp_limb64(a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) i8 {
const limb_cnt = usedLimbCount(bits);
const a = constLimbs(a_ptr, bits);
const b = constLimbs(b_ptr, bits);
var i: usize = 0;
if (is_signed) {
const sa: i64 = @bitCast(limbGet(a, limb_cnt - 1));
const sb: i64 = @bitCast(limbGet(b, limb_cnt - 1));
if (sa < sb) return -1;
if (sa > sb) return 1;
i += 1;
}
while (i < limb_cnt) : (i += 1) {
const ai = limbGet(a, limb_cnt - 1 - i);
const bi = limbGet(b, limb_cnt - 1 - i);
if (ai < bi) return -1;
if (ai > bi) return 1;
}
return 0;
}
fn test__cmp_limb64(comptime T: type, a: T, b: T, expected: i8) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var b_limbs = asLimbs(b);
const actual = __cmp_limb64(&a_limbs, &b_limbs, is_signed, int_info.bits);
try testing.expectEqual(expected, actual);
}
test __cmp_limb64 {
try test__cmp_limb64(u64, 1, 2, -1);
try test__cmp_limb64(u64, 2, 2, 0);
try test__cmp_limb64(u64, 3, 2, 1);
try test__cmp_limb64(u65, 1, 2, -1);
try test__cmp_limb64(u65, maxInt(u65), maxInt(u65), 0);
try test__cmp_limb64(u65, maxInt(u65), maxInt(u65) - 1, 1);
try test__cmp_limb64(u255, 1, 2, -1);
try test__cmp_limb64(u255, 7, 7, 0);
try test__cmp_limb64(u255, maxInt(u255), maxInt(u255) - 1, 1);
try test__cmp_limb64(i64, -1, 0, -1);
try test__cmp_limb64(i64, 0, 0, 0);
try test__cmp_limb64(i64, 1, 0, 1);
try test__cmp_limb64(i65, minInt(i65), maxInt(i65), -1);
try test__cmp_limb64(i65, -1, -1, 0);
try test__cmp_limb64(i65, maxInt(i65), minInt(i65), 1);
try test__cmp_limb64(i255, -3, 2, -1);
try test__cmp_limb64(i255, -5, -5, 0);
try test__cmp_limb64(i255, 2, -3, 1);
try test__cmp_limb64(u150, maxInt(u150) - 5, maxInt(u150) - 5, 0);
try test__cmp_limb64(i150, minInt(i150), -5, -1);
}
comptime {
symbol(&__and_limb64, "__and_limb64");
}
fn __and_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, bits: u16) callconv(.c) void {
const limb_cnt = limbCount(bits);
const out = out_ptr[0..limb_cnt];
const a = a_ptr[0..limb_cnt];
const b = b_ptr[0..limb_cnt];
var i: usize = 0;
while (i < limb_cnt) : (i += 1) {
limbSet(out, i, limbGet(a, i) & limbGet(b, i));
}
}
fn test__and_limb64(comptime T: type, a: T, b: T, expected: T) !void {
const int_info = @typeInfo(T).int;
var a_limbs = asLimbs(a);
var b_limbs = asLimbs(b);
var out: Limbs(T) = undefined;
__and_limb64(&out, &a_limbs, &b_limbs, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __and_limb64 {
try test__and_limb64(u64, 1, 2, 0);
try test__and_limb64(u64, maxInt(u64), 2, 2);
try test__and_limb64(u65, maxInt(u65), 2, 2);
try test__and_limb64(u255, maxInt(u255), 7, 7);
try test__and_limb64(i64, 1, 2, 0);
try test__and_limb64(i64, -1, 2, 2);
try test__and_limb64(i65, minInt(i65), -1, minInt(i65));
try test__and_limb64(i255, -1, 2, 2);
try test__and_limb64(u150, maxInt(u150), 7, 7);
try test__and_limb64(i150, -2, 3, 2);
}
comptime {
symbol(&__or_limb64, "__or_limb64");
}
fn __or_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, bits: u16) callconv(.c) void {
const limb_cnt = limbCount(bits);
const out = out_ptr[0..limb_cnt];
const a = a_ptr[0..limb_cnt];
const b = b_ptr[0..limb_cnt];
var i: usize = 0;
while (i < limb_cnt) : (i += 1) {
limbSet(out, i, limbGet(a, i) | limbGet(b, i));
}
}
fn test__or_limb64(comptime T: type, a: T, b: T, expected: T) !void {
const int_info = @typeInfo(T).int;
var a_limbs = asLimbs(a);
var b_limbs = asLimbs(b);
var out: Limbs(T) = undefined;
__or_limb64(&out, &a_limbs, &b_limbs, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __or_limb64 {
try test__or_limb64(u64, 1, 2, 3);
try test__or_limb64(u64, maxInt(u64), 2, maxInt(u64));
try test__or_limb64(u65, maxInt(u65), 2, maxInt(u65));
try test__or_limb64(u255, 1, 2, 3);
try test__or_limb64(i64, 1, 2, 3);
try test__or_limb64(i64, -1, 2, -1);
try test__or_limb64(i65, minInt(i65), 1, minInt(i65) + 1);
try test__or_limb64(i255, -3, 2, -1);
try test__or_limb64(u150, maxInt(u150) - 1, 3, maxInt(u150));
try test__or_limb64(i150, -2, 3, -1);
}
comptime {
symbol(&__xor_limb64, "__xor_limb64");
}
fn __xor_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, bits: u16) callconv(.c) void {
const limb_cnt = limbCount(bits);
const out = out_ptr[0..limb_cnt];
const a = a_ptr[0..limb_cnt];
const b = b_ptr[0..limb_cnt];
var i: usize = 0;
while (i < limb_cnt) : (i += 1) {
limbSet(out, i, limbGet(a, i) ^ limbGet(b, i));
}
}
fn test__xor_limb64(comptime T: type, a: T, b: T, expected: T) !void {
const int_info = @typeInfo(T).int;
var a_limbs = asLimbs(a);
var b_limbs = asLimbs(b);
var out: Limbs(T) = undefined;
__xor_limb64(&out, &a_limbs, &b_limbs, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __xor_limb64 {
try test__xor_limb64(u64, 1, 2, 3);
try test__xor_limb64(u64, 3, 2, 1);
try test__xor_limb64(u65, maxInt(u65), 2, maxInt(u65) - 2);
try test__xor_limb64(u255, 7, 3, 4);
try test__xor_limb64(i64, 3, 2, 1);
try test__xor_limb64(i64, -1, 2, -3);
try test__xor_limb64(i65, minInt(i65), -1, maxInt(i65));
try test__xor_limb64(i255, -3, 2, -1);
try test__xor_limb64(u150, maxInt(u150) - 1, 3, maxInt(u150) - 2);
try test__xor_limb64(i150, -2, 3, -3);
}
comptime {
symbol(&__not_limb64, "__not_limb64");
}
fn __not_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
var i: usize = 0;
while (i < limb_cnt - 1) : (i += 1) {
limbSet(out, i, ~limbGet(a, i));
}
var limb: u64 = ~limbGet(a, i);
if (!is_signed and bits % 64 != 0) {
limb = limbWrap(limb, is_signed, bits);
}
limbSet(out, i, limb);
fixLastLimb(out_ptr, is_signed, bits);
}
fn test__not_limb64(comptime T: type, a: T, expected: T) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var out: Limbs(T) = undefined;
__not_limb64(&out, &a_limbs, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __not_limb64 {
try test__not_limb64(u64, 1, maxInt(u64) - 1);
try test__not_limb64(u64, 3, maxInt(u64) - 3);
try test__not_limb64(u65, maxInt(u65), 0);
try test__not_limb64(u255, 7, maxInt(u255) - 7);
try test__not_limb64(i64, 3, -4);
try test__not_limb64(i64, -1, 0);
try test__not_limb64(i65, minInt(i65), maxInt(i65));
try test__not_limb64(i255, -3, 2);
try test__not_limb64(u150, maxInt(u150), 0);
try test__not_limb64(i150, maxInt(i150), minInt(i150));
}
comptime {
symbol(&__shlo_limb64, "__shlo_limb64");
}
fn __shlo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) bool {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
assert(shift < bits);
const limb_shift = shift / 64;
const bit_shift = shift % 64;
var carry: u64 = 0;
var i: usize = 0;
while (i < limb_cnt - 1) : (i += 1) {
if (i < limb_shift) {
limbSet(out, i, 0);
} else {
const limb = limbGet(a, i - limb_shift);
limbSet(out, i, (limb << @intCast(bit_shift)) | carry);
carry = if (bit_shift != 0) (limb >> @intCast(64 - bit_shift)) else 0;
}
}
const limb = limbGet(a, i - limb_shift);
const raw_last = (limb << @intCast(bit_shift)) | carry;
carry = if (bit_shift != 0) (limb >> @intCast(64 - bit_shift)) else 0;
const last = if (bits % 64 == 0) raw_last else limbWrap(raw_last, is_signed, bits);
limbSet(out, i, last);
const sign_extend: u64 = if (is_signed and (last >> 63) == 1) ~@as(u64, 0) else 0;
const expected_carry: u64 = if (bit_shift == 0) 0 else sign_extend >> @intCast(64 - bit_shift);
var overflow = carry != expected_carry;
if (bits % 64 != 0) {
overflow = overflow or raw_last != last;
}
var j = limb_cnt - limb_shift;
while (j < limb_cnt) : (j += 1) {
overflow = overflow or limbGet(a, j) != sign_extend;
}
fixLastLimb(out_ptr, is_signed, bits);
return overflow;
}
fn test__shlo_limb64(comptime T: type, a: T, shift: u16, expected: struct { T, bool }) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var out: Limbs(T) = undefined;
const overflow = __shlo_limb64(&out, &a_limbs, shift, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected[0]);
try testing.expectEqual(expected_limbs, out);
try testing.expectEqual(expected[1], overflow);
}
test __shlo_limb64 {
try test__shlo_limb64(u64, 0x1234_5678_9ABC_DEF0, 4, .{ 0x2345_6789_ABCD_EF00, true });
try test__shlo_limb64(u64, 0x8000_0000_0000_0001, 63, .{ 0x8000_0000_0000_0000, true });
try test__shlo_limb64(u65, 1, 64, .{ 0x1_0000_0000_0000_0000, false });
try test__shlo_limb64(u65, 0x1_0000_0000_0000_0000, 1, .{ 0, true });
try test__shlo_limb64(u128, 0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0, 4, .{ 0x2345_6789_ABCD_EF01_2345_6789_ABCD_EF00, true });
try test__shlo_limb64(u255, maxInt(u255), 1, .{ maxInt(u255) - 1, true });
try test__shlo_limb64(u633, 1 << 299, 333, .{ 1 << 632, false });
try test__shlo_limb64(u633, 1 << 300, 333, .{ 0, true });
try test__shlo_limb64(u633, 1 << 298, 333, .{ 1 << 631, false });
try test__shlo_limb64(i64, -2, 1, .{ -4, false });
try test__shlo_limb64(i64, minInt(i64), 1, .{ 0, true });
try test__shlo_limb64(i64, minInt(i64), 63, .{ 0, true });
try test__shlo_limb64(i65, minInt(i63), 1, .{ minInt(i64), false });
try test__shlo_limb64(i65, -1, 17, .{ -1 << 17, false });
try test__shlo_limb64(i65, -3, 64, .{ -1 << 64, true });
try test__shlo_limb64(i128, -0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0, 4, .{ -0x2345_6789_ABCD_EF01_2345_6789_ABCD_EF00, true });
try test__shlo_limb64(i255, -3, 1, .{ -6, false });
try test__shlo_limb64(i633, 1 << 298, 333, .{ 1 << 631, false });
try test__shlo_limb64(i633, 1 << 299, 333, .{ minInt(i633), true });
try test__shlo_limb64(i633, 1 << 300, 333, .{ 0, true });
try test__shlo_limb64(i633, 1 << 297, 333, .{ 1 << 630, false });
try test__shlo_limb64(i633, -1 << 299, 333, .{ -1 << 632, false });
try test__shlo_limb64(i633, -1 << 300, 333, .{ 0, true });
try test__shlo_limb64(i633, -1 << 298, 333, .{ -1 << 631, false });
try test__shlo_limb64(u150, maxInt(u150), 1, .{ maxInt(u150) - 1, true });
try test__shlo_limb64(i150, -3, 1, .{ -6, false });
}
comptime {
symbol(&__shr_limb64, "__shr_limb64");
}
fn __shr_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) void {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
assert(shift < bits);
const limb_shift = shift / 64;
const bit_shift = shift % 64;
const ms = limbGet(a, limb_cnt - 1);
const sign_extend: u64 = if (is_signed and (ms >> 63) == 1) ~@as(u64, 0) else 0;
var carry: u64 = if (bit_shift != 0) (sign_extend << @intCast(64 - bit_shift)) else 0;
var i: usize = 0;
while (i < limb_cnt) : (i += 1) {
const j = limb_cnt - 1 - i;
if (i < limb_shift) {
limbSet(out, j, sign_extend);
} else {
const limb = limbGet(a, j + limb_shift);
limbSet(out, j, (limb >> @intCast(bit_shift)) | carry);
carry = if (bit_shift != 0) (limb << @intCast(64 - bit_shift)) else 0;
}
}
fixLastLimb(out_ptr, is_signed, bits);
}
fn test__shr_limb64(comptime T: type, a: T, shift: u16, expected: T) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var out: Limbs(T) = undefined;
__shr_limb64(&out, &a_limbs, shift, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __shr_limb64 {
try test__shr_limb64(u64, 0x1234_5678_9ABC_DEF0, 4, 0x0123_4567_89AB_CDEF);
try test__shr_limb64(u64, 0x8000_0000_0000_0001, 63, 1);
try test__shr_limb64(u65, 0x1_0000_0000_0000_0000, 64, 1);
try test__shr_limb64(u65, 0x1_0000_0000_0000_0001, 1, 0x0_8000_0000_0000_0000);
try test__shr_limb64(u128, 0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0, 4, 0x0123_4567_89AB_CDEF_0123_4567_89AB_CDEF);
try test__shr_limb64(u255, maxInt(u255), 1, maxInt(u254));
try test__shr_limb64(u633, 1 << 333, 333, 1);
try test__shr_limb64(u633, 1 << 334, 333, 2);
try test__shr_limb64(u633, 1 << 332, 333, 0);
try test__shr_limb64(i64, -2, 1, -1);
try test__shr_limb64(i64, minInt(i64), 63, -1);
try test__shr_limb64(i65, minInt(i65), 1, minInt(i65) | (1 << 63));
try test__shr_limb64(i65, -1, 17, -1);
try test__shr_limb64(i128, -0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0, 4, -0x0123_4567_89AB_CDEF_0123_4567_89AB_CDEF);
try test__shr_limb64(i255, -3, 1, -2);
try test__shr_limb64(i633, 1 << 333, 333, 1);
try test__shr_limb64(i633, 1 << 334, 333, 2);
try test__shr_limb64(i633, 1 << 332, 333, 0);
try test__shr_limb64(i633, -1 << 333, 333, -1);
try test__shr_limb64(i633, -1 << 334, 333, -2);
try test__shr_limb64(i633, -1 << 332, 333, -1);
try test__shr_limb64(u150, maxInt(u150), 1, maxInt(u149));
try test__shr_limb64(i150, -3, 1, -2);
}
comptime {
symbol(&__clz_limb64, "__clz_limb64");
}
fn __clz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {
const limb_cnt = usedLimbCount(bits);
const a = constLimbs(a_ptr, bits);
var res: u16 = 0;
var i: usize = 0;
if (bits % 64 != 0) {
const limb = limbGet(a, limb_cnt - 1);
if (limb == 0) {
res += bits % 64;
} else {
return @clz(limb << @intCast(64 - bits % 64));
}
i += 1;
}
while (i < limb_cnt) : (i += 1) {
const j = limb_cnt - 1 - i;
const limb = limbGet(a, j);
if (limb == 0) {
res += 64;
} else {
res += @clz(limb);
break;
}
}
return res;
}
fn test__clz_limb64(comptime T: type, a: T, expected: u16) !void {
const int_info = @typeInfo(T).int;
var a_limbs = asLimbs(a);
const out = __clz_limb64(&a_limbs, int_info.bits);
try testing.expectEqual(expected, out);
}
test __clz_limb64 {
try test__clz_limb64(u64, 0, 64);
try test__clz_limb64(u65, 1 << 64, 0);
try test__clz_limb64(u65, 1 << 9, 55);
try test__clz_limb64(u128, 1 << 31, 96);
try test__clz_limb64(u255, 1 << 62, 192);
try test__clz_limb64(i64, -1, 0);
try test__clz_limb64(i65, minInt(i65), 0);
try test__clz_limb64(i65, 1 << 32, 32);
try test__clz_limb64(i128, 0, 128);
try test__clz_limb64(i255, 1 << 130, 124);
try test__clz_limb64(u150, 1 << 31, 118);
try test__clz_limb64(i150, maxInt(u65) - 1, 85);
}
comptime {
symbol(&__ctz_limb64, "__ctz_limb64");
}
fn __ctz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {
const limb_cnt = usedLimbCount(bits);
const a = constLimbs(a_ptr, bits);
var res: u16 = 0;
var i: usize = 0;
while (i < limb_cnt - 1) : (i += 1) {
const limb = limbGet(a, i);
if (limb == 0) {
res += 64;
} else {
res += @ctz(limb);
return res;
}
}
const limb = limbGet(a, i);
if (bits % 64 != 0 and limb == 0) {
res += bits % 64;
} else {
res += @ctz(limb);
}
return res;
}
fn test__ctz_limb64(comptime T: type, a: T, expected: u16) !void {
const int_info = @typeInfo(T).int;
var a_limbs = asLimbs(a);
const out = __ctz_limb64(&a_limbs, int_info.bits);
try testing.expectEqual(expected, out);
}
test __ctz_limb64 {
try test__ctz_limb64(u64, 1 << 17, 17);
try test__ctz_limb64(u65, 1 << 64, 64);
try test__ctz_limb64(u65, 0, 65);
try test__ctz_limb64(u128, 1 << 100, 100);
try test__ctz_limb64(u255, 1 << 200, 200);
try test__ctz_limb64(i64, -1 << 9, 9);
try test__ctz_limb64(i65, minInt(i65), 64);
try test__ctz_limb64(i65, 0, 65);
try test__ctz_limb64(i128, -1 << 73, 73);
try test__ctz_limb64(i255, 1 << 130, 130);
try test__ctz_limb64(u150, 1 << 101, 101);
try test__ctz_limb64(i150, -1 << 74, 74);
}
comptime {
symbol(&__popcount_limb64, "__popcount_limb64");
}
fn __popcount_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {
const limb_cnt = usedLimbCount(bits);
const a = constLimbs(a_ptr, bits);
var res: u16 = 0;
var i: usize = 0;
while (i < limb_cnt - 1) : (i += 1) {
res += @popCount(limbGet(a, i));
}
var limb = limbGet(a, i);
if (bits % 64 != 0) {
limb <<= @intCast(64 - bits % 64);
}
res += @popCount(limb);
return res;
}
fn test__popcount_limb64(comptime T: type, a: T, expected: u16) !void {
const int_info = @typeInfo(T).int;
var a_limbs = asLimbs(a);
const out = __popcount_limb64(&a_limbs, int_info.bits);
try testing.expectEqual(expected, out);
}
test __popcount_limb64 {
try test__popcount_limb64(u64, 0xF0F0_0000_0000_0001, 9);
try test__popcount_limb64(u65, 1 << 64, 1);
try test__popcount_limb64(u65, maxInt(u65), 65);
try test__popcount_limb64(u128, (1 << 100) | (1 << 5) | 1, 3);
try test__popcount_limb64(u255, maxInt(u255), 255);
try test__popcount_limb64(i64, -1, 64);
try test__popcount_limb64(i65, minInt(i65), 1);
try test__popcount_limb64(i65, -1, 65);
try test__popcount_limb64(i128, -1 << 7, 121);
try test__popcount_limb64(i255, -1 << 200, 55);
try test__popcount_limb64(u150, (1 << 149) | (1 << 65) | 1, 3);
try test__popcount_limb64(i150, -1 << 7, 143);
}
comptime {
symbol(&__bitreverse_limb64, "__bitreverse_limb64");
}
fn __bitreverse_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
var i: usize = 0;
while (i < limb_cnt) : (i += 1) {
const j = limb_cnt - 1 - i;
limbSet(out, j, @bitReverse(limbGet(a, i)));
}
if (bits % 64 != 0) {
__shr_limb64(out_ptr, out_ptr, 64 - bits % 64, is_signed, bits);
}
fixLastLimb(out_ptr, is_signed, bits);
}
fn test__bitreverse_limb64(comptime T: type, a: T, expected: T) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var out: Limbs(T) = undefined;
__bitreverse_limb64(&out, &a_limbs, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __bitreverse_limb64 {
try test__bitreverse_limb64(u64, 1 << 7, 1 << 56);
try test__bitreverse_limb64(u65, 1 << 64, 1);
try test__bitreverse_limb64(u65, 1 << 9, 1 << 55);
try test__bitreverse_limb64(u128, 1 << 100, 1 << 27);
try test__bitreverse_limb64(u255, 1 << 200, 1 << 54);
try test__bitreverse_limb64(i64, -1, -1);
try test__bitreverse_limb64(i65, 1 << 32, 1 << 32);
try test__bitreverse_limb64(i65, minInt(i65), 1);
try test__bitreverse_limb64(i128, 1 << 63, 1 << 64);
try test__bitreverse_limb64(i255, 1 << 130, 1 << 124);
try test__bitreverse_limb64(u150, 1 << 9, 1 << 140);
try test__bitreverse_limb64(i150, minInt(i150), 1);
}
comptime {
symbol(&__byteswap_limb64, "__byteswap_limb64");
}
fn __byteswap_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
assert(bits % 8 == 0);
var i: usize = 0;
while (i < limb_cnt) : (i += 1) {
const j = limb_cnt - 1 - i;
limbSet(out, j, @byteSwap(limbGet(a, i)));
}
if (bits % 64 != 0) {
__shr_limb64(out_ptr, out_ptr, 64 - bits % 64, is_signed, bits);
}
fixLastLimb(out_ptr, is_signed, bits);
}
fn test__byteswap_limb64(comptime T: type, a: T, expected: T) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var out: Limbs(T) = undefined;
__byteswap_limb64(&out, &a_limbs, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __byteswap_limb64 {
try test__byteswap_limb64(u64, 0x0123_4567_89AB_CDEF, 0xEFCD_AB89_6745_2301);
try test__byteswap_limb64(u72, 0x01_23_45_67_89_AB_CD_EF_11, 0x11_EF_CD_AB_89_67_45_23_01);
try test__byteswap_limb64(u128, 1 << 72, 1 << 48);
try test__byteswap_limb64(u248, 1, 1 << 240);
try test__byteswap_limb64(u256, 1 << 120, 1 << 128);
try test__byteswap_limb64(i64, minInt(i64), 128);
try test__byteswap_limb64(i72, 1, 1 << 64);
try test__byteswap_limb64(i72, -1, -1);
try test__byteswap_limb64(i128, 1 << 56, 1 << 64);
try test__byteswap_limb64(i248, minInt(i248), 128);
try test__byteswap_limb64(u152, 1, 1 << 144);
try test__byteswap_limb64(i152, 1 << 56, 1 << 88);
}
comptime {
symbol(&__mulo_limb64, "__mulo_limb64");
}
inline fn add3(x: *[3]u64, start: usize, v0: u64) void {
var i = start;
var v = v0;
while (i < 3) : (i += 1) {
const s = @addWithOverflow(x[i], v);
x[i] = s[0];
if (s[1] == 0) break;
v = 1;
}
}
fn mulwide(a: u64, b: u64) [2]u64 {
const muldXi = @import("mulXi3.zig").muldXi;
const limbs: [2]u64 = @bitCast(muldXi(u64, a, b));
return switch (endian) {
.little => limbs,
.big => .{ limbs[1], limbs[0] },
};
}
fn __mulo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
const limb_cnt = usedLimbCount(bits);
const out = varLimbs(out_ptr, bits);
const a = constLimbs(a_ptr, bits);
const b = constLimbs(b_ptr, bits);
@memset(out, 0);
const all_ones = ~@as(u64, 0);
const a_neg = is_signed and ((limbGet(a, limb_cnt - 1) >> 63) != 0);
const b_neg = is_signed and ((limbGet(b, limb_cnt - 1) >> 63) != 0);
var carry: [3]u64 = @splat(0);
var hi_zero = true;
var hi_ones = true;
var hi_borrow: u1 = 0;
var raw_last: u64 = 0;
var k: usize = 0;
while (k < 2 * limb_cnt) : (k += 1) {
var acc = carry;
var i: usize = if (k < limb_cnt) 0 else k - (limb_cnt - 1);
while (i < limb_cnt and i <= k) : (i += 1) {
const j = k - i;
if (j >= limb_cnt) continue;
const p = mulwide(limbGet(a, i), limbGet(b, j));
add3(&acc, 0, p[0]);
add3(&acc, 1, p[1]);
}
var limb = acc[0];
if (k < limb_cnt) {
limbSet(out, k, limb);
if (k == limb_cnt - 1) raw_last = limb;
} else {
if (is_signed) {
const h = k - limb_cnt;
const s0 = @subWithOverflow(limb, if (a_neg) limbGet(b, h) else 0);
const s1 = @subWithOverflow(s0[0], if (b_neg) limbGet(a, h) else 0);
const s2 = @subWithOverflow(s1[0], hi_borrow);
limb = s2[0];
hi_borrow = @intFromBool(s0[1] != 0 or s1[1] != 0 or s2[1] != 0);
}
hi_zero = hi_zero and limb == 0;
hi_ones = hi_ones and limb == all_ones;
}
carry = .{ acc[1], acc[2], 0 };
}
const last = if (bits % 64 == 0) raw_last else limbWrap(raw_last, is_signed, bits);
if (bits % 64 != 0) {
limbSet(out, limb_cnt - 1, last);
}
fixLastLimb(out_ptr, is_signed, bits);
if (!is_signed) {
return !hi_zero or raw_last != last;
}
const sign_extend: u64 = if ((last >> 63) == 1) all_ones else 0;
return (raw_last != last) or if (sign_extend == 0) !hi_zero else !hi_ones;
}
fn test__mulo_limb64(comptime T: type, a: T, b: T, expected: struct { T, bool }) !void {
const int_info = @typeInfo(T).int;
const is_signed = int_info.signedness == .signed;
var a_limbs = asLimbs(a);
var b_limbs = asLimbs(b);
var out: Limbs(T) = undefined;
const overflow = __mulo_limb64(&out, &a_limbs, &b_limbs, is_signed, int_info.bits);
const expected_limbs = asLimbs(expected[0]);
try testing.expectEqual(expected_limbs, out);
try testing.expectEqual(expected[1], overflow);
}
test __mulo_limb64 {
try test__mulo_limb64(u64, 3, 5, .{ 15, false });
try test__mulo_limb64(u64, maxInt(u64), 2, .{ maxInt(u64) - 1, true });
try test__mulo_limb64(u65, 1 << 32, 1 << 32, .{ 1 << 64, false });
try test__mulo_limb64(u65, 1 << 64, 2, .{ 0, true });
try test__mulo_limb64(u128, 1 << 80, 1 << 40, .{ 1 << 120, false });
try test__mulo_limb64(u128, 1 << 100, 1 << 40, .{ 0, true });
try test__mulo_limb64(u255, 7, 9, .{ 63, false });
try test__mulo_limb64(u255, maxInt(u255), 2, .{ maxInt(u255) - 1, true });
try test__mulo_limb64(i64, -3, 2, .{ -6, false });
try test__mulo_limb64(i64, maxInt(i64), 2, .{ -2, true });
try test__mulo_limb64(i65, 1 << 63, 2, .{ minInt(i65), true });
try test__mulo_limb64(i65, -1 << 32, 1 << 16, .{ -1 << 48, false });
try test__mulo_limb64(i128, 1 << 100, 1 << 27, .{ minInt(i128), true });
try test__mulo_limb64(i128, -1 << 80, 1 << 40, .{ -1 << 120, false });
try test__mulo_limb64(i255, -3, 2, .{ -6, false });
try test__mulo_limb64(i255, maxInt(i255), 2, .{ -2, true });
try test__mulo_limb64(u200, 0, maxInt(u200), .{ 0, false });
try test__mulo_limb64(u200, 1, maxInt(u200), .{ maxInt(u200), false });
try test__mulo_limb64(u200, 1 << 100, 1 << 99, .{ 1 << 199, false });
try test__mulo_limb64(u200, 1 << 100, 1 << 100, .{ 0, true });
try test__mulo_limb64(u200, maxInt(u200), maxInt(u200), .{ 1, true });
try test__mulo_limb64(i200, 0, -1, .{ 0, false });
try test__mulo_limb64(i200, -1, -1, .{ 1, false });
try test__mulo_limb64(i200, -1, minInt(i200), .{ minInt(i200), true });
try test__mulo_limb64(i200, maxInt(i200), 2, .{ -2, true });
try test__mulo_limb64(i200, 1 << 100, 1 << 98, .{ 1 << 198, false });
try test__mulo_limb64(i200, 1 << 100, 1 << 99, .{ minInt(i200), true });
try test__mulo_limb64(i200, maxInt(i200), maxInt(i200), .{ 1, true });
try test__mulo_limb64(i200, minInt(i200), minInt(i200), .{ 0, true });
try test__mulo_limb64(u150, maxInt(u150), 2, .{ maxInt(u150) - 1, true });
try test__mulo_limb64(i150, maxInt(i150), 2, .{ -2, true });
}
comptime {
symbol(&__abs_limb64, "__abs_limb64");
}
fn __abs_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, bits: u16) callconv(.c) void {
const limb_cnt = limbCount(bits);
const out = out_ptr[0..limb_cnt];
const a = a_ptr[0..limb_cnt];
const ms = limbGet(a, limb_cnt - 1);
if ((ms >> 63) == 0) {
@memcpy(out, a);
return;
}
var carry: u1 = 1;
var i: usize = 0;
while (i < limb_cnt) : (i += 1) {
const s = @addWithOverflow(~limbGet(a, i), carry);
limbSet(out, i, s[0]);
carry = s[1];
}
}
fn test__abs_limb64(comptime T: type, a: T, expected: @Int(.unsigned, @typeInfo(T).int.bits)) !void {
const int_info = @typeInfo(T).int;
comptime assert(int_info.signedness == .signed);
var a_limbs = asLimbs(a);
var out: Limbs(@TypeOf(expected)) = undefined;
__abs_limb64(&out, &a_limbs, int_info.bits);
const expected_limbs = asLimbs(expected);
try testing.expectEqual(expected_limbs, out);
}
test __abs_limb64 {
try test__abs_limb64(i64, 0, 0);
try test__abs_limb64(i64, -1, 1);
try test__abs_limb64(i64, minInt(i64), 1 << 63);
try test__abs_limb64(i65, -1, 1);
try test__abs_limb64(i65, minInt(i65), 1 << 64);
try test__abs_limb64(i65, maxInt(i65), maxInt(i65));
try test__abs_limb64(i128, -1 << 80, 1 << 80);
try test__abs_limb64(i128, 1 << 64, 1 << 64);
try test__abs_limb64(i200, -1 << 198, 1 << 198);
try test__abs_limb64(i255, -5, 5);
try test__abs_limb64(i255, minInt(i255), 1 << 254);
try test__abs_limb64(i150, -40, 40);
}