Emitting OpenMP Code from OMP Dialect and Lowering to LLVM IR
Introduction
Emitting OpenMP Code from OMP Dialect and Lowering to LLVM IR
Photo by Orfeas Green on Unsplash
Introduction
In this blog, we’ll explore how to emit OpenMP (omp) code using the MLIR OpenMP dialect, lower it to LLVM, and generate an executable binary.
We’ll go through the key steps:
- Constructing an MLIR module with OpenMP parallel execution.
- Lowering the MLIR code to LLVM IR.
- Translating LLVM IR to a final executable using Clang.
Setting Up the MLIR Context
The first step is to create an MLIR context and load the necessary dialects. In our case, we need the OpenMP (omp) and LLVM dialects.
MLIRContext context;
// Load the required dialects
context.getOrLoadDialect<mlir::omp::OpenMPDialect>();
context.getOrLoadDialect<mlir::LLVM::LLVMDialect>();
mlir::OpBuilder builder(&context);
mlir::ModuleOp module = builder.create<mlir::ModuleOp>(builder.getUnknownLoc());
This initializes the MLIR context and prepares it for generating operations.
Defining a Global String and Print Function
To print a message in OpenMP threads, we need:
- A global string (
"Hello World\n"). - The
printffunction.
Defining a Global String
auto i8Type = builder.getI8Type();
auto i8PtrType = mlir::LLVM::LLVMPointerType::get(&context);
const char *str = "Hello World\n\0";
size_t len = strlen(str);
auto arrayType = mlir::LLVM::LLVMArrayType::get(i8Type, len);
auto globalStr = builder.create<mlir::LLVM::GlobalOp>(
builder.getUnknownLoc(), arrayType, true,
mlir::LLVM::Linkage::Internal, ".str",
builder.getStringAttr(str));
module.push_back(globalStr);
Declaring printf
auto printfType = mlir::LLVM::LLVMFunctionType::get(i8PtrType, {i8PtrType}, true);
auto printfFunc = builder.create<mlir::LLVM::LLVMFuncOp>(
builder.getUnknownLoc(), "printf", printfType);
module.push_back(printfFunc);
Creating the main Function
Now, we define the main function, retrieve the address of our global string, and prepare it for printing.
auto int32Ty = builder.getI32Type();
auto funcType = LLVM::LLVMFunctionType::get(int32Ty, {int32Ty});
auto funcOp = builder.create<mlir::LLVM::LLVMFuncOp>(
builder.getUnknownLoc(), "main", funcType);
module.push_back(funcOp);
auto block = funcOp.addEntryBlock(builder);
builder.setInsertionPointToStart(block);
// Get pointer to string
auto zero = builder.create<mlir::LLVM::ConstantOp>(
builder.getUnknownLoc(), int32Ty, builder.getI32IntegerAttr(0));
auto strPtr = builder.create<mlir::LLVM::AddressOfOp>(
builder.getUnknownLoc(), globalStr);
auto gep = builder.create<mlir::LLVM::GEPOp>(
builder.getUnknownLoc(), i8PtrType, arrayType, strPtr,
mlir::ValueRange{zero, zero});
Here, we create the main function, allocate a block, and generate a pointer (gep) to access our string.
Emitting OpenMP Parallel Region
We now insert an OpenMP parallel block that calls printf:
mlir::Location dummyLoc = mlir::FileLineColLoc::get(
builder.getStringAttr("dummy.mlir"), 0, 0);
auto parallelOp = builder.create<mlir::omp::ParallelOp>(dummyLoc);
auto ¶llelRegion = parallelOp.getRegion();
auto *parallelBlock = builder.createBlock(¶llelRegion);
builder.setInsertionPointToStart(parallelBlock);
// Call printf inside parallel region
builder.create<mlir::LLVM::CallOp>(
builder.getUnknownLoc(), printfFunc, mlir::ValueRange{gep});
builder.create<mlir::omp::TerminatorOp>(builder.getUnknownLoc());
builder.setInsertionPointAfter(parallelOp);
This OpenMP parallel region ensures that multiple threads execute printf.
Finalizing the Function
We conclude by returning 33 as the exit status.
auto constOp = builder.create<mlir::LLVM::ConstantOp>(
builder.getUnknownLoc(), int32Ty, builder.getI32IntegerAttr(33));
auto retOp = builder.create<mlir::LLVM::ReturnOp>(
builder.getUnknownLoc(), constOp->getResult(0));
Before proceeding, we verify the MLIR module:
if (failed(verify(module))) {
llvm::errs() << "Error: Verification Failed\n";
return 0;
}
Lowering to LLVM IR
To generate LLVM IR, we apply the OpenMP-to-LLVM conversion pass:
PassManager pm(&context);
pm.addPass(createConvertOpenMPToLLVMPass());
if (failed(pm.run(module))) {
llvm::errs() << "Failed to run passes\n";
return 1;
}
module->dump();
Translating MLIR to LLVM IR
mlir-translate --mlir-to-llvmir output.mlir -o output.ll
Compiling and Running the Executable
clang output.ll -o a.out -fopenmp
./a.out
Expected output:
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
The number of lines printed depends on the system’s OpenMP thread count.
Conclusion
We successfully:
- Emitted OpenMP code in MLIR.
- Lowered it to LLVM IR.
- Compiled and executed the OpenMP-enabled program.
MLIR’s OpenMP dialect makes it easy to integrate parallel execution while maintaining flexibility for optimizations and transformations.
The complete code is available in our GitHub repository: Blog CodeBase Repo.
메타데이터
- post_id
- dd725d529005
- slug
- emitting-openmp-code-from-mlir-and-lowering-to-llvm-ir-dd725d529005
- url
- https://medium.com/@60b36t/emitting-openmp-code-from-mlir-and-lowering-to-llvm-ir-dd725d529005
- canonical_url
- https://medium.com/@60b36t/emitting-openmp-code-from-mlir-and-lowering-to-llvm-ir-dd725d529005
- author_url
- https://medium.com/@60b36t
- status
- ok
- fetched_at
- 2026-06-20 20:29:01