I’d like to define a callback function in MLIR and call an external C function, to which I pass the
callback function as argument. I think this use case is not covered yet by the LLVM dialect. One
can get the address of a global with AddressOf but that operation only works with globals
defined by a GlobalOp.
Has anyone already done such a thing? If not, I would like to extend MLIR to support function
pointers as well. My ideas so far were:
Allow AddressOfOp to take an LLVMFuncOp as operand (don’t know it that is doable).
Allow AddressOfOp to take a symbol reference. Could be messy since GlobalOp is an operand.
Add a new operator, say FuncAddressOfOp, which takes a symbol reference.
While studying the LLVM dialect to LLVM IR translation, I have found what I was looking for. Function pointers are not implemented with AddressOfOp (that one works only in combination with GlobalOp). Instead one has to create an LLVM::ConstantOp with a symbol reference. This is then translated to the LLVM function definition.
The relevant bits for the translation of symbol reference to function definition are in ModuleTranslation::getLLVMConstant().
That name implies it would return a GlobalOp, so I’d rather consider adding a different method and providing a way to check whether the operation takes an address of an symbol defined by llvm.mlir.global or by llvm.func.
I think it is translated to a pointer to an LLVM IR function (which is not a function definition). This mechanism precedes global/addressof and was intended for indirect calls. Is it sufficient for your use case?
In any case, it makes sense to look into deduplicating these mechanisms.
I tried to implement function pointers with a new LLVM::FunctionAddressOfOp op in order not to mess too much with the coupling between GlobalOp/AddressOfOp. Then I noticed that ConstantOp was already doing what I wanted to implement, so I reverted everything again. Later I regretted this because the verifier of ConstantOp is not very great (at least not with symbol references). For example, there is no type-checking against the function signature, which easily triggers assertion failures during translation to LLVM IR. Also it is not obvious whether ConstantOp’s result type should be a function type or a pointer to it (void(int) vs void(*)(int) in C++ speech).
I can see several options:
Implement better builders and verification for ConstantOp.
Create a FunctionAddressOfOp paired with LLVMFuncOp. AddressOfOp would work only with GlobalOp.
Add AddressOfOp::getFuncOp() and something like AddressOfOp::isFuncOp().
#2 might actually be the most user-friendly option, IMHO.
Would we also need a way to declare a FuncOp in the case there are circular indirect calls? I don’t know at what point the verifier is run, i.e. can it be that the verifier is executed on an operator with a symbol reference whose symbol has not been defined, yet?