authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-05-13 19:51:14+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-16 13:55:26-07:00
log0a2d3d41556a3bbe836dafa5321439fa6da9b464
treea9b8ee5dfd6bd4d9d32c5b8a8ec36c0c0a4d6f03
parentb94d165b69f2743d779a04b1719382207e341596

wasm: Improve overflow add/sub for ints <= 64bits

The implementation for add_with_overflow and sub_with_overflow is now a lot more robust and takes account for signed integers and arbitrary integer bitsizes. The final output is equal to that of the LLVM backend.

1 files changed, 76 insertions(+), 3 deletions(-)

src/arch/wasm/CodeGen.zig+76-3
......@@ -1450,8 +1450,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
14501450 .min => self.airMaxMin(inst, .min),
14511451 .mul_add => self.airMulAdd(inst),
14521452
1453 .add_with_overflow => self.airBinOpOverflow(inst, .add),
1454 .sub_with_overflow => self.airBinOpOverflow(inst, .sub),
1453 .add_with_overflow => self.airAddSubWithOverflow(inst, .add),
1454 .sub_with_overflow => self.airAddSubWithOverflow(inst, .sub),
14551455 .shl_with_overflow => self.airBinOpOverflow(inst, .shl),
14561456 .mul_with_overflow => self.airMulWithOverflow(inst),
14571457
......@@ -3988,7 +3988,7 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
39883988 const cmp_res = try self.cmp(rhs, diff, lhs_ty, .gt);
39893989 try self.emitWValue(cmp_res);
39903990 try self.addLabel(.local_set, overflow_bit.local);
3991 } else if (int_info.signedness == .unsigned and op == .sub) {
3991 } else if (op == .sub) {
39923992 const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt);
39933993 try self.emitWValue(cmp_res);
39943994 try self.addLabel(.local_set, overflow_bit.local);
......@@ -4050,6 +4050,79 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
40504050 return result_ptr;
40514051}
40524052
4053fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
4054 assert(op == .add or op == .sub);
4055 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
4056 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
4057 const lhs_op = try self.resolveInst(extra.lhs);
4058 const rhs_op = try self.resolveInst(extra.rhs);
4059 const lhs_ty = self.air.typeOf(extra.lhs);
4060
4061 if (lhs_ty.zigTypeTag() == .Vector) {
4062 return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
4063 }
4064
4065 const int_info = lhs_ty.intInfo(self.target);
4066 const is_signed = int_info.signedness == .signed;
4067 const wasm_bits = toWasmBits(int_info.bits) orelse {
4068 return self.fail("TODO: Implement sub_with_overflow for integer bitsize: {d}", .{int_info.bits});
4069 };
4070
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) {
4076 32 => WValue{ .imm32 = 0 },
4077 64 => WValue{ .imm64 = 0 },
4078 else => unreachable,
4079 };
4080 const shift_amt = wasm_bits - int_info.bits;
4081 const shift_val = switch (wasm_bits) {
4082 32 => WValue{ .imm32 = shift_amt },
4083 64 => WValue{ .imm64 = shift_amt },
4084 else => unreachable,
4085 };
4086
4087 // for signed integers, we first apply signed shifts by the difference in bits
4088 // to get the signed value, as we store it internally as 2's complement.
4089 const lhs = if (wasm_bits != int_info.bits and is_signed) blk: {
4090 const shl = try self.binOp(lhs_op, shift_val, lhs_ty, .shl);
4091 break :blk try self.binOp(shl, shift_val, lhs_ty, .shr);
4092 } else lhs_op;
4093 const rhs = if (wasm_bits != int_info.bits and is_signed) blk: {
4094 const shl = try self.binOp(rhs_op, shift_val, lhs_ty, .shl);
4095 break :blk try self.binOp(shl, shift_val, lhs_ty, .shr);
4096 } else rhs_op;
4097
4098 const bin_op = try self.binOp(lhs, rhs, lhs_ty, op);
4099 const result = if (wasm_bits != int_info.bits) blk: {
4100 break :blk try self.wrapOperand(bin_op, lhs_ty);
4101 } else bin_op;
4102
4103 const cmp_op: std.math.CompareOperator = if (op == .sub) .gt else .lt;
4104 const overflow_bit: WValue = if (is_signed) blk: {
4105 if (wasm_bits == int_info.bits) {
4106 const cmp_zero = try self.cmp(rhs, zero, lhs_ty, cmp_op);
4107 const lt = try self.cmp(bin_op, lhs, lhs_ty, .lt);
4108 break :blk try self.binOp(cmp_zero, lt, Type.u32, .xor); // result of cmp_zero and lt is always 32bit
4109 }
4110 const shl = try self.binOp(bin_op, shift_val, lhs_ty, .shl);
4111 const shr = try self.binOp(shl, shift_val, lhs_ty, .shr);
4112 break :blk try self.cmp(shr, bin_op, lhs_ty, .neq);
4113 } else if (wasm_bits == int_info.bits)
4114 try self.cmp(bin_op, lhs, lhs_ty, cmp_op)
4115 else
4116 try self.cmp(bin_op, result, lhs_ty, .neq);
4117
4118 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
4119 try self.store(result_ptr, result, lhs_ty, 0);
4120 const offset = @intCast(u32, lhs_ty.abiSize(self.target));
4121 try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);
4122
4123 return result_ptr;
4124}
4125
40534126fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
40544127 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
40554128 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;