From 9c20449cc5be5da0458556e143980b40d51e8776 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sat, 14 Oct 2023 12:52:31 +0200 Subject: [PATCH] wasm: lower min/max for floats to compiler_rt The min and max builtins in Zig have some intricate behavior related to floats, that is not replicated with the min and max wasm instructions or using simple select operations. By lowering these instructions to compiler_rt, handling around NaNs is done correctly. See also https://github.com/WebAssembly/design/issues/214 --- src/arch/wasm/CodeGen.zig | 28 +++++++++++++++++++++------- test/behavior/maximum_minimum.zig | 6 ++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index ebcccd781d..3eac273164 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -6253,8 +6253,10 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs }); } -fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: enum { max, min }) InnerError!void { +fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { + assert(op == .max or op == .min); const mod = func.bin_file.base.options.module.?; + const target = mod.getTarget(); const bin_op = func.air.instructions.items(.data)[inst].bin_op; const ty = func.typeOfIndex(inst); @@ -6269,13 +6271,25 @@ fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: enum { max, min }) InnerE const lhs = try func.resolveInst(bin_op.lhs); const rhs = try func.resolveInst(bin_op.rhs); - // operands to select from - try func.lowerToStack(lhs); - try func.lowerToStack(rhs); - _ = try func.cmp(lhs, rhs, ty, if (op == .max) .gt else .lt); + if (ty.zigTypeTag(mod) == .Float) { + var fn_name_buf: [64]u8 = undefined; + const float_bits = ty.floatBits(target); + const fn_name = std.fmt.bufPrint(&fn_name_buf, "{s}f{s}{s}", .{ + target_util.libcFloatPrefix(float_bits), + @tagName(op), + target_util.libcFloatSuffix(float_bits), + }) catch unreachable; + const result = try func.callIntrinsic(fn_name, &.{ ty.ip_index, ty.ip_index }, ty, &.{ lhs, rhs }); + try func.lowerToStack(result); + } else { + // operands to select from + try func.lowerToStack(lhs); + try func.lowerToStack(rhs); + _ = try func.cmp(lhs, rhs, ty, if (op == .max) .gt else .lt); - // based on the result from comparison, return operand 0 or 1. - try func.addTag(.select); + // based on the result from comparison, return operand 0 or 1. + try func.addTag(.select); + } // store result in local const result_ty = if (isByRef(ty, mod)) Type.u32 else ty; diff --git a/test/behavior/maximum_minimum.zig b/test/behavior/maximum_minimum.zig index 0b9ca8c636..ecc32a33f6 100644 --- a/test/behavior/maximum_minimum.zig +++ b/test/behavior/maximum_minimum.zig @@ -123,6 +123,12 @@ test "@min/max for floats" { try expectEqual(x, @min(y, x)); try expectEqual(y, @max(x, y)); try expectEqual(y, @max(y, x)); + + if (T != comptime_float) { + var nan: T = std.math.nan(T); + try expectEqual(y, @max(nan, y)); + try expectEqual(y, @max(y, nan)); + } } };