Since only mutable types and attributes can go into infinite recursion inside SubElementInterface::walkSubElement, and there are only a few of them (mutable types and attributes), we introduce a new flag in AbstractType/Attribute to indicate if a type or attribute is mutable. Then, inside SubElementInterface, we use a set to record visited mutable types and attributes that have been visited before.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Ah, I didn't think this was a possibility. It's unfortunate that walkSubElements has to be slowed down in general. It's particularly hot due to SymbolTable.
I think we can optimize for the common case and have the first N recursions not track a set (1, 2?). Also, is it possible to have nested types stack overflow the walk function?
mlir/lib/IR/SubElementInterfaces.cpp | ||
---|---|---|
71 | What about SmallPtrSet<Pointer union<Type,Attribute>>? |
Yeah, the set should be unnecessary in 99% of cases. This is something that is only necessary for mutable attributes/types, given that those are the only ones that could be recursive. Conceptually we could detect mutability on registration, add a bit for that to the AbstractAttribute/Type classes, and check that here.
This is a major revision.
- Add an isMutable flag on both AbstractType/Attribute to indicate whether the underlying type/attribute (storage) is mutable. This is done by checking if there is a member function LogicalResult mutate(StorageAllocator &, ...) defined in the storage class via SFINAE facilities.
- SubElementTypeInterface now only does bookkeeping on mutable types or attributes.
- Use unit test to test the new mutable type flag. Which effectively moves part of the unit test infrastructure from D127539 to here.
mlir/lib/IR/SubElementInterfaces.cpp | ||
---|---|---|
71 | Given that these sets are not on the hot path anymore, do you think it's still better to change the container type? I feel like two separate sets are more readable. |
Could mutability not be a trait instead?
mlir/include/mlir/IR/StorageUniquerSupport.h | ||
---|---|---|
79 | LLVM has a utility for this. LLVM::is_detected. You can find an example in AsmParser | |
mlir/lib/IR/SubElementInterfaces.cpp | ||
19 | I would have two separate recursive functions. One with the set and one without | |
71 | Yeah no preference then | |
80–83 | Drop llvm:: |
It is possible, for instance an opaque LLVMStruct cannot be mutated. But to have such granularity, we need to rely on non-static information, which is something AbstractType/AbstractAttribute cannot carry. Of course, we can have isMutable exposed on Type and Attribute, but I'm not sure about that. What do you think?
I'm not sure I understand what you're getting at. Mutability is tied to the abstract type/attribute implementation. Instead of abstractType->isMutable(), could we not have abstractType->hasTrait<TypeTrait::Mutable>()?
I see, I thought you were referring to "trait" as a general C++ concept rather than MLIR's Trait.
IIRC trait needs to be manually annotated on a type or attribute, while my current approach detects mutability out of the box. The reason I chose the current way was because TypeStorage/AttributeStorage use the same way to see if a type/attribute can be mutated. And I'm a little worry that using trait might make such property out-of-sync with TypeStorage/AttributeStorage, if, let's say, a dialect forgets to annotate their mutable types or attributes with trait.
I think @ftynse introduced mutable types and attributes and may be able to weigh in on this.
MLIR doesn't really "detect" mutability, more so "mutate" fails to compile if the type doesn't implement the function. Now that we're trying to query mutability, storing the results of a static check in a bit doesn't seem like the way to go. Traits make sense in this regard. To keep the two in sync, mutate can static assert that the concrete type implements the trait and the trait's verifier can static assert that the concrete type implements the function.
Having a MutableTypeInterface/MutableAttrInterface for specifying this seems like a good step forward. Manual annotation isn't necessarily a bad thing here, ODS can always sugar this better (though ODS doesn't support mutable attribute/types right now). We should consider the desired end state of specifying mutability for attributes and types.
Yes, ideally we can have MutableTypeInterface/MutableAttrInterface with a mutate function. But mutate functions in existing TypeStorage/AttrStorage relies on template-parameterized function arguments, which I don't think it can fit into the facilities for interface (An interface's Concept declares virtual functions but virtual functions can't have template).
Right now I'm planning to use a simple TypeTrait/AttrTrait to specify mutability to go forward on this patch.
Use a TypeTrait/AttributeTrait to indicate mutability instead of a flag in AbstractType/AbstractAttribute. I also added static asserts to make sure it syncs up with mutate function defined in the Type/Attribute storage
mlir/include/mlir/IR/StorageUniquerSupport.h | ||
---|---|---|
81 | FYI: For variadic functions, we can't use the normal way (like decltype(declval<T>().foo(...))) to match function signature. | |
mlir/lib/IR/SubElementInterfaces.cpp | ||
19 | If we split into two versions, the function without set can only call the same variant* (i.e. the one without set), which effectively makes an assumption that all sub-types of an immutable type are immutable -- I'm afraid this is not the case, a good counterexample is !llvm.ptr<struct<...>> (LLVM pointer is immutable while LLVM struct is mutable). *: Even if we use a "local" set to record visited types like void walkImmutableImpl(...) { set<Type> localVisited; if (mutable) walkMutableImpl(..., localVisited); else walkImmutableImpl(...); } It still can't prevent infinite recursion. |
mlir/include/mlir/IR/AttributeSupport.h | ||
---|---|---|
266–267 ↗ | (On Diff #437737) | I forgot to change this, just a second. |
mlir/include/mlir/IR/AttributeSupport.h | ||
---|---|---|
265 ↗ | (On Diff #437737) | This should be somewhere else... In one of the mutate function wrappers? |
mlir/include/mlir/IR/AttributeSupport.h | ||
---|---|---|
265 ↗ | (On Diff #437737) | Doing checks in one of the mutate wrappers allows up to only check for IsMutableType trait (because the wrapper will only be compiled / compilable if mutate function is defined) but we can't check the other direction: If IsMutableType is present, whether mutate function is defined. Thus, the check I wrote here checks for both at the same time (it's an "inverse XOR" check so it will only complain if only either mutate function or trait is present), in that sense I feel like putting it in the registration function is better |
mlir/include/mlir/IR/AttributeSupport.h | ||
---|---|---|
265 ↗ | (On Diff #437737) | Can't you static_assert from the Trait::verify? Some other traits do that. |
mlir/include/mlir/IR/AttributeSupport.h | ||
---|---|---|
265 ↗ | (On Diff #437737) |
Attribute and Type traits don't have verifiers. |
265 ↗ | (On Diff #437743) | Can we pass the type of the trait to StorageUserBase instead? and have the static assert inside of StorageUserBase::mutate? |
mlir/include/mlir/IR/Attributes.h | ||
243 | The Attr is redundant and doesn't add much. | |
mlir/include/mlir/IR/StorageUniquerSupport.h | ||
79 | We should be able to avoid this complexity. We really just need a static assert inside of StorageUserBase::mutate that it's only called when the parent is mutable. If necessary, we could do something similar to [1] that makes the static assert dependent on the template types of the mutate function (but ignores them) | |
mlir/include/mlir/IR/Types.h | ||
234 | Same here, I'd drop the Type. |
mlir/include/mlir/IR/Attributes.h | ||
---|---|---|
30 | Doesn't this mark all attributes with IsMutable? |
mlir/include/mlir/IR/Attributes.h | ||
---|---|---|
30 | Ah, nevermind. |
mlir/include/mlir/IR/StorageUniquerSupport.h | ||
---|---|---|
186 | This seems a little invasive to me -- that you have to pass in MutableTrait from the implementing classes. Can we not have a generic StorageUserTrait::IsMutable? That way every user of StorageUserBase doesn't need to also define its own mutability trait that's identical to all the other ones. |
Use a single StorageUserTrait::IsMutable to replace TypeTrait::IsMutable and AttributeTrait::IsMutable. The latter two, however, remain type aliases to StorageUserTrait::IsMutable for ease of use.
mlir/include/mlir/IR/StorageUniquerSupport.h | ||
---|---|---|
186 | I agree that a single IsMutable trait is easier and less intrusive. But I feel like many dialect authors might find StorageUserTrait a little confusing since they don't necessarily know what storage is in this context, as it's an implementation detail. So I'm turning the Attribute/TypeTrait::IsMutable here into aliases to StorageUserTrait::IsMutable |
Can we have a test that checks that we no longer go into infinite recursion? Otherwise, LG!
(Sorry, coming back from OOO)
right, this patch is effectively preparing for D127539, which will go into infinite recursion without it. Otherwise, I don't think any of the type or attribute (as of the current HEAD) with this interface will go into infinite recursion. Thus, I think it's more difficult to write such test in this patch (D127539 has tests to make sure we don't go into infinite recursion though). What do you think?
I would prefer if we could test this outside of the LLVM dialect if possible, given that it should just be a matter of using the Recursive type in the test dialect right?
Yes I think this is possible, I forgot there is a test dialect where we can exercise this part of the code by making type alias, which calls SubElementInterface, on its recursive type.
I will update this diff accordingly later.
Add generic tests to check if SubElemenTypeInterface goes into infinite recursion. Please take another look.
Note that since SubElementAttributeInterface shares most of the logics with SubElemenTypeInterface (including the newly introduced IsMutable trait), plus we don't have recursive attribute in TestDialect right now, I don't add a similar test for attribute.
Doesn't this mark all attributes with IsMutable?