authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-01-19 23:56:02+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-02-04 19:09:27+01:00
logb67d983abda198c69fbcde68a961e0e8b92b7939
tree40a052cb545a45cf8a37963347cf525ec35e6e3d
parent761594e2260eb780ab1861568e38a7066a7513df
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: vectorize add/sub overflow


2 files changed, 86 insertions(+), 83 deletions(-)

src/codegen/spirv.zig+85-80
...@@ -2582,103 +2582,108 @@ const DeclGen = struct {...@@ -2582,103 +2582,108 @@ const DeclGen = struct {
2582 const lhs = try self.resolve(extra.lhs);2582 const lhs = try self.resolve(extra.lhs);
2583 const rhs = try self.resolve(extra.rhs);2583 const rhs = try self.resolve(extra.rhs);
25842584
2585 const operand_ty = self.typeOf(extra.lhs);
2586 const result_ty = self.typeOfIndex(inst);2585 const result_ty = self.typeOfIndex(inst);
2586 const operand_ty = self.typeOf(extra.lhs);
2587 const ov_ty = result_ty.structFieldType(1, self.module);
2588
2589 const bool_ty_ref = try self.resolveType(Type.bool, .direct);
25872590
2588 const info = try self.arithmeticTypeInfo(operand_ty);2591 const info = try self.arithmeticTypeInfo(operand_ty);
2589 switch (info.class) {2592 switch (info.class) {
2590 .composite_integer => return self.todo("overflow ops for composite integers", .{}),2593 .composite_integer => return self.todo("overflow ops for composite integers", .{}),
2591 .strange_integer => return self.todo("overflow ops for strange integers", .{}),2594 .strange_integer, .integer => {},
2592 .integer => {},
2593 .float, .bool => unreachable,2595 .float, .bool => unreachable,
2594 }2596 }
25952597
2596 // The operand type must be the same as the result type in SPIR-V, which2598 var wip_result = try self.elementWise(operand_ty);
2597 // is the same as in Zig.2599 defer wip_result.deinit();
2598 const operand_ty_ref = try self.resolveType(operand_ty, .direct);2600 var wip_ov = try self.elementWise(ov_ty);
2599 const operand_ty_id = self.typeId(operand_ty_ref);2601 defer wip_ov.deinit();
2602 for (wip_result.results, wip_ov.results, 0..) |*value_id, *ov_id, i| {
2603 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);
2604 const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i);
26002605
2601 const bool_ty_ref = try self.resolveType(Type.bool, .direct);2606 // Normalize both so that we can properly check for overflow
2607 const lhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, lhs_elem_id, info);
2608 const rhs_norm_id = try self.normalizeInt(wip_result.scalar_ty_ref, rhs_elem_id, info);
2609 const op_result_id = self.spv.allocId();
26022610
2603 const ov_ty = result_ty.structFieldType(1, self.module);2611 try self.func.body.emit(self.spv.gpa, add, .{
2604 // Note: result is stored in a struct, so indirect representation.2612 .id_result_type = wip_result.scalar_ty_id,
2605 const ov_ty_ref = try self.resolveType(ov_ty, .indirect);2613 .id_result = op_result_id,
26062614 .operand_1 = lhs_norm_id,
2607 // TODO: Operations other than addition.2615 .operand_2 = rhs_norm_id,
2608 const value_id = self.spv.allocId();2616 });
2609 try self.func.body.emit(self.spv.gpa, add, .{
2610 .id_result_type = operand_ty_id,
2611 .id_result = value_id,
2612 .operand_1 = lhs,
2613 .operand_2 = rhs,
2614 });
26152617
2616 const overflowed_id = switch (info.signedness) {2618 // Normalize the result so that the comparisons go well
2617 .unsigned => blk: {2619 value_id.* = try self.normalizeInt(wip_result.scalar_ty_ref, op_result_id, info);
2618 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.2620
2619 // For subtraction the conditions need to be swapped.2621 const overflowed_id = switch (info.signedness) {
2620 const overflowed_id = self.spv.allocId();2622 .unsigned => blk: {
2621 try self.func.body.emit(self.spv.gpa, ucmp, .{2623 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
2622 .id_result_type = self.typeId(bool_ty_ref),2624 // For subtraction the conditions need to be swapped.
2623 .id_result = overflowed_id,2625 const overflowed_id = self.spv.allocId();
2624 .operand_1 = value_id,2626 try self.func.body.emit(self.spv.gpa, ucmp, .{
2625 .operand_2 = lhs,2627 .id_result_type = self.typeId(bool_ty_ref),
2626 });2628 .id_result = overflowed_id,
2627 break :blk overflowed_id;2629 .operand_1 = value_id.*,
2628 },2630 .operand_2 = lhs_norm_id,
2629 .signed => blk: {2631 });
2630 // lhs - rhs2632 break :blk overflowed_id;
2631 // For addition, overflow happened if:2633 },
2632 // - rhs is negative and value > lhs2634 .signed => blk: {
2633 // - rhs is positive and value < lhs2635 // lhs - rhs
2634 // This can be shortened to:2636 // For addition, overflow happened if:
2635 // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs)2637 // - rhs is negative and value > lhs
2636 // = (rhs < 0) == (value > lhs)2638 // - rhs is positive and value < lhs
2637 // = (rhs < 0) == (lhs < value)2639 // This can be shortened to:
2638 // Note that signed overflow is also wrapping in spir-v.2640 // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs)
2639 // For subtraction, overflow happened if:2641 // = (rhs < 0) == (value > lhs)
2640 // - rhs is negative and value < lhs2642 // = (rhs < 0) == (lhs < value)
2641 // - rhs is positive and value > lhs2643 // Note that signed overflow is also wrapping in spir-v.
2642 // This can be shortened to:2644 // For subtraction, overflow happened if:
2643 // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs)2645 // - rhs is negative and value < lhs
2644 // = (rhs < 0) == (value < lhs)2646 // - rhs is positive and value > lhs
2645 // = (rhs < 0) == (lhs > value)2647 // This can be shortened to:
26462648 // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs)
2647 const rhs_lt_zero_id = self.spv.allocId();2649 // = (rhs < 0) == (value < lhs)
2648 const zero_id = try self.constInt(operand_ty_ref, 0);2650 // = (rhs < 0) == (lhs > value)
2649 try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{2651
2650 .id_result_type = self.typeId(bool_ty_ref),2652 const rhs_lt_zero_id = self.spv.allocId();
2651 .id_result = rhs_lt_zero_id,2653 const zero_id = try self.constInt(wip_result.scalar_ty_ref, 0);
2652 .operand_1 = rhs,2654 try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{
2653 .operand_2 = zero_id,2655 .id_result_type = self.typeId(bool_ty_ref),
2654 });2656 .id_result = rhs_lt_zero_id,
2657 .operand_1 = rhs_norm_id,
2658 .operand_2 = zero_id,
2659 });
26552660
2656 const value_gt_lhs_id = self.spv.allocId();2661 const value_gt_lhs_id = self.spv.allocId();
2657 try self.func.body.emit(self.spv.gpa, scmp, .{2662 try self.func.body.emit(self.spv.gpa, scmp, .{
2658 .id_result_type = self.typeId(bool_ty_ref),2663 .id_result_type = self.typeId(bool_ty_ref),
2659 .id_result = value_gt_lhs_id,2664 .id_result = value_gt_lhs_id,
2660 .operand_1 = lhs,2665 .operand_1 = lhs_norm_id,
2661 .operand_2 = value_id,2666 .operand_2 = value_id.*,
2662 });2667 });
26632668
2664 const overflowed_id = self.spv.allocId();2669 const overflowed_id = self.spv.allocId();
2665 try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{2670 try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{
2666 .id_result_type = self.typeId(bool_ty_ref),2671 .id_result_type = self.typeId(bool_ty_ref),
2667 .id_result = overflowed_id,2672 .id_result = overflowed_id,
2668 .operand_1 = rhs_lt_zero_id,2673 .operand_1 = rhs_lt_zero_id,
2669 .operand_2 = value_gt_lhs_id,2674 .operand_2 = value_gt_lhs_id,
2670 });2675 });
2671 break :blk overflowed_id;2676 break :blk overflowed_id;
2672 },2677 },
2673 };2678 };
2679
2680 ov_id.* = try self.intFromBool(wip_ov.scalar_ty_ref, overflowed_id);
2681 }
26742682
2675 // Construct the struct that Zig wants as result.
2676 // The value should already be the correct type.
2677 const ov_id = try self.intFromBool(ov_ty_ref, overflowed_id);
2678 return try self.constructStruct(2683 return try self.constructStruct(
2679 result_ty,2684 result_ty,
2680 &.{ operand_ty, ov_ty },2685 &.{ operand_ty, ov_ty },
2681 &.{ value_id, ov_id },2686 &.{ try wip_result.finalize(), try wip_ov.finalize() },
2682 );2687 );
2683 }2688 }
26842689
test/behavior/vector.zig+1-3
...@@ -259,7 +259,6 @@ test "tuple to vector" {...@@ -259,7 +259,6 @@ test "tuple to vector" {
259 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO259 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
260 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO260 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
261 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO261 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
262 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
263262
264 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) {263 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) {
265 // Regressed with LLVM 14:264 // Regressed with LLVM 14:
...@@ -1063,7 +1062,7 @@ test "@addWithOverflow" {...@@ -1063,7 +1062,7 @@ test "@addWithOverflow" {
1063 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1062 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1064 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1063 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1065 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1064 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1066 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;1065 // if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
10671066
1068 const S = struct {1067 const S = struct {
1069 fn doTheTest() !void {1068 fn doTheTest() !void {
...@@ -1111,7 +1110,6 @@ test "@subWithOverflow" {...@@ -1111,7 +1110,6 @@ test "@subWithOverflow" {
1111 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1110 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1112 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1111 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1113 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1112 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1114 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
11151113
1116 const S = struct {1114 const S = struct {
1117 fn doTheTest() !void {1115 fn doTheTest() !void {