@@ -47,7 +47,8 @@ namespace MyTypes {
4747enum Kinds {
4848 // These kinds will be used in the examples below.
4949 Simple = Type::Kind::FIRST_PRIVATE_EXPERIMENTAL_0_TYPE,
50- Complex
50+ Complex,
51+ Recursive
5152};
5253}
5354```
@@ -58,13 +59,17 @@ As described above, `Type` objects in MLIR are value-typed and rely on having an
5859implicitly internal storage object that holds the actual data for the type. When
5960defining a new ` Type ` it isn't always necessary to define a new storage class.
6061So before defining the derived ` Type ` , it's important to know which of the two
61- classes of ` Type ` we are defining. Some types are ` primitives ` meaning they do
62+ classes of ` Type ` we are defining. Some types are _ primitives _ meaning they do
6263not have any parameters and are singletons uniqued by kind, like the
6364[ ` index ` type] ( LangRef.md#index-type ) . Parametric types on the other hand, have
6465additional information that differentiates different instances of the same
6566` Type ` kind. For example the [ ` integer ` type] ( LangRef.md#integer-type ) has a
6667bitwidth, making ` i8 ` and ` i16 ` be different instances of
67- [ ` integer ` type] ( LangRef.md#integer-type ) .
68+ [ ` integer ` type] ( LangRef.md#integer-type ) . Types can also have a mutable
69+ component, which can be used, for example, to construct self-referring recursive
70+ types. The mutable component _ cannot_ be used to differentiate types within the
71+ same kind, so usually such types are also parametric where the parameters serve
72+ to identify them.
6873
6974#### Simple non-parametric types
7075
@@ -240,6 +245,126 @@ public:
240245};
241246```
242247
248+ #### Types with a mutable component
249+
250+ Types with a mutable component require defining a type storage class regardless
251+ of being parametric. The storage contains both the parameters and the mutable
252+ component and is accessed in a thread-safe way by the type support
253+ infrastructure.
254+
255+ ##### Defining a type storage
256+
257+ In addition to the requirements for the type storage class for parametric types,
258+ the storage class for types with a mutable component must additionally obey the
259+ following.
260+
261+ * The mutable component must not participate in the storage key.
262+ * Provide a mutation method that is used to modify an existing instance of the
263+ storage. This method modifies the mutable component based on arguments,
264+ using `allocator` for any new dynamically-allocated storage, and indicates
265+ whether the modification was successful.
266+ - `LogicalResult mutate(StorageAllocator &allocator, Args ...&& args)`
267+
268+ Let's define a simple storage for recursive types, where a type is identified by
269+ its name and can contain another type including itself.
270+
271+ ```c++
272+ /// Here we define a storage class for a RecursiveType that is identified by its
273+ /// name and contains another type.
274+ struct RecursiveTypeStorage : public TypeStorage {
275+ /// The type is uniquely identified by its name. Note that the contained type
276+ /// is _not_ a part of the key.
277+ using KeyTy = StringRef;
278+
279+ /// Construct the storage from the type name. Explicitly initialize the
280+ /// containedType to nullptr, which is used as marker for the mutable
281+ /// component being not yet initialized.
282+ RecursiveTypeStorage(StringRef name) : name(name), containedType(nullptr) {}
283+
284+ /// Define the comparison function.
285+ bool operator==(const KeyTy &key) const { return key == name; }
286+
287+ /// Define a construction method for creating a new instance of the storage.
288+ static RecursiveTypeStorage *construct(StorageAllocator &allocator,
289+ const KeyTy &key) {
290+ // Note that the key string is copied into the allocator to ensure it
291+ // remains live as long as the storage itself.
292+ return new (allocator.allocate<RecursiveTypeStorage>())
293+ RecursiveTypeStorage(allocator.copyInto(key));
294+ }
295+
296+ /// Define a mutation method for changing the type after it is created. In
297+ /// many cases, we only want to set the mutable component once and reject
298+ /// any further modification, which can be achieved by returning failure from
299+ /// this function.
300+ LogicalResult mutate(StorageAllocator &, Type body) {
301+ // If the contained type has been initialized already, and the call tries
302+ // to change it, reject the change.
303+ if (containedType && containedType != body)
304+ return failure();
305+
306+ // Change the body successfully.
307+ containedType = body;
308+ return success();
309+ }
310+
311+ StringRef name;
312+ Type containedType;
313+ };
314+ ```
315+
316+ ##### Type class definition
317+
318+ Having defined the storage class, we can define the type class itself. This is
319+ similar to parametric types. ` Type::TypeBase ` provides a ` mutate ` method that
320+ forwards its arguments to the ` mutate ` method of the storage and ensures the
321+ modification happens under lock.
322+
323+ ``` c++
324+ class RecursiveType : public Type ::TypeBase<RecursiveType, Type,
325+ RecursiveTypeStorage> {
326+ public:
327+ /// Inherit parent constructors.
328+ using Base::Base;
329+
330+ /// This static method is used to support type inquiry through isa, cast,
331+ /// and dyn_cast.
332+ static bool kindof(unsigned kind) { return kind == MyTypes::Recursive; }
333+
334+ /// Creates an instance of the Recursive type. This only takes the type name
335+ /// and returns the type with uninitialized body.
336+ static RecursiveType get(MLIRContext * ctx, StringRef name) {
337+ // Call into the base to get a uniqued instance of this type. The parameter
338+ // (name) is passed after the kind.
339+ return Base::get(ctx, MyTypes::Recursive, name);
340+ }
341+
342+ /// Now we can change the mutable component of the type. This is an instance
343+ /// method callable on an already existing RecursiveType.
344+ void setBody(Type body) {
345+ // Call into the base to mutate the type.
346+ LogicalResult result = Base::mutate(body);
347+ // Most types expect mutation to always succeed, but types can implement
348+ // custom logic for handling mutation failures.
349+ assert(succeeded(result) &&
350+ "attempting to change the body of an already-initialized type");
351+ // Avoid unused-variable warning when building without assertions.
352+ (void) result;
353+ }
354+
355+ /// Returns the contained type, which may be null if it has not been
356+ /// initialized yet.
357+ Type getBody() {
358+ return getImpl()->containedType;
359+ }
360+
361+ /// Returns the name.
362+ StringRef getName() {
363+ return getImpl()->name;
364+ }
365+ };
366+ ```
367+
243368### Registering types with a Dialect
244369
245370Once the dialect types have been defined, they must then be registered with a
0 commit comments