authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-10-14 12:52:31+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-10-15 14:00:32+02:00
log9c20449cc5be5da0458556e143980b40d51e8776
treecfa8ddf891494b237ba3e9a5268f222c90821c0f
parent2fe16e072ac447de5826ee436f50f73f56876ff9
signature Signed by SSH key SHA256:CQ99aPxq+RueiL9u7z0FEki5Fm7V6T8q4PrEGmINrA4

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

2 files changed, 27 insertions(+), 7 deletions(-)

src/arch/wasm/CodeGen.zig+21-7
......@@ -6253,8 +6253,10 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
62536253 func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs });
62546254}
62556255
6256fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: enum { max, min }) InnerError!void {
6256fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void {
6257 assert(op == .max or op == .min);
62576258 const mod = func.bin_file.base.options.module.?;
6259 const target = mod.getTarget();
62586260 const bin_op = func.air.instructions.items(.data)[inst].bin_op;
62596261
62606262 const ty = func.typeOfIndex(inst);
......@@ -6269,13 +6271,25 @@ fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: enum { max, min }) InnerE
62696271 const lhs = try func.resolveInst(bin_op.lhs);
62706272 const rhs = try func.resolveInst(bin_op.rhs);
62716273
6272 // operands to select from
6273 try func.lowerToStack(lhs);
6274 try func.lowerToStack(rhs);
6275 _ = try func.cmp(lhs, rhs, ty, if (op == .max) .gt else .lt);
6274 if (ty.zigTypeTag(mod) == .Float) {
6275 var fn_name_buf: [64]u8 = undefined;
6276 const float_bits = ty.floatBits(target);
6277 const fn_name = std.fmt.bufPrint(&fn_name_buf, "{s}f{s}{s}", .{
6278 target_util.libcFloatPrefix(float_bits),
6279 @tagName(op),
6280 target_util.libcFloatSuffix(float_bits),
6281 }) catch unreachable;
6282 const result = try func.callIntrinsic(fn_name, &.{ ty.ip_index, ty.ip_index }, ty, &.{ lhs, rhs });
6283 try func.lowerToStack(result);
6284 } else {
6285 // operands to select from
6286 try func.lowerToStack(lhs);
6287 try func.lowerToStack(rhs);
6288 _ = try func.cmp(lhs, rhs, ty, if (op == .max) .gt else .lt);
62766289
6277 // based on the result from comparison, return operand 0 or 1.
6278 try func.addTag(.select);
6290 // based on the result from comparison, return operand 0 or 1.
6291 try func.addTag(.select);
6292 }
62796293
62806294 // store result in local
62816295 const result_ty = if (isByRef(ty, mod)) Type.u32 else ty;
test/behavior/maximum_minimum.zig+6
......@@ -123,6 +123,12 @@ test "@min/max for floats" {
123123 try expectEqual(x, @min(y, x));
124124 try expectEqual(y, @max(x, y));
125125 try expectEqual(y, @max(y, x));
126
127 if (T != comptime_float) {
128 var nan: T = std.math.nan(T);
129 try expectEqual(y, @max(nan, y));
130 try expectEqual(y, @max(y, nan));
131 }
126132 }
127133 };
128134