1# MLIR Python Bindings
2
3Current status: Under development and not enabled by default
4
5## Building
6
7### Pre-requisites
8
9* A relatively recent Python3 installation
10* Installation of python dependencies as specified in
11  `mlir/python/requirements.txt`
12
13### CMake variables
14
15* **`MLIR_ENABLE_BINDINGS_PYTHON`**`:BOOL`
16
17  Enables building the Python bindings. Defaults to `OFF`.
18
19* **`Python3_EXECUTABLE`**:`STRING`
20
21  Specifies the `python` executable used for the LLVM build, including for
22  determining header/link flags for the Python bindings. On systems with
23  multiple Python implementations, setting this explicitly to the preferred
24  `python3` executable is strongly recommended.
25
26* **`MLIR_BINDINGS_PYTHON_LOCK_VERSION`**`:BOOL`
27
28  Links the native extension against the Python runtime library, which is
29  optional on some platforms. While setting this to `OFF` can yield some greater
30  deployment flexibility, linking in this way allows the linker to report
31  compile time errors for unresolved symbols on all platforms, which makes for a
32  smoother development workflow. Defaults to `ON`.
33
34### Recommended development practices
35
36It is recommended to use a python virtual environment. Many ways exist for this,
37but the following is the simplest:
38
39```shell
40# Make sure your 'python' is what you expect. Note that on multi-python
41# systems, this may have a version suffix, and on many Linuxes and MacOS where
42# python2 and python3 co-exist, you may also want to use `python3`.
43which python
44python -m venv ~/.venv/mlirdev
45source ~/.venv/mlirdev/bin/activate
46
47# Note that many LTS distros will bundle a version of pip itself that is too
48# old to download all of the latest binaries for certain platforms.
49# The pip version can be obtained with `python -m pip --version`, and for
50# Linux specifically, this should be cross checked with minimum versions
51# here: https://github.com/pypa/manylinux
52# It is recommended to upgrade pip:
53python -m pip install --upgrade pip
54
55
56# Now the `python` command will resolve to your virtual environment and
57# packages will be installed there.
58python -m pip install -r mlir/python/requirements.txt
59
60# Now run `cmake`, `ninja`, et al.
61```
62
63For interactive use, it is sufficient to add the
64`tools/mlir/python_packages/mlir_core/` directory in your `build/` directory to
65the `PYTHONPATH`. Typically:
66
67```shell
68export PYTHONPATH=$(cd build && pwd)/tools/mlir/python_packages/mlir_core
69```
70
71Note that if you have installed (i.e. via `ninja install`, et al), then
72python packages for all enabled projects will be in your install tree under
73`python_packages/` (i.e. `python_packages/mlir_core`). Official distributions
74are built with a more specialized setup.
75
76## Design
77
78### Use cases
79
80There are likely two primary use cases for the MLIR python bindings:
81
821. Support users who expect that an installed version of LLVM/MLIR will yield
83   the ability to `import mlir` and use the API in a pure way out of the box.
84
851. Downstream integrations will likely want to include parts of the API in their
86   private namespace or specially built libraries, probably mixing it with other
87   python native bits.
88
89### Composable modules
90
91In order to support use case \#2, the Python bindings are organized into
92composable modules that downstream integrators can include and re-export into
93their own namespace if desired. This forces several design points:
94
95* Separate the construction/populating of a `py::module` from `PYBIND11_MODULE`
96  global constructor.
97
98* Introduce headers for C++-only wrapper classes as other related C++ modules
99  will need to interop with it.
100
101* Separate any initialization routines that depend on optional components into
102  its own module/dependency (currently, things like `registerAllDialects` fall
103  into this category).
104
105There are a lot of co-related issues of shared library linkage, distribution
106concerns, etc that affect such things. Organizing the code into composable
107modules (versus a monolithic `cpp` file) allows the flexibility to address many
108of these as needed over time. Also, compilation time for all of the template
109meta-programming in pybind scales with the number of things you define in a
110translation unit. Breaking into multiple translation units can significantly aid
111compile times for APIs with a large surface area.
112
113### Submodules
114
115Generally, the C++ codebase namespaces most things into the `mlir` namespace.
116However, in order to modularize and make the Python bindings easier to
117understand, sub-packages are defined that map roughly to the directory structure
118of functional units in MLIR.
119
120Examples:
121
122* `mlir.ir`
123* `mlir.passes` (`pass` is a reserved word :( )
124* `mlir.dialect`
125* `mlir.execution_engine` (aside from namespacing, it is important that
126  "bulky"/optional parts like this are isolated)
127
128In addition, initialization functions that imply optional dependencies should
129be in underscored (notionally private) modules such as `_init` and linked
130separately. This allows downstream integrators to completely customize what is
131included "in the box" and covers things like dialect registration,
132pass registration, etc.
133
134### Loader
135
136LLVM/MLIR is a non-trivial python-native project that is likely to co-exist with
137other non-trivial native extensions. As such, the native extension (i.e. the
138`.so`/`.pyd`/`.dylib`) is exported as a notionally private top-level symbol
139(`_mlir`), while a small set of Python code is provided in
140`mlir/_cext_loader.py` and siblings which loads and re-exports it. This
141split provides a place to stage code that needs to prepare the environment
142*before* the shared library is loaded into the Python runtime, and also
143provides a place that one-time initialization code can be invoked apart from
144module constructors.
145
146It is recommended to avoid using `__init__.py` files to the extent possible,
147until reaching a leaf package that represents a discrete component. The rule
148to keep in mind is that the presence of an `__init__.py` file prevents the
149ability to split anything at that level or below in the namespace into
150different directories, deployment packages, wheels, etc.
151
152See the documentation for more information and advice:
153https://packaging.python.org/guides/packaging-namespace-packages/
154
155### Use the C-API
156
157The Python APIs should seek to layer on top of the C-API to the degree possible.
158Especially for the core, dialect-independent parts, such a binding enables
159packaging decisions that would be difficult or impossible if spanning a C++ ABI
160boundary. In addition, factoring in this way side-steps some very difficult
161issues that arise when combining RTTI-based modules (which pybind derived things
162are) with non-RTTI polymorphic C++ code (the default compilation mode of LLVM).
163
164### Ownership in the Core IR
165
166There are several top-level types in the core IR that are strongly owned by their python-side reference:
167
168* `PyContext` (`mlir.ir.Context`)
169* `PyModule` (`mlir.ir.Module`)
170* `PyOperation` (`mlir.ir.Operation`) - but with caveats
171
172All other objects are dependent. All objects maintain a back-reference
173(keep-alive) to their closest containing top-level object. Further, dependent
174objects fall into two categories: a) uniqued (which live for the life-time of
175the context) and b) mutable. Mutable objects need additional machinery for
176keeping track of when the C++ instance that backs their Python object is no
177longer valid (typically due to some specific mutation of the IR, deletion, or
178bulk operation).
179
180### Optionality and argument ordering in the Core IR
181
182The following types support being bound to the current thread as a context manager:
183
184* `PyLocation` (`loc: mlir.ir.Location = None`)
185* `PyInsertionPoint` (`ip: mlir.ir.InsertionPoint = None`)
186* `PyMlirContext` (`context: mlir.ir.Context = None`)
187
188In order to support composability of function arguments, when these types appear
189as arguments, they should always be the last and appear in the above order and
190with the given names (which is generally the order in which they are expected to
191need to be expressed explicitly in special cases) as necessary. Each should
192carry a default value of `py::none()` and use either a manual or automatic
193conversion for resolving either with the explicit value or a value from the
194thread context manager (i.e. `DefaultingPyMlirContext` or
195`DefaultingPyLocation`).
196
197The rationale for this is that in Python, trailing keyword arguments to the
198*right* are the most composable, enabling a variety of strategies such as kwarg
199passthrough, default values, etc. Keeping function signatures composable
200increases the chances that interesting DSLs and higher level APIs can be
201constructed without a lot of exotic boilerplate.
202
203Used consistently, this enables a style of IR construction that rarely needs to
204use explicit contexts, locations, or insertion points but is free to do so when
205extra control is needed.
206
207#### Operation hierarchy
208
209As mentioned above, `PyOperation` is special because it can exist in either a
210top-level or dependent state. The life-cycle is unidirectional: operations can
211be created detached (top-level) and once added to another operation, they are
212then dependent for the remainder of their lifetime. The situation is more
213complicated when considering construction scenarios where an operation is added
214to a transitive parent that is still detached, necessitating further accounting
215at such transition points (i.e. all such added children are initially added to
216the IR with a parent of their outer-most detached operation, but then once it is
217added to an attached operation, they need to be re-parented to the containing
218module).
219
220Due to the validity and parenting accounting needs, `PyOperation` is the owner
221for regions and blocks and needs to be a top-level type that we can count on not
222aliasing. This let's us do things like selectively invalidating instances when
223mutations occur without worrying that there is some alias to the same operation
224in the hierarchy. Operations are also the only entity that are allowed to be in
225a detached state, and they are interned at the context level so that there is
226never more than one Python `mlir.ir.Operation` object for a unique
227`MlirOperation`, regardless of how it is obtained.
228
229The C/C++ API allows for Region/Block to also be detached, but it simplifies the
230ownership model a lot to eliminate that possibility in this API, allowing the
231Region/Block to be completely dependent on its owning operation for accounting.
232The aliasing of Python `Region`/`Block` instances to underlying
233`MlirRegion`/`MlirBlock` is considered benign and these objects are not interned
234in the context (unlike operations).
235
236If we ever want to re-introduce detached regions/blocks, we could do so with new
237"DetachedRegion" class or similar and also avoid the complexity of accounting.
238With the way it is now, we can avoid having a global live list for regions and
239blocks. We may end up needing an op-local one at some point TBD, depending on
240how hard it is to guarantee how mutations interact with their Python peer
241objects. We can cross that bridge easily when we get there.
242
243Module, when used purely from the Python API, can't alias anyway, so we can use
244it as a top-level ref type without a live-list for interning. If the API ever
245changes such that this cannot be guaranteed (i.e. by letting you marshal a
246native-defined Module in), then there would need to be a live table for it too.
247
248## Style
249
250In general, for the core parts of MLIR, the Python bindings should be largely
251isomorphic with the underlying C++ structures. However, concessions are made
252either for practicality or to give the resulting library an appropriately
253"Pythonic" flavor.
254
255### Properties vs get\*() methods
256
257Generally favor converting trivial methods like `getContext()`, `getName()`,
258`isEntryBlock()`, etc to read-only Python properties (i.e. `context`). It is
259primarily a matter of calling `def_property_readonly` vs `def` in binding code,
260and makes things feel much nicer to the Python side.
261
262For example, prefer:
263
264```c++
265m.def_property_readonly("context", ...)
266```
267
268Over:
269
270```c++
271m.def("getContext", ...)
272```
273
274### __repr__ methods
275
276Things that have nice printed representations are really great :)  If there is a
277reasonable printed form, it can be a significant productivity boost to wire that
278to the `__repr__` method (and verify it with a [doctest](#sample-doctest)).
279
280### CamelCase vs snake\_case
281
282Name functions/methods/properties in `snake_case` and classes in `CamelCase`. As
283a mechanical concession to Python style, this can go a long way to making the
284API feel like it fits in with its peers in the Python landscape.
285
286If in doubt, choose names that will flow properly with other
287[PEP 8 style names](https://pep8.org/#descriptive-naming-styles).
288
289### Prefer pseudo-containers
290
291Many core IR constructs provide methods directly on the instance to query count
292and begin/end iterators. Prefer hoisting these to dedicated pseudo containers.
293
294For example, a direct mapping of blocks within regions could be done this way:
295
296```python
297region = ...
298
299for block in region:
300
301  pass
302```
303
304However, this way is preferred:
305
306```python
307region = ...
308
309for block in region.blocks:
310
311  pass
312
313print(len(region.blocks))
314print(region.blocks[0])
315print(region.blocks[-1])
316```
317
318Instead of leaking STL-derived identifiers (`front`, `back`, etc), translate
319them to appropriate `__dunder__` methods and iterator wrappers in the bindings.
320
321Note that this can be taken too far, so use good judgment. For example, block
322arguments may appear container-like but have defined methods for lookup and
323mutation that would be hard to model properly without making semantics
324complicated. If running into these, just mirror the C/C++ API.
325
326### Provide one stop helpers for common things
327
328One stop helpers that aggregate over multiple low level entities can be
329incredibly helpful and are encouraged within reason. For example, making
330`Context` have a `parse_asm` or equivalent that avoids needing to explicitly
331construct a SourceMgr can be quite nice. One stop helpers do not have to be
332mutually exclusive with a more complete mapping of the backing constructs.
333
334## Testing
335
336Tests should be added in the `test/Bindings/Python` directory and should
337typically be `.py` files that have a lit run line.
338
339We use `lit` and `FileCheck` based tests:
340
341* For generative tests (those that produce IR), define a Python module that
342  constructs/prints the IR and pipe it through `FileCheck`.
343* Parsing should be kept self-contained within the module under test by use of
344  raw constants and an appropriate `parse_asm` call.
345* Any file I/O code should be staged through a tempfile vs relying on file
346  artifacts/paths outside of the test module.
347* For convenience, we also test non-generative API interactions with the same
348  mechanisms, printing and `CHECK`ing as needed.
349
350### Sample FileCheck test
351
352```python
353# RUN: %PYTHON %s | mlir-opt -split-input-file | FileCheck
354
355# TODO: Move to a test utility class once any of this actually exists.
356def print_module(f):
357  m = f()
358  print("// -----")
359  print("// TEST_FUNCTION:", f.__name__)
360  print(m.to_asm())
361  return f
362
363# CHECK-LABEL: TEST_FUNCTION: create_my_op
364@print_module
365def create_my_op():
366  m = mlir.ir.Module()
367  builder = m.new_op_builder()
368  # CHECK: mydialect.my_operation ...
369  builder.my_op()
370  return m
371```
372
373## Integration with ODS
374
375The MLIR Python bindings integrate with the tablegen-based ODS system for
376providing user-friendly wrappers around MLIR dialects and operations. There
377are multiple parts to this integration, outlined below. Most details have
378been elided: refer to the build rules and python sources under `mlir.dialects`
379for the canonical way to use this facility.
380
381Users are responsible for providing a `{DIALECT_NAMESPACE}.py` (or an
382equivalent directory with `__init__.py` file) as the entrypoint.
383
384### Generating `_{DIALECT_NAMESPACE}_ops_gen.py` wrapper modules
385
386Each dialect with a mapping to python requires that an appropriate
387`_{DIALECT_NAMESPACE}_ops_gen.py` wrapper module is created. This is done by
388invoking `mlir-tblgen` on a python-bindings specific tablegen wrapper that
389includes the boilerplate and actual dialect specific `td` file. An example, for
390the `StandardOps` (which is assigned the namespace `std` as a special case):
391
392```tablegen
393#ifndef PYTHON_BINDINGS_STANDARD_OPS
394#define PYTHON_BINDINGS_STANDARD_OPS
395
396include "mlir/Bindings/Python/Attributes.td"
397include "mlir/Dialect/StandardOps/IR/Ops.td"
398
399#endif
400```
401
402In the main repository, building the wrapper is done via the CMake function
403`add_mlir_dialect_python_bindings`, which invokes:
404
405```
406mlir-tblgen -gen-python-op-bindings -bind-dialect={DIALECT_NAMESPACE} \
407    {PYTHON_BINDING_TD_FILE}
408```
409
410The generates op classes must be included in the `{DIALECT_NAMESPACE}.py` file
411in a similar way that generated headers are included for C++ generated code:
412
413```python
414from ._my_dialect_ops_gen import *
415```
416
417### Extending the search path for wrapper modules
418
419When the python bindings need to locate a wrapper module, they consult the
420`dialect_search_path` and use it to find an appropriately named module. For
421the main repository, this search path is hard-coded to include the
422`mlir.dialects` module, which is where wrappers are emitted by the abobe build
423rule. Out of tree dialects and add their modules to the search path by calling:
424
425```python
426mlir._cext.append_dialect_search_prefix("myproject.mlir.dialects")
427```
428
429### Wrapper module code organization
430
431The wrapper module tablegen emitter outputs:
432
433* A `_Dialect` class (extending `mlir.ir.Dialect`) with a `DIALECT_NAMESPACE`
434  attribute.
435* An `{OpName}` class for each operation (extending `mlir.ir.OpView`).
436* Decorators for each of the above to register with the system.
437
438Note: In order to avoid naming conflicts, all internal names used by the wrapper
439module are prefixed by `_ods_`.
440
441Each concrete `OpView` subclass further defines several public-intended
442attributes:
443
444* `OPERATION_NAME` attribute with the `str` fully qualified operation name
445  (i.e. `std.absf`).
446* An `__init__` method for the *default builder* if one is defined or inferred
447  for the operation.
448* `@property` getter for each operand or result (using an auto-generated name
449  for unnamed of each).
450* `@property` getter, setter and deleter for each declared attribute.
451
452It further emits additional private-intended attributes meant for subclassing
453and customization (default cases omit these attributes in favor of the
454defaults on `OpView`):
455
456* `_ODS_REGIONS`: A specification on the number and types of regions.
457  Currently a tuple of (min_region_count, has_no_variadic_regions). Note that
458  the API does some light validation on this but the primary purpose is to
459  capture sufficient information to perform other default building and region
460  accessor generation.
461* `_ODS_OPERAND_SEGMENTS` and `_ODS_RESULT_SEGMENTS`: Black-box value which
462  indicates the structure of either the operand or results with respect to
463  variadics. Used by `OpView._ods_build_default` to decode operand and result
464  lists that contain lists.
465
466#### Default Builder
467
468Presently, only a single, default builder is mapped to the `__init__` method.
469The intent is that this `__init__` method represents the *most specific* of
470the builders typically generated for C++; however currently it is just the
471generic form below.
472
473* One argument for each declared result:
474  * For single-valued results: Each will accept an `mlir.ir.Type`.
475  * For variadic results: Each will accept a `List[mlir.ir.Type]`.
476* One argument for each declared operand or attribute:
477  * For single-valued operands: Each will accept an `mlir.ir.Value`.
478  * For variadic operands: Each will accept a `List[mlir.ir.Value]`.
479  * For attributes, it will accept an `mlir.ir.Attribute`.
480* Trailing usage-specific, optional keyword arguments:
481  * `loc`: An explicit `mlir.ir.Location` to use. Defaults to the location
482    bound to the thread (i.e. `with Location.unknown():`) or an error if none
483    is bound nor specified.
484  * `ip`: An explicit `mlir.ir.InsertionPoint` to use. Default to the insertion
485    point bound to the thread (i.e. `with InsertionPoint(...):`).
486
487In addition, each `OpView` inherits a `build_generic` method which allows
488construction via a (nested in the case of variadic) sequence of `results` and
489`operands`. This can be used to get some default construction semantics for
490operations that are otherwise unsupported in Python, at the expense of having
491a very generic signature.
492
493#### Extending Generated Op Classes
494
495Note that this is a rather complex mechanism and this section errs on the side
496of explicitness. Users are encouraged to find an example and duplicate it if
497they don't feel the need to understand the subtlety. The `builtin` dialect
498provides some relatively simple examples.
499
500As mentioned above, the build system generates Python sources like
501`_{DIALECT_NAMESPACE}_ops_gen.py` for each dialect with Python bindings. It
502is often desirable to to use these generated classes as a starting point for
503further customization, so an extension mechanism is provided to make this
504easy (you are always free to do ad-hoc patching in your `{DIALECT_NAMESPACE}.py`
505file but we prefer a more standard mechanism that is applied uniformly).
506
507To provide extensions, add a `_{DIALECT_NAMESPACE}_ops_ext.py` file to the
508`dialects` module (i.e. adjacent to your `{DIALECT_NAMESPACE}.py` top-level
509and the `*_ops_gen.py` file). Using the `builtin` dialect and `FuncOp` as an
510example, the generated code will include an import like this:
511
512```python
513try:
514  from . import _builtin_ops_ext as _ods_ext_module
515except ImportError:
516  _ods_ext_module = None
517```
518
519Then for each generated concrete `OpView` subclass, it will apply a decorator
520like:
521
522```python
523@_ods_cext.register_operation(_Dialect)
524@_ods_extend_opview_class(_ods_ext_module)
525class FuncOp(_ods_ir.OpView):
526```
527
528See the `_ods_common.py` `extend_opview_class` function for details of the
529mechanism. At a high level:
530
531* If the extension module exists, locate an extension class for the op (in
532  this example, `FuncOp`):
533  * First by looking for an attribute with the exact name in the extension
534    module.
535  * Falling back to calling a `select_opview_mixin(parent_opview_cls)`
536    function defined in the extension module.
537* If a mixin class is found, a new subclass is dynamically created that multiply
538  inherits from `({_builtin_ops_ext.FuncOp}, _builtin_ops_gen.FuncOp)`.
539
540The mixin class should not inherit from anything (i.e. directly extends
541`object` only). The facility is typically used to define custom `__init__`
542methods, properties, instance methods and static methods. Due to the
543inheritance ordering, the mixin class can act as though it extends the
544generated `OpView` subclass in most contexts (i.e.
545`issubclass(_builtin_ops_ext.FuncOp, OpView)` will return `False` but usage
546generally allows you treat it as duck typed as an `OpView`).
547
548There are a couple of recommendations, given how the class hierarchy is
549defined:
550
551* For static methods that need to instantiate the actual "leaf" op (which
552  is dynamically generated and would result in circular dependencies to try
553  to reference by name), prefer to use `@classmethod` and the concrete
554  subclass will be provided as your first `cls` argument. See
555  `_builtin_ops_ext.FuncOp.from_py_func` as an example.
556* If seeking to replace the generated `__init__` method entirely, you may
557  actually want to invoke the super-super-class `mlir.ir.OpView` constructor
558  directly, as it takes an `mlir.ir.Operation`, which is likely what you
559  are constructing (i.e. the generated `__init__` method likely adds more
560  API constraints than you want to expose in a custom builder).
561
562A pattern that comes up frequently is wanting to provide a sugared `__init__`
563method which has optional or type-polymorphism/implicit conversions but to
564otherwise want to invoke the default op building logic. For such cases,
565it is recommended to use an idiom such as:
566
567```python
568  def __init__(self, sugar, spice, *, loc=None, ip=None):
569    ... massage into result_type, operands, attributes ...
570    OpView.__init__(self, self.build_generic(
571        results=[result_type],
572        operands=operands,
573        attributes=attributes,
574        loc=loc,
575        ip=ip))
576```
577
578Refer to the documentation for `build_generic` for more information.
579