authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-20 08:48:46+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-06-20 08:48:46+02:00
loga97a39bea6022fb7449620384641cdfa70303f8d
treea88d96b2e12160fe2173162927fa0d16eb03cd51
parent74442f35030a9c4f4ff65db01a18e8fb2f2a1ecf
parent4957c7cbbd5ffa2c44970cc34a9274ae42f0f3fb
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11893 from Luukdegram/wasm-sat

stage2: wasm - saturating arithmetic

2 files changed, 268 insertions(+), 13 deletions(-)

src/arch/wasm/CodeGen.zig+216-3
...@@ -1432,8 +1432,10 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1432,8 +1432,10 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1432 .const_ty => unreachable,1432 .const_ty => unreachable,
14331433
1434 .add => self.airBinOp(inst, .add),1434 .add => self.airBinOp(inst, .add),
1435 .add_sat => self.airSatBinOp(inst, .add),
1435 .addwrap => self.airWrapBinOp(inst, .add),1436 .addwrap => self.airWrapBinOp(inst, .add),
1436 .sub => self.airBinOp(inst, .sub),1437 .sub => self.airBinOp(inst, .sub),
1438 .sub_sat => self.airSatBinOp(inst, .sub),
1437 .subwrap => self.airWrapBinOp(inst, .sub),1439 .subwrap => self.airWrapBinOp(inst, .sub),
1438 .mul => self.airBinOp(inst, .mul),1440 .mul => self.airBinOp(inst, .mul),
1439 .mulwrap => self.airWrapBinOp(inst, .mul),1441 .mulwrap => self.airWrapBinOp(inst, .mul),
...@@ -1452,6 +1454,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1452,6 +1454,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1452 .rem => self.airBinOp(inst, .rem),1454 .rem => self.airBinOp(inst, .rem),
1453 .shl => self.airWrapBinOp(inst, .shl),1455 .shl => self.airWrapBinOp(inst, .shl),
1454 .shl_exact => self.airBinOp(inst, .shl),1456 .shl_exact => self.airBinOp(inst, .shl),
1457 .shl_sat => self.airShlSat(inst),
1455 .shr, .shr_exact => self.airBinOp(inst, .shr),1458 .shr, .shr_exact => self.airBinOp(inst, .shr),
1456 .xor => self.airBinOp(inst, .xor),1459 .xor => self.airBinOp(inst, .xor),
1457 .max => self.airMaxMin(inst, .max),1460 .max => self.airMaxMin(inst, .max),
...@@ -1583,12 +1586,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1583,12 +1586,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
15831586
1584 .memcpy => self.airMemcpy(inst),1587 .memcpy => self.airMemcpy(inst),
15851588
1586 .add_sat,
1587 .sub_sat,
1588 .mul_sat,1589 .mul_sat,
1589 .mod,1590 .mod,
1590 .assembly,1591 .assembly,
1591 .shl_sat,
1592 .ret_addr,1592 .ret_addr,
1593 .frame_addr,1593 .frame_addr,
1594 .bit_reverse,1594 .bit_reverse,
...@@ -4878,3 +4878,216 @@ fn airCeilFloorTrunc(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValu...@@ -4878,3 +4878,216 @@ fn airCeilFloorTrunc(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValu
4878 try self.addLabel(.local_set, result.local);4878 try self.addLabel(.local_set, result.local);
4879 return result;4879 return result;
4880}4880}
4881
4882fn airSatBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
4883 assert(op == .add or op == .sub);
4884 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
4885
4886 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
4887 const ty = self.air.typeOfIndex(inst);
4888 const lhs = try self.resolveInst(bin_op.lhs);
4889 const rhs = try self.resolveInst(bin_op.rhs);
4890
4891 const int_info = ty.intInfo(self.target);
4892 const is_signed = int_info.signedness == .signed;
4893
4894 if (int_info.bits > 64) {
4895 return self.fail("TODO: saturating arithmetic for integers with bitsize '{d}'", .{int_info.bits});
4896 }
4897
4898 if (is_signed) {
4899 return signedSat(self, lhs, rhs, ty, op);
4900 }
4901
4902 const wasm_bits = toWasmBits(int_info.bits).?;
4903 const bin_result = try self.binOp(lhs, rhs, ty, op);
4904 if (wasm_bits != int_info.bits and op == .add) {
4905 const val: u64 = @intCast(u64, (@as(u65, 1) << @intCast(u7, int_info.bits)) - 1);
4906 const imm_val = switch (wasm_bits) {
4907 32 => WValue{ .imm32 = @intCast(u32, val) },
4908 64 => WValue{ .imm64 = val },
4909 else => unreachable,
4910 };
4911
4912 const cmp_result = try self.cmp(bin_result, imm_val, ty, .lt);
4913 try self.emitWValue(bin_result);
4914 try self.emitWValue(imm_val);
4915 try self.emitWValue(cmp_result);
4916 } else {
4917 const cmp_result = try self.cmp(bin_result, lhs, ty, if (op == .add) .lt else .gt);
4918 switch (wasm_bits) {
4919 32 => try self.addImm32(if (op == .add) @as(i32, -1) else 0),
4920 64 => try self.addImm64(if (op == .add) @bitCast(u64, @as(i64, -1)) else 0),
4921 else => unreachable,
4922 }
4923 try self.emitWValue(bin_result);
4924 try self.emitWValue(cmp_result);
4925 }
4926
4927 try self.addTag(.select);
4928 const result = try self.allocLocal(ty);
4929 try self.addLabel(.local_set, result.local);
4930 return result;
4931}
4932
4933fn signedSat(self: *Self, lhs_operand: WValue, rhs_operand: WValue, ty: Type, op: Op) InnerError!WValue {
4934 const int_info = ty.intInfo(self.target);
4935 const wasm_bits = toWasmBits(int_info.bits).?;
4936 const is_wasm_bits = wasm_bits == int_info.bits;
4937
4938 const lhs = if (!is_wasm_bits) try self.signAbsValue(lhs_operand, ty) else lhs_operand;
4939 const rhs = if (!is_wasm_bits) try self.signAbsValue(rhs_operand, ty) else rhs_operand;
4940
4941 const max_val: u64 = @intCast(u64, (@as(u65, 1) << @intCast(u7, int_info.bits - 1)) - 1);
4942 const min_val: i64 = (-@intCast(i64, @intCast(u63, max_val))) - 1;
4943 const max_wvalue = switch (wasm_bits) {
4944 32 => WValue{ .imm32 = @truncate(u32, max_val) },
4945 64 => WValue{ .imm64 = max_val },
4946 else => unreachable,
4947 };
4948 const min_wvalue = switch (wasm_bits) {
4949 32 => WValue{ .imm32 = @bitCast(u32, @truncate(i32, min_val)) },
4950 64 => WValue{ .imm64 = @bitCast(u64, min_val) },
4951 else => unreachable,
4952 };
4953
4954 const bin_result = try self.binOp(lhs, rhs, ty, op);
4955 if (!is_wasm_bits) {
4956 const cmp_result_lt = try self.cmp(bin_result, max_wvalue, ty, .lt);
4957 try self.emitWValue(bin_result);
4958 try self.emitWValue(max_wvalue);
4959 try self.emitWValue(cmp_result_lt);
4960 try self.addTag(.select);
4961 try self.addLabel(.local_set, bin_result.local); // re-use local
4962
4963 const cmp_result_gt = try self.cmp(bin_result, min_wvalue, ty, .gt);
4964 try self.emitWValue(bin_result);
4965 try self.emitWValue(min_wvalue);
4966 try self.emitWValue(cmp_result_gt);
4967 try self.addTag(.select);
4968 try self.addLabel(.local_set, bin_result.local); // re-use local
4969 return self.wrapOperand(bin_result, ty);
4970 } else {
4971 const zero = switch (wasm_bits) {
4972 32 => WValue{ .imm32 = 0 },
4973 64 => WValue{ .imm64 = 0 },
4974 else => unreachable,
4975 };
4976 const cmp_bin_result = try self.cmp(bin_result, lhs, ty, .lt);
4977 const cmp_zero_result = try self.cmp(rhs, zero, ty, if (op == .add) .lt else .gt);
4978 const xor = try self.binOp(cmp_zero_result, cmp_bin_result, Type.u32, .xor); // comparisons always return i32, so provide u32 as type to xor.
4979 const cmp_bin_zero_result = try self.cmp(bin_result, zero, ty, .lt);
4980 try self.emitWValue(max_wvalue);
4981 try self.emitWValue(min_wvalue);
4982 try self.emitWValue(cmp_bin_zero_result);
4983 try self.addTag(.select);
4984 try self.emitWValue(bin_result);
4985 try self.emitWValue(xor);
4986 try self.addTag(.select);
4987 try self.addLabel(.local_set, bin_result.local); // re-use local
4988 return bin_result;
4989 }
4990}
4991
4992fn airShlSat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
4993 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
4994
4995 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
4996 const ty = self.air.typeOfIndex(inst);
4997 const int_info = ty.intInfo(self.target);
4998 const is_signed = int_info.signedness == .signed;
4999 if (int_info.bits > 64) {
5000 return self.fail("TODO: Saturating shifting left for integers with bitsize '{d}'", .{int_info.bits});
5001 }
5002
5003 const lhs = try self.resolveInst(bin_op.lhs);
5004 const rhs = try self.resolveInst(bin_op.rhs);
5005 const wasm_bits = toWasmBits(int_info.bits).?;
5006 const result = try self.allocLocal(ty);
5007
5008 if (wasm_bits == int_info.bits) {
5009 const shl = try self.binOp(lhs, rhs, ty, .shl);
5010 const shr = try self.binOp(shl, rhs, ty, .shr);
5011 const cmp_result = try self.cmp(lhs, shr, ty, .neq);
5012
5013 switch (wasm_bits) {
5014 32 => blk: {
5015 if (!is_signed) {
5016 try self.addImm32(-1);
5017 break :blk;
5018 }
5019 const less_than_zero = try self.cmp(lhs, .{ .imm32 = 0 }, ty, .lt);
5020 try self.addImm32(std.math.minInt(i32));
5021 try self.addImm32(std.math.maxInt(i32));
5022 try self.emitWValue(less_than_zero);
5023 try self.addTag(.select);
5024 },
5025 64 => blk: {
5026 if (!is_signed) {
5027 try self.addImm64(@bitCast(u64, @as(i64, -1)));
5028 break :blk;
5029 }
5030 const less_than_zero = try self.cmp(lhs, .{ .imm64 = 0 }, ty, .lt);
5031 try self.addImm64(@bitCast(u64, @as(i64, std.math.minInt(i64))));
5032 try self.addImm64(@bitCast(u64, @as(i64, std.math.maxInt(i64))));
5033 try self.emitWValue(less_than_zero);
5034 try self.addTag(.select);
5035 },
5036 else => unreachable,
5037 }
5038 try self.emitWValue(shl);
5039 try self.emitWValue(cmp_result);
5040 try self.addTag(.select);
5041 try self.addLabel(.local_set, result.local);
5042 return result;
5043 } else {
5044 const shift_size = wasm_bits - int_info.bits;
5045 const shift_value = switch (wasm_bits) {
5046 32 => WValue{ .imm32 = shift_size },
5047 64 => WValue{ .imm64 = shift_size },
5048 else => unreachable,
5049 };
5050
5051 const shl_res = try self.binOp(lhs, shift_value, ty, .shl);
5052 const shl = try self.binOp(shl_res, rhs, ty, .shl);
5053 const shr = try self.binOp(shl, rhs, ty, .shr);
5054 const cmp_result = try self.cmp(shl_res, shr, ty, .neq);
5055
5056 switch (wasm_bits) {
5057 32 => blk: {
5058 if (!is_signed) {
5059 try self.addImm32(-1);
5060 break :blk;
5061 }
5062
5063 const less_than_zero = try self.cmp(shl_res, .{ .imm32 = 0 }, ty, .lt);
5064 try self.addImm32(std.math.minInt(i32));
5065 try self.addImm32(std.math.maxInt(i32));
5066 try self.emitWValue(less_than_zero);
5067 try self.addTag(.select);
5068 },
5069 64 => blk: {
5070 if (!is_signed) {
5071 try self.addImm64(@bitCast(u64, @as(i64, -1)));
5072 break :blk;
5073 }
5074
5075 const less_than_zero = try self.cmp(shl_res, .{ .imm64 = 0 }, ty, .lt);
5076 try self.addImm64(@bitCast(u64, @as(i64, std.math.minInt(i64))));
5077 try self.addImm64(@bitCast(u64, @as(i64, std.math.maxInt(i64))));
5078 try self.emitWValue(less_than_zero);
5079 try self.addTag(.select);
5080 },
5081 else => unreachable,
5082 }
5083 try self.emitWValue(shl);
5084 try self.emitWValue(cmp_result);
5085 try self.addTag(.select);
5086 try self.addLabel(.local_set, result.local);
5087 const shift_result = try self.binOp(result, shift_value, ty, .shr);
5088 if (is_signed) {
5089 return self.wrapOperand(shift_result, ty);
5090 }
5091 return shift_result;
5092 }
5093}
test/behavior/saturating_arithmetic.zig+52-10
...@@ -5,7 +5,6 @@ const maxInt = std.math.maxInt;...@@ -5,7 +5,6 @@ const maxInt = std.math.maxInt;
5const expect = std.testing.expect;5const expect = std.testing.expect;
66
7test "saturating add" {7test "saturating add" {
8 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
9 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO8 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
10 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO9 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
11 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO10 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
...@@ -20,8 +19,6 @@ test "saturating add" {...@@ -20,8 +19,6 @@ test "saturating add" {
20 try testSatAdd(i2, 1, -1, 0);19 try testSatAdd(i2, 1, -1, 0);
21 try testSatAdd(i2, -1, -1, -2);20 try testSatAdd(i2, -1, -1, -2);
22 try testSatAdd(i64, maxInt(i64), 1, maxInt(i64));21 try testSatAdd(i64, maxInt(i64), 1, maxInt(i64));
23 try testSatAdd(i128, maxInt(i128), -maxInt(i128), 0);
24 try testSatAdd(i128, minInt(i128), maxInt(i128), -1);
25 try testSatAdd(i8, 127, 127, 127);22 try testSatAdd(i8, 127, 127, 127);
26 try testSatAdd(u2, 0, 0, 0);23 try testSatAdd(u2, 0, 0, 0);
27 try testSatAdd(u2, 0, 1, 1);24 try testSatAdd(u2, 0, 1, 1);
...@@ -29,7 +26,6 @@ test "saturating add" {...@@ -29,7 +26,6 @@ test "saturating add" {
29 try testSatAdd(u8, 255, 255, 255);26 try testSatAdd(u8, 255, 255, 255);
30 try testSatAdd(u2, 3, 2, 3);27 try testSatAdd(u2, 3, 2, 3);
31 try testSatAdd(u3, 7, 1, 7);28 try testSatAdd(u3, 7, 1, 7);
32 try testSatAdd(u128, maxInt(u128), 1, maxInt(u128));
33 }29 }
3430
35 fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {31 fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {
...@@ -54,12 +50,36 @@ test "saturating add" {...@@ -54,12 +50,36 @@ test "saturating add" {
54 comptime try S.testSatAdd(comptime_int, 7, -593423721213448152027139550640105366508, -593423721213448152027139550640105366501);50 comptime try S.testSatAdd(comptime_int, 7, -593423721213448152027139550640105366508, -593423721213448152027139550640105366501);
55}51}
5652
57test "saturating subtraction" {53test "saturating add 128bit" {
58 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO54 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
59 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO55 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
60 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO56 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
61 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO57 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
62 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO58 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
59 const S = struct {
60 fn doTheTest() !void {
61 try testSatAdd(i128, maxInt(i128), -maxInt(i128), 0);
62 try testSatAdd(i128, minInt(i128), maxInt(i128), -1);
63 try testSatAdd(u128, maxInt(u128), 1, maxInt(u128));
64 }
65 fn testSatAdd(comptime T: type, lhs: T, rhs: T, expected: T) !void {
66 try expect((lhs +| rhs) == expected);
67
68 var x = lhs;
69 x +|= rhs;
70 try expect(x == expected);
71 }
72 };
73
74 try S.doTheTest();
75 comptime try S.doTheTest();
76}
77
78test "saturating subtraction" {
79 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
80 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
81 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
82 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
6383
64 const S = struct {84 const S = struct {
65 fn doTheTest() !void {85 fn doTheTest() !void {
...@@ -71,14 +91,11 @@ test "saturating subtraction" {...@@ -71,14 +91,11 @@ test "saturating subtraction" {
71 try testSatSub(i2, 1, -1, 1);91 try testSatSub(i2, 1, -1, 1);
72 try testSatSub(i2, -2, -2, 0);92 try testSatSub(i2, -2, -2, 0);
73 try testSatSub(i64, minInt(i64), 1, minInt(i64));93 try testSatSub(i64, minInt(i64), 1, minInt(i64));
74 try testSatSub(i128, maxInt(i128), -1, maxInt(i128));
75 try testSatSub(i128, minInt(i128), -maxInt(i128), -1);
76 try testSatSub(u2, 0, 0, 0);94 try testSatSub(u2, 0, 0, 0);
77 try testSatSub(u2, 0, 1, 0);95 try testSatSub(u2, 0, 1, 0);
78 try testSatSub(u5, 0, 31, 0);96 try testSatSub(u5, 0, 31, 0);
79 try testSatSub(u8, 10, 3, 7);97 try testSatSub(u8, 10, 3, 7);
80 try testSatSub(u8, 0, 255, 0);98 try testSatSub(u8, 0, 255, 0);
81 try testSatSub(u128, 0, maxInt(u128), 0);
82 }99 }
83100
84 fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {101 fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {
...@@ -103,6 +120,33 @@ test "saturating subtraction" {...@@ -103,6 +120,33 @@ test "saturating subtraction" {
103 comptime try S.testSatSub(comptime_int, 7, -593423721213448152027139550640105366508, 593423721213448152027139550640105366515);120 comptime try S.testSatSub(comptime_int, 7, -593423721213448152027139550640105366508, 593423721213448152027139550640105366515);
104}121}
105122
123test "saturating subtraction 128bit" {
124 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
125 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
126 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
127 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
128 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
129
130 const S = struct {
131 fn doTheTest() !void {
132 try testSatSub(i128, maxInt(i128), -1, maxInt(i128));
133 try testSatSub(i128, minInt(i128), -maxInt(i128), -1);
134 try testSatSub(u128, 0, maxInt(u128), 0);
135 }
136
137 fn testSatSub(comptime T: type, lhs: T, rhs: T, expected: T) !void {
138 try expect((lhs -| rhs) == expected);
139
140 var x = lhs;
141 x -|= rhs;
142 try expect(x == expected);
143 }
144 };
145
146 try S.doTheTest();
147 comptime try S.doTheTest();
148}
149
106test "saturating multiplication" {150test "saturating multiplication" {
107 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO151 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
108 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO152 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
...@@ -153,7 +197,6 @@ test "saturating multiplication" {...@@ -153,7 +197,6 @@ test "saturating multiplication" {
153}197}
154198
155test "saturating shift-left" {199test "saturating shift-left" {
156 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
157 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO200 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
158 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO201 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
159 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO202 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
...@@ -193,7 +236,6 @@ test "saturating shift-left" {...@@ -193,7 +236,6 @@ test "saturating shift-left" {
193}236}
194237
195test "saturating shl uses the LHS type" {238test "saturating shl uses the LHS type" {
196 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
197 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO239 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
198 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO240 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
199 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO241 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO