diff --git a/lib/compiler_rt/divmodei4.zig b/lib/compiler_rt/divmodei4.zig index 4fa168a675c86879640ceda8bc604ecd81918a8f..55c7ec5792549933d22e8962f58b8cb6e23a5d54 100644 --- a/lib/compiler_rt/divmodei4.zig +++ b/lib/compiler_rt/divmodei4.zig @@ -10,29 +10,30 @@ const symbol = @import("../compiler_rt.zig").symbol; comptime { symbol(&__divei4, "__divei4"); symbol(&__modei4, "__modei4"); + symbol(&__divei5, "__divei5"); + symbol(&__modei5, "__modei5"); } -inline fn limb(x: []u32, i: usize) *u32 { - return if (endian == .little) &x[i] else &x[x.len - 1 - i]; +inline fn limb(i: usize, len: usize) usize { + return if (endian == .little) i else len - 1 - i; } -inline fn neg(x: []u32) void { +inline fn neg(out: []u32, in: []const u32) void { var ov: u1 = 1; - for (0..x.len) |limb_index| { - const l = limb(x, limb_index); - l.*, ov = @addWithOverflow(~l.*, ov); + for (0..in.len) |limb_index| { + const new, ov = @addWithOverflow(~in[limb(limb_index, in.len)], ov); + out[limb(limb_index, out.len)] = new; } } -/// Mutates the arguments! -fn divmod(q: ?[]u32, r: ?[]u32, u: []u32, v: []u32) !void { - const u_sign: i32 = @bitCast(u[u.len - 1]); - const v_sign: i32 = @bitCast(v[v.len - 1]); - if (u_sign < 0) neg(u); - if (v_sign < 0) neg(v); - try @call(.always_inline, udivmod, .{ q, r, u, v }); - if (q) |x| if (u_sign ^ v_sign < 0) neg(x); - if (r) |x| if (u_sign < 0) neg(x); +fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32, tu: []u32, tv: []u32) !void { + const u_sign: i32 = @bitCast(u[limb(u.len - 1, u.len)]); + const v_sign: i32 = @bitCast(v[limb(v.len - 1, v.len)]); + if (u_sign < 0) neg(tu, u); + if (v_sign < 0) neg(tv, v); + try @call(.always_inline, udivmod, .{ q, r, if (u_sign < 0) tu else u, if (v_sign < 0) tv else v }); + if (q) |x| if (u_sign ^ v_sign < 0) neg(x, x); + if (r) |x| if (u_sign < 0) neg(x, x); } pub fn __divei4(q_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) void { @@ -41,7 +42,7 @@ pub fn __divei4(q_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) vo const q: []u32 = @ptrCast(@alignCast(q_p[0..byte_size])); const u: []u32 = @ptrCast(@alignCast(u_p[0..byte_size])); const v: []u32 = @ptrCast(@alignCast(v_p[0..byte_size])); - @call(.always_inline, divmod, .{ q, null, u, v }) catch unreachable; + @call(.always_inline, divmod, .{ q, null, u, v, u, v }) catch unreachable; } pub fn __modei4(r_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) void { @@ -50,5 +51,27 @@ pub fn __modei4(r_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) vo const r: []u32 = @ptrCast(@alignCast(r_p[0..byte_size])); const u: []u32 = @ptrCast(@alignCast(u_p[0..byte_size])); const v: []u32 = @ptrCast(@alignCast(v_p[0..byte_size])); - @call(.always_inline, divmod, .{ null, r, u, v }) catch unreachable; + @call(.always_inline, divmod, .{ null, r, u, v, u, v }) catch unreachable; +} + +pub fn __divei5(q_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, t_p: [*]u8, bits: usize) callconv(.c) void { + @setRuntimeSafety(compiler_rt.test_safety); + const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); + const q: []u32 = @ptrCast(@alignCast(q_p[0..byte_size])); + const u: []const u32 = @ptrCast(@alignCast(u_p[0..byte_size])); + const v: []const u32 = @ptrCast(@alignCast(v_p[0..byte_size])); + const tu: []u32 = @ptrCast(@alignCast(t_p[0..byte_size])); + const tv: []u32 = @ptrCast(@alignCast(t_p[byte_size..][0..byte_size])); + @call(.always_inline, divmod, .{ q, null, u, v, tu, tv }) catch unreachable; +} + +pub fn __modei5(r_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, t_p: [*]u8, bits: usize) callconv(.c) void { + @setRuntimeSafety(compiler_rt.test_safety); + const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); + const r: []u32 = @ptrCast(@alignCast(r_p[0..byte_size])); + const u: []const u32 = @ptrCast(@alignCast(u_p[0..byte_size])); + const v: []const u32 = @ptrCast(@alignCast(v_p[0..byte_size])); + const tu: []u32 = @ptrCast(@alignCast(t_p[0..byte_size])); + const tv: []u32 = @ptrCast(@alignCast(t_p[byte_size..][0..byte_size])); + @call(.always_inline, divmod, .{ null, r, u, v, tu, tv }) catch unreachable; } diff --git a/lib/compiler_rt/limb64.zig b/lib/compiler_rt/limb64.zig index 64f924e778a1730d9b63f44ed57ca19bdb35eb89..fa535dea851db2d95806c1fe91a97e858a7a6c81 100644 --- a/lib/compiler_rt/limb64.zig +++ b/lib/compiler_rt/limb64.zig @@ -7,6 +7,7 @@ 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(); @@ -24,10 +25,44 @@ inline fn limbSet(limbs: []u64, i: usize, value: u64) void { } } -fn limbCount(bits: u16) u16 { +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 sign: u64 = if (!is_signed or @as(i64, @bitCast(true_out[limb_cnt - 1])) >= 0) 0 else ~@as(u64, 0); + for (limb_cnt..true_limb_cnt) |i| { + true_out[i] = sign; + } +} + fn Limbs(T: type) type { const int_info = @typeInfo(T).int; const limb_cnt = comptime limbCount(int_info.bits); @@ -55,14 +90,14 @@ fn limbWrap(limb: u64, is_signed: bool, bits: u16) u64 { } comptime { - @export(&__addo_limb64, .{ .name = "__addo_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility }); + 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 = limbCount(bits); - const out = out_ptr[0..limb_cnt]; - const a = a_ptr[0..limb_cnt]; - const b = b_ptr[0..limb_cnt]; + 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; @@ -91,11 +126,13 @@ fn __addo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_s 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; } } @@ -124,17 +161,20 @@ test __addo_limb64 { 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 { - @export(&__subo_limb64, .{ .name = "__subo_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility }); + 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 = limbCount(bits); - const out = out_ptr[0..limb_cnt]; - const a = a_ptr[0..limb_cnt]; - const b = b_ptr[0..limb_cnt]; + 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; @@ -163,10 +203,12 @@ fn __subo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_s 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; } } @@ -195,19 +237,22 @@ test __subo_limb64 { 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 { - @export(&__cmp_limb64, .{ .name = "__cmp_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility }); + 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 = limbCount(bits); - const a = a_ptr[0..limb_cnt]; - const b = b_ptr[0..limb_cnt]; + const limb_cnt = usedLimbCount(bits); + const a = constLimbs(a_ptr, bits); + const b = constLimbs(b_ptr, bits); var i: usize = 0; if (is_signed) { @@ -263,4 +308,819 @@ test __cmp_limb64 { 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); } diff --git a/lib/compiler_rt/mulXi3.zig b/lib/compiler_rt/mulXi3.zig index 41df7283ea7c1e801c8a631e91724fc9e87f1a48..9fce9754f3a748e82e55728e5e18195970e53b52 100644 --- a/lib/compiler_rt/mulXi3.zig +++ b/lib/compiler_rt/mulXi3.zig @@ -63,7 +63,7 @@ fn DoubleInt(comptime T: type) type { }; } -fn muldXi(comptime T: type, a: T, b: T) DoubleInt(T) { +pub fn muldXi(comptime T: type, a: T, b: T) DoubleInt(T) { const DT = DoubleInt(T); const word_t = compiler_rt.HalveInt(DT, false); const bits_in_word_2 = @sizeOf(T) * 8 / 2; diff --git a/lib/compiler_rt/udivmodei4.zig b/lib/compiler_rt/udivmodei4.zig index f037639c72b334d04fa9d195af6f0e3ae6ee71d5..5aa8421bdb920c33c7d0f3a10c32ee9db6c21da0 100644 --- a/lib/compiler_rt/udivmodei4.zig +++ b/lib/compiler_rt/udivmodei4.zig @@ -13,6 +13,8 @@ const max_limbs = std.math.divCeil(usize, 65535, 32) catch unreachable; // max s comptime { symbol(&__udivei4, "__udivei4"); symbol(&__umodei4, "__umodei4"); + symbol(&__udivei5, "__udivei5"); + symbol(&__umodei5, "__umodei5"); } /// Get the value of a limb. @@ -132,6 +134,32 @@ pub fn __umodei4(r_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, bits: usize) ca @call(.always_inline, divmod, .{ null, r, u, v }) catch unreachable; } +pub fn __udivei5(q_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, t_p: [*]u8, bits: usize) callconv(.c) void { + @setRuntimeSafety(compiler_rt.test_safety); + const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); + const q: []u32 = @ptrCast(@alignCast(q_p[0..byte_size])); + const u: []const u32 = @ptrCast(@alignCast(u_p[0..byte_size])); + const v: []const u32 = @ptrCast(@alignCast(v_p[0..byte_size])); + const tu: []u32 = @ptrCast(@alignCast(t_p[0..byte_size])); + _ = tu; + const tv: []u32 = @ptrCast(@alignCast(t_p[byte_size..][0..byte_size])); + _ = tv; + @call(.always_inline, divmod, .{ q, null, u, v }) catch unreachable; +} + +pub fn __umodei5(r_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, t_p: [*]u8, bits: usize) callconv(.c) void { + @setRuntimeSafety(compiler_rt.test_safety); + const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits)); + const r: []u32 = @ptrCast(@alignCast(r_p[0..byte_size])); + const u: []const u32 = @ptrCast(@alignCast(u_p[0..byte_size])); + const v: []const u32 = @ptrCast(@alignCast(v_p[0..byte_size])); + const tu: []u32 = @ptrCast(@alignCast(t_p[0..byte_size])); + _ = tu; + const tv: []u32 = @ptrCast(@alignCast(t_p[byte_size..][0..byte_size])); + _ = tv; + @call(.always_inline, divmod, .{ null, r, u, v }) catch unreachable; +} + test "__udivei4/__umodei4" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index 82fd6353241cc11c4dbc4847896e5e95d8a43702..05181e22839d8d49d4b915c088d3f74fd49aca70 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const testing = std.testing; +const math = std.math; const mem = std.mem; const log = std.log.scoped(.codegen); @@ -1324,6 +1325,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .mod, .max, .min, + .div_exact, .div_trunc, .div_floor, => |tag| { @@ -1349,6 +1351,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .mod => try cg.floatMod(float_ty, lhs, rhs), .max => try cg.floatMax(float_ty, lhs, rhs), .min => try cg.floatMin(float_ty, lhs, rhs), + .div_exact => try cg.floatDiv(float_ty, lhs, rhs), .div_trunc => try cg.floatDivTrunc(float_ty, lhs, rhs), .div_floor => try cg.floatDivFloor(float_ty, lhs, rhs), else => unreachable, @@ -1366,6 +1369,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .mod => try cg.intMod(int_ty, lhs, rhs), .max => try cg.intMax(int_ty, lhs, rhs), .min => try cg.intMin(int_ty, lhs, rhs), + .div_exact => try cg.intDiv(int_ty, lhs, rhs), .div_trunc => try cg.intDiv(int_ty, lhs, rhs), .div_floor => try cg.intDivFloor(int_ty, lhs, rhs), else => unreachable, @@ -1389,19 +1393,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const result = try cg.floatDiv(.fromType(cg, ty), lhs, rhs); try cg.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); }, - .div_exact => { - const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; - const lhs = try cg.resolveInst(bin_op.lhs); - const rhs = try cg.resolveInst(bin_op.rhs); - const ty = cg.typeOfIndex(inst); - - if (ty.zigTypeTag(zcu) == .vector) { - return cg.fail("TODO: implement AIR op: div_exact for vectors", .{}); - } - - const result = try cg.intDiv(.fromType(cg, ty), lhs, rhs); - try cg.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); - }, .abs => { const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; const operand = try cg.resolveInst(ty_op.operand); @@ -2363,6 +2354,15 @@ const IntType = struct { } }; +fn intBackingBits(cg: *CodeGen, bits: u16) u16 { + return switch (bits) { + 0 => unreachable, + 1...32 => 32, + 33...64 => 64, + else => std.zig.target.intByteSize(cg.target, bits) * 8, + }; +} + fn intAdd(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue { switch (ty.bits) { 0 => unreachable, @@ -2486,7 +2486,19 @@ fn intMul(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return .stack; }, 65...128 => return cg.callIntrinsic(.__multi3, &.{ .i128_type, .i128_type }, Type.i128, &.{ lhs, rhs }), - else => return cg.fail("TODO: Support intMul for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(@intFromBool(ty.is_signed)); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__mulo_limb64); + try cg.addTag(.drop); + + return result; + }, } } @@ -2512,7 +2524,28 @@ fn intDiv(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return cg.callIntrinsic(.__udivti3, &.{ .i128_type, .i128_type }, Type.i128, &.{ lhs, rhs }); } }, - else => return cg.fail("TODO: Support intDiv for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + const bits = cg.intBackingBits(ty.bits); + var tmp = try cg.allocInt(.{ .is_signed = false, .bits = bits * 2 }); + if (ty.is_signed) { + _ = try cg.callIntrinsic( + .__divei5, + &.{ .usize_type, .usize_type, .usize_type, .usize_type, .usize_type }, + .void, + &.{ result, lhs, rhs, tmp, .{ .imm32 = ty.bits } }, + ); + } else { + _ = try cg.callIntrinsic( + .__udivei5, + &.{ .usize_type, .usize_type, .usize_type, .usize_type, .usize_type }, + .void, + &.{ result, lhs, rhs, tmp, .{ .imm32 = ty.bits } }, + ); + } + tmp.free(cg); + return result; + }, } } @@ -2564,7 +2597,22 @@ fn intDivFloor(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!W try cg.addTag(.i64_sub); return .stack; }, - else => return cg.fail("TODO: Support intDivFloor for signed integer bitsize: {d}", .{ty.bits}), + else => { + const q = try cg.intDiv(ty, lhs, rhs); + + const zero = try cg.intZeroValue(ty); + + const r = try cg.intRem(ty, lhs, rhs); + _ = try cg.intCmp(ty, .neq, r, zero); + + const sign_xor = try cg.intXor(ty, lhs, rhs); + _ = try cg.intCmp(ty, .lt, sign_xor, zero); + var adjust = try (try cg.intAnd(.u32, .stack, .stack)).toLocal(cg, Type.u32); + + const adjust_bigint = try cg.intCast(ty, .u32, adjust); + adjust.free(cg); + return try cg.intSub(ty, q, adjust_bigint); + }, } } @@ -2590,7 +2638,28 @@ fn intRem(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return cg.callIntrinsic(.__umodti3, &.{ .i128_type, .i128_type }, Type.i128, &.{ lhs, rhs }); } }, - else => return cg.fail("TODO: Support intRem for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + const bits = cg.intBackingBits(ty.bits); + var tmp = try cg.allocInt(.{ .is_signed = false, .bits = bits * 2 }); + if (ty.is_signed) { + _ = try cg.callIntrinsic( + .__modei5, + &.{ .usize_type, .usize_type, .usize_type, .usize_type, .usize_type }, + .void, + &.{ result, lhs, rhs, tmp, .{ .imm32 = ty.bits } }, + ); + } else { + _ = try cg.callIntrinsic( + .__umodei5, + &.{ .usize_type, .usize_type, .usize_type, .usize_type, .usize_type }, + .void, + &.{ result, lhs, rhs, tmp, .{ .imm32 = ty.bits } }, + ); + } + tmp.free(cg); + return result; + }, } } @@ -2635,7 +2704,17 @@ fn intAnd(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return result; }, - else => return cg.fail("TODO: Support intAnd for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__and_limb64); + + return result; + }, } } @@ -2669,7 +2748,17 @@ fn intOr(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return result; }, - else => return cg.fail("TODO: Support intOr for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__or_limb64); + + return result; + }, } } @@ -2703,7 +2792,17 @@ fn intXor(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return result; }, - else => return cg.fail("TODO: Support intXor for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__xor_limb64); + + return result; + }, } } @@ -2761,11 +2860,22 @@ fn intNot(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { return result; }, - else => return cg.fail("TODO: Support intNot for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(operand); + try cg.addImm32(@intFromBool(ty.is_signed)); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__not_limb64); + + return result; + }, } } // rhs is a shift count, pointing to i32 value +// does not perform wrapping, padding bits does not satisfy invariant fn intShl(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue { switch (ty.bits) { 0 => unreachable, @@ -2783,7 +2893,19 @@ fn intShl(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return .stack; }, 65...128 => return cg.callIntrinsic(.__ashlti3, &.{ .i128_type, .i32_type }, Type.i128, &.{ lhs, rhs }), - else => return cg.fail("TODO: Support intShl for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(@intFromBool(ty.is_signed)); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__shlo_limb64); + try cg.addTag(.drop); + + return result; + }, } } @@ -2811,7 +2933,18 @@ fn intShr(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue return cg.callIntrinsic(.__lshrti3, &.{ .i128_type, .i32_type }, Type.i128, &.{ lhs, rhs }); } }, - else => return cg.fail("TODO: Support intShr for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(@intFromBool(ty.is_signed)); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__shr_limb64); + + return result; + }, } } @@ -2871,7 +3004,16 @@ fn intAbs(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { const b = try cg.intSub(u128_ty, a, mask); return b; }, - else => return cg.fail("TODO: Support intAbs for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(operand); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__abs_limb64); + + return result; + }, } } @@ -2938,7 +3080,13 @@ fn intClz(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { try cg.addTag(.i32_wrap_i64); return .stack; }, - else => return cg.fail("TODO: Support intClz for integer bitsize: {d}", .{ty.bits}), + else => { + try cg.lowerToStack(operand); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__clz_limb64); + + return .stack; + }, } } @@ -2984,7 +3132,13 @@ fn intCtz(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { try cg.addTag(.i32_wrap_i64); return .stack; }, - else => return cg.fail("TODO: Support intCtz for integer bitsize: {d}", .{ty.bits}), + else => { + try cg.lowerToStack(operand); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__ctz_limb64); + + return .stack; + }, } } @@ -3024,7 +3178,13 @@ fn intPopCount(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { try cg.addTag(.i32_wrap_i64); return .stack; }, - else => return cg.fail("TODO: Support intPopCount for integer bitsize: {d}", .{ty.bits}), + else => { + try cg.lowerToStack(operand); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__popcount_limb64); + + return .stack; + }, } } @@ -3083,7 +3243,17 @@ fn intBitReverse(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { return tmp; } }, - else => return cg.fail("TODO: Support intBitReverse for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(operand); + try cg.addImm32(@intFromBool(ty.is_signed)); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__bitreverse_limb64); + + return result; + }, } } @@ -3111,35 +3281,48 @@ fn intByteSwap(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { return cg.intShr(ty, intrin_ret, .{ .imm32 = 64 - ty.bits }); }, 65...128 => { - const tmp = try cg.allocStack(Type.u128); + const result = try cg.allocStack(Type.u128); + + try cg.emitWValue(result); const low = try cg.load(operand, Type.u64, 0); - const high = try cg.load(operand, Type.u64, 8); - const swap_low = try cg.callIntrinsic( .__bswapdi2, &.{.u64_type}, Type.u64, &.{low}, ); + try cg.store(.stack, swap_low, Type.u64, result.offset() + 8); + + try cg.emitWValue(result); + + const high = try cg.load(operand, Type.u64, 8); const swap_high = try cg.callIntrinsic( .__bswapdi2, &.{.u64_type}, Type.u64, &.{high}, ); - - try cg.store(tmp, swap_low, Type.u64, tmp.offset() + 8); - try cg.store(tmp, swap_high, Type.u64, tmp.offset()); + try cg.store(.stack, swap_high, Type.u64, result.offset()); if (ty.bits < 128) { const shift_ty: IntType = .{ .is_signed = ty.is_signed, .bits = 128 }; - return cg.intShr(shift_ty, tmp, .{ .imm32 = 128 - ty.bits }); + return cg.intShr(shift_ty, result, .{ .imm32 = 128 - ty.bits }); } else { - return tmp; + return result; } }, - else => return cg.fail("TODO: Support intByteSwap for integer bitsize: {d}", .{ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(operand); + try cg.addImm32(@intFromBool(ty.is_signed)); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__byteswap_limb64); + + return result; + }, } } @@ -3197,7 +3380,49 @@ fn intWrap(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { return result; }, 128 => return operand, - else => return cg.fail("TODO: Support intWrap for integer bitsize: {d}", .{ty.bits}), + else => { + const bits = cg.intBackingBits(ty.bits); + if (ty.bits == bits) return operand; + + const result = try cg.allocInt(ty); + + const used_len = (math.divCeil(u16, ty.bits, 64) catch unreachable) * 8; + + if (ty.bits % 64 != 0) { + try cg.memcpy(result, operand, .{ .imm32 = used_len - 8 }); + const pad = 64 - ty.bits % 64; + + try cg.emitWValue(result); + _ = try cg.load(operand, Type.u64, used_len - 8); + if (ty.is_signed) { + try cg.addImm64(pad); + try cg.addTag(.i64_shl); + try cg.addImm64(pad); + try cg.addTag(.i64_shr_s); + } else { + try cg.addImm64(~@as(u64, 0) >> @intCast(pad)); + try cg.addTag(.i64_and); + } + try cg.store(.stack, .stack, Type.u64, result.offset() + used_len - 8); + } else { + try cg.memcpy(result, operand, .{ .imm32 = used_len }); + } + + const full_len = @divExact(bits, 8); + if (used_len + 8 == full_len) { // last limb needs sign extended + try cg.emitWValue(result); + if (ty.is_signed) { + _ = try cg.load(result, Type.u64, used_len - 8); + try cg.addImm64(63); + try cg.addTag(.i64_shr_s); + } else { + try cg.addImm64(0); + } + try cg.store(.stack, .stack, Type.u64, result.offset() + used_len); + } + + return result; + }, } } @@ -3214,8 +3439,8 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue { } else { return .{ .imm64 = ~@as(u64, 0) >> @intCast(64 - int_ty.bits) }; } - } else { - const result = try cg.allocStack(Type.u128); + } else if (int_ty.bits <= 128) { + const result = try cg.allocInt(int_ty); try cg.store(result, .{ .imm64 = ~@as(u64, 0) }, Type.u64, 0); if (int_ty.is_signed) { @@ -3223,6 +3448,24 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue { } else { try cg.store(result, .{ .imm64 = ~@as(u64, 0) >> @intCast(128 - int_ty.bits) }, Type.u64, 8); } + return result; + } else { + const result = try cg.allocInt(int_ty); + const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8); + const used_len = (math.divCeil(u16, int_ty.bits, 64) catch unreachable) * 8; + + try cg.memset(Type.u8, result, .{ .imm32 = used_len - 8 }, .{ .imm32 = 0xFF }); + + if (int_ty.is_signed) { + try cg.store(result, .{ .imm64 = (~@as(u64, 0) >> @intCast(used_len * 8 - int_ty.bits)) >> 1 }, Type.u64, used_len - 8); + } else { + try cg.store(result, .{ .imm64 = ~@as(u64, 0) >> @intCast(used_len * 8 - int_ty.bits) }, Type.u64, used_len - 8); + } + + if (used_len + 8 == full_len) { + try cg.store(result, .{ .imm64 = 0 }, Type.u64, full_len - 8); + } + return result; } } @@ -3235,10 +3478,23 @@ fn intMinValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue { return .{ .imm32 = ~@as(u32, 0) << @intCast(int_ty.bits - 1) }; } else if (int_ty.bits <= 64) { return .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - 1) }; - } else { - const result = try cg.allocStack(Type.u128); + } else if (int_ty.bits <= 128) { + const result = try cg.allocInt(int_ty); try cg.store(result, .{ .imm64 = 0 }, Type.u64, 0); try cg.store(result, .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - 65) }, Type.u64, 8); + return result; + } else { + const result = try cg.allocInt(int_ty); + const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8); + const used_len = (math.divCeil(u16, int_ty.bits, 64) catch unreachable) * 8; + + try cg.memset(Type.u8, result, .{ .imm32 = used_len - 8 }, .{ .imm32 = 0 }); + try cg.store(result, .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - (used_len - 8) * 8 - 1) }, Type.u64, used_len - 8); + + if (used_len + 8 == full_len) { + try cg.store(result, .{ .imm64 = ~@as(u64, 0) }, Type.u64, full_len - 8); + } + return result; } } @@ -3256,20 +3512,20 @@ fn intAddSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError defer rhs_is_neg.free(cg); const min_val = try cg.intMinValue(int_ty); - try cg.emitWValue(min_val); - try cg.emitWValue(max_val); + try cg.lowerToStack(min_val); + try cg.lowerToStack(max_val); try cg.emitWValue(rhs_is_neg); try cg.addTag(.select); - try cg.emitWValue(op_val); + try cg.lowerToStack(op_val); const overflow_cmp = try cg.intCmp(int_ty, .lt, op_val, lhs); const is_overflow = try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp); try cg.emitWValue(is_overflow); try cg.addTag(.select); return .stack; } else { - try cg.emitWValue(max_val); - try cg.emitWValue(op_val); + try cg.lowerToStack(max_val); + try cg.lowerToStack(op_val); const is_overflow = try cg.intCmp(int_ty, .lt, op_val, lhs); try cg.emitWValue(is_overflow); @@ -3290,12 +3546,12 @@ fn intSubSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError const max_val = try cg.intMaxValue(int_ty); const min_val = try cg.intMinValue(int_ty); - try cg.emitWValue(max_val); - try cg.emitWValue(min_val); + try cg.lowerToStack(max_val); + try cg.lowerToStack(min_val); try cg.emitWValue(rhs_is_neg); try cg.addTag(.select); - try cg.emitWValue(op_val); + try cg.lowerToStack(op_val); const overflow_cmp = try cg.intCmp(int_ty, .gt, op_val, lhs); const is_overflow = try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp); try cg.emitWValue(is_overflow); @@ -3304,8 +3560,8 @@ fn intSubSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError } else { const zero = try cg.intZeroValue(int_ty); - try cg.emitWValue(zero); - try cg.emitWValue(op_val); + try cg.lowerToStack(zero); + try cg.lowerToStack(op_val); const is_overflow = try cg.intCmp(int_ty, .lt, lhs, rhs); try cg.emitWValue(is_overflow); try cg.addTag(.select); @@ -3314,43 +3570,6 @@ fn intSubSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError } fn intMulSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue { - // Remove when > 128 int ops will be implemented in backend - if (int_ty.bits == 128) { - if (!int_ty.is_signed) { - return cg.fail("TODO: mul_sat for unsigned 128-bit integers", .{}); - } - - const overflow_ret = try cg.allocStack(Type.i32); - const ret = try cg.callIntrinsic( - .__muloti4, - &[_]InternPool.Index{ .i128_type, .i128_type, .usize_type }, - Type.i128, - &.{ lhs, rhs, overflow_ret }, - ); - try cg.lowerToStack(ret); - - const xor = try cg.intXor(int_ty, lhs, rhs); - const sign_v = try cg.intShr(int_ty, xor, .{ .imm32 = 127 }); - - // xor ~@as(u127, 0) - try cg.emitWValue(sign_v); - const lsb = try cg.load(sign_v, Type.u64, 0); - _ = try cg.intXor(.u64, lsb, .{ .imm64 = ~@as(u64, 0) }); - try cg.store(.stack, .stack, Type.u64, sign_v.offset()); - - try cg.emitWValue(sign_v); - const msb = try cg.load(sign_v, Type.u64, 8); - _ = try cg.intXor(.u64, msb, .{ .imm64 = ~@as(u64, 0) >> 1 }); - try cg.store(.stack, .stack, Type.u64, sign_v.offset() + 8); - - try cg.lowerToStack(sign_v); - _ = try cg.load(overflow_ret, Type.i32, 0); - try cg.addTag(.i32_eqz); - try cg.addTag(.select); - - return .stack; - } - const ext_ty: IntType = .{ .is_signed = int_ty.is_signed, .bits = int_ty.bits * 2 }; const lhs_ext = try cg.intCast(ext_ty, int_ty, lhs); @@ -3366,10 +3585,10 @@ fn intMulSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError if (int_ty.is_signed) { const min_val = try cg.intMinValue(int_ty); - try cg.emitWValue(min_val); + try cg.lowerToStack(min_val); - try cg.emitWValue(max_val); - try cg.emitWValue(op_val); + try cg.lowerToStack(max_val); + try cg.lowerToStack(op_val); const max_ext = try cg.intCast(ext_ty, int_ty, max_val); const ov_pos = try cg.intCmp(ext_ty, .lt, max_ext, mul_ext); try cg.emitWValue(ov_pos); @@ -3377,12 +3596,12 @@ fn intMulSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError const min_ext = try cg.intCast(ext_ty, int_ty, min_val); const ov_neg = try cg.intCmp(ext_ty, .gt, min_ext, mul_ext); - try cg.emitWValue(ov_neg); + try cg.lowerToStack(ov_neg); try cg.addTag(.select); return .stack; } else { - try cg.emitWValue(max_val); - try cg.emitWValue(op_val); + try cg.lowerToStack(max_val); + try cg.lowerToStack(op_val); const max_ext = try cg.intCast(ext_ty, int_ty, max_val); const is_overflow = try cg.intCmp(ext_ty, .lt, max_ext, mul_ext); try cg.emitWValue(is_overflow); @@ -3405,20 +3624,20 @@ fn intShlSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError const zero = try cg.intZeroValue(int_ty); const min_val = try cg.intMinValue(int_ty); - try cg.emitWValue(min_val); - try cg.emitWValue(max_val); + try cg.lowerToStack(min_val); + try cg.lowerToStack(max_val); const lhs_is_neg = try cg.intCmp(int_ty, .lt, lhs, zero); try cg.emitWValue(lhs_is_neg); try cg.addTag(.select); - try cg.emitWValue(op_val); + try cg.lowerToStack(op_val); const is_overflow = try cg.intCmp(int_ty, .neq, check_val, lhs); try cg.emitWValue(is_overflow); try cg.addTag(.select); return .stack; } else { - try cg.emitWValue(max_val); - try cg.emitWValue(op_val); + try cg.lowerToStack(max_val); + try cg.lowerToStack(op_val); const is_overflow = try cg.intCmp(int_ty, .neq, check_val, lhs); try cg.emitWValue(is_overflow); try cg.addTag(.select); @@ -3432,12 +3651,17 @@ fn intZeroValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue { 1...32 => return .{ .imm32 = 0 }, 33...64 => return .{ .imm64 = 0 }, 65...128 => { - const result = try cg.allocStack(Type.u128); + const result = try cg.allocInt(int_ty); try cg.store(result, .{ .imm64 = 0 }, Type.u64, 0); try cg.store(result, .{ .imm64 = 0 }, Type.u64, 8); return result; }, - else => return cg.fail("TODO: Implement intZeroValue for integer bitsize: {d}", .{int_ty.bits}), + else => { + const result = try cg.allocInt(int_ty); + const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8); + try cg.memset(Type.u8, result, .{ .imm32 = full_len }, .{ .imm32 = 0 }); + return result; + }, } } @@ -3561,68 +3785,6 @@ fn intMulOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) Inner _ = try cg.intCmp(new_ty, .neq, res_upcast, bin_op); try cg.addLocal(.local_set, overflow_bit.local.value); break :blk res_tmp; - } else if (int_ty.bits == 128 and !int_ty.is_signed) blk: { - var lhs_lsb = try (try cg.load(lhs, Type.u64, 0)).toLocal(cg, Type.u64); - defer lhs_lsb.free(cg); - var lhs_msb = try (try cg.load(lhs, Type.u64, 8)).toLocal(cg, Type.u64); - defer lhs_msb.free(cg); - var rhs_lsb = try (try cg.load(rhs, Type.u64, 0)).toLocal(cg, Type.u64); - defer rhs_lsb.free(cg); - var rhs_msb = try (try cg.load(rhs, Type.u64, 8)).toLocal(cg, Type.u64); - defer rhs_msb.free(cg); - - const zero: WValue = .{ .imm64 = 0 }; - - const cross_1 = try cg.callIntrinsic( - .__multi3, - &[_]InternPool.Index{.i64_type} ** 4, - Type.i128, - &.{ lhs_msb, zero, rhs_lsb, zero }, - ); - const cross_2 = try cg.callIntrinsic( - .__multi3, - &[_]InternPool.Index{.i64_type} ** 4, - Type.i128, - &.{ rhs_msb, zero, lhs_lsb, zero }, - ); - const mul_lsb = try cg.callIntrinsic( - .__multi3, - &[_]InternPool.Index{.i64_type} ** 4, - Type.i128, - &.{ rhs_lsb, zero, lhs_lsb, zero }, - ); - - const rhs_msb_not_zero = try cg.intCmp(.u64, .neq, rhs_msb, zero); - const lhs_msb_not_zero = try cg.intCmp(.u64, .neq, lhs_msb, zero); - const both_msb_not_zero = try cg.intAnd(.u32, rhs_msb_not_zero, lhs_msb_not_zero); - - const cross_1_msb = try cg.load(cross_1, .u64, 8); - const cross_1_msb_not_zero = try cg.intCmp(.u64, .neq, cross_1_msb, zero); - const cond_1 = try cg.intOr(.u32, both_msb_not_zero, cross_1_msb_not_zero); - - const cross_2_msb = try cg.load(cross_2, Type.u64, 8); - const cross_2_msb_not_zero = try cg.intCmp(.u64, .neq, cross_2_msb, zero); - const cond_2 = try cg.intOr(.u32, cond_1, cross_2_msb_not_zero); - - const cross_1_lsb = try cg.load(cross_1, Type.u64, 0); - const cross_2_lsb = try cg.load(cross_2, Type.u64, 0); - const cross_add = try cg.intAdd(.u64, cross_1_lsb, cross_2_lsb); - - var mul_lsb_msb = try (try cg.load(mul_lsb, Type.u64, 8)).toLocal(cg, Type.u64); - defer mul_lsb_msb.free(cg); - var all_add = try (try cg.intAdd(.u64, cross_add, mul_lsb_msb)).toLocal(cg, Type.u64); - defer all_add.free(cg); - const add_overflow = try cg.intCmp(.u64, .lt, all_add, mul_lsb_msb); - - _ = try cg.intOr(.u32, cond_2, add_overflow); - try cg.addLocal(.local_set, overflow_bit.local.value); - - const tmp_result = try cg.allocStack(Type.u128); - try cg.emitWValue(tmp_result); - const mul_lsb_lsb = try cg.load(mul_lsb, Type.u64, 0); - try cg.store(.stack, mul_lsb_lsb, Type.u64, tmp_result.offset()); - try cg.store(tmp_result, all_add, Type.u64, 8); - break :blk tmp_result; } else if (int_ty.bits == 128 and int_ty.is_signed) blk: { const overflow_ret = try cg.allocStack(Type.i32); const res = try cg.callIntrinsic( @@ -3634,44 +3796,53 @@ fn intMulOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) Inner _ = try cg.load(overflow_ret, Type.i32, 0); try cg.addLocal(.local_set, overflow_bit.local.value); break :blk res; - } else return cg.fail("TODO: intMulOverflow for bitsize {d}", .{int_ty.bits}); + } else { + const result = try cg.allocInt(int_ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(@intFromBool(int_ty.is_signed)); + try cg.addImm32(int_ty.bits); + try cg.addCallIntrinsic(.__mulo_limb64); + + return .{ .result = result, .ov = .stack }; + }; return .{ .result = result_val, .ov = .{ .local = overflow_bit.local } }; } -fn intShlOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { - switch (int_ty.bits) { +fn intShlOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { + switch (ty.bits) { 0 => unreachable, 1...128 => { - const raw_shl = try cg.intShl(int_ty, lhs, rhs); - const wrapped_shl = try cg.intWrap(int_ty, raw_shl); - const shl_tmp = try cg.toLocalInt(wrapped_shl, int_ty); + const raw_shl = try cg.intShl(ty, lhs, rhs); + const wrapped_shl = try cg.intWrap(ty, raw_shl); + const shl_tmp = try cg.toLocalInt(wrapped_shl, ty); - const shr = try cg.intShr(int_ty, shl_tmp, rhs); - const overflow_bit = try cg.intCmp(int_ty, .neq, shr, lhs); + const shr = try cg.intShr(ty, shl_tmp, rhs); + const overflow_bit = try cg.intCmp(ty, .neq, shr, lhs); return .{ .result = shl_tmp, .ov = overflow_bit }; }, - else => return cg.fail("TODO: Support intShlOverflow for integer bitsize: {d}", .{int_ty.bits}), + else => { + const result = try cg.allocInt(ty); + + try cg.lowerToStack(result); + try cg.lowerToStack(lhs); + try cg.lowerToStack(rhs); + try cg.addImm32(@intFromBool(ty.is_signed)); + try cg.addImm32(ty.bits); + try cg.addCallIntrinsic(.__shlo_limb64); + + return .{ .result = result, .ov = .stack }; + }, } } fn intCast(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) InnerError!WValue { - const src_bits: u16 = switch (src_ty.bits) { - 0 => unreachable, - 1...32 => 32, - 33...64 => 64, - 65...128 => 128, - else => unreachable, - }; - - const dest_bits: u16 = switch (dest_ty.bits) { - 0 => unreachable, - 1...32 => 32, - 33...64 => 64, - 65...128 => 128, - else => unreachable, - }; + const src_bits: u16 = cg.intBackingBits(src_ty.bits); + const dest_bits: u16 = cg.intBackingBits(dest_ty.bits); if (src_bits == dest_bits) { return operand; @@ -3683,34 +3854,78 @@ fn intCast(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) Inn return .stack; } else if (src_bits == 32 and dest_bits == 64) { try cg.emitWValue(operand); - try cg.addTag(if (dest_ty.is_signed) .i64_extend_i32_s else .i64_extend_i32_u); + try cg.addTag(if (src_ty.is_signed) .i64_extend_i32_s else .i64_extend_i32_u); return .stack; - } else if (dest_bits == 128) { - const stack_ptr = try cg.allocStack(Type.u128); - try cg.emitWValue(stack_ptr); + } else if (dest_bits >= 128) { + const result = try cg.allocInt(dest_ty); - const lhs = if (src_bits == 32) blk: { - const sign_ty: IntType = .{ .is_signed = dest_ty.is_signed, .bits = 64 }; - break :blk try (try cg.intCast(sign_ty, src_ty, operand)).toLocal(cg, Type.u64); - } else operand; + const dest_len = dest_bits / 8; - try cg.store(.stack, lhs, Type.u64, stack_ptr.offset()); - - if (dest_ty.is_signed) { - try cg.emitWValue(stack_ptr); - const shr = try cg.intShr(IntType.i64, lhs, .{ .imm32 = 63 }); - try cg.store(.stack, shr, Type.u64, 8 + stack_ptr.offset()); + if (dest_bits <= src_bits) { + assert(src_bits >= 128); + try cg.memcpy(result, operand, .{ .imm32 = dest_len }); } else { - try cg.store(stack_ptr, .{ .imm64 = 0 }, Type.u64, 8); - } + var src_len: u32 = undefined; + if (src_bits == 32) { + try cg.emitWValue(result); + try cg.emitWValue(operand); + try cg.addTag(if (src_ty.is_signed) .i64_extend_i32_s else .i64_extend_i32_u); + try cg.store(.stack, .stack, Type.u64, result.offset()); + src_len = 8; + } else if (src_bits == 64) { + try cg.emitWValue(result); + try cg.emitWValue(operand); + try cg.store(.stack, .stack, Type.u64, result.offset()); + src_len = 8; + } else { + src_len = src_bits / 8; + try cg.memcpy(result, operand, .{ .imm32 = src_len }); + } - if (src_bits == 32) { - var tmp_lhs = lhs; - tmp_lhs.free(cg); + if (dest_bits == 128) { + if (src_ty.is_signed) { + try cg.emitWValue(result); + if (src_bits == 32) { + try cg.emitWValue(operand); + try cg.addTag(if (dest_ty.is_signed) .i64_extend_i32_s else .i64_extend_i32_u); + } else if (src_bits == 64) { + try cg.emitWValue(operand); + } else unreachable; + const shr = try cg.intShr(IntType.i64, .stack, .{ .imm32 = 63 }); + try cg.store(.stack, shr, Type.u64, 8 + result.offset()); + } else { + try cg.store(result, .{ .imm64 = 0 }, Type.u64, 8); + } + } else { + var pad = result; + pad.stack_offset.value += src_len; + const memset_len = dest_len - src_len; + if (src_ty.is_signed) { + if (src_bits == 32) { + try cg.emitWValue(operand); + _ = try cg.intShr(IntType.i32, .stack, .{ .imm32 = 31 }); + } else if (src_bits == 64) { + try cg.emitWValue(operand); + _ = try cg.intShr(IntType.i64, .stack, .{ .imm32 = 63 }); + try cg.addTag(.i32_wrap_i64); + } else { + _ = try cg.load(operand, Type.u64, src_len - 8); + _ = try cg.intShr(IntType.i64, .stack, .{ .imm32 = 63 }); + try cg.addTag(.i32_wrap_i64); + } + var sign_byte = try @as(WValue, .stack).toLocal(cg, Type.u32); + try cg.memset(Type.u8, pad, .{ .imm32 = memset_len }, sign_byte); + sign_byte.free(cg); + } else { + try cg.memset(Type.u8, pad, .{ .imm32 = memset_len }, .{ .imm32 = 0 }); + } + } } - return stack_ptr; + return result; } else { + assert(dest_bits <= 64); + assert(src_bits >= 128); const load_ty = if (dest_bits == 32) Type.u32 else Type.u64; return cg.load(operand, load_ty, 0); } @@ -3719,13 +3934,7 @@ fn intCast(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) Inn fn intTrunc(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) InnerError!WValue { var result = try cg.intCast(dest_ty, src_ty, operand); - const dest_wasm_bits: u16 = switch (dest_ty.bits) { - 0 => unreachable, - 1...32 => 32, - 33...64 => 64, - 65...128 => 128, - else => return cg.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{dest_ty.bits}), - }; + const dest_wasm_bits = cg.intBackingBits(dest_ty.bits); if (dest_wasm_bits != dest_ty.bits) { result = try cg.intWrap(dest_ty, result); @@ -4275,7 +4484,34 @@ fn intFromFloat(cg: *CodeGen, dest_ty: IntType, src_ty: FloatType, operand: WVal return cg.callIntrinsic(intrinsic, &.{.f128_type}, Type.u128, &.{operand}); }, }, - else => return cg.fail("TODO: Support intFromFloat for integer bitsize: {d}", .{dest_ty.bits}), + else => { + const result = try cg.allocInt(dest_ty); + + switch (src_ty) { + .f16 => { + const intrinsic: Mir.Intrinsic = if (dest_ty.is_signed) .__fixhfei else .__fixunshfei; + _ = try cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type, .f16_type }, .void, &.{ result, .{ .imm32 = dest_ty.bits }, operand }); + }, + .f32 => { + const intrinsic: Mir.Intrinsic = if (dest_ty.is_signed) .__fixsfei else .__fixunssfei; + _ = try cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type, .f32_type }, .void, &.{ result, .{ .imm32 = dest_ty.bits }, operand }); + }, + .f64 => { + const intrinsic: Mir.Intrinsic = if (dest_ty.is_signed) .__fixdfei else .__fixunsdfei; + _ = try cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type, .f64_type }, .void, &.{ result, .{ .imm32 = dest_ty.bits }, operand }); + }, + .f80 => { + const intrinsic: Mir.Intrinsic = if (dest_ty.is_signed) .__fixxfei else .__fixunsxfei; + _ = try cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type, .f80_type }, .void, &.{ result, .{ .imm32 = dest_ty.bits }, operand }); + }, + .f128 => { + const intrinsic: Mir.Intrinsic = if (dest_ty.is_signed) .__fixtfei else .__fixunstfei; + _ = try cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type, .f128_type }, .void, &.{ result, .{ .imm32 = dest_ty.bits }, operand }); + }, + } + + return result; + }, } } @@ -4295,7 +4531,10 @@ fn floatFromInt(cg: *CodeGen, dest_ty: FloatType, src_ty: IntType, operand: WVal const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floattihf else .__floatuntihf; return cg.callIntrinsic(intrinsic, &.{.i128_type}, Type.f16, &.{operand}); }, - else => return cg.fail("TODO: Support floatFromInt for {d}-bit int to 16-bit float", .{src_ty.bits}), + else => { + const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floateihf else .__floatuneihf; + return cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type }, Type.f16, &.{ operand, .{ .imm32 = src_ty.bits } }); + }, }, .f32 => switch (src_ty.bits) { 0 => unreachable, @@ -4313,7 +4552,10 @@ fn floatFromInt(cg: *CodeGen, dest_ty: FloatType, src_ty: IntType, operand: WVal const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floattisf else .__floatuntisf; return cg.callIntrinsic(intrinsic, &.{.i128_type}, Type.f32, &.{operand}); }, - else => return cg.fail("TODO: Support floatFromInt for {d}-bit int to 32-bit float", .{src_ty.bits}), + else => { + const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floateisf else .__floatuneisf; + return cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type }, Type.f32, &.{ operand, .{ .imm32 = src_ty.bits } }); + }, }, .f64 => switch (src_ty.bits) { 0 => unreachable, @@ -4331,7 +4573,10 @@ fn floatFromInt(cg: *CodeGen, dest_ty: FloatType, src_ty: IntType, operand: WVal const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floattidf else .__floatuntidf; return cg.callIntrinsic(intrinsic, &.{.i128_type}, Type.f64, &.{operand}); }, - else => return cg.fail("TODO: Support floatFromInt for {d}-bit int to 64-bit float", .{src_ty.bits}), + else => { + const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floateidf else .__floatuneidf; + return cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type }, Type.f64, &.{ operand, .{ .imm32 = src_ty.bits } }); + }, }, .f80 => switch (src_ty.bits) { 0 => unreachable, @@ -4347,7 +4592,10 @@ fn floatFromInt(cg: *CodeGen, dest_ty: FloatType, src_ty: IntType, operand: WVal const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floattixf else .__floatuntixf; return cg.callIntrinsic(intrinsic, &.{.i128_type}, Type.f80, &.{operand}); }, - else => return cg.fail("TODO: Support floatFromInt for {d}-bit int to 80-bit float", .{src_ty.bits}), + else => { + const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floateixf else .__floatuneixf; + return cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type }, Type.f80, &.{ operand, .{ .imm32 = src_ty.bits } }); + }, }, .f128 => switch (src_ty.bits) { 0 => unreachable, @@ -4363,7 +4611,10 @@ fn floatFromInt(cg: *CodeGen, dest_ty: FloatType, src_ty: IntType, operand: WVal const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floattitf else .__floatuntitf; return cg.callIntrinsic(intrinsic, &.{.i128_type}, Type.f128, &.{operand}); }, - else => return cg.fail("TODO: Support floatFromInt for {d}-bit int to 128-bit float", .{src_ty.bits}), + else => { + const intrinsic: Mir.Intrinsic = if (src_ty.is_signed) .__floateitf else .__floatuneitf; + return cg.callIntrinsic(intrinsic, &.{ .usize_type, .usize_type }, Type.f128, &.{ operand, .{ .imm32 = src_ty.bits } }); + }, }, } } diff --git a/src/codegen/wasm/Mir.zig b/src/codegen/wasm/Mir.zig index 3276c0f15c36fa9046738b79e700e52a1ca0c366..11bf63bdfd884dc672f022acf7512769ee130c0f 100644 --- a/src/codegen/wasm/Mir.zig +++ b/src/codegen/wasm/Mir.zig @@ -825,6 +825,7 @@ pub const Intrinsic = enum(u32) { __ceilx, __cosh, __cosx, + __divei5, __divhf3, __divtf3, __divti3, @@ -846,33 +847,43 @@ pub const Intrinsic = enum(u32) { __fabsh, __fabsx, __fixdfdi, + __fixdfei, __fixdfsi, __fixdfti, __fixhfdi, + __fixhfei, __fixhfsi, __fixhfti, __fixsfdi, + __fixsfei, __fixsfsi, __fixsfti, __fixtfdi, + __fixtfei, __fixtfsi, __fixtfti, __fixunsdfdi, + __fixunsdfei, __fixunsdfsi, __fixunsdfti, __fixunshfdi, + __fixunshfei, __fixunshfsi, __fixunshfti, __fixunssfdi, + __fixunssfei, __fixunssfsi, __fixunssfti, __fixunstfdi, + __fixunstfei, __fixunstfsi, __fixunstfti, __fixunsxfdi, + __fixunsxfei, __fixunsxfsi, __fixunsxfti, __fixxfdi, + __fixxfei, __fixxfsi, __fixxfti, __floatdidf, @@ -880,6 +891,11 @@ pub const Intrinsic = enum(u32) { __floatdisf, __floatditf, __floatdixf, + __floateidf, + __floateihf, + __floateisf, + __floateitf, + __floateixf, __floatsidf, __floatsihf, __floatsisf, @@ -895,6 +911,11 @@ pub const Intrinsic = enum(u32) { __floatundisf, __floatunditf, __floatundixf, + __floatuneidf, + __floatuneihf, + __floatuneisf, + __floatuneitf, + __floatuneixf, __floatunsidf, __floatunsihf, __floatunsisf, @@ -930,6 +951,7 @@ pub const Intrinsic = enum(u32) { __lshrti3, __lttf2, __ltxf2, + __modei5, __modti3, __mulhf3, __mulodi4, @@ -960,7 +982,9 @@ pub const Intrinsic = enum(u32) { __truncxfdf2, __truncxfhf2, __truncxfsf2, + __udivei5, __udivti3, + __umodei5, __umodti3, ceilq, cos, @@ -1007,4 +1031,17 @@ pub const Intrinsic = enum(u32) { __addo_limb64, __subo_limb64, __cmp_limb64, + __and_limb64, + __or_limb64, + __xor_limb64, + __not_limb64, + __shlo_limb64, + __shr_limb64, + __clz_limb64, + __ctz_limb64, + __popcount_limb64, + __bitreverse_limb64, + __byteswap_limb64, + __mulo_limb64, + __abs_limb64, }; diff --git a/test/behavior/bit_shifting.zig b/test/behavior/bit_shifting.zig index 74bd8690b4228cc631726c4eb707c38a4da1bf7f..9fed23895b90f67ae734be965eb715a0e180a8bd 100644 --- a/test/behavior/bit_shifting.zig +++ b/test/behavior/bit_shifting.zig @@ -151,7 +151,6 @@ test "Saturating Shift Left" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const S = struct { fn shlSat(x: anytype, y: std.math.Log2Int(@TypeOf(x))) @TypeOf(x) { diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index a8f8bcb0221cfb34be16a326329c18dba3a03a41..51573e21fc9b9f6355b36b1f2785bbbad5902047 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -35,7 +35,6 @@ test "@bitCast iX -> uX (8, 16, 128)" { test "@bitCast iX -> uX exotic integers" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; @@ -80,7 +79,6 @@ fn conv_uN(comptime N: usize, x: std.meta.Int(.unsigned, N)) std.meta.Int(.signe test "bitcast uX to bytes" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; @@ -296,7 +294,6 @@ test "triple level result location with bitcast sandwich passed as tuple element test "@bitCast packed struct of floats" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -334,7 +331,6 @@ test "@bitCast packed struct of floats" { test "comptime @bitCast packed struct to int and back" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/byteswap.zig b/test/behavior/byteswap.zig index 3cf7b7e015a657221ae8c3d01a34eeb2f684778c..c73e4a5ea97663d840a8107a63439161e7ee903e 100644 --- a/test/behavior/byteswap.zig +++ b/test/behavior/byteswap.zig @@ -42,7 +42,6 @@ test "@byteSwap exotic integers" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; const ByteSwapIntTest = struct { fn run() !void { diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index f1f5203ddcb579d01dd7dac7d26263e50b4df335..fcc6d7c6b992b7a120cee9104577107571ffb23c 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -126,6 +126,56 @@ test "@floatFromInt" { try comptime S.doTheTest(); } +fn testIntFromFloat(comptime F: type, f: F, comptime I: type, i: I) !void { + try expect(@as(I, @intFromFloat(f)) == i); +} + +test "@intFromFloat > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testIntFromFloat(f16, 1024, u140, 1024); + try testIntFromFloat(f16, -1024, i140, -1024); + + try testIntFromFloat(f32, 1 << 24, u140, 1 << 24); + try testIntFromFloat(f32, -1 << 24, i140, -1 << 24); + + try testIntFromFloat(f64, 1 << 53, u200, 1 << 53); + try testIntFromFloat(f64, -1 << 53, i200, -1 << 53); + + try testIntFromFloat(f80, 1 << 63, u200, 1 << 63); + try testIntFromFloat(f80, -1 << 63, i200, -1 << 63); + + try testIntFromFloat(f128, 1 << 100, u200, 1 << 100); + try testIntFromFloat(f128, -1 << 100, i200, -1 << 100); +} + +fn testFloatFromInt(comptime I: type, i: I, comptime F: type, expected: F) !void { + try expect(@as(F, @floatFromInt(i)) == expected); +} + +test "@floatFromInt > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testFloatFromInt(u140, 1024, f16, 1024); + try testFloatFromInt(i140, -1024, f16, -1024); + + try testFloatFromInt(u140, 1 << 24, f32, 1 << 24); + try testFloatFromInt(i140, -1 << 24, f32, -1 << 24); + + try testFloatFromInt(u200, 1 << 53, f64, 1 << 53); + try testFloatFromInt(i200, -1 << 53, f64, -1 << 53); + + try testFloatFromInt(u200, 1 << 63, f80, 1 << 63); + try testFloatFromInt(i200, -1 << 63, f80, -1 << 63); + + try testFloatFromInt(u200, 1 << 100, f128, 1 << 100); + try testFloatFromInt(i200, -1 << 100, f128, -1 << 100); +} + test "@floatFromInt(f80)" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/behavior/cast_int.zig b/test/behavior/cast_int.zig index 8a1fcf79b2f0c9ab3a2105780fa37c3bffdbbd2a..841030a9e1a02fd23587b852bd73d2cdedfd5fdf 100644 --- a/test/behavior/cast_int.zig +++ b/test/behavior/cast_int.zig @@ -3,6 +3,7 @@ const std = @import("std"); const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const maxInt = std.math.maxInt; +const minInt = std.math.minInt; test "@intCast i32 to u7" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -136,6 +137,83 @@ test "coerce non byte-sized integers accross 32bits boundary" { } } +fn testIntCast(comptime S: type, a: S, comptime D: type, expected: D) !void { + const actual: D = @intCast(a); + try expect(actual == expected); +} + +test "@intCast <= 64 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + + try testIntCast(i32, minInt(i32), i64, minInt(i32)); + try testIntCast(i32, maxInt(i32), i64, maxInt(i32)); + try testIntCast(u32, maxInt(u32), u64, maxInt(u32)); + try testIntCast(u32, maxInt(i32), i64, maxInt(i32)); + try testIntCast(u32, maxInt(u32), i64, maxInt(u32)); + + try testIntCast(i32, 0, u32, 0); + try testIntCast(i32, maxInt(i32), u32, maxInt(i32)); + try testIntCast(i64, 0, u64, 0); + try testIntCast(i64, maxInt(i64), u64, maxInt(i64)); + + try testIntCast(u32, 0, i32, 0); + try testIntCast(u32, maxInt(i32), i32, maxInt(i32)); + try testIntCast(u64, 0, i64, 0); + try testIntCast(u64, maxInt(i64), i64, maxInt(i64)); + + try testIntCast(i64, minInt(i32), i32, minInt(i32)); + try testIntCast(i64, maxInt(i32), i32, maxInt(i32)); + try testIntCast(u64, maxInt(u32), u32, maxInt(u32)); + try testIntCast(i64, maxInt(u32), u32, maxInt(u32)); +} + +test "@intCast > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testIntCast(u8, 123, u140, 123); + try testIntCast(u64, 1 << 63, u140, 1 << 63); + try testIntCast(u127, maxInt(u127), u140, maxInt(u127)); + try testIntCast(i8, -42, i140, -42); + try testIntCast(i64, minInt(i64), i140, minInt(i64)); + try testIntCast(i127, maxInt(i127), i140, maxInt(i127)); + try testIntCast(u127, 1 << 100, i140, 1 << 100); + try testIntCast(i127, 1 << 100, u140, 1 << 100); + + try testIntCast(u140, 0, u128, 0); + try testIntCast(u140, 1 << 100, u128, 1 << 100); + try testIntCast(u140, maxInt(u128), u128, maxInt(u128)); + try testIntCast(i140, -1, i128, -1); + try testIntCast(i140, minInt(i128), i128, minInt(i128)); + try testIntCast(i140, maxInt(i128), i128, maxInt(i128)); + try testIntCast(u140, 1 << 100, i128, 1 << 100); + try testIntCast(i140, 1 << 100, u128, 1 << 100); + + try testIntCast(u16, 255, u256, 255); + try testIntCast(u128, 1 << 127, u256, 1 << 127); + try testIntCast(i16, -7, i256, -7); + try testIntCast(i128, minInt(i128), i256, minInt(i128)); + try testIntCast(u128, maxInt(i128), i256, maxInt(i128)); + try testIntCast(i128, 1 << 100, u256, 1 << 100); + + try testIntCast(u256, 1 << 139, u140, 1 << 139); + try testIntCast(u256, maxInt(u140), u140, maxInt(u140)); + try testIntCast(i256, -1, i140, -1); + try testIntCast(i256, minInt(i140), i140, minInt(i140)); + try testIntCast(i256, maxInt(i140), i140, maxInt(i140)); + try testIntCast(u256, 1 << 120, i140, 1 << 120); + try testIntCast(i256, 1 << 120, u140, 1 << 120); + + try testIntCast(i257, maxInt(i256), i256, maxInt(i256)); + try testIntCast(i257, minInt(i256), i256, minInt(i256)); + try testIntCast(u257, maxInt(u256), u256, maxInt(u256)); + try testIntCast(u257, 1 << 255, u256, 1 << 255); + + try testIntCast(u32, maxInt(u32), i255, maxInt(u32)); + try testIntCast(u64, maxInt(u64), i255, maxInt(u64)); + try testIntCast(u128, maxInt(u128), i255, maxInt(u128)); +} + const Piece = packed struct { color: Color, type: Type, diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 03550fef70d73da7b334842a25ed383d8867cc8b..57ef1d864b8e8259f20d49578e72f58a891c438b 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -511,7 +511,6 @@ var foo_contents = Foo{ .name = "a" }; const foo_ref = &foo_contents; test "runtime 128 bit integer division" { - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 254c8886334765e4b628aae896ac97d364c7983c..671dd9153e6305de83fc69572b6633029368aa30 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -61,17 +61,17 @@ fn assertFalse(b: bool) !void { try expect(!b); } -test "@clz" { +test "@clz small" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO - try testClz(); - try comptime testClz(); + try testClzSmall(); + try comptime testClzSmall(); } -fn testClz() !void { +fn testClzSmall() !void { try expect(testOneClz(u8, 0b10001010) == 0); try expect(testOneClz(u8, 0b00001010) == 4); try expect(testOneClz(u8, 0b00011010) == 3); @@ -142,17 +142,17 @@ fn expectVectorsEqual(a: anytype, b: anytype) !void { try expect(@reduce(.And, a == b)); } -test "@ctz" { +test "@ctz small" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - try testCtz(); - try comptime testCtz(); + try testCtzSmall(); + try comptime testCtzSmall(); } -fn testCtz() !void { +fn testCtzSmall() !void { try expect(testOneCtz(u8, 0b10100000) == 5); try expect(testOneCtz(u8, 0b10001010) == 1); try expect(testOneCtz(u8, 0b00000000) == 8); @@ -1119,10 +1119,38 @@ test "@mulWithOverflow bitsize 128 bits" { try testMulWithOverflow(i128, -1 << 63, -1 << 64, -1 << 127, 1); } +test "@mulWithOverflow > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testMulWithOverflow(u140, 0, maxInt(u140), 0, 0); + try testMulWithOverflow(u140, 1, maxInt(u140), maxInt(u140), 0); + try testMulWithOverflow(u140, 1 << 70, 1 << 69, 1 << 139, 0); + try testMulWithOverflow(u140, 1 << 70, 1 << 70, 0, 1); + + try testMulWithOverflow(u200, 1 << 100, 1 << 99, 1 << 199, 0); + try testMulWithOverflow(u200, 1 << 100, 1 << 100, 0, 1); + try testMulWithOverflow(u200, maxInt(u200), maxInt(u200), 1, 1); + try testMulWithOverflow(u200, maxInt(u200) - 1, 2, maxInt(u200) - 3, 1); + + try testMulWithOverflow(i140, 0, -1, 0, 0); + try testMulWithOverflow(i140, -1, -1, 1, 0); + try testMulWithOverflow(i140, 1 << 69, 1 << 69, 1 << 138, 0); + try testMulWithOverflow(i140, 1 << 69, 1 << 70, minInt(i140), 1); + try testMulWithOverflow(i140, -1 << 70, 1 << 20, -1 << 90, 0); + try testMulWithOverflow(i140, minInt(i140), -1, minInt(i140), 1); + + try testMulWithOverflow(i200, 1 << 100, 1 << 98, 1 << 198, 0); + try testMulWithOverflow(i200, 1 << 100, 1 << 99, minInt(i200), 1); + try testMulWithOverflow(i200, -1 << 120, 1 << 30, -1 << 150, 0); + try testMulWithOverflow(i200, minInt(i200), minInt(i200), 0, 1); + try testMulWithOverflow(i200, maxInt(i200), 2, -2, 1); + try testMulWithOverflow(i200, maxInt(i200), maxInt(i200), 1, 1); +} + test "@mulWithOverflow bitsize 256 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; @@ -1317,6 +1345,542 @@ test "@shlWithOverflow > 64 bits" { try testShlWithOverflow(i128, 0x0100_0000_0000_0000_0000000000000000, 9, 0, 1); } +test "@shlWithOverflow > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testShlWithOverflow(u140, 1 << 100, 20, 1 << 120, 0); + try testShlWithOverflow(u140, 1 << 100, 40, 0, 1); + try testShlWithOverflow(u140, 3, 138, (1 << 139) | (1 << 138), 0); + try testShlWithOverflow(u140, 7, 138, (1 << 139) | (1 << 138), 1); + + try testShlWithOverflow(u256, 1 << 200, 40, 1 << 240, 0); + try testShlWithOverflow(u256, 1 << 200, 55, 1 << 255, 0); + try testShlWithOverflow(u256, 1 << 200, 56, 0, 1); + try testShlWithOverflow(u256, maxInt(u256), 1, maxInt(u256) - 1, 1); + + try testShlWithOverflow(i140, 1 << 100, 20, 1 << 120, 0); + try testShlWithOverflow(i140, 1 << 100, 39, minInt(i140), 1); + try testShlWithOverflow(i140, -1 << 20, 10, -1 << 30, 0); + try testShlWithOverflow(i140, minInt(i140), 1, 0, 1); + + try testShlWithOverflow(i256, 1 << 200, 30, 1 << 230, 0); + try testShlWithOverflow(i256, 1 << 200, 55, minInt(i256), 1); + try testShlWithOverflow(i256, -1 << 120, 40, -1 << 160, 0); + try testShlWithOverflow(i256, minInt(i256), 1, 0, 1); +} + +fn testAnd(comptime T: type, a: T, b: T, expected: T) !void { + try expect((a & b) == expected); +} + +test "and > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testAnd(u140, (1 << 139) | (1 << 70) | 0xaa, (1 << 139) | (1 << 69) | 0xcc, (1 << 139) | 0x88); + try testAnd(u140, maxInt(u140), 1 << 100, 1 << 100); + try testAnd(u140, 0, maxInt(u140), 0); + try testAnd(u140, (1 << 80) | (1 << 17) | 1, (1 << 17) | (1 << 9) | 1, (1 << 17) | 1); + + try testAnd(u256, maxInt(u256), (1 << 255) | (1 << 200) | 7, (1 << 255) | (1 << 200) | 7); + try testAnd(u256, (1 << 255) | (1 << 5), (1 << 254) | (1 << 5), 1 << 5); + try testAnd(u256, (1 << 130) | (1 << 64) | (1 << 2), (1 << 130) | (1 << 63) | (1 << 2), (1 << 130) | (1 << 2)); + try testAnd(u256, 0, 1 << 200, 0); + + try testAnd(i140, -1, 1 << 17, 1 << 17); + try testAnd(i140, minInt(i140), -1, minInt(i140)); + try testAnd(i140, -1 << 40, (1 << 100) | (1 << 80) | (1 << 40), (1 << 100) | (1 << 80) | (1 << 40)); + try testAnd(i140, 0, maxInt(i140), 0); + + try testAnd(i256, -1, 1 << 200, 1 << 200); + try testAnd(i256, minInt(i256), maxInt(i256), 0); + try testAnd(i256, -1 << 130, -1 << 129, -1 << 130); + try testAnd(i256, minInt(i256), -1 << 10, minInt(i256)); +} + +fn testOr(comptime T: type, a: T, b: T, expected: T) !void { + try expect((a | b) == expected); +} + +test "or > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testOr(u140, 0, 1 << 139, 1 << 139); + try testOr(u140, (1 << 70) | 0xa, (1 << 69) | 0x5, (1 << 70) | (1 << 69) | 0xf); + try testOr(u140, maxInt(u140), 0, maxInt(u140)); + try testOr(u140, (1 << 17) | (1 << 3), (1 << 17) | (1 << 1), (1 << 17) | 0xa); + + try testOr(u256, 0, 1 << 255, 1 << 255); + try testOr(u256, (1 << 200) | 0x30, (1 << 199) | 0x0f, (1 << 200) | (1 << 199) | 0x3f); + try testOr(u256, maxInt(u256), 1 << 17, maxInt(u256)); + try testOr(u256, 1 << 130, 1 << 64, (1 << 130) | (1 << 64)); + + try testOr(i140, -1, 0, -1); + try testOr(i140, minInt(i140), 1, minInt(i140) + 1); + try testOr(i140, -1 << 40, (1 << 5) | 1, (-1 << 40) | 0x21); + try testOr(i140, 0, maxInt(i140), maxInt(i140)); + + try testOr(i256, -1, 1 << 200, -1); + try testOr(i256, minInt(i256), 1 << 17, minInt(i256) | (1 << 17)); + try testOr(i256, -1 << 130, 0xff, (-1 << 130) | 0xff); + try testOr(i256, 0, maxInt(i256), maxInt(i256)); +} + +fn testXor(comptime T: type, a: T, b: T, expected: T) !void { + try expect((a ^ b) == expected); +} + +test "xor > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testXor(u140, 0, maxInt(u140), maxInt(u140)); + try testXor(u140, 1 << 139, 1 << 139, 0); + try testXor(u140, (1 << 70) | 0xa, (1 << 69) | 0x5, (1 << 70) | (1 << 69) | 0xf); + try testXor(u140, maxInt(u140), 1 << 100, maxInt(u140) ^ (1 << 100)); + + try testXor(u256, 0, maxInt(u256), maxInt(u256)); + try testXor(u256, 1 << 255, 1 << 255, 0); + try testXor(u256, (1 << 200) | (1 << 5), (1 << 199) | (1 << 5), (1 << 200) | (1 << 199)); + try testXor(u256, maxInt(u256), 1 << 17, maxInt(u256) ^ (1 << 17)); + + try testXor(i140, -1, -1, 0); + try testXor(i140, -1, 0, -1); + try testXor(i140, minInt(i140), -1, maxInt(i140)); + try testXor(i140, -1 << 40, -1 << 39, 1 << 39); + + try testXor(i256, -1, -1, 0); + try testXor(i256, -1, 0, -1); + try testXor(i256, minInt(i256), -1, maxInt(i256)); + try testXor(i256, -1 << 130, -1 << 129, 1 << 129); +} + +fn testNot(comptime T: type, a: T, expected: T) !void { + try expect((~a) == expected); +} + +test "not > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testNot(u140, 0, maxInt(u140)); + try testNot(u140, maxInt(u140), 0); + try testNot(u140, 1 << 139, maxInt(u140) ^ (1 << 139)); + try testNot(u140, (1 << 17) | 1, maxInt(u140) ^ ((1 << 17) | 1)); + + try testNot(u256, 0, maxInt(u256)); + try testNot(u256, maxInt(u256), 0); + try testNot(u256, 1 << 255, maxInt(u256) ^ (1 << 255)); + try testNot(u256, (1 << 200) | (1 << 5), maxInt(u256) ^ ((1 << 200) | (1 << 5))); + + try testNot(i140, -1, 0); + try testNot(i140, 0, -1); + try testNot(i140, minInt(i140), maxInt(i140)); + try testNot(i140, -1 << 10, (1 << 10) - 1); + + try testNot(i256, -1, 0); + try testNot(i256, 0, -1); + try testNot(i256, minInt(i256), maxInt(i256)); + try testNot(i256, -1 << 200, (1 << 200) - 1); +} + +fn testShl(comptime T: type, a: T, b: std.math.Log2Int(T), expected: T) !void { + try expect((a << b) == expected); +} + +test "shl > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testShl(u140, 1 << 5, 10, 1 << 15); + try testShl(u140, 3, 138, (1 << 139) | (1 << 138)); + try testShl(u140, 1 << 139, 1, 0); + try testShl(u140, (1 << 70) | 1, 3, (1 << 73) | 8); + + try testShl(u256, 1 << 200, 20, 1 << 220); + try testShl(u256, 1 << 255, 1, 0); + try testShl(u256, (1 << 128) | 5, 7, (1 << 135) | 0x280); + try testShl(u256, maxInt(u256), 1, maxInt(u256) - 1); + + try testShl(i140, 1 << 20, 5, 1 << 25); + try testShl(i140, -1, 7, -128); + try testShl(i140, minInt(i140), 1, 0); + try testShl(i140, -1 << 10, 5, -1 << 15); + + try testShl(i256, 1 << 200, 30, 1 << 230); + try testShl(i256, -1, 200, -1 << 200); + try testShl(i256, minInt(i256), 1, 0); + try testShl(i256, -1 << 100, 50, -1 << 150); +} + +fn testShr(comptime T: type, a: T, b: std.math.Log2Int(T), expected: T) !void { + try expect((a >> b) == expected); +} + +test "shr > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testShr(u140, 1 << 139, 39, 1 << 100); + try testShr(u140, (1 << 70) | 8, 3, (1 << 67) | 1); + try testShr(u140, 1, 1, 0); + try testShr(u140, maxInt(u140), 139, 1); + + try testShr(u256, 1 << 255, 55, 1 << 200); + try testShr(u256, (1 << 200) | (1 << 7), 7, (1 << 193) | 1); + try testShr(u256, 1, 1, 0); + try testShr(u256, maxInt(u256), 255, 1); + + try testShr(i140, -1, 17, -1); + try testShr(i140, minInt(i140), 1, minInt(i140) >> 1); + try testShr(i140, -1 << 80, 40, -1 << 40); + try testShr(i140, -5, 1, -3); + + try testShr(i256, -1, 200, -1); + try testShr(i256, minInt(i256), 1, minInt(i256) >> 1); + try testShr(i256, -1 << 180, 80, -1 << 100); + try testShr(i256, -5, 1, -3); +} + +fn testClz(comptime T: type, a: T, expected: u16) !void { + try expect(@clz(a) == expected); +} + +test "@clz > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testClz(u140, 0, 140); + try testClz(u140, 1 << 139, 0); + try testClz(u140, 1 << 70, 69); + try testClz(u140, maxInt(u140), 0); + + try testClz(u256, 0, 256); + try testClz(u256, 1 << 255, 0); + try testClz(u256, 1 << 200, 55); + try testClz(u256, 1, 255); + + try testClz(i140, -1, 0); + try testClz(i140, minInt(i140), 0); + try testClz(i140, 1 << 70, 69); + try testClz(i140, 0, 140); + + try testClz(i256, -1, 0); + try testClz(i256, minInt(i256), 0); + try testClz(i256, 1 << 200, 55); + try testClz(i256, 0, 256); +} + +fn testCtz(comptime T: type, a: T, expected: u16) !void { + try expect(@ctz(a) == expected); +} + +test "@ctz > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testCtz(u140, 0, 140); + try testCtz(u140, 1 << 139, 139); + try testCtz(u140, 1 << 70, 70); + try testCtz(u140, maxInt(u140), 0); + + try testCtz(u256, 0, 256); + try testCtz(u256, 1 << 255, 255); + try testCtz(u256, 1 << 200, 200); + try testCtz(u256, 3 << 5, 5); + + try testCtz(i140, -1, 0); + try testCtz(i140, minInt(i140), 139); + try testCtz(i140, 0, 140); + try testCtz(i140, -1 << 70, 70); + + try testCtz(i256, -1, 0); + try testCtz(i256, minInt(i256), 255); + try testCtz(i256, 0, 256); + try testCtz(i256, -1 << 200, 200); +} + +fn testPopCount(comptime T: type, a: T, expected: u16) !void { + try expect(@popCount(a) == expected); +} + +test "@popCount > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testPopCount(u140, 0, 0); + try testPopCount(u140, maxInt(u140), 140); + try testPopCount(u140, (1 << 139) | (1 << 70) | 1, 3); + try testPopCount(u140, (1 << 5) - 1, 5); + + try testPopCount(u256, 0, 0); + try testPopCount(u256, maxInt(u256), 256); + try testPopCount(u256, (1 << 255) | (1 << 200) | (1 << 17) | (1 << 3), 4); + try testPopCount(u256, (1 << 64) - 1, 64); + + try testPopCount(i140, -1, 140); + try testPopCount(i140, minInt(i140), 1); + try testPopCount(i140, 0, 0); + try testPopCount(i140, -1 << 70, 70); + + try testPopCount(i256, -1, 256); + try testPopCount(i256, minInt(i256), 1); + try testPopCount(i256, 0, 0); + try testPopCount(i256, -1 << 200, 56); +} + +fn testBitReverse(comptime T: type, a: T, expected: T) !void { + try expect(@bitReverse(a) == expected); +} + +test "@bitReverse > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testBitReverse(u140, 1 << 139, 1); + try testBitReverse(u140, 1 << 70, 1 << 69); + try testBitReverse(u140, 0, 0); + try testBitReverse(u140, maxInt(u140), maxInt(u140)); + + try testBitReverse(u256, 1 << 255, 1); + try testBitReverse(u256, 1 << 200, 1 << 55); + try testBitReverse(u256, 0, 0); + try testBitReverse(u256, maxInt(u256), maxInt(u256)); + + try testBitReverse(i140, -1, -1); + try testBitReverse(i140, minInt(i140), 1); + try testBitReverse(i140, 1 << 70, 1 << 69); + try testBitReverse(i140, 0, 0); + + try testBitReverse(i256, -1, -1); + try testBitReverse(i256, minInt(i256), 1); + try testBitReverse(i256, 1 << 200, 1 << 55); + try testBitReverse(i256, 0, 0); +} + +fn testByteSwap(comptime T: type, a: T, expected: T) !void { + try expect(@byteSwap(a) == expected); +} + +test "@byteSwap > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testByteSwap(u144, 1 << 136, 1); + try testByteSwap(u144, 1, 1 << 136); + try testByteSwap(u144, 0, 0); + try testByteSwap(u144, maxInt(u144), maxInt(u144)); + + try testByteSwap(u256, 1 << 248, 1); + try testByteSwap(u256, 1 << 120, 1 << 128); + try testByteSwap(u256, 1, 1 << 248); + try testByteSwap(u256, maxInt(u256), maxInt(u256)); + + try testByteSwap(i144, -1, -1); + try testByteSwap(i144, minInt(i144), 128); + try testByteSwap(i144, 1, 1 << 136); + try testByteSwap(i144, 1 << 64, 1 << 72); + + try testByteSwap(i256, -1, -1); + try testByteSwap(i256, minInt(i256), 128); + try testByteSwap(i256, 1, 1 << 248); + try testByteSwap(i256, 1 << 120, 1 << 128); +} + +fn testMax(comptime T: type, a: T, b: T, expected: T) !void { + try expect(@max(a, b) == expected); +} + +test "@max > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testMax(u140, 0, maxInt(u140), maxInt(u140)); + try testMax(u140, 1 << 139, 1 << 138, 1 << 139); + try testMax(u140, (1 << 100) + 7, (1 << 100) + 3, (1 << 100) + 7); + try testMax(u140, maxInt(u140) - 1, maxInt(u140), maxInt(u140)); + + try testMax(u200, 1 << 199, 1 << 198, 1 << 199); + try testMax(u200, (1 << 150) + (1 << 17), (1 << 150) + (1 << 18), (1 << 150) + (1 << 18)); + try testMax(u200, 0, 1 << 123, 1 << 123); + try testMax(u200, maxInt(u200), maxInt(u200) - 1, maxInt(u200)); + + try testMax(i140, -1, 0, 0); + try testMax(i140, minInt(i140), maxInt(i140), maxInt(i140)); + try testMax(i140, -1 << 70, -1 << 69, -1 << 69); + try testMax(i140, (1 << 100) - 1, 1 << 100, 1 << 100); + + try testMax(i200, -1, minInt(i200), -1); + try testMax(i200, -1 << 150, -1 << 149, -1 << 149); + try testMax(i200, 1 << 198, (1 << 198) - 1, 1 << 198); + try testMax(i200, maxInt(i200), 0, maxInt(i200)); +} + +fn testMin(comptime T: type, a: T, b: T, expected: T) !void { + try expect(@min(a, b) == expected); +} + +test "@min > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testMin(u140, 0, maxInt(u140), 0); + try testMin(u140, 1 << 139, 1 << 138, 1 << 138); + try testMin(u140, (1 << 100) + 7, (1 << 100) + 3, (1 << 100) + 3); + try testMin(u140, maxInt(u140) - 1, maxInt(u140), maxInt(u140) - 1); + + try testMin(u200, 1 << 199, 1 << 198, 1 << 198); + try testMin(u200, (1 << 150) + (1 << 17), (1 << 150) + (1 << 18), (1 << 150) + (1 << 17)); + try testMin(u200, 0, 1 << 123, 0); + try testMin(u200, maxInt(u200), maxInt(u200) - 1, maxInt(u200) - 1); + + try testMin(i140, -1, 0, -1); + try testMin(i140, minInt(i140), maxInt(i140), minInt(i140)); + try testMin(i140, -1 << 70, -1 << 69, -1 << 70); + try testMin(i140, (1 << 100) - 1, 1 << 100, (1 << 100) - 1); + + try testMin(i200, -1, minInt(i200), minInt(i200)); + try testMin(i200, -1 << 150, -1 << 149, -1 << 150); + try testMin(i200, 1 << 198, (1 << 198) - 1, (1 << 198) - 1); + try testMin(i200, maxInt(i200), 0, 0); +} + +fn testAbs(comptime T: type, a: T, expected: anytype) !void { + try expect(@abs(a) == expected); +} + +test "@abs > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testAbs(u140, 0, 0); + try testAbs(u140, 1 << 139, 1 << 139); + try testAbs(u200, 123456789, 123456789); + try testAbs(u200, maxInt(u200), maxInt(u200)); + + try testAbs(i140, 0, 0); + try testAbs(i140, 1, 1); + try testAbs(i140, -1, 1); + try testAbs(i140, minInt(i140), 1 << 139); + + try testAbs(i200, 1 << 198, 1 << 198); + try testAbs(i200, -1 << 198, 1 << 198); + try testAbs(i200, maxInt(i200), maxInt(i200)); + try testAbs(i200, minInt(i200), 1 << 199); +} + +fn testRem(comptime T: type, numerator: T, denominator: T, expected: T) !void { + try expect(@rem(numerator, denominator) == expected); +} + +test "@rem > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testRem(u140, 0, maxInt(u140), 0); + try testRem(u140, maxInt(u140), maxInt(u140), 0); + try testRem(u140, maxInt(u140), 2, 1); + try testRem(u140, (1 << 139) + 5, 1 << 70, 5); + try testRem(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, 7); + try testRem(u200, 123, 1 << 100, 123); + try testRem(u200, 1 << 120, 1 << 60, 0); + try testRem(u200, maxInt(u200), 1 << 100, (1 << 100) - 1); + + try testRem(i140, 0, maxInt(i140), 0); + try testRem(i140, maxInt(i140), maxInt(i140), 0); + try testRem(i140, -((1 << 100) + 1), 1 << 50, -1); + try testRem(i140, (1 << 100) + 1, -(1 << 50), 1); + try testRem(i140, -((1 << 100) + 1), -(1 << 50), -1); + try testRem(i200, minInt(i200), 1, 0); + try testRem(i200, minInt(i200), -2, 0); + try testRem(i200, maxInt(i200), 2, 1); +} + +fn testMod(comptime T: type, numerator: T, denominator: T, expected: T) !void { + try expect(@mod(numerator, denominator) == expected); +} + +test "@mod > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testMod(u140, 0, maxInt(u140), 0); + try testMod(u140, maxInt(u140), maxInt(u140), 0); + try testMod(u140, maxInt(u140), 2, 1); + try testMod(u140, (1 << 139) + 5, 1 << 70, 5); + try testMod(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, 7); + try testMod(u200, 123, 1 << 100, 123); + try testMod(u200, 1 << 120, 1 << 60, 0); + try testMod(u200, maxInt(u200), 1 << 100, (1 << 100) - 1); + + try testMod(i140, 0, maxInt(i140), 0); + try testMod(i140, maxInt(i140), maxInt(i140), 0); + try testMod(i140, -((1 << 100) + 1), 1 << 50, (1 << 50) - 1); + try testMod(i140, (1 << 100) + 1, -(1 << 50), -(1 << 50) + 1); + try testMod(i140, -((1 << 100) + 1), -(1 << 50), -1); + try testMod(i200, minInt(i200), 1, 0); + try testMod(i200, minInt(i200), -2, 0); + try testMod(i200, maxInt(i200), 2, 1); +} + +fn testDivFloor(comptime T: type, numerator: T, denominator: T, expected: T) !void { + try expect(@divFloor(numerator, denominator) == expected); +} + +test "@divFloor > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testDivFloor(u140, 0, maxInt(u140), 0); + try testDivFloor(u140, maxInt(u140), maxInt(u140), 1); + try testDivFloor(u140, maxInt(u140), 2, maxInt(u140) >> 1); + try testDivFloor(u140, (1 << 139) + 5, 1 << 70, 1 << 69); + try testDivFloor(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, (1 << 50) + 1); + try testDivFloor(u200, 123, 1 << 100, 0); + try testDivFloor(u200, 1 << 120, 1 << 60, 1 << 60); + try testDivFloor(u200, maxInt(u200), 1 << 100, (1 << 100) - 1); + + try testDivFloor(i140, 0, maxInt(i140), 0); + try testDivFloor(i140, maxInt(i140), maxInt(i140), 1); + try testDivFloor(i140, -((1 << 100) + 1), 1 << 50, -(1 << 50) - 1); + try testDivFloor(i140, (1 << 100) + 1, -(1 << 50), -(1 << 50) - 1); + try testDivFloor(i140, -((1 << 100) + 1), -(1 << 50), 1 << 50); + try testDivFloor(i200, -3, 2, -2); + try testDivFloor(i200, minInt(i200), 1, minInt(i200)); + try testDivFloor(i200, minInt(i200), -2, 1 << 198); + try testDivFloor(i200, maxInt(i200), 2, (1 << 198) - 1); +} + +fn testDivTrunc(comptime T: type, numerator: T, denominator: T, expected: T) !void { + try expect(@divTrunc(numerator, denominator) == expected); +} + +test "@divTrunc > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testDivTrunc(u140, 0, maxInt(u140), 0); + try testDivTrunc(u140, maxInt(u140), maxInt(u140), 1); + try testDivTrunc(u140, maxInt(u140), 2, maxInt(u140) >> 1); + try testDivTrunc(u140, (1 << 139) + 5, 1 << 70, 1 << 69); + try testDivTrunc(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, (1 << 50) + 1); + try testDivTrunc(u200, 123, 1 << 100, 0); + try testDivTrunc(u200, 1 << 120, 1 << 60, 1 << 60); + try testDivTrunc(u200, maxInt(u200), 1 << 100, (1 << 100) - 1); + + try testDivTrunc(i140, 0, maxInt(i140), 0); + try testDivTrunc(i140, maxInt(i140), maxInt(i140), 1); + try testDivTrunc(i140, -((1 << 100) + 1), 1 << 50, -(1 << 50)); + try testDivTrunc(i140, (1 << 100) + 1, -(1 << 50), -(1 << 50)); + try testDivTrunc(i140, -((1 << 100) + 1), -(1 << 50), 1 << 50); + try testDivTrunc(i200, -3, 2, -1); + try testDivTrunc(i200, minInt(i200), 1, minInt(i200)); + try testDivTrunc(i200, minInt(i200), -2, 1 << 198); + try testDivTrunc(i200, maxInt(i200), 2, (1 << 198) - 1); +} + test "overflow arithmetic with u0 values" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index d5b01f7c12f71ac5e88976a4d50587ffc460a857..21538ba36923e765ec08c6f5960a0eda9b6bd383 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -582,7 +582,6 @@ test "packed struct fields modification" { test "nested packed struct field access test" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; @@ -736,7 +735,6 @@ test "nested packed struct at non-zero offset 2" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO packed structs larger than 64 bits if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index 900dee88110062e7a10a20fcbf7f27dd3d9b6c9c..949ad2b46062813c8383b5ce89e808de61ab7100 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -4,6 +4,14 @@ const minInt = std.math.minInt; const maxInt = std.math.maxInt; const expect = std.testing.expect; +fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void { + try expect((lhs +| rhs) == expected); + + var x = lhs; + x +|= rhs; + try expect(x == expected); +} + test "saturating add" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -28,32 +36,23 @@ test "saturating add" { try testSatAdd(u2, 3, 2, 3); try testSatAdd(u3, 7, 1, 7); } - - fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void { - try expect((lhs +| rhs) == expected); - - var x = lhs; - x +|= rhs; - try expect(x == expected); - } }; try S.doTheTest(); try comptime S.doTheTest(); - try comptime S.testSatAdd(comptime_int, 0, 0, 0); - try comptime S.testSatAdd(comptime_int, -1, 1, 0); - try comptime S.testSatAdd(comptime_int, 3, 2, 5); - try comptime S.testSatAdd(comptime_int, -3, -2, -5); - try comptime S.testSatAdd(comptime_int, 3, -2, 1); - try comptime S.testSatAdd(comptime_int, -3, 2, -1); - try comptime S.testSatAdd(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 1119305249183743626545271163355074748512); - try comptime S.testSatAdd(comptime_int, 7, -593423721213448152027139550640105366508, -593423721213448152027139550640105366501); + try comptime testSatAdd(comptime_int, 0, 0, 0); + try comptime testSatAdd(comptime_int, -1, 1, 0); + try comptime testSatAdd(comptime_int, 3, 2, 5); + try comptime testSatAdd(comptime_int, -3, -2, -5); + try comptime testSatAdd(comptime_int, 3, -2, 1); + try comptime testSatAdd(comptime_int, -3, 2, -1); + try comptime testSatAdd(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 1119305249183743626545271163355074748512); + try comptime testSatAdd(comptime_int, 7, -593423721213448152027139550640105366508, -593423721213448152027139550640105366501); } test "saturating add 128bit" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; @@ -65,19 +64,20 @@ test "saturating add 128bit" { try testSatAdd(i128, minInt(i128), maxInt(i128), -1); try testSatAdd(u128, maxInt(u128), 1, maxInt(u128)); } - fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void { - try expect((lhs +| rhs) == expected); - - var x = lhs; - x +|= rhs; - try expect(x == expected); - } }; try S.doTheTest(); try comptime S.doTheTest(); } +fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void { + try expect((lhs -| rhs) == expected); + + var x = lhs; + x -|= rhs; + try expect(x == expected); +} + test "saturating subtraction" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -101,32 +101,23 @@ test "saturating subtraction" { try testSatSub(u8, 10, 3, 7); try testSatSub(u8, 0, 255, 0); } - - fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void { - try expect((lhs -| rhs) == expected); - - var x = lhs; - x -|= rhs; - try expect(x == expected); - } }; try S.doTheTest(); try comptime S.doTheTest(); - try comptime S.testSatSub(comptime_int, 0, 0, 0); - try comptime S.testSatSub(comptime_int, 1, 1, 0); - try comptime S.testSatSub(comptime_int, 3, 2, 1); - try comptime S.testSatSub(comptime_int, -3, -2, -1); - try comptime S.testSatSub(comptime_int, 3, -2, 5); - try comptime S.testSatSub(comptime_int, -3, 2, -5); - try comptime S.testSatSub(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 182846383813587550256162760261375991602); - try comptime S.testSatSub(comptime_int, 7, -593423721213448152027139550640105366508, 593423721213448152027139550640105366515); + try comptime testSatSub(comptime_int, 0, 0, 0); + try comptime testSatSub(comptime_int, 1, 1, 0); + try comptime testSatSub(comptime_int, 3, 2, 1); + try comptime testSatSub(comptime_int, -3, -2, -1); + try comptime testSatSub(comptime_int, 3, -2, 5); + try comptime testSatSub(comptime_int, -3, 2, -5); + try comptime testSatSub(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 182846383813587550256162760261375991602); + try comptime testSatSub(comptime_int, 7, -593423721213448152027139550640105366508, 593423721213448152027139550640105366515); } test "saturating subtraction 128bit" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; @@ -138,14 +129,6 @@ test "saturating subtraction 128bit" { try testSatSub(i128, minInt(i128), -maxInt(i128), -1); try testSatSub(u128, 0, maxInt(u128), 0); } - - fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void { - try expect((lhs -| rhs) == expected); - - var x = lhs; - x -|= rhs; - try expect(x == expected); - } }; try S.doTheTest(); @@ -257,7 +240,6 @@ test "saturating mul i64, i128" { test "saturating multiplication" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; @@ -294,6 +276,14 @@ test "saturating multiplication" { try comptime testSatMul(comptime_int, 7, -593423721213448152027139550640105366508, -4153966048494137064189976854480737565556); } +fn testSatShl(comptime Lhs: type, lhs: Lhs, comptime Rhs: type, rhs: Rhs, expected: Lhs) !void { + try expect((lhs <<| rhs) == expected); + + var x = lhs; + x <<|= rhs; + try expect(x == expected); +} + test "saturating shift-left" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -320,23 +310,15 @@ test "saturating shift-left" { try testSatShl(u8, 0, u4, 8, 0); try testSatShl(u8, 3, u4, 8, maxInt(u8)); } - - fn testSatShl(comptime Lhs: type, lhs: Lhs, comptime Rhs: type, rhs: Rhs, expected: Lhs) !void { - try expect((lhs <<| rhs) == expected); - - var x = lhs; - x <<|= rhs; - try expect(x == expected); - } }; try S.doTheTest(); try comptime S.doTheTest(); - try comptime S.testSatShl(comptime_int, 0, comptime_int, 0, 0); - try comptime S.testSatShl(comptime_int, 1, comptime_int, 2, 4); - try comptime S.testSatShl(comptime_int, 13, comptime_int, 150, 18554220005177478453757717602843436772975706112); - try comptime S.testSatShl(comptime_int, -582769, comptime_int, 180, -893090893854873184096635538665358532628308979495815656505344); + try comptime testSatShl(comptime_int, 0, comptime_int, 0, 0); + try comptime testSatShl(comptime_int, 1, comptime_int, 2, 4); + try comptime testSatShl(comptime_int, 13, comptime_int, 150, 18554220005177478453757717602843436772975706112); + try comptime testSatShl(comptime_int, -582769, comptime_int, 180, -893090893854873184096635538665358532628308979495815656505344); } test "saturating shift-left large rhs" { @@ -386,3 +368,63 @@ test "saturating shl uses the LHS type" { try expect((1 <<| @as(u8, 200)) == 1606938044258990275541962092341162602522202993782792835301376); } + +test "sat add > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testSatAdd(u140, 0, 0, 0); + try testSatAdd(u140, maxInt(u140), 1, maxInt(u140)); + try testSatAdd(u200, 1 << 150, 1 << 20, (1 << 150) + (1 << 20)); + try testSatAdd(u200, maxInt(u200), maxInt(u200), maxInt(u200)); + + try testSatAdd(i140, minInt(i140), -1, minInt(i140)); + try testSatAdd(i140, maxInt(i140), 1, maxInt(i140)); + try testSatAdd(i200, -1 << 150, 1 << 149, -1 << 149); + try testSatAdd(i200, maxInt(i200), maxInt(i200), maxInt(i200)); +} + +test "sat sub > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testSatSub(u140, 0, 1, 0); + try testSatSub(u140, maxInt(u140), maxInt(u140), 0); + try testSatSub(u200, 1 << 150, 1 << 20, (1 << 150) - (1 << 20)); + try testSatSub(u200, maxInt(u200), 0, maxInt(u200)); + + try testSatSub(i140, minInt(i140), 1, minInt(i140)); + try testSatSub(i140, maxInt(i140), -1, maxInt(i140)); + try testSatSub(i200, -1 << 150, 1 << 149, -3 << 149); + try testSatSub(i200, 0, minInt(i200), maxInt(i200)); +} + +test "sat mul > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testSatMul(u140, 0, maxInt(u140), 0); + try testSatMul(u140, 1 << 70, 1 << 69, 1 << 139); + try testSatMul(u200, maxInt(u200), 2, maxInt(u200)); + try testSatMul(u200, maxInt(u200) - 1, 1, maxInt(u200) - 1); + + try testSatMul(i140, -1, maxInt(i140), -maxInt(i140)); + try testSatMul(i140, minInt(i140), -1, maxInt(i140)); + try testSatMul(i200, 1 << 100, 1 << 99, maxInt(i200)); + try testSatMul(i200, -1 << 150, 1 << 30, -1 << 180); +} + +test "sat shl > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testSatShl(u140, 0, u8, 17, 0); + try testSatShl(u140, 1 << 100, u8, 20, 1 << 120); + try testSatShl(u200, maxInt(u200), u8, 1, maxInt(u200)); + try testSatShl(u200, 1 << 199, u8, 1, maxInt(u200)); + + try testSatShl(i140, 0, u8, 17, 0); + try testSatShl(i140, 1 << 100, u8, 38, 1 << 138); + try testSatShl(i140, 1 << 100, u8, 39, maxInt(i140)); + try testSatShl(i200, minInt(i200) + 1, u8, 1, minInt(i200)); +} diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index b88597ad969def43ea7de26efdeaa869b5aa261b..1e0907d8f6a6148a7aa93405edda27d2cd58d044 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -540,7 +540,6 @@ test "zero-bit field in packed struct" { test "packed struct with non-ABI-aligned field" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 95fd81e58fa6a26244f6cbcc9a01f4b6aa38e370..c2a98de5283b3bf117cdc1db22fce9e534eaf5c2 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -1457,8 +1457,6 @@ test "switch on nested packed containers" { } test "switch on large types" { - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - const S = struct { fn doTheTest(a: u128, b: i500) !void { switch (a) { diff --git a/test/behavior/switch_loop.zig b/test/behavior/switch_loop.zig index 14794d2d120852838b94b3ba7d8e956fbd827441..4b8cdc71b5e0e458b878577d673390eb60739057 100644 --- a/test/behavior/switch_loop.zig +++ b/test/behavior/switch_loop.zig @@ -566,8 +566,6 @@ test "switch loop with packed unions with OPV" { } test "switch loop on large types" { - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; - const S = struct { fn doTheTest(a: u128, b: i500) !void { label: switch (a) { diff --git a/test/behavior/truncate.zig b/test/behavior/truncate.zig index 3d3c98b86544b012e81d2dacc848a09aae38131b..d92e411ad1acd4cb8fd5a4f3b8f97f4f903cc146 100644 --- a/test/behavior/truncate.zig +++ b/test/behavior/truncate.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const math = std.math; +const maxInt = math.maxInt; +const minInt = math.minInt; const builtin = @import("builtin"); const assert = std.debug.assert; const expect = std.testing.expect; @@ -64,6 +67,49 @@ test "truncate on comptime integer" { try expect(w == 0); } +fn testTruncate(comptime S: type, a: S, comptime D: type, expected: D) !void { + const actual: D = @truncate(a); + try expect(actual == expected); +} + +test "@truncate > 128 bits" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + + try testTruncate(u140, 0, u128, 0); + try testTruncate(u140, maxInt(u140), u128, maxInt(u128)); + try testTruncate(u140, 1 << 139, u128, 0); + try testTruncate(u140, (1 << 139) | (1 << 64) | 0x55, u128, (1 << 64) | 0x55); + try testTruncate(u140, (1 << 100) | (1 << 63), u128, (1 << 100) | (1 << 63)); + try testTruncate(u140, (1 << 130) | 0xabcd, u16, 0xabcd); + + try testTruncate(u256, 1 << 200, u128, 0); + try testTruncate(u256, (1 << 200) | (1 << 127) | 1, u128, (1 << 127) | 1); + try testTruncate(u256, maxInt(u256), u128, maxInt(u128)); + try testTruncate(u256, (1 << 255) | (1 << 128) | 0x1234_5678_9abc_def0, u64, 0x1234_5678_9abc_def0); + try testTruncate(u256, (1 << 250) | (1 << 32), u32, 0); + try testTruncate(u256, (1 << 129) | (1 << 63), u64, 1 << 63); + + try testTruncate(i140, 0, i128, 0); + try testTruncate(i140, -1, i128, -1); + try testTruncate(i140, -2, i8, -2); + try testTruncate(i140, -1 << 80, i64, 0); + try testTruncate(i140, (-1 << 80) | 0x1234, i16, 0x1234); + try testTruncate(i140, minInt(i140), i128, 0); + try testTruncate(i140, maxInt(i140), i128, -1); + try testTruncate(i140, (1 << 127) - 1, i128, maxInt(i128)); + + try testTruncate(i256, -1, i128, -1); + try testTruncate(i256, minInt(i256), i128, 0); + try testTruncate(i256, (-1 << 128) | maxInt(i128), i128, maxInt(i128)); + try testTruncate(i256, (-1 << 200) | (1 << 127), i128, minInt(i128)); + try testTruncate(i256, -255, i8, 1); + try testTruncate(i256, (-1 << 64) | 0x1234_5678, i32, 0x1234_5678); + + try testTruncate(i257, maxInt(i257), i256, -1); + try testTruncate(u257, maxInt(u257), u256, maxInt(u256)); +} + test "truncate on vectors" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;