| ... | @@ -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, |
| 1433 | | 1433 | |
| 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 { |
| 1583 | | 1586 | |
| 1584 | .memcpy => self.airMemcpy(inst), | 1587 | .memcpy => self.airMemcpy(inst), |
| 1585 | | 1588 | |
| 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 | |
| | 4882 | fn 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 | |
| | 4933 | fn 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 | |
| | 4992 | fn 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 | } |