| ... | @@ -1452,7 +1452,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { | ... | @@ -1452,7 +1452,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1452 | | 1452 | |
| 1453 | .add_with_overflow => self.airAddSubWithOverflow(inst, .add), | 1453 | .add_with_overflow => self.airAddSubWithOverflow(inst, .add), |
| 1454 | .sub_with_overflow => self.airAddSubWithOverflow(inst, .sub), | 1454 | .sub_with_overflow => self.airAddSubWithOverflow(inst, .sub), |
| 1455 | .shl_with_overflow => self.airBinOpOverflow(inst, .shl), | 1455 | .shl_with_overflow => self.airShlWithOverflow(inst), |
| 1456 | .mul_with_overflow => self.airMulWithOverflow(inst), | 1456 | .mul_with_overflow => self.airMulWithOverflow(inst), |
| 1457 | | 1457 | |
| 1458 | .clz => self.airClz(inst), | 1458 | .clz => self.airClz(inst), |
| ... | @@ -3941,115 +3941,6 @@ fn airPtrSliceFieldPtr(self: *Self, inst: Air.Inst.Index, offset: u32) InnerErro | ... | @@ -3941,115 +3941,6 @@ fn airPtrSliceFieldPtr(self: *Self, inst: Air.Inst.Index, offset: u32) InnerErro |
| 3941 | return self.buildPointerOffset(slice_ptr, offset, .new); | 3941 | return self.buildPointerOffset(slice_ptr, offset, .new); |
| 3942 | } | 3942 | } |
| 3943 | | 3943 | |
| 3944 | fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { | | |
| 3945 | if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; | | |
| 3946 | | | |
| 3947 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | | |
| 3948 | const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; | | |
| 3949 | const lhs = try self.resolveInst(extra.lhs); | | |
| 3950 | const rhs = try self.resolveInst(extra.rhs); | | |
| 3951 | const lhs_ty = self.air.typeOf(extra.lhs); | | |
| 3952 | | | |
| 3953 | if (lhs_ty.zigTypeTag() == .Vector) { | | |
| 3954 | return self.fail("TODO: Implement overflow arithmetic for vectors", .{}); | | |
| 3955 | } | | |
| 3956 | | | |
| 3957 | // We store the bit if it's overflowed or not in this. As it's zero-initialized | | |
| 3958 | // we only need to update it if an overflow (or underflow) occured. | | |
| 3959 | const overflow_bit = try self.allocLocal(Type.initTag(.u1)); | | |
| 3960 | const int_info = lhs_ty.intInfo(self.target); | | |
| 3961 | const wasm_bits = toWasmBits(int_info.bits) orelse { | | |
| 3962 | return self.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits}); | | |
| 3963 | }; | | |
| 3964 | | | |
| 3965 | const zero = switch (wasm_bits) { | | |
| 3966 | 32 => WValue{ .imm32 = 0 }, | | |
| 3967 | 64 => WValue{ .imm64 = 0 }, | | |
| 3968 | else => unreachable, | | |
| 3969 | }; | | |
| 3970 | const int_max = (@as(u65, 1) << @intCast(u7, int_info.bits - @boolToInt(int_info.signedness == .signed))) - 1; | | |
| 3971 | const int_max_wvalue = switch (wasm_bits) { | | |
| 3972 | 32 => WValue{ .imm32 = @intCast(u32, int_max) }, | | |
| 3973 | 64 => WValue{ .imm64 = @intCast(u64, int_max) }, | | |
| 3974 | else => unreachable, | | |
| 3975 | }; | | |
| 3976 | const int_min = if (int_info.signedness == .unsigned) | | |
| 3977 | @as(i64, 0) | | |
| 3978 | else | | |
| 3979 | -@as(i64, 1) << @intCast(u6, int_info.bits - 1); | | |
| 3980 | const int_min_wvalue = switch (wasm_bits) { | | |
| 3981 | 32 => WValue{ .imm32 = @bitCast(u32, @intCast(i32, int_min)) }, | | |
| 3982 | 64 => WValue{ .imm64 = @bitCast(u64, int_min) }, | | |
| 3983 | else => unreachable, | | |
| 3984 | }; | | |
| 3985 | | | |
| 3986 | if (int_info.signedness == .unsigned and op == .add) { | | |
| 3987 | const diff = try self.binOp(int_max_wvalue, lhs, lhs_ty, .sub); | | |
| 3988 | const cmp_res = try self.cmp(rhs, diff, lhs_ty, .gt); | | |
| 3989 | try self.emitWValue(cmp_res); | | |
| 3990 | try self.addLabel(.local_set, overflow_bit.local); | | |
| 3991 | } else if (op == .sub) { | | |
| 3992 | const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt); | | |
| 3993 | try self.emitWValue(cmp_res); | | |
| 3994 | try self.addLabel(.local_set, overflow_bit.local); | | |
| 3995 | } else if (int_info.signedness == .signed and op != .shl) { | | |
| 3996 | // for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow. | | |
| 3997 | // We first create an outer block, where we handle overflow. | | |
| 3998 | // Then we create an inner block, where underflow is handled. | | |
| 3999 | try self.startBlock(.block, wasm.block_empty); | | |
| 4000 | try self.startBlock(.block, wasm.block_empty); | | |
| 4001 | { | | |
| 4002 | try self.emitWValue(lhs); | | |
| 4003 | const cmp_result = try self.cmp(lhs, zero, lhs_ty, .lt); | | |
| 4004 | try self.emitWValue(cmp_result); | | |
| 4005 | } | | |
| 4006 | try self.addLabel(.br_if, 0); // break to outer block, and handle underflow | | |
| 4007 | | | |
| 4008 | // handle overflow | | |
| 4009 | { | | |
| 4010 | const diff = try self.binOp(int_max_wvalue, lhs, lhs_ty, .sub); | | |
| 4011 | const cmp_res = try self.cmp(rhs, diff, lhs_ty, if (op == .add) .gt else .lt); | | |
| 4012 | try self.emitWValue(cmp_res); | | |
| 4013 | try self.addLabel(.local_set, overflow_bit.local); | | |
| 4014 | } | | |
| 4015 | try self.addLabel(.br, 1); // break from blocks, and continue regular flow. | | |
| 4016 | try self.endBlock(); | | |
| 4017 | | | |
| 4018 | // handle underflow | | |
| 4019 | { | | |
| 4020 | const diff = try self.binOp(int_min_wvalue, lhs, lhs_ty, .sub); | | |
| 4021 | const cmp_res = try self.cmp(rhs, diff, lhs_ty, if (op == .add) .lt else .gt); | | |
| 4022 | try self.emitWValue(cmp_res); | | |
| 4023 | try self.addLabel(.local_set, overflow_bit.local); | | |
| 4024 | } | | |
| 4025 | try self.endBlock(); | | |
| 4026 | } | | |
| 4027 | | | |
| 4028 | const bin_op = if (op == .shl) blk: { | | |
| 4029 | const tmp_val = try self.binOp(lhs, rhs, lhs_ty, op); | | |
| 4030 | const cmp_res = try self.cmp(tmp_val, int_max_wvalue, lhs_ty, .gt); | | |
| 4031 | try self.emitWValue(cmp_res); | | |
| 4032 | try self.addLabel(.local_set, overflow_bit.local); | | |
| 4033 | | | |
| 4034 | try self.emitWValue(tmp_val); | | |
| 4035 | try self.emitWValue(int_max_wvalue); | | |
| 4036 | switch (wasm_bits) { | | |
| 4037 | 32 => try self.addTag(.i32_and), | | |
| 4038 | 64 => try self.addTag(.i64_and), | | |
| 4039 | else => unreachable, | | |
| 4040 | } | | |
| 4041 | try self.addLabel(.local_set, tmp_val.local); | | |
| 4042 | break :blk tmp_val; | | |
| 4043 | } else try self.wrapBinOp(lhs, rhs, lhs_ty, op); | | |
| 4044 | | | |
| 4045 | const result_ptr = try self.allocStack(self.air.typeOfIndex(inst)); | | |
| 4046 | try self.store(result_ptr, bin_op, lhs_ty, 0); | | |
| 4047 | const offset = @intCast(u32, lhs_ty.abiSize(self.target)); | | |
| 4048 | try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset); | | |
| 4049 | | | |
| 4050 | return result_ptr; | | |
| 4051 | } | | |
| 4052 | | | |
| 4053 | fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { | 3944 | fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 4054 | assert(op == .add or op == .sub); | 3945 | assert(op == .add or op == .sub); |
| 4055 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | 3946 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| ... | @@ -4065,13 +3956,9 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W | ... | @@ -4065,13 +3956,9 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W |
| 4065 | const int_info = lhs_ty.intInfo(self.target); | 3956 | const int_info = lhs_ty.intInfo(self.target); |
| 4066 | const is_signed = int_info.signedness == .signed; | 3957 | const is_signed = int_info.signedness == .signed; |
| 4067 | const wasm_bits = toWasmBits(int_info.bits) orelse { | 3958 | const wasm_bits = toWasmBits(int_info.bits) orelse { |
| 4068 | return self.fail("TODO: Implement sub_with_overflow for integer bitsize: {d}", .{int_info.bits}); | 3959 | return self.fail("TODO: Implement {{add/sub}}_with_overflow for integer bitsize: {d}", .{int_info.bits}); |
| 4069 | }; | 3960 | }; |
| 4070 | | 3961 | |
| 4071 | if (wasm_bits == 128) { | | |
| 4072 | return self.fail("TODO: Implement sub_with_overflow for 128 bit integers", .{}); | | |
| 4073 | } | | |
| 4074 | | | |
| 4075 | const zero = switch (wasm_bits) { | 3962 | const zero = switch (wasm_bits) { |
| 4076 | 32 => WValue{ .imm32 = 0 }, | 3963 | 32 => WValue{ .imm32 = 0 }, |
| 4077 | 64 => WValue{ .imm64 = 0 }, | 3964 | 64 => WValue{ .imm64 = 0 }, |
| ... | @@ -4123,6 +4010,53 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W | ... | @@ -4123,6 +4010,53 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W |
| 4123 | return result_ptr; | 4010 | return result_ptr; |
| 4124 | } | 4011 | } |
| 4125 | | 4012 | |
| | 4013 | fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| | 4014 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| | 4015 | const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; |
| | 4016 | const lhs = try self.resolveInst(extra.lhs); |
| | 4017 | const rhs = try self.resolveInst(extra.rhs); |
| | 4018 | const lhs_ty = self.air.typeOf(extra.lhs); |
| | 4019 | |
| | 4020 | if (lhs_ty.zigTypeTag() == .Vector) { |
| | 4021 | return self.fail("TODO: Implement overflow arithmetic for vectors", .{}); |
| | 4022 | } |
| | 4023 | |
| | 4024 | const int_info = lhs_ty.intInfo(self.target); |
| | 4025 | const is_signed = int_info.signedness == .signed; |
| | 4026 | const wasm_bits = toWasmBits(int_info.bits) orelse { |
| | 4027 | return self.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits}); |
| | 4028 | }; |
| | 4029 | |
| | 4030 | const shl = try self.binOp(lhs, rhs, lhs_ty, .shl); |
| | 4031 | const result = if (wasm_bits != int_info.bits) blk: { |
| | 4032 | break :blk try self.wrapOperand(shl, lhs_ty); |
| | 4033 | } else shl; |
| | 4034 | |
| | 4035 | const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: { |
| | 4036 | const shift_amt = wasm_bits - int_info.bits; |
| | 4037 | const shift_val = switch (wasm_bits) { |
| | 4038 | 32 => WValue{ .imm32 = shift_amt }, |
| | 4039 | 64 => WValue{ .imm64 = shift_amt }, |
| | 4040 | else => unreachable, |
| | 4041 | }; |
| | 4042 | |
| | 4043 | const secondary_shl = try self.binOp(shl, shift_val, lhs_ty, .shl); |
| | 4044 | const initial_shr = try self.binOp(secondary_shl, shift_val, lhs_ty, .shr); |
| | 4045 | const shr = try self.wrapBinOp(initial_shr, rhs, lhs_ty, .shr); |
| | 4046 | break :blk try self.cmp(lhs, shr, lhs_ty, .neq); |
| | 4047 | } else blk: { |
| | 4048 | const shr = try self.binOp(result, rhs, lhs_ty, .shr); |
| | 4049 | break :blk try self.cmp(lhs, shr, lhs_ty, .neq); |
| | 4050 | }; |
| | 4051 | |
| | 4052 | const result_ptr = try self.allocStack(self.air.typeOfIndex(inst)); |
| | 4053 | try self.store(result_ptr, result, lhs_ty, 0); |
| | 4054 | const offset = @intCast(u32, lhs_ty.abiSize(self.target)); |
| | 4055 | try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset); |
| | 4056 | |
| | 4057 | return result_ptr; |
| | 4058 | } |
| | 4059 | |
| 4126 | fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 4060 | fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 4127 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; | 4061 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| 4128 | const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; | 4062 | const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; |