authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-03-13 05:38:53+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-02 15:48:43+02:00
log557caecaaa78a1a434b72c38195b40a82fe42c74
tree609494590d67cf13e1e804d6a0e1cc74d8c9f598
parent3e0f55fcc7f90feb441b62cd8e84735646e50176

stage2-wasm: start big int support


8 files changed, 487 insertions(+), 30 deletions(-)

lib/compiler_rt.zig+2
......@@ -279,6 +279,8 @@ comptime {
279279 _ = @import("compiler_rt/divmodei4.zig");
280280 _ = @import("compiler_rt/udivmodei4.zig");
281281
282 _ = @import("compiler_rt/limb64.zig");
283
282284 // extra
283285 _ = @import("compiler_rt/os_version_check.zig");
284286 _ = @import("compiler_rt/emutls.zig");
lib/compiler_rt/limb64.zig created+266
......@@ -0,0 +1,266 @@
1const std = @import("std");
2const testing = std.testing;
3const assert = std.debug.assert;
4const maxInt = std.math.maxInt;
5const minInt = std.math.minInt;
6const divCeil = std.math.divCeil;
7
8const builtin = @import("builtin");
9const compiler_rt = @import("../compiler_rt.zig");
10
11const endian = builtin.cpu.arch.endian();
12
13inline fn limbGet(limbs: []const u64, i: usize) u64 {
14 return switch (endian) {
15 .little => limbs[i],
16 .big => limbs[limbs.len - 1 - i],
17 };
18}
19
20inline fn limbSet(limbs: []u64, i: usize, value: u64) void {
21 switch (endian) {
22 .little => limbs[i] = value,
23 .big => limbs[limbs.len - 1 - i] = value,
24 }
25}
26
27fn limbCount(bits: u16) u16 {
28 return divCeil(u16, bits, 64) catch unreachable;
29}
30
31fn Limbs(T: type) type {
32 const int_info = @typeInfo(T).int;
33 const limb_cnt = comptime limbCount(int_info.bits);
34 return [limb_cnt]u64;
35}
36
37fn asLimbs(v: anytype) Limbs(@TypeOf(v)) {
38 const T = @TypeOf(v);
39 const int_info = @typeInfo(T).int;
40 const limb_cnt = comptime limbCount(int_info.bits);
41 const ET = @Int(int_info.signedness, limb_cnt * 64);
42 return @bitCast(@as(ET, v));
43}
44
45fn limbWrap(limb: u64, is_signed: bool, bits: u16) u64 {
46 assert(bits % 64 != 0);
47 const pad_bits: u6 = @intCast(64 - bits % 64);
48 if (!is_signed) {
49 const s = limb << pad_bits;
50 return s >> pad_bits;
51 } else {
52 const s = @as(i64, @bitCast(limb)) << pad_bits;
53 return @bitCast(s >> pad_bits);
54 }
55}
56
57comptime {
58 @export(&__addo_limb64, .{ .name = "__addo_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility });
59}
60
61fn __addo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
62 const limb_cnt = limbCount(bits);
63 const out = out_ptr[0..limb_cnt];
64 const a = a_ptr[0..limb_cnt];
65 const b = b_ptr[0..limb_cnt];
66
67 var carry: u1 = 0;
68 var i: usize = 0;
69 while (i < limb_cnt - 1) : (i += 1) {
70 const s1 = @addWithOverflow(limbGet(a, i), limbGet(b, i));
71 const s2 = @addWithOverflow(s1[0], carry);
72 carry = s1[1] | s2[1];
73 limbSet(out, i, s2[0]);
74 }
75
76 const limb: u64 = b: {
77 if (!is_signed) {
78 const s1 = @addWithOverflow(limbGet(a, i), limbGet(b, i));
79 const s2 = @addWithOverflow(s1[0], carry);
80 carry = s1[1] | s2[1];
81 break :b s2[0];
82 } else {
83 const as: i64 = @bitCast(limbGet(a, i));
84 const bs: i64 = @bitCast(limbGet(b, i));
85 const s1 = @addWithOverflow(as, bs);
86 const s2 = @addWithOverflow(s1[0], carry);
87 carry = s1[1] | s2[1];
88 break :b @bitCast(s2[0]);
89 }
90 };
91
92 if (bits % 64 == 0) {
93 limbSet(out, i, limb);
94 return carry != 0;
95 } else {
96 assert(carry == 0);
97 const wrapped_limb = limbWrap(limb, is_signed, bits);
98 limbSet(out, i, wrapped_limb);
99 return wrapped_limb != limb;
100 }
101}
102
103fn test__addo_limb64(comptime T: type, a: T, b: T, expected: struct { T, bool }) !void {
104 const int_info = @typeInfo(T).int;
105 const is_signed = int_info.signedness == .signed;
106
107 var a_limbs = asLimbs(a);
108 var b_limbs = asLimbs(b);
109 var out: Limbs(T) = undefined;
110 const overflow = __addo_limb64(&out, &a_limbs, &b_limbs, is_signed, int_info.bits);
111
112 const expected_limbs = asLimbs(expected[0]);
113 try testing.expectEqual(expected_limbs, out);
114 try testing.expectEqual(expected[1], overflow);
115}
116
117test __addo_limb64 {
118 try test__addo_limb64(u64, 1, 2, .{ 3, false });
119 try test__addo_limb64(u64, maxInt(u64), 2, .{ 1, true });
120 try test__addo_limb64(u65, maxInt(u65), 2, .{ 1, true });
121 try test__addo_limb64(u255, 1, 2, .{ 3, false });
122
123 try test__addo_limb64(i64, 1, 2, .{ 3, false });
124 try test__addo_limb64(i64, maxInt(i64), 1, .{ minInt(i64), true });
125 try test__addo_limb64(i65, maxInt(i65), 1, .{ minInt(i65), true });
126 try test__addo_limb64(i255, -3, 2, .{ -1, false });
127}
128
129comptime {
130 @export(&__subo_limb64, .{ .name = "__subo_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility });
131}
132
133fn __subo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool {
134 const limb_cnt = limbCount(bits);
135 const out = out_ptr[0..limb_cnt];
136 const a = a_ptr[0..limb_cnt];
137 const b = b_ptr[0..limb_cnt];
138
139 var borrow: u1 = 0;
140 var i: usize = 0;
141 while (i < limb_cnt - 1) : (i += 1) {
142 const s1 = @subWithOverflow(limbGet(a, i), limbGet(b, i));
143 const s2 = @subWithOverflow(s1[0], borrow);
144 borrow = s1[1] | s2[1];
145 limbSet(out, i, s2[0]);
146 }
147
148 const limb: u64 = b: {
149 if (!is_signed) {
150 const s1 = @subWithOverflow(limbGet(a, i), limbGet(b, i));
151 const s2 = @subWithOverflow(s1[0], borrow);
152 borrow = s1[1] | s2[1];
153 break :b s2[0];
154 } else {
155 const as: i64 = @bitCast(limbGet(a, i));
156 const bs: i64 = @bitCast(limbGet(b, i));
157 const s1 = @subWithOverflow(as, bs);
158 const s2 = @subWithOverflow(s1[0], borrow);
159 borrow = s1[1] | s2[1];
160 break :b @bitCast(s2[0]);
161 }
162 };
163
164 if (bits % 64 == 0) {
165 limbSet(out, i, limb);
166 return borrow != 0;
167 } else {
168 const wrapped_limb = limbWrap(limb, is_signed, bits);
169 limbSet(out, i, wrapped_limb);
170 return borrow != 0 or wrapped_limb != limb;
171 }
172}
173
174fn test__subo_limb64(comptime T: type, a: T, b: T, expected: struct { T, bool }) !void {
175 const int_info = @typeInfo(T).int;
176 const is_signed = int_info.signedness == .signed;
177
178 var a_limbs = asLimbs(a);
179 var b_limbs = asLimbs(b);
180 var out: Limbs(T) = undefined;
181 const overflow = __subo_limb64(&out, &a_limbs, &b_limbs, is_signed, int_info.bits);
182
183 const expected_limbs = asLimbs(expected[0]);
184 try testing.expectEqual(expected_limbs, out);
185 try testing.expectEqual(expected[1], overflow);
186}
187
188test __subo_limb64 {
189 try test__subo_limb64(u64, 3, 2, .{ 1, false });
190 try test__subo_limb64(u64, 0, 1, .{ maxInt(u64), true });
191 try test__subo_limb64(u65, 0, 1, .{ maxInt(u65), true });
192 try test__subo_limb64(u255, 3, 2, .{ 1, false });
193
194 try test__subo_limb64(i64, 1, 2, .{ -1, false });
195 try test__subo_limb64(i64, minInt(i64), 1, .{ maxInt(i64), true });
196 try test__subo_limb64(i65, minInt(i65), 1, .{ maxInt(i65), true });
197 try test__subo_limb64(i255, -1, 2, .{ -3, false });
198}
199
200comptime {
201 @export(&__cmp_limb64, .{ .name = "__cmp_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility });
202}
203
204// a < b -> -1
205// a == b -> 0
206// a > b -> 1
207fn __cmp_limb64(a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) i8 {
208 const limb_cnt = limbCount(bits);
209 const a = a_ptr[0..limb_cnt];
210 const b = b_ptr[0..limb_cnt];
211
212 var i: usize = 0;
213 if (is_signed) {
214 const sa: i64 = @bitCast(limbGet(a, limb_cnt - 1));
215 const sb: i64 = @bitCast(limbGet(b, limb_cnt - 1));
216 if (sa < sb) return -1;
217 if (sa > sb) return 1;
218 i += 1;
219 }
220
221 while (i < limb_cnt) : (i += 1) {
222 const ai = limbGet(a, limb_cnt - 1 - i);
223 const bi = limbGet(b, limb_cnt - 1 - i);
224 if (ai < bi) return -1;
225 if (ai > bi) return 1;
226 }
227
228 return 0;
229}
230
231fn test__cmp_limb64(comptime T: type, a: T, b: T, expected: i8) !void {
232 const int_info = @typeInfo(T).int;
233 const is_signed = int_info.signedness == .signed;
234
235 var a_limbs = asLimbs(a);
236 var b_limbs = asLimbs(b);
237 const actual = __cmp_limb64(&a_limbs, &b_limbs, is_signed, int_info.bits);
238
239 try testing.expectEqual(expected, actual);
240}
241
242test __cmp_limb64 {
243 try test__cmp_limb64(u64, 1, 2, -1);
244 try test__cmp_limb64(u64, 2, 2, 0);
245 try test__cmp_limb64(u64, 3, 2, 1);
246
247 try test__cmp_limb64(u65, 1, 2, -1);
248 try test__cmp_limb64(u65, maxInt(u65), maxInt(u65), 0);
249 try test__cmp_limb64(u65, maxInt(u65), maxInt(u65) - 1, 1);
250
251 try test__cmp_limb64(u255, 1, 2, -1);
252 try test__cmp_limb64(u255, 7, 7, 0);
253 try test__cmp_limb64(u255, maxInt(u255), maxInt(u255) - 1, 1);
254
255 try test__cmp_limb64(i64, -1, 0, -1);
256 try test__cmp_limb64(i64, 0, 0, 0);
257 try test__cmp_limb64(i64, 1, 0, 1);
258
259 try test__cmp_limb64(i65, minInt(i65), maxInt(i65), -1);
260 try test__cmp_limb64(i65, -1, -1, 0);
261 try test__cmp_limb64(i65, maxInt(i65), minInt(i65), 1);
262
263 try test__cmp_limb64(i255, -3, 2, -1);
264 try test__cmp_limb64(i255, -5, -5, 0);
265 try test__cmp_limb64(i255, 2, -3, 1);
266}
src/codegen/wasm/CodeGen.zig+109-27
......@@ -498,6 +498,10 @@ fn addAtomicTag(cg: *CodeGen, tag: std.wasm.AtomicsOpcode) error{OutOfMemory}!vo
498498 try cg.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } });
499499}
500500
501fn addCallIntrinsic(cg: *CodeGen, intrinsic: Mir.Intrinsic) error{OutOfMemory}!void {
502 try cg.addInst(.{ .tag = .call_intrinsic, .data = .{ .intrinsic = intrinsic } });
503}
504
501505/// Appends entries to `mir_extra` based on the type of `extra`.
502506/// Returns the index into `mir_extra`
503507fn addExtra(cg: *CodeGen, extra: anytype) error{OutOfMemory}!u32 {
......@@ -1010,6 +1014,24 @@ fn allocStack(cg: *CodeGen, ty: Type) !WValue {
10101014 return .{ .stack_offset = .{ .value = offset, .references = 1 } };
10111015}
10121016
1017fn allocInt(cg: *CodeGen, int_ty: IntType) !WValue {
1018 if (cg.initial_stack_value == .none) {
1019 try cg.initializeStack();
1020 }
1021
1022 const abi_size = std.math.cast(u32, std.zig.target.intByteSize(cg.target, int_ty.bits)) orelse {
1023 return cg.fail("Integer ABI size exceeds max stack size", .{});
1024 };
1025 const abi_align: Alignment = .fromByteUnits(std.zig.target.intAlignment(cg.target, int_ty.bits));
1026
1027 cg.stack_alignment = cg.stack_alignment.max(abi_align);
1028
1029 const offset: u32 = @intCast(abi_align.forward(cg.stack_size));
1030 defer cg.stack_size = offset + abi_size;
1031
1032 return .{ .stack_offset = .{ .value = offset, .references = 1 } };
1033}
1034
10131035/// From a given AIR instruction generates a pointer to the stack where
10141036/// the value of its type will live.
10151037/// This is different from allocStack where this will use the pointer's alignment
......@@ -2393,7 +2415,18 @@ fn intAdd(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue
23932415 try cg.store(result, tmp_op, Type.u64, 8);
23942416 return result;
23952417 },
2396 else => return cg.fail("TODO: Support intAdd for integer bitsize: {d}", .{ty.bits}),
2418 else => {
2419 const result = try cg.allocInt(ty);
2420
2421 try cg.lowerToStack(result);
2422 try cg.lowerToStack(lhs);
2423 try cg.lowerToStack(rhs);
2424 try cg.addImm32(@intFromBool(ty.is_signed));
2425 try cg.addImm32(ty.bits);
2426 try cg.addCallIntrinsic(.__addo_limb64);
2427 try cg.addTag(.drop);
2428 return result;
2429 },
23972430 }
23982431}
23992432
......@@ -2435,7 +2468,19 @@ fn intSub(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue
24352468 try cg.store(result, tmp_op, Type.u64, 8);
24362469 return result;
24372470 },
2438 else => return cg.fail("TODO: Support intSub for integer bitsize: {d}", .{ty.bits}),
2471 else => {
2472 const result = try cg.allocInt(ty);
2473
2474 try cg.lowerToStack(result);
2475 try cg.lowerToStack(lhs);
2476 try cg.lowerToStack(rhs);
2477 try cg.addImm32(@intFromBool(ty.is_signed));
2478 try cg.addImm32(ty.bits);
2479 try cg.addCallIntrinsic(.__subo_limb64);
2480 try cg.addTag(.drop);
2481
2482 return result;
2483 },
24392484 }
24402485}
24412486
......@@ -3434,45 +3479,66 @@ const OverflowResult = struct {
34343479 ov: WValue,
34353480};
34363481
3437fn intAddOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult {
3438 switch (int_ty.bits) {
3482fn intAddOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult {
3483 switch (ty.bits) {
34393484 0 => unreachable,
34403485 1...128 => {
3441 const raw_result = try cg.intAdd(int_ty, lhs, rhs);
3442 const op_result = try cg.intWrap(int_ty, raw_result);
3443 const op_tmp = try cg.toLocalInt(op_result, int_ty);
3444
3445 const overflow_bit = if (int_ty.is_signed) blk: {
3446 const zero = try cg.intZeroValue(int_ty);
3447 const rhs_is_neg = try cg.intCmp(int_ty, .lt, rhs, zero);
3448 const overflow_cmp = try cg.intCmp(int_ty, .lt, op_tmp, lhs);
3486 const raw_result = try cg.intAdd(ty, lhs, rhs);
3487 const op_result = try cg.intWrap(ty, raw_result);
3488 const op_tmp = try cg.toLocalInt(op_result, ty);
3489
3490 const overflow_bit = if (ty.is_signed) blk: {
3491 const zero = try cg.intZeroValue(ty);
3492 const rhs_is_neg = try cg.intCmp(ty, .lt, rhs, zero);
3493 const overflow_cmp = try cg.intCmp(ty, .lt, op_tmp, lhs);
34493494 break :blk try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp);
3450 } else try cg.intCmp(int_ty, .lt, op_tmp, lhs);
3495 } else try cg.intCmp(ty, .lt, op_tmp, lhs);
34513496
34523497 return .{ .result = op_tmp, .ov = overflow_bit };
34533498 },
3454 else => return cg.fail("TODO: Support intAddOverflow for integer bitsize: {d}", .{int_ty.bits}),
3499 else => {
3500 const result = try cg.allocInt(ty);
3501
3502 try cg.lowerToStack(result);
3503 try cg.lowerToStack(lhs);
3504 try cg.lowerToStack(rhs);
3505 try cg.addImm32(@intFromBool(ty.is_signed));
3506 try cg.addImm32(ty.bits);
3507 try cg.addCallIntrinsic(.__addo_limb64);
3508
3509 return .{ .result = result, .ov = .stack };
3510 },
34553511 }
34563512}
34573513
3458fn intSubOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult {
3459 switch (int_ty.bits) {
3514fn intSubOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult {
3515 switch (ty.bits) {
34603516 0 => unreachable,
34613517 1...128 => {
3462 const raw_result = try cg.intSub(int_ty, lhs, rhs);
3463 const op_result = try cg.intWrap(int_ty, raw_result);
3464 const op_tmp = try cg.toLocalInt(op_result, int_ty);
3465
3466 const overflow_bit = if (int_ty.is_signed) blk: {
3467 const zero = try cg.intZeroValue(int_ty);
3468 const rhs_is_neg = try cg.intCmp(int_ty, .lt, rhs, zero);
3469 const overflow_cmp = try cg.intCmp(int_ty, .gt, op_tmp, lhs);
3518 const raw_result = try cg.intSub(ty, lhs, rhs);
3519 const op_result = try cg.intWrap(ty, raw_result);
3520 const op_tmp = try cg.toLocalInt(op_result, ty);
3521
3522 const overflow_bit = if (ty.is_signed) blk: {
3523 const zero = try cg.intZeroValue(ty);
3524 const rhs_is_neg = try cg.intCmp(ty, .lt, rhs, zero);
3525 const overflow_cmp = try cg.intCmp(ty, .gt, op_tmp, lhs);
34703526 break :blk try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp);
3471 } else try cg.intCmp(int_ty, .gt, op_tmp, lhs);
3527 } else try cg.intCmp(ty, .gt, op_tmp, lhs);
34723528
34733529 return .{ .result = op_tmp, .ov = overflow_bit };
34743530 },
3475 else => return cg.fail("TODO: Support intSubOverflow for integer bitsize: {d}", .{int_ty.bits}),
3531 else => {
3532 const result = try cg.allocInt(ty);
3533
3534 try cg.lowerToStack(result);
3535 try cg.lowerToStack(lhs);
3536 try cg.lowerToStack(rhs);
3537 try cg.addImm32(@intFromBool(ty.is_signed));
3538 try cg.addImm32(ty.bits);
3539 try cg.addCallIntrinsic(.__subo_limb64);
3540 return .{ .result = result, .ov = .stack };
3541 },
34763542 }
34773543}
34783544
......@@ -4764,7 +4830,23 @@ fn intCmp(cg: *CodeGen, ty: IntType, op: std.math.CompareOperator, lhs: WValue,
47644830
47654831 return .stack;
47664832 },
4767 else => return cg.fail("TODO: Support intCmp for integer bitsize: {d}", .{ty.bits}),
4833 else => {
4834 try cg.lowerToStack(lhs);
4835 try cg.lowerToStack(rhs);
4836 try cg.addImm32(@intFromBool(ty.is_signed));
4837 try cg.addImm32(ty.bits);
4838 try cg.addCallIntrinsic(.__cmp_limb64);
4839 try cg.addImm32(0);
4840 try cg.addTag(switch (op) {
4841 .eq => .i32_eq,
4842 .neq => .i32_ne,
4843 .lt => .i32_lt_s,
4844 .lte => .i32_le_s,
4845 .gte => .i32_ge_s,
4846 .gt => .i32_gt_s,
4847 });
4848 return .stack;
4849 },
47684850 }
47694851}
47704852
src/codegen/wasm/Emit.zig+1
......@@ -352,6 +352,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
352352 .end,
353353 .@"return",
354354 .@"unreachable",
355 .drop,
355356 .select,
356357 .i32_eqz,
357358 .i32_eq,
src/codegen/wasm/Mir.zig+7
......@@ -169,6 +169,10 @@ pub const Inst = struct {
169169 call_tag_name,
170170 /// Lowers to a `call` instruction, using `intrinsic`.
171171 call_intrinsic,
172 /// Pops a value from the stack, and discards it.
173 ///
174 /// Uses `tag` (no additional data).
175 drop = 0x1A,
172176 /// Pops three values from the stack and pushes
173177 /// the first or second value dependent on the third value.
174178 /// Uses `tag`
......@@ -1000,4 +1004,7 @@ pub const Intrinsic = enum(u32) {
10001004 tanf,
10011005 tanq,
10021006 truncq,
1007 __addo_limb64,
1008 __subo_limb64,
1009 __cmp_limb64,
10031010};
test/behavior/basic.zig+14
......@@ -1248,6 +1248,20 @@ test "integer compare <= 128 bits" {
12481248 }
12491249}
12501250
1251test "integer compare > 128 bits" {
1252 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1253 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
1254
1255 inline for (.{ u129, u255, u512, u800 }) |T| {
1256 try testUnsignedCmp(T);
1257 try comptime testUnsignedCmp(T);
1258 }
1259 inline for (.{ i129, i255, i512, i800 }) |T| {
1260 try testSignedCmp(T);
1261 try comptime testSignedCmp(T);
1262 }
1263}
1264
12511265test "reference to inferred local variable works as expected" {
12521266 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
12531267
test/behavior/int_comparison_elision.zig-1
......@@ -14,7 +14,6 @@ test "int comparison elision" {
1414 testIntEdges(i4);
1515
1616 // TODO: support int types > 128 bits wide in other backends
17 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
1817 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1918 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
2019
test/behavior/math.zig+88-2
......@@ -877,7 +877,7 @@ test "@addWithOverflow" {
877877 try testAddWithOverflow(isize, minInt(isize), -6, maxInt(isize) - 5, 1);
878878}
879879
880test "@addWithOverflow > 64 bits" {
880test "@addWithOverflow <= 128 bits" {
881881 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
882882 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
883883 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
......@@ -920,6 +920,50 @@ test "@addWithOverflow > 64 bits" {
920920 try testAddWithOverflow(i128, maxInt(i128), maxInt(i128) - 1, -3, 1);
921921}
922922
923test "@addWithOverflow > 128 bits" {
924 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
925 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
926 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
927 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
928 if (builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) return error.SkipZigTest;
929
930 try testAddWithOverflow(u129, 4, 105, 109, 0);
931 try testAddWithOverflow(u129, 1000, 100, 1100, 0);
932 try testAddWithOverflow(u129, 100, maxInt(u129) - 99, 0, 1);
933 try testAddWithOverflow(u129, maxInt(u129), maxInt(u129), maxInt(u129) - 1, 1);
934 try testAddWithOverflow(u129, maxInt(u129) - 1, maxInt(u129), maxInt(u129) - 2, 1);
935 try testAddWithOverflow(u129, maxInt(u129), maxInt(u129) - 1, maxInt(u129) - 2, 1);
936
937 try testAddWithOverflow(u400, 4, 105, 109, 0);
938 try testAddWithOverflow(u400, 1000, 100, 1100, 0);
939 try testAddWithOverflow(u400, 100, maxInt(u400) - 99, 0, 1);
940 try testAddWithOverflow(u400, maxInt(u400), maxInt(u400), maxInt(u400) - 1, 1);
941 try testAddWithOverflow(u400, maxInt(u400) - 1, maxInt(u400), maxInt(u400) - 2, 1);
942 try testAddWithOverflow(u400, maxInt(u400), maxInt(u400) - 1, maxInt(u400) - 2, 1);
943
944 try testAddWithOverflow(i129, 4, -105, -101, 0);
945 try testAddWithOverflow(i129, 1000, 100, 1100, 0);
946 try testAddWithOverflow(i129, minInt(i129), 1, minInt(i129) + 1, 0);
947 try testAddWithOverflow(i129, maxInt(i129), minInt(i129), -1, 0);
948 try testAddWithOverflow(i129, minInt(i129), maxInt(i129), -1, 0);
949 try testAddWithOverflow(i129, maxInt(i129), -2, maxInt(i129) - 2, 0);
950 try testAddWithOverflow(i129, maxInt(i129), maxInt(i129), -2, 1);
951 try testAddWithOverflow(i129, minInt(i129), minInt(i129), 0, 1);
952 try testAddWithOverflow(i129, maxInt(i129) - 1, maxInt(i129), -3, 1);
953 try testAddWithOverflow(i129, maxInt(i129), maxInt(i129) - 1, -3, 1);
954
955 try testAddWithOverflow(i400, 4, -105, -101, 0);
956 try testAddWithOverflow(i400, 1000, 100, 1100, 0);
957 try testAddWithOverflow(i400, minInt(i400), 1, minInt(i400) + 1, 0);
958 try testAddWithOverflow(i400, maxInt(i400), minInt(i400), -1, 0);
959 try testAddWithOverflow(i400, minInt(i400), maxInt(i400), -1, 0);
960 try testAddWithOverflow(i400, maxInt(i400), -2, maxInt(i400) - 2, 0);
961 try testAddWithOverflow(i400, maxInt(i400), maxInt(i400), -2, 1);
962 try testAddWithOverflow(i400, minInt(i400), minInt(i400), 0, 1);
963 try testAddWithOverflow(i400, maxInt(i400) - 1, maxInt(i400), -3, 1);
964 try testAddWithOverflow(i400, maxInt(i400), maxInt(i400) - 1, -3, 1);
965}
966
923967test "small int addition" {
924968 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
925969 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
......@@ -1119,7 +1163,7 @@ test "@subWithOverflow" {
11191163 try testSubWithOverflow(isize, minInt(isize), 6, maxInt(isize) - 5, 1);
11201164}
11211165
1122test "@subWithOverflow > 64 bits" {
1166test "@subWithOverflow <= 128 bits" {
11231167 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
11241168 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
11251169 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
......@@ -1160,6 +1204,48 @@ test "@subWithOverflow > 64 bits" {
11601204 try testSubWithOverflow(i128, maxInt(i128), -2, minInt(i128) + 1, 1);
11611205}
11621206
1207test "@subWithOverflow > 128 bits" {
1208 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1209 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1210 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1211 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
1212 if (builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) return error.SkipZigTest;
1213
1214 try testSubWithOverflow(u129, 4, 105, maxInt(u129) - 100, 1);
1215 try testSubWithOverflow(u129, 1000, 100, 900, 0);
1216 try testSubWithOverflow(u129, maxInt(u129), maxInt(u129), 0, 0);
1217 try testSubWithOverflow(u129, maxInt(u129) - 1, maxInt(u129), maxInt(u129), 1);
1218 try testSubWithOverflow(u129, maxInt(u129), maxInt(u129) - 1, 1, 0);
1219
1220 try testSubWithOverflow(u400, 4, 105, maxInt(u400) - 100, 1);
1221 try testSubWithOverflow(u400, 1000, 100, 900, 0);
1222 try testSubWithOverflow(u400, maxInt(u400), maxInt(u400), 0, 0);
1223 try testSubWithOverflow(u400, maxInt(u400) - 1, maxInt(u400), maxInt(u400), 1);
1224 try testSubWithOverflow(u400, maxInt(u400), maxInt(u400) - 1, 1, 0);
1225
1226 try testSubWithOverflow(i129, 4, 105, -101, 0);
1227 try testSubWithOverflow(i129, 1000, 100, 900, 0);
1228 try testSubWithOverflow(i129, maxInt(i129), maxInt(i129), 0, 0);
1229 try testSubWithOverflow(i129, minInt(i129), minInt(i129), 0, 0);
1230 try testSubWithOverflow(i129, maxInt(i129) - 1, maxInt(i129), -1, 0);
1231 try testSubWithOverflow(i129, maxInt(i129), maxInt(i129) - 1, 1, 0);
1232 try testSubWithOverflow(i129, minInt(i129), 1, maxInt(i129), 1);
1233 try testSubWithOverflow(i129, maxInt(i129), minInt(i129), -1, 1);
1234 try testSubWithOverflow(i129, minInt(i129), maxInt(i129), 1, 1);
1235 try testSubWithOverflow(i129, maxInt(i129), -2, minInt(i129) + 1, 1);
1236
1237 try testSubWithOverflow(i400, 4, 105, -101, 0);
1238 try testSubWithOverflow(i400, 1000, 100, 900, 0);
1239 try testSubWithOverflow(i400, maxInt(i400), maxInt(i400), 0, 0);
1240 try testSubWithOverflow(i400, minInt(i400), minInt(i400), 0, 0);
1241 try testSubWithOverflow(i400, maxInt(i400) - 1, maxInt(i400), -1, 0);
1242 try testSubWithOverflow(i400, maxInt(i400), maxInt(i400) - 1, 1, 0);
1243 try testSubWithOverflow(i400, minInt(i400), 1, maxInt(i400), 1);
1244 try testSubWithOverflow(i400, maxInt(i400), minInt(i400), -1, 1);
1245 try testSubWithOverflow(i400, minInt(i400), maxInt(i400), 1, 1);
1246 try testSubWithOverflow(i400, maxInt(i400), -2, minInt(i400) + 1, 1);
1247}
1248
11631249fn testShlWithOverflow(comptime T: type, a: T, b: math.Log2Int(T), shl: T, bit: u1) !void {
11641250 const ov = @shlWithOverflow(a, b);
11651251 try expect(ov[0] == shl);