219 Commits

Author SHA1 Message Date
Maksim Levental
c3f381ccfe
[mlir-python] Fix duplicate EnumAttr builder registration across dialects. (#187191)
When multiple dialects share td `#includes` (e.g. `affine` includes
`arith`), each dialect's `*_enum_gen.py` file registers attribute
builders under the same keys, causing "already registered" errors on the
second import; the first commit checks in such a case which currently
fails on main:

```
# | RuntimeError: Attribute builder for 'Arith_CmpFPredicateAttr' is already registered with func: <function _arith_cmpfpredicateattr at 0x78d13cbe9a80>
```

This PR implements a two-pronged fix:

1. Add `allow_existing=True` to `register_attribute_builder` (and the
underlying C++ `registerAttributeBuilder`). When set, silently skips
registration if the key already exists (first-wins semantics). This
handles `EnumInfo`-based builders which have no dialect prefix (e.g.
`AtomicRMWKindAttr`, `Arith_CmpFPredicateAttr`), which may be emitted by
every dialect whose td file includes the defining file;
2. Filter `EnumAttr` builders by `-bind-dialect` in
`EnumPythonBindingGen.cpp` and register them under dialect qualified
keys (`"dialect.AttrName"`). Update `OpPythonBindingGen.cpp` to look up
the same qualified keys for EnumAttr typed op attributes (detected via
`isSubClassOf("EnumAttr")`). Pass `-bind-dialect` from
`AddMLIRPython.cmake`.

This approach incurs no changes to `ir.py` registrations (no "builtin."
prefix), and no manual builder additions to individual dialect Python
files (unlike the previous attempt
https://github.com/llvm/llvm-project/pull/117918).

Note, this PR was "clauded" not "coded".
2026-03-19 21:02:23 -07:00
Twice
044776691a
[MLIR][Python] Refine the behavior of Python-defined dialect reloading (#186128)
This includes several changes:
- `Dialect.load(reload=False)` will fail if the dialect was already
loaded in a different context. To prevent the further program abortion.
- `Dialect.load(reload=True)` implies `replace=True` in
dialect/operation registering.
- `PyGlobals::registerDialectImpl` now has a parameter `replace`.
- `register_dialect` and `register_operation` is no longer exposed in
`mlir.dialects.ext`.

This should solve the registering problem found in writing transform
test cases by @rolfmorel.
2026-03-15 10:25:24 +08:00
RattataKing
39b3b2e0e8
[MLIR][Python] Add type filter to walk() binding and add get_ops_of_type() utility (#186131)
MLIR's C++ `Operation::walk` supports type-filtered traversal (e.g.
`op->walk([](arith::AddIOp op) { ... })`), but the Python binding
`op.walk()` requires users to manually implement type filtering inside
the callback function.

This PR adds type filtering into the python binding `op.walk()`, if
users pass `op_class`, walk() will only apply callback to matching ops.

This PR also adds a common use helper in mlir/ir that collects all ops
of a given type into a list. Users can just call: `ops =
ir.get_ops_of_type(root, op_class)`.
2026-03-13 13:36:30 -04:00
Sergei Lebedev
840cc16a8d
[MLIR] [Python] a few more fixes to type annotaitons (#186106)
* `_OperationBase.walk` was missing a default.
* `MLIRError` is now fully defined in C++. The monkey-patching
previously done in `_site_initialize` was opaque to type checkers.
2026-03-12 17:39:11 +00:00
Sergei Lebedev
421bc73cff
[MLIR] [Python] Fixed a few issues in the type annotations (#183021)
* Removed an explicit `nb::sig` for `static_typeid`. The inferred type
would
work just fine, and unqualified `TypeID`, which was there previously,
only
  really works for core types in the `ir` submodule.
* `DefaultingPyMlir*` helpers also produce qualified types, e.g.
  `_mlir.ir.Location` instead of bare `Location`.
* `ir.*.__enter__` now returns a concrete type instead of `object`, e.g.
  `ir.Context.__enter__` returns `Context`.
* `loc_tracebacks` uses `Generator` as the return type, since this is
what
  `contextmanager` expects in typeshed.
* Changed static methods on subclasses of `DenseElementsAttribute` to
return
  that concrete subclass, instead of `DenseElementsAttribute`.

---------

Co-authored-by: Maksim Levental <maksim.levental@gmail.com>
2026-03-08 13:22:04 +00:00
Jakub Kuderski
2c9720972e
[mlir][python] Add stable ABI (abi3) support (#183856)
Add `MLIR_ENABLE_PYTHON_STABLE_ABI` cmake flag to build bindings against
the Python limited/stable API (abi3 / PEP 384). This allow for
compatibility across different >=3.12 versions with a single .so /
wheel. We also require CMake >=3.26.

The stable ABI restricts usage to a subset of the CPython C API: frame
and code object structs are opaque, so introspection APIs like
`PyCode_Addr2Location`, `PyFrame_GetLasti`, and `PyFrame_GetCode` are
unavailable. The traceback-based auto-location logic is dropped because
we don’t have stable ABI to produce complete locations.

Assisted-by: claude
2026-03-01 13:45:52 +00:00
RattataKing
f55b86258c
[mlir][Python] Drop Python <=3.9 compatibility path (#183416)
According to PR #163499, minimum Python version for mlir-py is now 3.10,
we no longer need patches for py<=3.9.

This change aligns with the Python version bump policy discussed
[here](https://discourse.llvm.org/t/rfc-adopt-regularly-scheduled-python-minimum-version-bumps/88841).
2026-02-27 10:29:12 -05:00
Twice
754828a122
[MLIR][Python] Remove redundant methods (#182459)
At the moment, Pylance reports errors in `ir.pyi`, saying that some
overloaded methods are invalid. After looking into it, I found that some
of these methods have duplicate signatures and are defined more than
once. This PR is mainly to clean up those methods.
2026-02-20 18:22:58 +08:00
Twice
ef5a11a51d
[MLIR][Python] Fix generic class signature of ir.Value (#182447)
In the type stub, `Generic` isn’t explicitly imported. This causes
Pyright/Pylance to report an error and treat `Value` as not being a
generic type. Explicitly using `typing.Generic` fixes this.
2026-02-20 17:05:31 +08:00
RattataKing
8f6866c9e9
[MLIR][Python] Clean remaining LLVM dependencies in MLIR-PY bindings (#181779)
This PR fixed
[issues](https://github.com/iree-org/iree/actions/runs/21956878131/job/63423389868#step:7:211)
caused by dropping `LLVMSupport` in PR #180986, dropped the remaining
direct llvm dependencies from mlir-python binding files.

Previously `LLVMSupport` was dropped while some uncleaned `mlir/CAPI/*`
sources were still being pulled into mlir-py, and those files still
directly depended on LLVM headers. The issue was masked via a global
`include_directories(${LLVM_INCLUDE_DIRS})` in `mlir/CMakeLists.txt`,
out-of-tree builds (e.g., IREE) that define Python module targets
outside the mlir/ directory tree would fail with "no such llvm file"
errors.
2026-02-17 15:23:10 -05:00
Rolf Morel
a1d7cda1d7
[MLIR][Python] Impl XOpInterface(s) from Python, with X=Transform and 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 14:07:10 +00:00
RattataKing
75eecd27eb
[MLIR][Python] Remove partial LLVM APIs in python bindings (6/6) (#180986)
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-12 08:28:18 -05:00
Twice
972aa597de
[MLIR][Python] Make traits declarative in python-defined operations (#180748)
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-11 20:39:58 +08:00
Twice
dfc8fa80c9
[MLIR][Python] Refine trait support in MLIR Python (#180550)
This PR is mainly to address review suggestions in #179705.
2026-02-10 10:14:23 +08:00
Twice
fccbdcb15a
[MLIR][Python] Support dynamic traits in python-defined dialects (#179705)
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-09 22:26:56 +08:00
RattataKing
b20fca82ab
[MLIR][Python] Remove partial LLVM APIs in python bindings (3/n) (#178984)
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 13:18:32 -05:00
Maksim Levental
496d871ff3
[mlir][Python] fix liveContextMap under free-threading after #178529 (#179163)
#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-02-02 05:08:51 +00:00
Twice
f992f9719f
[MLIR][Python] Support dialect conversion in python bindings (#177782)
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-31 12:37:49 +08:00
RattataKing
0c99ca3494
[MLIR][Python] Remove partial LLVM APIs in python bindings (2/n) (#178529)
This PR continues work from #178290.
Cleaned up LLVM utilities in IRCore.cpp: `enumerate`, `zip`, `ArrayRef`,
`hash_value`.
2026-01-29 17:21:40 -05:00
RattataKing
0c41758502
[MLIR][Python] Remove partial LLVM APIs in python bindings (#178290)
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-28 13:27:25 -05:00
Rolf Morel
ecb1b0b90f
[MLIR][Python] Fix overly specific type annotation on PyValue.owner (#178003) 2026-01-26 18:32:08 +00:00
MaPePeR
351d06a819
[MLIR][Python] Improve Iterator performance. Don't throw in dunderNext 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](aa8578dc54/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-13 16:02:38 +00:00
Maksim Levental
619a9c1e81
[mlir][Python] downcast Value to BlockArgument or OpResult (#175264)
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-12 17:20:53 +00:00
Maksim Levental
0e4be262f4
[mlir][Python] fix dialect extensions which bind C types (#175405)
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 21:24:55 -08:00
MaPePeR
524fde8a4d
[MLIR][Python] Register OpAttributeMap as Mapping for match 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-10 09:09:05 -08:00
Twice
7873abb0a0
[MLIR][Python][NFC] Use enum class instead of enum (#174792)
This PR replace `enum`s with `enum class`es in Python bindings. No
functional change.
2026-01-08 12:40:14 +08:00
Maksim Levental
6021ed572f
[mlir][Python] use maybeDowncast for PyType/PyAttribute returns after #174156 (#174489)
#174156 made all gettors return `Py*` but skipped downcasting where
possible. So restore it by calling `.maybeDowncast`.
2026-01-05 22:39:38 +00:00
Maksim Levental
f0ef5dba6d
[mlir][Python] create MLIRPythonSupport (#171775)
# 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](d7c734b5a1/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](fdbee98df8/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](4ba51fcf79/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](bd9651bf78/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-05 09:08:13 -08:00
Rolf Morel
f12fcf030c
[MLIR][Transform][Python] transform.foreach wrapper and .owner OpViews (#172228)
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-14 22:10:31 +00:00
Mehdi Amini
b9fe6532a7
Revert "[MLIR][Transform][Python] transform.foreach wrapper and .owner OpViews" (#172225)
Reverts llvm/llvm-project#171544 ; bots are broken.
2025-12-14 21:27:02 +00:00
Rolf Morel
4cdec92827
[MLIR][Transform][Python] transform.foreach wrapper and .owner OpViews (#171544)
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 20:44:15 +00:00
Hongzheng Chen
86cc934b4a
[python] Expose replaceUsesOfWith C API (#171892)
This PR exposes the `replaceUsesOfWith` C API to Python
2025-12-11 16:09:18 -08:00
Benjamin Chetioui
012721d320
[mlir][python] Propagate error diagnostics when an op couldn't be created. (#169499) 2025-11-25 17:41:01 +00:00
Jacques Pienaar
5ab49edde2
[mlir][py][c] Enable setting block arg locations. (#169033)
This enables changing the location of a block argument. Follows the
approach for updating type of block arg.
2025-11-21 13:31:46 +00:00
Sergei Lebedev
31536e6e9a
[MLIR] [Python] ir.Value is now generic in the type of the value it holds (#166148)
This makes it similar to `mlir::TypedValue` in the MLIR C++ API and
allows users to be more specific about the values they produce or
accept.

Co-authored-by: Maksim Levental <maksim.levental@gmail.com>
2025-11-13 13:23:40 +00:00
Maksim Levental
8346a772bc
[MLIR][Python] fix PyRegionList __iter__ (#167466)
Fixes https://github.com/llvm/llvm-project/issues/167455
2025-11-11 07:25:50 -08:00
Maksim Levental
2d381bf65d
[MLIR][Python] add/fix docstrings in IRCore (#167063)
This PR adds all the missing doc strings in IRCore.cpp. It also

1. Normalizes all doc strings to have proper punctuation;
2. Inlines non-duplicated docstrings which are currently at the top of
the source file (and thereby possibly out of sync).

Follow-up PRs will do the same for the rest of the modules/source files.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-10 15:44:08 +00:00
Maksim Levental
c05ce9b005
[MLIR][Python] fix getOwner to return (typed) nb::object instead of abstract PyOpView (#165053)
https://github.com/llvm/llvm-project/pull/157930 changed `nb::object
getOwner()` to `PyOpView getOwner()` which implicitly constructs the
generic OpView against from a (possibly) concrete OpView. This PR fixes
that.
2025-10-26 01:48:46 +00:00
Perry Gibson
35cd291427
[mlir][python] add dict-style to IR attributes (#163200)
It makes sense that Attribute dicts/maps should behave like dicts in the
Python bindings. Previously this was not the case.
2025-10-16 18:42:05 +01:00
Twice
8181c3deae
[MLIR][Python] Expose the insertion point of pattern rewriter (#161001)
In [#160520](https://github.com/llvm/llvm-project/pull/160520), we
discussed the current limitations of PDL rewriting in Python (see [this
comment](https://github.com/llvm/llvm-project/pull/160520#issuecomment-3332326184)).
At the moment, we cannot create new operations in PDL native (python)
rewrite functions because the `PatternRewriter` APIs are not exposed.

This PR introduces bindings to retrieve the insertion point of the
`PatternRewriter`, enabling users to create new operations within Python
rewrite functions. With this capability, more complex rewrites e.g. with
branching and loops that involve op creations become possible.

---------

Co-authored-by: Maksim Levental <maksim.levental@gmail.com>
2025-10-05 11:12:11 +08:00
Maksim Levental
a3594cd644
[MLIR][Python] fixup Context and Location stubs and NanobindAdaptors (#161433)
add correct names for `NB_TYPE_CASTER(..., name)` so users of
`NanobindAdaptors.h` can generate the correct hints. Also fix a few
straggler stubs.
2025-10-02 08:18:47 -07:00
Maksim Levental
fea2cca4d6
[MLIR][Python] expose Operation::setLoc (#161594) 2025-10-01 21:57:10 -07:00
Maksim Levental
0d08ffd22c
[MLIR][Python] use nb::typed for return signatures (#160221)
https://github.com/llvm/llvm-project/pull/160183 removed `nb::typed`
annotation to fix bazel but it turned out to be simply a matter of not
using the correct version of nanobind (see
https://github.com/llvm/llvm-project/pull/160183#issuecomment-3321429155).
This PR restores those annotations but (mostly) moves to the return
positions of the actual methods.
2025-09-23 10:54:22 -07:00
Maksim Levental
4a9df48cf8
[MLIR][Python] restore APIs in terms of Mlir* types (#160203)
https://github.com/llvm/llvm-project/pull/157930 changed a few APIs from
`Mlir*` to `Py*` and broke users that were using them (see
https://github.com/llvm/llvm-project/pull/160183#issuecomment-3321383969).
This PR restores those APIs.
2025-09-22 15:00:57 -07:00
Maksim Levental
81cbd970cf
[MLIR][Python] remove nb::typed to fix bazel build (#160183)
https://github.com/llvm/llvm-project/pull/157930 broke bazel build (see
https://github.com/llvm/llvm-project/pull/157930#issuecomment-3318681217)
because bazel is stricter on implicit conversions (some difference in
flags passed to clang). This PR fixes by moving/removing `nb::typed`.

EDIT: and also the overlay...
2025-09-22 12:55:43 -07:00
Maksim Levental
efd96afedf
[MLIR][Python] reland (narrower) type stub generation (#157930)
This a reland of https://github.com/llvm/llvm-project/pull/155741 which
was reverted at https://github.com/llvm/llvm-project/pull/157831. This
version is narrower in scope - it only turns on automatic stub
generation for `MLIRPythonExtension.Core._mlir` and **does not do
anything automatically**. Specifically, the only CMake code added to
`AddMLIRPython.cmake` is the `mlir_generate_type_stubs` function which
is then used only in a manual way. The API for
`mlir_generate_type_stubs` is:

```
Arguments:
  MODULE_NAME: The fully-qualified name of the extension module (used for importing in python).
  DEPENDS_TARGETS: List of targets these type stubs depend on being built; usually corresponding to the
    specific extension module (e.g., something like StandalonePythonModules.extension._standaloneDialectsNanobind.dso)
    and the core bindings extension module (e.g., something like StandalonePythonModules.extension._mlir.dso).
  OUTPUT_DIR: The root output directory to emit the type stubs into.
  OUTPUTS: List of expected outputs.
  DEPENDS_TARGET_SRC_DEPS: List of cpp sources for extension library (for generating a DEPFILE).
  IMPORT_PATHS: List of paths to add to PYTHONPATH for stubgen.
  PATTERN_FILE: (Optional) Pattern file (see https://nanobind.readthedocs.io/en/latest/typing.html#pattern-files).
Outputs:
  NB_STUBGEN_CUSTOM_TARGET: The target corresponding to generation which other targets can depend on.
```

Downstream users should use `mlir_generate_type_stubs` in coordination
with `declare_mlir_python_sources` to turn on stub generation for their
own downstream dialect extensions and upstream dialect extensions if
they so choose. Standalone example shows an example.

Note, downstream will also need to set
`-DMLIR_PYTHON_PACKAGE_PREFIX=...` correctly for their bindings.
2025-09-20 18:47:32 +00:00
Maksim Levental
6a4f66476f
[MLIR][Python] restore liveModuleMap (#158506)
There are cases where the same module can have multiple references (via
`PyModule::forModule` via `PyModule::createFromCapsule`) and thus when
`PyModule`s get gc'd `mlirModuleDestroy` can get called multiple times
for the same actual underlying `mlir::Module` (i.e., double free). So we
do actually need a "liveness map" for modules.

Note, if `type_caster<MlirModule>::from_cpp` weren't a thing we could guarantree
this never happened except explicitly when users called `PyModule::createFromCapsule`.
2025-09-15 06:45:30 +02:00
Maksim Levental
c4181e51d1
[MLIR][Python] remove unnecessary arg.none() = nb::none() pattern (#157519)
We have `arg.none() = nb::none()` in a lot of places but this is no
longer necessary (as of
~[2022](62a23bb87b)).
2025-09-08 12:16:35 -07:00
Maksim Levental
9a0ed70319
[MLIR][Python] bind InsertionPointAfter (#157156) 2025-09-05 14:30:40 -07:00
Maksim Levental
70a291f322
[MLIR][Python] fix operation hashing (#156514)
https://github.com/llvm/llvm-project/pull/155114 broke op hashing
(because the python objects ceased to be reference equivalent). This PR
fixes by binding `OperationEquivalence::computeHash`.
2025-09-02 15:12:25 -05:00