This proposal focuses on `arith.maxf`

, `arith.minf`

, `vector.reduction <maxf>`

, and `vector.reduction <minf>`

in MLIR. We have identified certain issues that require attention, which are outlined in the following sections:

# Problem 1: Missing Semantics and Confusing Naming

In LLVM, there are two ways to find the minimum and maximum of two floating-point numbers: `minnum`

/`maxnum`

intrinsics and `minimum`

/`maximum`

intrinsics [1, 2]. Each pair exhibits different behavior when dealing with NaNs and +0.0/-0.0. The former returns the non-NaN argument when one of the arguments is NaN and does not distinguish between +0.0 and -0.0, whereas the latter propagates the NaN and does distinguish between +0.0 and -0.0. The same applies to the reduction variants [9, 10].

In MLIR, there is currently a single way to represent the minimum and maximum of two floating-point numbers: `arith.minf`

/`arith.maxf`

. The semantics of these operations adhere to the semantics of `minimum`

/`maximum`

intrinsics in LLVM [3]. The same applies to the reduction variants in the Vector dialect [11]. However, there are no variants in MLIR to model the semantics of LLVMâ€™s `minnum`

/`maxnum`

.

### Proposed Solution

We propose to rename `arith.minf`

/`arith.maxf`

to `arith.minimum`

/`arith.maximum`

to align their names with their LLVM counterparts. We would also like to introduce the corresponding `arith.minnum`

and `arith.maxnum`

operations to mirror the semantics of the `minnum`

/`maxnum`

intrinsics in LLVM. The same applies to the vector reductions operations in the Vector dialect: `vector.reduction <maxf>`

and `vector.reduction <minf>`

.

# Problem 2: `arith.minf`

and `arith.maxf`

lowering to LLVM

Currently, there is a bug in the lowering of `arith.maxf`

and `arith.minf`

to LLVM. Although their semantics [3, 4] clearly indicate that NaN should be propagated and +0.0/-0.0 should be differentiated, they are lowered to `minnum`

/`maxnum`

intrinsics without the proper handling of the cases described above [5]. Interestingly enough, the lowering of their vector reduction variants seems to have always been aligned with the described semantics [6] even before it was changed to the proper intrinsic lowering.

### Proposed Solution

We plan to fix the existing bug by changing the lowering of `arith.maxf`

and `arith.minf`

from the `minnum`

/`maxnum`

intrinsics to the `maximum`

/`minimum`

intrinsics in LLVM.

# Problem 3: Vector reductions lowering to SPIR-V

A quite similar bug can be identified in a distinct combination of operations and lowerings: vector reduction operations and their corresponding SPIR-V lowerings. While the lowerings utilize `spirv.CL.fmax`

and `spirv.CL.fmin`

operations, the documentation specifies that they are intended to exhibit behavior similar to the `llvm.m**num`

intrinsics [7]. However, there are no additional operations inserted to rectify the behavior as previously done in similar cases [8].

### Proposed Solution

Weâ€™re going to fix the bug by adding extra operations to make sure the right meaning is carried through, just like how it was done before for the LLVM lowering [6].

# Summary

## State before the changes

Operation | LLVM lowering | SPIR-V lowering |
---|---|---|

`arith.maxf` |
`llvm.maxnum` without enforcing the desired semantic |
Follows the semantics |

`arith.minf` |
`llvm.minnum` without enforcing the desired semantic |
Follows the semantics |

`vector.reduction <maxf>` |
`llvm.vector.reduce.fmaximum` |
sequence of `spirv.CL.fmax` es without enforcing the desired semantic |

`vector.reduction <minf>` |
`llvm.vector.reduce.fminimum` |
sequence of `spirv.CL.fmin` s without enforcing the desired semantic |

## State after the changes

Operation | Desired LLVM lowering | Desired SPIR-V lowering |
---|---|---|

`arith.maximumf` |
`llvm.maximum` |
Current `arith.maxf` lowering |

`arith.minimumf` |
`llvm.minimum` |
Current `arith.minf` lowering |

`arith.maxnumf` |
`llvm.maxnum` |
`spirv.CL.fmax` `spirv.GL.FMax` + additional checks to propagate non-NaN (*) |

`arith.minnumf` |
`llvm.minnum` |
`spirv.CL.fmin` `spirv.GL.FMin` + additional checks to propagate non-NaN (*) |

`vector.reduction <maximumf>` |
`llvm.vector.reduce.fmaximum` |
sequence of `spirv.CL.fmax` es + additional checks to propagate NaN |

`vector.reduction <minimumf>` |
`llvm.vector.reduce.fminimum` |
sequence of `spirv.CL.fmin` s + additional checks to propagate NaN |

`vector.reduction <maxf>` |
`llvm.vector.reduce.fmax` |
sequence of `spirv.CL.fmax` es |

`vector.reduction <minf>` |
`llvm.vector.reduce.fmin` |
sequence of `spirv.CL.fmin` s |

***** A note on SPIR-V lowerings for Arith operations with `m**num`

intrinsics

There are two lowerings from the Arith dialect to SPIR-V: `spirv.CL`

and `spirv.GL`

. The `spirv.CL`

operations behave similarly to the `llvm.m**num`

intrinsics when it comes to handling NaNs. However, the `spirv.GL`

operations have undefined results when one of the operands is NaN. To ensure consistent semantics, additional operations should be inserted in the `spirv.GL`

lowering to enforce the correct behavior, like the current lowerings but with the different intent.

# Work breakdown

## 1 Arith dialect

1.1 Change the lowering to `llvm.m **imum`

intrinsics for `arith.m**f`

operations.

1.2 Rename `arith.m**f`

to `arith.m**imumf`

.

1.3 Add `arith.m**numf`

operations.

1.4 Add `arith.m**numf`

LLVM lowerings.

1.5 Add `arith.m**numf`

SPIR-V lowerings.

## 2 Vector dialect

2.1 Rename `vector.reduction <m**f>`

to `vector.reduction <m**imumf>`

2.2 Fix SPIRV lowering for `vector.reduction <m**imumf>`

to propagate NaNs.

2.3 Add `vector.reduction <m**f>`

operations.

2.4 Add `vector.reduction <m**f>`

LLVM lowerings.

2.5 Add `vector.reduction <m**f>`

SPIR-V lowerings.

# References

`llvm.maxnum`

intrinsic`llvm.maximum`

intrinsic`arith.maxf`

Reference`arith.minf`

Reference`arith.maxf`

and`arith.minf`

LLVM Conversion tests- Vector Reduction example with explicit enforcement of semantics
`spirv.CL.fmax`

Reference- Vector Reduction operations SPIR-V lowering tests
`llvm.vector.reduce.fmax`

intrinsic`llvm.vector.reduce.fmaximum`

intrinsic- The implementation of the vector reduction intrinsic has always been aligned with the
`llvm.vector.reduce.fm**imum`

intrinsics [6].

Authors: @dcaballe and @unterumarmung

CC: @kuhar, @mehdi_amini, @nicolasvasilache, @ftynse and @banach-space