From 63831d89e21fc65f0483c943747cd6c94b7bf1e9 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Tue, 1 Jul 2025 19:20:43 +0200 Subject: [PATCH 01/10] spirv: saturating arithmetic implementation (only add/sub) --- src/codegen/spirv.zig | 91 ++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 8a782c54aaab..5c5a356ab8b7 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3249,10 +3249,12 @@ const NavGen = struct { .rem, .rem_optimized => try self.airArithOp(inst, .f_rem, .s_rem, .u_mod), .mod, .mod_optimized => try self.airArithOp(inst, .f_mod, .s_mod, .u_mod), - .add_with_overflow => try self.airAddSubOverflow(inst, .i_add, .u_lt, .s_lt), - .sub_with_overflow => try self.airAddSubOverflow(inst, .i_sub, .u_gt, .s_gt), + .add_with_overflow => try self.airAddSubWithOverflow(inst, .i_add), + .sub_with_overflow => try self.airAddSubWithOverflow(inst, .i_sub), .mul_with_overflow => try self.airMulOverflow(inst), .shl_with_overflow => try self.airShlOverflow(inst), + .add_sat => try self.airAddSubSaturating(inst, .i_add), + .sub_sat => try self.airAddSubSaturating(inst, .i_sub), .mul_add => try self.airMulAdd(inst), @@ -3654,28 +3656,14 @@ const NavGen = struct { } } - fn airAddSubOverflow( + fn buildAddSub( self: *NavGen, - inst: Air.Inst.Index, - comptime add: BinaryOp, - comptime ucmp: CmpPredicate, - comptime scmp: CmpPredicate, + lhs: Temporary, + rhs: Temporary, + result_ty: Type, + comptime op: BinaryOp, + comptime mode: enum { WithOverflow, Saturating }, ) !?IdRef { - _ = scmp; - // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers, - // there is in both cases only one extra operation required. For signed operations, - // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually - // normally set a carry bit. So the SPIR-V overflow operations are not particularly - // useful here. - - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; - - const lhs = try self.temporary(extra.lhs); - const rhs = try self.temporary(extra.rhs); - - const result_ty = self.typeOfIndex(inst); - const info = self.arithmeticTypeInfo(lhs.ty); switch (info.class) { .composite_integer => unreachable, // TODO @@ -3683,13 +3671,17 @@ const NavGen = struct { .float, .bool => unreachable, } - const sum = try self.buildBinary(add, lhs, rhs); + const sum = try self.buildBinary(op, lhs, rhs); const result = try self.normalize(sum, info); const overflowed = switch (info.signedness) { // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. // For subtraction the conditions need to be swapped. - .unsigned => try self.buildCmp(ucmp, result, lhs), + .unsigned => switch (op) { + .i_add => try self.buildCmp(.u_lt, result, lhs), + .i_sub => try self.buildCmp(.u_gt, result, lhs), + else => unreachable, + }, // For signed operations, we check the signs of the operands and the result. .signed => blk: { // Signed overflow detection using the sign bits of the operands and the result. @@ -3708,7 +3700,7 @@ const NavGen = struct { const signs_match = try self.buildCmp(.l_eq, lhs_is_neg, rhs_is_neg); const result_sign_differs = try self.buildCmp(.l_ne, lhs_is_neg, result_is_neg); - const overflow_condition = if (add == .i_add) + const overflow_condition = if (op == .i_add) signs_match else // .i_sub try self.buildUnary(.l_not, signs_match); @@ -3717,10 +3709,53 @@ const NavGen = struct { }, }; - const ov = try self.intFromBool(overflowed); + switch (mode) { + .WithOverflow => { + const ov = try self.intFromBool(overflowed); + const struct_ty_id = try self.resolveType(result_ty, .direct); + return try self.constructComposite(struct_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); + }, + .Saturating => { + const sat_val_tmp = blk: { + const scalar_ty = result_ty.scalarType(self.pt.zcu); + if (info.signedness == .signed and op == .i_sub) { + const min_val: i64 = if (info.bits == 0) 0 else -(@as(i64, 1) << @as(u6, @intCast(info.bits - 1))); + const min_id = try self.constInt(scalar_ty, min_val); + break :blk Temporary.init(scalar_ty, min_id); + } else { + const max_val: u64 = if (info.bits == 0) 0 else switch (info.signedness) { + .unsigned => if (info.bits == 64) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1, + .signed => (@as(u64, 1) << @as(u6, @intCast(info.bits - 1))) - 1, + }; + const max_id = try self.constInt(scalar_ty, max_val); + break :blk Temporary.init(scalar_ty, max_id); + } + }; + const final_result = try self.buildSelect(overflowed, sat_val_tmp, result); + return try final_result.materialize(self); + }, + } + } - const result_ty_id = try self.resolveType(result_ty, .direct); - return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); + fn airAddSubWithOverflow(self: *NavGen, inst: Air.Inst.Index, comptime op: BinaryOp) !?IdRef { + const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; + const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; + + const lhs = try self.temporary(extra.lhs); + const rhs = try self.temporary(extra.rhs); + + const result_ty = self.typeOfIndex(inst); + return self.buildAddSub(lhs, rhs, result_ty, op, .WithOverflow); + } + + fn airAddSubSaturating(self: *NavGen, inst: Air.Inst.Index, comptime op: BinaryOp) !?IdRef { + const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; + + const lhs = try self.temporary(bin_op.lhs); + const rhs = try self.temporary(bin_op.rhs); + + const result_ty = self.typeOfIndex(inst); + return self.buildAddSub(lhs, rhs, result_ty, op, .Saturating); } fn airMulOverflow(self: *NavGen, inst: Air.Inst.Index) !?IdRef { From ce09c0345511cb75828360790c0edeae574ef2d2 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Tue, 1 Jul 2025 20:58:45 +0200 Subject: [PATCH 02/10] spirv: saturating arithmetic implementation --- src/codegen/spirv.zig | 154 +++++++++++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 23 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 5c5a356ab8b7..328b9c953885 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3255,6 +3255,8 @@ const NavGen = struct { .shl_with_overflow => try self.airShlOverflow(inst), .add_sat => try self.airAddSubSaturating(inst, .i_add), .sub_sat => try self.airAddSubSaturating(inst, .i_sub), + .mul_sat => try self.airMulSaturating(inst), + .shl_sat => try self.airShlSaturating(inst), .mul_add => try self.airMulAdd(inst), @@ -3758,17 +3760,15 @@ const NavGen = struct { return self.buildAddSub(lhs, rhs, result_ty, op, .Saturating); } - fn airMulOverflow(self: *NavGen, inst: Air.Inst.Index) !?IdRef { + fn buildMul( + self: *NavGen, + lhs: Temporary, + rhs: Temporary, + result_ty: Type, + comptime mode: enum { WithOverflow, Saturating }, + ) !?IdRef { const pt = self.pt; - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; - - const lhs = try self.temporary(extra.lhs); - const rhs = try self.temporary(extra.rhs); - - const result_ty = self.typeOfIndex(inst); - const info = self.arithmeticTypeInfo(lhs.ty); switch (info.class) { .composite_integer => unreachable, // TODO @@ -3924,26 +3924,80 @@ const NavGen = struct { }, }; - const ov = try self.intFromBool(overflowed); + switch (mode) { + .WithOverflow => { + const ov = try self.intFromBool(overflowed); - const result_ty_id = try self.resolveType(result_ty, .direct); - return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); - } + const result_ty_id = try self.resolveType(result_ty, .direct); + return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); + }, + .Saturating => { + const sat_val_tmp: Temporary = blk: switch (info.signedness) { + .unsigned => { + const scalar_ty = result_ty.scalarType(self.pt.zcu); + const max_val: u64 = if (info.bits == 64) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; + const max_id = try self.constInt(scalar_ty, max_val); + break :blk .{ .ty = scalar_ty, .value = .{ .singleton = max_id } }; + }, + .signed => { + const zero = Temporary.init(rhs.ty, try self.constInt(rhs.ty, 0)); + const lhs_is_neg = try self.buildCmp(.s_lt, lhs, zero); + const rhs_is_neg = try self.buildCmp(.s_lt, rhs, zero); + const signs_differ = try self.buildCmp(.l_ne, lhs_is_neg, rhs_is_neg); - fn airShlOverflow(self: *NavGen, inst: Air.Inst.Index) !?IdRef { - const zcu = self.pt.zcu; + const scalar_ty = result_ty.scalarType(self.pt.zcu); + const min_val: i64 = if (info.bits == 0) 0 else -(@as(i64, 1) << @as(u6, @intCast(info.bits - 1))); + const max_val: u64 = if (info.bits == 0) 0 else (@as(u64, 1) << @as(u6, @intCast(info.bits - 1))) - 1; + + const min_id = try self.constInt(scalar_ty, min_val); + const max_id = try self.constInt(scalar_ty, max_val); + + break :blk try self.buildSelect( + signs_differ, + Temporary.init(scalar_ty, min_id), + Temporary.init(scalar_ty, max_id), + ); + }, + }; + const final_result = try self.buildSelect(overflowed, sat_val_tmp, result); + return try final_result.materialize(self); + }, + } + } + fn airMulOverflow(self: *NavGen, inst: Air.Inst.Index) !?IdRef { const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; - if (self.typeOf(extra.lhs).isVector(zcu) and !self.typeOf(extra.rhs).isVector(zcu)) { - return self.fail("vector shift with scalar rhs", .{}); - } + const lhs = try self.temporary(extra.lhs); + const rhs = try self.temporary(extra.rhs); - const base = try self.temporary(extra.lhs); - const shift = try self.temporary(extra.rhs); + const result_ty = self.typeOfIndex(inst); + return self.buildMul(lhs, rhs, result_ty, .WithOverflow); + } + + fn airMulSaturating(self: *NavGen, inst: Air.Inst.Index) !?IdRef { + const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; + + const lhs = try self.temporary(bin_op.lhs); + const rhs = try self.temporary(bin_op.rhs); const result_ty = self.typeOfIndex(inst); + return self.buildMul(lhs, rhs, result_ty, .Saturating); + } + + fn buildShl( + self: *NavGen, + base: Temporary, + shift: Temporary, + result_ty: Type, + comptime mode: enum { WithOverflow, Saturating }, + ) !?IdRef { + const zcu = self.pt.zcu; + + if (base.ty.isVector(zcu) and !shift.ty.isVector(zcu)) { + return self.fail("vector shift with scalar rhs", .{}); + } const info = self.arithmeticTypeInfo(base.ty); switch (info.class) { @@ -3965,10 +4019,64 @@ const NavGen = struct { }; const overflowed = try self.buildCmp(.i_ne, base, right); - const ov = try self.intFromBool(overflowed); - const result_ty_id = try self.resolveType(result_ty, .direct); - return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); + switch (mode) { + .WithOverflow => { + const ov = try self.intFromBool(overflowed); + const result_ty_id = try self.resolveType(result_ty, .direct); + return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); + }, + .Saturating => { + const sat_val_tmp: Temporary = blk: switch (info.signedness) { + .unsigned => { + const scalar_ty = result_ty.scalarType(self.pt.zcu); + const max_val: u64 = if (info.bits == 64) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; + const max_id = try self.constInt(scalar_ty, max_val); + break :blk .{ .ty = scalar_ty, .value = .{ .singleton = max_id } }; + }, + .signed => { + const zero = Temporary.init(base.ty, try self.constInt(base.ty, 0)); + const base_is_neg = try self.buildCmp(.s_lt, base, zero); + + const scalar_ty = result_ty.scalarType(self.pt.zcu); + const min_val: i64 = if (info.bits == 0) 0 else -(@as(i64, 1) << @as(u6, @intCast(info.bits - 1))); + const max_val: u64 = if (info.bits == 0) 0 else (@as(u64, 1) << @as(u6, @intCast(info.bits - 1))) - 1; + + const min_id = try self.constInt(scalar_ty, min_val); + const max_id = try self.constInt(scalar_ty, max_val); + + break :blk try self.buildSelect( + base_is_neg, + Temporary.init(scalar_ty, min_id), + Temporary.init(scalar_ty, max_id), + ); + }, + }; + const final_result = try self.buildSelect(overflowed, sat_val_tmp, result); + return try final_result.materialize(self); + }, + } + } + + fn airShlOverflow(self: *NavGen, inst: Air.Inst.Index) !?IdRef { + const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; + const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; + + const base = try self.temporary(extra.lhs); + const shift = try self.temporary(extra.rhs); + + const result_ty = self.typeOfIndex(inst); + return self.buildShl(base, shift, result_ty, .WithOverflow); + } + + fn airShlSaturating(self: *NavGen, inst: Air.Inst.Index) !?IdRef { + const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; + + const base = try self.temporary(bin_op.lhs); + const shift = try self.temporary(bin_op.rhs); + + const result_ty = self.typeOfIndex(inst); + return self.buildShl(base, shift, result_ty, .Saturating); } fn airMulAdd(self: *NavGen, inst: Air.Inst.Index) !?IdRef { From 5c4cfc2175a71007112896794ebd2a477915bcb1 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Wed, 2 Jul 2025 13:50:12 +0200 Subject: [PATCH 03/10] spirv: saturating arithmetic fixes for tests --- src/codegen/spirv.zig | 64 +++++++++++++++++-------- test/behavior/math.zig | 8 ++++ test/behavior/saturating_arithmetic.zig | 17 +++---- test/behavior/wrapping_arithmetic.zig | 37 +++++++------- 4 files changed, 79 insertions(+), 47 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 328b9c953885..6f4ef3bb5140 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3718,23 +3718,38 @@ const NavGen = struct { return try self.constructComposite(struct_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); }, .Saturating => { - const sat_val_tmp = blk: { - const scalar_ty = result_ty.scalarType(self.pt.zcu); - if (info.signedness == .signed and op == .i_sub) { - const min_val: i64 = if (info.bits == 0) 0 else -(@as(i64, 1) << @as(u6, @intCast(info.bits - 1))); - const min_id = try self.constInt(scalar_ty, min_val); - break :blk Temporary.init(scalar_ty, min_id); - } else { - const max_val: u64 = if (info.bits == 0) 0 else switch (info.signedness) { - .unsigned => if (info.bits == 64) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1, - .signed => (@as(u64, 1) << @as(u6, @intCast(info.bits - 1))) - 1, - }; - const max_id = try self.constInt(scalar_ty, max_val); - break :blk Temporary.init(scalar_ty, max_id); - } - }; - const final_result = try self.buildSelect(overflowed, sat_val_tmp, result); - return try final_result.materialize(self); + const scalar_ty = result_ty.scalarType(self.pt.zcu); + + if (info.signedness == .signed) { + const min_val: i64 = if (info.bits == 0) 0 else if (info.bits == 64) std.math.minInt(i64) else -(@as(i64, 1) << @as(u6, @intCast(info.bits - 1))); + const max_val: i64 = if (info.bits == 0) 0 else if (info.bits == 64) std.math.maxInt(i64) else (@as(i64, 1) << @as(u6, @intCast(info.bits - 1))) - 1; + + const min_id = try self.constInt(scalar_ty, min_val); + const max_id = try self.constInt(scalar_ty, max_val); + + const min_tmp = Temporary.init(scalar_ty, min_id); + const max_tmp = Temporary.init(scalar_ty, max_id); + + // The sign of the left-hand-side operand predicts the direction of saturation. + // If lhs is negative, any overflow/underflow will be towards min. + // If lhs is positive, any overflow will be towards max. + const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0)); + const lhs_is_neg = try self.buildCmp(.s_lt, lhs, zero); + + const saturation_value = try self.buildSelect(lhs_is_neg, min_tmp, max_tmp); + const final_result = try self.buildSelect(overflowed, saturation_value, result); + return try final_result.materialize(self); + } else { + const saturation_val: u64 = switch (op) { + .i_add => if (info.bits == 0) 0 else if (info.bits == 64) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1, // saturate to max on overflow + .i_sub => 0, // saturate to min (0) on underflow + else => unreachable, + }; + const saturation_id = try self.constInt(scalar_ty, saturation_val); + const saturation_tmp = Temporary.init(scalar_ty, saturation_id); + const final_result = try self.buildSelect(overflowed, saturation_tmp, result); + return try final_result.materialize(self); + } }, } } @@ -3932,9 +3947,9 @@ const NavGen = struct { return try self.constructComposite(result_ty_id, &.{ try result.materialize(self), try ov.materialize(self) }); }, .Saturating => { + const scalar_ty = result_ty.scalarType(self.pt.zcu); const sat_val_tmp: Temporary = blk: switch (info.signedness) { .unsigned => { - const scalar_ty = result_ty.scalarType(self.pt.zcu); const max_val: u64 = if (info.bits == 64) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; const max_id = try self.constInt(scalar_ty, max_val); break :blk .{ .ty = scalar_ty, .value = .{ .singleton = max_id } }; @@ -3945,7 +3960,6 @@ const NavGen = struct { const rhs_is_neg = try self.buildCmp(.s_lt, rhs, zero); const signs_differ = try self.buildCmp(.l_ne, lhs_is_neg, rhs_is_neg); - const scalar_ty = result_ty.scalarType(self.pt.zcu); const min_val: i64 = if (info.bits == 0) 0 else -(@as(i64, 1) << @as(u6, @intCast(info.bits - 1))); const max_val: u64 = if (info.bits == 0) 0 else (@as(u64, 1) << @as(u6, @intCast(info.bits - 1))) - 1; @@ -4013,12 +4027,22 @@ const NavGen = struct { const left = try self.buildBinary(.sll, base, casted_shift); const result = try self.normalize(left, info); + // Check if shift amount >= bit width, which always causes overflow (except when base is 0) + const bit_width_id = try self.constInt(base.ty.scalarType(zcu), info.bits); + const bit_width_tmp = Temporary.init(base.ty.scalarType(zcu), bit_width_id); + const shift_too_large = try self.buildCmp(.u_ge, casted_shift, bit_width_tmp); + + const zero_tmp = Temporary.init(base.ty, try self.constInt(base.ty, 0)); + const base_is_zero = try self.buildCmp(.i_eq, base, zero_tmp); + const large_shift_overflow = try self.buildBinary(.l_and, shift_too_large, try self.buildUnary(.l_not, base_is_zero)); + const right = switch (info.signedness) { .unsigned => try self.buildBinary(.srl, result, casted_shift), .signed => try self.buildBinary(.sra, result, casted_shift), }; - const overflowed = try self.buildCmp(.i_ne, base, right); + const round_trip_overflow = try self.buildCmp(.i_ne, base, right); + const overflowed = try self.buildBinary(.l_or, large_shift_overflow, round_trip_overflow); switch (mode) { .WithOverflow => { diff --git a/test/behavior/math.zig b/test/behavior/math.zig index d51be481988b..c92403acf338 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -872,6 +872,10 @@ test "@addWithOverflow" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO + try testAddWithOverflow(u8, 42, 0, 42, 0); + try testAddWithOverflow(i8, 42, 0, 42, 0); + try testAddWithOverflow(i8, -42, 0, -42, 0); + try testAddWithOverflow(u8, 250, 100, 94, 1); try testAddWithOverflow(u8, 100, 150, 250, 0); @@ -1118,6 +1122,10 @@ test "@subWithOverflow" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + try testAddWithOverflow(u8, 42, 0, 42, 0); + try testAddWithOverflow(i8, 42, 0, 42, 0); + try testAddWithOverflow(i8, -42, 0, -42, 0); + try testSubWithOverflow(u8, 1, 2, 255, 1); try testSubWithOverflow(u8, 1, 1, 0, 0); diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index 1abd5b4dabc0..e1f54796c538 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -4,11 +4,12 @@ const minInt = std.math.minInt; const maxInt = std.math.maxInt; const expect = std.testing.expect; +const spirv = builtin.zig_backend == .stage2_spirv; + test "saturating add" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { @@ -82,7 +83,6 @@ test "saturating subtraction" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { @@ -161,7 +161,6 @@ test "saturating multiplication <= 32 bits" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; @@ -260,7 +259,6 @@ test "saturating multiplication" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; @@ -277,11 +275,11 @@ test "saturating multiplication" { try testSatMul(i8, -128, -128, 127); try testSatMul(i8, maxInt(i8), maxInt(i8), maxInt(i8)); try testSatMul(i16, maxInt(i16), -1, minInt(i16) + 1); - try testSatMul(i128, maxInt(i128), -1, minInt(i128) + 1); - try testSatMul(i128, minInt(i128), -1, maxInt(i128)); + if (!spirv) try testSatMul(i128, maxInt(i128), -1, minInt(i128) + 1); + if (!spirv) try testSatMul(i128, minInt(i128), -1, maxInt(i128)); try testSatMul(u8, 10, 3, 30); try testSatMul(u8, 2, 255, 255); - try testSatMul(u128, maxInt(u128), maxInt(u128), maxInt(u128)); + if (!spirv) try testSatMul(u128, maxInt(u128), maxInt(u128), maxInt(u128)); } }; @@ -298,7 +296,6 @@ test "saturating shift-left" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { @@ -306,8 +303,8 @@ test "saturating shift-left" { try testSatShl(i8, 1, u8, 2, 4); try testSatShl(i8, 127, u8, 1, 127); try testSatShl(i8, -128, u8, 1, -128); - // TODO: remove this check once #9668 is completed - if (!builtin.cpu.arch.isWasm()) { + // TODO: remove wasm check once #9668 is completed + if (!spirv and !builtin.cpu.arch.isWasm()) { // skip testing ints > 64 bits on wasm due to miscompilation / wasmtime ci error try testSatShl(i128, maxInt(i128), u128, 64, maxInt(i128)); try testSatShl(u128, maxInt(u128), u128, 64, maxInt(u128)); diff --git a/test/behavior/wrapping_arithmetic.zig b/test/behavior/wrapping_arithmetic.zig index 7c5d60cc4100..9ff75cea55de 100644 --- a/test/behavior/wrapping_arithmetic.zig +++ b/test/behavior/wrapping_arithmetic.zig @@ -4,24 +4,27 @@ const minInt = std.math.minInt; const maxInt = std.math.maxInt; const expect = std.testing.expect; +const spirv = builtin.zig_backend == .stage2_spirv; + test "wrapping add" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { + try testWrapAdd(i8, 3, 0, 3); + try testWrapAdd(i8, -3, 0, -3); try testWrapAdd(i8, -3, 10, 7); try testWrapAdd(i8, -128, -128, 0); - try testWrapAdd(i2, 1, 1, -2); + if (!spirv) try testWrapAdd(i2, 1, 1, -2); try testWrapAdd(i64, maxInt(i64), 1, minInt(i64)); - try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); - try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); + if (!spirv) try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); + if (!spirv) try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); try testWrapAdd(i8, 127, 127, -2); try testWrapAdd(u8, 3, 10, 13); try testWrapAdd(u8, 255, 255, 254); - try testWrapAdd(u2, 3, 2, 1); - try testWrapAdd(u3, 7, 1, 0); - try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); + if (!spirv) try testWrapAdd(u2, 3, 2, 1); + if (!spirv) try testWrapAdd(u3, 7, 1, 0); + if (!spirv) try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); } fn testWrapAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -43,21 +46,22 @@ test "wrapping add" { } test "wrapping subtraction" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; const S = struct { fn doTheTest() !void { + try testWrapSub(i8, 3, 0, 3); + try testWrapSub(i8, -3, 0, -3); try testWrapSub(i8, -3, 10, -13); try testWrapSub(i8, -128, -128, 0); try testWrapSub(i8, -1, 127, -128); try testWrapSub(i64, minInt(i64), 1, maxInt(i64)); - try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); - try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); + if (!spirv) try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); + if (!spirv) try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); try testWrapSub(u8, 10, 3, 7); try testWrapSub(u8, 0, 255, 1); - try testWrapSub(u5, 0, 31, 1); - try testWrapSub(u128, 0, maxInt(u128), 1); + if (!spirv) try testWrapSub(u5, 0, 31, 1); + if (!spirv) try testWrapSub(u128, 0, maxInt(u128), 1); } fn testWrapSub(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -79,7 +83,6 @@ test "wrapping subtraction" { } test "wrapping multiplication" { - if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO: once #9660 has been solved, remove this line @@ -88,16 +91,16 @@ test "wrapping multiplication" { const S = struct { fn doTheTest() !void { try testWrapMul(i8, -3, 10, -30); - try testWrapMul(i4, 2, 4, -8); + if (!spirv) try testWrapMul(i4, 2, 4, -8); try testWrapMul(i8, 2, 127, -2); try testWrapMul(i8, -128, -128, 0); try testWrapMul(i8, maxInt(i8), maxInt(i8), 1); try testWrapMul(i16, maxInt(i16), -1, minInt(i16) + 1); - try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); - try testWrapMul(i128, minInt(i128), -1, minInt(i128)); + if (!spirv) try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); + if (!spirv) try testWrapMul(i128, minInt(i128), -1, minInt(i128)); try testWrapMul(u8, 10, 3, 30); try testWrapMul(u8, 2, 255, 254); - try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); + if (!spirv) try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); } fn testWrapMul(comptime T: type, lhs: T, rhs: T, expected: T) !void { From 388cb9c57de61ffeacd46a14b6094bbbbb36ff49 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Wed, 2 Jul 2025 14:34:20 +0200 Subject: [PATCH 04/10] spirv: refine unreachable case handling for arithmetic types --- src/codegen/spirv.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 6f4ef3bb5140..b58372f0aad2 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3612,8 +3612,8 @@ const NavGen = struct { const info = self.arithmeticTypeInfo(lhs.ty); const result = switch (info.class) { - .composite_integer => unreachable, // TODO - .integer, .strange_integer => switch (info.signedness) { + .composite_integer, .strange_integer => unreachable, // TODO + .integer => switch (info.signedness) { .signed => try self.buildBinary(sop, lhs, rhs), .unsigned => try self.buildBinary(uop, lhs, rhs), }, From 54add4dcbf647ceaf3b7e52b9d5a596f0dfb20c6 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Wed, 2 Jul 2025 19:49:20 +0200 Subject: [PATCH 05/10] spirv: update tests to use subtraction overflow checks --- test/behavior/math.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/behavior/math.zig b/test/behavior/math.zig index c92403acf338..aec371208f13 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1122,9 +1122,9 @@ test "@subWithOverflow" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - try testAddWithOverflow(u8, 42, 0, 42, 0); - try testAddWithOverflow(i8, 42, 0, 42, 0); - try testAddWithOverflow(i8, -42, 0, -42, 0); + try testSubWithOverflow(u8, 42, 0, 42, 0); + try testSubWithOverflow(i8, 42, 0, 42, 0); + try testSubWithOverflow(i8, -42, 0, -42, 0); try testSubWithOverflow(u8, 1, 2, 255, 1); try testSubWithOverflow(u8, 1, 1, 0, 0); From 8edf9ecf1bd9a36d1a2f621bde67c95a3da51f49 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Fri, 4 Jul 2025 13:28:33 +0200 Subject: [PATCH 06/10] tests: disable @subWithOverflow for riscv64 --- test/behavior/math.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/test/behavior/math.zig b/test/behavior/math.zig index aec371208f13..4798d7607603 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1121,6 +1121,7 @@ test "@subWithOverflow" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO try testSubWithOverflow(u8, 42, 0, 42, 0); try testSubWithOverflow(i8, 42, 0, 42, 0); From d6fa32f54c3547710fba8f1ab1a226d1d1ebad1c Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Tue, 8 Jul 2025 03:24:13 +0200 Subject: [PATCH 07/10] spirv: tests comply with codestyle --- test/behavior/saturating_arithmetic.zig | 12 ++++----- test/behavior/wrapping_arithmetic.zig | 36 ++++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index e1f54796c538..55ebdd5c4a5f 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -4,8 +4,6 @@ const minInt = std.math.minInt; const maxInt = std.math.maxInt; const expect = std.testing.expect; -const spirv = builtin.zig_backend == .stage2_spirv; - test "saturating add" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO @@ -275,11 +273,13 @@ test "saturating multiplication" { try testSatMul(i8, -128, -128, 127); try testSatMul(i8, maxInt(i8), maxInt(i8), maxInt(i8)); try testSatMul(i16, maxInt(i16), -1, minInt(i16) + 1); - if (!spirv) try testSatMul(i128, maxInt(i128), -1, minInt(i128) + 1); - if (!spirv) try testSatMul(i128, minInt(i128), -1, maxInt(i128)); try testSatMul(u8, 10, 3, 30); try testSatMul(u8, 2, 255, 255); - if (!spirv) try testSatMul(u128, maxInt(u128), maxInt(u128), maxInt(u128)); + if (builtin.zig_backend != .stage2_spirv) { + try testSatMul(i128, maxInt(i128), -1, minInt(i128) + 1); + try testSatMul(i128, minInt(i128), -1, maxInt(i128)); + try testSatMul(u128, maxInt(u128), maxInt(u128), maxInt(u128)); + } } }; @@ -304,7 +304,7 @@ test "saturating shift-left" { try testSatShl(i8, 127, u8, 1, 127); try testSatShl(i8, -128, u8, 1, -128); // TODO: remove wasm check once #9668 is completed - if (!spirv and !builtin.cpu.arch.isWasm()) { + if (builtin.zig_backend != .stage2_spirv and !builtin.cpu.arch.isWasm()) { // skip testing ints > 64 bits on wasm due to miscompilation / wasmtime ci error try testSatShl(i128, maxInt(i128), u128, 64, maxInt(i128)); try testSatShl(u128, maxInt(u128), u128, 64, maxInt(u128)); diff --git a/test/behavior/wrapping_arithmetic.zig b/test/behavior/wrapping_arithmetic.zig index 9ff75cea55de..1892b1c0bded 100644 --- a/test/behavior/wrapping_arithmetic.zig +++ b/test/behavior/wrapping_arithmetic.zig @@ -4,8 +4,6 @@ const minInt = std.math.minInt; const maxInt = std.math.maxInt; const expect = std.testing.expect; -const spirv = builtin.zig_backend == .stage2_spirv; - test "wrapping add" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; @@ -15,16 +13,18 @@ test "wrapping add" { try testWrapAdd(i8, -3, 0, -3); try testWrapAdd(i8, -3, 10, 7); try testWrapAdd(i8, -128, -128, 0); - if (!spirv) try testWrapAdd(i2, 1, 1, -2); try testWrapAdd(i64, maxInt(i64), 1, minInt(i64)); - if (!spirv) try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); - if (!spirv) try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); try testWrapAdd(i8, 127, 127, -2); try testWrapAdd(u8, 3, 10, 13); try testWrapAdd(u8, 255, 255, 254); - if (!spirv) try testWrapAdd(u2, 3, 2, 1); - if (!spirv) try testWrapAdd(u3, 7, 1, 0); - if (!spirv) try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); + if (builtin.zig_backend != .stage2_spirv) { + try testWrapAdd(i2, 1, 1, -2); + try testWrapAdd(u2, 3, 2, 1); + try testWrapAdd(u3, 7, 1, 0); + try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); + try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); + try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); + } } fn testWrapAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -56,12 +56,14 @@ test "wrapping subtraction" { try testWrapSub(i8, -128, -128, 0); try testWrapSub(i8, -1, 127, -128); try testWrapSub(i64, minInt(i64), 1, maxInt(i64)); - if (!spirv) try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); - if (!spirv) try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); try testWrapSub(u8, 10, 3, 7); try testWrapSub(u8, 0, 255, 1); - if (!spirv) try testWrapSub(u5, 0, 31, 1); - if (!spirv) try testWrapSub(u128, 0, maxInt(u128), 1); + if (builtin.zig_backend != .stage2_spirv) { + try testWrapSub(u5, 0, 31, 1); + try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); + try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); + try testWrapSub(u128, 0, maxInt(u128), 1); + } } fn testWrapSub(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -91,16 +93,18 @@ test "wrapping multiplication" { const S = struct { fn doTheTest() !void { try testWrapMul(i8, -3, 10, -30); - if (!spirv) try testWrapMul(i4, 2, 4, -8); try testWrapMul(i8, 2, 127, -2); try testWrapMul(i8, -128, -128, 0); try testWrapMul(i8, maxInt(i8), maxInt(i8), 1); try testWrapMul(i16, maxInt(i16), -1, minInt(i16) + 1); - if (!spirv) try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); - if (!spirv) try testWrapMul(i128, minInt(i128), -1, minInt(i128)); try testWrapMul(u8, 10, 3, 30); try testWrapMul(u8, 2, 255, 254); - if (!spirv) try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); + if (builtin.zig_backend != .stage2_spirv) { + try testWrapMul(i4, 2, 4, -8); + try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); + try testWrapMul(i128, minInt(i128), -1, minInt(i128)); + try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); + } } fn testWrapMul(comptime T: type, lhs: T, rhs: T, expected: T) !void { From 6c9ed5fe33f4c28ced6d9a108c25a6e2d06001c1 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Wed, 9 Jul 2025 17:56:40 +0200 Subject: [PATCH 08/10] spirv: tests comply with early return codestyle; #9668 is completed --- test/behavior/saturating_arithmetic.zig | 22 ++++++------- test/behavior/wrapping_arithmetic.zig | 43 +++++++++++++------------ 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index 55ebdd5c4a5f..caaceac001d1 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -275,11 +275,12 @@ test "saturating multiplication" { try testSatMul(i16, maxInt(i16), -1, minInt(i16) + 1); try testSatMul(u8, 10, 3, 30); try testSatMul(u8, 2, 255, 255); - if (builtin.zig_backend != .stage2_spirv) { - try testSatMul(i128, maxInt(i128), -1, minInt(i128) + 1); - try testSatMul(i128, minInt(i128), -1, maxInt(i128)); - try testSatMul(u128, maxInt(u128), maxInt(u128), maxInt(u128)); - } + + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO composite_integer + + try testSatMul(i128, maxInt(i128), -1, minInt(i128) + 1); + try testSatMul(i128, minInt(i128), -1, maxInt(i128)); + try testSatMul(u128, maxInt(u128), maxInt(u128), maxInt(u128)); } }; @@ -303,12 +304,6 @@ test "saturating shift-left" { try testSatShl(i8, 1, u8, 2, 4); try testSatShl(i8, 127, u8, 1, 127); try testSatShl(i8, -128, u8, 1, -128); - // TODO: remove wasm check once #9668 is completed - if (builtin.zig_backend != .stage2_spirv and !builtin.cpu.arch.isWasm()) { - // skip testing ints > 64 bits on wasm due to miscompilation / wasmtime ci error - try testSatShl(i128, maxInt(i128), u128, 64, maxInt(i128)); - try testSatShl(u128, maxInt(u128), u128, 64, maxInt(u128)); - } try testSatShl(u8, 1, u8, 2, 4); try testSatShl(u8, 255, u8, 1, 255); try testSatShl(i8, -3, u4, 8, minInt(i8)); @@ -316,6 +311,11 @@ test "saturating shift-left" { try testSatShl(i8, 3, u4, 8, maxInt(i8)); try testSatShl(u8, 0, u4, 8, 0); try testSatShl(u8, 3, u4, 8, maxInt(u8)); + + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO composite_integer + + try testSatShl(i128, maxInt(i128), u128, 64, maxInt(i128)); + try testSatShl(u128, maxInt(u128), u128, 64, maxInt(u128)); } fn testSatShl(comptime Lhs: type, lhs: Lhs, comptime Rhs: type, rhs: Rhs, expected: Lhs) !void { diff --git a/test/behavior/wrapping_arithmetic.zig b/test/behavior/wrapping_arithmetic.zig index 1892b1c0bded..91dcc23e4b29 100644 --- a/test/behavior/wrapping_arithmetic.zig +++ b/test/behavior/wrapping_arithmetic.zig @@ -17,14 +17,15 @@ test "wrapping add" { try testWrapAdd(i8, 127, 127, -2); try testWrapAdd(u8, 3, 10, 13); try testWrapAdd(u8, 255, 255, 254); - if (builtin.zig_backend != .stage2_spirv) { - try testWrapAdd(i2, 1, 1, -2); - try testWrapAdd(u2, 3, 2, 1); - try testWrapAdd(u3, 7, 1, 0); - try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); - try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); - try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); - } + + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO composite_integer, strange_integer + + try testWrapAdd(i2, 1, 1, -2); + try testWrapAdd(u2, 3, 2, 1); + try testWrapAdd(u3, 7, 1, 0); + try testWrapAdd(i128, maxInt(i128), -maxInt(i128), 0); + try testWrapAdd(i128, minInt(i128), maxInt(i128), -1); + try testWrapAdd(u128, maxInt(u128), 1, minInt(u128)); } fn testWrapAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -58,12 +59,13 @@ test "wrapping subtraction" { try testWrapSub(i64, minInt(i64), 1, maxInt(i64)); try testWrapSub(u8, 10, 3, 7); try testWrapSub(u8, 0, 255, 1); - if (builtin.zig_backend != .stage2_spirv) { - try testWrapSub(u5, 0, 31, 1); - try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); - try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); - try testWrapSub(u128, 0, maxInt(u128), 1); - } + + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO composite_integer, strange_integer + + try testWrapSub(u5, 0, 31, 1); + try testWrapSub(i128, maxInt(i128), -1, minInt(i128)); + try testWrapSub(i128, minInt(i128), -maxInt(i128), -1); + try testWrapSub(u128, 0, maxInt(u128), 1); } fn testWrapSub(comptime T: type, lhs: T, rhs: T, expected: T) !void { @@ -99,12 +101,13 @@ test "wrapping multiplication" { try testWrapMul(i16, maxInt(i16), -1, minInt(i16) + 1); try testWrapMul(u8, 10, 3, 30); try testWrapMul(u8, 2, 255, 254); - if (builtin.zig_backend != .stage2_spirv) { - try testWrapMul(i4, 2, 4, -8); - try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); - try testWrapMul(i128, minInt(i128), -1, minInt(i128)); - try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); - } + + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO composite_integer, strange_integer + + try testWrapMul(i4, 2, 4, -8); + try testWrapMul(i128, maxInt(i128), -1, minInt(i128) + 1); + try testWrapMul(i128, minInt(i128), -1, minInt(i128)); + try testWrapMul(u128, maxInt(u128), maxInt(u128), 1); } fn testWrapMul(comptime T: type, lhs: T, rhs: T, expected: T) !void { From f1cf85d37f06e186a8f6e5d72c870df7bb17ef1c Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Wed, 9 Jul 2025 18:45:48 +0200 Subject: [PATCH 09/10] spirv: return note back and use comptime functions instead of unreachable --- src/codegen/spirv.zig | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index b58372f0aad2..27b3b90ac9ad 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3666,6 +3666,12 @@ const NavGen = struct { comptime op: BinaryOp, comptime mode: enum { WithOverflow, Saturating }, ) !?IdRef { + // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers, + // there is in both cases only one extra operation required. For signed operations, + // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually + // normally set a carry bit. So the SPIR-V overflow operations are not particularly + // useful here. + const info = self.arithmeticTypeInfo(lhs.ty); switch (info.class) { .composite_integer => unreachable, // TODO @@ -3682,7 +3688,7 @@ const NavGen = struct { .unsigned => switch (op) { .i_add => try self.buildCmp(.u_lt, result, lhs), .i_sub => try self.buildCmp(.u_gt, result, lhs), - else => unreachable, + else => @compileLog(op), }, // For signed operations, we check the signs of the operands and the result. .signed => blk: { @@ -3702,10 +3708,11 @@ const NavGen = struct { const signs_match = try self.buildCmp(.l_eq, lhs_is_neg, rhs_is_neg); const result_sign_differs = try self.buildCmp(.l_ne, lhs_is_neg, result_is_neg); - const overflow_condition = if (op == .i_add) - signs_match - else // .i_sub - try self.buildUnary(.l_not, signs_match); + const overflow_condition = switch (op) { + .i_add => signs_match, + .i_sub => try self.buildUnary(.l_not, signs_match), + else => @compileLog(op), + }; break :blk try self.buildBinary(.l_and, overflow_condition, result_sign_differs); }, @@ -3743,7 +3750,7 @@ const NavGen = struct { const saturation_val: u64 = switch (op) { .i_add => if (info.bits == 0) 0 else if (info.bits == 64) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1, // saturate to max on overflow .i_sub => 0, // saturate to min (0) on underflow - else => unreachable, + else => @compileLog(op), }; const saturation_id = try self.constInt(scalar_ty, saturation_val); const saturation_tmp = Temporary.init(scalar_ty, saturation_id); From b5cd10cc829e571a9738d064512da32036ab1623 Mon Sep 17 00:00:00 2001 From: Ivan Stepanov Date: Thu, 10 Jul 2025 04:22:10 +0200 Subject: [PATCH 10/10] spirv: #9668 still there --- test/behavior/saturating_arithmetic.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index caaceac001d1..99a817a06c8e 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -313,6 +313,7 @@ test "saturating shift-left" { try testSatShl(u8, 3, u4, 8, maxInt(u8)); if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO composite_integer + if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO #9668 try testSatShl(i128, maxInt(i128), u128, 64, maxInt(i128)); try testSatShl(u128, maxInt(u128), u128, 64, maxInt(u128));