[Mlir-commits] [mlir] [mlir][SPIR-V] Add ArithToSPIRV patterns for ceildivui/ceildivsi/floordivsi (PR #212926)
Arseniy Obolenskiy
llvmlistbot at llvm.org
Thu Jul 30 01:47:11 PDT 2026
https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/212926
>From 99d2e85062bcea4d9a03cd204983609dac9f0ac5 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 30 Jul 2026 06:34:07 +0200
Subject: [PATCH 1/2] [mlir][SPIR-V] Add ArithToSPIRV patterns for
ceildivui/ceildivsi/floordivsi
---
.../Conversion/ArithToSPIRV/ArithToSPIRV.cpp | 125 ++++++++++++++++++
.../arith-to-spirv-unsupported.mlir | 9 ++
.../ArithToSPIRV/arith-to-spirv.mlir | 55 ++++++++
3 files changed, 189 insertions(+)
diff --git a/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp b/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
index 614ac2b43b5f2..e2e4ae36fc676 100644
--- a/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
+++ b/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
@@ -496,6 +496,128 @@ struct RemSIOpCLPattern final : public OpConversionPattern<arith::RemSIOp> {
}
};
+//===----------------------------------------------------------------------===//
+// CeilDivUIOp
+//===----------------------------------------------------------------------===//
+
+/// Converts arith.ceildivui to SPIR-V ops. Convert `ceildivui(n, m)` into
+/// `n == 0 ? 0 : (n-1)/m + 1`. Formula taken from the equivalent conversion
+/// in IndexToSPIRV.
+struct CeilDivUIOpPattern final : OpConversionPattern<arith::CeilDivUIOp> {
+ using Base::Base;
+
+ LogicalResult
+ matchAndRewrite(arith::CeilDivUIOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter &rewriter) const override {
+ Type dstType = getTypeConverter()->convertType(op.getType());
+ if (!dstType)
+ return getTypeConversionFailure(rewriter, op);
+
+ if (!getElementTypeOrSelf(op.getType()).isIndex() &&
+ dstType != op.getType())
+ return op.emitError("bitwidth emulation is not implemented yet on "
+ "unsigned op pattern version");
+
+ Location loc = op.getLoc();
+ Value n = adaptor.getLhs();
+ Value m = adaptor.getRhs();
+
+ Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
+ Value one = spirv::ConstantOp::getOne(dstType, loc, rewriter);
+
+ Value minusOne = spirv::ISubOp::create(rewriter, loc, n, one);
+ Value quotient = spirv::UDivOp::create(rewriter, loc, minusOne, m);
+ Value plusOne = spirv::IAddOp::create(rewriter, loc, quotient, one);
+
+ Value cmp = spirv::IEqualOp::create(rewriter, loc, n, zero);
+ rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, cmp, zero, plusOne);
+ return success();
+ }
+};
+
+//===----------------------------------------------------------------------===//
+// CeilDivSIOp
+//===----------------------------------------------------------------------===//
+
+/// Converts arith.ceildivsi to SPIR-V ops. Convert `ceildivsi(a, b)` into
+/// `q = a/b; (q*b != a && (a<0) == (b<0)) ? q+1 : q`. Formula taken from the
+/// equivalent conversion in ExpandOps.
+struct CeilDivSIOpPattern final : OpConversionPattern<arith::CeilDivSIOp> {
+ using Base::Base;
+
+ LogicalResult
+ matchAndRewrite(arith::CeilDivSIOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter &rewriter) const override {
+ Type dstType = getTypeConverter()->convertType(op.getType());
+ if (!dstType)
+ return getTypeConversionFailure(rewriter, op);
+
+ Location loc = op.getLoc();
+ Value a = adaptor.getLhs();
+ Value b = adaptor.getRhs();
+
+ Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
+ Value one = spirv::ConstantOp::getOne(dstType, loc, rewriter);
+
+ Value quotient = spirv::SDivOp::create(rewriter, loc, a, b);
+ Value product = spirv::IMulOp::create(rewriter, loc, quotient, b);
+ Value notEqual = spirv::INotEqualOp::create(rewriter, loc, a, product);
+
+ Value aNeg = spirv::SLessThanOp::create(rewriter, loc, a, zero);
+ Value bNeg = spirv::SLessThanOp::create(rewriter, loc, b, zero);
+ Value sameSign = spirv::LogicalEqualOp::create(rewriter, loc, aNeg, bNeg);
+ Value cond = spirv::LogicalAndOp::create(rewriter, loc, notEqual, sameSign);
+
+ Value quotientPlusOne = spirv::IAddOp::create(rewriter, loc, quotient, one);
+ rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, cond, quotientPlusOne,
+ quotient);
+ return success();
+ }
+};
+
+//===----------------------------------------------------------------------===//
+// FloorDivSIOp
+//===----------------------------------------------------------------------===//
+
+/// Converts arith.floordivsi to SPIR-V ops. Convert `floordivsi(a, b)` into
+/// `q = a/b; (q*b != a && (a<0) != (b<0)) ? q-1 : q`. Formula taken from the
+/// equivalent conversion in ExpandOps.
+struct FloorDivSIOpPattern final : OpConversionPattern<arith::FloorDivSIOp> {
+ using Base::Base;
+
+ LogicalResult
+ matchAndRewrite(arith::FloorDivSIOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter &rewriter) const override {
+ Type dstType = getTypeConverter()->convertType(op.getType());
+ if (!dstType)
+ return getTypeConversionFailure(rewriter, op);
+
+ Location loc = op.getLoc();
+ Value a = adaptor.getLhs();
+ Value b = adaptor.getRhs();
+
+ Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
+ Value minusOne = getScalarOrVectorConstInt(
+ dstType, static_cast<uint64_t>(-1), rewriter, loc);
+
+ Value quotient = spirv::SDivOp::create(rewriter, loc, a, b);
+ Value product = spirv::IMulOp::create(rewriter, loc, quotient, b);
+ Value notEqual = spirv::INotEqualOp::create(rewriter, loc, a, product);
+
+ Value aNeg = spirv::SLessThanOp::create(rewriter, loc, a, zero);
+ Value bNeg = spirv::SLessThanOp::create(rewriter, loc, b, zero);
+ Value diffSign =
+ spirv::LogicalNotEqualOp::create(rewriter, loc, aNeg, bNeg);
+ Value cond = spirv::LogicalAndOp::create(rewriter, loc, notEqual, diffSign);
+
+ Value quotientMinusOne =
+ spirv::IAddOp::create(rewriter, loc, quotient, minusOne);
+ rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, cond, quotientMinusOne,
+ quotient);
+ return success();
+ }
+};
+
//===----------------------------------------------------------------------===//
// BitwiseOp
//===----------------------------------------------------------------------===//
@@ -1476,6 +1598,9 @@ void mlir::arith::populateArithToSPIRVPatterns(
spirv::ElementwiseOpPattern<arith::DivUIOp, spirv::UDivOp>,
BoolIOpPattern<arith::DivSIOp, spirv::LogicalAndOp>, // same as divui on i1
spirv::ElementwiseOpPattern<arith::DivSIOp, spirv::SDivOp>,
+ CeilDivUIOpPattern,
+ CeilDivSIOpPattern,
+ FloorDivSIOpPattern,
BoolIOpAndNotPattern<arith::RemUIOp>, // remui(a,b) = a & ~b (see pattern comment)
spirv::ElementwiseOpPattern<arith::RemUIOp, spirv::UModOp>,
BoolIOpAndNotPattern<arith::RemSIOp>, // remsi(a,b) = a & ~b (see pattern comment)
diff --git a/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv-unsupported.mlir b/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv-unsupported.mlir
index 9ecebb502630c..08098e4552e07 100644
--- a/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv-unsupported.mlir
+++ b/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv-unsupported.mlir
@@ -62,6 +62,15 @@ func.func @int_vector4_invalid(%arg0: vector<2xi16>, %arg1: vector<2xi16>) {
// -----
+func.func @ceildivui_vector4_invalid(%arg0: vector<2xi16>, %arg1: vector<2xi16>) {
+ // expected-error @+2 {{failed to legalize operation 'arith.ceildivui'}}
+ // expected-error @+1 {{bitwidth emulation is not implemented yet on unsigned op}}
+ %0 = arith.ceildivui %arg0, %arg1: vector<2xi16>
+ return
+}
+
+// -----
+
func.func @int_vector_invalid_bitwidth(%arg0: vector<2xi12>) {
// expected-error @+1 {{failed to legalize operation 'arith.addi'}}
%0 = arith.addi %arg0, %arg0: vector<2xi12>
diff --git a/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv.mlir b/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv.mlir
index 4cef06992727b..312c4c059591a 100644
--- a/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv.mlir
+++ b/mlir/test/Conversion/ArithToSPIRV/arith-to-spirv.mlir
@@ -40,6 +40,61 @@ func.func @int32_scalar_srem(%lhs: i32, %rhs: i32) {
return
}
+// CHECK-LABEL: @scalar_ceildivui
+// CHECK-SAME: (%[[LHS:.+]]: i32, %[[RHS:.+]]: i32)
+func.func @scalar_ceildivui(%lhs: i32, %rhs: i32) -> i32 {
+ // CHECK: %[[MINUSONE:.+]] = spirv.ISub %[[LHS]], %{{.+}} : i32
+ // CHECK: %[[Q:.+]] = spirv.UDiv %[[MINUSONE]], %[[RHS]] : i32
+ // CHECK: %[[PLUSONE:.+]] = spirv.IAdd %[[Q]], %{{.+}} : i32
+ // CHECK: %[[ISZERO:.+]] = spirv.IEqual %[[LHS]], %{{.+}} : i32
+ // CHECK: %[[R:.+]] = spirv.Select %[[ISZERO]], %{{.+}}, %[[PLUSONE]] : i1, i32
+ // CHECK: return %[[R]]
+ %0 = arith.ceildivui %lhs, %rhs : i32
+ return %0 : i32
+}
+
+// CHECK-LABEL: @scalar_ceildivsi
+// CHECK-SAME: (%[[LHS:.+]]: i32, %[[RHS:.+]]: i32)
+func.func @scalar_ceildivsi(%lhs: i32, %rhs: i32) -> i32 {
+ // CHECK: %[[Q:.+]] = spirv.SDiv %[[LHS]], %[[RHS]] : i32
+ // CHECK: %[[PROD:.+]] = spirv.IMul %[[Q]], %[[RHS]] : i32
+ // CHECK: %[[NE:.+]] = spirv.INotEqual %[[LHS]], %[[PROD]] : i32
+ // CHECK: %[[LNEG:.+]] = spirv.SLessThan %[[LHS]], %{{.+}} : i32
+ // CHECK: %[[RNEG:.+]] = spirv.SLessThan %[[RHS]], %{{.+}} : i32
+ // CHECK: %[[SAMESIGN:.+]] = spirv.LogicalEqual %[[LNEG]], %[[RNEG]] : i1
+ // CHECK: %[[COND:.+]] = spirv.LogicalAnd %[[NE]], %[[SAMESIGN]] : i1
+ // CHECK: %[[QP1:.+]] = spirv.IAdd %[[Q]], %{{.+}} : i32
+ // CHECK: %[[R:.+]] = spirv.Select %[[COND]], %[[QP1]], %[[Q]] : i1, i32
+ // CHECK: return %[[R]]
+ %0 = arith.ceildivsi %lhs, %rhs : i32
+ return %0 : i32
+}
+
+// CHECK-LABEL: @scalar_floordivsi
+// CHECK-SAME: (%[[LHS:.+]]: i32, %[[RHS:.+]]: i32)
+func.func @scalar_floordivsi(%lhs: i32, %rhs: i32) -> i32 {
+ // CHECK: %[[Q:.+]] = spirv.SDiv %[[LHS]], %[[RHS]] : i32
+ // CHECK: %[[PROD:.+]] = spirv.IMul %[[Q]], %[[RHS]] : i32
+ // CHECK: %[[NE:.+]] = spirv.INotEqual %[[LHS]], %[[PROD]] : i32
+ // CHECK: %[[LNEG:.+]] = spirv.SLessThan %[[LHS]], %{{.+}} : i32
+ // CHECK: %[[RNEG:.+]] = spirv.SLessThan %[[RHS]], %{{.+}} : i32
+ // CHECK: %[[DIFFSIGN:.+]] = spirv.LogicalNotEqual %[[LNEG]], %[[RNEG]] : i1
+ // CHECK: %[[COND:.+]] = spirv.LogicalAnd %[[NE]], %[[DIFFSIGN]] : i1
+ // CHECK: %[[QM1:.+]] = spirv.IAdd %[[Q]], %{{.+}} : i32
+ // CHECK: %[[R:.+]] = spirv.Select %[[COND]], %[[QM1]], %[[Q]] : i1, i32
+ // CHECK: return %[[R]]
+ %0 = arith.floordivsi %lhs, %rhs : i32
+ return %0 : i32
+}
+
+// CHECK-LABEL: @vector_ceildivsi
+func.func @vector_ceildivsi(%lhs: vector<4xi32>, %rhs: vector<4xi32>) -> vector<4xi32> {
+ // CHECK: spirv.SDiv %{{.*}}, %{{.*}} : vector<4xi32>
+ // CHECK: spirv.Select %{{.*}}, %{{.*}}, %{{.*}} : vector<4xi1>, vector<4xi32>
+ %0 = arith.ceildivsi %lhs, %rhs : vector<4xi32>
+ return %0 : vector<4xi32>
+}
+
// CHECK-LABEL: @index_scalar
func.func @index_scalar(%lhs: index, %rhs: index) {
// CHECK: spirv.IAdd %{{.*}}, %{{.*}}: i32
>From 28e31d46da5eaa26e60813511860d6abf773c051 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 30 Jul 2026 10:46:51 +0200
Subject: [PATCH 2/2] fix
---
mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp b/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
index e2e4ae36fc676..f01aa434cb853 100644
--- a/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
+++ b/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
@@ -525,11 +525,12 @@ struct CeilDivUIOpPattern final : OpConversionPattern<arith::CeilDivUIOp> {
Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
Value one = spirv::ConstantOp::getOne(dstType, loc, rewriter);
+ Value cmp = spirv::IEqualOp::create(rewriter, loc, n, zero);
+
Value minusOne = spirv::ISubOp::create(rewriter, loc, n, one);
Value quotient = spirv::UDivOp::create(rewriter, loc, minusOne, m);
Value plusOne = spirv::IAddOp::create(rewriter, loc, quotient, one);
- Value cmp = spirv::IEqualOp::create(rewriter, loc, n, zero);
rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, cmp, zero, plusOne);
return success();
}
More information about the Mlir-commits
mailing list