Alex Zinenko | 75f239e | 2020-08-05 12:36:16 | [diff] [blame] | 1 | # MLIR C API |
| 2 | |
| 3 | **Current status: Under development, API unstable, built by default.** |
| 4 | |
| 5 | ## Design |
| 6 | |
| 7 | Many languages can interoperate with C but have a harder time with C++ due to |
| 8 | name mangling and memory model differences. Although the C API for MLIR can be |
| 9 | used directly from C, it is primarily intended to be wrapped in higher-level |
| 10 | language- or library-specific constructs. Therefore the API tends towards |
| 11 | simplicity and feature minimalism. |
| 12 | |
| 13 | **Note:** while the C API is expected to be more stable than C++ API, it |
| 14 | currently offers no stability guarantees. |
| 15 | |
| 16 | ### Scope |
| 17 | |
| 18 | The API is provided for core IR components (attributes, blocks, operations, |
| 19 | regions, types, values), Passes and some fundamental type and attribute kinds. |
| 20 | The core IR API is intentionally low-level, e.g. exposes a plain list of |
| 21 | operation's operands and attributes without attempting to assign "semantic" |
| 22 | names to them. Users of specific dialects are expected to wrap the core API in a |
| 23 | dialect-specific way, for example, by implementing an ODS backend. |
| 24 | |
| 25 | ### Object Model |
| 26 | |
| 27 | Core IR components are exposed as opaque _handles_ to an IR object existing in |
| 28 | C++. They are not intended to be inspected by the API users (and, in many cases, |
| 29 | cannot be meaningfully inspected). Instead the users are expected to pass |
| 30 | handles to the appropriate manipulation functions. |
| 31 | |
| 32 | The handle _may or may not_ own the underlying object. |
| 33 | |
| 34 | ### Naming Convention and Ownership Model |
| 35 | |
| 36 | All objects are prefixed with `Mlir`. They are typedefs and should be used |
| 37 | without `struct`. |
| 38 | |
| 39 | All functions are prefixed with `mlir`. |
| 40 | |
| 41 | Functions primarily operating on an instance of `MlirX` are prefixed with |
| 42 | `mlirX`. They take the instance being acted upon as their first argument (except |
| 43 | for creation functions). For example, `mlirOperationGetNumOperands` inspects an |
| 44 | `MlirOperation`, which it takes as its first operand. |
| 45 | |
| 46 | The *ownership* model is encoded in the naming convention as follows. |
| 47 | |
| 48 | - By default, the ownership is not transerred. |
| 49 | - Functions that tranfer the ownership of the result to the caller can be in |
| 50 | one of two forms: |
| 51 | * functions that create a new object have the name `mlirXCreate<...>`, for |
| 52 | example, `mlirOperationCreate`; |
| 53 | * functions that detach an object from a parent object have the name |
| 54 | `mlirYTake<...>`, for example `mlirOperationStateTakeRegion`. |
| 55 | - Functions that take ownership of some of their arguments have the form |
| 56 | `mlirY<...>OwnedX<...>` where `X` can refer to the type or any other |
| 57 | sufficiently unique description of the argument, the ownership of which will |
| 58 | be taken by the callee, for example `mlirRegionAppendOwnedBlock`. |
| 59 | - Functions that create an object by default do not transfer its ownership to |
| 60 | the caller, i.e. one of other objects passed in as an argument retains the |
| 61 | ownership, they have the form `mlirX<...>Get`. For example, |
| 62 | `mlirTypeParseGet`. |
| 63 | - Functions that destroy an object owned by the caller are of the form |
| 64 | `mlirXDestroy`. |
| 65 | |
| 66 | If the code owns an object, it is responsible for destroying the object when it |
| 67 | is no longer necessary. If an object that owns other objects is destroyed, any |
| 68 | handles to those objects become invalid. Note that types and attributes are |
| 69 | owned by the `MlirContext` in which they were created. |
| 70 | |
| 71 | ### Nullity |
| 72 | |
| 73 | A handle may refer to a _null_ object. It is the responsibility of the caller to |
Alex Zinenko | 321aa19 | 2020-08-11 16:25:09 | [diff] [blame^] | 74 | check if an object is null by using `mlirXIsNull(MlirX)`. API functions do _not_ |
Alex Zinenko | 75f239e | 2020-08-05 12:36:16 | [diff] [blame] | 75 | expect null objects as arguments unless explicitly stated otherwise. API |
| 76 | functions _may_ return null objects. |
| 77 | |
Alex Zinenko | 321aa19 | 2020-08-11 16:25:09 | [diff] [blame^] | 78 | ### Conversion To String and Printing |
| 79 | |
| 80 | IR objects can be converted to a string representation, for example for |
| 81 | printing, using `mlirXPrint(MlirX, MlirPrintCallback, void *)` functions. These |
| 82 | functions accept take arguments a callback with signature `void (*)(const char |
| 83 | *, intptr_t, void *)` and a pointer to user-defined data. They call the callback |
| 84 | and supply it with chunks of the string representation, provided as a pointer to |
| 85 | the first character and a length, and forward the user-defined data unmodified. |
| 86 | It is up to the caller to allocate memory if the string representation must be |
| 87 | stored and perform the copy. There is no guarantee that the pointer supplied to |
| 88 | the callback points to a null-terminated string, the size argument should be |
| 89 | used to find the end of the string. The callback may be called multiple times |
| 90 | with consecutive chunks of the string representation (the printing itself is |
| 91 | bufferred). |
| 92 | |
| 93 | *Rationale*: this approach allows the caller to have full control of the |
| 94 | allocation and avoid unnecessary allocation and copying inside the printer. |
| 95 | |
| 96 | For convenience, `mlirXDump(MlirX)` functions are provided to print the given |
| 97 | object to the standard error stream. |
| 98 | |
Alex Zinenko | 75f239e | 2020-08-05 12:36:16 | [diff] [blame] | 99 | ### Common Patterns |
| 100 | |
| 101 | The API adopts the following patterns for recurrent functionality in MLIR. |
| 102 | |
| 103 | #### Indexed Components |
| 104 | |
| 105 | An object has an _indexed component_ if it has fields accessible using a |
| 106 | zero-based contiguous integer index, typically arrays. For example, an |
| 107 | `MlirBlock` has its arguments as a indexed component. An object may have several |
| 108 | such components. For example, an `MlirOperation` has attributes, operands, |
| 109 | regions, results and successors. |
| 110 | |
| 111 | For indexed components, the following pair of functions is provided. |
| 112 | |
Alex Zinenko | af83858 | 2020-08-11 16:34:32 | [diff] [blame] | 113 | - `intptr_t mlirXGetNum<Y>s(MlirX)` returns the upper bound on the index. |
| 114 | - `MlirY mlirXGet<Y>(MlirX, intptr_t pos)` returns 'pos'-th subobject. |
| 115 | |
| 116 | The sizes are accepted and returned as signed pointer-sized integers, i.e. |
| 117 | `intptr_t`. This typedef is avalable in C99. |
Alex Zinenko | 75f239e | 2020-08-05 12:36:16 | [diff] [blame] | 118 | |
| 119 | Note that the name of subobject in the function does not necessarily match the |
| 120 | type of the subobject. For example, `mlirOperationGetOperand` returns a |
| 121 | `MlirValue`. |
| 122 | |
| 123 | #### Iterable Components |
| 124 | |
| 125 | An object has an _iterable component_ if it has iterators accessing its fields |
| 126 | in some order other than integer indexing, typically linked lists. For example, |
| 127 | an `MlirBlock` has an iterable list of operations it contains. An object may |
| 128 | have several iterable components. |
| 129 | |
| 130 | For iterable components, the following triple of functions is provided. |
| 131 | |
| 132 | - `MlirY mlirXGetFirst<Y>(MlirX)` returns the first subobject in the list. |
| 133 | - `MlirY mlirYGetNextIn<X>(MlirY)` returns the next subobject in the list that |
| 134 | contains the given object, or a null object if the given object is the last |
| 135 | in this list. |
| 136 | - `int mlirYIsNull(MlirY)` returns 1 if the given object is null. |
| 137 | |
| 138 | Note that the name of subobject in the function may or may not match its type. |
| 139 | |
| 140 | This approach enables one to iterate as follows. |
| 141 | |
| 142 | ```c++ |
| 143 | MlirY iter; |
| 144 | for (iter = mlirXGetFirst<Y>(x); !mlirYIsNull(iter); |
| 145 | iter = mlirYGetNextIn<X>(iter)) { |
| 146 | /* User 'iter'. */ |
| 147 | } |
| 148 | ``` |