As we are starting to use more and more interface, I see some issues with scaling their use. So before we add any more interfaces and go down a certain path, I’d like to see whether others have encountered similar issues and whether we can distill some interface best practices.
interface dependencies
When splitting apart the StructuredOpInterface
in linalg
and extracting the more general concept of DestinationStyleOpInterface
, we realized that after the splitting, the former interface ends up relying on the latter interface also being implemented. Currently, there is no way to encode this dependency other than mentioning it in a comment. Having dependencies defined in a more structured way would help with documentation and allow for improvements in generated code.
We cannot statically enforce dependencies, as interfaces on operations can also be implemented using external interfaces. So we can only decide the set of implemented interfaces at linktime, which practically means at runtime. But even checking at runtime in one place would be an improvement over code having to dyn_cast
to an interface that is a dependency and handle failure.
We could go even further and make methods from interface dependencies also visible in the dependent interface, removing the need for casts in the first place. As a downside, this might lead to even more name clashes.
namespaces/scopes
Which brings me to the second issue: We are starting to see name clashed between interface methods and methods defined on the operation itself. For external interface implementations this is not an issue. However, if an operation directly implements an interfaces, the interface methods are visible for the operation itself, as well.
A recent example of this was in the DestinationStyleOpInterface
that had a getInputs
method. We resolved the conflict by renaming the method to getDPSInputs
. However, adding ad-hoc namespaces to operations seems not like a good general solution.
Instead, we could consider to have interface implementations live in a different namespace/scope from regular methods (I will leave the C++ wizardry to make this happen to more qualified people). Doing so would lead to a couple more casts but at least it would always be clear which method is referenced.
partially implemented interfaces vs. many interfaces
In a recent review for an addition to the TilingInterface
we had a discussion as to whether an interface should rather have methods that are optionally implemented (i.e. might just always fail for an operation) or whether one should split the interface instead.
My take is to go for the latter, as I quite like the idea that one can dyn_cast
to an interface to check whether a certain operation, in principle, supports a behavior. Hence I suggested we have a TilingInterface
and a PartialReductionInterface
so that casting to the latter implies that the operation supports partial tiling of reductions.
The counter argument is that this leads to many smaller interfaces that jointly model behaviors needed for a transformation (like tiling and distributing in the above example). This adds boiler plate and makes navigating interfaces harder. I think this is manageable with documentation and tooling but would like to hear what others think.