[llvm-branch-commits] [flang] [Flang][OpenMP] PoC module support for allocate directives (PR #216022)
Sergio Afonso via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Aug 13 05:16:19 PDT 2026
https://github.com/skatrak updated https://github.com/llvm/llvm-project/pull/216022
>From 938319b016544f6d21c679591598b89494f542fe Mon Sep 17 00:00:00 2001
From: Sergio Afonso <Sergio.AfonsoFumero at amd.com>
Date: Wed, 12 Aug 2026 16:54:56 +0100
Subject: [PATCH 1/2] [Flang][OpenMP] PoC module support for allocate
directives
This patch implements partial support for `allocate` on Fortran
module variables, based on adding global constructor functions for each
impacted variable.
Shared as a proof of concept, because I have a few concerns about it:
1. It appears that Clang ignores `allocate` directives on global
variables instead. Is that the expected behavior?
2. The existing implementation for `allocate` in Flang doesn't
actually impact where the memory used for a variable resides. It
allocates/deallocates extra memory for it using OpenMP internal
compiler calls but then that storage is never used. The original
alloca is still used. This addition suffers from the same issue:
global constructors allocate extra memory that is never used to
update in any way the associated global variable or its users.
3. No `omp.allocate_free` (should be `omp.allocate.free`) can be added
by this approach.
4. The representation of `omp.allocate_dir` (should be `omp.allocate`)
doesn't seem prepared to actually allow the new value to be used by
other operations, as it returns no values.
I think that, before even thinking about adding module support for this
directive (if ignoring it isn't what the spec prescribes), proper
end-to-end basic support for it should be implemented first. What it
currently does is miscompiling and silently ignoring it rather than
warning the user about this directive being unimplemented.
Assisted-by: Claude Opus 4.6
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 137 ++++++++++++++----
.../omp-declarative-allocate-module.f90 | 42 ++++++
2 files changed, 151 insertions(+), 28 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/omp-declarative-allocate-module.f90
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index fad704cb90895..832a87a5c774c 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1839,6 +1839,45 @@ markDeclareTarget(mlir::Operation *op, lower::AbstractConverter &converter,
declareTargetOp.setDeclareTarget(deviceType, captureClause, automap);
}
+/// Register \c ctorFunc to run at program startup by creating or extending
+/// the module's \c llvm.mlir.global_ctors.
+static void registerGlobalConstructor(lower::AbstractConverter &converter,
+ mlir::LLVM::LLVMFuncOp ctorFunc) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ mlir::ModuleOp mod = builder.getModule();
+ mlir::MLIRContext *ctx = mod.getContext();
+
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ mlir::LLVM::GlobalCtorsOp existing;
+ mod.walk([&](mlir::LLVM::GlobalCtorsOp op) { existing = op; });
+
+ llvm::SmallVector<mlir::Attribute> ctors;
+ llvm::SmallVector<int32_t> priorities;
+ llvm::SmallVector<mlir::Attribute> data;
+ if (existing) {
+ ctors.assign(existing.getCtors().begin(), existing.getCtors().end());
+ for (mlir::Attribute p : existing.getPriorities())
+ priorities.push_back(llvm::cast<mlir::IntegerAttr>(p).getInt());
+ data.assign(existing.getData().begin(), existing.getData().end());
+ }
+
+ ctors.push_back(mlir::FlatSymbolRefAttr::get(ctx, ctorFunc.getSymName()));
+ priorities.push_back(0);
+ data.push_back(mlir::LLVM::ZeroAttr::get(ctx));
+
+ if (existing)
+ builder.setInsertionPoint(existing);
+ else
+ builder.setInsertionPointToEnd(mod.getBody());
+
+ mlir::LLVM::GlobalCtorsOp::create(
+ builder, ctorFunc.getLoc(), builder.getArrayAttr(ctors),
+ builder.getI32ArrayAttr(priorities), builder.getArrayAttr(data));
+
+ if (existing)
+ existing.erase();
+}
+
//===----------------------------------------------------------------------===//
// Op body generation helper structures and functions
//===----------------------------------------------------------------------===//
@@ -2981,27 +3020,25 @@ static void genWsloopClauses(
//===----------------------------------------------------------------------===//
// Code generation functions for leaf constructs
//===----------------------------------------------------------------------===//
-static mlir::omp::AllocateDirOp genAllocateDirOp(
- lower::AbstractConverter &converter, semantics::SemanticsContext &semaCtx,
- lower::StatementContext &stmtCtx, lower::pft::Evaluation &eval,
- mlir::Location loc, const ObjectList &objects, const ConstructQueue &queue,
- ConstructQueue::const_iterator item) {
- llvm::SmallVector<mlir::Value> operandRange;
- mlir::omp::AllocateDirOperands clauseOps;
- genAllocateClauses(converter, semaCtx, stmtCtx, objects, item->clauses, loc,
- operandRange, clauseOps);
+static mlir::omp::AllocateDirOp
+genAllocateDirOp(lower::AbstractConverter &converter, mlir::Location loc,
+ llvm::ArrayRef<mlir::Value> operandRange,
+ const mlir::omp::AllocateDirOperands &clauseOps, bool genFree) {
auto allocDirOp = mlir::omp::AllocateDirOp::create(
converter.getFirOpBuilder(), loc, operandRange, clauseOps.align,
clauseOps.allocator);
- // Register a cleanup at the Fortran scope exit.
- fir::FirOpBuilder *builder = &converter.getFirOpBuilder();
- mlir::Value allocator = clauseOps.allocator;
- converter.getFctCtx().attachCleanup([builder, loc, operandRange,
- allocator]() {
- mlir::omp::AllocateFreeOp::create(*builder, loc, operandRange, allocator);
- });
+ if (genFree) {
+ // Register a cleanup at the Fortran scope exit.
+ fir::FirOpBuilder *builder = &converter.getFirOpBuilder();
+ mlir::Value allocator = clauseOps.allocator;
+ llvm::SmallVector<mlir::Value> operands(operandRange.begin(),
+ operandRange.end());
+ converter.getFctCtx().attachCleanup([builder, loc, operands, allocator]() {
+ mlir::omp::AllocateFreeOp::create(*builder, loc, operands, allocator);
+ });
+ }
return allocDirOp;
}
@@ -5788,27 +5825,71 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
lower::pft::Evaluation &eval,
const parser::OmpAllocateDirective &allocate) {
- // The allocate directive is lowered as a runtime allocation with a matching
- // deallocation registered as a cleanup at the exit of the enclosing function
- // scope, which only works within a function. In the case of e.g. modules,
- // there is no place in which to emit the deallocation cleanup when that stage
- // is reached, crashing the compiler during teardown.
- if (!converter.getFirOpBuilder().getFunction())
- TODO(converter.genLocation(allocate.source),
- "OpenMP ALLOCATE directive in non-function declaration scope");
-
lower::StatementContext stmtCtx;
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
ObjectList objects = makeObjects((allocate.BeginDir().Arguments()), semaCtx);
const auto &clauseList = (allocate.BeginDir().Clauses());
List<Clause> clauses = makeClauses(clauseList, semaCtx);
mlir::Location loc = converter.genLocation(allocate.source);
ConstructQueue queue{buildConstructQueue(
- converter.getFirOpBuilder().getModule(), semaCtx, eval, allocate.source,
+ builder.getModule(), semaCtx, eval, allocate.source,
llvm::omp::Directive::OMPD_allocate, clauses)};
+ ConstructQueue::const_iterator item = queue.begin();
+
+ // In a module (or submodule) declaration scope the list items are fir.global
+ // variables. There is no enclosing function whose exit could host the runtime
+ // deallocation, and the runtime allocation cannot live in the constant
+ // fir.global initializer, so emit it (without a matching free) into a global
+ // constructor per variable that runs at program startup.
+ // TODO: SAVE variables, COMMON blocks.
+ if (converter.getCurrentScope().kind() == semantics::Scope::Kind::Module) {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ mlir::MLIRContext *ctx = builder.getContext();
- genAllocateDirOp(converter, semaCtx, stmtCtx, eval, loc, objects, queue,
- queue.begin());
+ for (const Object &object : objects) {
+ const semantics::Symbol &sym = *object.sym();
+ std::string ctorName =
+ converter.mangleName(sym.GetUltimate()) + "_omp_allocate_ctor";
+ builder.setInsertionPointToEnd(builder.getModule().getBody());
+ auto ctorFunc = mlir::LLVM::LLVMFuncOp::create(
+ builder, loc, ctorName,
+ mlir::LLVM::LLVMFunctionType::get(mlir::LLVM::LLVMVoidType::get(ctx),
+ {}));
+ ctorFunc.setLinkage(mlir::LLVM::Linkage::Internal);
+ builder.setInsertionPointToStart(ctorFunc.addEntryBlock(builder));
+
+ // Resolve the operand through the existing fir.global.
+ fir::GlobalOp global =
+ builder.getNamedGlobal(converter.mangleName(sym.GetUltimate()));
+ assert(global && "expected a global for a module variable");
+
+ llvm::SmallVector<mlir::Value, 1> operandRange{fir::AddrOfOp::create(
+ builder, loc, global.resultType(), global.getSymbol())};
+
+ // The empty object list leaves genAllocateClauses to only process the
+ // clauses; the addresses were resolved above.
+ mlir::omp::AllocateDirOperands clauseOps;
+ genAllocateClauses(converter, semaCtx, stmtCtx, /*objects=*/ObjectList{},
+ item->clauses, loc, operandRange, clauseOps);
+ genAllocateDirOp(converter, loc, operandRange, clauseOps,
+ /*genFree=*/false);
+ mlir::LLVM::ReturnOp::create(builder, loc, mlir::ValueRange{});
+
+ registerGlobalConstructor(converter, ctorFunc);
+ }
+
+ return;
+ } else if (!converter.getFirOpBuilder().getFunction()) {
+ TODO(converter.getCurrentLocation(), "non-function allocate directive");
+ }
+
+ llvm::SmallVector<mlir::Value> operandRange;
+ mlir::omp::AllocateDirOperands clauseOps;
+ genAllocateClauses(converter, semaCtx, stmtCtx, objects, item->clauses, loc,
+ operandRange, clauseOps);
+
+ genAllocateDirOp(converter, loc, operandRange, clauseOps, /*genFree=*/true);
}
static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/omp-declarative-allocate-module.f90 b/flang/test/Lower/OpenMP/omp-declarative-allocate-module.f90
new file mode 100644
index 0000000000000..214bd647d00cb
--- /dev/null
+++ b/flang/test/Lower/OpenMP/omp-declarative-allocate-module.f90
@@ -0,0 +1,42 @@
+! This test checks lowering of an OpenMP allocate Directive that appears in a
+! module or submodule declaration scope.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+module mymod
+ implicit none
+ integer :: x
+ real :: y
+ !$omp allocate(x, y)
+end module mymod
+
+submodule (mymod) mysub
+ implicit none
+ integer :: z
+ !$omp allocate(z)
+end submodule mysub
+
+! CHECK: fir.global @_QMmymodEx : i32
+! CHECK: fir.global @_QMmymodEy : f32
+
+! CHECK: llvm.func internal @_QMmymodEx_omp_allocate_ctor() {
+! CHECK: %[[X:.*]] = fir.address_of(@_QMmymodEx) : !fir.ref<i32>
+! CHECK: omp.allocate_dir(%[[X]] : !fir.ref<i32>)
+! CHECK: llvm.return
+! CHECK: }
+
+! CHECK: llvm.mlir.global_ctors ctors = [@_QMmymodEx_omp_allocate_ctor, @_QMmymodEy_omp_allocate_ctor, @_QMmymodSmysubEz_omp_allocate_ctor]
+
+! CHECK: llvm.func internal @_QMmymodEy_omp_allocate_ctor() {
+! CHECK: %[[Y:.*]] = fir.address_of(@_QMmymodEy) : !fir.ref<f32>
+! CHECK: omp.allocate_dir(%[[Y]] : !fir.ref<f32>)
+! CHECK: llvm.return
+! CHECK: }
+
+! CHECK: fir.global @_QMmymodSmysubEz : i32
+
+! CHECK: llvm.func internal @_QMmymodSmysubEz_omp_allocate_ctor() {
+! CHECK: %[[Z:.*]] = fir.address_of(@_QMmymodSmysubEz) : !fir.ref<i32>
+! CHECK: omp.allocate_dir(%[[Z]] : !fir.ref<i32>)
+! CHECK: llvm.return
+! CHECK: }
>From 71f0599300a4eb30492319f41deb395fe085c4ba Mon Sep 17 00:00:00 2001
From: Sergio Afonso <Sergio.AfonsoFumero at amd.com>
Date: Thu, 13 Aug 2026 13:16:04 +0100
Subject: [PATCH 2/2] format
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 832a87a5c774c..1d566db54f213 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3024,7 +3024,8 @@ static void genWsloopClauses(
static mlir::omp::AllocateDirOp
genAllocateDirOp(lower::AbstractConverter &converter, mlir::Location loc,
llvm::ArrayRef<mlir::Value> operandRange,
- const mlir::omp::AllocateDirOperands &clauseOps, bool genFree) {
+ const mlir::omp::AllocateDirOperands &clauseOps,
+ bool genFree) {
auto allocDirOp = mlir::omp::AllocateDirOp::create(
converter.getFirOpBuilder(), loc, operandRange, clauseOps.align,
clauseOps.allocator);
@@ -5832,9 +5833,9 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
List<Clause> clauses = makeClauses(clauseList, semaCtx);
mlir::Location loc = converter.genLocation(allocate.source);
- ConstructQueue queue{buildConstructQueue(
- builder.getModule(), semaCtx, eval, allocate.source,
- llvm::omp::Directive::OMPD_allocate, clauses)};
+ ConstructQueue queue{
+ buildConstructQueue(builder.getModule(), semaCtx, eval, allocate.source,
+ llvm::omp::Directive::OMPD_allocate, clauses)};
ConstructQueue::const_iterator item = queue.begin();
// In a module (or submodule) declaration scope the list items are fir.global
More information about the llvm-branch-commits
mailing list