Should func::CallOp and func::ConstantOp be generalized to refer to FunctionOpInterface?

Hi everyone,

Currently the func::CallOp and func::ConstantOp are limited to only refer to func::FuncOp. However, there is a (quite extensive) FunctionOpInterface (also implemented by FuncOp) that, as far as I understood, should motivate and ease the implementation of domain-specific function operations. Would it be possible to lift these limitations and allow above mentioned operations to refer to all operations implementing the FunctionOpInterface such that they can be reused or are there any reasons to explicitly forbid this? What would such a change entail (other than changing the two verification functions inside FuncOps.cpp)?

I’m currently implementing such a more specific function operation that has a few additional verified attributes and is limited to 0 or 1 return values. Of course, I also need to be able to call that function, but I don’t need anything specific there, so I’d currently just have to copy-paste the rest of the func dialect.

When we’re already at it, would it make sense to allow operations that implement the FunctionOpInterface to also reuse the func.return operation (i.e., generalizing the HasParent constraint)?

While we generally encourage operation reuse when possible, we also value consistency between operations. Extending to support arbitrary functions sounds like it would serve a limited use case while creating additional challenges. Practically, most “custom” function-like operations I’ve seen so far extend what func.func can do, so it makes little sense to use for them since it does not and arguably should not know about those extensions. For example, llvm.func supports variadic functions (and implements the FunctionOpInterface) and the corresponding can handle variadic arguments to the call. How one would disallow to target a variadic llvm.func if it accepted anything that implements the interface? How one would restrict func.return used inside llvm.func to only accept one operand because the surrounding function cannot handle more? On the other hand, it is possible to share the implementation code without sharing operation, we used to have a FunctionLike construct before it was turned into an interface. Same can be done with calls to simplify adding custom operations.