[flang-commits] [flang] [flang][mlir] Add flang to mlir lowering for groupprivate (PR #180934)

via flang-commits flang-commits at lists.llvm.org
Thu Jun 11 23:33:25 PDT 2026


https://github.com/skc7 updated https://github.com/llvm/llvm-project/pull/180934

>From 654b3a3cfd769da3f46e1522228e1389609b0a4c 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 01/12] [flang][mlir] Add flang to mlir lowering for
 groupprivate

---
 flang/include/flang/Lower/OpenMP.h            |   1 +
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 126 +++++++++++-
 flang/test/Lower/OpenMP/Todo/groupprivate.f90 |   9 -
 flang/test/Lower/OpenMP/groupprivate.f90      | 186 ++++++++++++++++++
 4 files changed, 311 insertions(+), 11 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 60b7fdc0e5322..22712ee4c3bf3 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -783,6 +783,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) {
@@ -1342,6 +1425,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()) {
@@ -3030,6 +3117,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(),
                                  hasDeviceAddrObjects, mapObjects,
                                  isDevicePtrObjects)) {
@@ -4627,8 +4719,9 @@ genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
-                   const parser::OmpGroupprivateDirective &directive) {
-  TODO(converter.getCurrentLocation(), "GROUPPRIVATE");
+                   const parser::OpenMPGroupprivate &directive) {
+  // The groupprivate directive is lowered when the variable is referenced
+  // inside target/teams regions.
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
@@ -5393,6 +5486,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,
@@ -5461,6 +5557,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

>From 975ad58f1e8f0d6e2de33bc7f146bc4cc642d0e7 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Mon, 4 May 2026 15:26:33 +0530
Subject: [PATCH 02/12] support device_type groupprivate lowering

---
 flang/include/flang/Lower/AbstractConverter.h | 11 ++++++++
 flang/include/flang/Lower/OpenMP.h            | 11 ++++++++
 flang/lib/Lower/Bridge.cpp                    | 11 ++++++++
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 25 ++++++++++++++++---
 flang/test/Lower/OpenMP/groupprivate.f90      | 22 ++++++++++++++++
 5 files changed, 77 insertions(+), 3 deletions(-)

diff --git a/flang/include/flang/Lower/AbstractConverter.h b/flang/include/flang/Lower/AbstractConverter.h
index 1b19807bff0cf..db3ad4411508c 100644
--- a/flang/include/flang/Lower/AbstractConverter.h
+++ b/flang/include/flang/Lower/AbstractConverter.h
@@ -77,6 +77,10 @@ class StatementContext;
 
 using ExprToValueMap = llvm::DenseMap<const SomeExpr *, mlir::Value>;
 
+/// Forward declaration only; the full definition lives in
+/// `flang/Lower/OpenMP.h`.
+struct OMPGroupprivateDeviceTypeInfo;
+
 //===----------------------------------------------------------------------===//
 // AbstractConverter interface
 //===----------------------------------------------------------------------===//
@@ -401,6 +405,13 @@ class AbstractConverter {
 
   virtual mlir::StateStack &getStateStack() = 0;
 
+  /// Return the per-converter table recording the device_type clause
+  /// associated with each groupprivate symbol. Used to communicate state from
+  /// the lowering of a `!$omp groupprivate` directive to the later creation of
+  /// `omp.groupprivate` operations inside teams regions. The returned object
+  /// is fully defined in `flang/Lower/OpenMP.h`.
+  virtual OMPGroupprivateDeviceTypeInfo &getOMPGroupprivateDeviceTypeInfo() = 0;
+
 private:
   /// Options controlling lowering behavior.
   const Fortran::lower::LoweringOptions &loweringOptions;
diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index 0cc2c6287aac7..992546024fc50 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -13,6 +13,7 @@
 #ifndef FORTRAN_LOWER_OPENMP_H
 #define FORTRAN_LOWER_OPENMP_H
 
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallVector.h"
 
 #include <cinttypes>
@@ -61,6 +62,16 @@ struct OMPDeferredDeclareTargetInfo {
   const Fortran::semantics::Symbol &sym;
 };
 
+/// Per-converter table that records the `device_type` modifier associated with
+/// each groupprivate symbol on the `!$omp groupprivate` directive that
+/// declared it. Populated when the directive is lowered and consumed when
+/// `omp.groupprivate` operations are emitted inside teams regions.
+struct OMPGroupprivateDeviceTypeInfo {
+  llvm::DenseMap<const Fortran::semantics::Symbol *,
+                 mlir::omp::DeclareTargetDeviceType>
+      map;
+};
+
 // Generate the OpenMP terminator for Operation at Location.
 mlir::Operation *genOpenMPTerminator(fir::FirOpBuilder &, mlir::Operation *,
                                      mlir::Location);
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 78f9de9c9420e..8f93a4c23659c 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1432,6 +1432,11 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
   mlir::StateStack &getStateStack() override { return stateStack; }
 
+  Fortran::lower::OMPGroupprivateDeviceTypeInfo &
+  getOMPGroupprivateDeviceTypeInfo() override {
+    return ompGroupprivateDeviceTypeInfo;
+  }
+
   /// Add the symbol to the local map and return `true`. If the symbol is
   /// already in the map and \p forced is `false`, the map is not updated.
   /// Instead the value `false` is returned.
@@ -7026,6 +7031,12 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   llvm::SmallVector<Fortran::lower::OMPDeferredDeclareTargetInfo>
       ompDeferredDeclareTarget;
 
+  /// Per-converter table recording the `device_type` clause for each symbol
+  /// declared in a `!$omp groupprivate` directive. Populated when the
+  /// directive is lowered and consumed when `omp.groupprivate` ops are emitted
+  /// inside teams regions.
+  Fortran::lower::OMPGroupprivateDeviceTypeInfo ompGroupprivateDeviceTypeInfo;
+
   const Fortran::lower::ExprToValueMap *exprValueOverrides{nullptr};
 
   /// Stack of derived type under construction to avoid infinite loops when
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 22712ee4c3bf3..cb8b143e68699 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -793,7 +793,6 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
   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);
@@ -801,8 +800,15 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
       return mlir::Value();
     }
 
+    // Look up the device_type recorded when the !$omp groupprivate directive
+    // was lowered. Default to 'any' if no explicit device_type was given.
     mlir::omp::DeclareTargetDeviceType deviceTypeEnum =
         mlir::omp::DeclareTargetDeviceType::any;
+    const auto &deviceTypeMap =
+        converter.getOMPGroupprivateDeviceTypeInfo().map;
+    auto it = deviceTypeMap.find(&sym.GetUltimate());
+    if (it != deviceTypeMap.end())
+      deviceTypeEnum = it->second;
     mlir::omp::DeclareTargetDeviceTypeAttr deviceTypeAttr =
         mlir::omp::DeclareTargetDeviceTypeAttr::get(firOpBuilder.getContext(),
                                                     deviceTypeEnum);
