Parameterizable Op Traits?

TL;DR: Is there a way to attach a known (name, value) pair to an AbstractOperation? I can’t find one, though the trait code is fairly complex.

There are properties of Ops (AbstractOperations) which various code (analysis, transformations, optimizations, etc.) may want to query when only given the AbstractOperation (so an OpInterface wouldn’t work). For example: in CIRCT, we may (will) eventually want to model device primitives with known (perhaps complex) latencies. Since each device primitive will be modeled as a different Op, it is a property of the Op definition rather than a particular instance of that device primitive (which would be an Operation*). Something which may want to use those data in a generic way is a scheduler: given a set of possible device primitives (AbstractOperations) map Operation*s on to them, wherein the source Operation*s don’t necessarily have to be mapped into their corresponding AbstractOperations – there is a many-to-many relationship of device primitives (AbstractOperations) which can implement Operation*s.

Operation traits (which are attached at to the AbstractOperation, yes?) are close, but they are binary. Ideally, we’d have parameterizable Op traits so client code could not only query if an AbstractOperation had a particular trait, but a value attached to said trait. E.g. the comb.add Op would have a trait latency with the value 1 (cycle). (Example oversimplified.)

Does this make sense? Is it possible to use (perhaps abuse) Operation Interfaces for this purpose (bearing in mind that the problem is getting those data out of an AbstractOperation)?

As far as I understand, you can query interfaces on AbstractOperation with getInterface, since the InterfaceMap is in the AbstractOperation. Is there a reason why this wouldn’t work here? (Maybe I’m just misunderstanding the problem).

The getInterface method is marked as “should not be used directly”. (I’m not sure why.) Assuming I can use it in a safe way, however, it returns the Interface “concept”. In InterfaceSupport.h, the documentation specifies that the concept cannot contain non-static data and the example only contains methods which look like they are run on Operation*s:

   struct ExampleInterfaceTraits {
     struct Concept {
       virtual unsigned getNumInputs(T t) const = 0;
     template <typename DerivedT> class Model {
       unsigned getNumInputs(T t) const final {
         return cast<DerivedT>(t).getNumInputs();

So it’s not clear to me how I’d use getInterface without an Operation* to get extra information about the Op definition (which I’m treating as synonymous with AbstractOperation). The way the code works to get an OpInterface from an Operation* (which implements it) is not something I understand, so I may just be ignorant of the full power of Interfaces. I’m essentially looking for an AbstractOperationInterface.

This sounds exactly like a static method on an interface.

Using it directly will bypass the dialect fallback mechanism for op interfaces, which is implemented in OpInterface::getInterfaceFor. Maybe the implementation can be lifted to AbstractOp. Otherwise it should work.

But what you want is static data with respect to Operation. As far as I understand, it’s a property of a concrete Op class, not of a concrete Operation, otherwise you wouldn’t be asking for traits. You can have a static method in the interface and have each concrete Op class implement that static method to return value_for_this_op_class;. Practically, the concept class is an explicit virtual table, which gets populated when the Op class is registered with the system (usually, when constructing the dialect). You don’t need an Operation instance for static interface methods.

Yes, static with respect to the Operation. Based on my (incorrect) reading of the code, I was under the impression that a static method on the interface context would be static with regard to the interface concept definition, not the Operation which was implementing it. What you’re saying is that the concept essentially becomes part of the class through a per concrete-Op-class template instantiation? If so, then you’re right – a static function would work just fine.

All interface methods are instance methods on the concept class. Well, technically, they are pointers to functions stored in the concept object.

Practically it looks along the lines of:

class Concept {
  RetTy (* instanceMethod)(Operation *, ArgTy);
  RetTy (* staticMethod)(ArgTy);

template <typename ConcreteOp>
class Model : public Concept {
  Model() : Concept { &instanceMethodImpl, &staticMethodImpl) {}

  static RetTy instanceMethodImpl(Operation *op, ArgTy arg) {
    return cast<ConcreteOp>(op).instanceMethod(arg);

  static RetTy staticMethod(ArgTy arg) {
    return ConcreteOp::staticMethod(arg);

class Interface : public Op<Interface, ...> {
  RetTy instanceMethod(ArgTy arg) {
    return impl->instanceMethod(getOperation(), arg);

  RetTy staticMethod(ArgTy arg) {
    return impl->staticMethod(arg);

  Concept *impl;
  /* getOperation() is available because thanks to deriving Op */

it’s more involved in reality because of the fallback mechanism, relying on traits for default implementation and sharing between different kinds of interfaces. The concept class is not the interface class. It is does not become part of the Op class either. It is essentially a call dispatch mechanism with more flexibility than virtual tables, i.e. the dispatching logic can be more complicated than just forwarding to the similarly-named method on the concrete object. It’s possible to use it directly, but it requires care.

It looks like your idea of AbstractOpInterface is good from usability perspective. It would only have static interface methods that don’t need Operation *, along the lines of

class AbstractOpInterfaceTy {
  AbstractOpInterfaceTy(const AbstractOperation &aop) : impl(aop.getInterface<InterfaceTy>())
  RetTy staticMethod(ArgTy arg) {
  Concept *impl;

Having a static interface class SGTM.

– River