Passing function pointer as argument to external function

Hi all,

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:

  1. Allow AddressOfOp to take an LLVMFuncOp as operand (don’t know it that is doable).
  2. Allow AddressOfOp to take a symbol reference. Could be messy since GlobalOp is an operand.
  3. Add a new operator, say FuncAddressOfOp, which takes a symbol reference.

What do you think?

/Manuel

AdressOfOp takes the SymbolRefAttr (https://github.com/llvm/llvm-project/blob/master/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td#L535), there’s no notion of an op taking another op as operand… Maybe you are confusing it with the build function that takes a GlobalOp as an API simplification.

The actual place to look at is the translation of that to LLVM IR and whether it produces the right kind of constant.

Ok, I have not noticed that AddressOfOp actually takes a SymbolRefAttr, which would be a good thing. However, that is of no help because the verifier checks that the symbol was defined by a GlobalOp (https://github.com/llvm/llvm-project/blob/master/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp#L830). Also the translator to LLVM IR relies on this (https://github.com/llvm/llvm-project/blob/master/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp#L369).

Would it make sense to extend AddressOfOp::getGlobal() to also allow LLVMFuncOp as symbol source?

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:

  1. Implement better builders and verification for ConstantOp.
  2. Create a FunctionAddressOfOp paired with LLVMFuncOp. AddressOfOp would work only with GlobalOp.
  3. 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?

I’m asking because translation to LLVM IR has some special handling for cyclic call graphs (llvm-project/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp at main · llvm/llvm-project · GitHub).

Since ConstantOp does no verification, it can handle these cases already (which is important for me).