From 383e6ffc7b9e22613f4c4b15ebdcd2bf487573d6 Mon Sep 17 00:00:00 2001 From: riverbl <94326797+riverbl@users.noreply.github.com> Date: Sun, 20 Aug 2023 11:39:07 +0100 Subject: [PATCH] Implement `@mod` and fix bugs with `divFloor` for wasm Implement lowering code for `@mod` on integers in the stage2 wasm backend Fix invalid wasm being produced for `@divFloor` on signed integers by the stage2 wasm backend --- src/arch/wasm/CodeGen.zig | 173 +++++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 49 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 1d13686d87..bad99e9f59 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1852,6 +1852,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { .bool_and => func.airBinOp(inst, .@"and"), .bool_or => func.airBinOp(inst, .@"or"), .rem => func.airBinOp(inst, .rem), + .mod => func.airMod(inst), .shl => func.airWrapBinOp(inst, .shl), .shl_exact => func.airBinOp(inst, .shl), .shl_sat => func.airShlSat(inst), @@ -2012,7 +2013,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { .frame_addr => func.airFrameAddress(inst), .mul_sat, - .mod, .assembly, .bit_reverse, .is_err_ptr, @@ -4151,7 +4151,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro // when we upcast from a smaller integer to larger // integers, we must get its absolute value similar to // i64_extend_i32_s instruction. - return func.signAbsValue(operand, given); + return func.signExtendInt(operand, given); } return func.wrapOperand(operand, wanted); } @@ -5649,13 +5649,13 @@ fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerErro // for signed integers, we first apply signed shifts by the difference in bits // to get the signed value, as we store it internally as 2's complement. var lhs = if (wasm_bits != int_info.bits and is_signed) blk: { - break :blk try (try func.signAbsValue(lhs_op, lhs_ty)).toLocal(func, lhs_ty); + break :blk try (try func.signExtendInt(lhs_op, lhs_ty)).toLocal(func, lhs_ty); } else lhs_op; var rhs = if (wasm_bits != int_info.bits and is_signed) blk: { - break :blk try (try func.signAbsValue(rhs_op, lhs_ty)).toLocal(func, lhs_ty); + break :blk try (try func.signExtendInt(rhs_op, lhs_ty)).toLocal(func, lhs_ty); } else rhs_op; - // in this case, we performed a signAbsValue which created a temporary local + // in this case, we performed a signExtendInt which created a temporary local // so let's free this so it can be re-used instead. // In the other case we do not want to free it, because that would free the // resolved instructions which may be referenced by other instructions. @@ -5677,7 +5677,7 @@ fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerErro const lt = try func.cmp(bin_op, lhs, lhs_ty, .lt); break :blk try func.binOp(cmp_zero, lt, Type.u32, .xor); } - const abs = try func.signAbsValue(bin_op, lhs_ty); + const abs = try func.signExtendInt(bin_op, lhs_ty); break :blk try func.cmp(abs, bin_op, lhs_ty, .neq); } else if (wasm_bits == int_info.bits) try func.cmp(bin_op, lhs, lhs_ty, cmp_op) @@ -5797,7 +5797,7 @@ fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: { // emit lhs to stack to we can keep 'wrapped' on the stack also try func.emitWValue(lhs); - const abs = try func.signAbsValue(shl, lhs_ty); + const abs = try func.signExtendInt(shl, lhs_ty); const wrapped = try func.wrapBinOp(abs, rhs_final, lhs_ty, .shr); break :blk try func.cmp(.{ .stack = {} }, wrapped, lhs_ty, .neq); } else blk: { @@ -5869,10 +5869,10 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { break :blk down_cast; } } else if (int_info.signedness == .signed and wasm_bits == 32) blk: { - const lhs_abs = try func.signAbsValue(lhs, lhs_ty); - const rhs_abs = try func.signAbsValue(rhs, lhs_ty); + const lhs_abs = try func.signExtendInt(lhs, lhs_ty); + const rhs_abs = try func.signExtendInt(rhs, lhs_ty); const bin_op = try (try func.binOp(lhs_abs, rhs_abs, lhs_ty, .mul)).toLocal(func, lhs_ty); - const mul_abs = try func.signAbsValue(bin_op, lhs_ty); + const mul_abs = try func.signExtendInt(bin_op, lhs_ty); _ = try func.cmp(mul_abs, bin_op, lhs_ty, .neq); try func.addLabel(.local_set, overflow_bit.local.value); break :blk try func.wrapOperand(bin_op, lhs_ty); @@ -6405,47 +6405,77 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { const rhs = try func.resolveInst(bin_op.rhs); if (ty.isUnsignedInt(mod)) { - const result = try (try func.binOp(lhs, rhs, ty, .div)).toLocal(func, ty); - return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); + _ = try func.binOp(lhs, rhs, ty, .div); } else if (ty.isSignedInt(mod)) { const int_bits = ty.intInfo(mod).bits; const wasm_bits = toWasmBits(int_bits) orelse { - return func.fail("TODO: `@divFloor` for signed integers larger than '{d}' bits", .{int_bits}); + return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); }; - const lhs_res = if (wasm_bits != int_bits) blk: { - break :blk try (try func.signAbsValue(lhs, ty)).toLocal(func, ty); - } else lhs; - const rhs_res = if (wasm_bits != int_bits) blk: { - break :blk try (try func.signAbsValue(rhs, ty)).toLocal(func, ty); - } else rhs; + + if (wasm_bits > 64) { + return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); + } + + const lhs_wasm = if (wasm_bits != int_bits) + try (try func.signExtendInt(lhs, ty)).toLocal(func, ty) + else + lhs; + + const rhs_wasm = if (wasm_bits != int_bits) + try (try func.signExtendInt(rhs, ty)).toLocal(func, ty) + else + rhs; const zero = switch (wasm_bits) { 32 => WValue{ .imm32 = 0 }, 64 => WValue{ .imm64 = 0 }, - else => unreachable, + else => @panic("Unexpected wasm integer width"), }; - const div_result = try func.allocLocal(ty); - // leave on stack - _ = try func.binOp(lhs_res, rhs_res, ty, .div); - try func.addLabel(.local_tee, div_result.local.value); - _ = try func.cmp(lhs_res, zero, ty, .lt); - _ = try func.cmp(rhs_res, zero, ty, .lt); + // tee leaves the value on the stack and stores it in a local. + const quotient = try func.allocLocal(ty); + _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .div); + try func.addLabel(.local_tee, quotient.local.value); + + // select takes a 32 bit value as the condition, so in the 64 bit case we use eqz to narrow + // the 64 bit value we want to use as the condition to 32 bits. + // This also inverts the condition (non 0 => 0, 0 => 1), so we put the adjusted and + // non-adjusted quotients on the stack in the opposite order for 32 vs 64 bits. + if (wasm_bits == 64) { + try func.emitWValue(quotient); + } + + // 0 if the signs of rhs_wasm and lhs_wasm are the same, 1 otherwise. + _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .xor); + _ = try func.cmp(.stack, zero, ty, .lt); + switch (wasm_bits) { 32 => { - try func.addTag(.i32_xor); try func.addTag(.i32_sub); + try func.emitWValue(quotient); }, 64 => { - try func.addTag(.i64_xor); + try func.addTag(.i64_extend_i32_u); try func.addTag(.i64_sub); }, - else => unreachable, + else => @panic("Unexpected wasm integer width"), } - try func.emitWValue(div_result); - // leave value on the stack - _ = try func.binOp(lhs_res, rhs_res, ty, .rem); + + _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem); + + if (wasm_bits == 64) { + try func.addTag(.i64_eqz); + } + try func.addTag(.select); + + // We need to zero the high bits because N bit comparisons consider all 32 or 64 bits, and + // expect all but the lowest N bits to be 0. + // TODO: Should we be zeroing the high bits here or should we be ignoring the high bits + // when performing comparisons? + if (int_bits != wasm_bits) { + _ = try func.wrapOperand(.{ .stack = {} }, ty); + } } else { const float_bits = ty.floatBits(func.target); if (float_bits > 64) { @@ -6453,15 +6483,11 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { } const is_f16 = float_bits == 16; - const lhs_operand = if (is_f16) blk: { - break :blk try func.fpext(lhs, Type.f16, Type.f32); - } else lhs; - const rhs_operand = if (is_f16) blk: { - break :blk try func.fpext(rhs, Type.f16, Type.f32); - } else rhs; + const lhs_wasm = if (is_f16) try func.fpext(lhs, Type.f16, Type.f32) else lhs; + const rhs_wasm = if (is_f16) try func.fpext(rhs, Type.f16, Type.f32) else rhs; - try func.emitWValue(lhs_operand); - try func.emitWValue(rhs_operand); + try func.emitWValue(lhs_wasm); + try func.emitWValue(rhs_wasm); switch (float_bits) { 16, 32 => { @@ -6498,8 +6524,8 @@ fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WVal if (wasm_bits != int_bits) { // Leave both values on the stack - _ = try func.signAbsValue(lhs, ty); - _ = try func.signAbsValue(rhs, ty); + _ = try func.signExtendInt(lhs, ty); + _ = try func.signExtendInt(rhs, ty); } else { try func.emitWValue(lhs); try func.emitWValue(rhs); @@ -6511,19 +6537,68 @@ fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WVal return result; } -/// Retrieves the absolute value of a signed integer -/// NOTE: Leaves the result value on the stack. -fn signAbsValue(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { +/// Remainder after floor division, defined by: +/// @divFloor(a, b) * b + @mod(a, b) = a +fn airMod(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { + const bin_op = func.air.instructions.items(.data)[inst].bin_op; + + const mod = func.bin_file.base.options.module.?; + const ty = func.typeOfIndex(inst); + const lhs = try func.resolveInst(bin_op.lhs); + const rhs = try func.resolveInst(bin_op.rhs); + + if (ty.isUnsignedInt(mod)) { + _ = try func.binOp(lhs, rhs, ty, .rem); + } else if (ty.isSignedInt(mod)) { + // The wasm rem instruction gives the remainder after truncating division (rounding towards + // 0), equivalent to @rem. + // We make use of the fact that: + // @mod(a, b) = @rem(@rem(a, b) + b, b) + const int_bits = ty.intInfo(mod).bits; + const wasm_bits = toWasmBits(int_bits) orelse { + return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); + }; + + if (wasm_bits > 64) { + return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); + } + + const lhs_wasm = if (wasm_bits != int_bits) + try (try func.signExtendInt(lhs, ty)).toLocal(func, ty) + else + lhs; + + const rhs_wasm = if (wasm_bits != int_bits) + try (try func.signExtendInt(rhs, ty)).toLocal(func, ty) + else + rhs; + + _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem); + _ = try func.binOp(.stack, rhs_wasm, ty, .add); + _ = try func.binOp(.stack, rhs_wasm, ty, .rem); + } else { + return func.fail("TODO: implement `@mod` on floating point types for {}", .{func.target.cpu.arch}); + } + + const result = try func.allocLocal(ty); + try func.addLabel(.local_set, result.local.value); + func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); +} + +/// Sign extends an N bit signed integer and pushes the result to the stack. +/// The result will be sign extended to 32 bits if N <= 32 or 64 bits if N <= 64. +/// Support for integers wider than 64 bits has not yet been implemented. +fn signExtendInt(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { const mod = func.bin_file.base.options.module.?; const int_bits = ty.intInfo(mod).bits; const wasm_bits = toWasmBits(int_bits) orelse { - return func.fail("TODO: signAbsValue for signed integers larger than '{d}' bits", .{int_bits}); + return func.fail("TODO: signExtendInt for signed integers larger than '{d}' bits", .{int_bits}); }; const shift_val = switch (wasm_bits) { 32 => WValue{ .imm32 = wasm_bits - int_bits }, 64 => WValue{ .imm64 = wasm_bits - int_bits }, - else => return func.fail("TODO: signAbsValue for i128", .{}), + else => return func.fail("TODO: signExtendInt for i128", .{}), }; try func.emitWValue(operand); @@ -6604,10 +6679,10 @@ fn signedSat(func: *CodeGen, lhs_operand: WValue, rhs_operand: WValue, ty: Type, const is_wasm_bits = wasm_bits == int_info.bits; var lhs = if (!is_wasm_bits) lhs: { - break :lhs try (try func.signAbsValue(lhs_operand, ty)).toLocal(func, ty); + break :lhs try (try func.signExtendInt(lhs_operand, ty)).toLocal(func, ty); } else lhs_operand; var rhs = if (!is_wasm_bits) rhs: { - break :rhs try (try func.signAbsValue(rhs_operand, ty)).toLocal(func, ty); + break :rhs try (try func.signExtendInt(rhs_operand, ty)).toLocal(func, ty); } else rhs_operand; const max_val: u64 = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(int_info.bits - 1))) - 1));