[Mlir-commits] [mlir] [mlir][gpu][math] Fix assertion in OpToFuncCallLowering on vector results (PR #215317)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Mon Aug 10 08:58:47 PDT 2026
https://github.com/SeongjaeP created https://github.com/llvm/llvm-project/pull/215317
`OpToFuncCallLowering` lowers scalar math ops to target runtime function calls
such as libdevice, OCML, and OCL functions. For ops without the
`SameOperandsAndResultType` trait it asserts that the operand and result types
match, with an escape hatch for bool results:
```c++
bool isResultBool = op->getResultTypes().front().isInteger(1);
```
That escape hatch only fires for scalars: `Type::isInteger(1)` is false for
`vector<2xi1>`, which is a `VectorType`, not an `IntegerType`. So the FP
classification ops (`math.isinf`, `math.isfinite`, `math.isnan`), whose operand
and result types genuinely differ, hit the assertion as soon as they are applied
to a vector:
```
$ mlir-opt repro.mlir -convert-math-to-nvvm
mlir-opt: OpToFuncCallLowering.h:80: ... Assertion
`(op->getResultTypes().front() == op->getOperand(0).getType() || isResultBool)
&& "expected op with same operand and result types"' failed.
```
## Fix
Bail out on non-scalar results before the assertion and let the co-registered
`ScalarizeVectorOpLowering` handle them:
```c++
Type opResultType = op->getResultTypes().front();
if (!opResultType.isIntOrIndexOrFloat())
return rewriter.notifyMatchFailure(op, "expected scalar result type");
bool isResultBool = opResultType.isInteger(1);
```
For builds without assertions, this does not change the generated IR. The
pattern already returned `failure()` for vector types when `getFunctionName()`
returned an empty name, allowing the co-registered `ScalarizeVectorOpLowering`
to handle the operation. This check moves that decision before the assertion,
so assertion-enabled and release builds follow the same lowering path, and the
reported match failure states the actual reason. With
`-debug-only=dialect-conversion`:
```
Legalizing operation : 'math.isinf' {
* Pattern : 'math.isinf -> ()' {
** Failure : expected scalar result type
} -> FAILURE : pattern failed to match
* Pattern : 'math.isinf -> ()' {
** Insert : 'llvm.extractelement' ... 'math.isinf' ... 'llvm.insertelement'
** Replace : 'math.isinf'
```
### Why not relax `isResultBool` instead
The issue suggested `getElementTypeOrSelf(...).isInteger(1)`. I built that
variant and confirmed it produces IR identical to this patch today — but only
because the pattern still exits via the `funcName.empty()` path further down,
without reporting a match failure reason. By then `isResultBool` has been used
to decide `resultType = i32`, which is not meaningful for a `vector<2xi1>`
result; the scalar-only tail of the pattern (returning the result as `i32` per
the ABI and comparing it with `icmp ne %r, 0 : i32`) would emit wrong IR if it
were ever reached, e.g. if `getFunctionName()` later learned about vector types.
It would also weaken the assertion, which would then pass for operand/result
types that genuinely differ.
### Other consumers of `OpToFuncCallLowering`
The issue asked whether the other consumers need auditing.
`OpToFuncCallLowering` is instantiated in three places, all of which
co-register `ScalarizeVectorOpLowering` for the same operations:
- `MathToNVVM.cpp`
- `MathToROCDL.cpp`
- `MathToXeVM.cpp`
A `vectorizable`-trait check does not seem necessary here: this pattern only
needs to reject non-scalar results before entering its scalar-call lowering.
Whether an operation may be unrolled element-wise is determined when the caller
registers `ScalarizeVectorOpLowering`. `impl::scalarizeVectorOp` handles both
`VectorType` and `LLVM::LLVMArrayType` operands.
The new test covers both vector classification — lowered to per-element
`__nv_isinff`, `__nv_finitef`, and `__nv_isnanf` calls — and unchanged scalar
lowering.
Fixes #210855
>From 46cbbf6935298c263627a7ae29465b13972087b8 Mon Sep 17 00:00:00 2001
From: SeongjaeP <psjj960507 at gmail.com>
Date: Mon, 10 Aug 2026 13:59:31 +0000
Subject: [PATCH] [mlir][gpu][math] Fix assertion in OpToFuncCallLowering on
vector results
---
.../GPUCommon/OpToFuncCallLowering.h | 10 ++++++-
.../Conversion/MathToNVVM/math-to-nvvm.mlir | 29 +++++++++++++++++++
2 files changed, 38 insertions(+), 1 deletion(-)
create mode 100644 mlir/test/Conversion/MathToNVVM/math-to-nvvm.mlir
diff --git a/mlir/lib/Conversion/GPUCommon/OpToFuncCallLowering.h b/mlir/lib/Conversion/GPUCommon/OpToFuncCallLowering.h
index cb9b6da071839..ea6d7c9f2da1d 100644
--- a/mlir/lib/Conversion/GPUCommon/OpToFuncCallLowering.h
+++ b/mlir/lib/Conversion/GPUCommon/OpToFuncCallLowering.h
@@ -72,7 +72,15 @@ struct OpToFuncCallLowering : public ConvertOpToLLVMPattern<SourceOp> {
std::is_base_of<OpTrait::OneResult<SourceOp>, SourceOp>::value,
"expected single result op");
- bool isResultBool = op->getResultTypes().front().isInteger(1);
+ // This pattern only handles scalar ops. Ops with shaped (e.g. vector)
+ // result types, such as `math.isinf` on `vector<Nxf32>`, are expected to be
+ // scalarized first by `ScalarizeVectorOpLowering`, which is co-registered
+ // for these ops; bail out so that pattern can take over.
+ Type opResultType = op->getResultTypes().front();
+ if (!opResultType.isIntOrIndexOrFloat())
+ return rewriter.notifyMatchFailure(op, "expected scalar result type");
+
+ bool isResultBool = opResultType.isInteger(1);
if constexpr (!std::is_base_of<OpTrait::SameOperandsAndResultType<SourceOp>,
SourceOp>::value) {
assert(op->getNumOperands() > 0 &&
diff --git a/mlir/test/Conversion/MathToNVVM/math-to-nvvm.mlir b/mlir/test/Conversion/MathToNVVM/math-to-nvvm.mlir
new file mode 100644
index 0000000000000..ac14ab15fd80c
--- /dev/null
+++ b/mlir/test/Conversion/MathToNVVM/math-to-nvvm.mlir
@@ -0,0 +1,29 @@
+// RUN: mlir-opt %s -convert-math-to-nvvm | FileCheck %s
+
+// Classification ops return a bool, so their operand and result types differ.
+// On shaped operands `OpToFuncCallLowering` bails out and `ScalarizeVectorOpLowering`
+// unrolls them element-wise, lowering each element to a libdevice call.
+
+// CHECK-LABEL: func.func @fpclass_vector(
+// CHECK-SAME: %[[ARG:.*]]: vector<2xf32>)
+func.func @fpclass_vector(%arg: vector<2xf32>) -> (vector<2xi1>, vector<2xi1>, vector<2xi1>) {
+ // CHECK-COUNT-2: llvm.call @__nv_isinff({{.*}}) : (f32) -> i32
+ %inf = math.isinf %arg : vector<2xf32>
+ // CHECK-COUNT-2: llvm.call @__nv_finitef({{.*}}) : (f32) -> i32
+ %finite = math.isfinite %arg : vector<2xf32>
+ // CHECK-COUNT-2: llvm.call @__nv_isnanf({{.*}}) : (f32) -> i32
+ %nan = math.isnan %arg : vector<2xf32>
+ return %inf, %finite, %nan : vector<2xi1>, vector<2xi1>, vector<2xi1>
+}
+
+// CHECK-LABEL: func.func @fpclass_scalar(
+func.func @fpclass_scalar(%arg: f32) -> (i1, i1, i1) {
+ // CHECK: %[[INF:.*]] = llvm.call @__nv_isinff({{.*}}) : (f32) -> i32
+ // CHECK: llvm.icmp "ne" %[[INF]], {{.*}} : i32
+ %inf = math.isinf %arg : f32
+ // CHECK: llvm.call @__nv_finitef({{.*}}) : (f32) -> i32
+ %finite = math.isfinite %arg : f32
+ // CHECK: llvm.call @__nv_isnanf({{.*}}) : (f32) -> i32
+ %nan = math.isnan %arg : f32
+ return %inf, %finite, %nan : i1, i1, i1
+}
More information about the Mlir-commits
mailing list