| # MLIR C API | |
| **Current status: Under development, API unstable, built by default.** | |
| [TOC] | |
| ## Design | |
| Many languages can interoperate with C but have a harder time with C++ due to | |
| name mangling and memory model differences. Although the C API for MLIR can be | |
| used directly from C, it is primarily intended to be wrapped in higher-level | |
| language- or library-specific constructs. Therefore the API tends towards | |
| simplicity and feature minimalism. | |
| **Note:** while the C API is expected to be more stable than C++ API, it | |
| currently offers no stability guarantees. | |
| ### Scope | |
| The API is provided for core IR components (attributes, blocks, operations, | |
| regions, types, values), Passes and some fundamental type and attribute kinds. | |
| The core IR API is intentionally low-level, e.g. exposes a plain list of | |
| operation's operands and attributes without attempting to assign "semantic" | |
| names to them. Users of specific dialects are expected to wrap the core API in a | |
| dialect-specific way, for example, by implementing an ODS backend. | |
| ### Object Model | |
| Core IR components are exposed as opaque _handles_ to an IR object existing in | |
| C++. They are not intended to be inspected by the API users (and, in many cases, | |
| cannot be meaningfully inspected). Instead the users are expected to pass | |
| handles to the appropriate manipulation functions. | |
| The handle _may or may not_ own the underlying object. | |
| ### Naming Convention and Ownership Model | |
| All objects are prefixed with `Mlir`. They are typedefs and should be used | |
| without `struct`. | |
| All functions are prefixed with `mlir`. | |
| Functions primarily operating on an instance of `MlirX` are prefixed with | |
| `mlirX`. They take the instance being acted upon as their first argument (except | |
| for creation functions). For example, `mlirOperationGetNumOperands` inspects an | |
| `MlirOperation`, which it takes as its first operand. | |
| The *ownership* model is encoded in the naming convention as follows. | |
| - By default, the ownership is not transferred. | |
| - Functions that transfer the ownership of the result to the caller can be in | |
| one of two forms: | |
| * functions that create a new object have the name `mlirXCreate<...>`, for | |
| example, `mlirOperationCreate`; | |
| * functions that detach an object from a parent object have the name | |
| `mlirYTake<...>`, for example `mlirOperationStateTakeRegion`. | |
| - Functions that take ownership of some of their arguments have the form | |
| `mlirY<...>OwnedX<...>` where `X` can refer to the type or any other | |
| sufficiently unique description of the argument, the ownership of which will | |
| be taken by the callee, for example `mlirRegionAppendOwnedBlock`. | |
| - Functions that create an object by default do not transfer its ownership to | |
| the caller, i.e. one of other objects passed in as an argument retains the | |
| ownership, they have the form `mlirX<...>Get`. For example, | |
| `mlirTypeParseGet`. | |
| - Functions that destroy an object owned by the caller are of the form | |
| `mlirXDestroy`. | |
| If the code owns an object, it is responsible for destroying the object when it | |
| is no longer necessary. If an object that owns other objects is destroyed, any | |
| handles to those objects become invalid. Note that types and attributes are | |
| owned by the `MlirContext` in which they were created. The context also provides | |
| `mlirContextBeginTransientScope(MlirContext)` and | |
| `mlirContextEndTransientScope(MlirContext)` to manage transient scopes: types, | |
| attributes, affine expressions, and unregistered operations created within a | |
| transient scope are invalidated and reclaimed when the transient scope ends. | |
| ### Nullity | |
| A handle may refer to a _null_ object. It is the responsibility of the caller to | |
| check if an object is null by using `mlirXIsNull(MlirX)`. API functions do _not_ | |
| expect null objects as arguments unless explicitly stated otherwise. API | |
| functions _may_ return null objects. | |
| ### Type Hierarchies | |
| MLIR objects can form type hierarchies in C++. For example, all IR classes | |
| representing types are derived from `mlir::Type`, some of them may also be also | |
| derived from common base classes such as `mlir::ShapedType` or dialect-specific | |
| base classes. Type hierarchies are exposed to C API through naming conventions | |
| as follows. | |
| - Only the top-level class of each hierarchy is exposed, e.g. `MlirType` is | |
| defined as a type but `MlirShapedType` is not. This avoids the need for | |
| explicit upcasting when passing an object of a derived type to a function | |
| that expects a base type (this happens more often in core/standard APIs, | |
| while downcasting usually involves further checks anyway). | |
| - A type `Y` that derives from `X` provides a function `int mlirXIsAY(MlirX)` | |
| that returns a non-zero value if the given dynamic instance of `X` is also | |
| an instance of `Y`. For example, `int MlirTypeIsAInteger(MlirType)`. | |
| - A function that expects a derived type as its first argument takes the base | |
| type instead and documents the expectation by using `Y` in its name | |
| `MlirY<...>(MlirX, ...)`. This function asserts that the dynamic instance of | |
| its first argument is `Y`, and it is the responsibility of the caller to | |
| ensure it is indeed the case. | |
| ### Auxiliary Types | |
| #### `StringRef` | |
| Numerous MLIR functions return instances of `StringRef` to refer to a non-owning | |
| segment of a string. This segment may or may not be null-terminated. In C API, | |
| these are represented as instances of `MlirStringRef` structure that contains a | |
| pointer to the first character of the string fragment (`str`) and the fragment | |
| length (`length`). Note that the fragment is _not necessarily_ null-terminated, | |
| the `length` field must be used to identify the last character. `MlirStringRef` | |
| is a non-owning pointer, the caller is in charge of performing the copy or | |
| ensuring that the pointee outlives all uses of `MlirStringRef`. | |
| ### Printing | |
| IR objects can be printed using `mlirXPrint(MlirX, MlirStringCallback, void *)` | |
| functions. These functions accept take arguments a callback with signature `void | |
| (*)(const char *, intptr_t, void *)` and a pointer to user-defined data. They | |
| call the callback and supply it with chunks of the string representation, | |
| provided as a pointer to the first character and a length, and forward the | |
| user-defined data unmodified. It is up to the caller to allocate memory if the | |
| string representation must be stored and perform the copy. There is no guarantee | |
| that the pointer supplied to the callback points to a null-terminated string, | |
| the size argument should be used to find the end of the string. The callback may | |
| be called multiple times with consecutive chunks of the string representation | |
| (the printing itself is buffered). | |
| *Rationale*: this approach allows the caller to have full control of the | |
| allocation and avoid unnecessary allocation and copying inside the printer. | |
| For convenience, `mlirXDump(MlirX)` functions are provided to print the given | |
| object to the standard error stream. | |
| ## Common Patterns | |
| The API adopts the following patterns for recurrent functionality in MLIR. | |
| ### Indexed Components | |
| An object has an _indexed component_ if it has fields accessible using a | |
| zero-based contiguous integer index, typically arrays. For example, an | |
| `MlirBlock` has its arguments as an indexed component. An object may have | |
| several such components. For example, an `MlirOperation` has attributes, | |
| operands, regions, results and successors. | |
| For indexed components, the following pair of functions is provided. | |
| - `intptr_t mlirXGetNum<Y>s(MlirX)` returns the upper bound on the index. | |
| - `MlirY mlirXGet<Y>(MlirX, intptr_t pos)` returns 'pos'-th subobject. | |
| The sizes are accepted and returned as signed pointer-sized integers, i.e. | |
| `intptr_t`. This typedef is available in C99. | |
| Note that the name of subobject in the function does not necessarily match the | |
| type of the subobject. For example, `mlirOperationGetOperand` returns an | |
| `MlirValue`. | |
| ### Iterable Components | |
| An object has an _iterable component_ if it has iterators accessing its fields | |
| in some order other than integer indexing, typically linked lists. For example, | |
| an `MlirBlock` has an iterable list of operations it contains. An object may | |
| have several iterable components. | |
| For iterable components, the following triple of functions is provided. | |
| - `MlirY mlirXGetFirst<Y>(MlirX)` returns the first subobject in the list. | |
| - `MlirY mlirYGetNextIn<X>(MlirY)` returns the next subobject in the list that | |
| contains the given object, or a null object if the given object is the last | |
| in this list. | |
| - `int mlirYIsNull(MlirY)` returns 1 if the given object is null. | |
| Note that the name of subobject in the function may or may not match its type. | |
| This approach enables one to iterate as follows. | |
| ```c++ | |
| MlirY iter; | |
| for (iter = mlirXGetFirst<Y>(x); !mlirYIsNull(iter); | |
| iter = mlirYGetNextIn<X>(iter)) { | |
| /* User 'iter'. */ | |
| } | |
| ``` | |
| ## Extending the API | |
| ### Extensions for Dialect Attributes and Types | |
| Dialect attributes and types can follow the example of builtin attributes and | |
| types, provided that implementations live in separate directories, i.e. | |
| `include/mlir-c/<...>Dialect/` and `lib/CAPI/<...>Dialect/`. The core APIs | |
| provide implementation-private headers in `include/mlir/CAPI/IR` that allow one | |
| to convert between opaque C structures for core IR components and their C++ | |
| counterparts. `wrap` converts a C++ class into a C structure and `unwrap` does | |
| the inverse conversion. Once the C++ object is available, the API implementation | |
| should rely on `isa` to implement `mlirXIsAY` and is expected to use `cast` | |
| inside other API calls. | |
| ### Extensions for Interfaces | |
| Interfaces can follow the example of IR interfaces and should be placed in the | |
| appropriate library (e.g., common interfaces in `mlir-c/Interfaces` and | |
| dialect-specific interfaces in their dialect library). Similarly to other type | |
| hierarchies, interfaces are not expected to have objects of their own type and | |
| instead operate on top-level objects: `MlirAttribute`, `MlirOperation` and | |
| `MlirType`. Static interface methods are expected to take as leading argument a | |
| canonical identifier of the class, `MlirStringRef` with the name for operations | |
| and `MlirTypeID` for attributes and types, followed by `MlirContext` in which | |
| the interfaces are registered. | |
| Individual interfaces are expected provide a `mlir<InterfaceName>TypeID()` | |
| function that can be used to check whether an object or a class implements this | |
| interface using `mlir<Attribute/Operation/Type>ImplementsInterface` or | |
| `mlir<Attribute/Operation?Type>ImplementsInterfaceStatic` functions, | |
| respectively. Rationale: C++ `isa` only works when an object exists, static | |
| methods are usually dispatched to using templates; lookup by `TypeID` in | |
| `MLIRContext` works even without an object. |