How build a mlir::LLVM::CallOp for indirect call?

i saw that the mlir-asm is able to call a mlir::Value for indirect call, but i seem have not find a suitable build function :

static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, LLVMFuncOp func, ValueRange args);
  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, TypeRange results, StringAttr callee, ValueRange args = {});
  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, TypeRange results, StringRef callee, ValueRange args = {});
  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, /*optional*/::mlir::Type result, /*optional*/::mlir::FlatSymbolRefAttr callee, ::mlir::ValueRange odsArg_0, ::mlir::LLVM::FastmathFlagsAttr fastmathFlags);
  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::TypeRange resultTypes, /*optional*/::mlir::FlatSymbolRefAttr callee, ::mlir::ValueRange odsArg_0, ::mlir::LLVM::FastmathFlagsAttr fastmathFlags);
  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, /*optional*/::mlir::Type result, /*optional*/::mlir::FlatSymbolRefAttr callee, ::mlir::ValueRange odsArg_0, ::mlir::LLVM::FastmathFlags fastmathFlags = {});
  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::TypeRange resultTypes, /*optional*/::mlir::FlatSymbolRefAttr callee, ::mlir::ValueRange odsArg_0, ::mlir::LLVM::FastmathFlags fastmathFlags = {});
  static void build(::mlir::OpBuilder &, ::mlir::OperationState &odsState, ::mlir::TypeRange resultTypes, ::mlir::ValueRange operands, ::llvm::ArrayRef<::mlir::NamedAttribute> attributes = {});

The last one (with generic lists of everything) can be used to construct this. The pointer to the function is the first operand, and the call arguments are the remaining operands. You may want to add a builder overload for this case for better readability.

thx

I tried many possibilities, but build failed. i guess that the reason is i used the opaque pointer. but my the pointer to function is from the result of calling dlsym function, so how should i cast opaque pointer to a function type?

Failed how? Does it assert? Does it not produce the IR you want?

Then i try used the CallIndirectOp of the func dialect:

You are building a func.IndirectCallOp, not llvm.CallOp, hence the type mismatch. Build LLVM::CallOp instead in the way explained above.

Please see the previous text . remain is error, but no more error info.

It is unclear whether that error comes from MLIR, from your code or from the library at that point. I suggest you generate the IR after conversion to the LLVM dialect, run mlir-translate to get LLVM IR, then either run lli or compile it with clang. And check for errors on each stage.

I just commit the build CallOp, then it work fine. if my build call spell is right, then the error should be obvious, my spell is the last code on picture :

builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), __void, std::vector<mlir::Value>{free_func});

I know the free_func is a invalid address, but that is the error of runtime, i think if i give a pointer , the builder should be able to generate a corresponding indirect call ir. and i think the CallIndirectOp of func dialct is same, so i think maybe the error from opaque ptr according to the error info of CallIndirectOp.

The fact that commenting out the operation removes the error does not imply that the error is in MLIR. I don’t see how opaque pointers may have an effect here if the produced LLVM IR is valid. This is why you must split the process into several stages like described above.

If you are attempting to call free from libc, you are calling it with an incorrect signature. It expects a pointer and you give it nothing. So when it attempts to read access the non-existing argument, it may result in a segmentation fault (depending on the calling convention supported by the hardware).

ok , thank you. i wil be try it.

note: i’m not used free from libc. it is from dlsym. the fact is that it is not exist. i have not create it. but i don’t think it affect generating code.

I have solved it, and i find some funny things. the following Test 1-4 is no problem. but Test 5, Could it be a bug?

#include "mlir/IR/Verifier.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"

