| Issue |
183798
|
| Summary |
More generic property handling (notes materializing old RFCs)
|
| Labels |
enhancement,
mlir:core
|
| Assignees |
krzysz00
|
| Reporter |
krzysz00
|
This issue is me looking to scribble down the rough plan for implementing the more general property system (that'll grow to registrations, mere general parsing, etc.) so that there's a reference for what I had in mind when I kicked off a big long chain of PRs. People can read it if they really want but it may not make too much sense
All names are *super* subject to change.
# Step 1: OpaqueProperties => PropertyRef
Currently, when we handle a blob of properties in a generic context, we use `OpaqueProperties`, which is a weird way to spell `void*`. I think the first step here is changing that to dealing with `PropertyRef`, which'll be `{mlir::TypeID storageTypeId; void* data;}', and generally have the same interface as the existing object. Its default would be typeId<void>, nullptr so you can have the no-properties case represented.
`OperationName` will have `getPropertiesTypeId` or something like that to signal what (if any) properties should be in there.
This'll let various builders and such be more type-safe and will set us up for generic handling of properties.
## Step 1.5: `OwningPropertyRef`
OwningPropertyRef is PropertyRef that free()s that `void*` on destruction, so we can return it from parsers and such
# Step 2: PropertyDesc
Currently, all the core information about a property (as opposed to things like its default value and predicate, which can vary per instance) lives in tablegen, gets inlined into operations, and doesn't have an obvious independent existence in the C++.
If, as I've been talked into last year, we want a more general infrastructure for handling these things, we'll want something like this
```c++
class GenericPropDesc {
using InterfaceType = PropRef;
using StorageType = OwningPropRef;
virtual InterfaceType convertFromStorage(StorageType& owning);
virtual InterfaceType convertToStorage(const InterfaceType&, StorageType&);
virtual size_t getStorageSize();
...
virtual StorageType createStorage();
virtual ParseResult parse(OpAsmParser&, StorageType&);
...
// This'll have read/write to bitcode, but won't have the attribute freeze methods, since we can be rid of them
// And probably, for registration
SmallString getName();
// Probably the usual LLVM RTTI bits.
};
```
The generic methods here will be stubs initially.
>From there, we get the actual implementation, which'll probably tablegen up in a separate step - using representative example types
```
class StringProp : public GenericPropDesc {
using InterfaceType = StringRef;
using StorageType = std::string;
OwningPropRef create(...) { return OwningPropRef<std::string>(storageDefault()) } override;
static StorageType storageDefault() { return StorageType(); }
static InterfaceType getDefault() { return StringRef(); }
...
// parse, print, etc, but with the concrete type and static
}
```
Operation `Properties` structs will also define one of these things, and they'll have a `printExcludingKeys` so we can implement the `prop-dict` directive without reference to attributes.
This also means that the `Property` in tablegen will gain a namespace attribute so we know where to put this thing.
There'll definitely be a blanket implementation covering `Attribute`s.
Then there's the question of how to handle combinators like `OptionalProp` in this scheme. My sense of this is something along the lines of
```
template<typename BasePropDesc, bool shortFormat> OptionalPropDesc<BaseProp, shortFormat> : GenericPropDesc {
... // implementation of optional that calls all those static methods.
}
```
Tablegen-side, we'll probably have `OptionalProp<Property p, bit shortFormat>` becoming
```
PropCombinatorInstance<OptionalPropDesc, (p, shortFormat> {
let predicate = [make it optional, as currently);
let hasOptionalParser = ...;
...
let descType = "::mlir::OptionalPropDesc<" # p.descType # ", " # shortFormat # ">";
)
```
We might have a tablegen-side `PropCombinator` class defining the C++-side arguments to these templates so we can do tablegen reasoning about them, or we might not. Materializing this sort of thing in tablegen might make handling `DefaultValuePropDesc<Base, Base::InterfaceType newDefault>` easier - since we'll want that one to inherit from `Base` but we don't want that for the optional.
(Or maybe we'll end up with a `PropertyDef` sort of situation - this'll probably become much clearer when implementation starts)
Constraints still won't be getting materialized into this system - it'd be a bit weird to do that, since they go down quite separate code paths.
# Step 3: Registration, generic parsing, etc.
We can very likely do a string => PropertyDesc map in the context, where that string'll be - let's tentatively go with - the fully qualified C++ type of the property descriptor at issue. The general rules preventing that type from diverging in different translation units will, I expect, keep this workable and allow property registration to proceed on the basis that, if you try to register something that's already registered, that's a no-op. So we won't need to tie properties to dialects or the like.
>From there, the generic form of parse becomes "grab the quoted string giving the type we're parsing, look that up in the registrations, then parse the contents of the following `<>`. So we'd end up with syntax like `#"::mlir::IntegerProp<int32_t>"<8>` as the fully generic form, but more realistically, it'd be `#"::mlir::dialect::FooOp::Properties"<x = 1, y = read | write>`.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs