[Mlir-commits] [mlir] [MLIR][Python] Remove partial LLVM APIs in python bindings (PR #178290)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Jan 28 09:04:16 PST 2026


https://github.com/RattataKing updated https://github.com/llvm/llvm-project/pull/178290

>From d086d65fba707930a60ac2d14f01535ca195cb08 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 18:03:36 +0000
Subject: [PATCH 01/14] Replace twine with std str

---
 mlir/include/mlir/Bindings/Python/IRCore.h |  22 ++---
 mlir/lib/Bindings/Python/IRCore.cpp        | 103 +++++++++------------
 2 files changed, 53 insertions(+), 72 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index f9fc34e82c972..1bc6fafdde122 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -947,10 +947,9 @@ class MLIR_PYTHON_API_EXPORTED PyConcreteType : public BaseTy {
     if (!DerivedTy::isaFunction(orig)) {
       auto origRepr =
           nanobind::cast<std::string>(nanobind::repr(nanobind::cast(orig)));
-      throw nanobind::value_error((::llvm::Twine("Cannot cast type to ") +
+      throw nanobind::value_error((std::string("Cannot cast type to ") +
                                    DerivedTy::pyClassName + " (from " +
                                    origRepr + ")")
-                                      .str()
                                       .c_str());
     }
     return orig;
@@ -966,8 +965,7 @@ class MLIR_PYTHON_API_EXPORTED PyConcreteType : public BaseTy {
           if (DerivedTy::getTypeIdFunction)
             return PyTypeID(DerivedTy::getTypeIdFunction());
           throw nanobind::attribute_error(
-              (DerivedTy::pyClassName + ::llvm::Twine(" has no typeid."))
-                  .str()
+              (DerivedTy::pyClassName + std::string(" has no typeid."))
                   .c_str());
         },
         nanobind::sig("def static_typeid(/) -> TypeID"));
@@ -1080,10 +1078,9 @@ class MLIR_PYTHON_API_EXPORTED PyConcreteAttribute : public BaseTy {
     if (!DerivedTy::isaFunction(orig)) {
       auto origRepr =
           nanobind::cast<std::string>(nanobind::repr(nanobind::cast(orig)));
-      throw nanobind::value_error((::llvm::Twine("Cannot cast attribute to ") +
+      throw nanobind::value_error((std::string("Cannot cast attribute to ") +
                                    DerivedTy::pyClassName + " (from " +
                                    origRepr + ")")
-                                      .str()
                                       .c_str());
     }
     return orig;
@@ -1111,8 +1108,7 @@ class MLIR_PYTHON_API_EXPORTED PyConcreteAttribute : public BaseTy {
           if (DerivedTy::getTypeIdFunction)
             return PyTypeID(DerivedTy::getTypeIdFunction());
           throw nanobind::attribute_error(
-              (DerivedTy::pyClassName + ::llvm::Twine(" has no typeid."))
-                  .str()
+              (DerivedTy::pyClassName + std::string(" has no typeid."))
                   .c_str());
         },
         nanobind::sig("def static_typeid(/) -> TypeID"));
@@ -1329,9 +1325,9 @@ class MLIR_PYTHON_API_EXPORTED PySymbolTable {
 /// Custom exception that allows access to error diagnostic information. This is
 /// converted to the `ir.MLIRError` python exception when thrown.
 struct MLIR_PYTHON_API_EXPORTED MLIRError {
-  MLIRError(::llvm::Twine message,
+  MLIRError(std::string message,
             std::vector<PyDiagnostic::DiagnosticInfo> &&errorDiagnostics = {})
-      : message(message.str()), errorDiagnostics(std::move(errorDiagnostics)) {}
+      : message(message), errorDiagnostics(std::move(errorDiagnostics)) {}
   std::string message;
   std::vector<PyDiagnostic::DiagnosticInfo> errorDiagnostics;
 };
@@ -1549,10 +1545,9 @@ class MLIR_PYTHON_API_EXPORTED PyConcreteValue : public PyValue {
     if (!DerivedTy::isaFunction(orig.get())) {
       auto origRepr =
           nanobind::cast<std::string>(nanobind::repr(nanobind::cast(orig)));
-      throw nanobind::value_error((::llvm::Twine("Cannot cast value to ") +
+      throw nanobind::value_error((std::string("Cannot cast value to ") +
                                    DerivedTy::pyClassName + " (from " +
                                    origRepr + ")")
-                                      .str()
                                       .c_str());
     }
     return orig.get();
@@ -1561,9 +1556,8 @@ class MLIR_PYTHON_API_EXPORTED PyConcreteValue : public PyValue {
   /// Binds the Python module objects to functions of this class.
   static void bind(nanobind::module_ &m) {
     auto cls = ClassTy(m, DerivedTy::pyClassName, nanobind::is_generic(),
-                       nanobind::sig((::llvm::Twine("class ") +
+                       nanobind::sig((std::string("class ") +
                                       DerivedTy::pyClassName + "(Value[_T])")
-                                         .str()
                                          .c_str()));
     cls.def(nanobind::init<PyValue &>(), nanobind::keep_alive<0, 1>(),
             nanobind::arg("value"));
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index eb00363a54034..9d88e7c7c9f09 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -29,7 +29,6 @@ using namespace mlir;
 
 using llvm::SmallVector;
 using llvm::StringRef;
-using llvm::Twine;
 
 static const char kModuleParseDocstring[] =
     R"(Parses a module's assembly format from a string.
@@ -100,9 +99,8 @@ MlirBlock createBlock(const nb::sequence &pyArgTypes,
   }
 
   if (argTypes.size() != argLocs.size())
-    throw nb::value_error(("Expected " + Twine(argTypes.size()) +
-                           " locations, got: " + Twine(argLocs.size()))
-                              .str()
+    throw nb::value_error(("Expected " + std::to_string(argTypes.size()) +
+                           " locations, got: " + std::to_string(argLocs.size()))
                               .c_str());
   return mlirBlockCreate(argTypes.size(), argTypes.data(), argLocs.data());
 }
@@ -824,7 +822,7 @@ MlirDialect PyDialects::getDialectForKey(const std::string &key,
   MlirDialect dialect = mlirContextGetOrLoadDialect(getContext()->get(),
                                                     {key.data(), key.size()});
   if (mlirDialectIsNull(dialect)) {
-    std::string msg = (Twine("Dialect '") + key + "' not found").str();
+    std::string msg = (std::string("Dialect '") + key + "' not found");
     if (attrError)
       throw nb::attribute_error(msg.c_str());
     throw nb::index_error(msg.c_str());
@@ -1105,9 +1103,8 @@ void PyOperationBase::writeBytecode(const nb::object &fileOrStringObject,
       operation, config, accum.getCallback(), accum.getUserData());
   mlirBytecodeWriterConfigDestroy(config);
   if (mlirLogicalResultIsFailure(res))
-    throw nb::value_error((Twine("Unable to honor desired bytecode version ") +
-                           Twine(*bytecodeVersion))
-                              .str()
+    throw nb::value_error((std::string("Unable to honor desired bytecode version ") +
+                           std::to_string(*bytecodeVersion))
                               .c_str());
 }
 
@@ -1481,10 +1478,9 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
         if (!resultTypes.back())
           throw nb::cast_error();
       } catch (nb::cast_error &err) {
-        throw nb::value_error((llvm::Twine("Result ") +
-                               llvm::Twine(it.index()) + " of operation \"" +
-                               name + "\" must be a Type (" + err.what() + ")")
-                                  .str()
+        throw nb::value_error((std::string("Result ") +
+                               std::to_string(it.index()) + " of operation \"" +
+                               std::string(name) + "\" must be a Type (" + err.what() + ")")
                                   .c_str());
       }
     }
@@ -1492,12 +1488,11 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
     // Sized result unpacking.
     auto resultSegmentSpec = nb::cast<std::vector<int>>(resultSegmentSpecObj);
     if (resultSegmentSpec.size() != resultTypeList.size()) {
-      throw nb::value_error((llvm::Twine("Operation \"") + name +
+      throw nb::value_error((std::string("Operation \"") + std::string(name) +
                              "\" requires " +
-                             llvm::Twine(resultSegmentSpec.size()) +
+                             std::to_string(resultSegmentSpec.size()) +
                              " result segments but was provided " +
-                             llvm::Twine(resultTypeList.size()))
-                                .str()
+                             std::to_string(resultTypeList.size()))
                                 .c_str());
     }
     resultSegmentLengths.reserve(resultTypeList.size());
@@ -1516,18 +1511,16 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
             resultSegmentLengths.push_back(0);
           } else {
             throw nb::value_error(
-                (llvm::Twine("Result ") + llvm::Twine(it.index()) +
-                 " of operation \"" + name +
+                (std::string("Result ") + std::to_string(it.index()) +
+                 " of operation \"" + std::string(name) +
                  "\" must be a Type (was None and result is not optional)")
-                    .str()
                     .c_str());
           }
         } catch (nb::cast_error &err) {
-          throw nb::value_error((llvm::Twine("Result ") +
-                                 llvm::Twine(it.index()) + " of operation \"" +
-                                 name + "\" must be a Type (" + err.what() +
+          throw nb::value_error((std::string("Result ") +
+                                 std::to_string(it.index()) + " of operation \"" +
+                                 std::string(name) + "\" must be a Type (" + err.what() +
                                  ")")
-                                    .str()
                                     .c_str());
         }
       } else if (segmentSpec == -1) {
@@ -1551,8 +1544,8 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error((llvm::Twine("Result ") +
-                                 llvm::Twine(it.index()) + " of operation \"" +
+          throw nb::value_error((std::string("Result ") +
+                                 std::to_string(it.index()) + " of operation \"" +
                                  name + "\" must be a Sequence of Types (" +
                                  err.what() + ")")
                                     .str()
@@ -1569,9 +1562,9 @@ MlirValue getUniqueResult(MlirOperation operation) {
   auto numResults = mlirOperationGetNumResults(operation);
   if (numResults != 1) {
     auto name = mlirIdentifierStr(mlirOperationGetName(operation));
-    throw nb::value_error((Twine("Cannot call .result on operation ") +
+    throw nb::value_error((std::string("Cannot call .result on operation ") +
                            StringRef(name.data, name.length) + " which has " +
-                           Twine(numResults) +
+                           std::to_string(numResults) +
                            " results (it is only valid for operations with a "
                            "single result)")
                               .str()
@@ -1626,18 +1619,16 @@ nb::object PyOpView::buildGeneric(
   }
   if (*regions < opMinRegionCount) {
     throw nb::value_error(
-        (llvm::Twine("Operation \"") + name + "\" requires a minimum of " +
-         llvm::Twine(opMinRegionCount) +
-         " regions but was built with regions=" + llvm::Twine(*regions))
-            .str()
+        (std::string("Operation \"") + std::string(name) + "\" requires a minimum of " +
+         std::to_string(opMinRegionCount) +
+         " regions but was built with regions=" + std::to_string(*regions))
             .c_str());
   }
   if (opHasNoVariadicRegions && *regions > opMinRegionCount) {
     throw nb::value_error(
-        (llvm::Twine("Operation \"") + name + "\" requires a maximum of " +
-         llvm::Twine(opMinRegionCount) +
-         " regions but was built with regions=" + llvm::Twine(*regions))
-            .str()
+        (std::string("Operation \"") + std::string(name) + "\" requires a maximum of " +
+         std::to_string(opMinRegionCount) +
+         " regions but was built with regions=" + std::to_string(*regions))
             .c_str());
   }
 
@@ -1657,10 +1648,9 @@ nb::object PyOpView::buildGeneric(
       try {
         operands.push_back(getOpResultOrValue(it.value()));
       } catch (nb::builtin_exception &err) {
-        throw nb::value_error((llvm::Twine("Operand ") +
-                               llvm::Twine(it.index()) + " of operation \"" +
-                               name + "\" must be a Value (" + err.what() + ")")
-                                  .str()
+        throw nb::value_error((std::string("Operand ") +
+                               std::to_string(it.index()) + " of operation \"" +
+                               std::string(name) + "\" must be a Value (" + err.what() + ")")
                                   .c_str());
       }
     }
@@ -1668,12 +1658,11 @@ nb::object PyOpView::buildGeneric(
     // Sized operand unpacking.
     auto operandSegmentSpec = nb::cast<std::vector<int>>(operandSegmentSpecObj);
     if (operandSegmentSpec.size() != operandList.size()) {
-      throw nb::value_error((llvm::Twine("Operation \"") + name +
+      throw nb::value_error((std::string("Operation \"") + std::string(name) +
                              "\" requires " +
-                             llvm::Twine(operandSegmentSpec.size()) +
+                             std::to_string(operandSegmentSpec.size()) +
                              "operand segments but was provided " +
-                             llvm::Twine(operandList.size()))
-                                .str()
+                             std::to_string(operandList.size()))
                                 .c_str());
     }
     operandSegmentLengths.reserve(operandList.size());
@@ -1688,11 +1677,11 @@ nb::object PyOpView::buildGeneric(
 
             operands.push_back(getOpResultOrValue(operand));
           } catch (nb::builtin_exception &err) {
-            throw nb::value_error((llvm::Twine("Operand ") +
-                                   llvm::Twine(it.index()) +
-                                   " of operation \"" + name +
+            throw nb::value_error((std::string("Operand ") +
+                                   std::to_string(it.index()) +
+                                   " of operation \"" 
+                                   + std::string(name) +
                                    "\" must be a Value (" + err.what() + ")")
-                                      .str()
                                       .c_str());
           }
 
@@ -1702,10 +1691,9 @@ nb::object PyOpView::buildGeneric(
           operandSegmentLengths.push_back(0);
         } else {
           throw nb::value_error(
-              (llvm::Twine("Operand ") + llvm::Twine(it.index()) +
-               " of operation \"" + name +
+              (std::string("Operand ") + std::to_string(it.index()) +
+               " of operation \"" + std::string(name) +
                "\" must be a Value (was None and operand is not optional)")
-                  .str()
                   .c_str());
         }
       } else if (segmentSpec == -1) {
@@ -1726,11 +1714,10 @@ nb::object PyOpView::buildGeneric(
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error((llvm::Twine("Operand ") +
-                                 llvm::Twine(it.index()) + " of operation \"" +
-                                 name + "\" must be a Sequence of Values (" +
+          throw nb::value_error((std::string("Operand ") +
+                                 std::to_string(it.index()) + " of operation \"" +
+                                 std::string(name) + "\" must be a Sequence of Values (" +
                                  err.what() + ")")
-                                    .str()
                                     .c_str());
         }
       } else {
@@ -2954,7 +2941,7 @@ void populateIRCore(nb::module_ &m) {
                 self.get(), {name.data(), name.size()});
             if (mlirDialectIsNull(dialect)) {
               throw nb::value_error(
-                  (Twine("Dialect '") + name + "' not found").str().c_str());
+                  (std::string("Dialect '") + name + "' not found").c_str());
             }
             return PyDialectDescriptor(self.getRef(), dialect);
           },
@@ -3944,8 +3931,8 @@ void populateIRCore(nb::module_ &m) {
             mlirIdentifierStr(mlirOperationGetName(*parsed.get()));
         std::string_view parsedOpName(identifier.data, identifier.length);
         if (clsOpName != parsedOpName)
-          throw MLIRError(Twine("Expected a '") + clsOpName + "' op, got: '" +
-                          parsedOpName + "'");
+          throw MLIRError(std::string("Expected a '") + std::string(clsOpName) + "' op, got: '" +
+                          std::string(parsedOpName) + "'");
         return PyOpView::constructDerived(cls, parsed.getObject());
       },
       "cls"_a, "source"_a, nb::kw_only(), "source_name"_a = "",
@@ -4478,7 +4465,7 @@ void populateIRCore(nb::module_ &m) {
               return PyTypeID(mlirTypeID);
             auto origRepr = nb::cast<std::string>(nb::repr(nb::cast(self)));
             throw nb::value_error(
-                (origRepr + llvm::Twine(" has no typeid.")).str().c_str());
+                (origRepr + std::string(" has no typeid.")).c_str());
           },
           "Returns the `TypeID` of the `Type`, or raises `ValueError` if "
           "`Type` has no "

>From 5b5c86dfe2bf809de3cb9b8e145ee9faa569d5f5 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 19:56:17 +0000
Subject: [PATCH 02/14] Replace ArrayRef with ptr and size

---
 mlir/include/mlir/Bindings/Python/IRCore.h |  4 +++-
 mlir/lib/Bindings/Python/IRCore.cpp        | 12 +++++++-----
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index 1bc6fafdde122..1565ada0226c3 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -10,6 +10,7 @@
 #ifndef MLIR_BINDINGS_PYTHON_IRCORE_H
 #define MLIR_BINDINGS_PYTHON_IRCORE_H
 
+#include <cstddef>
 #include <optional>
 #include <sstream>
 #include <utility>
@@ -687,7 +688,8 @@ class MLIR_PYTHON_API_EXPORTED PyOperation : public PyOperationBase,
   /// Creates an operation. See corresponding python docstring.
   static nanobind::object
   create(std::string_view name, std::optional<std::vector<PyType *>> results,
-         llvm::ArrayRef<MlirValue> operands,
+         const MlirValue *operands,
+         size_t numOperands,
          std::optional<nanobind::dict> attributes,
          std::optional<std::vector<PyBlock *>> successors, int regions,
          PyLocation &location, const nanobind::object &ip, bool inferType);
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 9d88e7c7c9f09..a7d4dad42b52c 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -1252,7 +1252,8 @@ static void maybeInsertOperation(PyOperationRef &op,
 
 nb::object PyOperation::create(std::string_view name,
                                std::optional<std::vector<PyType *>> results,
-                               llvm::ArrayRef<MlirValue> operands,
+                               const MlirValue *operands,
+                               size_t numOperands,
                                std::optional<nb::dict> attributes,
                                std::optional<std::vector<PyBlock *>> successors,
                                int regions, PyLocation &location,
@@ -1322,8 +1323,8 @@ nb::object PyOperation::create(std::string_view name,
   // point, exceptions cannot be thrown or else the state will leak.
   MlirOperationState state =
       mlirOperationStateGet(toMlirStringRef(name), location);
-  if (!operands.empty())
-    mlirOperationStateAddOperands(&state, operands.size(), operands.data());
+  if (numOperands)
+    mlirOperationStateAddOperands(&state, numOperands, operands);
   state.enableResultTypeInference = inferType;
   if (!mlirResults.empty())
     mlirOperationStateAddResults(&state, mlirResults.size(),
@@ -1763,7 +1764,8 @@ nb::object PyOpView::buildGeneric(
   // Delegate to create.
   return PyOperation::create(name,
                              /*results=*/std::move(resultTypes),
-                             /*operands=*/operands,
+                             /*operands=*/operands.data(),
+                             /*numOperands=*/operands.size(),
                              /*attributes=*/std::move(attributes),
                              /*successors=*/std::move(successors),
                              /*regions=*/*regions, location, maybeIp,
@@ -3761,7 +3763,7 @@ void populateIRCore(nb::module_ &m) {
             }
 
             PyLocation pyLoc = maybeGetTracebackLocation(location);
-            return PyOperation::create(name, results, mlirOperands, attributes,
+            return PyOperation::create(name, results, mlirOperands.data(), mlirOperands.size(), attributes,
                                        successors, regions, pyLoc, maybeIp,
                                        inferType);
           },

>From c6f9159d485a7b9278379190a8a99bbe48f73841 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 20:08:30 +0000
Subject: [PATCH 03/14] Replaced smallvector with std vector

---
 mlir/lib/Bindings/Python/IRCore.cpp | 32 +++++++++++++----------------
 1 file changed, 14 insertions(+), 18 deletions(-)

diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index a7d4dad42b52c..726b12c7e72af 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -18,8 +18,6 @@
 #include "mlir-c/Diagnostics.h"
 #include "mlir-c/IR.h"
 #include "mlir-c/Support.h"
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/SmallVector.h"
 
 #include <optional>
 
@@ -27,8 +25,6 @@ namespace nb = nanobind;
 using namespace nb::literals;
 using namespace mlir;
 
-using llvm::SmallVector;
-using llvm::StringRef;
 
 static const char kModuleParseDocstring[] =
     R"(Parses a module's assembly format from a string.
@@ -80,13 +76,13 @@ namespace MLIR_BINDINGS_PYTHON_DOMAIN {
 
 MlirBlock createBlock(const nb::sequence &pyArgTypes,
                       const std::optional<nb::sequence> &pyArgLocs) {
-  SmallVector<MlirType> argTypes;
+  std::vector<MlirType> argTypes;
   argTypes.reserve(nb::len(pyArgTypes));
   for (const auto &pyType : pyArgTypes)
     argTypes.push_back(
         nb::cast<python::MLIR_BINDINGS_PYTHON_DOMAIN::PyType &>(pyType));
 
-  SmallVector<MlirLocation> argLocs;
+  std::vector<MlirLocation> argLocs;
   if (pyArgLocs) {
     argLocs.reserve(nb::len(*pyArgLocs));
     for (const auto &pyLoc : *pyArgLocs)
@@ -1258,9 +1254,9 @@ nb::object PyOperation::create(std::string_view name,
                                std::optional<std::vector<PyBlock *>> successors,
                                int regions, PyLocation &location,
                                const nb::object &maybeIp, bool inferType) {
-  llvm::SmallVector<MlirType, 4> mlirResults;
-  llvm::SmallVector<MlirBlock, 4> mlirSuccessors;
-  llvm::SmallVector<std::pair<std::string, MlirAttribute>, 4> mlirAttributes;
+  std::vector<MlirType> mlirResults;
+  std::vector<MlirBlock> mlirSuccessors;
+  std::vector<std::pair<std::string, MlirAttribute>> mlirAttributes;
 
   // General parameter validation.
   if (regions < 0)
@@ -1333,7 +1329,7 @@ nb::object PyOperation::create(std::string_view name,
     // Note that the attribute names directly reference bytes in
     // mlirAttributes, so that vector must not be changed from here
     // on.
-    llvm::SmallVector<MlirNamedAttribute, 4> mlirNamedAttributes;
+    std::vector<MlirNamedAttribute> mlirNamedAttributes;
     mlirNamedAttributes.reserve(mlirAttributes.size());
     for (auto &it : mlirAttributes)
       mlirNamedAttributes.push_back(mlirNamedAttributeGet(
@@ -1347,7 +1343,7 @@ nb::object PyOperation::create(std::string_view name,
     mlirOperationStateAddSuccessors(&state, mlirSuccessors.size(),
                                     mlirSuccessors.data());
   if (regions) {
-    llvm::SmallVector<MlirRegion, 4> mlirRegions;
+    std::vector<MlirRegion> mlirRegions;
     mlirRegions.resize(regions);
     for (int i = 0; i < regions; ++i)
       mlirRegions[i] = mlirRegionCreate();
@@ -1641,7 +1637,7 @@ nb::object PyOpView::buildGeneric(
   }
 
   // Unpack operands.
-  llvm::SmallVector<MlirValue, 4> operands;
+  std::vector<MlirValue> operands;
   operands.reserve(operands.size());
   if (operandSegmentSpecObj.is_none()) {
     // Non-sized operand unpacking.
@@ -3248,7 +3244,7 @@ void populateIRCore(nb::module_ &m) {
           [](const std::vector<PyLocation> &pyLocations,
              std::optional<PyAttribute> metadata,
              DefaultingPyMlirContext context) {
-            llvm::SmallVector<MlirLocation, 4> locations;
+            std::vector<MlirLocation> locations;
             locations.reserve(pyLocations.size());
             for (auto &pyLocation : pyLocations)
               locations.push_back(pyLocation.get());
@@ -3752,7 +3748,7 @@ void populateIRCore(nb::module_ &m) {
              const nb::object &maybeIp,
              bool inferType) -> nb::typed<nb::object, PyOperation> {
             // Unpack/validate operands.
-            llvm::SmallVector<MlirValue, 4> mlirOperands;
+            std::vector<MlirValue> mlirOperands;
             if (operands) {
               mlirOperands.reserve(operands->size());
               for (PyValue *operand : *operands) {
@@ -4658,8 +4654,8 @@ void populateIRCore(nb::module_ &m) {
       .def(
           "replace_all_uses_except",
           [](PyValue &self, PyValue &with, const nb::list &exceptions) {
-            // Convert Python list to a SmallVector of MlirOperations
-            llvm::SmallVector<MlirOperation> exceptionOps;
+            // Convert Python list to a std::vector of MlirOperations
+            std::vector<MlirOperation> exceptionOps;
             for (nb::handle exception : exceptions) {
               exceptionOps.push_back(nb::cast<PyOperation &>(exception).get());
             }
@@ -4680,8 +4676,8 @@ void populateIRCore(nb::module_ &m) {
           "replace_all_uses_except",
           [](PyValue &self, PyValue &with,
              std::vector<PyOperation> &exceptions) {
-            // Convert Python list to a SmallVector of MlirOperations
-            llvm::SmallVector<MlirOperation> exceptionOps;
+            // Convert Python list to a std::vector of MlirOperations
+            std::vector<MlirOperation> exceptionOps;
             for (PyOperation &exception : exceptions)
               exceptionOps.push_back(exception);
             mlirValueReplaceAllUsesExcept(

>From 2e870e6fa65369af6c1561a6565b326bf9b5de6c Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 20:10:22 +0000
Subject: [PATCH 04/14] Run clang format

---
 mlir/include/mlir/Bindings/Python/IRCore.h |  3 +-
 mlir/lib/Bindings/Python/IRCore.cpp        | 66 +++++++++++-----------
 2 files changed, 34 insertions(+), 35 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index 1565ada0226c3..4ac341d759055 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -688,8 +688,7 @@ class MLIR_PYTHON_API_EXPORTED PyOperation : public PyOperationBase,
   /// Creates an operation. See corresponding python docstring.
   static nanobind::object
   create(std::string_view name, std::optional<std::vector<PyType *>> results,
-         const MlirValue *operands,
-         size_t numOperands,
+         const MlirValue *operands, size_t numOperands,
          std::optional<nanobind::dict> attributes,
          std::optional<std::vector<PyBlock *>> successors, int regions,
          PyLocation &location, const nanobind::object &ip, bool inferType);
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 726b12c7e72af..a2814100a77dc 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -25,7 +25,6 @@ namespace nb = nanobind;
 using namespace nb::literals;
 using namespace mlir;
 
-
 static const char kModuleParseDocstring[] =
     R"(Parses a module's assembly format from a string.
 
@@ -1099,9 +1098,10 @@ void PyOperationBase::writeBytecode(const nb::object &fileOrStringObject,
       operation, config, accum.getCallback(), accum.getUserData());
   mlirBytecodeWriterConfigDestroy(config);
   if (mlirLogicalResultIsFailure(res))
-    throw nb::value_error((std::string("Unable to honor desired bytecode version ") +
-                           std::to_string(*bytecodeVersion))
-                              .c_str());
+    throw nb::value_error(
+        (std::string("Unable to honor desired bytecode version ") +
+         std::to_string(*bytecodeVersion))
+            .c_str());
 }
 
 void PyOperationBase::walk(std::function<PyWalkResult(MlirOperation)> callback,
@@ -1248,8 +1248,7 @@ static void maybeInsertOperation(PyOperationRef &op,
 
 nb::object PyOperation::create(std::string_view name,
                                std::optional<std::vector<PyType *>> results,
-                               const MlirValue *operands,
-                               size_t numOperands,
+                               const MlirValue *operands, size_t numOperands,
                                std::optional<nb::dict> attributes,
                                std::optional<std::vector<PyBlock *>> successors,
                                int regions, PyLocation &location,
@@ -1477,7 +1476,8 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
       } catch (nb::cast_error &err) {
         throw nb::value_error((std::string("Result ") +
                                std::to_string(it.index()) + " of operation \"" +
-                               std::string(name) + "\" must be a Type (" + err.what() + ")")
+                               std::string(name) + "\" must be a Type (" +
+                               err.what() + ")")
                                   .c_str());
       }
     }
@@ -1515,9 +1515,9 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
           }
         } catch (nb::cast_error &err) {
           throw nb::value_error((std::string("Result ") +
-                                 std::to_string(it.index()) + " of operation \"" +
-                                 std::string(name) + "\" must be a Type (" + err.what() +
-                                 ")")
+                                 std::to_string(it.index()) +
+                                 " of operation \"" + std::string(name) +
+                                 "\" must be a Type (" + err.what() + ")")
                                     .c_str());
         }
       } else if (segmentSpec == -1) {
@@ -1541,12 +1541,12 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error((std::string("Result ") +
-                                 std::to_string(it.index()) + " of operation \"" +
-                                 name + "\" must be a Sequence of Types (" +
-                                 err.what() + ")")
-                                    .str()
-                                    .c_str());
+          throw nb::value_error(
+              (std::string("Result ") + std::to_string(it.index()) +
+               " of operation \"" + name + "\" must be a Sequence of Types (" +
+               err.what() + ")")
+                  .str()
+                  .c_str());
         }
       } else {
         throw nb::value_error("Unexpected segment spec");
@@ -1616,15 +1616,15 @@ nb::object PyOpView::buildGeneric(
   }
   if (*regions < opMinRegionCount) {
     throw nb::value_error(
-        (std::string("Operation \"") + std::string(name) + "\" requires a minimum of " +
-         std::to_string(opMinRegionCount) +
+        (std::string("Operation \"") + std::string(name) +
+         "\" requires a minimum of " + std::to_string(opMinRegionCount) +
          " regions but was built with regions=" + std::to_string(*regions))
             .c_str());
   }
   if (opHasNoVariadicRegions && *regions > opMinRegionCount) {
     throw nb::value_error(
-        (std::string("Operation \"") + std::string(name) + "\" requires a maximum of " +
-         std::to_string(opMinRegionCount) +
+        (std::string("Operation \"") + std::string(name) +
+         "\" requires a maximum of " + std::to_string(opMinRegionCount) +
          " regions but was built with regions=" + std::to_string(*regions))
             .c_str());
   }
@@ -1647,7 +1647,8 @@ nb::object PyOpView::buildGeneric(
       } catch (nb::builtin_exception &err) {
         throw nb::value_error((std::string("Operand ") +
                                std::to_string(it.index()) + " of operation \"" +
-                               std::string(name) + "\" must be a Value (" + err.what() + ")")
+                               std::string(name) + "\" must be a Value (" +
+                               err.what() + ")")
                                   .c_str());
       }
     }
@@ -1676,8 +1677,7 @@ nb::object PyOpView::buildGeneric(
           } catch (nb::builtin_exception &err) {
             throw nb::value_error((std::string("Operand ") +
                                    std::to_string(it.index()) +
-                                   " of operation \"" 
-                                   + std::string(name) +
+                                   " of operation \"" + std::string(name) +
                                    "\" must be a Value (" + err.what() + ")")
                                       .c_str());
           }
@@ -1711,11 +1711,11 @@ nb::object PyOpView::buildGeneric(
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error((std::string("Operand ") +
-                                 std::to_string(it.index()) + " of operation \"" +
-                                 std::string(name) + "\" must be a Sequence of Values (" +
-                                 err.what() + ")")
-                                    .c_str());
+          throw nb::value_error(
+              (std::string("Operand ") + std::to_string(it.index()) +
+               " of operation \"" + std::string(name) +
+               "\" must be a Sequence of Values (" + err.what() + ")")
+                  .c_str());
         }
       } else {
         throw nb::value_error("Unexpected segment spec");
@@ -3759,9 +3759,9 @@ void populateIRCore(nb::module_ &m) {
             }
 
             PyLocation pyLoc = maybeGetTracebackLocation(location);
-            return PyOperation::create(name, results, mlirOperands.data(), mlirOperands.size(), attributes,
-                                       successors, regions, pyLoc, maybeIp,
-                                       inferType);
+            return PyOperation::create(
+                name, results, mlirOperands.data(), mlirOperands.size(),
+                attributes, successors, regions, pyLoc, maybeIp, inferType);
           },
           "name"_a, "results"_a = nb::none(), "operands"_a = nb::none(),
           "attributes"_a = nb::none(), "successors"_a = nb::none(),
@@ -3929,8 +3929,8 @@ void populateIRCore(nb::module_ &m) {
             mlirIdentifierStr(mlirOperationGetName(*parsed.get()));
         std::string_view parsedOpName(identifier.data, identifier.length);
         if (clsOpName != parsedOpName)
-          throw MLIRError(std::string("Expected a '") + std::string(clsOpName) + "' op, got: '" +
-                          std::string(parsedOpName) + "'");
+          throw MLIRError(std::string("Expected a '") + std::string(clsOpName) +
+                          "' op, got: '" + std::string(parsedOpName) + "'");
         return PyOpView::constructDerived(cls, parsed.getObject());
       },
       "cls"_a, "source"_a, nb::kw_only(), "source_name"_a = "",

>From 748525c6ee8b11f1db5b222daf4bd6e099dab5f1 Mon Sep 17 00:00:00 2001
From: RattataKing <46631728+RattataKing at users.noreply.github.com>
Date: Tue, 27 Jan 2026 15:39:58 -0500
Subject: [PATCH 05/14] Add std::move to prevent extra copy

Co-authored-by: Jakub Kuderski <kubakuderski at gmail.com>
---
 mlir/include/mlir/Bindings/Python/IRCore.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index 4ac341d759055..42fc2b8fd22c7 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -1328,7 +1328,7 @@ class MLIR_PYTHON_API_EXPORTED PySymbolTable {
 struct MLIR_PYTHON_API_EXPORTED MLIRError {
   MLIRError(std::string message,
             std::vector<PyDiagnostic::DiagnosticInfo> &&errorDiagnostics = {})
-      : message(message), errorDiagnostics(std::move(errorDiagnostics)) {}
+      : message(std::move(message)), errorDiagnostics(std::move(errorDiagnostics)) {}
   std::string message;
   std::vector<PyDiagnostic::DiagnosticInfo> errorDiagnostics;
 };

>From 2e6762b17307a86b3832135bb3dc29cde3348709 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 21:40:47 +0000
Subject: [PATCH 06/14] Add join

---
 mlir/lib/Bindings/Python/IRCore.cpp | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index a2814100a77dc..4fe0aabf8359f 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -20,6 +20,8 @@
 #include "mlir-c/Support.h"
 
 #include <optional>
+#include <sstream>
+#include <string>
 
 namespace nb = nanobind;
 using namespace nb::literals;
@@ -46,6 +48,14 @@ operations.
 // Utilities.
 //------------------------------------------------------------------------------
 
+/// Local helper to concatenate string and integer arguments into a std::string.
+template <typename... Ts>
+std::string join(const Ts&... args) {
+  std::ostringstream oss;
+  (oss << ... << args);
+  return oss.str();
+}
+
 /// Helper for creating an @classmethod.
 template <class Func, typename... Args>
 static nb::object classmethod(Func f, Args... args) {
@@ -94,8 +104,8 @@ MlirBlock createBlock(const nb::sequence &pyArgTypes,
   }
 
   if (argTypes.size() != argLocs.size())
-    throw nb::value_error(("Expected " + std::to_string(argTypes.size()) +
-                           " locations, got: " + std::to_string(argLocs.size()))
+    throw nb::value_error(join("Expected ", argTypes.size(), 
+                           " locations, got: ", argLocs.size())
                               .c_str());
   return mlirBlockCreate(argTypes.size(), argTypes.data(), argLocs.data());
 }
@@ -1098,9 +1108,9 @@ void PyOperationBase::writeBytecode(const nb::object &fileOrStringObject,
       operation, config, accum.getCallback(), accum.getUserData());
   mlirBytecodeWriterConfigDestroy(config);
   if (mlirLogicalResultIsFailure(res))
-    throw nb::value_error(
-        (std::string("Unable to honor desired bytecode version ") +
-         std::to_string(*bytecodeVersion))
+    throw nb::value_error(join
+        ("Unable to honor desired bytecode version ", 
+         *bytecodeVersion)
             .c_str());
 }
 

>From 7c3f9f3bdd4af59e1679aeca000c91a5782a95c9 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 21:46:44 +0000
Subject: [PATCH 07/14] Replace StringRef with string_view

---
 mlir/lib/Bindings/Python/IRCore.cpp | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 4fe0aabf8359f..63f60c42dd24d 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -1386,7 +1386,7 @@ nb::object PyOperation::createOpView() {
   MlirIdentifier ident = mlirOperationGetName(get());
   MlirStringRef identStr = mlirIdentifierStr(ident);
   auto operationCls = PyGlobals::get().lookupOperationClass(
-      StringRef(identStr.data, identStr.length));
+      std::string_view(identStr.data, identStr.length));
   if (operationCls)
     return PyOpView::constructDerived(*operationCls, getRef().getObject());
   return nb::cast(PyOpView(getRef().getObject()));
@@ -1471,7 +1471,7 @@ PyOpResultList PyOpResultList::slice(intptr_t startIndex, intptr_t length,
 // PyOpView
 //------------------------------------------------------------------------------
 
-static void populateResultTypes(StringRef name, nb::list resultTypeList,
+static void populateResultTypes(std::string_view name, nb::list resultTypeList,
                                 const nb::object &resultSegmentSpecObj,
                                 std::vector<int32_t> &resultSegmentLengths,
                                 std::vector<PyType *> &resultTypes) {
@@ -1553,9 +1553,8 @@ static void populateResultTypes(StringRef name, nb::list resultTypeList,
           // above "casts". Just keep the scope above small and catch them all.
           throw nb::value_error(
               (std::string("Result ") + std::to_string(it.index()) +
-               " of operation \"" + name + "\" must be a Sequence of Types (" +
+               " of operation \"" + std::string(name) + "\" must be a Sequence of Types (" +
                err.what() + ")")
-                  .str()
                   .c_str());
         }
       } else {
@@ -1570,11 +1569,10 @@ MlirValue getUniqueResult(MlirOperation operation) {
   if (numResults != 1) {
     auto name = mlirIdentifierStr(mlirOperationGetName(operation));
     throw nb::value_error((std::string("Cannot call .result on operation ") +
-                           StringRef(name.data, name.length) + " which has " +
+                           std::string(name.data, name.length) + " which has " +
                            std::to_string(numResults) +
                            " results (it is only valid for operations with a "
                            "single result)")
-                              .str()
                               .c_str());
   }
   return mlirOperationGetResult(operation, 0);
@@ -2614,7 +2612,7 @@ MlirLocation tracebackToLocation(MlirContext ctx) {
     PyCodeObject *code = PyFrame_GetCode(pyFrame);
     auto fileNameStr =
         nb::cast<std::string>(nb::borrow<nb::str>(code->co_filename));
-    llvm::StringRef fileName(fileNameStr);
+    std::string_view fileName(fileNameStr);
     if (!PyGlobals::get().getTracebackLoc().isUserTracebackFilename(fileName))
       continue;
 
@@ -2622,14 +2620,14 @@ MlirLocation tracebackToLocation(MlirContext ctx) {
 #if PY_VERSION_HEX < 0x030B00F0
     std::string name =
         nb::cast<std::string>(nb::borrow<nb::str>(code->co_name));
-    llvm::StringRef funcName(name);
+    std::string_view funcName(name);
     int startLine = PyFrame_GetLineNumber(pyFrame);
     MlirLocation loc =
         mlirLocationFileLineColGet(ctx, wrap(fileName), startLine, 0);
 #else
     std::string name =
         nb::cast<std::string>(nb::borrow<nb::str>(code->co_qualname));
-    llvm::StringRef funcName(name);
+    std::string_view funcName(name);
     int startLine, startCol, endLine, endCol;
     int lasti = PyFrame_GetLasti(pyFrame);
     if (!PyCode_Addr2Location(code, lasti, &startLine, &startCol, &endLine,

>From 2141bcb238e14be20606264e259df51381894edf Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 22:40:52 +0000
Subject: [PATCH 08/14] Apply join

---
 mlir/lib/Bindings/Python/IRCore.cpp | 96 ++++++++++++++---------------
 1 file changed, 45 insertions(+), 51 deletions(-)

diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 63f60c42dd24d..cdd871d4aad19 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -1484,10 +1484,9 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
         if (!resultTypes.back())
           throw nb::cast_error();
       } catch (nb::cast_error &err) {
-        throw nb::value_error((std::string("Result ") +
-                               std::to_string(it.index()) + " of operation \"" +
-                               std::string(name) + "\" must be a Type (" +
-                               err.what() + ")")
+        throw nb::value_error(join("Result ", it.index(), " of operation \"", 
+                               name, "\" must be a Type (", 
+                               err.what(), ")")
                                   .c_str());
       }
     }
@@ -1495,11 +1494,10 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
     // Sized result unpacking.
     auto resultSegmentSpec = nb::cast<std::vector<int>>(resultSegmentSpecObj);
     if (resultSegmentSpec.size() != resultTypeList.size()) {
-      throw nb::value_error((std::string("Operation \"") + std::string(name) +
-                             "\" requires " +
-                             std::to_string(resultSegmentSpec.size()) +
-                             " result segments but was provided " +
-                             std::to_string(resultTypeList.size()))
+      throw nb::value_error(join("Operation \"", name, 
+                             "\" requires ", resultSegmentSpec.size(), 
+                             " result segments but was provided ", 
+                             resultTypeList.size())
                                 .c_str());
     }
     resultSegmentLengths.reserve(resultTypeList.size());
@@ -1517,17 +1515,16 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
             // Allowed to be optional.
             resultSegmentLengths.push_back(0);
           } else {
-            throw nb::value_error(
-                (std::string("Result ") + std::to_string(it.index()) +
-                 " of operation \"" + std::string(name) +
+            throw nb::value_error(join
+                ("Result ", it.index(), 
+                 " of operation \"", name, 
                  "\" must be a Type (was None and result is not optional)")
                     .c_str());
           }
         } catch (nb::cast_error &err) {
-          throw nb::value_error((std::string("Result ") +
-                                 std::to_string(it.index()) +
-                                 " of operation \"" + std::string(name) +
-                                 "\" must be a Type (" + err.what() + ")")
+          throw nb::value_error(join("Result ", it.index(), 
+                                 " of operation \"" , name, 
+                                 "\" must be a Type (", err.what(), ")")
                                     .c_str());
         }
       } else if (segmentSpec == -1) {
@@ -1551,10 +1548,10 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error(
-              (std::string("Result ") + std::to_string(it.index()) +
-               " of operation \"" + std::string(name) + "\" must be a Sequence of Types (" +
-               err.what() + ")")
+          throw nb::value_error(join
+              ("Result ", it.index(), 
+               " of operation \"", name, "\" must be a Sequence of Types (", 
+               err.what(), ")")
                   .c_str());
         }
       } else {
@@ -1568,9 +1565,8 @@ MlirValue getUniqueResult(MlirOperation operation) {
   auto numResults = mlirOperationGetNumResults(operation);
   if (numResults != 1) {
     auto name = mlirIdentifierStr(mlirOperationGetName(operation));
-    throw nb::value_error((std::string("Cannot call .result on operation ") +
-                           std::string(name.data, name.length) + " which has " +
-                           std::to_string(numResults) +
+    throw nb::value_error(join("Cannot call .result on operation ", 
+                           std::string_view(name.data, name.length), " which has ", numResults, 
                            " results (it is only valid for operations with a "
                            "single result)")
                               .c_str());
@@ -1623,17 +1619,17 @@ nb::object PyOpView::buildGeneric(
     regions = opMinRegionCount;
   }
   if (*regions < opMinRegionCount) {
-    throw nb::value_error(
-        (std::string("Operation \"") + std::string(name) +
-         "\" requires a minimum of " + std::to_string(opMinRegionCount) +
-         " regions but was built with regions=" + std::to_string(*regions))
+    throw nb::value_error(join
+        ("Operation \"", name, 
+         "\" requires a minimum of ", opMinRegionCount, 
+         " regions but was built with regions=", *regions)
             .c_str());
   }
   if (opHasNoVariadicRegions && *regions > opMinRegionCount) {
-    throw nb::value_error(
-        (std::string("Operation \"") + std::string(name) +
-         "\" requires a maximum of " + std::to_string(opMinRegionCount) +
-         " regions but was built with regions=" + std::to_string(*regions))
+    throw nb::value_error(join
+        ("Operation \"", name, 
+         "\" requires a maximum of ", opMinRegionCount, 
+         " regions but was built with regions=", *regions)
             .c_str());
   }
 
@@ -1653,10 +1649,9 @@ nb::object PyOpView::buildGeneric(
       try {
         operands.push_back(getOpResultOrValue(it.value()));
       } catch (nb::builtin_exception &err) {
-        throw nb::value_error((std::string("Operand ") +
-                               std::to_string(it.index()) + " of operation \"" +
-                               std::string(name) + "\" must be a Value (" +
-                               err.what() + ")")
+        throw nb::value_error(join("Operand ", it.index(), " of operation \"", 
+                               name, "\" must be a Value (", 
+                               err.what(), ")")
                                   .c_str());
       }
     }
@@ -1664,11 +1659,11 @@ nb::object PyOpView::buildGeneric(
     // Sized operand unpacking.
     auto operandSegmentSpec = nb::cast<std::vector<int>>(operandSegmentSpecObj);
     if (operandSegmentSpec.size() != operandList.size()) {
-      throw nb::value_error((std::string("Operation \"") + std::string(name) +
-                             "\" requires " +
-                             std::to_string(operandSegmentSpec.size()) +
-                             "operand segments but was provided " +
-                             std::to_string(operandList.size()))
+      throw nb::value_error(join("Operation \"", name,
+                             "\" requires ", 
+                             operandSegmentSpec.size(), 
+                             "operand segments but was provided ", 
+                             operandList.size())
                                 .c_str());
     }
     operandSegmentLengths.reserve(operandList.size());
@@ -1683,10 +1678,9 @@ nb::object PyOpView::buildGeneric(
 
             operands.push_back(getOpResultOrValue(operand));
           } catch (nb::builtin_exception &err) {
-            throw nb::value_error((std::string("Operand ") +
-                                   std::to_string(it.index()) +
-                                   " of operation \"" + std::string(name) +
-                                   "\" must be a Value (" + err.what() + ")")
+            throw nb::value_error(join("Operand ", it.index(), 
+                                   " of operation \"", name, 
+                                   "\" must be a Value (",  err.what(), ")")
                                       .c_str());
           }
 
@@ -1695,9 +1689,9 @@ nb::object PyOpView::buildGeneric(
           // Allowed to be optional.
           operandSegmentLengths.push_back(0);
         } else {
-          throw nb::value_error(
-              (std::string("Operand ") + std::to_string(it.index()) +
-               " of operation \"" + std::string(name) +
+          throw nb::value_error(join
+              ("Operand ", it.index(), 
+               " of operation \"", name, 
                "\" must be a Value (was None and operand is not optional)")
                   .c_str());
         }
@@ -1719,10 +1713,10 @@ nb::object PyOpView::buildGeneric(
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error(
-              (std::string("Operand ") + std::to_string(it.index()) +
-               " of operation \"" + std::string(name) +
-               "\" must be a Sequence of Values (" + err.what() + ")")
+          throw nb::value_error(join
+              ("Operand ", it.index(), 
+               " of operation \"", name, 
+               "\" must be a Sequence of Values (", err.what(), ")")
                   .c_str());
         }
       } else {

>From bc1eec859b9970803c50d78a03f5f2af9f246d50 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 22:45:47 +0000
Subject: [PATCH 09/14] Apply more join

---
 mlir/lib/Bindings/Python/IRCore.cpp | 29 +++++++++++++----------------
 1 file changed, 13 insertions(+), 16 deletions(-)

diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index cdd871d4aad19..26deb7d0409c7 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -1289,9 +1289,8 @@ nb::object PyOperation::create(std::string_view name,
       try {
         key = nb::cast<std::string>(it.first);
       } catch (nb::cast_error &err) {
-        std::string msg = "Invalid attribute key (not a string) when "
-                          "attempting to create the operation \"" +
-                          std::string(name) + "\" (" + err.what() + ")";
+        std::string msg = join("Invalid attribute key (not a string) when "
+                          "attempting to create the operation \"", name, "\" (", err.what(), ")");
         throw nb::type_error(msg.c_str());
       }
       try {
@@ -1299,16 +1298,14 @@ nb::object PyOperation::create(std::string_view name,
         // TODO: Verify attribute originates from the same context.
         mlirAttributes.emplace_back(std::move(key), attribute);
       } catch (nb::cast_error &err) {
-        std::string msg = "Invalid attribute value for the key \"" + key +
-                          "\" when attempting to create the operation \"" +
-                          std::string(name) + "\" (" + err.what() + ")";
+        std::string msg = join("Invalid attribute value for the key \"", key, 
+                          "\" when attempting to create the operation \"", name, "\" (", err.what(), ")");
         throw nb::type_error(msg.c_str());
       } catch (std::runtime_error &) {
         // This exception seems thrown when the value is "None".
-        std::string msg =
-            "Found an invalid (`None`?) attribute value for the key \"" + key +
-            "\" when attempting to create the operation \"" +
-            std::string(name) + "\"";
+        std::string msg =join(
+            "Found an invalid (`None`?) attribute value for the key \"", key, 
+            "\" when attempting to create the operation \"",name, "\"");
         throw std::runtime_error(msg);
       }
     }
@@ -2057,8 +2054,8 @@ nb::object PySymbolTable::dunderGetItem(const std::string &name) {
   MlirOperation symbol = mlirSymbolTableLookup(
       symbolTable, mlirStringRefCreate(name.data(), name.length()));
   if (mlirOperationIsNull(symbol))
-    throw nb::key_error(
-        ("Symbol '" + name + "' not in the symbol table.").c_str());
+    throw nb::key_error(join
+        ("Symbol '", name, "' not in the symbol table.").c_str());
 
   return PyOperation::forOperation(operation->getContext(), symbol,
                                    operation.getObject())
@@ -2940,8 +2937,8 @@ void populateIRCore(nb::module_ &m) {
             MlirDialect dialect = mlirContextGetOrLoadDialect(
                 self.get(), {name.data(), name.size()});
             if (mlirDialectIsNull(dialect)) {
-              throw nb::value_error(
-                  (std::string("Dialect '") + name + "' not found").c_str());
+              throw nb::value_error(join
+                  ("Dialect '", name, "' not found").c_str());
             }
             return PyDialectDescriptor(self.getRef(), dialect);
           },
@@ -3931,8 +3928,8 @@ void populateIRCore(nb::module_ &m) {
             mlirIdentifierStr(mlirOperationGetName(*parsed.get()));
         std::string_view parsedOpName(identifier.data, identifier.length);
         if (clsOpName != parsedOpName)
-          throw MLIRError(std::string("Expected a '") + std::string(clsOpName) +
-                          "' op, got: '" + std::string(parsedOpName) + "'");
+          throw MLIRError(join("Expected a '", clsOpName, 
+                          "' op, got: '", parsedOpName, "'"));
         return PyOpView::constructDerived(cls, parsed.getObject());
       },
       "cls"_a, "source"_a, nb::kw_only(), "source_name"_a = "",

>From c02dc835dbbad051d10db98c9bef7eeb5fa1cb23 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 22:47:00 +0000
Subject: [PATCH 10/14] Run clang-format

---
 mlir/include/mlir/Bindings/Python/IRCore.h |   3 +-
 mlir/lib/Bindings/Python/IRCore.cpp        | 143 ++++++++++-----------
 2 files changed, 70 insertions(+), 76 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index 42fc2b8fd22c7..c706fdedbc41d 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -1328,7 +1328,8 @@ class MLIR_PYTHON_API_EXPORTED PySymbolTable {
 struct MLIR_PYTHON_API_EXPORTED MLIRError {
   MLIRError(std::string message,
             std::vector<PyDiagnostic::DiagnosticInfo> &&errorDiagnostics = {})
-      : message(std::move(message)), errorDiagnostics(std::move(errorDiagnostics)) {}
+      : message(std::move(message)),
+        errorDiagnostics(std::move(errorDiagnostics)) {}
   std::string message;
   std::vector<PyDiagnostic::DiagnosticInfo> errorDiagnostics;
 };
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 26deb7d0409c7..dbb44d459497d 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -50,7 +50,7 @@ operations.
 
 /// Local helper to concatenate string and integer arguments into a std::string.
 template <typename... Ts>
-std::string join(const Ts&... args) {
+std::string join(const Ts &...args) {
   std::ostringstream oss;
   (oss << ... << args);
   return oss.str();
@@ -104,9 +104,9 @@ MlirBlock createBlock(const nb::sequence &pyArgTypes,
   }
 
   if (argTypes.size() != argLocs.size())
-    throw nb::value_error(join("Expected ", argTypes.size(), 
-                           " locations, got: ", argLocs.size())
-                              .c_str());
+    throw nb::value_error(
+        join("Expected ", argTypes.size(), " locations, got: ", argLocs.size())
+            .c_str());
   return mlirBlockCreate(argTypes.size(), argTypes.data(), argLocs.data());
 }
 
@@ -1108,9 +1108,8 @@ void PyOperationBase::writeBytecode(const nb::object &fileOrStringObject,
       operation, config, accum.getCallback(), accum.getUserData());
   mlirBytecodeWriterConfigDestroy(config);
   if (mlirLogicalResultIsFailure(res))
-    throw nb::value_error(join
-        ("Unable to honor desired bytecode version ", 
-         *bytecodeVersion)
+    throw nb::value_error(
+        join("Unable to honor desired bytecode version ", *bytecodeVersion)
             .c_str());
 }
 
@@ -1290,7 +1289,8 @@ nb::object PyOperation::create(std::string_view name,
         key = nb::cast<std::string>(it.first);
       } catch (nb::cast_error &err) {
         std::string msg = join("Invalid attribute key (not a string) when "
-                          "attempting to create the operation \"", name, "\" (", err.what(), ")");
+                               "attempting to create the operation \"",
+                               name, "\" (", err.what(), ")");
         throw nb::type_error(msg.c_str());
       }
       try {
@@ -1298,14 +1298,15 @@ nb::object PyOperation::create(std::string_view name,
         // TODO: Verify attribute originates from the same context.
         mlirAttributes.emplace_back(std::move(key), attribute);
       } catch (nb::cast_error &err) {
-        std::string msg = join("Invalid attribute value for the key \"", key, 
-                          "\" when attempting to create the operation \"", name, "\" (", err.what(), ")");
+        std::string msg = join("Invalid attribute value for the key \"", key,
+                               "\" when attempting to create the operation \"",
+                               name, "\" (", err.what(), ")");
         throw nb::type_error(msg.c_str());
       } catch (std::runtime_error &) {
         // This exception seems thrown when the value is "None".
-        std::string msg =join(
-            "Found an invalid (`None`?) attribute value for the key \"", key, 
-            "\" when attempting to create the operation \"",name, "\"");
+        std::string msg = join(
+            "Found an invalid (`None`?) attribute value for the key \"", key,
+            "\" when attempting to create the operation \"", name, "\"");
         throw std::runtime_error(msg);
       }
     }
@@ -1481,9 +1482,8 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
         if (!resultTypes.back())
           throw nb::cast_error();
       } catch (nb::cast_error &err) {
-        throw nb::value_error(join("Result ", it.index(), " of operation \"", 
-                               name, "\" must be a Type (", 
-                               err.what(), ")")
+        throw nb::value_error(join("Result ", it.index(), " of operation \"",
+                                   name, "\" must be a Type (", err.what(), ")")
                                   .c_str());
       }
     }
@@ -1491,11 +1491,10 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
     // Sized result unpacking.
     auto resultSegmentSpec = nb::cast<std::vector<int>>(resultSegmentSpecObj);
     if (resultSegmentSpec.size() != resultTypeList.size()) {
-      throw nb::value_error(join("Operation \"", name, 
-                             "\" requires ", resultSegmentSpec.size(), 
-                             " result segments but was provided ", 
-                             resultTypeList.size())
-                                .c_str());
+      throw nb::value_error(
+          join("Operation \"", name, "\" requires ", resultSegmentSpec.size(),
+               " result segments but was provided ", resultTypeList.size())
+              .c_str());
     }
     resultSegmentLengths.reserve(resultTypeList.size());
     for (const auto &it :
@@ -1512,16 +1511,15 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
             // Allowed to be optional.
             resultSegmentLengths.push_back(0);
           } else {
-            throw nb::value_error(join
-                ("Result ", it.index(), 
-                 " of operation \"", name, 
-                 "\" must be a Type (was None and result is not optional)")
+            throw nb::value_error(
+                join("Result ", it.index(), " of operation \"", name,
+                     "\" must be a Type (was None and result is not optional)")
                     .c_str());
           }
         } catch (nb::cast_error &err) {
-          throw nb::value_error(join("Result ", it.index(), 
-                                 " of operation \"" , name, 
-                                 "\" must be a Type (", err.what(), ")")
+          throw nb::value_error(join("Result ", it.index(), " of operation \"",
+                                     name, "\" must be a Type (", err.what(),
+                                     ")")
                                     .c_str());
         }
       } else if (segmentSpec == -1) {
@@ -1545,11 +1543,10 @@ static void populateResultTypes(std::string_view name, nb::list resultTypeList,
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error(join
-              ("Result ", it.index(), 
-               " of operation \"", name, "\" must be a Sequence of Types (", 
-               err.what(), ")")
-                  .c_str());
+          throw nb::value_error(join("Result ", it.index(), " of operation \"",
+                                     name, "\" must be a Sequence of Types (",
+                                     err.what(), ")")
+                                    .c_str());
         }
       } else {
         throw nb::value_error("Unexpected segment spec");
@@ -1562,11 +1559,13 @@ MlirValue getUniqueResult(MlirOperation operation) {
   auto numResults = mlirOperationGetNumResults(operation);
   if (numResults != 1) {
     auto name = mlirIdentifierStr(mlirOperationGetName(operation));
-    throw nb::value_error(join("Cannot call .result on operation ", 
-                           std::string_view(name.data, name.length), " which has ", numResults, 
-                           " results (it is only valid for operations with a "
-                           "single result)")
-                              .c_str());
+    throw nb::value_error(
+        join("Cannot call .result on operation ",
+             std::string_view(name.data, name.length), " which has ",
+             numResults,
+             " results (it is only valid for operations with a "
+             "single result)")
+            .c_str());
   }
   return mlirOperationGetResult(operation, 0);
 }
@@ -1616,18 +1615,16 @@ nb::object PyOpView::buildGeneric(
     regions = opMinRegionCount;
   }
   if (*regions < opMinRegionCount) {
-    throw nb::value_error(join
-        ("Operation \"", name, 
-         "\" requires a minimum of ", opMinRegionCount, 
-         " regions but was built with regions=", *regions)
-            .c_str());
+    throw nb::value_error(join("Operation \"", name,
+                               "\" requires a minimum of ", opMinRegionCount,
+                               " regions but was built with regions=", *regions)
+                              .c_str());
   }
   if (opHasNoVariadicRegions && *regions > opMinRegionCount) {
-    throw nb::value_error(join
-        ("Operation \"", name, 
-         "\" requires a maximum of ", opMinRegionCount, 
-         " regions but was built with regions=", *regions)
-            .c_str());
+    throw nb::value_error(join("Operation \"", name,
+                               "\" requires a maximum of ", opMinRegionCount,
+                               " regions but was built with regions=", *regions)
+                              .c_str());
   }
 
   // Unpack results.
@@ -1646,9 +1643,9 @@ nb::object PyOpView::buildGeneric(
       try {
         operands.push_back(getOpResultOrValue(it.value()));
       } catch (nb::builtin_exception &err) {
-        throw nb::value_error(join("Operand ", it.index(), " of operation \"", 
-                               name, "\" must be a Value (", 
-                               err.what(), ")")
+        throw nb::value_error(join("Operand ", it.index(), " of operation \"",
+                                   name, "\" must be a Value (", err.what(),
+                                   ")")
                                   .c_str());
       }
     }
@@ -1656,12 +1653,10 @@ nb::object PyOpView::buildGeneric(
     // Sized operand unpacking.
     auto operandSegmentSpec = nb::cast<std::vector<int>>(operandSegmentSpecObj);
     if (operandSegmentSpec.size() != operandList.size()) {
-      throw nb::value_error(join("Operation \"", name,
-                             "\" requires ", 
-                             operandSegmentSpec.size(), 
-                             "operand segments but was provided ", 
-                             operandList.size())
-                                .c_str());
+      throw nb::value_error(
+          join("Operation \"", name, "\" requires ", operandSegmentSpec.size(),
+               "operand segments but was provided ", operandList.size())
+              .c_str());
     }
     operandSegmentLengths.reserve(operandList.size());
     for (const auto &it :
@@ -1675,9 +1670,9 @@ nb::object PyOpView::buildGeneric(
 
             operands.push_back(getOpResultOrValue(operand));
           } catch (nb::builtin_exception &err) {
-            throw nb::value_error(join("Operand ", it.index(), 
-                                   " of operation \"", name, 
-                                   "\" must be a Value (",  err.what(), ")")
+            throw nb::value_error(join("Operand ", it.index(),
+                                       " of operation \"", name,
+                                       "\" must be a Value (", err.what(), ")")
                                       .c_str());
           }
 
@@ -1686,10 +1681,9 @@ nb::object PyOpView::buildGeneric(
           // Allowed to be optional.
           operandSegmentLengths.push_back(0);
         } else {
-          throw nb::value_error(join
-              ("Operand ", it.index(), 
-               " of operation \"", name, 
-               "\" must be a Value (was None and operand is not optional)")
+          throw nb::value_error(
+              join("Operand ", it.index(), " of operation \"", name,
+                   "\" must be a Value (was None and operand is not optional)")
                   .c_str());
         }
       } else if (segmentSpec == -1) {
@@ -1710,11 +1704,10 @@ nb::object PyOpView::buildGeneric(
           // NOTE: Sloppy to be using a catch-all here, but there are at least
           // three different unrelated exceptions that can be thrown in the
           // above "casts". Just keep the scope above small and catch them all.
-          throw nb::value_error(join
-              ("Operand ", it.index(), 
-               " of operation \"", name, 
-               "\" must be a Sequence of Values (", err.what(), ")")
-                  .c_str());
+          throw nb::value_error(join("Operand ", it.index(), " of operation \"",
+                                     name, "\" must be a Sequence of Values (",
+                                     err.what(), ")")
+                                    .c_str());
         }
       } else {
         throw nb::value_error("Unexpected segment spec");
@@ -2054,8 +2047,8 @@ nb::object PySymbolTable::dunderGetItem(const std::string &name) {
   MlirOperation symbol = mlirSymbolTableLookup(
       symbolTable, mlirStringRefCreate(name.data(), name.length()));
   if (mlirOperationIsNull(symbol))
-    throw nb::key_error(join
-        ("Symbol '", name, "' not in the symbol table.").c_str());
+    throw nb::key_error(
+        join("Symbol '", name, "' not in the symbol table.").c_str());
 
   return PyOperation::forOperation(operation->getContext(), symbol,
                                    operation.getObject())
@@ -2937,8 +2930,8 @@ void populateIRCore(nb::module_ &m) {
             MlirDialect dialect = mlirContextGetOrLoadDialect(
                 self.get(), {name.data(), name.size()});
             if (mlirDialectIsNull(dialect)) {
-              throw nb::value_error(join
-                  ("Dialect '", name, "' not found").c_str());
+              throw nb::value_error(
+                  join("Dialect '", name, "' not found").c_str());
             }
             return PyDialectDescriptor(self.getRef(), dialect);
           },
@@ -3928,8 +3921,8 @@ void populateIRCore(nb::module_ &m) {
             mlirIdentifierStr(mlirOperationGetName(*parsed.get()));
         std::string_view parsedOpName(identifier.data, identifier.length);
         if (clsOpName != parsedOpName)
-          throw MLIRError(join("Expected a '", clsOpName, 
-                          "' op, got: '", parsedOpName, "'"));
+          throw MLIRError(join("Expected a '", clsOpName, "' op, got: '",
+                               parsedOpName, "'"));
         return PyOpView::constructDerived(cls, parsed.getObject());
       },
       "cls"_a, "source"_a, nb::kw_only(), "source_name"_a = "",

>From ac07c9c77686675a567625f8e4c82e88e6cecd1f Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 22:58:35 +0000
Subject: [PATCH 11/14] Replace densemap with unorder_map

---
 mlir/include/mlir/Bindings/Python/IRCore.h | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index c706fdedbc41d..c91e17e5a239a 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -30,7 +30,6 @@
 #include "mlir/Bindings/Python/Nanobind.h"
 #include "mlir/Bindings/Python/NanobindAdaptors.h"
 
-#include "llvm/ADT/DenseMap.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/ThreadPool.h"
 
@@ -256,7 +255,7 @@ class MLIR_PYTHON_API_EXPORTED PyMlirContext {
   // extension mechanism on the MlirContext for stashing user pointers.
   // Note that this holds a handle, which does not imply ownership.
   // Mappings will be removed when the context is destructed.
-  using LiveContextMap = llvm::DenseMap<void *, PyMlirContext *>;
+  using LiveContextMap = std::unordered_map<void *, PyMlirContext *>;
   static nanobind::ft_mutex live_contexts_mutex;
   static LiveContextMap &getLiveContexts();
 
@@ -265,7 +264,7 @@ class MLIR_PYTHON_API_EXPORTED PyMlirContext {
   // from this map, and while it still exists as an instance, any
   // attempt to access it will raise an error.
   using LiveModuleMap =
-      llvm::DenseMap<const void *, std::pair<nanobind::handle, PyModule *>>;
+      std::unordered_map<const void *, std::pair<nanobind::handle, PyModule *>>;
   LiveModuleMap liveModules;
 
   bool emitErrorDiagnostics = false;

>From 85258531cdfd539684f8bea1d175d9bcb0fd44e6 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Tue, 27 Jan 2026 23:08:25 +0000
Subject: [PATCH 12/14] Replace function_ref with std function

---
 mlir/include/mlir/Bindings/Python/IRCore.h | 5 ++---
 mlir/lib/Bindings/Python/IRCore.cpp        | 3 +--
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index c91e17e5a239a..b4c27f26545e8 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -1820,9 +1820,8 @@ class MLIR_PYTHON_API_EXPORTED PyOpAttributeMap {
 
   bool dunderContains(const std::string &name);
 
-  static void
-  forEachAttr(MlirOperation op,
-              llvm::function_ref<void(MlirStringRef, MlirAttribute)> fn);
+  static void forEachAttr(MlirOperation op,
+                          std::function<void(MlirStringRef, MlirAttribute)> fn);
 
   static void bind(nanobind::module_ &m);
 
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index dbb44d459497d..7c6309874c754 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -2422,8 +2422,7 @@ bool PyOpAttributeMap::dunderContains(const std::string &name) {
 }
 
 void PyOpAttributeMap::forEachAttr(
-    MlirOperation op,
-    llvm::function_ref<void(MlirStringRef, MlirAttribute)> fn) {
+    MlirOperation op, std::function<void(MlirStringRef, MlirAttribute)> fn) {
   intptr_t n = mlirOperationGetNumAttributes(op);
   for (intptr_t i = 0; i < n; ++i) {
     MlirNamedAttribute na = mlirOperationGetAttribute(op, i);

>From bf6e023a232ce45178b3d61ff6c0e1e906c2b087 Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Wed, 28 Jan 2026 16:12:59 +0000
Subject: [PATCH 13/14] Small fix

---
 mlir/lib/Bindings/Python/IRCore.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 7c6309874c754..740928731fb1e 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -50,7 +50,7 @@ operations.
 
 /// Local helper to concatenate string and integer arguments into a std::string.
 template <typename... Ts>
-std::string join(const Ts &...args) {
+static std::string join(const Ts &...args) {
   std::ostringstream oss;
   (oss << ... << args);
   return oss.str();
@@ -827,7 +827,7 @@ MlirDialect PyDialects::getDialectForKey(const std::string &key,
   MlirDialect dialect = mlirContextGetOrLoadDialect(getContext()->get(),
                                                     {key.data(), key.size()});
   if (mlirDialectIsNull(dialect)) {
-    std::string msg = (std::string("Dialect '") + key + "' not found");
+    std::string msg = join("Dialect '", key, "' not found");
     if (attrError)
       throw nb::attribute_error(msg.c_str());
     throw nb::index_error(msg.c_str());
@@ -1326,7 +1326,7 @@ nb::object PyOperation::create(std::string_view name,
   // point, exceptions cannot be thrown or else the state will leak.
   MlirOperationState state =
       mlirOperationStateGet(toMlirStringRef(name), location);
-  if (numOperands)
+  if (numOperands > 0)
     mlirOperationStateAddOperands(&state, numOperands, operands);
   state.enableResultTypeInference = inferType;
   if (!mlirResults.empty())

>From 5b4502c1f791b5893f303e54aac68b460d8fa32f Mon Sep 17 00:00:00 2001
From: Amily Wu <amilywu2 at amd.com>
Date: Wed, 28 Jan 2026 17:03:32 +0000
Subject: [PATCH 14/14] Add more join

---
 mlir/lib/Bindings/Python/IRCore.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 740928731fb1e..8ce98fea2b0b7 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -4453,8 +4453,7 @@ void populateIRCore(nb::module_ &m) {
             if (!mlirTypeIDIsNull(mlirTypeID))
               return PyTypeID(mlirTypeID);
             auto origRepr = nb::cast<std::string>(nb::repr(nb::cast(self)));
-            throw nb::value_error(
-                (origRepr + std::string(" has no typeid.")).c_str());
+            throw nb::value_error(join(origRepr, " has no typeid.").c_str());
           },
           "Returns the `TypeID` of the `Type`, or raises `ValueError` if "
           "`Type` has no "



More information about the Mlir-commits mailing list