diff --git a/lib/compiler_rt/divmodei4.zig b/lib/compiler_rt/divmodei4.zig index 4fa168a675..f30eded317 100644 --- a/lib/compiler_rt/divmodei4.zig +++ b/lib/compiler_rt/divmodei4.zig @@ -24,23 +24,30 @@ inline fn neg(x: []u32) void { } } -/// Mutates the arguments! -fn divmod(q: ?[]u32, r: ?[]u32, u: []u32, v: []u32) !void { +const max_limbs = std.math.divCeil(usize, 65535, 32) catch unreachable; + +fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const 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 }); + var ua: [max_limbs]u32 = undefined; + const us = ua[0..u.len]; + @memcpy(us, u); + var va: [max_limbs]u32 = undefined; + const vs = va[0..v.len]; + @memcpy(vs, v); + if (u_sign < 0) neg(us); + if (v_sign < 0) neg(vs); + try @call(.always_inline, udivmod, .{ q, r, us, vs }); if (q) |x| if (u_sign ^ v_sign < 0) neg(x); if (r) |x| if (u_sign < 0) neg(x); } -pub fn __divei4(q_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) void { +pub fn __divei4(q_p: [*]u8, u_p: [*]const u8, v_p: [*]const 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: []u32 = @ptrCast(@alignCast(u_p[0..byte_size])); - const v: []u32 = @ptrCast(@alignCast(v_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])); @call(.always_inline, divmod, .{ q, null, u, v }) catch unreachable; } diff --git a/lib/compiler_rt/limb64.zig b/lib/compiler_rt/limb64.zig index c2dc4da8cf..618e60af36 100644 --- a/lib/compiler_rt/limb64.zig +++ b/lib/compiler_rt/limb64.zig @@ -25,10 +25,26 @@ 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 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); @@ -60,7 +76,7 @@ comptime { } 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 limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; const b = b_ptr[0..limb_cnt]; @@ -92,11 +108,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; } } @@ -132,7 +150,7 @@ comptime { } 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 limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; const b = b_ptr[0..limb_cnt]; @@ -164,10 +182,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; } } @@ -206,7 +226,7 @@ comptime { // 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 limb_cnt = usedLimbCount(bits); const a = a_ptr[0..limb_cnt]; const b = b_ptr[0..limb_cnt]; @@ -391,7 +411,7 @@ comptime { } fn __not_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; @@ -405,6 +425,7 @@ fn __not_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16 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 { @@ -436,7 +457,7 @@ comptime { } fn __shlo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) bool { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; @@ -477,6 +498,7 @@ fn __shlo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bo overflow = overflow or limbGet(a, j) != sign_extend; } + fixLastLimb(out_ptr, is_signed, bits); return overflow; } @@ -526,7 +548,7 @@ comptime { } fn __shr_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) void { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; @@ -594,7 +616,7 @@ comptime { } fn __clz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const a = a_ptr[0..limb_cnt]; var res: u16 = 0; @@ -652,7 +674,7 @@ comptime { } fn __ctz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const a = a_ptr[0..limb_cnt]; var res: u16 = 0; @@ -705,7 +727,7 @@ comptime { } fn __popcount_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const a = a_ptr[0..limb_cnt]; var res: u16 = 0; @@ -751,7 +773,7 @@ comptime { } fn __bitreverse_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; @@ -764,6 +786,7 @@ fn __bitreverse_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bi 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 { @@ -797,7 +820,7 @@ comptime { } fn __byteswap_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void { - const limb_cnt = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; @@ -812,6 +835,7 @@ fn __byteswap_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits 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 { @@ -861,7 +885,7 @@ fn mulwide(a: u64, b: u64) [2]u64 { } 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 = limbCount(bits); + const limb_cnt = usedLimbCount(bits); const out = out_ptr[0..limb_cnt]; const a = a_ptr[0..limb_cnt]; @@ -921,6 +945,8 @@ fn __mulo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_s limbSet(out, limb_cnt - 1, last); } + fixLastLimb(out_ptr, is_signed, bits); + if (!is_signed) { return !hi_zero or raw_last != last; } diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index ff498d603a..8fff084c16 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -2357,6 +2357,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, @@ -2518,7 +2527,15 @@ 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); + if (ty.is_signed) { + _ = try cg.callIntrinsic(.__divei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } }); + } else { + _ = try cg.callIntrinsic(.__udivei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } }); + } + return result; + }, } } @@ -2570,7 +2587,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); + }, } } @@ -2596,7 +2628,15 @@ 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); + if (ty.is_signed) { + _ = try cg.callIntrinsic(.__modei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } }); + } else { + _ = try cg.callIntrinsic(.__umodei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } }); + } + return result; + }, } } @@ -3315,26 +3355,43 @@ fn intWrap(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { }, 128 => return operand, else => { - const bits = mem.alignForward(u16, ty.bits, 64); + const bits = cg.intBackingBits(ty.bits); if (ty.bits == bits) return operand; const result = try cg.allocInt(ty); - const len = bits / 8; - try cg.memcpy(result, operand, .{ .imm32 = len - 8 }); + const copy_len = (ty.bits / 64) * 8; + try cg.memcpy(result, operand, .{ .imm32 = copy_len }); - try cg.emitWValue(result); - _ = try cg.load(operand, Type.u64, len - 8); - if (ty.is_signed) { - try cg.addImm64(bits - ty.bits); - try cg.addTag(.i64_shl); - try cg.addImm64(bits - ty.bits); - try cg.addTag(.i64_shr_s); - } else { - try cg.addImm64(~@as(u64, 0) >> @intCast(bits - ty.bits)); - try cg.addTag(.i64_and); + if (ty.bits % 64 != 0) { + const pad = 64 - ty.bits % 64; + + try cg.emitWValue(result); + _ = try cg.load(operand, Type.u64, copy_len); + 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() + copy_len); + } + + const full_len = @divExact(bits, 8); + if (copy_len + 16 == full_len) { // last limb needs sign extended + try cg.emitWValue(result); + if (ty.is_signed) { + _ = try cg.load(result, Type.u64, copy_len); + try cg.addImm64(63); + try cg.addTag(.i64_shr_s); + } else { + try cg.addImm64(0); + } + try cg.store(.stack, .stack, Type.u64, result.offset() + copy_len + 8); } - try cg.store(.stack, .stack, Type.u64, result.offset() + len - 8); return result; }, @@ -3354,8 +3411,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) { @@ -3363,6 +3420,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 normal_len = (int_ty.bits / 64) * 8; + + try cg.memset(Type.u8, result, .{ .imm32 = normal_len }, .{ .imm32 = 0xFF }); + + if (int_ty.is_signed) { + try cg.store(result, .{ .imm64 = (~@as(u64, 0) >> @intCast((normal_len + 8) * 8 - int_ty.bits)) >> 1 }, Type.u64, normal_len); + } else { + try cg.store(result, .{ .imm64 = ~@as(u64, 0) >> @intCast((normal_len + 8) * 8 - int_ty.bits) }, Type.u64, normal_len); + } + + if (normal_len + 16 == full_len) { + try cg.store(result, .{ .imm64 = 0 }, Type.u64, full_len - 8); + } + return result; } } @@ -3375,10 +3450,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 normal_len = (int_ty.bits / 64) * 8; + + try cg.memset(Type.u8, result, .{ .imm32 = normal_len }, .{ .imm32 = 0 }); + try cg.store(result, .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - normal_len * 8 - 1) }, Type.u64, normal_len); + + if (normal_len + 16 == full_len) { + try cg.store(result, .{ .imm64 = ~@as(u64, 0) }, Type.u64, full_len - 8); + } + return result; } } @@ -3572,12 +3660,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; + }, } } @@ -3757,17 +3850,8 @@ fn intShlOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerErro } 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, - else => mem.alignForward(u16, src_ty.bits, 64), - }; - - const dest_bits: u16 = switch (dest_ty.bits) { - 0 => unreachable, - 1...32 => 32, - else => mem.alignForward(u16, dest_ty.bits, 64), - }; + 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; @@ -3859,11 +3943,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, - else => mem.alignForward(u16, dest_ty.bits, 64), - }; + const dest_wasm_bits = cg.intBackingBits(dest_ty.bits); if (dest_wasm_bits != dest_ty.bits) { result = try cg.intWrap(dest_ty, result); diff --git a/src/codegen/wasm/Mir.zig b/src/codegen/wasm/Mir.zig index 15a48dae49..d2d48e9bee 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, + __divei4, __divhf3, __divtf3, __divti3, @@ -950,6 +951,7 @@ pub const Intrinsic = enum(u32) { __lshrti3, __lttf2, __ltxf2, + __modei4, __modti3, __mulhf3, __mulodi4, @@ -980,7 +982,9 @@ pub const Intrinsic = enum(u32) { __truncxfdf2, __truncxfhf2, __truncxfsf2, + __udivei4, __udivti3, + __umodei4, __umodti3, ceilq, cos, diff --git a/test/behavior/math.zig b/test/behavior/math.zig index b72eea6aa0..e62f7be41f 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1736,6 +1736,112 @@ test "@abs > 128 bits" { 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; + + 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; + + 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; + + 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; + + 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;