authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-04-08 03:47:24+02:00
committergravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-04-09 00:14:09+02:00
logf2a842db5caa7a91faade2636106327cc6707ad3
tree239acaad481e37a6c630006b4e4c7c99055b2ea6
parentaa7874657b3439134eb4cd8b65271fb9cc38fdad

stage2-wasm: sat ops


2 files changed, 121 insertions(+), 121 deletions(-)

src/codegen/wasm/CodeGen.zig+21-58
......@@ -3484,20 +3484,20 @@ fn intAddSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError
34843484 defer rhs_is_neg.free(cg);
34853485 const min_val = try cg.intMinValue(int_ty);
34863486
3487 try cg.emitWValue(min_val);
3488 try cg.emitWValue(max_val);
3487 try cg.lowerToStack(min_val);
3488 try cg.lowerToStack(max_val);
34893489 try cg.emitWValue(rhs_is_neg);
34903490 try cg.addTag(.select);
34913491
3492 try cg.emitWValue(op_val);
3492 try cg.lowerToStack(op_val);
34933493 const overflow_cmp = try cg.intCmp(int_ty, .lt, op_val, lhs);
34943494 const is_overflow = try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp);
34953495 try cg.emitWValue(is_overflow);
34963496 try cg.addTag(.select);
34973497 return .stack;
34983498 } else {
3499 try cg.emitWValue(max_val);
3500 try cg.emitWValue(op_val);
3499 try cg.lowerToStack(max_val);
3500 try cg.lowerToStack(op_val);
35013501
35023502 const is_overflow = try cg.intCmp(int_ty, .lt, op_val, lhs);
35033503 try cg.emitWValue(is_overflow);
......@@ -3518,12 +3518,12 @@ fn intSubSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError
35183518 const max_val = try cg.intMaxValue(int_ty);
35193519 const min_val = try cg.intMinValue(int_ty);
35203520
3521 try cg.emitWValue(max_val);
3522 try cg.emitWValue(min_val);
3521 try cg.lowerToStack(max_val);
3522 try cg.lowerToStack(min_val);
35233523 try cg.emitWValue(rhs_is_neg);
35243524 try cg.addTag(.select);
35253525
3526 try cg.emitWValue(op_val);
3526 try cg.lowerToStack(op_val);
35273527 const overflow_cmp = try cg.intCmp(int_ty, .gt, op_val, lhs);
35283528 const is_overflow = try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp);
35293529 try cg.emitWValue(is_overflow);
......@@ -3532,8 +3532,8 @@ fn intSubSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError
35323532 } else {
35333533 const zero = try cg.intZeroValue(int_ty);
35343534
3535 try cg.emitWValue(zero);
3536 try cg.emitWValue(op_val);
3535 try cg.lowerToStack(zero);
3536 try cg.lowerToStack(op_val);
35373537 const is_overflow = try cg.intCmp(int_ty, .lt, lhs, rhs);
35383538 try cg.emitWValue(is_overflow);
35393539 try cg.addTag(.select);
......@@ -3542,43 +3542,6 @@ fn intSubSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError
35423542}
35433543
35443544fn intMulSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue {
3545 // Remove when > 128 int ops will be implemented in backend
3546 if (int_ty.bits == 128) {
3547 if (!int_ty.is_signed) {
3548 return cg.fail("TODO: mul_sat for unsigned 128-bit integers", .{});
3549 }
3550
3551 const overflow_ret = try cg.allocStack(Type.i32);
3552 const ret = try cg.callIntrinsic(
3553 .__muloti4,
3554 &[_]InternPool.Index{ .i128_type, .i128_type, .usize_type },
3555 Type.i128,
3556 &.{ lhs, rhs, overflow_ret },
3557 );
3558 try cg.lowerToStack(ret);
3559
3560 const xor = try cg.intXor(int_ty, lhs, rhs);
3561 const sign_v = try cg.intShr(int_ty, xor, .{ .imm32 = 127 });
3562
3563 // xor ~@as(u127, 0)
3564 try cg.emitWValue(sign_v);
3565 const lsb = try cg.load(sign_v, Type.u64, 0);
3566 _ = try cg.intXor(.u64, lsb, .{ .imm64 = ~@as(u64, 0) });
3567 try cg.store(.stack, .stack, Type.u64, sign_v.offset());
3568
3569 try cg.emitWValue(sign_v);
3570 const msb = try cg.load(sign_v, Type.u64, 8);
3571 _ = try cg.intXor(.u64, msb, .{ .imm64 = ~@as(u64, 0) >> 1 });
3572 try cg.store(.stack, .stack, Type.u64, sign_v.offset() + 8);
3573
3574 try cg.lowerToStack(sign_v);
3575 _ = try cg.load(overflow_ret, Type.i32, 0);
3576 try cg.addTag(.i32_eqz);
3577 try cg.addTag(.select);
3578
3579 return .stack;
3580 }
3581
35823545 const ext_ty: IntType = .{ .is_signed = int_ty.is_signed, .bits = int_ty.bits * 2 };
35833546
35843547 const lhs_ext = try cg.intCast(ext_ty, int_ty, lhs);
......@@ -3594,10 +3557,10 @@ fn intMulSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError
35943557 if (int_ty.is_signed) {
35953558 const min_val = try cg.intMinValue(int_ty);
35963559
3597 try cg.emitWValue(min_val);
3560 try cg.lowerToStack(min_val);
35983561
3599 try cg.emitWValue(max_val);
3600 try cg.emitWValue(op_val);
3562 try cg.lowerToStack(max_val);
3563 try cg.lowerToStack(op_val);
36013564 const max_ext = try cg.intCast(ext_ty, int_ty, max_val);
36023565 const ov_pos = try cg.intCmp(ext_ty, .lt, max_ext, mul_ext);
36033566 try cg.emitWValue(ov_pos);
......@@ -3605,12 +3568,12 @@ fn intMulSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError
36053568
36063569 const min_ext = try cg.intCast(ext_ty, int_ty, min_val);
36073570 const ov_neg = try cg.intCmp(ext_ty, .gt, min_ext, mul_ext);
3608 try cg.emitWValue(ov_neg);
3571 try cg.lowerToStack(ov_neg);
36093572 try cg.addTag(.select);
36103573 return .stack;
36113574 } else {
3612 try cg.emitWValue(max_val);
3613 try cg.emitWValue(op_val);
3575 try cg.lowerToStack(max_val);
3576 try cg.lowerToStack(op_val);
36143577 const max_ext = try cg.intCast(ext_ty, int_ty, max_val);
36153578 const is_overflow = try cg.intCmp(ext_ty, .lt, max_ext, mul_ext);
36163579 try cg.emitWValue(is_overflow);
......@@ -3633,20 +3596,20 @@ fn intShlSat(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError
36333596 const zero = try cg.intZeroValue(int_ty);
36343597 const min_val = try cg.intMinValue(int_ty);
36353598
3636 try cg.emitWValue(min_val);
3637 try cg.emitWValue(max_val);
3599 try cg.lowerToStack(min_val);
3600 try cg.lowerToStack(max_val);
36383601 const lhs_is_neg = try cg.intCmp(int_ty, .lt, lhs, zero);
36393602 try cg.emitWValue(lhs_is_neg);
36403603 try cg.addTag(.select);
36413604
3642 try cg.emitWValue(op_val);
3605 try cg.lowerToStack(op_val);
36433606 const is_overflow = try cg.intCmp(int_ty, .neq, check_val, lhs);
36443607 try cg.emitWValue(is_overflow);
36453608 try cg.addTag(.select);
36463609 return .stack;
36473610 } else {
3648 try cg.emitWValue(max_val);
3649 try cg.emitWValue(op_val);
3611 try cg.lowerToStack(max_val);
3612 try cg.lowerToStack(op_val);
36503613 const is_overflow = try cg.intCmp(int_ty, .neq, check_val, lhs);
36513614 try cg.emitWValue(is_overflow);
36523615 try cg.addTag(.select);
test/behavior/saturating_arithmetic.zig+100-63
......@@ -4,6 +4,14 @@ const minInt = std.math.minInt;
44const maxInt = std.math.maxInt;
55const expect = std.testing.expect;
66
7fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {
8 try expect((lhs +| rhs) == expected);
9
10 var x = lhs;
11 x +|= rhs;
12 try expect(x == expected);
13}
14
715test "saturating add" {
816 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
917 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -28,32 +36,23 @@ test "saturating add" {
2836 try testSatAdd(u2, 3, 2, 3);
2937 try testSatAdd(u3, 7, 1, 7);
3038 }
31
32 fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {
33 try expect((lhs +| rhs) == expected);
34
35 var x = lhs;
36 x +|= rhs;
37 try expect(x == expected);
38 }
3939 };
4040
4141 try S.doTheTest();
4242 try comptime S.doTheTest();
4343
44 try comptime S.testSatAdd(comptime_int, 0, 0, 0);
45 try comptime S.testSatAdd(comptime_int, -1, 1, 0);
46 try comptime S.testSatAdd(comptime_int, 3, 2, 5);
47 try comptime S.testSatAdd(comptime_int, -3, -2, -5);
48 try comptime S.testSatAdd(comptime_int, 3, -2, 1);
49 try comptime S.testSatAdd(comptime_int, -3, 2, -1);
50 try comptime S.testSatAdd(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 1119305249183743626545271163355074748512);
51 try comptime S.testSatAdd(comptime_int, 7, -593423721213448152027139550640105366508, -593423721213448152027139550640105366501);
44 try comptime testSatAdd(comptime_int, 0, 0, 0);
45 try comptime testSatAdd(comptime_int, -1, 1, 0);
46 try comptime testSatAdd(comptime_int, 3, 2, 5);
47 try comptime testSatAdd(comptime_int, -3, -2, -5);
48 try comptime testSatAdd(comptime_int, 3, -2, 1);
49 try comptime testSatAdd(comptime_int, -3, 2, -1);
50 try comptime testSatAdd(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 1119305249183743626545271163355074748512);
51 try comptime testSatAdd(comptime_int, 7, -593423721213448152027139550640105366508, -593423721213448152027139550640105366501);
5252}
5353
5454test "saturating add 128bit" {
5555 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
56 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
5756 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
5857 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
5958 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
......@@ -65,19 +64,20 @@ test "saturating add 128bit" {
6564 try testSatAdd(i128, minInt(i128), maxInt(i128), -1);
6665 try testSatAdd(u128, maxInt(u128), 1, maxInt(u128));
6766 }
68 fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {
69 try expect((lhs +| rhs) == expected);
70
71 var x = lhs;
72 x +|= rhs;
73 try expect(x == expected);
74 }
7567 };
7668
7769 try S.doTheTest();
7870 try comptime S.doTheTest();
7971}
8072
73fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {
74 try expect((lhs -| rhs) == expected);
75
76 var x = lhs;
77 x -|= rhs;
78 try expect(x == expected);
79}
80
8181test "saturating subtraction" {
8282 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
8383 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -101,32 +101,23 @@ test "saturating subtraction" {
101101 try testSatSub(u8, 10, 3, 7);
102102 try testSatSub(u8, 0, 255, 0);
103103 }
104
105 fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {
106 try expect((lhs -| rhs) == expected);
107
108 var x = lhs;
109 x -|= rhs;
110 try expect(x == expected);
111 }
112104 };
113105
114106 try S.doTheTest();
115107 try comptime S.doTheTest();
116108
117 try comptime S.testSatSub(comptime_int, 0, 0, 0);
118 try comptime S.testSatSub(comptime_int, 1, 1, 0);
119 try comptime S.testSatSub(comptime_int, 3, 2, 1);
120 try comptime S.testSatSub(comptime_int, -3, -2, -1);
121 try comptime S.testSatSub(comptime_int, 3, -2, 5);
122 try comptime S.testSatSub(comptime_int, -3, 2, -5);
123 try comptime S.testSatSub(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 182846383813587550256162760261375991602);
124 try comptime S.testSatSub(comptime_int, 7, -593423721213448152027139550640105366508, 593423721213448152027139550640105366515);
109 try comptime testSatSub(comptime_int, 0, 0, 0);
110 try comptime testSatSub(comptime_int, 1, 1, 0);
111 try comptime testSatSub(comptime_int, 3, 2, 1);
112 try comptime testSatSub(comptime_int, -3, -2, -1);
113 try comptime testSatSub(comptime_int, 3, -2, 5);
114 try comptime testSatSub(comptime_int, -3, 2, -5);
115 try comptime testSatSub(comptime_int, 651075816498665588400716961808225370057, 468229432685078038144554201546849378455, 182846383813587550256162760261375991602);
116 try comptime testSatSub(comptime_int, 7, -593423721213448152027139550640105366508, 593423721213448152027139550640105366515);
125117}
126118
127119test "saturating subtraction 128bit" {
128120 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
129 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
130121 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
131122 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
132123 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
......@@ -138,14 +129,6 @@ test "saturating subtraction 128bit" {
138129 try testSatSub(i128, minInt(i128), -maxInt(i128), -1);
139130 try testSatSub(u128, 0, maxInt(u128), 0);
140131 }
141
142 fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {
143 try expect((lhs -| rhs) == expected);
144
145 var x = lhs;
146 x -|= rhs;
147 try expect(x == expected);
148 }
149132 };
150133
151134 try S.doTheTest();
......@@ -257,7 +240,6 @@ test "saturating mul i64, i128" {
257240
258241test "saturating multiplication" {
259242 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
260 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
261243 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
262244 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
263245 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
......@@ -294,6 +276,14 @@ test "saturating multiplication" {
294276 try comptime testSatMul(comptime_int, 7, -593423721213448152027139550640105366508, -4153966048494137064189976854480737565556);
295277}
296278
279fn testSatShl(comptime Lhs: type, lhs: Lhs, comptime Rhs: type, rhs: Rhs, expected: Lhs) !void {
280 try expect((lhs <<| rhs) == expected);
281
282 var x = lhs;
283 x <<|= rhs;
284 try expect(x == expected);
285}
286
297287test "saturating shift-left" {
298288 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
299289 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -320,23 +310,15 @@ test "saturating shift-left" {
320310 try testSatShl(u8, 0, u4, 8, 0);
321311 try testSatShl(u8, 3, u4, 8, maxInt(u8));
322312 }
323
324 fn testSatShl(comptime Lhs: type, lhs: Lhs, comptime Rhs: type, rhs: Rhs, expected: Lhs) !void {
325 try expect((lhs <<| rhs) == expected);
326
327 var x = lhs;
328 x <<|= rhs;
329 try expect(x == expected);
330 }
331313 };
332314
333315 try S.doTheTest();
334316 try comptime S.doTheTest();
335317
336 try comptime S.testSatShl(comptime_int, 0, comptime_int, 0, 0);
337 try comptime S.testSatShl(comptime_int, 1, comptime_int, 2, 4);
338 try comptime S.testSatShl(comptime_int, 13, comptime_int, 150, 18554220005177478453757717602843436772975706112);
339 try comptime S.testSatShl(comptime_int, -582769, comptime_int, 180, -893090893854873184096635538665358532628308979495815656505344);
318 try comptime testSatShl(comptime_int, 0, comptime_int, 0, 0);
319 try comptime testSatShl(comptime_int, 1, comptime_int, 2, 4);
320 try comptime testSatShl(comptime_int, 13, comptime_int, 150, 18554220005177478453757717602843436772975706112);
321 try comptime testSatShl(comptime_int, -582769, comptime_int, 180, -893090893854873184096635538665358532628308979495815656505344);
340322}
341323
342324test "saturating shift-left large rhs" {
......@@ -344,7 +326,6 @@ test "saturating shift-left large rhs" {
344326 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
345327 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
346328 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
347 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
348329
349330 {
350331 var lhs: u8 = undefined;
......@@ -386,3 +367,59 @@ test "saturating shl uses the LHS type" {
386367
387368 try expect((1 <<| @as(u8, 200)) == 1606938044258990275541962092341162602522202993782792835301376);
388369}
370
371test "sat add > 128 bits" {
372 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
373
374 try testSatAdd(u140, 0, 0, 0);
375 try testSatAdd(u140, maxInt(u140), 1, maxInt(u140));
376 try testSatAdd(u200, 1 << 150, 1 << 20, (1 << 150) + (1 << 20));
377 try testSatAdd(u200, maxInt(u200), maxInt(u200), maxInt(u200));
378
379 try testSatAdd(i140, minInt(i140), -1, minInt(i140));
380 try testSatAdd(i140, maxInt(i140), 1, maxInt(i140));
381 try testSatAdd(i200, -1 << 150, 1 << 149, -1 << 149);
382 try testSatAdd(i200, maxInt(i200), maxInt(i200), maxInt(i200));
383}
384
385test "sat sub > 128 bits" {
386 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
387
388 try testSatSub(u140, 0, 1, 0);
389 try testSatSub(u140, maxInt(u140), maxInt(u140), 0);
390 try testSatSub(u200, 1 << 150, 1 << 20, (1 << 150) - (1 << 20));
391 try testSatSub(u200, maxInt(u200), 0, maxInt(u200));
392
393 try testSatSub(i140, minInt(i140), 1, minInt(i140));
394 try testSatSub(i140, maxInt(i140), -1, maxInt(i140));
395 try testSatSub(i200, -1 << 150, 1 << 149, -3 << 149);
396 try testSatSub(i200, 0, minInt(i200), maxInt(i200));
397}
398
399test "sat mul > 128 bits" {
400 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
401
402 try testSatMul(u140, 0, maxInt(u140), 0);
403 try testSatMul(u140, 1 << 70, 1 << 69, 1 << 139);
404 try testSatMul(u200, maxInt(u200), 2, maxInt(u200));
405 try testSatMul(u200, maxInt(u200) - 1, 1, maxInt(u200) - 1);
406
407 try testSatMul(i140, -1, maxInt(i140), -maxInt(i140));
408 try testSatMul(i140, minInt(i140), -1, maxInt(i140));
409 try testSatMul(i200, 1 << 100, 1 << 99, maxInt(i200));
410 try testSatMul(i200, -1 << 150, 1 << 30, -1 << 180);
411}
412
413test "sat shl > 128 bits" {
414 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
415
416 try testSatShl(u140, 0, u8, 17, 0);
417 try testSatShl(u140, 1 << 100, u8, 20, 1 << 120);
418 try testSatShl(u200, maxInt(u200), u8, 1, maxInt(u200));
419 try testSatShl(u200, 1 << 199, u8, 1, maxInt(u200));
420
421 try testSatShl(i140, 0, u8, 17, 0);
422 try testSatShl(i140, 1 << 100, u8, 38, 1 << 138);
423 try testSatShl(i140, 1 << 100, u8, 39, maxInt(i140));
424 try testSatShl(i200, minInt(i200) + 1, u8, 1, minInt(i200));
425}