Right now there is no common API to invoke a Value containing an arbitrary instance of a specific type. This is for example desirable when creating dialects working on memory, in which one would like to get initializers for newly created memory locations. The only way to achieve something similar is to define procedures to generate operations creating values for every types one wants to support, creating a lot of inter-dialect dependencies and making it hard to expand upon.
Instead, I propose we create a simple DefaultValueInterface that would provide a method capable of creating (or fetching) a value containing a default value for the types that implement the interface. I would expect the interface to look something like this:
def DefaultValueInterface : TypeInterface<"DefaultValueInterface"> {
let cppNamespace = "::mlir";
let description = [{
This interface provides a common API to construct default values for types
on demand. This is useful when an instance of a type is needed with no
requirement on which specific instance.
The default value implementation of a type can provide guarantees on which
instance is the default, at the discretion of the implementor.
}];
let methods = [
InterfaceMethod<[{
Constructs or obtains a default value for this type.
The builder's insertion point is set where the default value must be
live.
}],
"::mlir::Value", "getDefaultValue", (ins
"::mlir::OpBuilder &":$builder
)>,
];
}
This interface could live in the builtin dialect, in BuiltinTypeInterfaces.td
.
Let me develop on a specific example of why this would be useful. When implementing mem2reg interfaces for MemRef, one needs to specify a default value in case a load would happen before a store or if an allocated value is live in a dead branch. Because MemRef supports arbitrary types using MemRefElementTypeInterface, one cannot always provide a default for the mem2reg interfaces. The only alternative approach is to hardcode a set list of supported default types, creating hard to expand type switches and introducing unnecessary dependencies to MemRef (like for example the complex dialect).
An issue with this approach is that it is not always obvious which instance of a type should be the default. However, I think it can still be useful without specifying any property about the default value beyond it being of the expected type, for example in the example above. The type can implement more specific interfaces for values with more guarantees (for example addition neutral or multiplication neutral elements for rings).
Another issue is that sometimes a default value is worse than a generic undefined value. For example, in the LLVM dialect implementation of the mem2reg interface, llvm.undef
is used instead of a default (for example say 0 for integers) because that would allow further optimization on this undefined value (and also because it is the LLVM-specified behavior, but that is coincidental). However, I think having default per-type do not prevent using better values like this. In the context of MemRef, there is no notion of undefined value so defaults are the next best thing, but in dialects where undefined values exist they can simply be used instead. It may also be the case that one in fact wants to specify a consistent and defined default.
If this addition sounds interesting to you, I will open a revision to add it soon after. Any comment is definitely welcome!
@kuhar I think considering your work on poison semantics you may have an interesting opinion on this.