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
signaturebadge-check 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 {...@@ -6253,8 +6253,10 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
6253 func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs });6253 func.finishAir(inst, result_ptr, &.{ extra.lhs, extra.rhs });
6254}6254}
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);
6257 const mod = func.bin_file.base.options.module.?;6258 const mod = func.bin_file.base.options.module.?;
6259 const target = mod.getTarget();
6258 const bin_op = func.air.instructions.items(.data)[inst].bin_op;6260 const bin_op = func.air.instructions.items(.data)[inst].bin_op;
62596261
6260 const ty = func.typeOfIndex(inst);6262 const ty = func.typeOfIndex(inst);
...@@ -6269,13 +6271,25 @@ fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: enum { max, min }) InnerE...@@ -6269,13 +6271,25 @@ fn airMaxMin(func: *CodeGen, inst: Air.Inst.Index, op: enum { max, min }) InnerE
6269 const lhs = try func.resolveInst(bin_op.lhs);6271 const lhs = try func.resolveInst(bin_op.lhs);
6270 const rhs = try func.resolveInst(bin_op.rhs);6272 const rhs = try func.resolveInst(bin_op.rhs);
62716273
6272 // operands to select from6274 if (ty.zigTypeTag(mod) == .Float) {
6273 try func.lowerToStack(lhs);6275 var fn_name_buf: [64]u8 = undefined;
6274 try func.lowerToStack(rhs);6276 const float_bits = ty.floatBits(target);
6275 _ = try func.cmp(lhs, rhs, ty, if (op == .max) .gt else .lt);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.6290 // based on the result from comparison, return operand 0 or 1.
6278 try func.addTag(.select);6291 try func.addTag(.select);
6292 }
62796293
6280 // store result in local6294 // store result in local
6281 const result_ty = if (isByRef(ty, mod)) Type.u32 else ty;6295 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" {...@@ -123,6 +123,12 @@ test "@min/max for floats" {
123 try expectEqual(x, @min(y, x));123 try expectEqual(x, @min(y, x));
124 try expectEqual(y, @max(x, y));124 try expectEqual(y, @max(x, y));
125 try expectEqual(y, @max(y, x));125 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 }
126 }132 }
127 };133 };
128134