[flang-commits] [flang] [Flang][OpenMP][Sema] Add OpenMP warning when mapping local descriptors to device on enter without a corresponding exit (PR #205580)

Akash Banerjee via flang-commits flang-commits at lists.llvm.org
Wed Jun 24 08:57:43 PDT 2026


https://github.com/TIFitis updated https://github.com/llvm/llvm-project/pull/205580

>From ae606ede19a4eb9cb131305bacfd28a783a513fd Mon Sep 17 00:00:00 2001
From: agozillon <Andrew.Gozillon at amd.com>
Date: Wed, 10 Jun 2026 10:14:36 +0200
Subject: [PATCH] [Flang][OpenMP][Sema] Add OpenMP warning when mapping local
 descriptors to device on enter without a corresponding exit (#201060)

This PR aims to add a new warning to Flang that will emit when a user
tries to map a local/temporary descriptor to device on an enter
directive without also applying it to a corresponding exit directive.
This problem can cause some pretty unique and difficult to track down
errors in programs as it can result in a user unintentionally locking
into place a stack allocated descriptor that has fallen out of scope,
which can result in a later clash with another stack allocated variable
that's being mapped and just happens to reside in the old descriptor
address range.

So this PR attempts to warn about this problem to prevent users doing
so, it's of note that we handle some of these cases in our
MapInfoFinalization pass, but I believe we should still include these
cases for portability reasons and incase we ever backtrack on our
decision to silently support some of these cases.

Made this warning as it was a suggestion from Michael Klemm and seemed
like a good PR to add to guide users to avoid this pattern (as it
unfortunately seems to be a common one that pops up). I'll perhaps look
into an optimization pass that tries to resolve some of these cases
silently in the future, but this will have to do in the meantime.
---
 flang/include/flang/Semantics/openmp-utils.h  |  6 ++
 flang/lib/Semantics/check-omp-structure.cpp   | 54 +++++++++++
 flang/lib/Semantics/check-omp-structure.h     |  8 ++
 flang/lib/Semantics/openmp-utils.cpp          | 23 +++++
 ...arget-enter-data-temp-descriptor-omp61.f90 | 97 +++++++++++++++++++
 .../target-enter-data-temp-descriptor.f90     | 93 ++++++++++++++++++
 6 files changed, 281 insertions(+)
 create mode 100644 flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90
 create mode 100644 flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index d2bfeca68bf84..75bab5f6cb8ec 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -119,6 +119,12 @@ const Symbol *GetHostSymbol(const Symbol &sym);
 bool IsMapEnteringType(parser::OmpMapType::Value type);
 bool IsMapExitingType(parser::OmpMapType::Value type);
 
+// Returns true if the symbol has a temporary stack-allocated descriptor.
+// This includes assumed-shape and assumed-rank dummy arguments that are
+// not allocatable or pointer. These descriptors are created on the caller's
+// stack and become invalid after the function returns.
+bool HasTemporaryStackDescriptor(const Symbol &symbol);
+
 MaybeExpr GetEvaluateExpr(const parser::Expr &parserExpr);
 template <typename T> MaybeExpr GetEvaluateExpr(const T &inp) {
   return GetEvaluateExpr(parser::UnwrapRef<parser::Expr>(inp));
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 81600fa1ddbb9..d4572ec685e61 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -142,7 +142,27 @@ void OmpStructureChecker::Enter(const parser::SubroutineStmt &x) {
   scopeStack_.push_back(sym->scope());
 }
 
+void OmpStructureChecker::CheckTempDescriptorMappings() {
+  unsigned version{context_.langOptions().OpenMPVersion};
+  for (const auto &[symbol, source] : tempDescriptorEnterMaps_) {
+    if (tempDescriptorExitMaps_.find(symbol) == tempDescriptorExitMaps_.end()) {
+      if (version >= 61) {
+        context_.Warn(common::UsageWarning::OpenMPUsage, source,
+            "The map of '%s' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data"_warn_en_US,
+            symbol->name());
+      } else {
+        context_.Warn(common::UsageWarning::OpenMPUsage, source,
+            "The map of '%s' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference"_warn_en_US,
+            symbol->name());
+      }
+    }
+  }
+  tempDescriptorEnterMaps_.clear();
+  tempDescriptorExitMaps_.clear();
+}
+
 void OmpStructureChecker::Enter(const parser::EndSubroutineStmt &x) {
+  CheckTempDescriptorMappings();
   scopeStack_.pop_back();
 }
 
@@ -152,6 +172,7 @@ void OmpStructureChecker::Enter(const parser::FunctionStmt &x) {
 }
 
 void OmpStructureChecker::Enter(const parser::EndFunctionStmt &x) {
+  CheckTempDescriptorMappings();
   scopeStack_.pop_back();
 }
 
@@ -161,6 +182,7 @@ void OmpStructureChecker::Enter(const parser::MpSubprogramStmt &x) {
 }
 
 void OmpStructureChecker::Enter(const parser::EndMpSubprogramStmt &x) {
+  CheckTempDescriptorMappings();
   scopeStack_.pop_back();
 }
 
@@ -4646,6 +4668,38 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Map &x) {
       }
     }
   }
