authorgravatar for 94326797+riverbl@users.noreply.github.comriverbl <94326797+riverbl@users.noreply.github.com> 2023-08-20 11:39:07+01:00
committergravatar for 94326797+riverbl@users.noreply.github.comriverbl <94326797+riverbl@users.noreply.github.com> 2023-08-23 16:52:30+01:00
log383e6ffc7b9e22613f4c4b15ebdcd2bf487573d6
tree02012e9546a082e0f347d6039c5c5b029fc1bc72
parent6780a6bbfa7bbd44036de3b1338011d1e5bb43f3

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

1 files changed, 124 insertions(+), 49 deletions(-)

src/arch/wasm/CodeGen.zig+124-49
...@@ -1852,6 +1852,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1852,6 +1852,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1852 .bool_and => func.airBinOp(inst, .@"and"),1852 .bool_and => func.airBinOp(inst, .@"and"),
1853 .bool_or => func.airBinOp(inst, .@"or"),1853 .bool_or => func.airBinOp(inst, .@"or"),
1854 .rem => func.airBinOp(inst, .rem),1854 .rem => func.airBinOp(inst, .rem),
1855 .mod => func.airMod(inst),
1855 .shl => func.airWrapBinOp(inst, .shl),1856 .shl => func.airWrapBinOp(inst, .shl),
1856 .shl_exact => func.airBinOp(inst, .shl),1857 .shl_exact => func.airBinOp(inst, .shl),
1857 .shl_sat => func.airShlSat(inst),1858 .shl_sat => func.airShlSat(inst),
...@@ -2012,7 +2013,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -2012,7 +2013,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2012 .frame_addr => func.airFrameAddress(inst),2013 .frame_addr => func.airFrameAddress(inst),
20132014
2014 .mul_sat,2015 .mul_sat,
2015 .mod,
2016 .assembly,2016 .assembly,
2017 .bit_reverse,2017 .bit_reverse,
2018 .is_err_ptr,2018 .is_err_ptr,
...@@ -4151,7 +4151,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro...@@ -4151,7 +4151,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro
4151 // when we upcast from a smaller integer to larger4151 // when we upcast from a smaller integer to larger
4152 // integers, we must get its absolute value similar to4152 // integers, we must get its absolute value similar to
4153 // i64_extend_i32_s instruction.4153 // i64_extend_i32_s instruction.
4154 return func.signAbsValue(operand, given);4154 return func.signExtendInt(operand, given);
4155 }4155 }
4156 return func.wrapOperand(operand, wanted);4156 return func.wrapOperand(operand, wanted);
4157 }4157 }
...@@ -5649,13 +5649,13 @@ fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerErro...@@ -5649,13 +5649,13 @@ fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerErro
5649 // for signed integers, we first apply signed shifts by the difference in bits5649 // for signed integers, we first apply signed shifts by the difference in bits
5650 // to get the signed value, as we store it internally as 2's complement.5650 // to get the signed value, as we store it internally as 2's complement.
5651 var lhs = if (wasm_bits != int_info.bits and is_signed) blk: {5651 var lhs = if (wasm_bits != int_info.bits and is_signed) blk: {
5652 break :blk try (try func.signAbsValue(lhs_op, lhs_ty)).toLocal(func, lhs_ty);5652 break :blk try (try func.signExtendInt(lhs_op, lhs_ty)).toLocal(func, lhs_ty);
5653 } else lhs_op;5653 } else lhs_op;
5654 var rhs = if (wasm_bits != int_info.bits and is_signed) blk: {5654 var rhs = if (wasm_bits != int_info.bits and is_signed) blk: {
5655 break :blk try (try func.signAbsValue(rhs_op, lhs_ty)).toLocal(func, lhs_ty);5655 break :blk try (try func.signExtendInt(rhs_op, lhs_ty)).toLocal(func, lhs_ty);
5656 } else rhs_op;5656 } else rhs_op;
56575657
5658 // in this case, we performed a signAbsValue which created a temporary local5658 // in this case, we performed a signExtendInt which created a temporary local
5659 // so let's free this so it can be re-used instead.5659 // so let's free this so it can be re-used instead.
5660 // In the other case we do not want to free it, because that would free the5660 // In the other case we do not want to free it, because that would free the
5661 // resolved instructions which may be referenced by other instructions.5661 // resolved instructions which may be referenced by other instructions.
...@@ -5677,7 +5677,7 @@ fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerErro...@@ -5677,7 +5677,7 @@ fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerErro
5677 const lt = try func.cmp(bin_op, lhs, lhs_ty, .lt);5677 const lt = try func.cmp(bin_op, lhs, lhs_ty, .lt);
5678 break :blk try func.binOp(cmp_zero, lt, Type.u32, .xor);5678 break :blk try func.binOp(cmp_zero, lt, Type.u32, .xor);
5679 }5679 }
5680 const abs = try func.signAbsValue(bin_op, lhs_ty);5680 const abs = try func.signExtendInt(bin_op, lhs_ty);
5681 break :blk try func.cmp(abs, bin_op, lhs_ty, .neq);5681 break :blk try func.cmp(abs, bin_op, lhs_ty, .neq);
5682 } else if (wasm_bits == int_info.bits)5682 } else if (wasm_bits == int_info.bits)
5683 try func.cmp(bin_op, lhs, lhs_ty, cmp_op)5683 try func.cmp(bin_op, lhs, lhs_ty, cmp_op)
...@@ -5797,7 +5797,7 @@ fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -5797,7 +5797,7 @@ fn airShlWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
5797 const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: {5797 const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: {
5798 // emit lhs to stack to we can keep 'wrapped' on the stack also5798 // emit lhs to stack to we can keep 'wrapped' on the stack also
5799 try func.emitWValue(lhs);5799 try func.emitWValue(lhs);
5800 const abs = try func.signAbsValue(shl, lhs_ty);5800 const abs = try func.signExtendInt(shl, lhs_ty);
5801 const wrapped = try func.wrapBinOp(abs, rhs_final, lhs_ty, .shr);5801 const wrapped = try func.wrapBinOp(abs, rhs_final, lhs_ty, .shr);
5802 break :blk try func.cmp(.{ .stack = {} }, wrapped, lhs_ty, .neq);5802 break :blk try func.cmp(.{ .stack = {} }, wrapped, lhs_ty, .neq);
5803 } else blk: {5803 } else blk: {
...@@ -5869,10 +5869,10 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -5869,10 +5869,10 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
5869 break :blk down_cast;5869 break :blk down_cast;
5870 }5870 }
5871 } else if (int_info.signedness == .signed and wasm_bits == 32) blk: {5871 } else if (int_info.signedness == .signed and wasm_bits == 32) blk: {
5872 const lhs_abs = try func.signAbsValue(lhs, lhs_ty);5872 const lhs_abs = try func.signExtendInt(lhs, lhs_ty);
5873 const rhs_abs = try func.signAbsValue(rhs, lhs_ty);5873 const rhs_abs = try func.signExtendInt(rhs, lhs_ty);
5874 const bin_op = try (try func.binOp(lhs_abs, rhs_abs, lhs_ty, .mul)).toLocal(func, lhs_ty);5874 const bin_op = try (try func.binOp(lhs_abs, rhs_abs, lhs_ty, .mul)).toLocal(func, lhs_ty);
5875 const mul_abs = try func.signAbsValue(bin_op, lhs_ty);5875 const mul_abs = try func.signExtendInt(bin_op, lhs_ty);
5876 _ = try func.cmp(mul_abs, bin_op, lhs_ty, .neq);5876 _ = try func.cmp(mul_abs, bin_op, lhs_ty, .neq);
5877 try func.addLabel(.local_set, overflow_bit.local.value);5877 try func.addLabel(.local_set, overflow_bit.local.value);
5878 break :blk try func.wrapOperand(bin_op, lhs_ty);5878 break :blk try func.wrapOperand(bin_op, lhs_ty);
...@@ -6405,47 +6405,77 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -6405,47 +6405,77 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
6405 const rhs = try func.resolveInst(bin_op.rhs);6405 const rhs = try func.resolveInst(bin_op.rhs);
64066406
6407 if (ty.isUnsignedInt(mod)) {6407 if (ty.isUnsignedInt(mod)) {
6408 const result = try (try func.binOp(lhs, rhs, ty, .div)).toLocal(func, ty);6408 _ = try func.binOp(lhs, rhs, ty, .div);
6409 return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
6410 } else if (ty.isSignedInt(mod)) {6409 } else if (ty.isSignedInt(mod)) {
6411 const int_bits = ty.intInfo(mod).bits;6410 const int_bits = ty.intInfo(mod).bits;
6412 const wasm_bits = toWasmBits(int_bits) orelse {6411 const wasm_bits = toWasmBits(int_bits) orelse {
6413 return func.fail("TODO: `@divFloor` for signed integers larger than '{d}' bits", .{int_bits});6412 return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
6414 };6413 };
6415 const lhs_res = if (wasm_bits != int_bits) blk: {6414
6416 break :blk try (try func.signAbsValue(lhs, ty)).toLocal(func, ty);6415 if (wasm_bits > 64) {
6417 } else lhs;6416 return func.fail("TODO: `@divFloor` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
6418 const rhs_res = if (wasm_bits != int_bits) blk: {6417 }
6419 break :blk try (try func.signAbsValue(rhs, ty)).toLocal(func, ty);6418
6420 } else rhs;6419 const lhs_wasm = if (wasm_bits != int_bits)
6420 try (try func.signExtendInt(lhs, ty)).toLocal(func, ty)
6421 else
6422 lhs;
6423
6424 const rhs_wasm = if (wasm_bits != int_bits)
6425 try (try func.signExtendInt(rhs, ty)).toLocal(func, ty)
6426 else
6427 rhs;
64216428
6422 const zero = switch (wasm_bits) {6429 const zero = switch (wasm_bits) {
6423 32 => WValue{ .imm32 = 0 },6430 32 => WValue{ .imm32 = 0 },
6424 64 => WValue{ .imm64 = 0 },6431 64 => WValue{ .imm64 = 0 },
6425 else => unreachable,6432 else => @panic("Unexpected wasm integer width"),
6426 };6433 };
64276434
6428 const div_result = try func.allocLocal(ty);6435 // tee leaves the value on the stack and stores it in a local.
6429 // leave on stack6436 const quotient = try func.allocLocal(ty);
6430 _ = try func.binOp(lhs_res, rhs_res, ty, .div);6437 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .div);
6431 try func.addLabel(.local_tee, div_result.local.value);6438 try func.addLabel(.local_tee, quotient.local.value);
6432 _ = try func.cmp(lhs_res, zero, ty, .lt);6439
6433 _ = try func.cmp(rhs_res, zero, ty, .lt);6440 // select takes a 32 bit value as the condition, so in the 64 bit case we use eqz to narrow
6441 // the 64 bit value we want to use as the condition to 32 bits.
6442 // This also inverts the condition (non 0 => 0, 0 => 1), so we put the adjusted and
6443 // non-adjusted quotients on the stack in the opposite order for 32 vs 64 bits.
6444 if (wasm_bits == 64) {
6445 try func.emitWValue(quotient);
6446 }
6447
6448 // 0 if the signs of rhs_wasm and lhs_wasm are the same, 1 otherwise.
6449 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .xor);
6450 _ = try func.cmp(.stack, zero, ty, .lt);
6451
6434 switch (wasm_bits) {6452 switch (wasm_bits) {
6435 32 => {6453 32 => {
6436 try func.addTag(.i32_xor);
6437 try func.addTag(.i32_sub);6454 try func.addTag(.i32_sub);
6455 try func.emitWValue(quotient);
6438 },6456 },
6439 64 => {6457 64 => {
6440 try func.addTag(.i64_xor);6458 try func.addTag(.i64_extend_i32_u);
6441 try func.addTag(.i64_sub);6459 try func.addTag(.i64_sub);
6442 },6460 },
6443 else => unreachable,6461 else => @panic("Unexpected wasm integer width"),
6462 }
6463
6464 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem);
6465
6466 if (wasm_bits == 64) {
6467 try func.addTag(.i64_eqz);
6444 }6468 }
6445 try func.emitWValue(div_result);6469
6446 // leave value on the stack
6447 _ = try func.binOp(lhs_res, rhs_res, ty, .rem);
6448 try func.addTag(.select);6470 try func.addTag(.select);
6471
6472 // We need to zero the high bits because N bit comparisons consider all 32 or 64 bits, and
6473 // expect all but the lowest N bits to be 0.
6474 // TODO: Should we be zeroing the high bits here or should we be ignoring the high bits
6475 // when performing comparisons?
6476 if (int_bits != wasm_bits) {
6477 _ = try func.wrapOperand(.{ .stack = {} }, ty);
6478 }
6449 } else {6479 } else {
6450 const float_bits = ty.floatBits(func.target);6480 const float_bits = ty.floatBits(func.target);
6451 if (float_bits > 64) {6481 if (float_bits > 64) {
...@@ -6453,15 +6483,11 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -6453,15 +6483,11 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
6453 }6483 }
6454 const is_f16 = float_bits == 16;6484 const is_f16 = float_bits == 16;
64556485
6456 const lhs_operand = if (is_f16) blk: {6486 const lhs_wasm = if (is_f16) try func.fpext(lhs, Type.f16, Type.f32) else lhs;
6457 break :blk try func.fpext(lhs, Type.f16, Type.f32);6487 const rhs_wasm = if (is_f16) try func.fpext(rhs, Type.f16, Type.f32) else rhs;
6458 } else lhs;
6459 const rhs_operand = if (is_f16) blk: {
6460 break :blk try func.fpext(rhs, Type.f16, Type.f32);
6461 } else rhs;
64626488
6463 try func.emitWValue(lhs_operand);6489 try func.emitWValue(lhs_wasm);
6464 try func.emitWValue(rhs_operand);6490 try func.emitWValue(rhs_wasm);
64656491
6466 switch (float_bits) {6492 switch (float_bits) {
6467 16, 32 => {6493 16, 32 => {
...@@ -6498,8 +6524,8 @@ fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WVal...@@ -6498,8 +6524,8 @@ fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WVal
64986524
6499 if (wasm_bits != int_bits) {6525 if (wasm_bits != int_bits) {
6500 // Leave both values on the stack6526 // Leave both values on the stack
6501 _ = try func.signAbsValue(lhs, ty);6527 _ = try func.signExtendInt(lhs, ty);
6502 _ = try func.signAbsValue(rhs, ty);6528 _ = try func.signExtendInt(rhs, ty);
6503 } else {6529 } else {
6504 try func.emitWValue(lhs);6530 try func.emitWValue(lhs);
6505 try func.emitWValue(rhs);6531 try func.emitWValue(rhs);
...@@ -6511,19 +6537,68 @@ fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WVal...@@ -6511,19 +6537,68 @@ fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WVal
6511 return result;6537 return result;
6512}6538}
65136539
6514/// Retrieves the absolute value of a signed integer6540/// Remainder after floor division, defined by:
6515/// NOTE: Leaves the result value on the stack.6541/// @divFloor(a, b) * b + @mod(a, b) = a
6516fn signAbsValue(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {6542fn airMod(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
6543 const bin_op = func.air.instructions.items(.data)[inst].bin_op;
6544
6545 const mod = func.bin_file.base.options.module.?;
6546 const ty = func.typeOfIndex(inst);
6547 const lhs = try func.resolveInst(bin_op.lhs);
6548 const rhs = try func.resolveInst(bin_op.rhs);
6549
6550 if (ty.isUnsignedInt(mod)) {
6551 _ = try func.binOp(lhs, rhs, ty, .rem);
6552 } else if (ty.isSignedInt(mod)) {
6553 // The wasm rem instruction gives the remainder after truncating division (rounding towards
6554 // 0), equivalent to @rem.
6555 // We make use of the fact that:
6556 // @mod(a, b) = @rem(@rem(a, b) + b, b)
6557 const int_bits = ty.intInfo(mod).bits;
6558 const wasm_bits = toWasmBits(int_bits) orelse {
6559 return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
6560 };
6561
6562 if (wasm_bits > 64) {
6563 return func.fail("TODO: `@mod` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits});
6564 }
6565
6566 const lhs_wasm = if (wasm_bits != int_bits)
6567 try (try func.signExtendInt(lhs, ty)).toLocal(func, ty)
6568 else
6569 lhs;
6570
6571 const rhs_wasm = if (wasm_bits != int_bits)
6572 try (try func.signExtendInt(rhs, ty)).toLocal(func, ty)
6573 else
6574 rhs;
6575
6576 _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem);
6577 _ = try func.binOp(.stack, rhs_wasm, ty, .add);
6578 _ = try func.binOp(.stack, rhs_wasm, ty, .rem);
6579 } else {
6580 return func.fail("TODO: implement `@mod` on floating point types for {}", .{func.target.cpu.arch});
6581 }
6582
6583 const result = try func.allocLocal(ty);
6584 try func.addLabel(.local_set, result.local.value);
6585 func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs });
6586}
6587
6588/// Sign extends an N bit signed integer and pushes the result to the stack.
6589/// The result will be sign extended to 32 bits if N <= 32 or 64 bits if N <= 64.
6590/// Support for integers wider than 64 bits has not yet been implemented.
6591fn signExtendInt(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {
6517 const mod = func.bin_file.base.options.module.?;6592 const mod = func.bin_file.base.options.module.?;
6518 const int_bits = ty.intInfo(mod).bits;6593 const int_bits = ty.intInfo(mod).bits;
6519 const wasm_bits = toWasmBits(int_bits) orelse {6594 const wasm_bits = toWasmBits(int_bits) orelse {
6520 return func.fail("TODO: signAbsValue for signed integers larger than '{d}' bits", .{int_bits});6595 return func.fail("TODO: signExtendInt for signed integers larger than '{d}' bits", .{int_bits});
6521 };6596 };
65226597
6523 const shift_val = switch (wasm_bits) {6598 const shift_val = switch (wasm_bits) {
6524 32 => WValue{ .imm32 = wasm_bits - int_bits },6599 32 => WValue{ .imm32 = wasm_bits - int_bits },
6525 64 => WValue{ .imm64 = wasm_bits - int_bits },6600 64 => WValue{ .imm64 = wasm_bits - int_bits },
6526 else => return func.fail("TODO: signAbsValue for i128", .{}),6601 else => return func.fail("TODO: signExtendInt for i128", .{}),
6527 };6602 };
65286603
6529 try func.emitWValue(operand);6604 try func.emitWValue(operand);
...@@ -6604,10 +6679,10 @@ fn signedSat(func: *CodeGen, lhs_operand: WValue, rhs_operand: WValue, ty: Type,...@@ -6604,10 +6679,10 @@ fn signedSat(func: *CodeGen, lhs_operand: WValue, rhs_operand: WValue, ty: Type,
6604 const is_wasm_bits = wasm_bits == int_info.bits;6679 const is_wasm_bits = wasm_bits == int_info.bits;
66056680
6606 var lhs = if (!is_wasm_bits) lhs: {6681 var lhs = if (!is_wasm_bits) lhs: {
6607 break :lhs try (try func.signAbsValue(lhs_operand, ty)).toLocal(func, ty);6682 break :lhs try (try func.signExtendInt(lhs_operand, ty)).toLocal(func, ty);
6608 } else lhs_operand;6683 } else lhs_operand;
6609 var rhs = if (!is_wasm_bits) rhs: {6684 var rhs = if (!is_wasm_bits) rhs: {
6610 break :rhs try (try func.signAbsValue(rhs_operand, ty)).toLocal(func, ty);6685 break :rhs try (try func.signExtendInt(rhs_operand, ty)).toLocal(func, ty);
6611 } else rhs_operand;6686 } else rhs_operand;
66126687
6613 const max_val: u64 = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(int_info.bits - 1))) - 1));6688 const max_val: u64 = @as(u64, @intCast((@as(u65, 1) << @as(u7, @intCast(int_info.bits - 1))) - 1));