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?


AdressOfOp takes the SymbolRefAttr (, 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 ( Also the translator to LLVM IR relies on this (

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 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 (

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