aboutsummaryrefslogtreecommitdiff
path: root/mlir/lib/Bindings/Python
AgeCommit message (Collapse)AuthorFilesLines
2026-02-12[MLIR][Python] Impl XOpInterface(s) from Python, with X=Transform and ↵Rolf Morel6-197/+628
X=MemoryEffects (#176920) Provides the infrastructure for implementing and late-binding OpInterfaces from Python. * On the mlir-c API declaration side, each `XOpInterface` has a callback struct, with a callback for each method and a userdata member (provided as an arg to each method), and a `mlirXOpInterfaceAttachFallbackModel(ctx, op_name, callbacks)` func. * This CAPI is implemented by defining a subclass of `XOpInterface::FallbackModel` that holds the callback struct and has each method call the corresponding callback (with userdata as an arg). Given a callback struct, a new `FallbackModel` is created and attached, i.e. late bound, to the named op. (MLIR's interface infrastructure is such that the thus registered `FallbackModel` will be returned in case the op gets cast to the `XOpInterface`.) * On the Python side, we expose a stand-in `XOpInterface` base class which has one (class)method: `XOpInterface.attach(cls, op_name, ctx)`. Python users subclass this class (`class MyInterfaceImpl(XOpInterface): ...`) and implement the interface's methods (with the right names and signatures). The user calls `attach` on the subclass (`MyInterfaceImpl.attach("my_dialect.my_op", ctx)`) which prepares the callbacks struct _with userdata set to the subclass_ (as we use it to lookup methods). These callbacks (and userdata) are then registered as an `XOpInterface::FallbackModel` by `mlirXOpInterfaceAttachFallbackModel(...)`. From then on the Python methods will be used to respond to calls to the interface methods (originating in C++). This PR enables implementing the TransformOpInterface and the MemoryEffectsOpInterface, both of which are required for making an op into a transform op. Everything besides the above linked code is there to facilitate exposing the interfaces: the right types for the arguments of the methods are exposed as are functions/methods for manipulating these arguments (e.g. specifying side effects on `OpOperand`s and `OpResult`s and being able to access and set the transform handles associated with args and results).
2026-02-12[MLIR][Python] Remove partial LLVM APIs in python bindings (6/6) (#180986)RattataKing1-3/+11
This PR completed work from https://github.com/llvm/llvm-project/pull/178290. Switched the last few python bindings that still relied on LLVM over to the C API, and dropped `LLVMsupport` dependency from MLIR cmake.
2026-02-11[MLIR] [Bazel] Moved stubgen_runner.py out of llvm-project-overlay (#181029)Sergei Lebedev1-0/+54
It was checked into the overlay by accident.
2026-02-11[mlir][IR] `DenseElementsAttr`: Remove `i1` dense packing special case (#180397)Matthias Springer1-100/+6
`DenseElementsAttr` stores elements in a `ArrayRef<char>` buffer, where each element is padded to a full byte. Before this commit, there used to be a special storage format for `i1` elements: they used to be densely packed, i.e., 1 bit per element. This commit removes the dense packing special case for `i1`. This commit removes complexity from `DenseElementsAttr`. If dense packing is needed in the future it could be implemented in a general way that works for all element types (based on #179122). Discussion: https://discourse.llvm.org/t/denseelementsattr-i1-element-type/62525
2026-02-11[MLIR][Python] Make traits declarative in python-defined operations (#180748)Twice1-5/+6
This will support two syntax in python-defined dialects. First is that traits can now be declared in class parameters, e.g. ```python class ParentIsIfTrait(DynamicOpTrait): #define a python-side trait @staticmethod def verify_invariants(op) -> bool: if not isinstance(op.parent.opview, IfOp): op.location.emit_error( f"{op.name} should be put inside {IfOp.OPERATION_NAME}" ) return False return True class YieldOp( # attach two traits: IsTerminatorTrait, ParentIsIfTrait TestRegion.Operation, name="yield", traits=[IsTerminatorTrait, ParentIsIfTrait] ): ... ``` Second is that users can directly define `verify_invariants`/`verify_region_invariants` methods in the operation to add additional custom verification logic. And this is implemented via traits. ```python class YieldOp(TestRegion.Operation, name="yield", ...): value: Operand[Any] def verify_invariants(self) -> bool: # define a method directly if self.parent.results[0].type != self.value.type: self.location.emit_error( "result type mismatch between YieldOp and its parent IfOp" ) return False return True ``` Previously we use `verify`/`verify_region` as method names (in yesterday's PR #179705), but in this PR they are renamed to `verify_invariants`/`verify_region_invariants` because there are conflicts between the newly-added `verify` method and `ir.OpView.verify` method: - `verify_invariants` is just to attach **additional** verification logic. but `OpView.verify` is to construct an OperationVerifer and do full verification for an operation, so the semantics is not same between these two. We should not shadow the `OpView.verify` method by defining a new semantically-different `verify` method. - it will make users confuse between these two `verify` methods, since they have different meaning. - if users didn't define the `verify` method in their python-defined operation, `DynamicOpTraits.attach(opname, MyOpCls)` still do the attaching (because `hasattr("verify")` returns `True`) and seg fault (because we cannot attach `OpView.verify`). --------- Co-authored-by: Rolf Morel <rolfmorel@gmail.com>
2026-02-10[MLIR][Python] Remove partial LLVM APIs in python bindings (5/n) (#180644)RattataKing3-29/+98
This PR continues work from https://github.com/llvm/llvm-project/pull/178290 Added local helper functions to avoid dependency on LLVM APIs. --------- Co-authored-by: Jakub Kuderski <kubakuderski@gmail.com>
2026-02-10[MLIR][Python] Refine trait support in MLIR Python (#180550)Twice1-4/+5
This PR is mainly to address review suggestions in #179705.
2026-02-09[MLIR][Python] Support dynamic traits in python-defined dialects (#179705)Twice1-0/+119
This is a follow-up PR of #169045 and the second part of #179086. In #179086, we added support for defining regions in Python-defined ops, but its usefulness was quite limited because we still couldn’t mark an op as a `Terminator` or `NoTerminator`. In this PR, we port the `DynamicOpTrait` (introduced on the C++ side for `DynamicDialect` in #177735) to Python, so we can dynamically attach traits to Python-defined ops.
2026-02-06[MLIR][Python] Remove partial LLVM APIs in python bindings (4/n) (#180256)RattataKing6-110/+92
This PR continues work from #178290 It replaces some LLVM utilities with straightforward `std::` equivalents.
2026-02-04[MLIR][Python] Remove partial LLVM APIs in python bindings (3/n) (#178984)RattataKing3-24/+20
This PR continues work from #178290 It cleans up multiple LLVM utilities in *.h files under `mlir/Bindings/python`, along with the corresponding *.cpp files.
2026-02-04[MLIR][Python] Ignore the returned status of `loadDialectModule` in lookup ↵Twice1-6/+4
functions (#179609) Since `loadDialectModule` doesn't work for Python-defined dialects (`mlir.dialects.ext`), currently we should lookup for dialect/operation/opadaptor class even if the `loadDialectModule` function fails. It's also because users can import some modules manually, and we do already ignore it in some cases: https://github.com/llvm/llvm-project/blob/e2061328a8ae51cdf80e211ad27dd13d7bba766d/mlir/lib/Bindings/Python/Globals.cpp#L163-L166 Related to https://github.com/llvm/llvm-project/pull/176920#discussion_r2762029022.
2026-02-02[mlir][Python] fix liveContextMap under free-threading after #178529 (#179163)Maksim Levental1-1/+1
#178529 introduced a small bug under free-threading by bumping a reference count (or something like that) when accessing the operand list passed to `build_generic`. This PR fixes that.
2026-01-31[MLIR][Python] Support dialect conversion in python bindings (#177782)Twice3-2/+370
This PR adds dialect conversion support to the MLIR Python bindings. Because it introduces a number of new APIs, it’s a fairly large PR. It mainly includes the following parts: * Add a set of types and APIs to the C API, including `MlirConversionTarget`, `MlirConversionPattern`, `MlirTypeConverter`, `MlirConversionPatternRewriter`, and others. * Add the corresponding types and APIs to the Python bindings. * Extend `mlir-tblgen` with codegen for Python adaptor classes, which generates an adaptor class for each op. Note that this PR only adds support for 1-to-1 conversions, 1-to-N type/value conversions are not supported yet. --------- Co-authored-by: Maksim Levental <maksim.levental@gmail.com>
2026-01-29[MLIR][Python] Remove partial LLVM APIs in python bindings (2/n) (#178529)RattataKing1-57/+50
This PR continues work from #178290. Cleaned up LLVM utilities in IRCore.cpp: `enumerate`, `zip`, `ArrayRef`, `hash_value`.
2026-01-29[mlir] Apply clang-tidy check llvm-use-vector-utils. NFC. (#178526)Jakub Kuderski1-4/+5
2026-01-28[MLIR][Python] Remove partial LLVM APIs in python bindings (#178290)RattataKing1-128/+103
mlir-py bindings should only rely on C mlir APIs. This PR replaced partial LLVM utilities (`Twine`, `ArrayRef`, `SmallVector`, `StringRef`) with equivalent STL, and added a `join()` helper function in `IRCore.cpp` to concat strings. --------- Co-authored-by: Jakub Kuderski <kubakuderski@gmail.com>
2026-01-26[MLIR][Python] Fix overly specific type annotation on PyValue.owner (#178003)Rolf Morel1-1/+2
2026-01-24[mlir][python] Support Arbitrary Precision Integers in MLIR C API and Python ↵Ryan Kim1-8/+82
Bindings (#177733) This PR extends the MLIR C API and Python bindings to support **arbitrary-precision integers (`APInt`)**, overcoming the previous limitation where `IntegerAttr` values were restricted to 64 bits. Cryptographic applications often require integer types much larger than standard machine words (e.g., the 256-bit modulus for the BN254 curve). Previously, attempting to bind these values resulted in truncation or errors. This PR exposes the underlying word-based `APInt` structure via the C API and updates the Python bindings to seamlessly handle Python's arbitrary-precision integers.
2026-01-16[mlir][Python] remove stray nb::cast (#176299)Maksim Levental1-6/+0
In https://github.com/llvm/llvm-project/pull/155114 we removed `liveOperations` but forgot this line which was being used to invalidate operations under a transform root, which currently isn't being used for anything. So remove. FYI this led to a subtle double free bug after https://github.com/llvm/llvm-project/pull/175405: ```python @test_in_context def check_builtin(): module = builtin_d.ModuleOp() with module.context, ir.Location.unknown(): transform_module = builtin_d.Module.create() transform_module.operation.attributes["transform.with_named_sequence"] = ( ir.UnitAttr.get() ) with ir.InsertionPoint(transform_module.body): named_sequence = NamedSequenceOp("__transform_main", [any_op_t()], []) with ir.InsertionPoint(named_sequence.body): YieldOp([]) interp.apply_named_sequence( module, transform_module.body.operations[0], transform_module, ) ``` with error ``` python(7436,0x1f95a93c0) malloc: *** error for object 0x6000002b0000: pointer being freed was not allocated python(7436,0x1f95a93c0) malloc: *** set a breakpoint in malloc_error_break to debug ``` This is because ``` nb::object obj = nb::cast(payloadRoot); ``` is actually equivalent to ``` nb::object obj = nb::cast(payloadRoot, nb::rv_policy::automatic); ``` which is actually equivalent to ``` nb::object obj = nb::cast(payloadRoot, nb::rv_policy::copy); ``` because I changed the API to `PyOperationBase &payloadRoot` i.e., an lvalue reference and `nb::rv_policy::automatic` decays to `nb::rv_policy::copy` for [lvalue refs](https://nanobind.readthedocs.io/en/latest/api_core.html#_CPPv4N8nanobind9rv_policy9automaticE).
2026-01-13[MLIR][Python] Improve Iterator performance. Don't `throw` in `dunderNext` ↵MaPePeR2-13/+25
methods. (#175377) In https://github.com/llvm/llvm-project/pull/174139#issuecomment-3733259370 I wrote a scuffed benchmark that mostly iterates MLIR Container Types in Python. My changes from that PR made the performance worse, so I closed it. However, when experimetning with that I also saw a large(?) performance gain by changing the `dunderNext` methods of the various Iterators to use `PyErr_SetNone(PyExc_StopIteration);` instead of `throw nb::stop_iteration();`. <details><summary>Benchmark attempt script</summary> ```python import timeit from mlir.ir import Context, Location, Module, InsertionPoint, Block, Region, OpView from mlir.dialects import func, builtin, scf, arith def generate_module(): m = Module.create() with InsertionPoint(m.body): f = func.FuncOp("main", builtin.FunctionType.get([], [])) with InsertionPoint(f.body.blocks.append()): generate_ops(10, 2) func.ReturnOp([]) return m def generate_ops(count: int, depth: int): if depth == 0: return lower = arith.ConstantOp(builtin.IntegerType.get_signless(64), 0) upper = arith.ConstantOp(builtin.IntegerType.get_signless(64), 100) step = arith.ConstantOp(builtin.IntegerType.get_signless(64), 1) for i in range(count): forop = scf.ForOp(lower, upper, step) with InsertionPoint(forop.region.blocks[0]): generate_ops(count, depth - 1) scf.YieldOp([]) def walk_module(m: Module): walk_block(m.body) def walk_region(region: Region): for block in region.blocks: walk_block(block) def walk_block(block: Block): for predecessors in block.predecessors: pass for successors in block.successors: pass for op in block.operations: walk_op(op) def walk_op(op: OpView): for result in op.results: pass for successors in op.successors: pass for operands in op.operands: pass for region in op.regions: walk_region(region) with Context(), Location.unknown(): m = generate_module() # From timeit.main: t = timeit.Timer(lambda: walk_module(m)) number, _ = t.autorange() repeats = 5 raw_timings = t.repeat(repeats, number) timings = [dt / number for dt in raw_timings] best = min(timings) print(f"{number} loops, best of {repeats}: {best * 1000:.3g} msecs per loop") ``` </details> The performance of the benchmark went from ``` 50 loops, best of 5: 5.97 msecs per loop ``` to ``` 50 loops, best of 5: 5.12 msecs per loop ``` in my setup, which is a ~14% improvement. (Though you should validate that yourself, probably. My test setup is very scuffed) The functions were previously set to return a C++ type like `PyRegion`. Because of the removal of the `throw` they now had to [return a `NULL` value to Python](https://github.com/python/cpython/blob/aa8578dc54df2af9daa3353566359e602e5905cf/Objects/call.c#L49-L61), so I changed the return type to `nanobind::typed<nanobind::object,PyRegion>` so I could return an `nb::object()` in case an error was set and otherwise `nb::cast` the `PyRegion` value to `nb::object` instead of returning it directly. I'm not a huge fan, that this changes the external "Usage" of the functions, because now they won't bubble up exceptions, when they are called from C++ The return type and Python Error State have to be checked instead. I couldn't find any location that called them in llvm itself, though. Maybe these functions should not be public, because they are only supposed to be called from Python anyway? --------- Co-authored-by: Maksim Levental <maksim.levental@gmail.com>
2026-01-12[mlir][Python] downcast Value to BlockArgument or OpResult (#175264)Maksim Levental2-32/+31
This PR adds "downcasting" of `ir.Value` to either `BlockArgument` or `OpResult` (and then potentially further down if a user-registered "value caster" exists). Also this PR changes `__str__` to return the correct thing (`OpResult(...)` or `BlockArgument(...)` instead of generic `Value(...)`).
2026-01-11[MLIR][Python] Rename `GreedyRewriteDriverConfig` to `GreedyRewriteConfig` ↵Twice1-24/+22
(#175409) This is mainly for two purposes: 1. to keep it consistent with the C++ class name `mlir::GreedyRewriteConfig`, 2. to make it shorter. Since this type was only added a few days ago (654b3e844f21d3f64521e9cb028efdfebbf99bb4), it shouldn’t cause any obvious compatibility issues.
2026-01-10[mlir][Python] fix dialect extensions which bind C types (#175405)Maksim Levental8-97/+162
Fix some dialect bindings I missed in https://github.com/llvm/llvm-project/pull/174156 so they don't bind C structs (because that leads to multiple registration in the case when multiple packages are used simultaneously).
2026-01-10[MLIR][Python] Register `OpAttributeMap` as `Mapping` for `match` ↵MaPePeR1-0/+13
compatibility (#174292) This is a continuation of the idea from #174091 to add `match` support for MLIR containers. In this PR the `OpAttributeMap` container is registered as a `Mapping`, so be mapped as a "dictionary" in `match` statements. For this to work the `get(key, default=None)` method had to be implemented. Those are pretty much copys of `dunderGetItemNamed` and `dunderGetItemIndexed` with an added argument and `nb::object` as return type, because they can now return other types than just `PyAttribute`. Was unsure if I should refactor this to make `dunderGetItem...` use the new `getWithDefault...` or if a separate method is preferred. Kept it as a copy for simplicitys sake for now. Even though the `OpAttributeMap` supports indexing by `int` and `str`, Python does not allow to register it as a `Sequence` and a `Mapping` at the same time. If it is registered as a Sequence it only returns the attribute names as string, not as `NamedAttribute`. It is technically possible to also use integer keys for the `dict`-like match, but it doesn't provide any constraints on the number of attributes, etc., so probably not recommended. <details><summary>Example</summary> ```python from mlir.ir import Context, Module, OpAttributeMap from collections.abc import Sequence ctx = Context() ctx.allow_unregistered_dialects = True module = Module.parse( r""" "some.op"() { some.attribute = 1 : i8, other.attribute = 3.0, dependent = "text" } : () -> () """, ctx, ) op = module.body.operations[0] def test(attr): match attr: case [*args]: print("matched a Sequence", args) case _: print("Didn't match as Sequence") match attr: case {"some.attribute": a, "other.attribute": b, "dependent": c}: print("Matched as Mapping individually", a, b, c) case _: print("Didn't match a Mapping") match attr: case {0: a, 1: b}: print("Matched as Mapping with 2 int keys", a, b) case _: print("Didn't match as Mapping with 2 int keys") print("Registered as Mapping only:") test(op.attributes) print("\nAfter additonally registering as Sequence:") Sequence.register(OpAttributeMap) test(op.attributes) ``` Output: ``` Registered as Mapping only: Didn't match as Sequence Matched as Mapping individually 1 : i8 3.000000e+00 : f64 "text" Matched as Mapping with 2 int keys NamedAttribute(dependent="text") NamedAttribute(other.attribute=3.000000e+00 : f64) After additonally registering as Sequence: matched a Sequence ['dependent', 'other.attribute', 'some.attribute'] Didn't match a Mapping Didn't match as Mapping with 2 int keys ``` </details> makslevental Would be great if you could take a look again ❤️ --------- Co-authored-by: Maksim Levental <maksim.levental@gmail.com>
2026-01-08[MLIR][Python] Add GreedyRewriteDriverConfig parameter to ↵Maksim Levental1-31/+39
apply_patterns_and_fold_greedily (#174913) We already have `GreedyRewriteDriverConfig` on the Python side, but it hasn’t yet been exposed as a parameter of `apply_patterns_and_fold_greedily`. This PR does that. Before: ```python def apply_patterns_and_fold_greedily(module: ir.Module, set: FrozenRewritePatternSet) -> None def apply_patterns_and_fold_greedily(op: ir._OperationBase, set: FrozenRewritePatternSet) -> None ``` After: ```python def apply_patterns_and_fold_greedily(module: ir.Module, set: FrozenRewritePatternSet, config: GreedyRewriteDriverConfig | None = None) -> None def apply_patterns_and_fold_greedily(op: ir._OperationBase, set: FrozenRewritePatternSet, config: GreedyRewriteDriverConfig | None = None) -> None ``` Note this PR is adapted from https://github.com/llvm/llvm-project/pull/174785 but using `std::optional` instead of `nb::object`. Note, this required refactoring `PyGreedyRewriteDriverConfig` to have a `std::shared_ptr` so that it could support a copy-ctor. Co-authored-by: PragmaTwice <twice@apache.org>
2026-01-08[MLIR][Python][NFC] Use enum class instead of enum (#174792)Twice4-56/+48
This PR replace `enum`s with `enum class`es in Python bindings. No functional change.
2026-01-07[mlir][py] ability to downcast AffineExpr after #172892 (#174808)Oleksandr "Alex" Zinenko1-4/+26
AffineExpr is a separate hierarchy of LLVM-style nested classes that doesn't rely on TypeID and is not extensible. We need the ability to downcast the Python equivalent of those to a specific subclass that was seemingly lost in PR #172892. Bring it back by having an explicit cast. We don't really need user-defined type casters here since AffineExpr is entirely closed and not typed, unlike values.
2026-01-07[MLIR][Python] Forward the name of MLIR attrs to Python side (#174756)Twice2-0/+3
This PR is quite similiar to #174700. In this PR, I added a C API for each (upstream) MLIR attributes to retrieve its name (for example, `StringAttr -> mlirStringAttrGetName() -> "builtin.string"`), and exposed a corresponding type_name class attribute in the Python bindings (e.g., `StringAttr.attr_name -> "builtin.string"`). This can be used in various places to avoid hard-coded strings, such as eliminating the manual string in `irdl.base("#builtin.string")`. Note that parts of this PR (mainly mechanical changes) were produced via GitHub Copilot and GPT-5.2. I have manually reviewed the changes and verified them with tests to ensure correctness.
2026-01-07[MLIR][Python] Forward the name of MLIR types to Python side (#174700)Twice8-0/+28
In this PR, I added a C API for each (upstream) MLIR type to retrieve its type name (for example, `IntegerType` -> `mlirIntegerTypeGetName()` -> `"builtin.integer"`), and exposed a corresponding `type_name` class attribute in the Python bindings (e.g., `IntegerType.type_name` -> `"builtin.integer"`). This can be used in various places to avoid hard-coded strings, such as eliminating the manual string in `irdl.base("!builtin.integer")`. Note that parts of this PR (mainly mechanical changes) were produced via GitHub Copilot and GPT-5.2. I have manually reviewed the changes and verified them with tests to ensure correctness.
2026-01-06[MLIR][Python] Add a `.get` method to `IntegerType` (#174406)Twice1-0/+33
In this PR, I added a `.get` class method to `IntegerType`. The main goal is to ensure that types from upstream dialects have a `.get` method (at least for the builtin dialect). The benefit is that, for any MLIR type, we can construct an instance directly without special-casing types that don’t provide a `.get` method. The design mirrors `mlir::IntegerType` in C++: it takes `width` and `signedness` parameters, and `signedness` defaults to `signless`. It is related to #169045.
2026-01-05[mlir][Python] use maybeDowncast for PyType/PyAttribute returns after ↵Maksim Levental6-33/+44
#174156 (#174489) #174156 made all gettors return `Py*` but skipped downcasting where possible. So restore it by calling `.maybeDowncast`.
2026-01-05[mlir][Python] use canonical Python `isinstance` instead of ↵Maksim Levental3-6/+20
`Type.isinstance` (#172892) We've been able to do `isinstance(x, Type)` for a quite a while now (since https://github.com/llvm/llvm-project/commit/bfb1ba752655bf09b35c486f6cc9817dbedfb1bb) so remove `Type.isinstance` and the the special-casing (`_is_integer_type`, `_is_floating_point_type`, `_is_index_type`) in some places (and therefore support various `fp8`, `fp6`, `fp4` types).
2026-01-05[MLIR][Python] Use correct namespace for registering SMT dialect (#174468)Maksim Levental1-1/+1
use `python::MLIR_BINDINGS_PYTHON_DOMAIN` namespace for registering SMT dialect.
2026-01-05[MLIR] Use correct namespace for registering MLIR dialects (#174463)Aiden Grossman1-1/+2
This seems to match how everything else was updated in the commit. This was failing in the bazel build, and possibly elsewhere. Fixes https://github.com/llvm/llvm-project/commit/ee3338d135adc183fbd2fc7dc28af6a34bdaa60a.
2026-01-05[mlir][Python] port in-tree dialect extensions to use MLIRPythonSupport ↵Maksim Levental10-1017/+1395
(#174156) This PR ports all in-tree dialect extensions to use the `PyConcreteType`, `PyConcreteAttribute` CRTPs instead of `mlir_pure_subclass`. After this PR we can soft deprecate `mlir_pure_subclass`. Also API signatures are updated to use `Py*` instead of `Mlir*` so that type "inference" and hints are improved.
2026-01-05[mlir][Python] move IRTypes and IRAttributes to MLIRPythonSupport (#174118)Maksim Levental3-2337/+1571
This PR continues the work of https://github.com/llvm/llvm-project/pull/171775 by moving more useful types/attributes into MLIRPythonSupport. You can now do ```c++ struct PyTestIntegerRankedTensorType : mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN::PyConcreteType< PyTestIntegerRankedTensorType, mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN::PyRankedTensorType> struct PyTestTensorValue : mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN::PyConcreteValue< PyTestTensorValue> ``` instead of `mlir_type_subclass` and `mlir_value_subclass`; **specifically manual registration of the "value caster" via indirection through the Python interpreter is no longer necessary** . You can also now freely use all such types at the nanobind API level (e.g., overload based on `FP*`): ```c++ using mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN; standaloneM.def("print_fp_type", [](PyF16Type &) { nb::print("this is a fp16 type"); }); standaloneM.def("print_fp_type", [](PyF32Type &) { nb::print("this is a fp32 type"); }); standaloneM.def("print_fp_type", [](PyF64Type &) { nb::print("this is a fp64 type"); }); ``` Note, here we only port `PythonTestModuleNanobind` but there is a follow-up PR that ports **all** in-tree dialect extensions https://github.com/llvm/llvm-project/pull/174156 to use these. After that one we can soft deprecate `mlir_pure_subclass`. Note, depends on https://github.com/llvm/llvm-project/pull/171775
2026-01-05[mlir][Python] create MLIRPythonSupport (#171775)Maksim Levental15-3311/+1129
# What This PR adds a shared library `MLIRPythonSupport` which contains all of the CRTP classes ike `PyConcreteValue`, `PyConcreteType`, `PyConcreteAttribute`, as well as other useful code like `Defaulting*` and etc enabling their reuse in downstream projects. Downstream projects can now do ```c++ struct PyTestType : mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN::PyConcreteType<PyTestType> { ... }; class PyTestAttr : public mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN::PyConcreteAttribute<PyTestAttr> { ... } NB_MODULE(_mlirPythonTestNanobind, m) { PyTestType::bind(m); PyTestAttr::bind(m); } ``` instead of using the discordant alternative `mlir_type_subclass`/`mlir_attr_subclass` (same goes for `PyConcreteValue`/`mlir_value_subclass`). # Why This PR is mostly code motion (along with CMake) but before I describe the changes I want to state the goals/benefits: 1. Currently upstream "core" extensions and "dialect" extensions ([all of the `Dialect*` extensions here](https://github.com/llvm/llvm-project/tree/d7c734b5a14bd91e1c76e2ce0014c19f9deef487/mlir/lib/Bindings/Python)) are a two-tier system; **a**. [core extensions](https://github.com/llvm/llvm-project/blob/main/mlir/lib/Bindings/Python/IRTypes.cpp#L361) enjoy first class support as far as type inference[^3], type stub generation, and ease of implementation, while dialect extensions [have poorer support](https://reviews.llvm.org/D150927), incorrect type stub generation much more tedious (boilerplate) implementation; **b**. Crucially, this two-tiered system is reflected in the fact that **the two sets of types/attributes are not in the same Python object hierarchy**. To wit: `isinstance(..., Type)` and `isinstance(..., Attribute)` are not supported for the dialect extensions[^2]; **c**. Since these types are not exposed in public headers, downstream users (dialect extensions or not) cannot write functions that overload on e.g. `PyFloat8*Type` - that's quite a [useful feature](https://github.com/nod-ai/PI/blob/fdbee98df8376f47818e6b47e1cf089528c9d48d/cpp_ext/TorchOps.cpp#L29-L69)! 2. The dialect extensions incur a sizeable performance penalty relative to the core extensions in that every single trip across the wire (either `python->cpp` or `cpp->python`) requires work in addition to nanobind's own casting/construction pipeline; **a**. When going from `python->cpp`, [we extract the capsule object from the Python object](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h#L219C24-L219C46) and then extract from the capsule the `Mlir*` opaque struct/ptr. This side isn't so onerous; **b**. When going from `cpp->python` we call long-hand call Python `import` APIs and construct the Python object using `_CAPICreate`. Note, there at least 2 `attr` calls incurred in addition to `_CAPICreate`; this is already much more [efficiently handled by nanobind itself](https://github.com/wjakob/nanobind/blob/4ba51fcf795971c5d603d875ae4184bc0c9bd8e6/src/nb_internals.h#L381-L382)! 3. This division blocks various features: in some configurations[^1] we trigger a circular import bug because "dialect" types and attributes perform an [import of the root `_mlir` module](https://github.com/llvm/llvm-project/blob/bd9651bf78f2b1713a8203e0bd5b97f7ff199924/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h#L585) when they are created (the types themselves, not even instances of those types). This blocks type stub generation for dialect extensions (i.e., the reason we currently only generate type stubs for `_mlir`). # How Prior this was not done/possible because of "ODR" issues but I have resolved those issues; the basic idea for how we solve this is "move things we want to share into shared libraries": 1. Move IRCore (stuff like `PyConcreteValue`, `PyConcreteType`, `PyConcreteAttribute`) into `MLIRPythonSupport`; - Note, we move the rest of the things in `IRModule.h` (renamed to `IRCore.h`) because `PyConcreteValue`, `PyConcreteType`, `PyConcreteAttribute` depend on them. This makes for a bigger PR than one would hope for but ultimately I think we should give people access to these classes to use as they see fit (specifically inherit from, but also liberally use in bindings signatures instead of the opaque `Mlir*` struct wrappers). 2. Put all of this code into a nested namespace `MLIR_BINDINGS_PYTHON_DOMAIN` which is determined by a compile time define (and tied to `MLIR_BINDINGS_PYTHON_NB_DOMAIN`). This is necessary in order to prevent conflicts on both symbol name **and** typeid (necessary for nanobind to not double register binded types) between multiple bindings libraries (e.g., `torch-mlir`, and `jax`). Note [nanobind doesn't support `module_local` like pybind11](https://nanobind.readthedocs.io/en/latest/porting.html#removed-features). It does support `NB_DOMAIN` but that is not sufficient for disambiguating typeids across projects (to wit: we currently define `NB_DOMAIN` and it was still necessary to move everything to a nested namespace); 3. Build the [nanobind library itself as a shared object](https://github.com/wjakob/nanobind/blob/master/cmake/nanobind-config.cmake#L127) (and link it to both the extensions and `MLIRPythonSupport`). 4. CMake to make this work, in-tree, out-of-tree, downstream, upstream, etc. # Testing Three tests are added here 1. `PythonTestModuleNanobind` is ported to use `PyConcreteType<PyTestType>` instead of `mlir_type_subclass` and `PyConcreteAttribute<PyTestAttr>` instead of `mlir_atrr_subclass`, verifying this works for non-core extensions in-tree; 2. `StandaloneExtensionNanobind` is ported to use `struct PyCustomType : mlir::python::MLIR_BINDINGS_PYTHON_DOMAIN::PyConcreteType<PyCustomType>` instead of `mlir_type_subclass` verifying this works for non-core extensions out-of-tree; 3. `StandaloneExtensionNanobind`'s `smoketest` is extended to also load another bindings package (namely `mlir`) verifying `MLIR_BINDINGS_PYTHON_DOMAIN` successfully disambiguates symbols and typeids. I have also tested this downstream: https://github.com/llvm/eudsl/pull/287 as well run the following builder bots: mlir-nvidia-gcc7: https://lab.llvm.org/buildbot/#/buildrequests/6654424?redirect_to_build=true I have also tested against IREE: https://github.com/iree-org/iree/pull/21916 # Integration It is highly recommended to set the CMake var `MLIR_BINDINGS_PYTHON_NB_DOMAIN` (which will also determine `MLIR_BINDINGS_PYTHON_DOMAIN`) to something unique for each downstream. This can also be passed explicitly to `add_mlir_python_modules` if your project builds multiple bindings packages. I added a `WARNING` to this effect in `AddMLIRPython.cmake`. [^3]: Python values being typed correctly when exiting from cpp; [^1]: Specifically when the modules are imported using `importlib`, which occurs with nanobind's [stubgen](https://github.com/wjakob/nanobind/blob/master/src/stubgen.py#L965); [^2]: The workaround we implemented was a class method for the dialect bindings called `Class.isinstance(...)`;
2026-01-02[LLVM][ADT] Migrate users of `make_scope_exit` to CTAD (#174030)Victor Chernyakin1-2/+2
This is a followup to #173131, which introduced the CTAD functionality.
2026-01-02[mlir][c] Enable creating and setting greedy rewrite confing. (#162429)Jacques Pienaar1-6/+126
Done very mechanically. This changes that one cannot just pass null config to C API for config.
2025-12-30[MLIR][Python][NFC] Refine the docstring of `PassManager.add(callable, ..)` ↵Twice1-1/+13
(#173945) This PR expands the docstring for `PassManager.run(callable, ..)` by adding descriptions for each parameter, making it easier for users to understand. No functional change.
2025-12-30[MLIR][Python] Refine the support of `RewritePatternSet.add` (#173874)Twice1-5/+27
This patch includes the following changes: - `RewritePatternSet.add` now accepts op name (e.g. `.add("arith.addi", fn)`) besides op class (e.g. `.add(arith.AddIOp, fn)`) - add a concrete signature and a more complete docstring to `RewritePatternSet.add`.
2025-12-26[MLIR][Python] Add support of the walk pattern rewrite driver (#173562)Twice1-1/+12
MLIR currently has three main pattern rewrite drivers (see [https://mlir.llvm.org/docs/PatternRewriter/#common-pattern-drivers](https://mlir.llvm.org/docs/PatternRewriter/#common-pattern-drivers)): * Dialect Conversion Driver * Walk Pattern Rewrite Driver * Greedy Pattern Rewrite Driver Right now, we already support the greedy pattern rewrite driver in the C API and Python bindings. This PR adds support for the walk pattern rewrite driver. This lightweight driver, unlike the greedy driver, does not repeatedly apply patterns; instead, it walks the IR once. API-wise, the main change is adding the `walk_and_apply_patterns` function. Note that the listener parameter is not supported now.
2025-12-16[mlir][amdgpu] Add Python bindings for TDM types (#172309)Tim Gymnich1-0/+65
Add bindings for: - `TDMBaseType` - `TDMDescriptorType` - `TDMGatherBaseType`
2025-12-14[MLIR][Transform][Python] transform.foreach wrapper and .owner OpViews (#172228)Rolf Morel1-4/+4
Friendlier wrapper for transform.foreach. To facilitate that friendliness, makes it so that OpResult.owner returns the relevant OpView instead of Operation. For good measure, also changes Value.owner to return OpView instead of Operation, thereby ensuring consistency. That is, makes it is so that all op-returning .owner accessors return OpView (and thereby give access to all goodies available on registered OpViews.) Reland of #171544 due to fixup for integration test.
2025-12-14Revert "[MLIR][Transform][Python] transform.foreach wrapper and .owner ↵Mehdi Amini1-4/+4
OpViews" (#172225) Reverts llvm/llvm-project#171544 ; bots are broken.
2025-12-14[MLIR][Transform][Python] transform.foreach wrapper and .owner OpViews (#171544)Rolf Morel1-4/+4
Friendlier wrapper for `transform.foreach`. To facilitate that friendliness, makes it so that `OpResult.owner` returns the relevant `OpView` instead of `Operation`. For good measure, also changes `Value.owner` to return `OpView` instead of `Operation`, thereby ensuring consistency. That is, makes it is so that all op-returning `.owner` accessors return `OpView` (and thereby give access to all goodies available on registered `OpView`s.)
2025-12-14[MLIR][Python][Transform] Print diagnostics also upon success (#172188)Rolf Morel1-1/+10
If we do not collect the diagnostics from the CollectDiagnosticsToStringScope, even when the named_sequence applied successfully, the Scope object's destructor will assert (with a unhelpful message).
2025-12-11[python] Expose replaceUsesOfWith C API (#171892)Hongzheng Chen1-0/+8
This PR exposes the `replaceUsesOfWith` C API to Python
2025-12-10[MLIR] Apply clang-tidy fixes for llvm-include-order in ↵Mehdi Amini1-1/+1
TransformInterpreter.cpp (NFC)
2025-12-07[MLIR][ExecutionEngine] Enable PIC option (#170995)Tianqi Chen1-5/+5
This PR enables the MLIR execution engine to dump object file as PIC code, which is needed when the object file is later bundled into a dynamic shared library. --------- Co-authored-by: Mehdi Amini <joker.eph@gmail.com>