From ad4f0dda8b8c270594ed24a27c808f8bd43924bf Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 7 May 2022 17:04:19 +0200 Subject: [PATCH] wasm: Fix `@floatToInt` and split overflow ops As we now store negative signed integers as two's complement, we must also ensure that when truncating a float, its value is wrapped around the integer's size. This also splits `@mulWithOverflow` into its own function to make the code more maintainable and reduce branching. --- src/arch/wasm/CodeGen.zig | 260 ++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 140 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 306c27a4a048561c41112be1802d7dead906f0ed..9318b4ecca3f4e6f45060dc5d5a7b957cd10c315 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1424,7 +1424,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .add_with_overflow => self.airBinOpOverflow(inst, .add), .sub_with_overflow => self.airBinOpOverflow(inst, .sub), .shl_with_overflow => self.airBinOpOverflow(inst, .shl), - .mul_with_overflow => self.airBinOpOverflow(inst, .mul), + .mul_with_overflow => self.airMulWithOverflow(inst), .clz => self.airClz(inst), .ctz => self.airCtz(inst), @@ -1927,7 +1927,14 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - return self.wrapBinOp(lhs, rhs, self.air.typeOf(bin_op.lhs), op); + const ty = self.air.typeOf(bin_op.lhs); + if (ty.zigTypeTag() == .Vector) { + return self.fail("TODO: Implement wrapping arithmetic for vectors", .{}); + } else if (ty.abiSize(self.target) > 8) { + return self.fail("TODO: Implement wrapping arithmetic for bitsize > 64", .{}); + } + + return self.wrapBinOp(lhs, rhs, ty, op); } fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue { @@ -1941,31 +1948,8 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError }); try self.addTag(Mir.Inst.Tag.fromOpcode(opcode)); const bin_local = try self.allocLocal(ty); - - const int_info = ty.intInfo(self.target); - const bitsize = int_info.bits; - const is_signed = int_info.signedness == .signed; - // if target type bitsize is x < 32 and 32 > x < 64, we perform - // result & ((1< 64) { - return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{}); - } - - // save the result in a temporary try self.addLabel(.local_set, bin_local.local); - return bin_local; + return self.wrapOperand(bin_local, ty); } /// Wraps an operand based on a given type's bitsize. @@ -2855,11 +2839,12 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const ty = self.air.getRefType(ty_op.ty); const operand = try self.resolveInst(ty_op.operand); - const ref_ty = self.air.typeOf(ty_op.operand); - if (ty.abiSize(self.target) > 8 or ref_ty.abiSize(self.target) > 8) { + const operand_ty = self.air.typeOf(ty_op.operand); + if (ty.abiSize(self.target) > 8 or operand_ty.abiSize(self.target) > 8) { return self.fail("todo Wasm intcast for bitsize > 64", .{}); } - return self.intcast(operand, ty, ref_ty); + + return self.intcast(operand, operand_ty, ty); } /// Upcasts or downcasts an integer based on the given and wanted types, @@ -3102,63 +3087,17 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { } fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { - if (self.liveness.isUnused(inst)) return WValue.none; + if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand = try self.resolveInst(ty_op.operand); - const op_ty = self.air.typeOf(ty_op.operand); - const int_info = self.air.getRefType(ty_op.ty).intInfo(self.target); + const wanted_ty = self.air.getRefType(ty_op.ty); + const int_info = wanted_ty.intInfo(self.target); const wanted_bits = int_info.bits; - const result = try self.allocLocal(self.air.getRefType(ty_op.ty)); - const op_bits = op_ty.intInfo(self.target).bits; - const wasm_bits = toWasmBits(wanted_bits) orelse + _ = toWasmBits(wanted_bits) orelse { return self.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{wanted_bits}); - - // Use wasm's instruction to wrap from 64bit to 32bit integer when possible - if (op_bits == 64 and wanted_bits == 32) { - try self.emitWValue(operand); - try self.addTag(.i32_wrap_i64); - try self.addLabel(.local_set, result.local); - return result; - } - - // Any other truncation must be done manually - if (int_info.signedness == .unsigned) { - const mask = (@as(u65, 1) << @intCast(u7, wanted_bits)) - 1; - try self.emitWValue(operand); - switch (wasm_bits) { - 32 => { - try self.addImm32(@bitCast(i32, @intCast(u32, mask))); - try self.addTag(.i32_and); - }, - 64 => { - try self.addImm64(@intCast(u64, mask)); - try self.addTag(.i64_and); - }, - else => unreachable, - } - } else { - const shift_bits = wasm_bits - wanted_bits; - try self.emitWValue(operand); - switch (wasm_bits) { - 32 => { - try self.addImm32(@bitCast(i16, shift_bits)); - try self.addTag(.i32_shl); - try self.addImm32(@bitCast(i16, shift_bits)); - try self.addTag(.i32_shr_s); - }, - 64 => { - try self.addImm64(shift_bits); - try self.addTag(.i64_shl); - try self.addImm64(shift_bits); - try self.addTag(.i64_shr_s); - }, - else => unreachable, - } - } - - try self.addLabel(.local_set, result.local); - return result; + }; + return self.wrapOperand(operand, wanted_ty); } fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { @@ -3448,7 +3387,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const result = try self.allocLocal(dest_ty); try self.addLabel(.local_set, result.local); - return result; + + return self.wrapOperand(result, dest_ty); } fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!WValue { @@ -3952,6 +3892,10 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue const rhs = try self.resolveInst(extra.rhs); const lhs_ty = self.air.typeOf(extra.lhs); + if (lhs_ty.zigTypeTag() == .Vector) { + return self.fail("TODO: Implement overflow arithmetic for vectors", .{}); + } + // We store the bit if it's overflowed or not in this. As it's zero-initialized // we only need to update it if an overflow (or underflow) occured. const overflow_bit = try self.allocLocal(Type.initTag(.u1)); @@ -3990,7 +3934,7 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt); try self.emitWValue(cmp_res); try self.addLabel(.local_set, overflow_bit.local); - } else if (int_info.signedness == .signed and op != .shl and op != .mul) { + } else if (int_info.signedness == .signed and op != .shl) { // for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow. // We first create an outer block, where we handle overflow. // Then we create an inner block, where underflow is handled. @@ -4038,63 +3982,6 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue } try self.addLabel(.local_set, tmp_val.local); break :blk tmp_val; - } else if (op == .mul) blk: { - // for 32 & 64 bitsize we calculate overflow - // differently. - if (int_info.bits == 32) { - const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64; - const lhs_upcast = try self.intcast(lhs, lhs_ty, new_ty); - const rhs_upcast = try self.intcast(rhs, lhs_ty, new_ty); - const bin_op = try self.binOp(lhs_upcast, rhs_upcast, new_ty, op); - if (int_info.signedness == .unsigned) { - const shr = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr); - const wrap = try self.intcast(shr, new_ty, lhs_ty); - const cmp_res = try self.cmp(wrap, zero, lhs_ty, .neq); - try self.emitWValue(cmp_res); - try self.addLabel(.local_set, overflow_bit.local); - break :blk try self.intcast(bin_op, new_ty, lhs_ty); - } else { - const down_cast = try self.intcast(bin_op, new_ty, lhs_ty); - const shr = try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr); - - const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr); - const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty); - const cmp_res = try self.cmp(down_shr_res, shr, lhs_ty, .neq); - try self.emitWValue(cmp_res); - try self.addLabel(.local_set, overflow_bit.local); - break :blk down_cast; - } - } else if (int_info.signedness == .signed) { - const shift_imm = if (wasm_bits == 32) - WValue{ .imm32 = wasm_bits - int_info.bits } - else - WValue{ .imm64 = wasm_bits - int_info.bits }; - - const lhs_shl = try self.binOp(lhs, shift_imm, lhs_ty, .shl); - const lhs_shr = try self.binOp(lhs_shl, shift_imm, lhs_ty, .shr); - const rhs_shl = try self.binOp(rhs, shift_imm, lhs_ty, .shl); - const rhs_shr = try self.binOp(rhs_shl, shift_imm, lhs_ty, .shr); - - const bin_op = try self.binOp(lhs_shr, rhs_shr, lhs_ty, op); - const shl = try self.binOp(bin_op, shift_imm, lhs_ty, .shl); - const shr = try self.binOp(shl, shift_imm, lhs_ty, .shr); - - const cmp_op = try self.cmp(shr, bin_op, lhs_ty, .neq); - try self.emitWValue(cmp_op); - try self.addLabel(.local_set, overflow_bit.local); - break :blk try self.wrapOperand(bin_op, lhs_ty); - } else { - const bin_op = try self.binOp(lhs, rhs, lhs_ty, op); - const shift_imm = if (wasm_bits == 32) - WValue{ .imm32 = int_info.bits } - else - WValue{ .imm64 = int_info.bits }; - const shr = try self.binOp(bin_op, shift_imm, lhs_ty, .shr); - const cmp_op = try self.cmp(shr, zero, lhs_ty, .neq); - try self.emitWValue(cmp_op); - try self.addLabel(.local_set, overflow_bit.local); - break :blk try self.wrapOperand(bin_op, lhs_ty); - } } else try self.wrapBinOp(lhs, rhs, lhs_ty, op); const result_ptr = try self.allocStack(self.air.typeOfIndex(inst)); @@ -4105,6 +3992,99 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue return result_ptr; } +fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; + const lhs = try self.resolveInst(extra.lhs); + const rhs = try self.resolveInst(extra.rhs); + const lhs_ty = self.air.typeOf(extra.lhs); + + if (lhs_ty.zigTypeTag() == .Vector) { + return self.fail("TODO: Implement overflow arithmetic for vectors", .{}); + } + + // We store the bit if it's overflowed or not in this. As it's zero-initialized + // we only need to update it if an overflow (or underflow) occured. + const overflow_bit = try self.allocLocal(Type.initTag(.u1)); + const int_info = lhs_ty.intInfo(self.target); + const wasm_bits = toWasmBits(int_info.bits) orelse { + return self.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits}); + }; + + if (wasm_bits == 64) { + return self.fail("TODO: Implement `@mulWithOverflow` for integer bitsize: {d}", .{int_info.bits}); + } + + const zero = switch (wasm_bits) { + 32 => WValue{ .imm32 = 0 }, + 64 => WValue{ .imm64 = 0 }, + else => unreachable, + }; + + // for 32 bit integers we upcast it to a 64bit integer + const bin_op = if (int_info.bits == 32) blk: { + const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64; + const lhs_upcast = try self.intcast(lhs, lhs_ty, new_ty); + const rhs_upcast = try self.intcast(rhs, lhs_ty, new_ty); + const bin_op = try self.binOp(lhs_upcast, rhs_upcast, new_ty, .mul); + if (int_info.signedness == .unsigned) { + const shr = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr); + const wrap = try self.intcast(shr, new_ty, lhs_ty); + const cmp_res = try self.cmp(wrap, zero, lhs_ty, .neq); + try self.emitWValue(cmp_res); + try self.addLabel(.local_set, overflow_bit.local); + break :blk try self.intcast(bin_op, new_ty, lhs_ty); + } else { + const down_cast = try self.intcast(bin_op, new_ty, lhs_ty); + const shr = try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr); + + const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr); + const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty); + const cmp_res = try self.cmp(down_shr_res, shr, lhs_ty, .neq); + try self.emitWValue(cmp_res); + try self.addLabel(.local_set, overflow_bit.local); + break :blk down_cast; + } + } else if (int_info.signedness == .signed) blk: { + const shift_imm = if (wasm_bits == 32) + WValue{ .imm32 = wasm_bits - int_info.bits } + else + WValue{ .imm64 = wasm_bits - int_info.bits }; + + const lhs_shl = try self.binOp(lhs, shift_imm, lhs_ty, .shl); + const lhs_shr = try self.binOp(lhs_shl, shift_imm, lhs_ty, .shr); + const rhs_shl = try self.binOp(rhs, shift_imm, lhs_ty, .shl); + const rhs_shr = try self.binOp(rhs_shl, shift_imm, lhs_ty, .shr); + + const bin_op = try self.binOp(lhs_shr, rhs_shr, lhs_ty, .mul); + const shl = try self.binOp(bin_op, shift_imm, lhs_ty, .shl); + const shr = try self.binOp(shl, shift_imm, lhs_ty, .shr); + + const cmp_op = try self.cmp(shr, bin_op, lhs_ty, .neq); + try self.emitWValue(cmp_op); + try self.addLabel(.local_set, overflow_bit.local); + break :blk try self.wrapOperand(bin_op, lhs_ty); + } else blk: { + const bin_op = try self.binOp(lhs, rhs, lhs_ty, .mul); + const shift_imm = if (wasm_bits == 32) + WValue{ .imm32 = int_info.bits } + else + WValue{ .imm64 = int_info.bits }; + const shr = try self.binOp(bin_op, shift_imm, lhs_ty, .shr); + const cmp_op = try self.cmp(shr, zero, lhs_ty, .neq); + try self.emitWValue(cmp_op); + try self.addLabel(.local_set, overflow_bit.local); + break :blk try self.wrapOperand(bin_op, lhs_ty); + }; + + const result_ptr = try self.allocStack(self.air.typeOfIndex(inst)); + try self.store(result_ptr, bin_op, lhs_ty, 0); + const offset = @intCast(u32, lhs_ty.abiSize(self.target)); + try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset); + + return result_ptr; +} + fn airMaxMin(self: *Self, inst: Air.Inst.Index, op: enum { max, min }) InnerError!WValue { if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; const bin_op = self.air.instructions.items(.data)[inst].bin_op; -- 2.54.0