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