+
+  // If we are an enter or exit map, iterate over the maps and add them to
+  // containers that track if the symbol has been referenced in both an
+  // enter/exit map in the current scope, if it falls into the category of
+  // having a temporary stack descriptor. If we have reference modifiers, we
+  // ignore the warning and trust that the user knows what they are doing
+  // already, as they are aware the type comes with a descriptor and pointer
+  // combination.
+  //
+  // We will utilise this information to emit a warning later if the neccesary
+  // conditions are met, where we have an enter map without a corresponding exit
+  // in the current scope.
+  bool hasRefModifier{
+      OmpGetUniqueModifier<parser::OmpRefModifier>(modifiers) != nullptr};
+  if (!hasRefModifier &&
+      (llvm::is_contained(leafs, Directive::OMPD_target_enter_data) ||
+          llvm::is_contained(leafs, Directive::OMPD_target_exit_data))) {
+    for (const parser::OmpObject &object : objects.v) {
+      if (const Symbol *sym{GetObjectSymbol(object, /*ultimate=*/true)}) {
+        if (HasTemporaryStackDescriptor(*sym)) {
+          auto maybeSource{GetObjectSource(object)};
+          parser::CharBlock source{
+              maybeSource.value_or(GetContext().clauseSource)};
+          if (llvm::is_contained(leafs, Directive::OMPD_target_enter_data)) {
+            tempDescriptorEnterMaps_.emplace(sym, source);
+          } else {
+            tempDescriptorExitMaps_.insert(sym);
+          }
+        }
+      }
+    }
+  }
 }
 
 void OmpStructureChecker::Enter(const parser::OmpClause::Schedule &x) {
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 4499e2a213384..256383d890cab 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -368,6 +368,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
       const parser::OmpClause &initClause);
   void CheckAllowedRequiresClause(llvm::omp::Clause clause);
   void AddEndDirectiveClauses(const parser::OmpClauseList &clauses);
+  void CheckTempDescriptorMappings();
 
   void EnterDirectiveNest(const int index) { directiveNest_[index]++; }
   void ExitDirectiveNest(const int index) { directiveNest_[index]--; }
@@ -394,6 +395,13 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   // IF clauses that referenced them. If there was no modifier, the entire
   // directive is assumed to be listed.
   std::map<llvm::omp::Directive, parser::CharBlock> ifLeafs_;
+
+  // Track symbols with temporary stack descriptors mapped in TARGET ENTER DATA
+  // and symbols mapped in TARGET EXIT DATA within the current function scope.
+  // Used to warn about potential issues with mapping temporary descriptors.
+  std::multimap<const Symbol *, parser::CharBlock> tempDescriptorEnterMaps_;
+  std::set<const Symbol *> tempDescriptorExitMaps_;
+
   // Stack of nested DO loops and OpenMP constructs.
   // This is used to verify DO loop nest for DOACROSS, and branches into
   // and out of OpenMP constructs.
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 0556920877f45..cfdf2f8eb56c9 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -343,6 +343,29 @@ bool IsMapExitingType(parser::OmpMapType::Value type) {
   }
 }
 
