mirror of
https://github.com/ziglang/zig.git
synced 2025-12-24 23:23:07 +00:00
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
This commit is contained in:
parent
6780a6bbfa
commit
383e6ffc7b
@ -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));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user