int main()
{
	mlir::MLIRContext ctx;
	ctx.getOrLoadDialect<mlir::LLVM::LLVMDialect>();

	mlir::OpBuilder builder(&ctx);
	mlir::ModuleOp theModule;
	theModule = mlir::ModuleOp::create(builder.getUnknownLoc());
	builder.setInsertionPointToEnd(theModule.getBody());

	auto i64 = builder.getI64Type();
	auto __void = mlir::LLVM::LLVMVoidType::get(builder.getContext());
	auto opaque = mlir::LLVM::LLVMPointerType::get(builder.getContext());

	auto func_ty = mlir::LLVM::LLVMFunctionType::get(i64, std::vector<mlir::Type>{i64, i64}, false);
	auto max = builder.create<mlir::LLVM::LLVMFuncOp>(builder.getUnknownLoc(), "max", func_ty);

	auto a = builder.create<mlir::LLVM::ConstantOp>(builder.getUnknownLoc(), builder.getI64IntegerAttr(1));
	auto b = builder.create<mlir::LLVM::ConstantOp>(builder.getUnknownLoc(), builder.getI64IntegerAttr(2));
	builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), max, std::vector<mlir::Value>{a, b});	

	auto max_addr = builder.create<mlir::LLVM::AddressOfOp>(builder.getUnknownLoc(), max);
	{	// 1. here work fine
		builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), i64, std::vector<mlir::Value>{max_addr, a,b});
	}
	{	// 2. here work faied. (i64 to opaque)
		//auto i_max_addr = builder.create<mlir::LLVM::PtrToIntOp>(builder.getUnknownLoc(), i64, max_addr);
		//auto _max_addr = builder.create<mlir::LLVM::IntToPtrOp>(builder.getUnknownLoc(), opaque, i_max_addr);
		//builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), i64, std::vector<mlir::Value>{_max_addr, a,b});
	}
	{	// 3. here generated ir, but it has a little strange. (i64 to func_ty)
		//auto i_max_addr = builder.create<mlir::LLVM::PtrToIntOp>(builder.getUnknownLoc(), i64, max_addr);
		//auto _max_addr = builder.create<mlir::LLVM::IntToPtrOp>(builder.getUnknownLoc(), func_ty, i_max_addr);
		//builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), i64, std::vector<mlir::Value>{_max_addr, a,b});
	}
	{	// 4. here work fine. (i64 to ptr<func_ty>)
		//auto func_ptr = mlir::LLVM::LLVMPointerType::get(func_ty);
		//auto i_max_addr = builder.create<mlir::LLVM::PtrToIntOp>(builder.getUnknownLoc(), i64, max_addr);
		//auto _max_addr = builder.create<mlir::LLVM::IntToPtrOp>(builder.getUnknownLoc(), func_ptr, i_max_addr);
		//builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), i64, std::vector<mlir::Value>{_max_addr, a,b});
	}
	{	// 5.  when the result of func_ty is __void, it generated ir, but it has a little strange. (same to Test1, but result is __void)
		//builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), __void, std::vector<mlir::Value>{max_addr, a,b});
	}

	if(failed(mlir::verify(theModule))) {
		theModule.emitError("module verification error");
	}
	theModule->dump();
	return 0;
}

This code creates a function that returns i64 and the version (5) attempts to create a pointer to this function with the function type returning void. There’s no bug in MLIR here, the code is wrong. The verifier correctly indicates this: error: 'llvm.call' op result type mismatch: '!llvm.void' != 'i64'. When called through an opaque pointer, the verifier is unable to see the type mismatch. This doesn’t make the code valid.

the fact is that both returning void type also are a error. but both returning i64 are ok. the error info is :

error: 'llvm.call' op calling function with void result must not produce values                                                                                                                                                                                                             
error: module verification error    

the following is the code that both return __void :

#include "mlir/IR/Verifier.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"

int main()
{
	mlir::MLIRContext ctx;
	ctx.getOrLoadDialect<mlir::LLVM::LLVMDialect>();

	mlir::OpBuilder builder(&ctx);
	mlir::ModuleOp theModule;
	theModule = mlir::ModuleOp::create(builder.getUnknownLoc());
	builder.setInsertionPointToEnd(theModule.getBody());

	auto i64 = builder.getI64Type();
	auto __void = mlir::LLVM::LLVMVoidType::get(builder.getContext());
	auto opaque = mlir::LLVM::LLVMPointerType::get(builder.getContext());

	auto func_ty = mlir::LLVM::LLVMFunctionType::get(__void, std::vector<mlir::Type>{i64, i64}, false);
	auto max = builder.create<mlir::LLVM::LLVMFuncOp>(builder.getUnknownLoc(), "max", func_ty);

	auto a = builder.create<mlir::LLVM::ConstantOp>(builder.getUnknownLoc(), builder.getI64IntegerAttr(1));
	auto b = builder.create<mlir::LLVM::ConstantOp>(builder.getUnknownLoc(), builder.getI64IntegerAttr(2));
	builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), max, std::vector<mlir::Value>{a, b});	

	auto max_addr = builder.create<mlir::LLVM::AddressOfOp>(builder.getUnknownLoc(), max);
	{	// 5.  when the result of func_ty is __void, it generated ir, but it has a little strange. (same to Test1, but result is __void)
		builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), __void, std::vector<mlir::Value>{max_addr, a,b});
	}

	if(failed(mlir::verify(theModule))) {
		theModule.emitError("module verification error");
	}
	theModule->dump();
	return 0;
}

It is impossible to create a value of type !llvm.void. So the function call operation must not return anything. By indicating __void as its result type in the builder, this code requests the operation to have a result value of type !llvm.void. This is invalid IR that is correctly detected by the verifier. The correct builder call should look like builder.create<mlir::LLVM::CallOp>(builder.getUnknownLoc(), TypeRange(), std::vector<mlir::Value>{max_addr, a,b});. Again, if the function pointer is opaque, the verifier cannot inspect the types.

I suppose we could improve the CallOp::build method to ignore the result type if it is !llvm.void.

1 Like

thanks again, all problems have been solved.

Thanks @ftynse … I am stuck at here for weeks. I tried use mlir NoneType as void, and finally, got LLVMVoidType, and got this error, and got your fix.