@@ -4720,8 +4726,21 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
                    const parser::OpenMPGroupprivate &directive) {
-  // The groupprivate directive is lowered when the variable is referenced
-  // inside target/teams regions.
+  // The omp.groupprivate operation itself is created lazily when the symbol
+  // is referenced inside a teams region (see groupprivatizeVars). Here we
+  // only extract the device_type clause (if any) and record it per-symbol so
+  // that the later op-creation can emit it on omp.groupprivate.
+  ObjectList objects = makeObjects(directive.v.Arguments(), semaCtx);
+  List<Clause> clauses = makeClauses(directive.v.Clauses(), semaCtx);
+  ClauseProcessor cp(converter, semaCtx, clauses);
+  mlir::omp::DeviceTypeClauseOps deviceTypeOps;
+  cp.processDeviceType(deviceTypeOps);
+
+  auto &deviceTypeMap = converter.getOMPGroupprivateDeviceTypeInfo().map;
+  for (const Object &obj : objects) {
+    if (const semantics::Symbol *sym = obj.sym())
+      deviceTypeMap[&sym->GetUltimate()] = deviceTypeOps.deviceType;
+  }
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/groupprivate.f90 b/flang/test/Lower/OpenMP/groupprivate.f90
index 9d3809b2a2999..b7bb4407fad30 100644
--- a/flang/test/Lower/OpenMP/groupprivate.f90
+++ b/flang/test/Lower/OpenMP/groupprivate.f90
@@ -184,3 +184,25 @@ subroutine test_multi_common_groupprivate()
     b1 = 2
   !$omp end teams
 end subroutine
+
+! Test 10: device_type(host) and device_type(nohost) clauses are honored.
+module m_dt
+  implicit none
+  integer, save :: gp_h
+  integer, save :: gp_nh
+  !$omp groupprivate(gp_h)  device_type(host)
+  !$omp groupprivate(gp_nh) device_type(nohost)
+end module
+
+! CHECK-LABEL: func.func @_QPtest_device_type_groupprivate
+! CHECK: omp.teams
+! CHECK-DAG: omp.groupprivate @_QMm_dtEgp_h device_type (host) : !fir.ref<i32>
+! CHECK-DAG: omp.groupprivate @_QMm_dtEgp_nh device_type (nohost) : !fir.ref<i32>
+subroutine test_device_type_groupprivate()
+  use m_dt
+
+  !$omp teams
+    gp_h = 1
+    gp_nh = 2
+  !$omp end teams
+end subroutine

>From 2f59fe00ba7881c91aeda580f108e0f99ce94e12 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Thu, 7 May 2026 11:43:18 +0530
Subject: [PATCH 03/12] [flang][OpenMP] Replace OMPGroupprivateDeviceTypeInfo
 struct with a using alias

---
 flang/include/flang/Lower/AbstractConverter.h | 8 ++------
 flang/include/flang/Lower/OpenMP.h            | 8 +++-----
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 5 ++---
 3 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/flang/include/flang/Lower/AbstractConverter.h b/flang/include/flang/Lower/AbstractConverter.h
index db3ad4411508c..08b446af8c1b9 100644
--- a/flang/include/flang/Lower/AbstractConverter.h
+++ b/flang/include/flang/Lower/AbstractConverter.h
@@ -14,6 +14,7 @@
 #define FORTRAN_LOWER_ABSTRACTCONVERTER_H
 
 #include "flang/Lower/LoweringOptions.h"
+#include "flang/Lower/OpenMP.h"
 #include "flang/Lower/PFTDefs.h"
 #include "flang/Lower/StatementContext.h"
 #include "flang/Lower/Support/Utils.h"
@@ -77,10 +78,6 @@ class StatementContext;
 
 using ExprToValueMap = llvm::DenseMap<const SomeExpr *, mlir::Value>;
 
-/// Forward declaration only; the full definition lives in
-/// `flang/Lower/OpenMP.h`.
-struct OMPGroupprivateDeviceTypeInfo;
-
 //===----------------------------------------------------------------------===//
 // AbstractConverter interface
 //===----------------------------------------------------------------------===//
@@ -408,8 +405,7 @@ class AbstractConverter {
   /// Return the per-converter table recording the device_type clause
   /// associated with each groupprivate symbol. Used to communicate state from
   /// the lowering of a `!$omp groupprivate` directive to the later creation of
-  /// `omp.groupprivate` operations inside teams regions. The returned object
-  /// is fully defined in `flang/Lower/OpenMP.h`.
+  /// `omp.groupprivate` operations inside teams regions.
   virtual OMPGroupprivateDeviceTypeInfo &getOMPGroupprivateDeviceTypeInfo() = 0;
 
 private:
diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index 992546024fc50..da1e7d55fbf15 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -66,11 +66,9 @@ struct OMPDeferredDeclareTargetInfo {
 /// each groupprivate symbol on the `!$omp groupprivate` directive that
 /// declared it. Populated when the directive is lowered and consumed when
 /// `omp.groupprivate` operations are emitted inside teams regions.
-struct OMPGroupprivateDeviceTypeInfo {
-  llvm::DenseMap<const Fortran::semantics::Symbol *,
-                 mlir::omp::DeclareTargetDeviceType>
-      map;
-};
+using OMPGroupprivateDeviceTypeInfo =
+    llvm::DenseMap<const Fortran::semantics::Symbol *,
+                   mlir::omp::DeclareTargetDeviceType>;
 
 // Generate the OpenMP terminator for Operation at Location.
 mlir::Operation *genOpenMPTerminator(fir::FirOpBuilder &, mlir::Operation *,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index cb8b143e68699..22cd69a4b8f75 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -804,8 +804,7 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
     // was lowered. Default to 'any' if no explicit device_type was given.
     mlir::omp::DeclareTargetDeviceType deviceTypeEnum =
         mlir::omp::DeclareTargetDeviceType::any;
-    const auto &deviceTypeMap =
-        converter.getOMPGroupprivateDeviceTypeInfo().map;
+    const auto &deviceTypeMap = converter.getOMPGroupprivateDeviceTypeInfo();
     auto it = deviceTypeMap.find(&sym.GetUltimate());
     if (it != deviceTypeMap.end())
       deviceTypeEnum = it->second;
@@ -4736,7 +4735,7 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   mlir::omp::DeviceTypeClauseOps deviceTypeOps;
   cp.processDeviceType(deviceTypeOps);
 
-  auto &deviceTypeMap = converter.getOMPGroupprivateDeviceTypeInfo().map;
+  auto &deviceTypeMap = converter.getOMPGroupprivateDeviceTypeInfo();
   for (const Object &obj : objects) {
     if (const semantics::Symbol *sym = obj.sym())
       deviceTypeMap[&sym->GetUltimate()] = deviceTypeOps.deviceType;

>From ed00e5c90ed6c9a43351e237dd9f083db598d613 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Wed, 13 May 2026 15:39:16 +0530
Subject: [PATCH 04/12] NFCcode changes

---
 flang/include/flang/Lower/OpenMP.h |  2 +-
 flang/lib/Lower/OpenMP/OpenMP.cpp  | 58 +++++++++++++++---------------
 2 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index da1e7d55fbf15..6d858202d04fa 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -89,8 +89,8 @@ void genOpenMPDeclarativeConstruct(AbstractConverter &,
 void genOpenMPSymbolProperties(AbstractConverter &converter,
                                const pft::Variable &var);
 
-void genThreadprivateOp(AbstractConverter &, const pft::Variable &);
 void genGroupprivateOp(AbstractConverter &, const pft::Variable &);
+void genThreadprivateOp(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 22cd69a4b8f75..f9ae94698c75e 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4724,7 +4724,7 @@ genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
-                   const parser::OpenMPGroupprivate &directive) {
+                   const parser::OmpGroupprivateDirective &directive) {
   // The omp.groupprivate operation itself is created lazily when the symbol
   // is referenced inside a teams region (see groupprivatizeVars). Here we
   // only extract the device_type clause (if any) and record it per-symbol so
@@ -5499,14 +5499,40 @@ void Fortran::lower::genOpenMPSymbolProperties(
   assert(var.hasSymbol() && "Expecting Symbol");
   const semantics::Symbol &sym = var.getSymbol();
 
+  if (sym.test(semantics::Symbol::Flag::OmpGroupPrivate))
+    lower::genGroupprivateOp(converter, var);
+
   if (sym.test(semantics::Symbol::Flag::OmpThreadprivate))
     lower::genThreadprivateOp(converter, var);
 
   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::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.
 }
 
 void Fortran::lower::genThreadprivateOp(lower::AbstractConverter &converter,
@@ -5575,32 +5601,6 @@ 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

>From bd5cac623a3b9d898ae8cb7c46f7b59aae645c32 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Thu, 14 May 2026 16:43:41 +0530
Subject: [PATCH 05/12] [flang][OpenMP] Propagate groupprivate device_type via
 .mod files

---
 flang/include/flang/Lower/AbstractConverter.h |   7 -
 flang/include/flang/Lower/OpenMP.h            |   9 --
 flang/include/flang/Semantics/semantics.h     |  21 +++
 flang/lib/Lower/Bridge.cpp                    |  11 --
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  53 ++++---
 flang/lib/Semantics/mod-file.cpp              |  48 +++++-
 flang/lib/Semantics/resolve-directives.cpp    |  33 ++++
 flang/lib/Semantics/semantics.cpp             |  14 ++
 .../Lower/OpenMP/groupprivate-modfile.f90     |  36 +++++
 flang/test/Lower/OpenMP/groupprivate.f90      | 142 +++++++++++++-----
 10 files changed, 286 insertions(+), 88 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/groupprivate-modfile.f90

diff --git a/flang/include/flang/Lower/AbstractConverter.h b/flang/include/flang/Lower/AbstractConverter.h
index 08b446af8c1b9..1b19807bff0cf 100644
--- a/flang/include/flang/Lower/AbstractConverter.h
+++ b/flang/include/flang/Lower/AbstractConverter.h
@@ -14,7 +14,6 @@
 #define FORTRAN_LOWER_ABSTRACTCONVERTER_H
 
 #include "flang/Lower/LoweringOptions.h"
-#include "flang/Lower/OpenMP.h"
 #include "flang/Lower/PFTDefs.h"
 #include "flang/Lower/StatementContext.h"
 #include "flang/Lower/Support/Utils.h"
@@ -402,12 +401,6 @@ class AbstractConverter {
 
   virtual mlir::StateStack &getStateStack() = 0;
 
-  /// Return the per-converter table recording the device_type clause
-  /// associated with each groupprivate symbol. Used to communicate state from
-  /// the lowering of a `!$omp groupprivate` directive to the later creation of
-  /// `omp.groupprivate` operations inside teams regions.
-  virtual OMPGroupprivateDeviceTypeInfo &getOMPGroupprivateDeviceTypeInfo() = 0;
-
 private:
   /// Options controlling lowering behavior.
   const Fortran::lower::LoweringOptions &loweringOptions;
diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index 6d858202d04fa..882ebad9071bc 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -13,7 +13,6 @@
 #ifndef FORTRAN_LOWER_OPENMP_H
 #define FORTRAN_LOWER_OPENMP_H
 
-#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallVector.h"
 
 #include <cinttypes>
@@ -62,14 +61,6 @@ struct OMPDeferredDeclareTargetInfo {
   const Fortran::semantics::Symbol &sym;
 };
 
-/// Per-converter table that records the `device_type` modifier associated with
-/// each groupprivate symbol on the `!$omp groupprivate` directive that
-/// declared it. Populated when the directive is lowered and consumed when
-/// `omp.groupprivate` operations are emitted inside teams regions.
-using OMPGroupprivateDeviceTypeInfo =
-    llvm::DenseMap<const Fortran::semantics::Symbol *,
-                   mlir::omp::DeclareTargetDeviceType>;
-
 // Generate the OpenMP terminator for Operation at Location.
 mlir::Operation *genOpenMPTerminator(fir::FirOpBuilder &, mlir::Operation *,
                                      mlir::Location);
diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index 5e98378fc49be..120c927b6b6f2 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -20,6 +20,7 @@
 #include "flang/Support/Fortran-features.h"
 #include "flang/Support/LangOptions.h"
 #include <iosfwd>
+#include <optional>
 #include <set>
 #include <string>
 #include <vector>
@@ -57,6 +58,11 @@ class Symbol;
 class CommonBlockMap;
 using CommonBlockList = std::vector<std::pair<SymbolRef, std::size_t>>;
 
+// `device_type` modifier values that can appear on `!$omp groupprivate`.
+// Kept as a semantics-layer enum so this header does not depend on MLIR.
+// The lowering layer converts to mlir::omp::DeclareTargetDeviceType.
+enum class OmpGroupprivateDeviceType { Any, Host, Nohost };
+
 using ConstructNode = std::variant<const parser::AssociateConstruct *,
     const parser::BlockConstruct *, const parser::CaseConstruct *,
     const parser::ChangeTeamConstruct *, const parser::CriticalConstruct *,
@@ -348,6 +354,19 @@ class SemanticsContext {
     return accObjectDuplicates_.count(o) != 0;
   }
 
+  // Record the `device_type` modifier observed on a `!$omp groupprivate`
+  // directive declaring \p symbol. Called during semantic analysis so the
+  // value is available before any OpenMP lowering, removing the dependency on
+  // lowering order between the directive's owning unit and any teams region
+  // that references the symbol.
+  void SetOmpGroupprivateDeviceType(const Symbol &, OmpGroupprivateDeviceType);
+  // Look up the recorded `device_type` modifier for \p symbol. Returns
+  // std::nullopt for symbols not flagged as groupprivate (or flagged before
+  // this mechanism was populated), in which case the caller should default to
+  // OmpGroupprivateDeviceType::Any per the OpenMP spec.
+  std::optional<OmpGroupprivateDeviceType> GetOmpGroupprivateDeviceType(
+      const Symbol &) const;
+
   void DumpSymbols(llvm::raw_ostream &);
 
   // Top-level ProgramTrees are owned by the SemanticsContext for persistence.
@@ -409,6 +428,8 @@ class SemanticsContext {
   UnorderedSymbolSet isUsed_;
   std::set<const parser::AccObject *> accObjectDuplicates_;
   std::list<ProgramTree> programTrees_;
+  std::map<SymbolRef, OmpGroupprivateDeviceType, SymbolAddressCompare>
+      ompGroupprivateDeviceTypes_;
 };
 
 class Semantics {
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 8f93a4c23659c..78f9de9c9420e 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1432,11 +1432,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
   mlir::StateStack &getStateStack() override { return stateStack; }
 
-  Fortran::lower::OMPGroupprivateDeviceTypeInfo &
-  getOMPGroupprivateDeviceTypeInfo() override {
-    return ompGroupprivateDeviceTypeInfo;
-  }
-
   /// Add the symbol to the local map and return `true`. If the symbol is
   /// already in the map and \p forced is `false`, the map is not updated.
   /// Instead the value `false` is returned.
@@ -7031,12 +7026,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   llvm::SmallVector<Fortran::lower::OMPDeferredDeclareTargetInfo>
       ompDeferredDeclareTarget;
 
-  /// Per-converter table recording the `device_type` clause for each symbol
-  /// declared in a `!$omp groupprivate` directive. Populated when the
-  /// directive is lowered and consumed when `omp.groupprivate` ops are emitted
-  /// inside teams regions.
-  Fortran::lower::OMPGroupprivateDeviceTypeInfo ompGroupprivateDeviceTypeInfo;
-
   const Fortran::lower::ExprToValueMap *exprValueOverrides{nullptr};
 
   /// Stack of derived type under construction to avoid infinite loops when
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index f9ae94698c75e..ebc32254e44d6 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -783,7 +783,24 @@ static void threadPrivatizeVars(lower::AbstractConverter &converter,
   }
 }
 
+// Translate a semantics-layer groupprivate device_type to the MLIR enum used
+// by omp.groupprivate. Kept here (rather than in semantics) to avoid making
+// the semantics layer depend on MLIR.
+static mlir::omp::DeclareTargetDeviceType
+toMLIRDeclareTargetDeviceType(semantics::OmpGroupprivateDeviceType deviceType) {
+  switch (deviceType) {
+  case semantics::OmpGroupprivateDeviceType::Any:
+    return mlir::omp::DeclareTargetDeviceType::any;
+  case semantics::OmpGroupprivateDeviceType::Host:
+    return mlir::omp::DeclareTargetDeviceType::host;
+  case semantics::OmpGroupprivateDeviceType::Nohost:
+    return mlir::omp::DeclareTargetDeviceType::nohost;
+  }
+  llvm_unreachable("invalid OmpGroupprivateDeviceType");
+}
+
 static void groupprivatizeVars(lower::AbstractConverter &converter,
+                               semantics::SemanticsContext &semaCtx,
                                lower::pft::Evaluation &eval) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   mlir::Location currentLocation = converter.getCurrentLocation();
@@ -800,14 +817,16 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
       return mlir::Value();
     }
 
-    // Look up the device_type recorded when the !$omp groupprivate directive
-    // was lowered. Default to 'any' if no explicit device_type was given.
+    // The device_type modifier was recorded on the symbol during semantic
+    // analysis. Default to 'any' when the directive omitted a device_type
+    // clause (per OpenMP spec) or the symbol was flagged before this
+    // mechanism became available (e.g. a stale module file).
     mlir::omp::DeclareTargetDeviceType deviceTypeEnum =
         mlir::omp::DeclareTargetDeviceType::any;
-    const auto &deviceTypeMap = converter.getOMPGroupprivateDeviceTypeInfo();
-    auto it = deviceTypeMap.find(&sym.GetUltimate());
-    if (it != deviceTypeMap.end())
-      deviceTypeEnum = it->second;
+    if (auto recorded =
+            semaCtx.GetOmpGroupprivateDeviceType(sym.GetUltimate())) {
+      deviceTypeEnum = toMLIRDeclareTargetDeviceType(*recorded);
+    }
     mlir::omp::DeclareTargetDeviceTypeAttr deviceTypeAttr =
         mlir::omp::DeclareTargetDeviceTypeAttr::get(firOpBuilder.getContext(),
                                                     deviceTypeEnum);
@@ -1431,7 +1450,7 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
   }
 
   if (info.dir == llvm::omp::Directive::OMPD_teams) {
-    groupprivatizeVars(info.converter, info.eval);
+    groupprivatizeVars(info.converter, info.semaCtx, info.eval);
   }
 
   if (!info.genSkeletonOnly) {
@@ -4725,21 +4744,11 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
                    const parser::OmpGroupprivateDirective &directive) {
-  // The omp.groupprivate operation itself is created lazily when the symbol
-  // is referenced inside a teams region (see groupprivatizeVars). Here we
-  // only extract the device_type clause (if any) and record it per-symbol so
-  // that the later op-creation can emit it on omp.groupprivate.
-  ObjectList objects = makeObjects(directive.v.Arguments(), semaCtx);
-  List<Clause> clauses = makeClauses(directive.v.Clauses(), semaCtx);
-  ClauseProcessor cp(converter, semaCtx, clauses);
-  mlir::omp::DeviceTypeClauseOps deviceTypeOps;
-  cp.processDeviceType(deviceTypeOps);
-
-  auto &deviceTypeMap = converter.getOMPGroupprivateDeviceTypeInfo();
-  for (const Object &obj : objects) {
-    if (const semantics::Symbol *sym = obj.sym())
-      deviceTypeMap[&sym->GetUltimate()] = deviceTypeOps.deviceType;
-  }
+  // The OmpGroupPrivate symbol flag and any device_type modifier are
+  // recorded during semantic analysis (see OmpAttributeVisitor::Pre on
+  // OmpGroupprivateDirective). The omp.groupprivate operation itself is
+  // materialised on first use inside a teams region by groupprivatizeVars,
+  // so this declarative directive has no remaining lowering work to do.
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 2865d16c68bb8..6c68cae5894e9 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -62,6 +62,8 @@ static void PutShapeSpec(llvm::raw_ostream &, const ShapeSpec &);
 static void PutShape(
     llvm::raw_ostream &, const ArraySpec &, char open, char close);
 static void PutMapper(llvm::raw_ostream &, const Symbol &, SemanticsContext &);
+static void PutOmpGroupprivateDirective(
+    llvm::raw_ostream &, const Symbol &, SemanticsContext &);
 
 static llvm::raw_ostream &PutAttr(llvm::raw_ostream &, Attr);
 static llvm::raw_ostream &PutType(llvm::raw_ostream &, const DeclTypeSpec &);
@@ -590,6 +592,10 @@ void ModFileWriter::PutSymbol(
                   x.isExplicitBindName(), ""s);
               decls_ << "::/" << symbol.name() << "/\n";
             }
+            // Common-block symbols are processed by this branch (not the
+            // generic catch-all below), so emit any per-symbol OpenMP
+            // directives - e.g. `!$omp groupprivate(/blk/)` - here too.
+            PutOmpGroupprivateDirective(decls_, symbol, context_);
           },
           [](const HostAssocDetails &) {},
           [](const MiscDetails &) {},
@@ -1313,15 +1319,53 @@ void PutOpenACCDirective(llvm::raw_ostream &os, const Symbol &symbol) {
   }
 }
 
-void PutOpenMPDirective(llvm::raw_ostream &os, const Symbol &symbol) {
+// Re-emit `!$omp groupprivate` for symbols flagged during the module's own
+// semantic analysis, including the device_type modifier if one was recorded
+// (otherwise omit it so the default `any` semantics apply). This lets a TU
+// that `use`s this module recover the directive's information from the .mod
+// file rather than the original source - mirroring how `requires` is
+// propagated. Handles both ordinary variables and common-block names (which
+// must be wrapped in slashes when reparsed).
+static void PutOmpGroupprivateDirective(
+    llvm::raw_ostream &os, const Symbol &symbol, SemanticsContext &context) {
+  if (!symbol.test(Symbol::Flag::OmpGroupPrivate)) {
+    return;
+  }
+  os << "!$omp groupprivate(";
+  if (symbol.detailsIf<CommonBlockDetails>()) {
+    os << '/' << symbol.name() << '/';
+  } else {
+    os << symbol.name();
+  }
+  os << ")";
+  if (auto deviceType{
+          context.GetOmpGroupprivateDeviceType(symbol.GetUltimate())}) {
+    switch (*deviceType) {
+    case OmpGroupprivateDeviceType::Any:
+      // Default - emit nothing.
+      break;
+    case OmpGroupprivateDeviceType::Host:
+      os << " device_type(host)";
+      break;
+    case OmpGroupprivateDeviceType::Nohost:
+      os << " device_type(nohost)";
+      break;
+    }
+  }
+  os << "\n";
+}
+
+void PutOpenMPDirective(
+    llvm::raw_ostream &os, const Symbol &symbol, SemanticsContext &context) {
   if (symbol.test(Symbol::Flag::OmpThreadprivate)) {
     os << "!$omp threadprivate(" << symbol.name() << ")\n";
   }
+  PutOmpGroupprivateDirective(os, symbol, context);
 }
 
 void ModFileWriter::PutDirective(llvm::raw_ostream &os, const Symbol &symbol) {
   PutOpenACCDirective(os, symbol);
-  PutOpenMPDirective(os, symbol);
+  PutOpenMPDirective(os, symbol, context_);
 }
 
 struct Temp {
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 5d72698b0250d..07bc39a9340e8 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2191,6 +2191,39 @@ bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) {
       ResolveOmpObject(*object, Symbol::Flag::OmpGroupPrivate);
     }
   }
+
+  // OpenMP 6.0 allows an optional device_type clause on the groupprivate
+  // directive. Decode it once here (defaulting to `any` per the spec) so the
+  // value can be attached to every object listed by the directive.
+  OmpGroupprivateDeviceType deviceType{OmpGroupprivateDeviceType::Any};
+  for (const parser::OmpClause &clause : x.v.Clauses().v) {
+    if (const auto *dt{std::get_if<parser::OmpClause::DeviceType>(&clause.u)}) {
+      switch (dt->v.v) {
+      case parser::OmpDeviceTypeClause::DeviceTypeDescription::Any:
+        deviceType = OmpGroupprivateDeviceType::Any;
+        break;
+      case parser::OmpDeviceTypeClause::DeviceTypeDescription::Host:
+        deviceType = OmpGroupprivateDeviceType::Host;
+        break;
+      case parser::OmpDeviceTypeClause::DeviceTypeDescription::Nohost:
+        deviceType = OmpGroupprivateDeviceType::Nohost;
+        break;
+      }
+      break;
+    }
+  }
+
+  // Record device_type against the ultimate symbol of each resolved object so
+  // it is reachable during lowering regardless of the order in which the
+  // owning unit and its referencing teams regions are lowered within this TU.
+  for (const parser::OmpArgument &arg : x.v.Arguments().v) {
+    if (const parser::OmpObject * object{parser::omp::GetArgumentObject(arg)}) {
+      if (const Symbol * sym{omp::GetObjectSymbol(*object)}) {
+        context_.SetOmpGroupprivateDeviceType(sym->GetUltimate(), deviceType);
+      }
+    }
+  }
+
   return true;
 }
 
diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp
index e4ac2f73c3976..cec4e67faaabc 100644
--- a/flang/lib/Semantics/semantics.cpp
+++ b/flang/lib/Semantics/semantics.cpp
@@ -440,6 +440,20 @@ void SemanticsContext::CheckError(const Symbol &symbol) {
   }
 }
 
+void SemanticsContext::SetOmpGroupprivateDeviceType(
+    const Symbol &symbol, OmpGroupprivateDeviceType deviceType) {
+  ompGroupprivateDeviceTypes_.insert_or_assign(SymbolRef{symbol}, deviceType);
+}
+
+std::optional<OmpGroupprivateDeviceType>
+SemanticsContext::GetOmpGroupprivateDeviceType(const Symbol &symbol) const {
+  auto it{ompGroupprivateDeviceTypes_.find(SymbolRef{symbol})};
+  if (it == ompGroupprivateDeviceTypes_.end()) {
+    return std::nullopt;
+  }
+  return it->second;
+}
+
 bool SemanticsContext::ScopeIndexComparator::operator()(
     parser::CharBlock x, parser::CharBlock y) const {
   return x.begin() < y.begin() ||
diff --git a/flang/test/Lower/OpenMP/groupprivate-modfile.f90 b/flang/test/Lower/OpenMP/groupprivate-modfile.f90
new file mode 100644
index 0000000000000..cc30d7c8ddb46
--- /dev/null
+++ b/flang/test/Lower/OpenMP/groupprivate-modfile.f90
@@ -0,0 +1,36 @@
+! Cross-TU propagation of `groupprivate` and its device_type via .mod files.
+
+! RUN: rm -rf %t && split-file %s %t
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=60 -module-dir %t %t/m.f90 -o - > /dev/null
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=60 -J %t %t/use.f90 -o - | FileCheck %s
+
+! First RUN builds gp_mod.mod.
+! Second RUN lowers a consumer that only USE-associates the module
+! and checks that the omp.groupprivate op picks up device_type recovered
+! from the .mod file rather than the original source.
+
+!--- m.f90
+module gp_mod
+  implicit none
+  integer, save :: gp_x
+  !$omp groupprivate(gp_x) device_type(host)
+end module gp_mod
+
+!--- use.f90
+program use_gp_mod
+  use gp_mod
+  implicit none
+  !$omp teams
+    gp_x = 42
+  !$omp end teams
+end program use_gp_mod
+
+! CHECK-LABEL: func.func @_QQmain
+! CHECK:         fir.address_of(@_QMgp_modEgp_x) : !fir.ref<i32>
+! CHECK:         omp.teams {
+! CHECK:           %[[GP:.*]] = omp.groupprivate @_QMgp_modEgp_x device_type (host) : !fir.ref<i32>
+! CHECK:           %[[DECL:.*]]:2 = hlfir.declare %[[GP]] {uniq_name = "_QMgp_modEgp_x"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           %[[C42:.*]] = arith.constant 42 : i32
+! CHECK:           hlfir.assign %[[C42]] to %[[DECL]]#0 : i32, !fir.ref<i32>
+! CHECK:           omp.terminator
+! CHECK:         }
diff --git a/flang/test/Lower/OpenMP/groupprivate.f90 b/flang/test/Lower/OpenMP/groupprivate.f90
index b7bb4407fad30..f84f3f2782a69 100644
--- a/flang/test/Lower/OpenMP/groupprivate.f90
+++ b/flang/test/Lower/OpenMP/groupprivate.f90
@@ -14,9 +14,12 @@ module m
 end module
 
 ! CHECK-LABEL: func.func @_QPtest_groupprivate
-! CHECK: omp.target
-! CHECK:   omp.teams
-! CHECK:     %{{.*}} = omp.groupprivate @_QMmEx device_type (any) : !fir.ref<i32>
+! CHECK:         omp.target {
+! CHECK:           omp.teams {
+! CHECK:             %[[GP:.*]] = omp.groupprivate @_QMmEx device_type (any) : !fir.ref<i32>
+! CHECK:             %[[DECL:.*]]:2 = hlfir.declare %[[GP]] {uniq_name = "_QMmEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:             %[[C10:.*]] = arith.constant 10 : i32
+! CHECK:             hlfir.assign %[[C10]] to %[[DECL]]#0 : i32, !fir.ref<i32>
 subroutine test_groupprivate()
   use m
 
@@ -37,11 +40,15 @@ module m2
 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
+! CHECK:         omp.target {
+! CHECK:           omp.teams {
+! CHECK:             %[[GP:.*]] = omp.groupprivate @blk_ device_type (any) : !fir.ref<!fir.array<12xi8>>
+! CHECK:             %[[DECL_X:.*]]:2 = hlfir.declare %{{.*}} storage(%[[GP]][0]) {uniq_name = "_QMm2Ecb_x"}
+! CHECK:             %[[DECL_Y:.*]]:2 = hlfir.declare %{{.*}} storage(%[[GP]][4]) {uniq_name = "_QMm2Ecb_y"}
+! CHECK:             %[[DECL_Z:.*]]:2 = hlfir.declare %{{.*}} storage(%[[GP]][8]) {uniq_name = "_QMm2Ecb_z"}
+! CHECK:             hlfir.assign %{{.*}} to %[[DECL_X]]#0 : i32, !fir.ref<i32>
+! CHECK:             hlfir.assign %{{.*}} to %[[DECL_Y]]#0 : i32, !fir.ref<i32>
+! CHECK:             hlfir.assign %{{.*}} to %[[DECL_Z]]#0 : f32, !fir.ref<f32>
 subroutine test_common_block_groupprivate()
   use m2
 
@@ -56,8 +63,11 @@ subroutine test_common_block_groupprivate()
 
 ! 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>
+! CHECK:         omp.teams {
+! CHECK:           %[[GP:.*]] = omp.groupprivate @_QFtest_local_save_groupprivateElocal_x device_type (any) : !fir.ref<i32>
+! CHECK:           %[[DECL:.*]]:2 = hlfir.declare %[[GP]] {uniq_name = "_QFtest_local_save_groupprivateElocal_x"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           %[[C42:.*]] = arith.constant 42 : i32
+! CHECK:           hlfir.assign %[[C42]] to %[[DECL]]#0 : i32, !fir.ref<i32>
 subroutine test_local_save_groupprivate()
   integer, save :: local_x
   !$omp groupprivate(local_x)
@@ -77,11 +87,17 @@ module m_multi
 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
+! CHECK:         omp.target {
+! CHECK:           omp.teams {
+! CHECK:             %[[GP_A:.*]] = omp.groupprivate @_QMm_multiEgp_a device_type (any) : !fir.ref<i32>
+! CHECK:             %[[DECL_A:.*]]:2 = hlfir.declare %[[GP_A]] {uniq_name = "_QMm_multiEgp_a"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:             %[[GP_B:.*]] = omp.groupprivate @_QMm_multiEgp_b device_type (any) : !fir.ref<i32>
+! CHECK:             %[[DECL_B:.*]]:2 = hlfir.declare %[[GP_B]] {uniq_name = "_QMm_multiEgp_b"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:             %[[GP_C:.*]] = omp.groupprivate @_QMm_multiEgp_c device_type (any) : !fir.ref<f32>
+! CHECK:             %[[DECL_C:.*]]:2 = hlfir.declare %[[GP_C]] {uniq_name = "_QMm_multiEgp_c"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+! CHECK:             hlfir.assign %{{.*}} to %[[DECL_A]]#0 : i32, !fir.ref<i32>
+! CHECK:             hlfir.assign %{{.*}} to %[[DECL_B]]#0 : i32, !fir.ref<i32>
+! CHECK:             hlfir.assign %{{.*}} to %[[DECL_C]]#0 : f32, !fir.ref<f32>
 subroutine test_multiple_groupprivate()
   use m_multi
 
@@ -94,12 +110,19 @@ subroutine test_multiple_groupprivate()
   !$omp end target
 end subroutine
 
-! Test 5: Same variable referenced multiple times produces only one op.
+! Test 5: Same variable referenced multiple times produces only one op, and
+! every reference accesses the per-team copy via the same hlfir.declare.
 ! CHECK-LABEL: func.func @_QPtest_repeated_ref_groupprivate
-! CHECK: omp.teams
-! CHECK: omp.groupprivate @_QMmEx
-! CHECK-NOT: omp.groupprivate @_QMmEx
-! CHECK: omp.terminator
+! CHECK:         omp.teams {
+! CHECK:           %[[GP:.*]] = omp.groupprivate @_QMmEx device_type (any) : !fir.ref<i32>
+! CHECK:           %[[DECL:.*]]:2 = hlfir.declare %[[GP]] {uniq_name = "_QMmEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK-NOT:       omp.groupprivate @_QMmEx
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL]]#0 : i32, !fir.ref<i32>
+! CHECK:           %{{.*}} = fir.load %[[DECL]]#0 : !fir.ref<i32>
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL]]#0 : i32, !fir.ref<i32>
+! CHECK:           %{{.*}} = fir.load %[[DECL]]#0 : !fir.ref<i32>
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL]]#0 : i32, !fir.ref<i32>
+! CHECK:           omp.terminator
 subroutine test_repeated_ref_groupprivate()
   use m
 
@@ -114,9 +137,12 @@ subroutine test_repeated_ref_groupprivate()
 
 ! 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>
+! CHECK-NOT:     omp.target
+! CHECK:         omp.teams {
+! CHECK:           %[[GP:.*]] = omp.groupprivate @_QMmEx device_type (any) : !fir.ref<i32>
+! CHECK:           %[[DECL:.*]]:2 = hlfir.declare %[[GP]] {uniq_name = "_QMmEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           %[[C100:.*]] = arith.constant 100 : i32
+! CHECK:           hlfir.assign %[[C100]] to %[[DECL]]#0 : i32, !fir.ref<i32>
 subroutine test_standalone_teams_groupprivate()
   use m
 
@@ -125,12 +151,17 @@ subroutine test_standalone_teams_groupprivate()
   !$omp end teams
 end subroutine
 
-! Test 7: Groupprivate variable is not added to target's implicit map_entries.
+! Test 7: Groupprivate variable is not added to target's implicit map_entries,
+! and the access inside the teams region goes through the omp.groupprivate
+! result rather than the original host global.
 ! 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
+! CHECK-NOT:     omp.map.info
+! CHECK:         omp.target {
+! CHECK:           omp.teams {
+! CHECK:             %[[GP:.*]] = omp.groupprivate @_QMmEx device_type (any) : !fir.ref<i32>
+! CHECK:             %[[DECL:.*]]:2 = hlfir.declare %[[GP]] {uniq_name = "_QMmEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:             %[[C200:.*]] = arith.constant 200 : i32
+! CHECK:             hlfir.assign %[[C200]] to %[[DECL]]#0 : i32, !fir.ref<i32>
 subroutine test_target_skip_map_groupprivate()
   use m
 
@@ -150,9 +181,15 @@ module m_types
 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>>
+! CHECK:         omp.teams {
+! CHECK:           %[[GP_R8:.*]] = omp.groupprivate @_QMm_typesEgp_r8 device_type (any) : !fir.ref<f64>
+! CHECK:           %[[DECL_R8:.*]]:2 = hlfir.declare %[[GP_R8]] {uniq_name = "_QMm_typesEgp_r8"} : (!fir.ref<f64>) -> (!fir.ref<f64>, !fir.ref<f64>)
+! CHECK:           %[[GP_IARR:.*]] = omp.groupprivate @_QMm_typesEgp_iarr device_type (any) : !fir.ref<!fir.array<4xi32>>
+! CHECK:           %[[SHAPE:.*]] = fir.shape %{{.*}} : (index) -> !fir.shape<1>
+! CHECK:           %[[DECL_IARR:.*]]:2 = hlfir.declare %[[GP_IARR]](%[[SHAPE]]) {uniq_name = "_QMm_typesEgp_iarr"}
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL_R8]]#0 : f64, !fir.ref<f64>
+! CHECK:           %[[ELT:.*]] = hlfir.designate %[[DECL_IARR]]#0 (%{{.*}}) : (!fir.ref<!fir.array<4xi32>>, index) -> !fir.ref<i32>
+! CHECK:           hlfir.assign %{{.*}} to %[[ELT]] : i32, !fir.ref<i32>
 subroutine test_types_groupprivate()
   use m_types
 
@@ -173,9 +210,13 @@ module m_blocks
 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)
+! CHECK:         omp.teams {
+! CHECK:           %[[GP_A:.*]] = omp.groupprivate @blka_ device_type (any) : !fir.ref<!fir.array<8xi8>>
+! CHECK:           %[[DECL_A1:.*]]:2 = hlfir.declare %{{.*}} storage(%[[GP_A]][0]) {uniq_name = "_QMm_blocksEa1"}
+! CHECK:           %[[GP_B:.*]] = omp.groupprivate @blkb_ device_type (any) : !fir.ref<!fir.array<8xi8>>
+! CHECK:           %[[DECL_B1:.*]]:2 = hlfir.declare %{{.*}} storage(%[[GP_B]][0]) {uniq_name = "_QMm_blocksEb1"}
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL_A1]]#0 : i32, !fir.ref<i32>
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL_B1]]#0 : i32, !fir.ref<i32>
 subroutine test_multi_common_groupprivate()
   use m_blocks
 
@@ -195,9 +236,13 @@ module m_dt
 end module
 
 ! CHECK-LABEL: func.func @_QPtest_device_type_groupprivate
-! CHECK: omp.teams
-! CHECK-DAG: omp.groupprivate @_QMm_dtEgp_h device_type (host) : !fir.ref<i32>
-! CHECK-DAG: omp.groupprivate @_QMm_dtEgp_nh device_type (nohost) : !fir.ref<i32>
+! CHECK:         omp.teams {
+! CHECK:           %[[GP_H:.*]] = omp.groupprivate @_QMm_dtEgp_h device_type (host) : !fir.ref<i32>
+! CHECK:           %[[DECL_H:.*]]:2 = hlfir.declare %[[GP_H]] {uniq_name = "_QMm_dtEgp_h"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           %[[GP_NH:.*]] = omp.groupprivate @_QMm_dtEgp_nh device_type (nohost) : !fir.ref<i32>
+! CHECK:           %[[DECL_NH:.*]]:2 = hlfir.declare %[[GP_NH]] {uniq_name = "_QMm_dtEgp_nh"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL_H]]#0 : i32, !fir.ref<i32>
+! CHECK:           hlfir.assign %{{.*}} to %[[DECL_NH]]#0 : i32, !fir.ref<i32>
 subroutine test_device_type_groupprivate()
   use m_dt
 
@@ -206,3 +251,26 @@ subroutine test_device_type_groupprivate()
     gp_nh = 2
   !$omp end teams
 end subroutine
+
+! Test 11: The module owning the !$omp groupprivate directive
+! is declared AFTER a subroutine that already
+! references the variable inside a teams region.
+! CHECK-LABEL: func.func @_QPtest_module_after_subroutine_groupprivate
+! CHECK:         omp.teams {
+! CHECK:           %[[GP:.*]] = omp.groupprivate @_QMm_lateEgp_late device_type (host) : !fir.ref<i32>
+! CHECK:           %[[DECL:.*]]:2 = hlfir.declare %[[GP]] {uniq_name = "_QMm_lateEgp_late"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
+! CHECK:           %[[C7:.*]] = arith.constant 7 : i32
+! CHECK:           hlfir.assign %[[C7]] to %[[DECL]]#0 : i32, !fir.ref<i32>
+subroutine test_module_after_subroutine_groupprivate()
+  use m_late
+
+  !$omp teams
+    gp_late = 7
+  !$omp end teams
+end subroutine
+
+module m_late
+  implicit none
+  integer, save :: gp_late
+  !$omp groupprivate(gp_late) device_type(host)
+end module

>From c0bcb998f0838c56bbf36abb7243b56e2a7aa4dc Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Thu, 14 May 2026 16:59:56 +0530
Subject: [PATCH 06/12] code-format fix

---
 flang/lib/Semantics/resolve-directives.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 07bc39a9340e8..891005d287eff 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2217,8 +2217,8 @@ bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) {
   // it is reachable during lowering regardless of the order in which the
   // owning unit and its referencing teams regions are lowered within this TU.
   for (const parser::OmpArgument &arg : x.v.Arguments().v) {
-    if (const parser::OmpObject * object{parser::omp::GetArgumentObject(arg)}) {
-      if (const Symbol * sym{omp::GetObjectSymbol(*object)}) {
+    if (const parser::OmpObject *object{parser::omp::GetArgumentObject(arg)}) {
+      if (const Symbol *sym{omp::GetObjectSymbol(*object)}) {
         context_.SetOmpGroupprivateDeviceType(sym->GetUltimate(), deviceType);
       }
     }

>From e6db954e6021eefb9ce512c92d5771d007066df1 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Mon, 8 Jun 2026 12:35:15 +0530
Subject: [PATCH 07/12] [flang][OpenMP] Store groupprivate device_type on
 symbol details

---
 flang/include/flang/Semantics/semantics.h  | 20 ------
 flang/include/flang/Semantics/symbol.h     | 11 ++++
 flang/lib/Lower/OpenMP/OpenMP.cpp          | 36 ++++++-----
 flang/lib/Semantics/mod-file.cpp           | 72 ++++++++--------------
 flang/lib/Semantics/resolve-directives.cpp | 47 +++++++-------
 flang/lib/Semantics/semantics.cpp          | 14 -----
 6 files changed, 77 insertions(+), 123 deletions(-)

diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index 120c927b6b6f2..d4e53279fada2 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -58,11 +58,6 @@ class Symbol;
 class CommonBlockMap;
 using CommonBlockList = std::vector<std::pair<SymbolRef, std::size_t>>;
 
-// `device_type` modifier values that can appear on `!$omp groupprivate`.
-// Kept as a semantics-layer enum so this header does not depend on MLIR.
-// The lowering layer converts to mlir::omp::DeclareTargetDeviceType.
-enum class OmpGroupprivateDeviceType { Any, Host, Nohost };
-
 using ConstructNode = std::variant<const parser::AssociateConstruct *,
     const parser::BlockConstruct *, const parser::CaseConstruct *,
     const parser::ChangeTeamConstruct *, const parser::CriticalConstruct *,
@@ -354,19 +349,6 @@ class SemanticsContext {
     return accObjectDuplicates_.count(o) != 0;
   }
 
-  // Record the `device_type` modifier observed on a `!$omp groupprivate`
-  // directive declaring \p symbol. Called during semantic analysis so the
-  // value is available before any OpenMP lowering, removing the dependency on
-  // lowering order between the directive's owning unit and any teams region
-  // that references the symbol.
-  void SetOmpGroupprivateDeviceType(const Symbol &, OmpGroupprivateDeviceType);
-  // Look up the recorded `device_type` modifier for \p symbol. Returns
-  // std::nullopt for symbols not flagged as groupprivate (or flagged before
-  // this mechanism was populated), in which case the caller should default to
-  // OmpGroupprivateDeviceType::Any per the OpenMP spec.
-  std::optional<OmpGroupprivateDeviceType> GetOmpGroupprivateDeviceType(
-      const Symbol &) const;
-
   void DumpSymbols(llvm::raw_ostream &);
 
   // Top-level ProgramTrees are owned by the SemanticsContext for persistence.
@@ -428,8 +410,6 @@ class SemanticsContext {
   UnorderedSymbolSet isUsed_;
   std::set<const parser::AccObject *> accObjectDuplicates_;
   std::list<ProgramTree> programTrees_;
-  std::map<SymbolRef, OmpGroupprivateDeviceType, SymbolAddressCompare>
-      ompGroupprivateDeviceTypes_;
 };
 
 class Semantics {
diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 40c49c594a707..5c0c868fc4ef1 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -76,6 +76,14 @@ class WithOmpDeclarative {
     ompDeviceType_ = device;
   }
 
+  const std::optional<common::OmpDeviceType> &
+  ompGroupprivateDeviceType() const {
+    return ompGroupprivateDeviceType_;
+  }
+  void set_ompGroupprivateDeviceType(common::OmpDeviceType device) {
+    ompGroupprivateDeviceType_ = device;
+  }
+
   void printClauseSet(llvm::raw_ostream &os, const OmpClauseSet &clauses,
       parser::CharBlock name = parser::CharBlock{}) const;
   friend llvm::raw_ostream &operator<<(
@@ -99,6 +107,9 @@ class WithOmpDeclarative {
   // The argument to DEVICE_TYPE clause. Only needed when the clause is
   // present in the ompDeclTarget_ set.
   std::optional<common::OmpDeviceType> ompDeviceType_;
+  // The argument to a DEVICE_TYPE clause on a GROUPPRIVATE directive declaring
+  // this symbol. Absent means the spec default (any).
+  std::optional<common::OmpDeviceType> ompGroupprivateDeviceType_;
 };
 
 // A module or submodule.
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index ebc32254e44d6..401fb956272ec 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -783,24 +783,22 @@ static void threadPrivatizeVars(lower::AbstractConverter &converter,
   }
 }
 
-// Translate a semantics-layer groupprivate device_type to the MLIR enum used
-// by omp.groupprivate. Kept here (rather than in semantics) to avoid making
-// the semantics layer depend on MLIR.
+// Translate a semantics-layer device_type to the MLIR enum used by
+// omp.groupprivate.
 static mlir::omp::DeclareTargetDeviceType
-toMLIRDeclareTargetDeviceType(semantics::OmpGroupprivateDeviceType deviceType) {
+toMLIRDeclareTargetDeviceType(Fortran::common::OmpDeviceType deviceType) {
   switch (deviceType) {
-  case semantics::OmpGroupprivateDeviceType::Any:
+  case Fortran::common::OmpDeviceType::Any:
     return mlir::omp::DeclareTargetDeviceType::any;
-  case semantics::OmpGroupprivateDeviceType::Host:
+  case Fortran::common::OmpDeviceType::Host:
     return mlir::omp::DeclareTargetDeviceType::host;
-  case semantics::OmpGroupprivateDeviceType::Nohost:
+  case Fortran::common::OmpDeviceType::Nohost:
     return mlir::omp::DeclareTargetDeviceType::nohost;
   }
-  llvm_unreachable("invalid OmpGroupprivateDeviceType");
+  llvm_unreachable("invalid OmpDeviceType");
 }
 
 static void groupprivatizeVars(lower::AbstractConverter &converter,
-                               semantics::SemanticsContext &semaCtx,
                                lower::pft::Evaluation &eval) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   mlir::Location currentLocation = converter.getCurrentLocation();
@@ -818,15 +816,19 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
     }
 
     // The device_type modifier was recorded on the symbol during semantic
-    // analysis. Default to 'any' when the directive omitted a device_type
-    // clause (per OpenMP spec) or the symbol was flagged before this
-    // mechanism became available (e.g. a stale module file).
+    // analysis.
     mlir::omp::DeclareTargetDeviceType deviceTypeEnum =
         mlir::omp::DeclareTargetDeviceType::any;
-    if (auto recorded =
-            semaCtx.GetOmpGroupprivateDeviceType(sym.GetUltimate())) {
-      deviceTypeEnum = toMLIRDeclareTargetDeviceType(*recorded);
-    }
+    Fortran::common::visit(
+        [&](auto &&details) {
+          using TypeD = llvm::remove_cvref_t<decltype(details)>;
+          if constexpr (std::is_base_of_v<semantics::WithOmpDeclarative,
+                                          TypeD>) {
+            if (auto dt = details.ompGroupprivateDeviceType())
+              deviceTypeEnum = toMLIRDeclareTargetDeviceType(*dt);
+          }
+        },
+        sym.GetUltimate().details());
     mlir::omp::DeclareTargetDeviceTypeAttr deviceTypeAttr =
         mlir::omp::DeclareTargetDeviceTypeAttr::get(firOpBuilder.getContext(),
                                                     deviceTypeEnum);
@@ -1450,7 +1452,7 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
   }
 
   if (info.dir == llvm::omp::Directive::OMPD_teams) {
-    groupprivatizeVars(info.converter, info.semaCtx, info.eval);
+    groupprivatizeVars(info.converter, info.eval);
   }
 
   if (!info.genSkeletonOnly) {
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 6c68cae5894e9..221f0daeb7e11 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -62,8 +62,6 @@ static void PutShapeSpec(llvm::raw_ostream &, const ShapeSpec &);
 static void PutShape(
     llvm::raw_ostream &, const ArraySpec &, char open, char close);
 static void PutMapper(llvm::raw_ostream &, const Symbol &, SemanticsContext &);
-static void PutOmpGroupprivateDirective(
-    llvm::raw_ostream &, const Symbol &, SemanticsContext &);
 
 static llvm::raw_ostream &PutAttr(llvm::raw_ostream &, Attr);
 static llvm::raw_ostream &PutType(llvm::raw_ostream &, const DeclTypeSpec &);
@@ -410,6 +408,30 @@ static void PutOpenMPDeclarativeDirectives(llvm::raw_ostream &os,
         decls->printClauseSet(os, dtgt, symbol.name());
         os << "\n";
       }
+      // Re-emit `!$omp groupprivate` (and any recorded device_type) so a TU
+      // that `use`s this module recovers the directive from the .mod file.
+      // Common-block names must be wrapped in slashes when reparsed.
+      if (symbol.test(Symbol::Flag::OmpGroupPrivate)) {
+        os << "!$omp groupprivate(";
+        if (symbol.detailsIf<CommonBlockDetails>())
+          os << '/' << symbol.name() << '/';
+        else
+          os << symbol.name();
+        os << ")";
+        if (auto deviceType{decls->ompGroupprivateDeviceType()}) {
+          switch (*deviceType) {
+          case common::OmpDeviceType::Any:
+            break;
+          case common::OmpDeviceType::Host:
+            os << " device_type(host)";
+            break;
+          case common::OmpDeviceType::Nohost:
+            os << " device_type(nohost)";
+            break;
+          }
+        }
+        os << "\n";
+      }
     }
   }
 }
@@ -592,10 +614,6 @@ void ModFileWriter::PutSymbol(
                   x.isExplicitBindName(), ""s);
               decls_ << "::/" << symbol.name() << "/\n";
             }
-            // Common-block symbols are processed by this branch (not the
-            // generic catch-all below), so emit any per-symbol OpenMP
-            // directives - e.g. `!$omp groupprivate(/blk/)` - here too.
-            PutOmpGroupprivateDirective(decls_, symbol, context_);
           },
           [](const HostAssocDetails &) {},
           [](const MiscDetails &) {},
@@ -1319,53 +1337,15 @@ void PutOpenACCDirective(llvm::raw_ostream &os, const Symbol &symbol) {
   }
 }
 
-// Re-emit `!$omp groupprivate` for symbols flagged during the module's own
-// semantic analysis, including the device_type modifier if one was recorded
-// (otherwise omit it so the default `any` semantics apply). This lets a TU
-// that `use`s this module recover the directive's information from the .mod
-// file rather than the original source - mirroring how `requires` is
-// propagated. Handles both ordinary variables and common-block names (which
-// must be wrapped in slashes when reparsed).
-static void PutOmpGroupprivateDirective(
-    llvm::raw_ostream &os, const Symbol &symbol, SemanticsContext &context) {
-  if (!symbol.test(Symbol::Flag::OmpGroupPrivate)) {
-    return;
-  }
-  os << "!$omp groupprivate(";
-  if (symbol.detailsIf<CommonBlockDetails>()) {
-    os << '/' << symbol.name() << '/';
-  } else {
-    os << symbol.name();
-  }
-  os << ")";
-  if (auto deviceType{
-          context.GetOmpGroupprivateDeviceType(symbol.GetUltimate())}) {
-    switch (*deviceType) {
-    case OmpGroupprivateDeviceType::Any:
-      // Default - emit nothing.
-      break;
-    case OmpGroupprivateDeviceType::Host:
-      os << " device_type(host)";
-      break;
-    case OmpGroupprivateDeviceType::Nohost:
-      os << " device_type(nohost)";
-      break;
-    }
-  }
-  os << "\n";
-}
-
-void PutOpenMPDirective(
-    llvm::raw_ostream &os, const Symbol &symbol, SemanticsContext &context) {
+void PutOpenMPDirective(llvm::raw_ostream &os, const Symbol &symbol) {
   if (symbol.test(Symbol::Flag::OmpThreadprivate)) {
     os << "!$omp threadprivate(" << symbol.name() << ")\n";
   }
-  PutOmpGroupprivateDirective(os, symbol, context);
 }
 
 void ModFileWriter::PutDirective(llvm::raw_ostream &os, const Symbol &symbol) {
   PutOpenACCDirective(os, symbol);
-  PutOpenMPDirective(os, symbol, context_);
+  PutOpenMPDirective(os, symbol);
 }
 
 struct Temp {
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 891005d287eff..a939cd8b15527 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2192,34 +2192,29 @@ bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) {
     }
   }
 
-  // OpenMP 6.0 allows an optional device_type clause on the groupprivate
-  // directive. Decode it once here (defaulting to `any` per the spec) so the
-  // value can be attached to every object listed by the directive.
-  OmpGroupprivateDeviceType deviceType{OmpGroupprivateDeviceType::Any};
-  for (const parser::OmpClause &clause : x.v.Clauses().v) {
-    if (const auto *dt{std::get_if<parser::OmpClause::DeviceType>(&clause.u)}) {
-      switch (dt->v.v) {
-      case parser::OmpDeviceTypeClause::DeviceTypeDescription::Any:
-        deviceType = OmpGroupprivateDeviceType::Any;
-        break;
-      case parser::OmpDeviceTypeClause::DeviceTypeDescription::Host:
-        deviceType = OmpGroupprivateDeviceType::Host;
-        break;
-      case parser::OmpDeviceTypeClause::DeviceTypeDescription::Nohost:
-        deviceType = OmpGroupprivateDeviceType::Nohost;
-        break;
-      }
-      break;
-    }
+  // OpenMP 6.0 allows an optional device_type clause on groupprivate. Record it
+  // on each listed object's ultimate symbol so lowering and .mod emission can
+  // read it. An absent clause means the spec default (any).
+  std::optional<common::OmpDeviceType> device;
+  if (auto *devClause{
+          parser::omp::FindClause(x.v, llvm::omp::Clause::OMPC_device_type)}) {
+    device = parser::UnwrapRef<common::OmpDeviceType>(*devClause);
   }
 
-  // Record device_type against the ultimate symbol of each resolved object so
-  // it is reachable during lowering regardless of the order in which the
-  // owning unit and its referencing teams regions are lowered within this TU.
-  for (const parser::OmpArgument &arg : x.v.Arguments().v) {
-    if (const parser::OmpObject *object{parser::omp::GetArgumentObject(arg)}) {
-      if (const Symbol *sym{omp::GetObjectSymbol(*object)}) {
-        context_.SetOmpGroupprivateDeviceType(sym->GetUltimate(), deviceType);
+  if (device) {
+    for (const parser::OmpArgument &arg : x.v.Arguments().v) {
+      if (const parser::OmpObject *
+          object{parser::omp::GetArgumentObject(arg)}) {
+        if (const Symbol * sym{omp::GetObjectSymbol(*object)}) {
+          common::visit(
+              [&](auto &d) {
+                using TypeD = llvm::remove_cvref_t<decltype(d)>;
+                if constexpr (std::is_base_of_v<WithOmpDeclarative, TypeD>) {
+                  d.set_ompGroupprivateDeviceType(*device);
+                }
+              },
+              const_cast<Symbol &>(sym->GetUltimate()).details());
+        }
       }
     }
   }
diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp
index cec4e67faaabc..e4ac2f73c3976 100644
--- a/flang/lib/Semantics/semantics.cpp
+++ b/flang/lib/Semantics/semantics.cpp
@@ -440,20 +440,6 @@ void SemanticsContext::CheckError(const Symbol &symbol) {
   }
 }
 
-void SemanticsContext::SetOmpGroupprivateDeviceType(
-    const Symbol &symbol, OmpGroupprivateDeviceType deviceType) {
-  ompGroupprivateDeviceTypes_.insert_or_assign(SymbolRef{symbol}, deviceType);
-}
-
-std::optional<OmpGroupprivateDeviceType>
-SemanticsContext::GetOmpGroupprivateDeviceType(const Symbol &symbol) const {
-  auto it{ompGroupprivateDeviceTypes_.find(SymbolRef{symbol})};
-  if (it == ompGroupprivateDeviceTypes_.end()) {
-    return std::nullopt;
-  }
-  return it->second;
-}
-
 bool SemanticsContext::ScopeIndexComparator::operator()(
     parser::CharBlock x, parser::CharBlock y) const {
   return x.begin() < y.begin() ||

>From 32088da987161f5457257ae37a9c06cd49079a22 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Mon, 8 Jun 2026 12:51:52 +0530
Subject: [PATCH 08/12] code-foramt fix

---
 flang/lib/Lower/OpenMP/OpenMP.cpp          | 8 +++-----
 flang/lib/Semantics/resolve-directives.cpp | 6 +++---
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 401fb956272ec..73e0b7e884cd8 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4746,11 +4746,9 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
                    const parser::OmpGroupprivateDirective &directive) {
-  // The OmpGroupPrivate symbol flag and any device_type modifier are
-  // recorded during semantic analysis (see OmpAttributeVisitor::Pre on
-  // OmpGroupprivateDirective). The omp.groupprivate operation itself is
-  // materialised on first use inside a teams region by groupprivatizeVars,
-  // so this declarative directive has no remaining lowering work to do.
+  // The semantic analysis sets the flag and device_type on the
+  // symbols; omp.groupprivate is materialised on first use in a teams region
+  // by groupprivatizeVars.
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index a939cd8b15527..949e683a7050b 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2203,9 +2203,9 @@ bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) {
 
   if (device) {
     for (const parser::OmpArgument &arg : x.v.Arguments().v) {
-      if (const parser::OmpObject *
-          object{parser::omp::GetArgumentObject(arg)}) {
-        if (const Symbol * sym{omp::GetObjectSymbol(*object)}) {
+      if (const parser::OmpObject *object{
+              parser::omp::GetArgumentObject(arg)}) {
+        if (const Symbol *sym{omp::GetObjectSymbol(*object)}) {
           common::visit(
               [&](auto &d) {
                 using TypeD = llvm::remove_cvref_t<decltype(d)>;

>From b5e2436a7d3bbf9c3868dc68649b243396dfdd6c Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Tue, 9 Jun 2026 12:17:26 +0530
Subject: [PATCH 09/12] [flang][OpenMP] Handle groupprivate device_type like
 declare_target

---
 flang/include/flang/Semantics/semantics.h  |  1 -
 flang/include/flang/Semantics/symbol.h     | 12 ++++++++++--
 flang/lib/Semantics/mod-file.cpp           | 20 ++++++++------------
 flang/lib/Semantics/resolve-directives.cpp | 14 ++++++++------
 flang/lib/Semantics/symbol.cpp             | 15 ++++++++++++---
 5 files changed, 38 insertions(+), 24 deletions(-)

diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index d4e53279fada2..5e98378fc49be 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -20,7 +20,6 @@
 #include "flang/Support/Fortran-features.h"
 #include "flang/Support/LangOptions.h"
 #include <iosfwd>
-#include <optional>
 #include <set>
 #include <string>
 #include <vector>
diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 5c0c868fc4ef1..8a125e2a7810b 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -76,6 +76,9 @@ class WithOmpDeclarative {
     ompDeviceType_ = device;
   }
 
+  const OmpClauseSet &ompGroupprivate() const { return ompGroupprivate_; }
+  void set_ompGroupprivate(OmpClauseSet clauses) { ompGroupprivate_ = clauses; }
+
   const std::optional<common::OmpDeviceType> &
   ompGroupprivateDeviceType() const {
     return ompGroupprivateDeviceType_;
@@ -84,8 +87,11 @@ class WithOmpDeclarative {
     ompGroupprivateDeviceType_ = device;
   }
 
+  // \p deviceType overrides the DEVICE_TYPE clause value to print (used for
+  // GROUPPRIVATE); when absent the DECLARE_TARGET device_type is used.
   void printClauseSet(llvm::raw_ostream &os, const OmpClauseSet &clauses,
-      parser::CharBlock name = parser::CharBlock{}) const;
+      parser::CharBlock name = parser::CharBlock{},
+      std::optional<common::OmpDeviceType> deviceType = std::nullopt) const;
   friend llvm::raw_ostream &operator<<(
       llvm::raw_ostream &, const WithOmpDeclarative &);
 
@@ -107,8 +113,10 @@ class WithOmpDeclarative {
   // The argument to DEVICE_TYPE clause. Only needed when the clause is
   // present in the ompDeclTarget_ set.
   std::optional<common::OmpDeviceType> ompDeviceType_;
+  // The set of clauses on a GROUPPRIVATE directive declaring this symbol.
+  OmpClauseSet ompGroupprivate_;
   // The argument to a DEVICE_TYPE clause on a GROUPPRIVATE directive declaring
-  // this symbol. Absent means the spec default (any).
+  // this symbol. Only needed when the clause is present in ompGroupprivate_.
   std::optional<common::OmpDeviceType> ompGroupprivateDeviceType_;
 };
 
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 221f0daeb7e11..809c27723c240 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -412,23 +412,19 @@ static void PutOpenMPDeclarativeDirectives(llvm::raw_ostream &os,
       // that `use`s this module recovers the directive from the .mod file.
       // Common-block names must be wrapped in slashes when reparsed.
       if (symbol.test(Symbol::Flag::OmpGroupPrivate)) {
-        os << "!$omp groupprivate(";
+        os << "!$omp "
+           << parser::ToLowerCaseLetters(llvm::omp::getOpenMPDirectiveName(
+                  llvm::omp::Directive::OMPD_groupprivate, version))
+           << "(";
         if (symbol.detailsIf<CommonBlockDetails>())
           os << '/' << symbol.name() << '/';
         else
           os << symbol.name();
         os << ")";
-        if (auto deviceType{decls->ompGroupprivateDeviceType()}) {
-          switch (*deviceType) {
-          case common::OmpDeviceType::Any:
-            break;
-          case common::OmpDeviceType::Host:
-            os << " device_type(host)";
-            break;
-          case common::OmpDeviceType::Nohost:
-            os << " device_type(nohost)";
-            break;
-          }
+        if (const OmpClauseSet & gp{decls->ompGroupprivate()}; gp.count()) {
+          os << " ";
+          decls->printClauseSet(
+              os, gp, parser::CharBlock{}, decls->ompGroupprivateDeviceType());
         }
         os << "\n";
       }
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 949e683a7050b..7f82f464c4b56 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2195,13 +2195,13 @@ bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) {
   // OpenMP 6.0 allows an optional device_type clause on groupprivate. Record it
   // on each listed object's ultimate symbol so lowering and .mod emission can
   // read it. An absent clause means the spec default (any).
-  std::optional<common::OmpDeviceType> device;
   if (auto *devClause{
           parser::omp::FindClause(x.v, llvm::omp::Clause::OMPC_device_type)}) {
-    device = parser::UnwrapRef<common::OmpDeviceType>(*devClause);
-  }
-
-  if (device) {
+    unsigned version{context_.langOptions().OpenMPVersion};
+    common::OmpDeviceType device{
+        parser::UnwrapRef<common::OmpDeviceType>(*devClause)};
+    WithOmpDeclarative::OmpClauseSet clauses;
+    clauses.set(llvm::omp::Clause::OMPC_device_type);
     for (const parser::OmpArgument &arg : x.v.Arguments().v) {
       if (const parser::OmpObject *object{
               parser::omp::GetArgumentObject(arg)}) {
@@ -2210,7 +2210,9 @@ bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) {
               [&](auto &d) {
                 using TypeD = llvm::remove_cvref_t<decltype(d)>;
                 if constexpr (std::is_base_of_v<WithOmpDeclarative, TypeD>) {
-                  d.set_ompGroupprivateDeviceType(*device);
+                  d.set_version(version);
+                  d.set_ompGroupprivate(clauses);
+                  d.set_ompGroupprivateDeviceType(device);
                 }
               },
               const_cast<Symbol &>(sym->GetUltimate()).details());
diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index 546ed3e2fa023..a3dffdb0af847 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -71,7 +71,8 @@ static void DumpList(llvm::raw_ostream &os, const char *label, const T &list) {
 }
 
 void WithOmpDeclarative::printClauseSet(llvm::raw_ostream &os,
-    const OmpClauseSet &clauses, parser::CharBlock name) const {
+    const OmpClauseSet &clauses, parser::CharBlock name,
+    std::optional<common::OmpDeviceType> deviceType) const {
   auto toLower = parser::ToLowerCaseLetters;
 
   size_t idx{0}, size{clauses.count()};
@@ -81,9 +82,11 @@ void WithOmpDeclarative::printClauseSet(llvm::raw_ostream &os,
     case llvm::omp::Clause::OMPC_atomic_default_mem_order:
       os << '(' << toLower(EnumToString(*ompAtomicDefaultMemOrder())) << ')';
       break;
-    case llvm::omp::Clause::OMPC_device_type:
-      os << "(" << toLower(EnumToString(*ompDeviceType())) << ')';
+    case llvm::omp::Clause::OMPC_device_type: {
+      common::OmpDeviceType dt{deviceType ? *deviceType : *ompDeviceType()};
+      os << '(' << toLower(EnumToString(dt)) << ')';
       break;
+    }
     case llvm::omp::Clause::OMPC_enter:
     case llvm::omp::Clause::OMPC_link:
       if (!name.empty()) {
@@ -116,6 +119,12 @@ llvm::raw_ostream &operator<<(
     x.printClauseSet(os, dtgt);
     os << ')';
   }
+  if (const OmpClauseSet & gp{x.ompGroupprivate()}; gp.count()) {
+    os << " OmpGroupprivateFlags:(";
+    x.printClauseSet(
+        os, gp, parser::CharBlock{}, x.ompGroupprivateDeviceType());
+    os << ')';
+  }
   return os;
 }
 

>From d956fecc4542169d9b8b16f8e4c3c3a8d4772e2c Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Tue, 9 Jun 2026 12:56:31 +0530
Subject: [PATCH 10/12] code-format

---
 flang/lib/Semantics/mod-file.cpp | 2 +-
 flang/lib/Semantics/symbol.cpp   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 809c27723c240..e61a16b612a6b 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -421,7 +421,7 @@ static void PutOpenMPDeclarativeDirectives(llvm::raw_ostream &os,
         else
           os << symbol.name();
         os << ")";
-        if (const OmpClauseSet & gp{decls->ompGroupprivate()}; gp.count()) {
+        if (const OmpClauseSet &gp{decls->ompGroupprivate()}; gp.count()) {
           os << " ";
           decls->printClauseSet(
               os, gp, parser::CharBlock{}, decls->ompGroupprivateDeviceType());
diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index a3dffdb0af847..74acc2c7d8adc 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -119,7 +119,7 @@ llvm::raw_ostream &operator<<(
     x.printClauseSet(os, dtgt);
     os << ')';
   }
-  if (const OmpClauseSet & gp{x.ompGroupprivate()}; gp.count()) {
+  if (const OmpClauseSet &gp{x.ompGroupprivate()}; gp.count()) {
     os << " OmpGroupprivateFlags:(";
     x.printClauseSet(
         os, gp, parser::CharBlock{}, x.ompGroupprivateDeviceType());

>From 1fd692f1d3310748a9f991e8325014265b97c994 Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Wed, 10 Jun 2026 15:16:42 +0530
Subject: [PATCH 11/12] [flang][OpenMP] Unify groupprivate device_type handling
 with declare_target

---
 flang/include/flang/Semantics/symbol.h     | 18 ++++----
 flang/lib/Lower/OpenMP/OpenMP.cpp          | 26 +++++-------
 flang/lib/Semantics/mod-file.cpp           | 21 ++++------
 flang/lib/Semantics/resolve-directives.cpp | 49 +++++++++++-----------
 flang/lib/Semantics/symbol.cpp             | 20 +++++----
 5 files changed, 66 insertions(+), 68 deletions(-)

diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 8a125e2a7810b..9320932eca85f 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -69,11 +69,11 @@ class WithOmpDeclarative {
   const OmpClauseSet &ompDeclTarget() const { return ompDeclTarget_; }
   void set_ompDeclTarget(OmpClauseSet clauses) { ompDeclTarget_ = clauses; }
 
-  const std::optional<common::OmpDeviceType> &ompDeviceType() const {
-    return ompDeviceType_;
+  const std::optional<common::OmpDeviceType> &ompDeclTargetDeviceType() const {
+    return ompDeclTargetDeviceType_;
   }
   void set_ompDeclTarget(common::OmpDeviceType device) {
-    ompDeviceType_ = device;
+    ompDeclTargetDeviceType_ = device;
   }
 
   const OmpClauseSet &ompGroupprivate() const { return ompGroupprivate_; }
@@ -83,15 +83,15 @@ class WithOmpDeclarative {
   ompGroupprivateDeviceType() const {
     return ompGroupprivateDeviceType_;
   }
-  void set_ompGroupprivateDeviceType(common::OmpDeviceType device) {
+  void set_ompGroupprivate(common::OmpDeviceType device) {
     ompGroupprivateDeviceType_ = device;
   }
 
-  // \p deviceType overrides the DEVICE_TYPE clause value to print (used for
-  // GROUPPRIVATE); when absent the DECLARE_TARGET device_type is used.
+  // \p dir selects which directive's DEVICE_TYPE value to print, since the
+  // clause can be carried by both DECLARE_TARGET and GROUPPRIVATE.
   void printClauseSet(llvm::raw_ostream &os, const OmpClauseSet &clauses,
-      parser::CharBlock name = parser::CharBlock{},
-      std::optional<common::OmpDeviceType> deviceType = std::nullopt) const;
+      llvm::omp::Directive dir,
+      parser::CharBlock name = parser::CharBlock{}) const;
   friend llvm::raw_ostream &operator<<(
       llvm::raw_ostream &, const WithOmpDeclarative &);
 
@@ -112,7 +112,7 @@ class WithOmpDeclarative {
   OmpClauseSet ompDeclTarget_;
   // The argument to DEVICE_TYPE clause. Only needed when the clause is
   // present in the ompDeclTarget_ set.
-  std::optional<common::OmpDeviceType> ompDeviceType_;
+  std::optional<common::OmpDeviceType> ompDeclTargetDeviceType_;
   // The set of clauses on a GROUPPRIVATE directive declaring this symbol.
   OmpClauseSet ompGroupprivate_;
   // The argument to a DEVICE_TYPE clause on a GROUPPRIVATE directive declaring
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 73e0b7e884cd8..49dfcb444d6e3 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -53,6 +53,7 @@
 #include "mlir/Support/StateStack.h"
 #include "mlir/Transforms/RegionUtils.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallSet.h"
 
 using namespace Fortran::lower::omp;
 using namespace Fortran::common::openmp;
@@ -834,7 +835,7 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
                                                     deviceTypeEnum);
 
     // omp.groupprivate takes a flat symbol reference and returns
-    // the address of the per-team copy of the global variable.
+    // the address of the per-contention group copy of the global variable.
     return mlir::omp::GroupprivateOp::create(
         firOpBuilder, currentLocation, global.resultType(), global.getSymbol(),
         deviceTypeAttr);
@@ -845,20 +846,18 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
                              semantics::Symbol::Flag::OmpGroupPrivate,
                              /*collectSymbols=*/true,
                              /*collectHostAssociatedSymbols=*/true);
-  std::set<semantics::SourceName> groupprivateSymNames;
+  llvm::SmallSet<semantics::SourceName, 8> 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];
+  for (const semantics::Symbol *sym : groupprivateSyms) {
     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())
+    if (!groupprivateSymNames.insert(sym->name()).second)
       continue;
-    groupprivateSymNames.insert(sym->name());
 
     if (const semantics::Symbol *common =
             semantics::FindCommonBlockContaining(sym->GetUltimate())) {
@@ -881,9 +880,8 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
       symGroupprivateValue = genGroupprivateOp(*sym);
     }
 
-    if (!symGroupprivateValue) {
+    if (!symGroupprivateValue)
       continue;
-    }
 
     fir::ExtendedValue sexv = converter.getSymbolExtendedValue(*sym);
     fir::ExtendedValue symGroupprivateExv =
@@ -1451,9 +1449,9 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
     }
   }
 
-  if (info.dir == llvm::omp::Directive::OMPD_teams) {
+  // TODO: groupprivate is currently only materialised for `teams` constructs.
+  if (info.dir == llvm::omp::Directive::OMPD_teams)
     groupprivatizeVars(info.converter, info.eval);
-  }
 
   if (!info.genSkeletonOnly) {
     if (ConstructQueue::const_iterator next = std::next(item);
@@ -3144,7 +3142,7 @@ genTargetOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
       return;
 
     // Skip groupprivate symbols - they don't need to be mapped because
-    // groupprivate creates its own LDS storage.
+    // groupprivate creates its own storage.
     if (sym.GetUltimate().test(semantics::Symbol::Flag::OmpGroupPrivate))
       return;
 
@@ -4747,8 +4745,7 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    lower::pft::Evaluation &eval,
                    const parser::OmpGroupprivateDirective &directive) {
   // The semantic analysis sets the flag and device_type on the
-  // symbols; omp.groupprivate is materialised on first use in a teams region
-  // by groupprivatizeVars.
+  // symbols; omp.groupprivate is materialised by groupprivatizeVars.
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
@@ -5540,8 +5537,7 @@ void Fortran::lower::genGroupprivateOp(lower::AbstractConverter &converter,
       globalInitialization(converter, firOpBuilder, sym, var, currentLocation);
   }
 
-  // The actual omp.groupprivate operation is created by groupprivatizeVars
-  // when entering a teams region.
+  // The actual omp.groupprivate operation is created by groupprivatizeVars.
 }
 
 void Fortran::lower::genThreadprivateOp(lower::AbstractConverter &converter,
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index e61a16b612a6b..89a535c6ff6f9 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -387,7 +387,7 @@ static void PutOpenMPRequirements(
       os << "!$omp "
          << parser::ToLowerCaseLetters(llvm::omp::getOpenMPDirectiveName(
                 llvm::omp::Directive::OMPD_requires, version));
-      decls->printClauseSet(os, reqs);
+      decls->printClauseSet(os, reqs, llvm::omp::Directive::OMPD_requires);
       os << "\n";
     }
   }
@@ -405,13 +405,14 @@ static void PutOpenMPDeclarativeDirectives(llvm::raw_ostream &os,
            << parser::ToLowerCaseLetters(llvm::omp::getOpenMPDirectiveName(
                   llvm::omp::Directive::OMPD_declare_target, version))
            << " ";
-        decls->printClauseSet(os, dtgt, symbol.name());
+        decls->printClauseSet(
+            os, dtgt, llvm::omp::Directive::OMPD_declare_target, symbol.name());
         os << "\n";
       }
-      // Re-emit `!$omp groupprivate` (and any recorded device_type) so a TU
-      // that `use`s this module recovers the directive from the .mod file.
-      // Common-block names must be wrapped in slashes when reparsed.
-      if (symbol.test(Symbol::Flag::OmpGroupPrivate)) {
+      // Re-emit `!$omp groupprivate` (and its device_type) so a TU that `use`s
+      // this module recovers the directive from the .mod file. Common-block
+      // names must be wrapped in slashes when reparsed.
+      if (const OmpClauseSet &gp{decls->ompGroupprivate()}; gp.count()) {
         os << "!$omp "
            << parser::ToLowerCaseLetters(llvm::omp::getOpenMPDirectiveName(
                   llvm::omp::Directive::OMPD_groupprivate, version))
@@ -420,12 +421,8 @@ static void PutOpenMPDeclarativeDirectives(llvm::raw_ostream &os,
           os << '/' << symbol.name() << '/';
         else
           os << symbol.name();
-        os << ")";
-        if (const OmpClauseSet &gp{decls->ompGroupprivate()}; gp.count()) {
-          os << " ";
-          decls->printClauseSet(
-              os, gp, parser::CharBlock{}, decls->ompGroupprivateDeviceType());
-        }
+        os << ") ";
+        decls->printClauseSet(os, gp, llvm::omp::Directive::OMPD_groupprivate);
         os << "\n";
       }
     }
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 7f82f464c4b56..26a76daa3f6ee 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2192,31 +2192,32 @@ bool OmpAttributeVisitor::Pre(const parser::OmpGroupprivateDirective &x) {
     }
   }
 
-  // OpenMP 6.0 allows an optional device_type clause on groupprivate. Record it
-  // on each listed object's ultimate symbol so lowering and .mod emission can
-  // read it. An absent clause means the spec default (any).
+  // groupprivate carries an optional device_type clause (OpenMP 6.0). Always
+  // record it, like declare_target's enter clause, so a clause-less directive
+  // still round-trips through symbol dumps and .mod files; an absent clause
+  // means the spec default (any).
+  common::OmpDeviceType device{common::OmpDeviceType::Any};
   if (auto *devClause{
           parser::omp::FindClause(x.v, llvm::omp::Clause::OMPC_device_type)}) {
-    unsigned version{context_.langOptions().OpenMPVersion};
-    common::OmpDeviceType device{
-        parser::UnwrapRef<common::OmpDeviceType>(*devClause)};
-    WithOmpDeclarative::OmpClauseSet clauses;
-    clauses.set(llvm::omp::Clause::OMPC_device_type);
-    for (const parser::OmpArgument &arg : x.v.Arguments().v) {
-      if (const parser::OmpObject *object{
-              parser::omp::GetArgumentObject(arg)}) {
-        if (const Symbol *sym{omp::GetObjectSymbol(*object)}) {
-          common::visit(
-              [&](auto &d) {
-                using TypeD = llvm::remove_cvref_t<decltype(d)>;
-                if constexpr (std::is_base_of_v<WithOmpDeclarative, TypeD>) {
-                  d.set_version(version);
-                  d.set_ompGroupprivate(clauses);
-                  d.set_ompGroupprivateDeviceType(device);
-                }
-              },
-              const_cast<Symbol &>(sym->GetUltimate()).details());
-        }
+    device = parser::UnwrapRef<common::OmpDeviceType>(*devClause);
+  }
+
+  unsigned version{context_.langOptions().OpenMPVersion};
+  WithOmpDeclarative::OmpClauseSet clauses;
+  clauses.set(llvm::omp::Clause::OMPC_device_type);
+  for (const parser::OmpArgument &arg : x.v.Arguments().v) {
+    if (const parser::OmpObject *object{parser::omp::GetArgumentObject(arg)}) {
+      if (const Symbol *sym{omp::GetObjectSymbol(*object)}) {
+        common::visit(
+            [&](auto &d) {
+              using TypeD = llvm::remove_cvref_t<decltype(d)>;
+              if constexpr (std::is_base_of_v<WithOmpDeclarative, TypeD>) {
+                d.set_version(version);
+                d.set_ompGroupprivate(clauses);
+                d.set_ompGroupprivate(device);
+              }
+            },
+            const_cast<Symbol &>(sym->GetUltimate()).details());
       }
     }
   }
@@ -2316,7 +2317,7 @@ bool OmpAttributeVisitor::Pre(const parser::OmpDeclareTargetDirective &x) {
             if (device) {
               clauseSet.set(llvm::omp::Clause::OMPC_device_type);
               auto &deviceType{
-                  const_cast<decltype(device) &>(d.ompDeviceType())};
+                  const_cast<decltype(device) &>(d.ompDeclTargetDeviceType())};
               deviceType = device;
             }
           } else if constexpr (std::is_same_v<GenericDetails, TypeD> ||
diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index 74acc2c7d8adc..3560dac0f4a26 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -71,8 +71,8 @@ static void DumpList(llvm::raw_ostream &os, const char *label, const T &list) {
 }
 
 void WithOmpDeclarative::printClauseSet(llvm::raw_ostream &os,
-    const OmpClauseSet &clauses, parser::CharBlock name,
-    std::optional<common::OmpDeviceType> deviceType) const {
+    const OmpClauseSet &clauses, llvm::omp::Directive dir,
+    parser::CharBlock name) const {
   auto toLower = parser::ToLowerCaseLetters;
 
   size_t idx{0}, size{clauses.count()};
@@ -83,8 +83,13 @@ void WithOmpDeclarative::printClauseSet(llvm::raw_ostream &os,
       os << '(' << toLower(EnumToString(*ompAtomicDefaultMemOrder())) << ')';
       break;
     case llvm::omp::Clause::OMPC_device_type: {
-      common::OmpDeviceType dt{deviceType ? *deviceType : *ompDeviceType()};
-      os << '(' << toLower(EnumToString(dt)) << ')';
+      // device_type is carried by several directives; print the value
+      // recorded for the one being emitted.
+      const std::optional<common::OmpDeviceType> &dt{
+          dir == llvm::omp::Directive::OMPD_groupprivate
+              ? ompGroupprivateDeviceType()
+              : ompDeclTargetDeviceType()};
+      os << '(' << toLower(EnumToString(*dt)) << ')';
       break;
     }
     case llvm::omp::Clause::OMPC_enter:
@@ -111,18 +116,17 @@ llvm::raw_ostream &operator<<(
 
   if (const OmpClauseSet &reqs{x.ompRequires()}; reqs.count()) {
     os << " OmpRequirements:(";
-    x.printClauseSet(os, reqs);
+    x.printClauseSet(os, reqs, llvm::omp::Directive::OMPD_requires);
     os << ')';
   }
   if (const OmpClauseSet &dtgt{x.ompDeclTarget()}; dtgt.count()) {
     os << " OmpDeclareTargetFlags:(";
-    x.printClauseSet(os, dtgt);
+    x.printClauseSet(os, dtgt, llvm::omp::Directive::OMPD_declare_target);
     os << ')';
   }
   if (const OmpClauseSet &gp{x.ompGroupprivate()}; gp.count()) {
     os << " OmpGroupprivateFlags:(";
-    x.printClauseSet(
-        os, gp, parser::CharBlock{}, x.ompGroupprivateDeviceType());
+    x.printClauseSet(os, gp, llvm::omp::Directive::OMPD_groupprivate);
     os << ')';
   }
   return os;

>From 2258cad8cdafa650937e79ee4ed54d7fe8f0a9db Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Thu, 11 Jun 2026 19:31:08 +0530
Subject: [PATCH 12/12] NFC changes

---
 flang/include/flang/Semantics/symbol.h | 4 ++--
 flang/lib/Lower/OpenMP/OpenMP.cpp      | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 9320932eca85f..23c26ba733e86 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -87,8 +87,8 @@ class WithOmpDeclarative {
     ompGroupprivateDeviceType_ = device;
   }
 
-  // \p dir selects which directive's DEVICE_TYPE value to print, since the
-  // clause can be carried by both DECLARE_TARGET and GROUPPRIVATE.
+  // \p dir indicates to which declarative directive the given clauses
+  // belong to.
   void printClauseSet(llvm::raw_ostream &os, const OmpClauseSet &clauses,
       llvm::omp::Directive dir,
       parser::CharBlock name = parser::CharBlock{}) const;
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 49dfcb444d6e3..82280f38843f2 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -53,6 +53,7 @@
 #include "mlir/Support/StateStack.h"
 #include "mlir/Transforms/RegionUtils.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallSet.h"
 
 using namespace Fortran::lower::omp;
@@ -812,9 +813,8 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
   auto genGroupprivateOp = [&](const semantics::Symbol &sym) -> mlir::Value {
     std::string globalName = converter.mangleName(sym);
     fir::GlobalOp global = module.lookupSymbol<fir::GlobalOp>(globalName);
-    if (!global) {
+    if (!global)
       return mlir::Value();
-    }
 
     // The device_type modifier was recorded on the symbol during semantic
     // analysis.
@@ -850,7 +850,7 @@ static void groupprivatizeVars(lower::AbstractConverter &converter,
 
   // For a COMMON block, the GroupprivateOp is generated for the block itself
   // instead of its members.
-  llvm::SetVector<const semantics::Symbol *> commonSyms;
+  llvm::SmallPtrSet<const semantics::Symbol *, 8> commonSyms;
 
   for (const semantics::Symbol *sym : groupprivateSyms) {
     mlir::Value symGroupprivateValue;
@@ -5537,7 +5537,7 @@ void Fortran::lower::genGroupprivateOp(lower::AbstractConverter &converter,
       globalInitialization(converter, firOpBuilder, sym, var, currentLocation);
   }
 
-  // The actual omp.groupprivate operation is created by groupprivatizeVars.
+  // The actual omp.groupprivate operations are created by groupprivatizeVars.
 }
 
 void Fortran::lower::genThreadprivateOp(lower::AbstractConverter &converter,



More information about the flang-commits mailing list