[flang-commits] [flang] [flang][mlir] Add flang to mlir lowering for groupprivate (PR #180934)
via flang-commits
flang-commits at lists.llvm.org
Mon May 4 02:16:00 PDT 2026
https://github.com/skc7 updated https://github.com/llvm/llvm-project/pull/180934
>From a297558ef7707850717897e36ded05f4304d7e3f Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Wed, 11 Feb 2026 18:24:52 +0530
Subject: [PATCH] [flang][mlir] Add flang to mlir lowering for groupprivate
---
flang/include/flang/Lower/OpenMP.h | 1 +
flang/lib/Lower/OpenMP/OpenMP.cpp | 124 +++++++++++-
flang/test/Lower/OpenMP/Todo/groupprivate.f90 | 9 -
flang/test/Lower/OpenMP/groupprivate.f90 | 186 ++++++++++++++++++
4 files changed, 310 insertions(+), 10 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/Todo/groupprivate.f90
create mode 100644 flang/test/Lower/OpenMP/groupprivate.f90
diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index 852a120c3782c..0cc2c6287aac7 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -81,6 +81,7 @@ void genOpenMPSymbolProperties(AbstractConverter &converter,
const pft::Variable &var);
void genThreadprivateOp(AbstractConverter &, const pft::Variable &);
+void genGroupprivateOp(AbstractConverter &, const pft::Variable &);
void genDeclareTargetIntGlobal(AbstractConverter &, const pft::Variable &);
bool isOpenMPTargetConstruct(const parser::OpenMPConstruct &);
bool isOpenMPDeviceDeclareTarget(Fortran::lower::AbstractConverter &,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 88d28cf94b045..50902505cae61 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -693,6 +693,89 @@ static void threadPrivatizeVars(lower::AbstractConverter &converter,
}
}
+static void groupprivatizeVars(lower::AbstractConverter &converter,
+ lower::pft::Evaluation &eval) {
+ fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+ mlir::Location currentLocation = converter.getCurrentLocation();
+ mlir::OpBuilder::InsertionGuard guard(firOpBuilder);
+ firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock());
+
+ auto module = converter.getModuleOp();
+
+ // Create a groupprivate operation for the symbol.
+ // TODO: Extract device_type from the groupprivate directive.
+ auto genGroupprivateOp = [&](const semantics::Symbol &sym) -> mlir::Value {
+ std::string globalName = converter.mangleName(sym);
+ fir::GlobalOp global = module.lookupSymbol<fir::GlobalOp>(globalName);
+ if (!global) {
+ return mlir::Value();
+ }
+
+ mlir::omp::DeclareTargetDeviceType deviceTypeEnum =
+ mlir::omp::DeclareTargetDeviceType::any;
+ mlir::omp::DeclareTargetDeviceTypeAttr deviceTypeAttr =
+ mlir::omp::DeclareTargetDeviceTypeAttr::get(firOpBuilder.getContext(),
+ deviceTypeEnum);
+
+ // omp.groupprivate takes a flat symbol reference and returns
+ // the address of the per-team copy of the global variable.
+ return mlir::omp::GroupprivateOp::create(
+ firOpBuilder, currentLocation, global.resultType(), global.getSymbol(),
+ deviceTypeAttr);
+ };
+
+ llvm::SetVector<const semantics::Symbol *> groupprivateSyms;
+ converter.collectSymbolSet(eval, groupprivateSyms,
+ semantics::Symbol::Flag::OmpGroupPrivate,
+ /*collectSymbols=*/true,
+ /*collectHostAssociatedSymbols=*/true);
+ std::set<semantics::SourceName> groupprivateSymNames;
+
+ // For a COMMON block, the GroupprivateOp is generated for the block itself
+ // instead of its members.
+ llvm::SetVector<const semantics::Symbol *> commonSyms;
+
+ for (std::size_t i = 0; i < groupprivateSyms.size(); i++) {
+ const semantics::Symbol *sym = groupprivateSyms[i];
+ mlir::Value symGroupprivateValue;
+ // The variable may be used more than once, and each reference has one
+ // symbol with the same name. Only do once for references of one variable.
+ if (groupprivateSymNames.find(sym->name()) != groupprivateSymNames.end())
+ continue;
+ groupprivateSymNames.insert(sym->name());
+
+ if (const semantics::Symbol *common =
+ semantics::FindCommonBlockContaining(sym->GetUltimate())) {
+ // Handle common block members: create groupprivate op for the entire
+ // common block, then compute member offset.
+ mlir::Value commonGroupprivateValue;
+ if (commonSyms.contains(common)) {
+ commonGroupprivateValue = converter.getSymbolAddress(*common);
+ } else {
+ commonGroupprivateValue = genGroupprivateOp(*common);
+ if (!commonGroupprivateValue)
+ continue;
+ converter.bindSymbol(*common, commonGroupprivateValue);
+ commonSyms.insert(common);
+ }
+ symGroupprivateValue = lower::genCommonBlockMember(
+ converter, currentLocation, sym->GetUltimate(),
+ commonGroupprivateValue, common->size());
+ } else {
+ symGroupprivateValue = genGroupprivateOp(*sym);
+ }
+
+ if (!symGroupprivateValue) {
+ continue;
+ }
+
+ fir::ExtendedValue sexv = converter.getSymbolExtendedValue(*sym);
+ fir::ExtendedValue symGroupprivateExv =
+ getExtendedValue(sexv, symGroupprivateValue);
+ converter.bindSymbol(*sym, symGroupprivateExv);
+ }
+}
+
static mlir::Operation *setLoopVar(lower::AbstractConverter &converter,
mlir::Location loc, mlir::Value indexVal,
const semantics::Symbol *sym) {
@@ -1248,6 +1331,10 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
}
}
+ if (info.dir == llvm::omp::Directive::OMPD_teams) {
+ groupprivatizeVars(info.converter, info.eval);
+ }
+
if (!info.genSkeletonOnly) {
if (ConstructQueue::const_iterator next = std::next(item);
next != queue.end()) {
@@ -2918,6 +3005,11 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
!symbolsWithDynamicSubstring.contains(&sym.GetUltimate()))
return;
+ // Skip groupprivate symbols - they don't need to be mapped because
+ // groupprivate creates its own LDS storage.
+ if (sym.GetUltimate().test(semantics::Symbol::Flag::OmpGroupPrivate))
+ return;
+
if (!isDuplicateMappedSymbol(sym, dsp.getAllSymbolsToPrivatize(),
hasDeviceAddrSyms, mapSyms, isDevicePtrSyms)) {
if (const auto *details =
@@ -4392,7 +4484,8 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
lower::pft::Evaluation &eval,
const parser::OpenMPGroupprivate &directive) {
- TODO(converter.getCurrentLocation(), "GROUPPRIVATE");
+ // The groupprivate directive is lowered when the variable is referenced
+ // inside target/teams regions.
}
static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
@@ -4806,6 +4899,9 @@ void Fortran::lower::genOpenMPSymbolProperties(
if (sym.test(semantics::Symbol::Flag::OmpDeclareTarget))
lower::genDeclareTargetIntGlobal(converter, var);
+
+ if (sym.test(semantics::Symbol::Flag::OmpGroupPrivate))
+ lower::genGroupprivateOp(converter, var);
}
void Fortran::lower::genThreadprivateOp(lower::AbstractConverter &converter,
@@ -4874,6 +4970,32 @@ void Fortran::lower::genThreadprivateOp(lower::AbstractConverter &converter,
converter.bindSymbol(sym, symThreadprivateExv);
}
+void Fortran::lower::genGroupprivateOp(lower::AbstractConverter &converter,
+ const lower::pft::Variable &var) {
+ const semantics::Symbol &sym = var.getSymbol();
+
+ // For common block members, the groupprivate op is generated for the entire
+ // common block in groupprivatizeVars, not for individual members here.
+ // The common block already has a global, so nothing to do here.
+ if (semantics::FindCommonBlockContaining(sym.GetUltimate()))
+ return;
+
+ // Handle non-global variables: local variables with the SAVE attribute can
+ // appear in a groupprivate directive. Promote them to fir.global so that
+ // omp.groupprivate can reference them by symbol name.
+ if (!var.isGlobal()) {
+ fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+ mlir::Location currentLocation = converter.getCurrentLocation();
+ auto module = converter.getModuleOp();
+ std::string globalName = converter.mangleName(sym);
+ if (!module.lookupSymbol<fir::GlobalOp>(globalName))
+ globalInitialization(converter, firOpBuilder, sym, var, currentLocation);
+ }
+
+ // The actual omp.groupprivate operation is created by groupprivatizeVars
+ // when entering a teams region.
+}
+
// This function replicates threadprivate's behaviour of generating
// an internal fir.GlobalOp for non-global variables in the main program
// that have the implicit SAVE attribute, to simplifiy LLVM-IR and MLIR
diff --git a/flang/test/Lower/OpenMP/Todo/groupprivate.f90 b/flang/test/Lower/OpenMP/Todo/groupprivate.f90
deleted file mode 100644
index 9ad9b9382760c..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/groupprivate.f90
+++ /dev/null
@@ -1,9 +0,0 @@
-!RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=60 -o - %s 2>&1 | FileCheck %s
-
-!CHECK: not yet implemented: GROUPPRIVATE
-
-module m
-implicit none
-integer :: x
-!$omp groupprivate(x)
-end module
diff --git a/flang/test/Lower/OpenMP/groupprivate.f90 b/flang/test/Lower/OpenMP/groupprivate.f90
new file mode 100644
index 0000000000000..9d3809b2a2999
--- /dev/null
+++ b/flang/test/Lower/OpenMP/groupprivate.f90
@@ -0,0 +1,186 @@
+!RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=60 %s -o - | FileCheck %s
+
+! Test lowering of groupprivate directive to omp.groupprivate.
+
+! CHECK-DAG: fir.global common @blk_
+! CHECK-DAG: fir.global common @blka_
+! CHECK-DAG: fir.global common @blkb_
+
+! Test 1: Basic groupprivate with single module variable.
+module m
+ implicit none
+ integer, save :: x
+ !$omp groupprivate(x)
+end module
+
+! CHECK-LABEL: func.func @_QPtest_groupprivate
+! CHECK: omp.target
+! CHECK: omp.teams
+! CHECK: %{{.*}} = omp.groupprivate @_QMmEx device_type (any) : !fir.ref<i32>
+subroutine test_groupprivate()
+ use m
+
+ !$omp target
+ !$omp teams
+ x = 10
+ !$omp end teams
+ !$omp end target
+end subroutine
+
+! Test 2: Groupprivate with common block.
+module m2
+ implicit none
+ integer :: cb_x, cb_y
+ real :: cb_z
+ common /blk/ cb_x, cb_y, cb_z
+ !$omp groupprivate(/blk/)
+end module
+
+! CHECK-LABEL: func.func @_QPtest_common_block_groupprivate
+! CHECK: omp.target
+! CHECK: omp.teams
+! CHECK: %{{.*}} = omp.groupprivate @blk_ device_type (any) : !fir.ref<!fir.array<12xi8>>
+! CHECK: fir.coordinate_of
+! CHECK: fir.convert
+subroutine test_common_block_groupprivate()
+ use m2
+
+ !$omp target
+ !$omp teams
+ cb_x = 1
+ cb_y = 2
+ cb_z = 3.0
+ !$omp end teams
+ !$omp end target
+end subroutine
+
+! Test 3: Local SAVE variable promoted to fir.global by globalInitialization.
+! CHECK-LABEL: func.func @_QPtest_local_save_groupprivate
+! CHECK: omp.teams
+! CHECK: %{{.*}} = omp.groupprivate @_QFtest_local_save_groupprivateElocal_x device_type (any) : !fir.ref<i32>
+subroutine test_local_save_groupprivate()
+ integer, save :: local_x
+ !$omp groupprivate(local_x)
+
+ !$omp teams
+ local_x = 42
+ !$omp end teams
+end subroutine
+
+! Test 4: Multiple groupprivate variables in same teams region.
+module m_multi
+ implicit none
+ integer, save :: gp_a
+ integer, save :: gp_b
+ real, save :: gp_c
+ !$omp groupprivate(gp_a, gp_b, gp_c)
+end module
+
+! CHECK-LABEL: func.func @_QPtest_multiple_groupprivate
+! CHECK: omp.target
+! CHECK: omp.teams
+! CHECK-DAG: omp.groupprivate @_QMm_multiEgp_a
+! CHECK-DAG: omp.groupprivate @_QMm_multiEgp_b
+! CHECK-DAG: omp.groupprivate @_QMm_multiEgp_c
+subroutine test_multiple_groupprivate()
+ use m_multi
+
+ !$omp target
+ !$omp teams
+ gp_a = 1
+ gp_b = 2
+ gp_c = 3.0
+ !$omp end teams
+ !$omp end target
+end subroutine
+
+! Test 5: Same variable referenced multiple times produces only one op.
+! CHECK-LABEL: func.func @_QPtest_repeated_ref_groupprivate
+! CHECK: omp.teams
+! CHECK: omp.groupprivate @_QMmEx
+! CHECK-NOT: omp.groupprivate @_QMmEx
+! CHECK: omp.terminator
+subroutine test_repeated_ref_groupprivate()
+ use m
+
+ !$omp target
+ !$omp teams
+ x = 10
+ x = x + 5
+ x = x * 2
+ !$omp end teams
+ !$omp end target
+end subroutine
+
+! Test 6: Standalone teams (no enclosing target) still triggers groupprivate.
+! CHECK-LABEL: func.func @_QPtest_standalone_teams_groupprivate
+! CHECK-NOT: omp.target
+! CHECK: omp.teams
+! CHECK: %{{.*}} = omp.groupprivate @_QMmEx device_type (any) : !fir.ref<i32>
+subroutine test_standalone_teams_groupprivate()
+ use m
+
+ !$omp teams
+ x = 100
+ !$omp end teams
+end subroutine
+
+! Test 7: Groupprivate variable is not added to target's implicit map_entries.
+! CHECK-LABEL: func.func @_QPtest_target_skip_map_groupprivate
+! CHECK-NOT: omp.map.info {{.*}}@_QMmEx
+! CHECK: omp.target
+! CHECK: omp.teams
+! CHECK: omp.groupprivate @_QMmEx
+subroutine test_target_skip_map_groupprivate()
+ use m
+
+ !$omp target
+ !$omp teams
+ x = 200
+ !$omp end teams
+ !$omp end target
+end subroutine
+
+! Test 8: Groupprivate works with various element types (scalar, array, real).
+module m_types
+ implicit none
+ real(8), save :: gp_r8
+ integer, save :: gp_iarr(4)
+ !$omp groupprivate(gp_r8, gp_iarr)
+end module
+
+! CHECK-LABEL: func.func @_QPtest_types_groupprivate
+! CHECK: omp.teams
+! CHECK-DAG: omp.groupprivate @_QMm_typesEgp_r8 device_type (any) : !fir.ref<f64>
+! CHECK-DAG: omp.groupprivate @_QMm_typesEgp_iarr device_type (any) : !fir.ref<!fir.array<4xi32>>
+subroutine test_types_groupprivate()
+ use m_types
+
+ !$omp teams
+ gp_r8 = 1.0d0
+ gp_iarr(1) = 99
+ !$omp end teams
+end subroutine
+
+! Test 9: Multiple distinct common blocks each get their own omp.groupprivate.
+module m_blocks
+ implicit none
+ integer :: a1, a2
+ integer :: b1, b2
+ common /blka/ a1, a2
+ common /blkb/ b1, b2
+ !$omp groupprivate(/blka/, /blkb/)
+end module
+
+! CHECK-LABEL: func.func @_QPtest_multi_common_groupprivate
+! CHECK: omp.teams
+! CHECK-DAG: omp.groupprivate @blka_ device_type (any)
+! CHECK-DAG: omp.groupprivate @blkb_ device_type (any)
+subroutine test_multi_common_groupprivate()
+ use m_blocks
+
+ !$omp teams
+ a1 = 1
+ b1 = 2
+ !$omp end teams
+end subroutine
More information about the flang-commits
mailing list