1# MLIR C API 2 3**Current status: Under development, API unstable, built by default.** 4 5[TOC] 6 7## Design 8 9Many languages can interoperate with C but have a harder time with C++ due to 10name mangling and memory model differences. Although the C API for MLIR can be 11used directly from C, it is primarily intended to be wrapped in higher-level 12language- or library-specific constructs. Therefore the API tends towards 13simplicity and feature minimalism. 14 15**Note:** while the C API is expected to be more stable than C++ API, it 16currently offers no stability guarantees. 17 18### Scope 19 20The API is provided for core IR components (attributes, blocks, operations, 21regions, types, values), Passes and some fundamental type and attribute kinds. 22The core IR API is intentionally low-level, e.g. exposes a plain list of 23operation's operands and attributes without attempting to assign "semantic" 24names to them. Users of specific dialects are expected to wrap the core API in a 25dialect-specific way, for example, by implementing an ODS backend. 26 27### Object Model 28 29Core IR components are exposed as opaque _handles_ to an IR object existing in 30C++. They are not intended to be inspected by the API users (and, in many cases, 31cannot be meaningfully inspected). Instead the users are expected to pass 32handles to the appropriate manipulation functions. 33 34The handle _may or may not_ own the underlying object. 35 36### Naming Convention and Ownership Model 37 38All objects are prefixed with `Mlir`. They are typedefs and should be used 39without `struct`. 40 41All functions are prefixed with `mlir`. 42 43Functions primarily operating on an instance of `MlirX` are prefixed with 44`mlirX`. They take the instance being acted upon as their first argument (except 45for creation functions). For example, `mlirOperationGetNumOperands` inspects an 46`MlirOperation`, which it takes as its first operand. 47 48The *ownership* model is encoded in the naming convention as follows. 49 50- By default, the ownership is not transferred. 51- Functions that transfer the ownership of the result to the caller can be in 52 one of two forms: 53 * functions that create a new object have the name `mlirXCreate<...>`, for 54 example, `mlirOperationCreate`; 55 * functions that detach an object from a parent object have the name 56 `mlirYTake<...>`, for example `mlirOperationStateTakeRegion`. 57- Functions that take ownership of some of their arguments have the form 58 `mlirY<...>OwnedX<...>` where `X` can refer to the type or any other 59 sufficiently unique description of the argument, the ownership of which will 60 be taken by the callee, for example `mlirRegionAppendOwnedBlock`. 61- Functions that create an object by default do not transfer its ownership to 62 the caller, i.e. one of other objects passed in as an argument retains the 63 ownership, they have the form `mlirX<...>Get`. For example, 64 `mlirTypeParseGet`. 65- Functions that destroy an object owned by the caller are of the form 66 `mlirXDestroy`. 67 68If the code owns an object, it is responsible for destroying the object when it 69is no longer necessary. If an object that owns other objects is destroyed, any 70handles to those objects become invalid. Note that types and attributes are 71owned by the `MlirContext` in which they were created. 72 73### Nullity 74 75A handle may refer to a _null_ object. It is the responsibility of the caller to 76check if an object is null by using `mlirXIsNull(MlirX)`. API functions do _not_ 77expect null objects as arguments unless explicitly stated otherwise. API 78functions _may_ return null objects. 79 80### Type Hierarchies 81 82MLIR objects can form type hierarchies in C++. For example, all IR classes 83representing types are derived from `mlir::Type`, some of them may also be also 84derived from common base classes such as `mlir::ShapedType` or dialect-specific 85base classes. Type hierarchies are exposed to C API through naming conventions 86as follows. 87 88- Only the top-level class of each hierarchy is exposed, e.g. `MlirType` is 89 defined as a type but `MlirShapedType` is not. This avoids the need for 90 explicit upcasting when passing an object of a derived type to a function 91 that expects a base type (this happens more often in core/standard APIs, 92 while downcasting usually involves further checks anyway). 93- A type `Y` that derives from `X` provides a function `int mlirXIsAY(MlirX)` 94 that returns a non-zero value if the given dynamic instance of `X` is also 95 an instance of `Y`. For example, `int MlirTypeIsAInteger(MlirType)`. 96- A function that expects a derived type as its first argument takes the base 97 type instead and documents the expectation by using `Y` in its name 98 `MlirY<...>(MlirX, ...)`. This function asserts that the dynamic instance of 99 its first argument is `Y`, and it is the responsibility of the caller to 100 ensure it is indeed the case. 101 102### Auxiliary Types 103 104#### `StringRef` 105 106Numerous MLIR functions return instances of `StringRef` to refer to a non-owning 107segment of a string. This segment may or may not be null-terminated. In C API, 108these are represented as instances of `MlirStringRef` structure that contains a 109pointer to the first character of the string fragment (`str`) and the fragment 110length (`length`). Note that the fragment is _not necessarily_ null-terminated, 111the `length` field must be used to identify the last character. `MlirStringRef` 112is a non-owning pointer, the caller is in charge of performing the copy or 113ensuring that the pointee outlives all uses of `MlirStringRef`. 114 115### Printing 116 117IR objects can be printed using `mlirXPrint(MlirX, MlirStringCallback, void *)` 118functions. These functions accept take arguments a callback with signature `void 119(*)(const char *, intptr_t, void *)` and a pointer to user-defined data. They 120call the callback and supply it with chunks of the string representation, 121provided as a pointer to the first character and a length, and forward the 122user-defined data unmodified. It is up to the caller to allocate memory if the 123string representation must be stored and perform the copy. There is no guarantee 124that the pointer supplied to the callback points to a null-terminated string, 125the size argument should be used to find the end of the string. The callback may 126be called multiple times with consecutive chunks of the string representation 127(the printing itself is buffered). 128 129*Rationale*: this approach allows the caller to have full control of the 130allocation and avoid unnecessary allocation and copying inside the printer. 131 132For convenience, `mlirXDump(MlirX)` functions are provided to print the given 133object to the standard error stream. 134 135## Common Patterns 136 137The API adopts the following patterns for recurrent functionality in MLIR. 138 139### Indexed Components 140 141An object has an _indexed component_ if it has fields accessible using a 142zero-based contiguous integer index, typically arrays. For example, an 143`MlirBlock` has its arguments as an indexed component. An object may have 144several such components. For example, an `MlirOperation` has attributes, 145operands, regions, results and successors. 146 147For indexed components, the following pair of functions is provided. 148 149- `intptr_t mlirXGetNum<Y>s(MlirX)` returns the upper bound on the index. 150- `MlirY mlirXGet<Y>(MlirX, intptr_t pos)` returns 'pos'-th subobject. 151 152The sizes are accepted and returned as signed pointer-sized integers, i.e. 153`intptr_t`. This typedef is available in C99. 154 155Note that the name of subobject in the function does not necessarily match the 156type of the subobject. For example, `mlirOperationGetOperand` returns an 157`MlirValue`. 158 159### Iterable Components 160 161An object has an _iterable component_ if it has iterators accessing its fields 162in some order other than integer indexing, typically linked lists. For example, 163an `MlirBlock` has an iterable list of operations it contains. An object may 164have several iterable components. 165 166For iterable components, the following triple of functions is provided. 167 168- `MlirY mlirXGetFirst<Y>(MlirX)` returns the first subobject in the list. 169- `MlirY mlirYGetNextIn<X>(MlirY)` returns the next subobject in the list that 170 contains the given object, or a null object if the given object is the last 171 in this list. 172- `int mlirYIsNull(MlirY)` returns 1 if the given object is null. 173 174Note that the name of subobject in the function may or may not match its type. 175 176This approach enables one to iterate as follows. 177 178```c++ 179MlirY iter; 180for (iter = mlirXGetFirst<Y>(x); !mlirYIsNull(iter); 181 iter = mlirYGetNextIn<X>(iter)) { 182 /* User 'iter'. */ 183} 184``` 185 186## Extending the API 187 188### Extensions for Dialect Attributes and Types 189 190Dialect attributes and types can follow the example of builtin attributes and 191types, provided that implementations live in separate directories, i.e. 192`include/mlir-c/<...>Dialect/` and `lib/CAPI/<...>Dialect/`. The core APIs 193provide implementation-private headers in `include/mlir/CAPI/IR` that allow one 194to convert between opaque C structures for core IR components and their C++ 195counterparts. `wrap` converts a C++ class into a C structure and `unwrap` does 196the inverse conversion. Once the C++ object is available, the API implementation 197should rely on `isa` to implement `mlirXIsAY` and is expected to use `cast` 198inside other API calls. 199 200### Extensions for Interfaces 201 202Interfaces can follow the example of IR interfaces and should be placed in the 203appropriate library (e.g., common interfaces in `mlir-c/Interfaces` and 204dialect-specific interfaces in their dialect library). Similarly to other type 205hierarchies, interfaces are not expected to have objects of their own type and 206instead operate on top-level objects: `MlirAttribute`, `MlirOperation` and 207`MlirType`. Static interface methods are expected to take as leading argument a 208canonical identifier of the class, `MlirStringRef` with the name for operations 209and `MlirTypeID` for attributes and types, followed by `MlirContext` in which 210the interfaces are registered. 211 212Individual interfaces are expected provide a `mlir<InterfaceName>TypeID()` 213function that can be used to check whether an object or a class implements this 214interface using `mlir<Attribute/Operation/Type>ImplementsInterface` or 215`mlir<Attribute/Operation?Type>ImplementsInterfaceStatic` functions, 216respectively. Rationale: C++ `isa` only works when an object exists, static 217methods are usually dispatched to using templates; lookup by `TypeID` in 218`MLIRContext` works even without an object. 219