1# MLIR Python Bindings 2 3**Current status**: Under development and not enabled by default 4 5[TOC] 6 7## Building 8 9### Pre-requisites 10 11* A relatively recent Python3 installation 12* Installation of python dependencies as specified in 13 `mlir/python/requirements.txt` 14 15### CMake variables 16 17* **`MLIR_ENABLE_BINDINGS_PYTHON`**`:BOOL` 18 19 Enables building the Python bindings. Defaults to `OFF`. 20 21* **`Python3_EXECUTABLE`**:`STRING` 22 23 Specifies the `python` executable used for the LLVM build, including for 24 determining header/link flags for the Python bindings. On systems with 25 multiple Python implementations, setting this explicitly to the preferred 26 `python3` executable is strongly recommended. 27 28* **`MLIR_BINDINGS_PYTHON_LOCK_VERSION`**`:BOOL` 29 30 Links the native extension against the Python runtime library, which is 31 optional on some platforms. While setting this to `OFF` can yield some greater 32 deployment flexibility, linking in this way allows the linker to report 33 compile time errors for unresolved symbols on all platforms, which makes for a 34 smoother development workflow. Defaults to `ON`. 35 36### Recommended development practices 37 38It is recommended to use a python virtual environment. Many ways exist for this, 39but the following is the simplest: 40 41```shell 42# Make sure your 'python' is what you expect. Note that on multi-python 43# systems, this may have a version suffix, and on many Linuxes and MacOS where 44# python2 and python3 co-exist, you may also want to use `python3`. 45which python 46python -m venv ~/.venv/mlirdev 47source ~/.venv/mlirdev/bin/activate 48 49# Note that many LTS distros will bundle a version of pip itself that is too 50# old to download all of the latest binaries for certain platforms. 51# The pip version can be obtained with `python -m pip --version`, and for 52# Linux specifically, this should be cross checked with minimum versions 53# here: https://github.com/pypa/manylinux 54# It is recommended to upgrade pip: 55python -m pip install --upgrade pip 56 57 58# Now the `python` command will resolve to your virtual environment and 59# packages will be installed there. 60python -m pip install -r mlir/python/requirements.txt 61 62# Now run `cmake`, `ninja`, et al. 63``` 64 65For interactive use, it is sufficient to add the 66`tools/mlir/python_packages/mlir_core/` directory in your `build/` directory to 67the `PYTHONPATH`. Typically: 68 69```shell 70export PYTHONPATH=$(cd build && pwd)/tools/mlir/python_packages/mlir_core 71``` 72 73Note that if you have installed (i.e. via `ninja install`, et al), then 74python packages for all enabled projects will be in your install tree under 75`python_packages/` (i.e. `python_packages/mlir_core`). Official distributions 76are built with a more specialized setup. 77 78## Design 79 80### Use cases 81 82There are likely two primary use cases for the MLIR python bindings: 83 841. Support users who expect that an installed version of LLVM/MLIR will yield 85 the ability to `import mlir` and use the API in a pure way out of the box. 86 871. Downstream integrations will likely want to include parts of the API in their 88 private namespace or specially built libraries, probably mixing it with other 89 python native bits. 90 91### Composable modules 92 93In order to support use case \#2, the Python bindings are organized into 94composable modules that downstream integrators can include and re-export into 95their own namespace if desired. This forces several design points: 96 97* Separate the construction/populating of a `py::module` from `PYBIND11_MODULE` 98 global constructor. 99 100* Introduce headers for C++-only wrapper classes as other related C++ modules 101 will need to interop with it. 102 103* Separate any initialization routines that depend on optional components into 104 its own module/dependency (currently, things like `registerAllDialects` fall 105 into this category). 106 107There are a lot of co-related issues of shared library linkage, distribution 108concerns, etc that affect such things. Organizing the code into composable 109modules (versus a monolithic `cpp` file) allows the flexibility to address many 110of these as needed over time. Also, compilation time for all of the template 111meta-programming in pybind scales with the number of things you define in a 112translation unit. Breaking into multiple translation units can significantly aid 113compile times for APIs with a large surface area. 114 115### Submodules 116 117Generally, the C++ codebase namespaces most things into the `mlir` namespace. 118However, in order to modularize and make the Python bindings easier to 119understand, sub-packages are defined that map roughly to the directory structure 120of functional units in MLIR. 121 122Examples: 123 124* `mlir.ir` 125* `mlir.passes` (`pass` is a reserved word :( ) 126* `mlir.dialect` 127* `mlir.execution_engine` (aside from namespacing, it is important that 128 "bulky"/optional parts like this are isolated) 129 130In addition, initialization functions that imply optional dependencies should 131be in underscored (notionally private) modules such as `_init` and linked 132separately. This allows downstream integrators to completely customize what is 133included "in the box" and covers things like dialect registration, 134pass registration, etc. 135 136### Loader 137 138LLVM/MLIR is a non-trivial python-native project that is likely to co-exist with 139other non-trivial native extensions. As such, the native extension (i.e. the 140`.so`/`.pyd`/`.dylib`) is exported as a notionally private top-level symbol 141(`_mlir`), while a small set of Python code is provided in 142`mlir/_cext_loader.py` and siblings which loads and re-exports it. This 143split provides a place to stage code that needs to prepare the environment 144*before* the shared library is loaded into the Python runtime, and also 145provides a place that one-time initialization code can be invoked apart from 146module constructors. 147 148It is recommended to avoid using `__init__.py` files to the extent possible, 149until reaching a leaf package that represents a discrete component. The rule 150to keep in mind is that the presence of an `__init__.py` file prevents the 151ability to split anything at that level or below in the namespace into 152different directories, deployment packages, wheels, etc. 153 154See the documentation for more information and advice: 155https://packaging.python.org/guides/packaging-namespace-packages/ 156 157### Use the C-API 158 159The Python APIs should seek to layer on top of the C-API to the degree possible. 160Especially for the core, dialect-independent parts, such a binding enables 161packaging decisions that would be difficult or impossible if spanning a C++ ABI 162boundary. In addition, factoring in this way side-steps some very difficult 163issues that arise when combining RTTI-based modules (which pybind derived things 164are) with non-RTTI polymorphic C++ code (the default compilation mode of LLVM). 165 166### Ownership in the Core IR 167 168There are several top-level types in the core IR that are strongly owned by their python-side reference: 169 170* `PyContext` (`mlir.ir.Context`) 171* `PyModule` (`mlir.ir.Module`) 172* `PyOperation` (`mlir.ir.Operation`) - but with caveats 173 174All other objects are dependent. All objects maintain a back-reference 175(keep-alive) to their closest containing top-level object. Further, dependent 176objects fall into two categories: a) uniqued (which live for the life-time of 177the context) and b) mutable. Mutable objects need additional machinery for 178keeping track of when the C++ instance that backs their Python object is no 179longer valid (typically due to some specific mutation of the IR, deletion, or 180bulk operation). 181 182### Optionality and argument ordering in the Core IR 183 184The following types support being bound to the current thread as a context manager: 185 186* `PyLocation` (`loc: mlir.ir.Location = None`) 187* `PyInsertionPoint` (`ip: mlir.ir.InsertionPoint = None`) 188* `PyMlirContext` (`context: mlir.ir.Context = None`) 189 190In order to support composability of function arguments, when these types appear 191as arguments, they should always be the last and appear in the above order and 192with the given names (which is generally the order in which they are expected to 193need to be expressed explicitly in special cases) as necessary. Each should 194carry a default value of `py::none()` and use either a manual or automatic 195conversion for resolving either with the explicit value or a value from the 196thread context manager (i.e. `DefaultingPyMlirContext` or 197`DefaultingPyLocation`). 198 199The rationale for this is that in Python, trailing keyword arguments to the 200*right* are the most composable, enabling a variety of strategies such as kwarg 201passthrough, default values, etc. Keeping function signatures composable 202increases the chances that interesting DSLs and higher level APIs can be 203constructed without a lot of exotic boilerplate. 204 205Used consistently, this enables a style of IR construction that rarely needs to 206use explicit contexts, locations, or insertion points but is free to do so when 207extra control is needed. 208 209#### Operation hierarchy 210 211As mentioned above, `PyOperation` is special because it can exist in either a 212top-level or dependent state. The life-cycle is unidirectional: operations can 213be created detached (top-level) and once added to another operation, they are 214then dependent for the remainder of their lifetime. The situation is more 215complicated when considering construction scenarios where an operation is added 216to a transitive parent that is still detached, necessitating further accounting 217at such transition points (i.e. all such added children are initially added to 218the IR with a parent of their outer-most detached operation, but then once it is 219added to an attached operation, they need to be re-parented to the containing 220module). 221 222Due to the validity and parenting accounting needs, `PyOperation` is the owner 223for regions and blocks and needs to be a top-level type that we can count on not 224aliasing. This let's us do things like selectively invalidating instances when 225mutations occur without worrying that there is some alias to the same operation 226in the hierarchy. Operations are also the only entity that are allowed to be in 227a detached state, and they are interned at the context level so that there is 228never more than one Python `mlir.ir.Operation` object for a unique 229`MlirOperation`, regardless of how it is obtained. 230 231The C/C++ API allows for Region/Block to also be detached, but it simplifies the 232ownership model a lot to eliminate that possibility in this API, allowing the 233Region/Block to be completely dependent on its owning operation for accounting. 234The aliasing of Python `Region`/`Block` instances to underlying 235`MlirRegion`/`MlirBlock` is considered benign and these objects are not interned 236in the context (unlike operations). 237 238If we ever want to re-introduce detached regions/blocks, we could do so with new 239"DetachedRegion" class or similar and also avoid the complexity of accounting. 240With the way it is now, we can avoid having a global live list for regions and 241blocks. We may end up needing an op-local one at some point TBD, depending on 242how hard it is to guarantee how mutations interact with their Python peer 243objects. We can cross that bridge easily when we get there. 244 245Module, when used purely from the Python API, can't alias anyway, so we can use 246it as a top-level ref type without a live-list for interning. If the API ever 247changes such that this cannot be guaranteed (i.e. by letting you marshal a 248native-defined Module in), then there would need to be a live table for it too. 249 250## User-level API 251 252### Context Management 253 254The bindings rely on Python 255[context managers](https://docs.python.org/3/reference/datamodel.html#context-managers) 256(`with` statements) to simplify creation and handling of IR objects by omitting 257repeated arguments such as MLIR contexts, operation insertion points and 258locations. A context manager sets up the default object to be used by all 259binding calls within the following context and in the same thread. This default 260can be overridden by specific calls through the dedicated keyword arguments. 261 262#### MLIR Context 263 264An MLIR context is a top-level entity that owns attributes and types and is 265referenced from virtually all IR constructs. Contexts also provide thread safety 266at the C++ level. In Python bindings, the MLIR context is also a Python context 267manager, one can write: 268 269```python 270from mlir.ir import Context, Module 271 272with Context() as ctx: 273 # IR construction using `ctx` as context. 274 275 # For example, parsing an MLIR module from string requires the context. 276 Module.parse("builtin.module {}") 277``` 278 279IR objects referencing a context usually provide access to it through the 280`.context` property. Most IR-constructing functions expect the context to be 281provided in some form. In case of attributes and types, the context may be 282extracted from the contained attribute or type. In case of operations, the 283context is systematically extracted from Locations (see below). When the context 284cannot be extracted from any argument, the bindings API expects the (keyword) 285argument `context`. If it is not provided or set to `None` (default), it will be 286looked up from an implicit stack of contexts maintained by the bindings in the 287current thread and updated by context managers. If there is no surrounding 288context, an error will be raised. 289 290Note that it is possible to manually specify the MLIR context both inside and 291outside of the `with` statement: 292 293```python 294from mlir.ir import Context, Module 295 296standalone_ctx = Context() 297with Context() as managed_ctx: 298 # Parse a module in managed_ctx. 299 Module.parse("...") 300 301 # Parse a module in standalone_ctx (override the context manager). 302 Module.parse("...", context=standalone_ctx) 303 304# Parse a module without using context managers. 305Module.parse("...", context=standalone_ctx) 306``` 307 308The context object remains live as long as there are IR objects referencing it. 309 310#### Insertion Points and Locations 311 312When constructing an MLIR operation, two pieces of information are required: 313 314- an *insertion point* that indicates where the operation is to be created in 315 the IR region/block/operation structure (usually before or after another 316 operation, or at the end of some block); it may be missing, at which point 317 the operation is created in the *detached* state; 318- a *location* that contains user-understandable information about the source 319 of the operation (for example, file/line/column information), which must 320 always be provided as it carries a reference to the MLIR context. 321 322Both can be provided using context managers or explicitly as keyword arguments 323in the operation constructor. They can be also provided as keyword arguments 324`ip` and `loc` both within and outside of the context manager. 325 326```python 327from mlir.ir import Context, InsertionPoint, Location, Module, Operation 328 329with Context() as ctx: 330 module = Module.create() 331 332 # Prepare for inserting operations into the body of the module and indicate 333 # that these operations originate in the "f.mlir" file at the given line and 334 # column. 335 with InsertionPoint(module.body), Location.file("f.mlir", line=42, col=1): 336 # This operation will be inserted at the end of the module body and will 337 # have the location set up by the context manager. 338 Operation(<...>) 339 340 # This operation will be inserted at the end of the module (and after the 341 # previously constructed operation) and will have the location provided as 342 # the keyword argument. 343 Operation(<...>, loc=Location.file("g.mlir", line=1, col=10)) 344 345 # This operation will be inserted at the *beginning* of the block rather 346 # than at its end. 347 Operation(<...>, ip=InsertionPoint.at_block_begin(module.body)) 348``` 349 350Note that `Location` needs an MLIR context to be constructed. It can take the 351context set up in the current thread by some surrounding context manager, or 352accept it as an explicit argument: 353 354```python 355from mlir.ir import Context, Location 356 357# Create a context and a location in this context in the same `with` statement. 358with Context() as ctx, Location.file("f.mlir", line=42, col=1, context=ctx): 359 pass 360``` 361 362Locations are owned by the context and maintain it live as long as they are 363(transitively) referenced from somewhere in Python code. 364 365Unlike locations, the insertion point may be left unspecified (or, equivalently, 366set to `None` or `False`) during operation construction. In this case, the 367operation is created in the *detached* state, that is, it is not added into the 368region of another operation and is owned by the caller. This is usually the case 369for top-level operations that contain the IR, such as modules. Regions, blocks 370and values contained in an operation point back to it and maintain it live. 371 372### Inspecting IR Objects 373 374Inspecting the IR is one of the primary tasks the Python bindings are designed 375for. One can traverse the IR operation/region/block structure and inspect their 376aspects such as operation attributes and value types. 377 378#### Operations, Regions and Blocks 379 380Operations are represented as either: 381 382- the generic `Operation` class, useful in particular for generic processing 383 of unregistered operations; or 384- a specific subclass of `OpView` that provides more semantically-loaded 385 accessors to operation properties. 386 387Given an `OpView` subclass, one can obtain an `Operation` using its `.operation` 388property. Given an `Operation`, one can obtain the corresponding `OpView` using 389its `.opview` property *as long as* the corresponding class has been set up. 390This typically means that the Python module of its dialect has been loaded. By 391default, the `OpView` version is produced when navigating the IR tree. 392 393One can check if an operation has a specific type by means of Python's 394`isinstance` function: 395 396```python 397operation = <...> 398opview = <...> 399if isinstance(operation.opview, mydialect.MyOp): 400 pass 401if isinstance(opview, mydialect.MyOp): 402 pass 403``` 404 405The components of an operation can be inspected using its properties. 406 407- `attributes` is a collection of operation attributes . It can be subscripted 408 as both dictionary and sequence, e.g., both `operation.attributes["value"]` 409 and `operation.attributes[0]` will work. There is no guarantee on the order 410 in which the attributes are traversed when iterating over the `attributes` 411 property as sequence. 412- `operands` is a sequence collection of operation operands. 413- `results` is a sequence collection of operation results. 414- `regions` is a sequence collection of regions attached to the operation. 415 416The objects produced by `operands` and `results` have a `.types` property that 417contains a sequence collection of types of the corresponding values. 418 419```python 420from mlir.ir import Operation 421 422operation1 = <...> 423operation2 = <...> 424if operation1.results.types == operation2.operand.types: 425 pass 426``` 427 428`OpView` subclasses for specific operations may provide leaner accessors to 429properties of an opeation. For example, named attributes, operand and results 430are usually accessible as properties of the `OpView` subclass with the same 431name, such as `operation.const_value` instead of 432`operation.attributes["const_value"]`. If this name is a reserved Python 433keyword, it is suffixed with an underscore. 434 435The operation itself is iterable, which provides access to the attached regions 436in order: 437 438```python 439from mlir.ir import Operation 440 441operation = <...> 442for region in operation: 443 do_something_with_region(region) 444``` 445 446A region is conceptually a sequence of blocks. Objects of the `Region` class are 447thus iterable, which provides access to the blocks. One can also use the 448`.blocks` property. 449 450```python 451# Regions are directly iterable and give acceess to blocks. 452for block1, block2 in zip(operation.regions[0], operation.regions[0].blocks) 453 assert block1 == block2 454``` 455 456A block contains a sequence of operations, and has several additional 457properties. Objects of the `Block` class are iterable and provide access to the 458operations contained in the block. So does the `.operations` property. Blocks 459also have a list of arguments available as a sequence collection using the 460`.arguments` property. 461 462Block and region belong to the parent operation in Python bindings and keep it 463alive. This operation can be accessed using the `.owner` property. 464 465#### Attributes and Types 466 467Attributes and types are (mostly) immutable context-owned objects. They are 468represented as either: 469 470- an opaque `Attribute` or `Type` object supporting printing and comparsion; 471 or 472- a concrete subclass thereof with access to properties of the attribute or 473 type. 474 475Given an `Attribute` or `Type` object, one can obtain a concrete subclass using 476the constructor of the subclass. This may raise a `ValueError` if the attribute 477or type does not have the expected subclass: 478 479```python 480from mlir.ir import Attribute, Type 481from mlir.<dialect> import ConcreteAttr, ConcreteType 482 483attribute = <...> 484type = <...> 485try: 486 concrete_attr = ConcreteAttr(attribute) 487 concrete_type = ConcreteType(type) 488except ValueError as e: 489 # Handle incorrect subclass. 490``` 491 492In addition, concrete attribute and type classes provide a static `isinstance` 493method to check whether an object of the opaque `Attribute` or `Type` type can 494be downcasted: 495 496```python 497from mlir.ir import Attribute, Type 498from mlir.<dialect> import ConcreteAttr, ConcreteType 499 500attribute = <...> 501type = <...> 502 503# No need to handle errors here. 504if ConcreteAttr.isinstance(attribute): 505 concrete_attr = ConcreteAttr(attribute) 506if ConcreteType.isinstance(type): 507 concrete_type = ConcreteType(type) 508``` 509 510By default, and unlike operations, attributes and types are returned from IR 511traversals using the opaque `Attribute` or `Type` that needs to be downcasted. 512 513Concrete attribute and type classes usually expose their properties as Python 514readonly properties. For example, the elemental type of a tensor type can be 515accessed using the `.element_type` property. 516 517#### Values 518 519MLIR has two kinds of values based on their defining object: block arguments and 520operation results. Values are handled similarly to attributes and types. They 521are represented as either: 522 523- a generic `Value` object; or 524- a concrete `BlockArgument` or `OpResult` object. 525 526The former provides all the generic functionality such as comparison, type 527access and printing. The latter provide access to the defining block or 528operation and the position of the value within it. By default, the generic 529`Value` objects are returned from IR traversals. Downcasting is implemented 530through concrete subclass constructors, similarly to attribtues and types: 531 532```python 533from mlir.ir import BlockArgument, OpResult, Value 534 535value = ... 536 537# Set `concrete` to the specific value subclass. 538try: 539 concrete = BlockArgument(value) 540except ValueError: 541 # This must not raise another ValueError as values are either block arguments 542 # or op results. 543 concrete = OpResult(value) 544``` 545 546### Creating IR Objects 547 548Python bindings also support IR creation and manipulation. 549 550#### Operations, Regions and Blocks 551 552Operations can be created given a `Location` and an optional `InsertionPoint`. 553It is often easier to user context managers to specify locations and insertion 554points for several operations created in a row as decribed above. 555 556Concrete operations can be created by using constructors of the corresponding 557`OpView` subclasses. The generic, default form of the constructor accepts: 558 559- an optional sequence of types for operation results (`results`); 560- an optional sequence of values for operation operands, or another operation 561 producing those values (`operands`); 562- an optional dictionary of operation attributes (`attributes`); 563- an optional sequence of successor blocks (`successors`); 564- the number of regions to attach to the operation (`regions`, default `0`); 565- the `loc` keyword argument containing the `Location` of this operation; if 566 `None`, the location created by the closest context manager is used or an 567 exception will be raised if there is no context manager; 568- the `ip` keyword argument indicating where the operation will be inserted in 569 the IR; if `None`, the insertion point created by the closest context 570 manager is used; if there is no surrounding context manager, the operation 571 is created in the detached state. 572 573Most operations will customize the constructor to accept a reduced list of 574arguments that are relevant for the operation. For example, zero-result 575operations may omit the `results` argument, so can the operations where the 576result types can be derived from operand types unambiguously. As a concrete 577example, built-in function operations can be constructed by providing a function 578name as string and its argument and result types as a tuple of sequences: 579 580```python 581from mlir.ir import Context, Module 582from mlir.dialects import builtin 583 584with Context(): 585 module = Module.create() 586 with InsertionPoint(module.body), Location.unknown(): 587 func = builtin.FuncOp("main", ([], [])) 588``` 589 590Also see below for constructors generated from ODS. 591 592Operations can also be constructed using the generic class and based on the 593canonical string name of the operation using `Operation.create`. It accepts the 594operation name as string, which must exactly match the canonical name of the 595operation in C++ or ODS, followed by the same argument list as the default 596constructor for `OpView`. *This form is discouraged* from use and is intended 597for generic operation processing. 598 599```python 600from mlir.ir import Context, Module 601from mlir.dialects import builtin 602 603with Context(): 604 module = Module.create() 605 with InsertionPoint(module.body), Location.unknown(): 606 # Operations can be created in a generic way. 607 func = Operation.create( 608 "builtin.func", results=[], operands=[], 609 attributes={"type":TypeAttr.get(FunctionType.get([], []))}, 610 successors=None, regions=1) 611 # The result will be downcasted to the concrete `OpView` subclass if 612 # available. 613 assert isinstance(func, builtin.FuncOp) 614``` 615 616Regions are created for an operation when constructing it on the C++ side. They 617are not constructible in Python and are not expected to exist outside of 618operations (unlike in C++ that supports detached regions). 619 620Blocks can be created within a given region and inserted before or after another 621block of the same region using `create_before()`, `create_after()` methods of 622the `Block` class. They are not expected to exist outside of regions (unlike in 623C++ that supports detached blocks). 624 625Blocks can be used to create `InsertionPoint`s, which can point to the beginning 626or the end of the block, or just before its terminator. It is common for 627`OpView` subclasses to provide a `.body` property that can be used to construct 628an `InsertionPoint`. For example, builtin `Module` and `FuncOp` provide a 629`.body` and `.add_entry_blocK()`, respectively. 630 631#### Attributes and Types 632 633Attributes and types can be created given a `Context` or another attribute or 634type object that already references the context. To indicate that they are owned 635by the context, they are obtained by calling the static `get` method on the 636concrete attribute or type class. These method take as arguments the data 637necessary to construct the attribute or type and a the keyword `context` 638argument when the context cannot be derived from other arguments. 639 640```python 641from mlir.ir import Context, F32Type, FloatAttr 642 643# Attribute and types require access to an MLIR context, either directly or 644# through another context-owned object. 645ctx = Context() 646f32 = F32Type.get(context=ctx) 647pi = FloatAttr.get(f32, 3.14) 648 649# They may use the context defined by the surrounding context manager. 650with Context(): 651 f32 = F32Type.get() 652 pi = FloatAttr.get(f32, 3.14) 653``` 654 655Some attributes provide additional construction methods for clarity. 656 657```python 658from mlir.ir import Context, IntegerAttr, IntegerType 659 660with Context(): 661 i8 = IntegerType.get_signless(8) 662 IntegerAttr.get(i8, 42) 663``` 664 665Builtin attribute can often be constructed from Python types with similar 666structure. For example, `ArrayAttr` can be constructed from a sequence 667collection of attributes, and a `DictAttr` can be constructed from a dictionary: 668 669```python 670from mlir.ir import ArrayAttr, Context, DictAttr, UnitAttr 671 672with Context(): 673 array = ArrayAttr.get([UnitAttr.get(), UnitAttr.get()]) 674 dictionary = DictAttr.get({"array": array, "unit": UnitAttr.get()}) 675``` 676 677## Style 678 679In general, for the core parts of MLIR, the Python bindings should be largely 680isomorphic with the underlying C++ structures. However, concessions are made 681either for practicality or to give the resulting library an appropriately 682"Pythonic" flavor. 683 684### Properties vs get\*() methods 685 686Generally favor converting trivial methods like `getContext()`, `getName()`, 687`isEntryBlock()`, etc to read-only Python properties (i.e. `context`). It is 688primarily a matter of calling `def_property_readonly` vs `def` in binding code, 689and makes things feel much nicer to the Python side. 690 691For example, prefer: 692 693```c++ 694m.def_property_readonly("context", ...) 695``` 696 697Over: 698 699```c++ 700m.def("getContext", ...) 701``` 702 703### __repr__ methods 704 705Things that have nice printed representations are really great :) If there is a 706reasonable printed form, it can be a significant productivity boost to wire that 707to the `__repr__` method (and verify it with a [doctest](#sample-doctest)). 708 709### CamelCase vs snake\_case 710 711Name functions/methods/properties in `snake_case` and classes in `CamelCase`. As 712a mechanical concession to Python style, this can go a long way to making the 713API feel like it fits in with its peers in the Python landscape. 714 715If in doubt, choose names that will flow properly with other 716[PEP 8 style names](https://pep8.org/#descriptive-naming-styles). 717 718### Prefer pseudo-containers 719 720Many core IR constructs provide methods directly on the instance to query count 721and begin/end iterators. Prefer hoisting these to dedicated pseudo containers. 722 723For example, a direct mapping of blocks within regions could be done this way: 724 725```python 726region = ... 727 728for block in region: 729 730 pass 731``` 732 733However, this way is preferred: 734 735```python 736region = ... 737 738for block in region.blocks: 739 740 pass 741 742print(len(region.blocks)) 743print(region.blocks[0]) 744print(region.blocks[-1]) 745``` 746 747Instead of leaking STL-derived identifiers (`front`, `back`, etc), translate 748them to appropriate `__dunder__` methods and iterator wrappers in the bindings. 749 750Note that this can be taken too far, so use good judgment. For example, block 751arguments may appear container-like but have defined methods for lookup and 752mutation that would be hard to model properly without making semantics 753complicated. If running into these, just mirror the C/C++ API. 754 755### Provide one stop helpers for common things 756 757One stop helpers that aggregate over multiple low level entities can be 758incredibly helpful and are encouraged within reason. For example, making 759`Context` have a `parse_asm` or equivalent that avoids needing to explicitly 760construct a SourceMgr can be quite nice. One stop helpers do not have to be 761mutually exclusive with a more complete mapping of the backing constructs. 762 763## Testing 764 765Tests should be added in the `test/Bindings/Python` directory and should 766typically be `.py` files that have a lit run line. 767 768We use `lit` and `FileCheck` based tests: 769 770* For generative tests (those that produce IR), define a Python module that 771 constructs/prints the IR and pipe it through `FileCheck`. 772* Parsing should be kept self-contained within the module under test by use of 773 raw constants and an appropriate `parse_asm` call. 774* Any file I/O code should be staged through a tempfile vs relying on file 775 artifacts/paths outside of the test module. 776* For convenience, we also test non-generative API interactions with the same 777 mechanisms, printing and `CHECK`ing as needed. 778 779### Sample FileCheck test 780 781```python 782# RUN: %PYTHON %s | mlir-opt -split-input-file | FileCheck 783 784# TODO: Move to a test utility class once any of this actually exists. 785def print_module(f): 786 m = f() 787 print("// -----") 788 print("// TEST_FUNCTION:", f.__name__) 789 print(m.to_asm()) 790 return f 791 792# CHECK-LABEL: TEST_FUNCTION: create_my_op 793@print_module 794def create_my_op(): 795 m = mlir.ir.Module() 796 builder = m.new_op_builder() 797 # CHECK: mydialect.my_operation ... 798 builder.my_op() 799 return m 800``` 801 802## Integration with ODS 803 804The MLIR Python bindings integrate with the tablegen-based ODS system for 805providing user-friendly wrappers around MLIR dialects and operations. There 806are multiple parts to this integration, outlined below. Most details have 807been elided: refer to the build rules and python sources under `mlir.dialects` 808for the canonical way to use this facility. 809 810Users are responsible for providing a `{DIALECT_NAMESPACE}.py` (or an 811equivalent directory with `__init__.py` file) as the entrypoint. 812 813### Generating `_{DIALECT_NAMESPACE}_ops_gen.py` wrapper modules 814 815Each dialect with a mapping to python requires that an appropriate 816`_{DIALECT_NAMESPACE}_ops_gen.py` wrapper module is created. This is done by 817invoking `mlir-tblgen` on a python-bindings specific tablegen wrapper that 818includes the boilerplate and actual dialect specific `td` file. An example, for 819the `StandardOps` (which is assigned the namespace `std` as a special case): 820 821```tablegen 822#ifndef PYTHON_BINDINGS_STANDARD_OPS 823#define PYTHON_BINDINGS_STANDARD_OPS 824 825include "mlir/Bindings/Python/Attributes.td" 826include "mlir/Dialect/StandardOps/IR/Ops.td" 827 828#endif 829``` 830 831In the main repository, building the wrapper is done via the CMake function 832`add_mlir_dialect_python_bindings`, which invokes: 833 834``` 835mlir-tblgen -gen-python-op-bindings -bind-dialect={DIALECT_NAMESPACE} \ 836 {PYTHON_BINDING_TD_FILE} 837``` 838 839The generates op classes must be included in the `{DIALECT_NAMESPACE}.py` file 840in a similar way that generated headers are included for C++ generated code: 841 842```python 843from ._my_dialect_ops_gen import * 844``` 845 846### Extending the search path for wrapper modules 847 848When the python bindings need to locate a wrapper module, they consult the 849`dialect_search_path` and use it to find an appropriately named module. For 850the main repository, this search path is hard-coded to include the 851`mlir.dialects` module, which is where wrappers are emitted by the abobe build 852rule. Out of tree dialects and add their modules to the search path by calling: 853 854```python 855mlir._cext.append_dialect_search_prefix("myproject.mlir.dialects") 856``` 857 858### Wrapper module code organization 859 860The wrapper module tablegen emitter outputs: 861 862* A `_Dialect` class (extending `mlir.ir.Dialect`) with a `DIALECT_NAMESPACE` 863 attribute. 864* An `{OpName}` class for each operation (extending `mlir.ir.OpView`). 865* Decorators for each of the above to register with the system. 866 867Note: In order to avoid naming conflicts, all internal names used by the wrapper 868module are prefixed by `_ods_`. 869 870Each concrete `OpView` subclass further defines several public-intended 871attributes: 872 873* `OPERATION_NAME` attribute with the `str` fully qualified operation name 874 (i.e. `std.absf`). 875* An `__init__` method for the *default builder* if one is defined or inferred 876 for the operation. 877* `@property` getter for each operand or result (using an auto-generated name 878 for unnamed of each). 879* `@property` getter, setter and deleter for each declared attribute. 880 881It further emits additional private-intended attributes meant for subclassing 882and customization (default cases omit these attributes in favor of the 883defaults on `OpView`): 884 885* `_ODS_REGIONS`: A specification on the number and types of regions. 886 Currently a tuple of (min_region_count, has_no_variadic_regions). Note that 887 the API does some light validation on this but the primary purpose is to 888 capture sufficient information to perform other default building and region 889 accessor generation. 890* `_ODS_OPERAND_SEGMENTS` and `_ODS_RESULT_SEGMENTS`: Black-box value which 891 indicates the structure of either the operand or results with respect to 892 variadics. Used by `OpView._ods_build_default` to decode operand and result 893 lists that contain lists. 894 895#### Default Builder 896 897Presently, only a single, default builder is mapped to the `__init__` method. 898The intent is that this `__init__` method represents the *most specific* of 899the builders typically generated for C++; however currently it is just the 900generic form below. 901 902* One argument for each declared result: 903 * For single-valued results: Each will accept an `mlir.ir.Type`. 904 * For variadic results: Each will accept a `List[mlir.ir.Type]`. 905* One argument for each declared operand or attribute: 906 * For single-valued operands: Each will accept an `mlir.ir.Value`. 907 * For variadic operands: Each will accept a `List[mlir.ir.Value]`. 908 * For attributes, it will accept an `mlir.ir.Attribute`. 909* Trailing usage-specific, optional keyword arguments: 910 * `loc`: An explicit `mlir.ir.Location` to use. Defaults to the location 911 bound to the thread (i.e. `with Location.unknown():`) or an error if none 912 is bound nor specified. 913 * `ip`: An explicit `mlir.ir.InsertionPoint` to use. Default to the insertion 914 point bound to the thread (i.e. `with InsertionPoint(...):`). 915 916In addition, each `OpView` inherits a `build_generic` method which allows 917construction via a (nested in the case of variadic) sequence of `results` and 918`operands`. This can be used to get some default construction semantics for 919operations that are otherwise unsupported in Python, at the expense of having 920a very generic signature. 921 922#### Extending Generated Op Classes 923 924Note that this is a rather complex mechanism and this section errs on the side 925of explicitness. Users are encouraged to find an example and duplicate it if 926they don't feel the need to understand the subtlety. The `builtin` dialect 927provides some relatively simple examples. 928 929As mentioned above, the build system generates Python sources like 930`_{DIALECT_NAMESPACE}_ops_gen.py` for each dialect with Python bindings. It 931is often desirable to to use these generated classes as a starting point for 932further customization, so an extension mechanism is provided to make this 933easy (you are always free to do ad-hoc patching in your `{DIALECT_NAMESPACE}.py` 934file but we prefer a more standard mechanism that is applied uniformly). 935 936To provide extensions, add a `_{DIALECT_NAMESPACE}_ops_ext.py` file to the 937`dialects` module (i.e. adjacent to your `{DIALECT_NAMESPACE}.py` top-level 938and the `*_ops_gen.py` file). Using the `builtin` dialect and `FuncOp` as an 939example, the generated code will include an import like this: 940 941```python 942try: 943 from . import _builtin_ops_ext as _ods_ext_module 944except ImportError: 945 _ods_ext_module = None 946``` 947 948Then for each generated concrete `OpView` subclass, it will apply a decorator 949like: 950 951```python 952@_ods_cext.register_operation(_Dialect) 953@_ods_extend_opview_class(_ods_ext_module) 954class FuncOp(_ods_ir.OpView): 955``` 956 957See the `_ods_common.py` `extend_opview_class` function for details of the 958mechanism. At a high level: 959 960* If the extension module exists, locate an extension class for the op (in 961 this example, `FuncOp`): 962 * First by looking for an attribute with the exact name in the extension 963 module. 964 * Falling back to calling a `select_opview_mixin(parent_opview_cls)` 965 function defined in the extension module. 966* If a mixin class is found, a new subclass is dynamically created that multiply 967 inherits from `({_builtin_ops_ext.FuncOp}, _builtin_ops_gen.FuncOp)`. 968 969The mixin class should not inherit from anything (i.e. directly extends 970`object` only). The facility is typically used to define custom `__init__` 971methods, properties, instance methods and static methods. Due to the 972inheritance ordering, the mixin class can act as though it extends the 973generated `OpView` subclass in most contexts (i.e. 974`issubclass(_builtin_ops_ext.FuncOp, OpView)` will return `False` but usage 975generally allows you treat it as duck typed as an `OpView`). 976 977There are a couple of recommendations, given how the class hierarchy is 978defined: 979 980* For static methods that need to instantiate the actual "leaf" op (which 981 is dynamically generated and would result in circular dependencies to try 982 to reference by name), prefer to use `@classmethod` and the concrete 983 subclass will be provided as your first `cls` argument. See 984 `_builtin_ops_ext.FuncOp.from_py_func` as an example. 985* If seeking to replace the generated `__init__` method entirely, you may 986 actually want to invoke the super-super-class `mlir.ir.OpView` constructor 987 directly, as it takes an `mlir.ir.Operation`, which is likely what you 988 are constructing (i.e. the generated `__init__` method likely adds more 989 API constraints than you want to expose in a custom builder). 990 991A pattern that comes up frequently is wanting to provide a sugared `__init__` 992method which has optional or type-polymorphism/implicit conversions but to 993otherwise want to invoke the default op building logic. For such cases, 994it is recommended to use an idiom such as: 995 996```python 997 def __init__(self, sugar, spice, *, loc=None, ip=None): 998 ... massage into result_type, operands, attributes ... 999 OpView.__init__(self, self.build_generic( 1000 results=[result_type], 1001 operands=operands, 1002 attributes=attributes, 1003 loc=loc, 1004 ip=ip)) 1005``` 1006 1007Refer to the documentation for `build_generic` for more information. 1008