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