[Mlir-commits] [mlir] [mlir][arith] Implement ValueBoundsOpInterface for ExtSI op (PR #204476)

Max Graey llvmlistbot at llvm.org
Wed Jun 24 05:15:59 PDT 2026


https://github.com/MaxGraey updated https://github.com/llvm/llvm-project/pull/204476

>From 2ab4060e783a65d7c7d84f8ea833a328e8ae7ffd Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Thu, 18 Jun 2026 01:07:25 +0300
Subject: [PATCH 1/5] add ExtSI and IndexCastUI ops for pressburger's value
 bound

---
 mlir/lib/Dialect/Arith/IR/ArithDialect.cpp    |  5 +-
 .../Arith/IR/ValueBoundsOpInterfaceImpl.cpp   | 68 +++++++++++++------
 .../Arith/value-bounds-op-interface-impl.mlir | 48 +++++++++++++
 3 files changed, 97 insertions(+), 24 deletions(-)

diff --git a/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp b/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp
index ec611ca7924be..ea16839d43a5e 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp
@@ -55,8 +55,9 @@ void arith::ArithDialect::initialize() {
                            SelectOp>();
   declarePromisedInterfaces<bufferization::BufferizableOpInterface, ConstantOp,
                             IndexCastOp, SelectOp>();
-  declarePromisedInterfaces<ValueBoundsOpInterface, AddIOp, ConstantOp, SubIOp,
-                            MulIOp, SelectOp, FloorDivSIOp, MinSIOp, MaxSIOp>();
+  declarePromisedInterfaces<ValueBoundsOpInterface, ConstantOp, IndexCastUIOp,
+                            ExtSIOp, AddIOp, SubIOp, MulIOp, SelectOp,
+                            FloorDivSIOp, MinSIOp, MaxSIOp>();
 }
 
 /// Materialize an integer or floating point constant.
diff --git a/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp b/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp
index 3440c60f169d3..572e5a149112b 100644
--- a/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp
@@ -17,6 +17,43 @@ namespace mlir {
 namespace arith {
 namespace {
 
+struct ConstantOpInterface
+    : public ValueBoundsOpInterface::ExternalModel<ConstantOpInterface,
+                                                   ConstantOp> {
+  void populateBoundsForIndexValue(Operation *op, Value value,
+                                   ValueBoundsConstraintSet &cstr) const {
+    auto constantOp = cast<ConstantOp>(op);
+    assert(value == constantOp.getResult() && "invalid value");
+
+    if (auto attr = llvm::dyn_cast<IntegerAttr>(constantOp.getValue()))
+      cstr.bound(value) == attr.getInt();
+  }
+};
+
+struct IndexCastUIOpInterface
+    : public ValueBoundsOpInterface::ExternalModel<IndexCastUIOpInterface,
+                                                   IndexCastUIOp> {
+  void populateBoundsForIndexValue(Operation *op, Value value,
+                                   ValueBoundsConstraintSet &cstr) const {
+    auto indexCastOp = cast<IndexCastUIOp>(op);
+    assert(value == indexCastOp.getOut() && "invalid value");
+    cstr.bound(value) >= 0;
+  }
+};
+
+struct ExtSIOpInterface
+    : public ValueBoundsOpInterface::ExternalModel<ExtSIOpInterface, ExtSIOp> {
+  void populateBoundsForIndexValue(Operation *op, Value value,
+                                   ValueBoundsConstraintSet &cstr) const {
+    auto extSIOp = cast<ExtSIOp>(op);
+    assert(value == extSIOp.getOut() && "invalid value");
+
+    // Sign extension preserves the signed value (unlike zero extension where
+    // the result may be negative), so the bound is an exact equality.
+    cstr.bound(value) == cstr.getExpr(extSIOp.getIn());
+  }
+};
+
 struct AddIOpInterface
     : public ValueBoundsOpInterface::ExternalModel<AddIOpInterface, AddIOp> {
   void populateBoundsForIndexValue(Operation *op, Value value,
@@ -36,19 +73,6 @@ struct AddIOpInterface
   }
 };
 
-struct ConstantOpInterface
-    : public ValueBoundsOpInterface::ExternalModel<ConstantOpInterface,
-                                                   ConstantOp> {
-  void populateBoundsForIndexValue(Operation *op, Value value,
-                                   ValueBoundsConstraintSet &cstr) const {
-    auto constantOp = cast<ConstantOp>(op);
-    assert(value == constantOp.getResult() && "invalid value");
-
-    if (auto attr = llvm::dyn_cast<IntegerAttr>(constantOp.getValue()))
-      cstr.bound(value) == attr.getInt();
-  }
-};
-
 struct SubIOpInterface
     : public ValueBoundsOpInterface::ExternalModel<SubIOpInterface, SubIOp> {
   void populateBoundsForIndexValue(Operation *op, Value value,
@@ -71,7 +95,7 @@ struct MulIOpInterface
 
     AffineExpr lhs = cstr.getExpr(mulIOp.getLhs());
     AffineExpr rhs = cstr.getExpr(mulIOp.getRhs());
-    cstr.bound(value) == lhs *rhs;
+    cstr.bound(value) == (lhs * rhs);
   }
 };
 
@@ -162,11 +186,10 @@ struct SelectOpInterface
 };
 
 struct MinSIOpInterface
-    : public ValueBoundsOpInterface::ExternalModel<MinSIOpInterface,
-                                                   arith::MinSIOp> {
+    : public ValueBoundsOpInterface::ExternalModel<MinSIOpInterface, MinSIOp> {
   void populateBoundsForIndexValue(Operation *op, Value value,
                                    ValueBoundsConstraintSet &cstr) const {
-    auto minOp = cast<arith::MinSIOp>(op);
+    auto minOp = cast<MinSIOp>(op);
     assert(value == minOp.getResult() && "invalid value");
 
     AffineExpr lhs = cstr.getExpr(minOp.getLhs());
@@ -177,13 +200,12 @@ struct MinSIOpInterface
 };
 
 struct MaxSIOpInterface
-    : public ValueBoundsOpInterface::ExternalModel<MaxSIOpInterface,
-                                                   arith::MaxSIOp> {
+    : public ValueBoundsOpInterface::ExternalModel<MaxSIOpInterface, MaxSIOp> {
   void populateBoundsForIndexValue(Operation *op, Value value,
                                    ValueBoundsConstraintSet &cstr) const {
-    auto maxOp = cast<arith::MaxSIOp>(op);
+    auto maxOp = cast<MaxSIOp>(op);
     assert(value == maxOp.getResult() && "invalid value");
-    
+
     AffineExpr lhs = cstr.getExpr(maxOp.getLhs());
     AffineExpr rhs = cstr.getExpr(maxOp.getRhs());
     cstr.bound(value) >= lhs;
@@ -197,8 +219,10 @@ struct MaxSIOpInterface
 void mlir::arith::registerValueBoundsOpInterfaceExternalModels(
     DialectRegistry &registry) {
   registry.addExtension(+[](MLIRContext *ctx, arith::ArithDialect *dialect) {
-    arith::AddIOp::attachInterface<arith::AddIOpInterface>(*ctx);
     arith::ConstantOp::attachInterface<arith::ConstantOpInterface>(*ctx);
+    arith::IndexCastUIOp::attachInterface<arith::IndexCastUIOpInterface>(*ctx);
+    arith::ExtSIOp::attachInterface<arith::ExtSIOpInterface>(*ctx);
+    arith::AddIOp::attachInterface<arith::AddIOpInterface>(*ctx);
     arith::SubIOp::attachInterface<arith::SubIOpInterface>(*ctx);
     arith::MulIOp::attachInterface<arith::MulIOpInterface>(*ctx);
     arith::FloorDivSIOp::attachInterface<arith::FloorDivSIOpInterface>(*ctx);
diff --git a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
index 3f55037f46f09..dddbabe3a70b1 100644
--- a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
+++ b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
@@ -217,3 +217,51 @@ func.func @arith_maxsi_ub(%a: index) -> index {
   %1 = "test.reify_bound"(%0) {type = "UB"} : (index) -> (index)
   return %1 : index
 }
+
+// -----
+
+// CHECK-LABEL: func @arith_index_castui(
+//  CHECK-SAME:     %[[a:.*]]: i32
+//       CHECK:   %[[lb:.*]] = arith.constant 0 : index
+//       CHECK:   return %[[lb]]
+func.func @arith_index_castui(%a: i32) -> index {
+  %0 = arith.index_castui %a : i32 to index
+  %1 = "test.reify_bound"(%0) {type = "LB"} : (index) -> (index)
+  return %1 : index
+}
+
+// -----
+
+func.func @arith_index_castui_ub(%a: i32) -> index {
+  %0 = arith.index_castui %a : i32 to index
+  // Unsigned cast is non-negative but has no upper bound.
+  // expected-error @below{{could not reify bound}}
+  %1 = "test.reify_bound"(%0) {type = "UB"} : (index) -> (index)
+  return %1 : index
+}
+
+// -----
+
+// CHECK-LABEL: func @arith_extsi(
+//       CHECK:   %[[c:.*]] = arith.constant -5 : index
+//       CHECK:   return %[[c]]
+func.func @arith_extsi() -> index {
+  %c_5 = arith.constant -5 : i32
+  %ext = arith.extsi %c_5 : i32 to i64
+  %0 = "test.reify_bound"(%ext) {constant, allow_integer_type} : (i64) -> (index)
+  return %0 : index
+}
+
+// -----
+
+// CHECK-LABEL: func @arith_extsi_propagates_bound(
+//  CHECK-SAME:     %[[a:.*]]: i32
+//       CHECK:   %[[ub:.*]] = arith.constant 5 : index
+//       CHECK:   return %[[ub]]
+func.func @arith_extsi_propagates_bound(%a: i32) -> index {
+  %c4 = arith.constant 4 : i32
+  %min = arith.minsi %a, %c4 : i32
+  %ext = arith.extsi %min : i32 to i64
+  %0 = "test.reify_bound"(%ext) {type = "UB", allow_integer_type} : (i64) -> (index)
+  return %0 : index
+}

>From 42b53912a5052945fe8c038a75977d43bfa2ad5f Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Thu, 18 Jun 2026 01:23:34 +0300
Subject: [PATCH 2/5] remove complex sample

---
 .../Arith/value-bounds-op-interface-impl.mlir      | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
index dddbabe3a70b1..d226abe29e565 100644
--- a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
+++ b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
@@ -251,17 +251,3 @@ func.func @arith_extsi() -> index {
   %0 = "test.reify_bound"(%ext) {constant, allow_integer_type} : (i64) -> (index)
   return %0 : index
 }
-
-// -----
-
-// CHECK-LABEL: func @arith_extsi_propagates_bound(
-//  CHECK-SAME:     %[[a:.*]]: i32
-//       CHECK:   %[[ub:.*]] = arith.constant 5 : index
-//       CHECK:   return %[[ub]]
-func.func @arith_extsi_propagates_bound(%a: i32) -> index {
-  %c4 = arith.constant 4 : i32
-  %min = arith.minsi %a, %c4 : i32
-  %ext = arith.extsi %min : i32 to i64
-  %0 = "test.reify_bound"(%ext) {type = "UB", allow_integer_type} : (i64) -> (index)
-  return %0 : index
-}

>From 10339faf2b44489824658c3de0eef2ad49cf47d6 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Thu, 18 Jun 2026 01:40:16 +0300
Subject: [PATCH 3/5] trigger infra again

---
 mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
index d226abe29e565..b843159c01271 100644
--- a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
+++ b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
@@ -242,10 +242,10 @@ func.func @arith_index_castui_ub(%a: i32) -> index {
 
 // -----
 
-// CHECK-LABEL: func @arith_extsi(
+// CHECK-LABEL: func @arith_extsi_const(
 //       CHECK:   %[[c:.*]] = arith.constant -5 : index
 //       CHECK:   return %[[c]]
-func.func @arith_extsi() -> index {
+func.func @arith_extsi_const() -> index {
   %c_5 = arith.constant -5 : i32
   %ext = arith.extsi %c_5 : i32 to i64
   %0 = "test.reify_bound"(%ext) {constant, allow_integer_type} : (i64) -> (index)

>From 8491da3dbc707ce9acbaff8a3a32a7aa1ddb7431 Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Wed, 24 Jun 2026 15:14:11 +0300
Subject: [PATCH 4/5] remove model for IndexCastUIOp

---
 .../Arith/IR/ValueBoundsOpInterfaceImpl.cpp   | 12 ----------
 .../Arith/value-bounds-op-interface-impl.mlir | 22 -------------------
 2 files changed, 34 deletions(-)

diff --git a/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp b/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp
index 572e5a149112b..c260f0eabd0c2 100644
--- a/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ValueBoundsOpInterfaceImpl.cpp
@@ -30,17 +30,6 @@ struct ConstantOpInterface
   }
 };
 
-struct IndexCastUIOpInterface
-    : public ValueBoundsOpInterface::ExternalModel<IndexCastUIOpInterface,
-                                                   IndexCastUIOp> {
-  void populateBoundsForIndexValue(Operation *op, Value value,
-                                   ValueBoundsConstraintSet &cstr) const {
-    auto indexCastOp = cast<IndexCastUIOp>(op);
-    assert(value == indexCastOp.getOut() && "invalid value");
-    cstr.bound(value) >= 0;
-  }
-};
-
 struct ExtSIOpInterface
     : public ValueBoundsOpInterface::ExternalModel<ExtSIOpInterface, ExtSIOp> {
   void populateBoundsForIndexValue(Operation *op, Value value,
@@ -220,7 +209,6 @@ void mlir::arith::registerValueBoundsOpInterfaceExternalModels(
     DialectRegistry &registry) {
   registry.addExtension(+[](MLIRContext *ctx, arith::ArithDialect *dialect) {
     arith::ConstantOp::attachInterface<arith::ConstantOpInterface>(*ctx);
-    arith::IndexCastUIOp::attachInterface<arith::IndexCastUIOpInterface>(*ctx);
     arith::ExtSIOp::attachInterface<arith::ExtSIOpInterface>(*ctx);
     arith::AddIOp::attachInterface<arith::AddIOpInterface>(*ctx);
     arith::SubIOp::attachInterface<arith::SubIOpInterface>(*ctx);
diff --git a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
index b843159c01271..5b0105c8d6dcc 100644
--- a/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
+++ b/mlir/test/Dialect/Arith/value-bounds-op-interface-impl.mlir
@@ -220,28 +220,6 @@ func.func @arith_maxsi_ub(%a: index) -> index {
 
 // -----
 
-// CHECK-LABEL: func @arith_index_castui(
-//  CHECK-SAME:     %[[a:.*]]: i32
-//       CHECK:   %[[lb:.*]] = arith.constant 0 : index
-//       CHECK:   return %[[lb]]
-func.func @arith_index_castui(%a: i32) -> index {
-  %0 = arith.index_castui %a : i32 to index
-  %1 = "test.reify_bound"(%0) {type = "LB"} : (index) -> (index)
-  return %1 : index
-}
-
-// -----
-
-func.func @arith_index_castui_ub(%a: i32) -> index {
-  %0 = arith.index_castui %a : i32 to index
-  // Unsigned cast is non-negative but has no upper bound.
-  // expected-error @below{{could not reify bound}}
-  %1 = "test.reify_bound"(%0) {type = "UB"} : (index) -> (index)
-  return %1 : index
-}
-
-// -----
-
 // CHECK-LABEL: func @arith_extsi_const(
 //       CHECK:   %[[c:.*]] = arith.constant -5 : index
 //       CHECK:   return %[[c]]

>From be739e22f16ec567f1636ace1fccbae3783f5ecb Mon Sep 17 00:00:00 2001
From: MaxGraey <maxgraey at gmail.com>
Date: Wed, 24 Jun 2026 15:15:44 +0300
Subject: [PATCH 5/5] cleanup

---
 mlir/lib/Dialect/Arith/IR/ArithDialect.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp b/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp
index ea16839d43a5e..bf005aea3bf5f 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithDialect.cpp
@@ -55,9 +55,9 @@ void arith::ArithDialect::initialize() {
                            SelectOp>();
   declarePromisedInterfaces<bufferization::BufferizableOpInterface, ConstantOp,
                             IndexCastOp, SelectOp>();
-  declarePromisedInterfaces<ValueBoundsOpInterface, ConstantOp, IndexCastUIOp,
-                            ExtSIOp, AddIOp, SubIOp, MulIOp, SelectOp,
-                            FloorDivSIOp, MinSIOp, MaxSIOp>();
+  declarePromisedInterfaces<ValueBoundsOpInterface, ConstantOp, ExtSIOp, AddIOp,
+                            SubIOp, MulIOp, SelectOp, FloorDivSIOp, MinSIOp,
+                            MaxSIOp>();
 }
 
 /// Materialize an integer or floating point constant.



More information about the Mlir-commits mailing list