Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 122 additions & 47 deletions src/arch/wasm/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -6405,63 +6405,89 @@ 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,
};

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,
}
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) {
return func.fail("TODO: `@divFloor` for floats with bitsize: {d}", .{float_bits});
}
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 => {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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));
Expand Down
54 changes: 54 additions & 0 deletions test/behavior/stage2_wasm_div.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;

test "wasm integer division" {
// This test is copied from int_div.zig, with additional test cases for @divFloor on floats.
// TODO: Remove this test once the division tests in math.zig and int_div.zig pass with the
// stage2 wasm backend.
if (builtin.zig_backend != .stage2_wasm) return error.SkipZigTest;

try testDivision();
try comptime testDivision();
}
fn testDivision() !void {
try expect(div(u32, 13, 3) == 4);
try expect(div(u64, 13, 3) == 4);
try expect(div(u8, 13, 3) == 4);

try expect(divFloor(i8, 5, 3) == 1);
try expect(divFloor(i16, -5, 3) == -2);
try expect(divFloor(i64, -0x80000000, -2) == 0x40000000);
try expect(divFloor(i32, 0, -0x80000000) == 0);
try expect(divFloor(i64, -0x40000001, 0x40000000) == -2);
try expect(divFloor(i32, -0x80000000, 1) == -0x80000000);
try expect(divFloor(i32, 10, 12) == 0);
try expect(divFloor(i32, -14, 12) == -2);
try expect(divFloor(i32, -2, 12) == -1);
try expect(divFloor(f32, 56.0, 9.0) == 6.0);
try expect(divFloor(f32, 1053.0, -41.0) == -26.0);
try expect(divFloor(f16, -43.0, 12.0) == -4.0);
try expect(divFloor(f64, -90.0, -9.0) == 10.0);

try expect(mod(u32, 10, 12) == 10);
try expect(mod(i32, 10, 12) == 10);
try expect(mod(i64, -14, 12) == 10);
try expect(mod(i16, -2, 12) == 10);
try expect(mod(i8, -2, 12) == 10);

try expect(rem(i32, 10, 12) == 10);
try expect(rem(i32, -14, 12) == -2);
try expect(rem(i32, -2, 12) == -2);
}
fn div(comptime T: type, a: T, b: T) T {
return a / b;
}
fn divFloor(comptime T: type, a: T, b: T) T {
return @divFloor(a, b);
}
fn mod(comptime T: type, a: T, b: T) T {
return @mod(a, b);
}
fn rem(comptime T: type, a: T, b: T) T {
return @rem(a, b);
}