commit aa7874657b3439134eb4cd8b65271fb9cc38fdad (tree)
parent fff887874e917e2955002c323fb0286cedfbfd84
Author: Pavel Verigo <paul.verigo@gmail.com>
Date: Wed, 8 Apr 2026 03:33:54 +0200
stage2-wasm: bigint div mod rem
Diffstat:
5 files changed, 283 insertions(+), 60 deletions(-)
diff --git 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
@@ -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
@@ -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) {
@@ -3364,6 +3421,24 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {
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,11 +3450,24 @@ 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
@@ -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
@@ -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;