+// This function aims to return true when a symbol is going to result
+// in a temporary stack descriptor being allocated for it in the
+// lowering that may pose an issue for data mapping if left on
+// device accidentally.
+bool HasTemporaryStackDescriptor(const Symbol &symbol) {
+  const Symbol &ultimate(symbol.GetUltimate());
+  bool isDummy = IsDummy(ultimate);
+
+  if (IsAllocatableOrPointer(ultimate)) {
+    return !isDummy;
+  }
+
+  if (!isDummy) {
+    return false;
+  }
+
+  if (const auto *obj = ultimate.detailsIf<ObjectEntityDetails>()) {
+    return obj->IsAssumedShape() || obj->IsAssumedRank();
+  }
+
+  return false;
+}
+
 static MaybeExpr GetEvaluateExprFromTyped(const parser::TypedExpr &typedExpr) {
   // ForwardOwningPointer           typedExpr
   // `- GenericExprWrapper          ^.get()
diff --git a/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90 b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90
new file mode 100644
index 0000000000000..e766b1b44d0fa
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor-omp61.f90
@@ -0,0 +1,97 @@
+! RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=61 -Werror -Wno-experimental-option
+
+! Check for OpenMP 6.1+ specific warning that includes ref_ptee suggestion
+! when mapping variables with temporary stack descriptors on TARGET ENTER DATA
+! without a corresponding TARGET EXIT DATA.
+
+subroutine test_assumed_shape_warning(arr)
+  integer, intent(inout), dimension(:,:) :: arr(:)
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data [-Wopenmp-usage]
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_assumed_rank_warning(arr)
+  integer, intent(inout) :: arr(..)
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data [-Wopenmp-usage]
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_local_allocatable_warning()
+  integer, allocatable :: local_arr(:)
+  allocate(local_arr(100))
+  !WARNING: The map of 'local_arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data [-Wopenmp-usage]
+  !$omp target enter data map(to: local_arr)
+  deallocate(local_arr)
+end subroutine
+
+subroutine test_local_pointer_warning()
+  integer, pointer :: local_ptr(:)
+  allocate(local_ptr(100))
+  !WARNING: The map of 'local_ptr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data [-Wopenmp-usage]
+  !$omp target enter data map(to: local_ptr)
+  deallocate(local_ptr)
+end subroutine
+
+module test_module
+contains
+  subroutine test_module_procedure_warning(arr)
+    integer, intent(inout) :: arr(:)
+    !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference. To avoid mapping the descriptor utilize OpenMP's ref_ptee reference modifier to map just the data [-Wopenmp-usage]
+    !$omp target enter data map(to: arr)
+  end subroutine
+
+  subroutine test_module_procedure_with_exit(arr)
+    integer, intent(inout) :: arr(:)
+    !$omp target enter data map(to: arr)
+    !$omp target exit data map(from: arr)
+  end subroutine
+end module
+
+! Test cases where warnings should not be emitted, the test_errors.py script
+! should fail if we emit errors for these that are not checked, so no need to
+! verify with an explicit check.
+
+subroutine test_ref_ptee_no_warning(arr)
+  integer, intent(inout) :: arr(:)
+  !$omp target enter data map(ref_ptee, to: arr)
+end subroutine
+
+subroutine test_ref_ptr_no_warning(arr)
+  integer, intent(inout) :: arr(:)
+  !$omp target enter data map(ref_ptr, to: arr)
+end subroutine
+
+subroutine test_ref_ptr_ptee_no_warning(arr)
+  integer, intent(inout) :: arr(:)
+  !$omp target enter data map(ref_ptr_ptee, to: arr)
+end subroutine
+
+subroutine test_with_exit_data(arr)
+  integer, intent(inout) :: arr(:)
+  !$omp target enter data map(to: arr)
+  !$omp target exit data map(from: arr)
+end subroutine
+
+subroutine test_explicit_shape_no_warning(arr, n)
+  integer, intent(in) :: n
+  integer, intent(inout) :: arr(n)
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_local_allocatable_with_exit()
+  integer, allocatable :: local_arr(:)
+  allocate(local_arr(100))
+  !$omp target enter data map(to: local_arr)
+  !$omp target exit data map(from: local_arr)
+  deallocate(local_arr)
+end subroutine
+
+subroutine test_allocatable_dummy_no_warning(arr)
+  integer, allocatable, intent(inout) :: arr(:)
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_pointer_dummy_no_warning(ptr)
+  integer, pointer, intent(inout) :: ptr(:)
+  !$omp target enter data map(to: ptr)
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90 b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90
new file mode 100644
index 0000000000000..bd1eb98ebec60
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/target-enter-data-temp-descriptor.f90
@@ -0,0 +1,93 @@
+! RUN: %python %S/../test_errors.py %s %flang -fopenmp -Werror -fopenmp-version=52 -Wno-experimental-option
+
+! Check for warning when mapping variables with temporary stack descriptors
+! (assumed-shape, assumed-rank, local allocatables, local pointers) on
+! TARGET ENTER DATA without a corresponding TARGET EXIT DATA in the same scope.
+
+subroutine test_assumed_shape_warning(arr)
+  integer, intent(inout) :: arr(:)
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_assumed_shape_2d_warning(arr)
+  integer, intent(inout) :: arr(:,:)
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_assumed_rank_warning(arr)
+  integer, intent(inout) :: arr(..)
+  !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_local_pointer_warning()
+  integer, pointer :: local_ptr(:)
+  allocate(local_ptr(100))
+  !WARNING: The map of 'local_ptr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+  !$omp target enter data map(to: local_ptr)
+  deallocate(local_ptr)
+end subroutine
+
+subroutine test_local_allocatable_warning()
+  integer, allocatable :: local_arr(:)
+  allocate(local_arr(100))
+  !WARNING: The map of 'local_arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+  !$omp target enter data map(to: local_arr)
+  deallocate(local_arr)
+end subroutine
+
+module test_module
+contains
+  subroutine test_module_procedure_warning(arr)
+    integer, intent(inout) :: arr(:)
+    !WARNING: The map of 'arr' may include a descriptor that is created locally. Mapping this descriptor without an appropriate TARGET EXIT DATA in the same scope may result in the device retaining an invalid descriptor reference [-Wopenmp-usage]
+    !$omp target enter data map(to: arr)
+  end subroutine
+
+  subroutine test_module_procedure_with_exit(arr)
+    integer, intent(inout) :: arr(:)
+    !$omp target enter data map(to: arr)
+    !$omp target exit data map(from: arr)
+  end subroutine
+end module
+
+! Test cases where warnings should not be emitted, the test_errors.py script
+! should fail if we emit errors for these that are not checked, so no need to
+! verify with an explicit check.
+
+subroutine test_pointer_dummy_no_warning(ptr)
+  integer, pointer, intent(inout) :: ptr(:)
+  !$omp target enter data map(to: ptr)
+end subroutine
+
+subroutine test_allocatable_dummy_no_warning(arr)
+  integer, allocatable, intent(inout) :: arr(:)
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_with_exit_data(arr)
+  integer, intent(inout) :: arr(:)
+  !$omp target enter data map(to: arr)
+  !$omp target exit data map(from: arr)
+end subroutine
+
+subroutine test_explicit_shape_no_warning(arr, n)
+  integer, intent(in) :: n
+  integer, intent(inout) :: arr(n)
+  !$omp target enter data map(to: arr)
+end subroutine
+
+subroutine test_assumed_size_no_warning(arr)
+  integer, intent(inout) :: arr(*)
+  !$omp target enter data map(to: arr(1:10))
+end subroutine
+
+subroutine test_local_allocatable_with_exit()
+  integer, allocatable :: local_arr(:)
+  allocate(local_arr(100))
+  !$omp target enter data map(to: local_arr)
+  !$omp target exit data map(from: local_arr)
+  deallocate(local_arr)
+end subroutine



More information about the flang-commits mailing list