authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-04-08 03:33:54+02:00
committergravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-04-09 00:14:09+02:00
logaa7874657b3439134eb4cd8b65271fb9cc38fdad
treebabc9d61c1cbed627cdba474787e7959c67cd4dc
parentfff887874e917e2955002c323fb0286cedfbfd84

stage2-wasm: bigint div mod rem


5 files changed, 283 insertions(+), 60 deletions(-)

lib/compiler_rt/divmodei4.zig+15-8
...@@ -24,23 +24,30 @@ inline fn neg(x: []u32) void {...@@ -24,23 +24,30 @@ inline fn neg(x: []u32) void {
24 }24 }
25}25}
2626
27/// Mutates the arguments!27const max_limbs = std.math.divCeil(usize, 65535, 32) catch unreachable;
28fn divmod(q: ?[]u32, r: ?[]u32, u: []u32, v: []u32) !void {28
29fn divmod(q: ?[]u32, r: ?[]u32, u: []const u32, v: []const u32) !void {
29 const u_sign: i32 = @bitCast(u[u.len - 1]);30 const u_sign: i32 = @bitCast(u[u.len - 1]);
30 const v_sign: i32 = @bitCast(v[v.len - 1]);31 const v_sign: i32 = @bitCast(v[v.len - 1]);
31 if (u_sign < 0) neg(u);32 var ua: [max_limbs]u32 = undefined;
32 if (v_sign < 0) neg(v);33 const us = ua[0..u.len];
33 try @call(.always_inline, udivmod, .{ q, r, u, v });34 @memcpy(us, u);
35 var va: [max_limbs]u32 = undefined;
36 const vs = va[0..v.len];
37 @memcpy(vs, v);
38 if (u_sign < 0) neg(us);
39 if (v_sign < 0) neg(vs);
40 try @call(.always_inline, udivmod, .{ q, r, us, vs });
34 if (q) |x| if (u_sign ^ v_sign < 0) neg(x);41 if (q) |x| if (u_sign ^ v_sign < 0) neg(x);
35 if (r) |x| if (u_sign < 0) neg(x);42 if (r) |x| if (u_sign < 0) neg(x);
36}43}
3744
38pub fn __divei4(q_p: [*]u8, u_p: [*]u8, v_p: [*]u8, bits: usize) callconv(.c) void {45pub fn __divei4(q_p: [*]u8, u_p: [*]const u8, v_p: [*]const u8, bits: usize) callconv(.c) void {
39 @setRuntimeSafety(compiler_rt.test_safety);46 @setRuntimeSafety(compiler_rt.test_safety);
40 const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits));47 const byte_size = std.zig.target.intByteSize(&builtin.target, @intCast(bits));
41 const q: []u32 = @ptrCast(@alignCast(q_p[0..byte_size]));48 const q: []u32 = @ptrCast(@alignCast(q_p[0..byte_size]));
42 const u: []u32 = @ptrCast(@alignCast(u_p[0..byte_size]));49 const u: []const u32 = @ptrCast(@alignCast(u_p[0..byte_size]));
43 const v: []u32 = @ptrCast(@alignCast(v_p[0..byte_size]));50 const v: []const u32 = @ptrCast(@alignCast(v_p[0..byte_size]));
44 @call(.always_inline, divmod, .{ q, null, u, v }) catch unreachable;51 @call(.always_inline, divmod, .{ q, null, u, v }) catch unreachable;
45}52}
4653
lib/compiler_rt/limb64.zig+39-13
...@@ -25,10 +25,26 @@ inline fn limbSet(limbs: []u64, i: usize, value: u64) void {...@@ -25,10 +25,26 @@ inline fn limbSet(limbs: []u64, i: usize, value: u64) void {
25 }25 }
26}26}
2727
28fn limbCount(bits: u16) u16 {28fn usedLimbCount(bits: u16) u16 {
29 return divCeil(u16, bits, 64) catch unreachable;29 return divCeil(u16, bits, 64) catch unreachable;
30}30}
3131
32fn limbCount(bits: u16) u16 {
33 return @divExact(std.zig.target.intByteSize(&builtin.target, bits), 8);
34}
35
36fn fixLastLimb(out_ptr: [*]u64, is_signed: bool, bits: u16) void {
37 const limb_cnt = usedLimbCount(bits);
38 const true_limb_cnt = limbCount(bits);
39 if (limb_cnt == true_limb_cnt) return;
40 const true_out = out_ptr[0..true_limb_cnt];
41
42 const sign: u64 = if (!is_signed or @as(i64, @bitCast(true_out[limb_cnt - 1])) >= 0) 0 else ~@as(u64, 0);
43 for (limb_cnt..true_limb_cnt) |i| {
44 true_out[i] = sign;
45 }
46}
47
32fn Limbs(T: type) type {48fn Limbs(T: type) type {
33 const int_info = @typeInfo(T).int;49 const int_info = @typeInfo(T).int;
34 const limb_cnt = comptime limbCount(int_info.bits);50 const limb_cnt = comptime limbCount(int_info.bits);
...@@ -60,7 +76,7 @@ comptime {...@@ -60,7 +76,7 @@ comptime {
60}76}
6177
62fn __addo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {78fn __addo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
63 const limb_cnt = limbCount(bits);79 const limb_cnt = usedLimbCount(bits);
64 const out = out_ptr[0..limb_cnt];80 const out = out_ptr[0..limb_cnt];
65 const a = a_ptr[0..limb_cnt];81 const a = a_ptr[0..limb_cnt];
66 const b = b_ptr[0..limb_cnt];82 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...@@ -92,11 +108,13 @@ fn __addo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_s
92108
93 if (bits % 64 == 0) {109 if (bits % 64 == 0) {
94 limbSet(out, i, limb);110 limbSet(out, i, limb);
111 fixLastLimb(out_ptr, is_signed, bits);
95 return carry != 0;112 return carry != 0;
96 } else {113 } else {
97 assert(carry == 0);114 assert(carry == 0);
98 const wrapped_limb = limbWrap(limb, is_signed, bits);115 const wrapped_limb = limbWrap(limb, is_signed, bits);
99 limbSet(out, i, wrapped_limb);116 limbSet(out, i, wrapped_limb);
117 fixLastLimb(out_ptr, is_signed, bits);
100 return wrapped_limb != limb;118 return wrapped_limb != limb;
101 }119 }
102}120}
...@@ -132,7 +150,7 @@ comptime {...@@ -132,7 +150,7 @@ comptime {
132}150}
133151
134fn __subo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {152fn __subo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
135 const limb_cnt = limbCount(bits);153 const limb_cnt = usedLimbCount(bits);
136 const out = out_ptr[0..limb_cnt];154 const out = out_ptr[0..limb_cnt];
137 const a = a_ptr[0..limb_cnt];155 const a = a_ptr[0..limb_cnt];
138 const b = b_ptr[0..limb_cnt];156 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...@@ -164,10 +182,12 @@ fn __subo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_s
164182
165 if (bits % 64 == 0) {183 if (bits % 64 == 0) {
166 limbSet(out, i, limb);184 limbSet(out, i, limb);
185 fixLastLimb(out_ptr, is_signed, bits);
167 return borrow != 0;186 return borrow != 0;
168 } else {187 } else {
169 const wrapped_limb = limbWrap(limb, is_signed, bits);188 const wrapped_limb = limbWrap(limb, is_signed, bits);
170 limbSet(out, i, wrapped_limb);189 limbSet(out, i, wrapped_limb);
190 fixLastLimb(out_ptr, is_signed, bits);
171 return borrow != 0 or wrapped_limb != limb;191 return borrow != 0 or wrapped_limb != limb;
172 }192 }
173}193}
...@@ -206,7 +226,7 @@ comptime {...@@ -206,7 +226,7 @@ comptime {
206// a == b -> 0226// a == b -> 0
207// a > b -> 1227// a > b -> 1
208fn __cmp_limb64(a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) i8 {228fn __cmp_limb64(a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) i8 {
209 const limb_cnt = limbCount(bits);229 const limb_cnt = usedLimbCount(bits);
210 const a = a_ptr[0..limb_cnt];230 const a = a_ptr[0..limb_cnt];
211 const b = b_ptr[0..limb_cnt];231 const b = b_ptr[0..limb_cnt];
212232
...@@ -391,7 +411,7 @@ comptime {...@@ -391,7 +411,7 @@ comptime {
391}411}
392412
393fn __not_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {413fn __not_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {
394 const limb_cnt = limbCount(bits);414 const limb_cnt = usedLimbCount(bits);
395 const out = out_ptr[0..limb_cnt];415 const out = out_ptr[0..limb_cnt];
396 const a = a_ptr[0..limb_cnt];416 const a = a_ptr[0..limb_cnt];
397417
...@@ -405,6 +425,7 @@ fn __not_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16...@@ -405,6 +425,7 @@ fn __not_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16
405 limb = limbWrap(limb, is_signed, bits);425 limb = limbWrap(limb, is_signed, bits);
406 }426 }
407 limbSet(out, i, limb);427 limbSet(out, i, limb);
428 fixLastLimb(out_ptr, is_signed, bits);
408}429}
409430
410fn test__not_limb64(comptime T: type, a: T, expected: T) !void {431fn test__not_limb64(comptime T: type, a: T, expected: T) !void {
...@@ -436,7 +457,7 @@ comptime {...@@ -436,7 +457,7 @@ comptime {
436}457}
437458
438fn __shlo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) bool {459fn __shlo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) bool {
439 const limb_cnt = limbCount(bits);460 const limb_cnt = usedLimbCount(bits);
440 const out = out_ptr[0..limb_cnt];461 const out = out_ptr[0..limb_cnt];
441 const a = a_ptr[0..limb_cnt];462 const a = a_ptr[0..limb_cnt];
442463
...@@ -477,6 +498,7 @@ fn __shlo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bo...@@ -477,6 +498,7 @@ fn __shlo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bo
477 overflow = overflow or limbGet(a, j) != sign_extend;498 overflow = overflow or limbGet(a, j) != sign_extend;
478 }499 }
479500
501 fixLastLimb(out_ptr, is_signed, bits);
480 return overflow;502 return overflow;
481}503}
482504
...@@ -526,7 +548,7 @@ comptime {...@@ -526,7 +548,7 @@ comptime {
526}548}
527549
528fn __shr_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) void {550fn __shr_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, shift: u16, is_signed: bool, bits: u16) callconv(.c) void {
529 const limb_cnt = limbCount(bits);551 const limb_cnt = usedLimbCount(bits);
530 const out = out_ptr[0..limb_cnt];552 const out = out_ptr[0..limb_cnt];
531 const a = a_ptr[0..limb_cnt];553 const a = a_ptr[0..limb_cnt];
532554
...@@ -594,7 +616,7 @@ comptime {...@@ -594,7 +616,7 @@ comptime {
594}616}
595617
596fn __clz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {618fn __clz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {
597 const limb_cnt = limbCount(bits);619 const limb_cnt = usedLimbCount(bits);
598 const a = a_ptr[0..limb_cnt];620 const a = a_ptr[0..limb_cnt];
599621
600 var res: u16 = 0;622 var res: u16 = 0;
...@@ -652,7 +674,7 @@ comptime {...@@ -652,7 +674,7 @@ comptime {
652}674}
653675
654fn __ctz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {676fn __ctz_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {
655 const limb_cnt = limbCount(bits);677 const limb_cnt = usedLimbCount(bits);
656 const a = a_ptr[0..limb_cnt];678 const a = a_ptr[0..limb_cnt];
657679
658 var res: u16 = 0;680 var res: u16 = 0;
...@@ -705,7 +727,7 @@ comptime {...@@ -705,7 +727,7 @@ comptime {
705}727}
706728
707fn __popcount_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {729fn __popcount_limb64(a_ptr: [*]const u64, bits: u16) callconv(.c) u16 {
708 const limb_cnt = limbCount(bits);730 const limb_cnt = usedLimbCount(bits);
709 const a = a_ptr[0..limb_cnt];731 const a = a_ptr[0..limb_cnt];
710732
711 var res: u16 = 0;733 var res: u16 = 0;
...@@ -751,7 +773,7 @@ comptime {...@@ -751,7 +773,7 @@ comptime {
751}773}
752774
753fn __bitreverse_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {775fn __bitreverse_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {
754 const limb_cnt = limbCount(bits);776 const limb_cnt = usedLimbCount(bits);
755 const out = out_ptr[0..limb_cnt];777 const out = out_ptr[0..limb_cnt];
756 const a = a_ptr[0..limb_cnt];778 const a = a_ptr[0..limb_cnt];
757779
...@@ -764,6 +786,7 @@ fn __bitreverse_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bi...@@ -764,6 +786,7 @@ fn __bitreverse_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bi
764 if (bits % 64 != 0) {786 if (bits % 64 != 0) {
765 __shr_limb64(out_ptr, out_ptr, 64 - bits % 64, is_signed, bits);787 __shr_limb64(out_ptr, out_ptr, 64 - bits % 64, is_signed, bits);
766 }788 }
789 fixLastLimb(out_ptr, is_signed, bits);
767}790}
768791
769fn test__bitreverse_limb64(comptime T: type, a: T, expected: T) !void {792fn test__bitreverse_limb64(comptime T: type, a: T, expected: T) !void {
...@@ -797,7 +820,7 @@ comptime {...@@ -797,7 +820,7 @@ comptime {
797}820}
798821
799fn __byteswap_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {822fn __byteswap_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) void {
800 const limb_cnt = limbCount(bits);823 const limb_cnt = usedLimbCount(bits);
801 const out = out_ptr[0..limb_cnt];824 const out = out_ptr[0..limb_cnt];
802 const a = a_ptr[0..limb_cnt];825 const a = a_ptr[0..limb_cnt];
803826
...@@ -812,6 +835,7 @@ fn __byteswap_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits...@@ -812,6 +835,7 @@ fn __byteswap_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, is_signed: bool, bits
812 if (bits % 64 != 0) {835 if (bits % 64 != 0) {
813 __shr_limb64(out_ptr, out_ptr, 64 - bits % 64, is_signed, bits);836 __shr_limb64(out_ptr, out_ptr, 64 - bits % 64, is_signed, bits);
814 }837 }
838 fixLastLimb(out_ptr, is_signed, bits);
815}839}
816840
817fn test__byteswap_limb64(comptime T: type, a: T, expected: T) !void {841fn test__byteswap_limb64(comptime T: type, a: T, expected: T) !void {
...@@ -861,7 +885,7 @@ fn mulwide(a: u64, b: u64) [2]u64 {...@@ -861,7 +885,7 @@ fn mulwide(a: u64, b: u64) [2]u64 {
861}885}
862886
863fn __mulo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {887fn __mulo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
864 const limb_cnt = limbCount(bits);888 const limb_cnt = usedLimbCount(bits);
865889
866 const out = out_ptr[0..limb_cnt];890 const out = out_ptr[0..limb_cnt];
867 const a = a_ptr[0..limb_cnt];891 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...@@ -921,6 +945,8 @@ fn __mulo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_s
921 limbSet(out, limb_cnt - 1, last);945 limbSet(out, limb_cnt - 1, last);
922 }946 }
923947
948 fixLastLimb(out_ptr, is_signed, bits);
949
924 if (!is_signed) {950 if (!is_signed) {
925 return !hi_zero or raw_last != last;951 return !hi_zero or raw_last != last;
926 }952 }
src/codegen/wasm/CodeGen.zig+119-39
...@@ -2357,6 +2357,15 @@ const IntType = struct {...@@ -2357,6 +2357,15 @@ const IntType = struct {
2357 }2357 }
2358};2358};
23592359
2360fn intBackingBits(cg: *CodeGen, bits: u16) u16 {
2361 return switch (bits) {
2362 0 => unreachable,
2363 1...32 => 32,
2364 33...64 => 64,
2365 else => std.zig.target.intByteSize(cg.target, bits) * 8,
2366 };
2367}
2368
2360fn intAdd(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue {2369fn intAdd(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue {
2361 switch (ty.bits) {2370 switch (ty.bits) {
2362 0 => unreachable,2371 0 => unreachable,
...@@ -2518,7 +2527,15 @@ fn intDiv(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue...@@ -2518,7 +2527,15 @@ fn intDiv(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue
2518 return cg.callIntrinsic(.__udivti3, &.{ .i128_type, .i128_type }, Type.i128, &.{ lhs, rhs });2527 return cg.callIntrinsic(.__udivti3, &.{ .i128_type, .i128_type }, Type.i128, &.{ lhs, rhs });
2519 }2528 }
2520 },2529 },
2521 else => return cg.fail("TODO: Support intDiv for integer bitsize: {d}", .{ty.bits}),2530 else => {
2531 const result = try cg.allocInt(ty);
2532 if (ty.is_signed) {
2533 _ = try cg.callIntrinsic(.__divei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } });
2534 } else {
2535 _ = try cg.callIntrinsic(.__udivei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } });
2536 }
2537 return result;
2538 },
2522 }2539 }
2523}2540}
25242541
...@@ -2570,7 +2587,22 @@ fn intDivFloor(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!W...@@ -2570,7 +2587,22 @@ fn intDivFloor(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!W
2570 try cg.addTag(.i64_sub);2587 try cg.addTag(.i64_sub);
2571 return .stack;2588 return .stack;
2572 },2589 },
2573 else => return cg.fail("TODO: Support intDivFloor for signed integer bitsize: {d}", .{ty.bits}),2590 else => {
2591 const q = try cg.intDiv(ty, lhs, rhs);
2592
2593 const zero = try cg.intZeroValue(ty);
2594
2595 const r = try cg.intRem(ty, lhs, rhs);
2596 _ = try cg.intCmp(ty, .neq, r, zero);
2597
2598 const sign_xor = try cg.intXor(ty, lhs, rhs);
2599 _ = try cg.intCmp(ty, .lt, sign_xor, zero);
2600 var adjust = try (try cg.intAnd(.u32, .stack, .stack)).toLocal(cg, Type.u32);
2601
2602 const adjust_bigint = try cg.intCast(ty, .u32, adjust);
2603 adjust.free(cg);
2604 return try cg.intSub(ty, q, adjust_bigint);
2605 },
2574 }2606 }
2575}2607}
25762608
...@@ -2596,7 +2628,15 @@ fn intRem(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue...@@ -2596,7 +2628,15 @@ fn intRem(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue
2596 return cg.callIntrinsic(.__umodti3, &.{ .i128_type, .i128_type }, Type.i128, &.{ lhs, rhs });2628 return cg.callIntrinsic(.__umodti3, &.{ .i128_type, .i128_type }, Type.i128, &.{ lhs, rhs });
2597 }2629 }
2598 },2630 },
2599 else => return cg.fail("TODO: Support intRem for integer bitsize: {d}", .{ty.bits}),2631 else => {
2632 const result = try cg.allocInt(ty);
2633 if (ty.is_signed) {
2634 _ = try cg.callIntrinsic(.__modei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } });
2635 } else {
2636 _ = try cg.callIntrinsic(.__umodei4, &.{ .usize_type, .usize_type, .usize_type, .usize_type }, .void, &.{ result, lhs, rhs, .{ .imm32 = ty.bits } });
2637 }
2638 return result;
2639 },
2600 }2640 }
2601}2641}
26022642
...@@ -3315,26 +3355,43 @@ fn intWrap(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue {...@@ -3315,26 +3355,43 @@ fn intWrap(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue {
3315 },3355 },
3316 128 => return operand,3356 128 => return operand,
3317 else => {3357 else => {
3318 const bits = mem.alignForward(u16, ty.bits, 64);3358 const bits = cg.intBackingBits(ty.bits);
3319 if (ty.bits == bits) return operand;3359 if (ty.bits == bits) return operand;
33203360
3321 const result = try cg.allocInt(ty);3361 const result = try cg.allocInt(ty);
33223362
3323 const len = bits / 8;3363 const copy_len = (ty.bits / 64) * 8;
3324 try cg.memcpy(result, operand, .{ .imm32 = len - 8 });3364 try cg.memcpy(result, operand, .{ .imm32 = copy_len });
33253365
3326 try cg.emitWValue(result);3366 if (ty.bits % 64 != 0) {
3327 _ = try cg.load(operand, Type.u64, len - 8);3367 const pad = 64 - ty.bits % 64;
3328 if (ty.is_signed) {3368
3329 try cg.addImm64(bits - ty.bits);3369 try cg.emitWValue(result);
3330 try cg.addTag(.i64_shl);3370 _ = try cg.load(operand, Type.u64, copy_len);
3331 try cg.addImm64(bits - ty.bits);3371 if (ty.is_signed) {
3332 try cg.addTag(.i64_shr_s);3372 try cg.addImm64(pad);
3333 } else {3373 try cg.addTag(.i64_shl);
3334 try cg.addImm64(~@as(u64, 0) >> @intCast(bits - ty.bits));3374 try cg.addImm64(pad);
3335 try cg.addTag(.i64_and);3375 try cg.addTag(.i64_shr_s);
3376 } else {
3377 try cg.addImm64(~@as(u64, 0) >> @intCast(pad));
3378 try cg.addTag(.i64_and);
3379 }
3380 try cg.store(.stack, .stack, Type.u64, result.offset() + copy_len);
3381 }
3382
3383 const full_len = @divExact(bits, 8);
3384 if (copy_len + 16 == full_len) { // last limb needs sign extended
3385 try cg.emitWValue(result);
3386 if (ty.is_signed) {
3387 _ = try cg.load(result, Type.u64, copy_len);
3388 try cg.addImm64(63);
3389 try cg.addTag(.i64_shr_s);
3390 } else {
3391 try cg.addImm64(0);
3392 }
3393 try cg.store(.stack, .stack, Type.u64, result.offset() + copy_len + 8);
3336 }3394 }
3337 try cg.store(.stack, .stack, Type.u64, result.offset() + len - 8);
33383395
3339 return result;3396 return result;
3340 },3397 },
...@@ -3354,8 +3411,8 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {...@@ -3354,8 +3411,8 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {
3354 } else {3411 } else {
3355 return .{ .imm64 = ~@as(u64, 0) >> @intCast(64 - int_ty.bits) };3412 return .{ .imm64 = ~@as(u64, 0) >> @intCast(64 - int_ty.bits) };
3356 }3413 }
3357 } else {3414 } else if (int_ty.bits <= 128) {
3358 const result = try cg.allocStack(Type.u128);3415 const result = try cg.allocInt(int_ty);
3359 try cg.store(result, .{ .imm64 = ~@as(u64, 0) }, Type.u64, 0);3416 try cg.store(result, .{ .imm64 = ~@as(u64, 0) }, Type.u64, 0);
33603417
3361 if (int_ty.is_signed) {3418 if (int_ty.is_signed) {
...@@ -3363,6 +3420,24 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {...@@ -3363,6 +3420,24 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {
3363 } else {3420 } else {
3364 try cg.store(result, .{ .imm64 = ~@as(u64, 0) >> @intCast(128 - int_ty.bits) }, Type.u64, 8);3421 try cg.store(result, .{ .imm64 = ~@as(u64, 0) >> @intCast(128 - int_ty.bits) }, Type.u64, 8);
3365 }3422 }
3423 return result;
3424 } else {
3425 const result = try cg.allocInt(int_ty);
3426 const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8);
3427 const normal_len = (int_ty.bits / 64) * 8;
3428
3429 try cg.memset(Type.u8, result, .{ .imm32 = normal_len }, .{ .imm32 = 0xFF });
3430
3431 if (int_ty.is_signed) {
3432 try cg.store(result, .{ .imm64 = (~@as(u64, 0) >> @intCast((normal_len + 8) * 8 - int_ty.bits)) >> 1 }, Type.u64, normal_len);
3433 } else {
3434 try cg.store(result, .{ .imm64 = ~@as(u64, 0) >> @intCast((normal_len + 8) * 8 - int_ty.bits) }, Type.u64, normal_len);
3435 }
3436
3437 if (normal_len + 16 == full_len) {
3438 try cg.store(result, .{ .imm64 = 0 }, Type.u64, full_len - 8);
3439 }
3440
3366 return result;3441 return result;
3367 }3442 }
3368}3443}
...@@ -3375,10 +3450,23 @@ fn intMinValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {...@@ -3375,10 +3450,23 @@ fn intMinValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {
3375 return .{ .imm32 = ~@as(u32, 0) << @intCast(int_ty.bits - 1) };3450 return .{ .imm32 = ~@as(u32, 0) << @intCast(int_ty.bits - 1) };
3376 } else if (int_ty.bits <= 64) {3451 } else if (int_ty.bits <= 64) {
3377 return .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - 1) };3452 return .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - 1) };
3378 } else {3453 } else if (int_ty.bits <= 128) {
3379 const result = try cg.allocStack(Type.u128);3454 const result = try cg.allocInt(int_ty);
3380 try cg.store(result, .{ .imm64 = 0 }, Type.u64, 0);3455 try cg.store(result, .{ .imm64 = 0 }, Type.u64, 0);
3381 try cg.store(result, .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - 65) }, Type.u64, 8);3456 try cg.store(result, .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - 65) }, Type.u64, 8);
3457 return result;
3458 } else {
3459 const result = try cg.allocInt(int_ty);
3460 const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8);
3461 const normal_len = (int_ty.bits / 64) * 8;
3462
3463 try cg.memset(Type.u8, result, .{ .imm32 = normal_len }, .{ .imm32 = 0 });
3464 try cg.store(result, .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - normal_len * 8 - 1) }, Type.u64, normal_len);
3465
3466 if (normal_len + 16 == full_len) {
3467 try cg.store(result, .{ .imm64 = ~@as(u64, 0) }, Type.u64, full_len - 8);
3468 }
3469
3382 return result;3470 return result;
3383 }3471 }
3384}3472}
...@@ -3572,12 +3660,17 @@ fn intZeroValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {...@@ -3572,12 +3660,17 @@ fn intZeroValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue {
3572 1...32 => return .{ .imm32 = 0 },3660 1...32 => return .{ .imm32 = 0 },
3573 33...64 => return .{ .imm64 = 0 },3661 33...64 => return .{ .imm64 = 0 },
3574 65...128 => {3662 65...128 => {
3575 const result = try cg.allocStack(Type.u128);3663 const result = try cg.allocInt(int_ty);
3576 try cg.store(result, .{ .imm64 = 0 }, Type.u64, 0);3664 try cg.store(result, .{ .imm64 = 0 }, Type.u64, 0);
3577 try cg.store(result, .{ .imm64 = 0 }, Type.u64, 8);3665 try cg.store(result, .{ .imm64 = 0 }, Type.u64, 8);
3578 return result;3666 return result;
3579 },3667 },
3580 else => return cg.fail("TODO: Implement intZeroValue for integer bitsize: {d}", .{int_ty.bits}),3668 else => {
3669 const result = try cg.allocInt(int_ty);
3670 const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8);
3671 try cg.memset(Type.u8, result, .{ .imm32 = full_len }, .{ .imm32 = 0 });
3672 return result;
3673 },
3581 }3674 }
3582}3675}
35833676
...@@ -3757,17 +3850,8 @@ fn intShlOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerErro...@@ -3757,17 +3850,8 @@ fn intShlOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerErro
3757}3850}
37583851
3759fn intCast(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) InnerError!WValue {3852fn intCast(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) InnerError!WValue {
3760 const src_bits: u16 = switch (src_ty.bits) {3853 const src_bits: u16 = cg.intBackingBits(src_ty.bits);
3761 0 => unreachable,3854 const dest_bits: u16 = cg.intBackingBits(dest_ty.bits);
3762 1...32 => 32,
3763 else => mem.alignForward(u16, src_ty.bits, 64),
3764 };
3765
3766 const dest_bits: u16 = switch (dest_ty.bits) {
3767 0 => unreachable,
3768 1...32 => 32,
3769 else => mem.alignForward(u16, dest_ty.bits, 64),
3770 };
37713855
3772 if (src_bits == dest_bits) {3856 if (src_bits == dest_bits) {
3773 return operand;3857 return operand;
...@@ -3859,11 +3943,7 @@ fn intCast(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) Inn...@@ -3859,11 +3943,7 @@ fn intCast(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) Inn
3859fn intTrunc(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) InnerError!WValue {3943fn intTrunc(cg: *CodeGen, dest_ty: IntType, src_ty: IntType, operand: WValue) InnerError!WValue {
3860 var result = try cg.intCast(dest_ty, src_ty, operand);3944 var result = try cg.intCast(dest_ty, src_ty, operand);
38613945
3862 const dest_wasm_bits: u16 = switch (dest_ty.bits) {3946 const dest_wasm_bits = cg.intBackingBits(dest_ty.bits);
3863 0 => unreachable,
3864 1...32 => 32,
3865 else => mem.alignForward(u16, dest_ty.bits, 64),
3866 };
38673947
3868 if (dest_wasm_bits != dest_ty.bits) {3948 if (dest_wasm_bits != dest_ty.bits) {
3869 result = try cg.intWrap(dest_ty, result);3949 result = try cg.intWrap(dest_ty, result);
src/codegen/wasm/Mir.zig+4
...@@ -825,6 +825,7 @@ pub const Intrinsic = enum(u32) {...@@ -825,6 +825,7 @@ pub const Intrinsic = enum(u32) {
825 __ceilx,825 __ceilx,
826 __cosh,826 __cosh,
827 __cosx,827 __cosx,
828 __divei4,
828 __divhf3,829 __divhf3,
829 __divtf3,830 __divtf3,
830 __divti3,831 __divti3,
...@@ -950,6 +951,7 @@ pub const Intrinsic = enum(u32) {...@@ -950,6 +951,7 @@ pub const Intrinsic = enum(u32) {
950 __lshrti3,951 __lshrti3,
951 __lttf2,952 __lttf2,
952 __ltxf2,953 __ltxf2,
954 __modei4,
953 __modti3,955 __modti3,
954 __mulhf3,956 __mulhf3,
955 __mulodi4,957 __mulodi4,
...@@ -980,7 +982,9 @@ pub const Intrinsic = enum(u32) {...@@ -980,7 +982,9 @@ pub const Intrinsic = enum(u32) {
980 __truncxfdf2,982 __truncxfdf2,
981 __truncxfhf2,983 __truncxfhf2,
982 __truncxfsf2,984 __truncxfsf2,
985 __udivei4,
983 __udivti3,986 __udivti3,
987 __umodei4,
984 __umodti3,988 __umodti3,
985 ceilq,989 ceilq,
986 cos,990 cos,
test/behavior/math.zig+106
...@@ -1736,6 +1736,112 @@ test "@abs > 128 bits" {...@@ -1736,6 +1736,112 @@ test "@abs > 128 bits" {
1736 try testAbs(i200, minInt(i200), 1 << 199);1736 try testAbs(i200, minInt(i200), 1 << 199);
1737}1737}
17381738
1739fn testRem(comptime T: type, numerator: T, denominator: T, expected: T) !void {
1740 try expect(@rem(numerator, denominator) == expected);
1741}
1742
1743test "@rem > 128 bits" {
1744 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1745
1746 try testRem(u140, 0, maxInt(u140), 0);
1747 try testRem(u140, maxInt(u140), maxInt(u140), 0);
1748 try testRem(u140, maxInt(u140), 2, 1);
1749 try testRem(u140, (1 << 139) + 5, 1 << 70, 5);
1750 try testRem(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, 7);
1751 try testRem(u200, 123, 1 << 100, 123);
1752 try testRem(u200, 1 << 120, 1 << 60, 0);
1753 try testRem(u200, maxInt(u200), 1 << 100, (1 << 100) - 1);
1754
1755 try testRem(i140, 0, maxInt(i140), 0);
1756 try testRem(i140, maxInt(i140), maxInt(i140), 0);
1757 try testRem(i140, -((1 << 100) + 1), 1 << 50, -1);
1758 try testRem(i140, (1 << 100) + 1, -(1 << 50), 1);
1759 try testRem(i140, -((1 << 100) + 1), -(1 << 50), -1);
1760 try testRem(i200, minInt(i200), 1, 0);
1761 try testRem(i200, minInt(i200), -2, 0);
1762 try testRem(i200, maxInt(i200), 2, 1);
1763}
1764
1765fn testMod(comptime T: type, numerator: T, denominator: T, expected: T) !void {
1766 try expect(@mod(numerator, denominator) == expected);
1767}
1768
1769test "@mod > 128 bits" {
1770 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1771
1772 try testMod(u140, 0, maxInt(u140), 0);
1773 try testMod(u140, maxInt(u140), maxInt(u140), 0);
1774 try testMod(u140, maxInt(u140), 2, 1);
1775 try testMod(u140, (1 << 139) + 5, 1 << 70, 5);
1776 try testMod(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, 7);
1777 try testMod(u200, 123, 1 << 100, 123);
1778 try testMod(u200, 1 << 120, 1 << 60, 0);
1779 try testMod(u200, maxInt(u200), 1 << 100, (1 << 100) - 1);
1780
1781 try testMod(i140, 0, maxInt(i140), 0);
1782 try testMod(i140, maxInt(i140), maxInt(i140), 0);
1783 try testMod(i140, -((1 << 100) + 1), 1 << 50, (1 << 50) - 1);
1784 try testMod(i140, (1 << 100) + 1, -(1 << 50), -(1 << 50) + 1);
1785 try testMod(i140, -((1 << 100) + 1), -(1 << 50), -1);
1786 try testMod(i200, minInt(i200), 1, 0);
1787 try testMod(i200, minInt(i200), -2, 0);
1788 try testMod(i200, maxInt(i200), 2, 1);
1789}
1790
1791fn testDivFloor(comptime T: type, numerator: T, denominator: T, expected: T) !void {
1792 try expect(@divFloor(numerator, denominator) == expected);
1793}
1794
1795test "@divFloor > 128 bits" {
1796 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1797
1798 try testDivFloor(u140, 0, maxInt(u140), 0);
1799 try testDivFloor(u140, maxInt(u140), maxInt(u140), 1);
1800 try testDivFloor(u140, maxInt(u140), 2, maxInt(u140) >> 1);
1801 try testDivFloor(u140, (1 << 139) + 5, 1 << 70, 1 << 69);
1802 try testDivFloor(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, (1 << 50) + 1);
1803 try testDivFloor(u200, 123, 1 << 100, 0);
1804 try testDivFloor(u200, 1 << 120, 1 << 60, 1 << 60);
1805 try testDivFloor(u200, maxInt(u200), 1 << 100, (1 << 100) - 1);
1806
1807 try testDivFloor(i140, 0, maxInt(i140), 0);
1808 try testDivFloor(i140, maxInt(i140), maxInt(i140), 1);
1809 try testDivFloor(i140, -((1 << 100) + 1), 1 << 50, -(1 << 50) - 1);
1810 try testDivFloor(i140, (1 << 100) + 1, -(1 << 50), -(1 << 50) - 1);
1811 try testDivFloor(i140, -((1 << 100) + 1), -(1 << 50), 1 << 50);
1812 try testDivFloor(i200, -3, 2, -2);
1813 try testDivFloor(i200, minInt(i200), 1, minInt(i200));
1814 try testDivFloor(i200, minInt(i200), -2, 1 << 198);
1815 try testDivFloor(i200, maxInt(i200), 2, (1 << 198) - 1);
1816}
1817
1818fn testDivTrunc(comptime T: type, numerator: T, denominator: T, expected: T) !void {
1819 try expect(@divTrunc(numerator, denominator) == expected);
1820}
1821
1822test "@divTrunc > 128 bits" {
1823 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
1824
1825 try testDivTrunc(u140, 0, maxInt(u140), 0);
1826 try testDivTrunc(u140, maxInt(u140), maxInt(u140), 1);
1827 try testDivTrunc(u140, maxInt(u140), 2, maxInt(u140) >> 1);
1828 try testDivTrunc(u140, (1 << 139) + 5, 1 << 70, 1 << 69);
1829 try testDivTrunc(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, (1 << 50) + 1);
1830 try testDivTrunc(u200, 123, 1 << 100, 0);
1831 try testDivTrunc(u200, 1 << 120, 1 << 60, 1 << 60);
1832 try testDivTrunc(u200, maxInt(u200), 1 << 100, (1 << 100) - 1);
1833
1834 try testDivTrunc(i140, 0, maxInt(i140), 0);
1835 try testDivTrunc(i140, maxInt(i140), maxInt(i140), 1);
1836 try testDivTrunc(i140, -((1 << 100) + 1), 1 << 50, -(1 << 50));
1837 try testDivTrunc(i140, (1 << 100) + 1, -(1 << 50), -(1 << 50));
1838 try testDivTrunc(i140, -((1 << 100) + 1), -(1 << 50), 1 << 50);
1839 try testDivTrunc(i200, -3, 2, -1);
1840 try testDivTrunc(i200, minInt(i200), 1, minInt(i200));
1841 try testDivTrunc(i200, minInt(i200), -2, 1 << 198);
1842 try testDivTrunc(i200, maxInt(i200), 2, (1 << 198) - 1);
1843}
1844
1739test "overflow arithmetic with u0 values" {1845test "overflow arithmetic with u0 values" {
1740 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;1846 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
17411847