[flang-commits] [flang] [llvm] [Flang][OpenMP] Support certain directives in PURE procedures (PR #212676)

via flang-commits flang-commits at lists.llvm.org
Tue Jul 28 21:54:03 PDT 2026


https://github.com/ShashwathiNavada updated https://github.com/llvm/llvm-project/pull/212676

>From 1c3c1b7d9e3047650abfb7a80e1ed346c18b97ee Mon Sep 17 00:00:00 2001
From: ShashwathiNavada <shashwathinavada at gmail.com>
Date: Tue, 28 Jul 2026 22:52:45 -0500
Subject: [PATCH 1/2] [FLANG][OpenMP] support certain directives in PURE
 procedures

---
 flang/lib/Semantics/check-omp-structure.cpp   | 18 +++++
 flang/lib/Semantics/check-omp-structure.h     |  2 +
 .../test/Semantics/OpenMP/pure-procedure.f90  | 76 +++++++++++++++++++
 .../llvm/Frontend/Directive/DirectiveBase.td  |  5 ++
 llvm/include/llvm/Frontend/OpenMP/OMP.td      | 12 +++
 llvm/include/llvm/TableGen/DirectiveEmitter.h |  2 +
 .../utils/TableGen/Basic/DirectiveEmitter.cpp | 22 ++++++
 7 files changed, 137 insertions(+)
 create mode 100644 flang/test/Semantics/OpenMP/pure-procedure.f90

diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index f77382c9045fc..76d0b0266838d 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -945,6 +945,22 @@ void OmpStructureChecker::CheckDirectiveDeprecation(
   // one another, but only the top-level directive should cause a warning.
 }
 
+void OmpStructureChecker::CheckDirectiveInPureProcedure(
+    parser::CharBlock source, llvm::omp::Directive id) {
+  // A directive that does not have the "pure" property is not permitted to
+  // appear in a Fortran PURE procedure, since it may introduce side effects.
+  if (llvm::omp::isDirectivePure(id)) {
+    return;
+  }
+  const Scope &scope{context_.FindScope(source)};
+  if (FindPureProcedureContaining(scope)) {
+    unsigned version{context_.langOptions().OpenMPVersion};
+    context_.Say(source,
+        "The OpenMP directive '%s' is not allowed in a PURE procedure"_err_en_US,
+        parser::omp::GetUpperName(id, version));
+  }
+}
+
 std::pair<const parser::OmpClause *, const parser::OmpClause *>
 OmpStructureChecker::FindMutuallyExclusiveClauses(
     llvm::omp::ClauseSet exclusive,
@@ -1293,6 +1309,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPConstruct &x) {
   PushContextAndClauseSets(dirName.source, dirName.v);
   dirStack_.push_back(&GetOmpDirectiveSpecification(x));
   CheckDirectiveDeprecation(x);
+  CheckDirectiveInPureProcedure(dirName.source, dirName.v);
 
   // Verify clauses
   common::visit(
@@ -1349,6 +1366,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPDeclarativeConstruct &x) {
   CheckClauses(dirName, llvm::iterator_range(dirStack_.back()->Clauses().v),
       llvm::iterator_range(std::list<parser::OmpClause>{}));
 
+  CheckDirectiveInPureProcedure(dirName.source, dirName.v);
   EnterDirectiveNest(DeclarativeNest);
 }
 
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index f8aa47043a614..f275b2a6bd1e5 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -346,6 +346,8 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
   void CheckDirectiveSpelling(
       parser::CharBlock spelling, llvm::omp::Directive id);
   void CheckDirectiveDeprecation(const parser::OpenMPConstruct &x);
+  void CheckDirectiveInPureProcedure(
+      parser::CharBlock source, llvm::omp::Directive id);
   void CheckClauses(parser::OmpDirectiveName dirName,
       llvm::iterator_range<ClauseIterator> beginClauses,
       llvm::iterator_range<ClauseIterator> endClauses);
diff --git a/flang/test/Semantics/OpenMP/pure-procedure.f90 b/flang/test/Semantics/OpenMP/pure-procedure.f90
new file mode 100644
index 0000000000000..0270c1d5e0b79
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/pure-procedure.f90
@@ -0,0 +1,76 @@
+! RUN: %python %S/../test_errors.py %s %flang_fc1 -fopenmp -fopenmp-version=60
+
+! Only directives that have the "pure" property are permitted to appear in a
+! Fortran PURE procedure. These are: metadirective, assume, assumes, nothing,
+! error, and the loop-transforming directives (tile, unroll, reverse,
+! interchange, fuse, split, stripe).
+! (The SPLIT directive is omitted below only because its required COUNTS clause
+! is not yet parsed; it is still marked pure.)
+
+module m
+contains
+  pure subroutine pure_ok(a, n)
+    !$omp assumes no_openmp
+    integer, intent(in) :: n
+    integer, intent(inout) :: a(n)
+    integer :: i, j
+    !$omp nothing
+    !$omp assume no_openmp
+    !$omp end assume
+    !$omp metadirective when(user={condition(.true.)}: nothing)
+    !$omp error at(execution) severity(warning) message("ok")
+    !$omp tile sizes(4)
+    do i = 1, n
+      a(i) = a(i) + 1
+    end do
+    !$omp unroll partial(2)
+    do i = 1, n
+      a(i) = a(i) * 2
+    end do
+    !$omp reverse
+    do i = 1, n
+      a(i) = a(i) - 1
+    end do
+    !$omp interchange
+    do i = 1, n
+      do j = 1, n
+        a(i) = a(i) + j
+      end do
+    end do
+    !$omp stripe sizes(4)
+    do i = 1, n
+      a(i) = a(i) + 1
+    end do
+    !$omp fuse
+    do i = 1, n
+      a(i) = a(i) + 1
+    end do
+    do i = 1, n
+      a(i) = a(i) + 2
+    end do
+  end subroutine
+
+  pure subroutine pure_bad(a, n, r)
+    integer, intent(in) :: n
+    integer, intent(inout) :: a(n)
+    integer, intent(out) :: r
+    integer :: i
+    !ERROR: The OpenMP directive 'PARALLEL' is not allowed in a PURE procedure
+    !$omp parallel
+    !$omp end parallel
+    !ERROR: The OpenMP directive 'BARRIER' is not allowed in a PURE procedure
+    !$omp barrier
+    !ERROR: The OpenMP directive 'ATOMIC' is not allowed in a PURE procedure
+    !$omp atomic
+    r = r + 1
+    !ERROR: The OpenMP directive 'PARALLEL DO' is not allowed in a PURE procedure
+    !$omp parallel do
+    do i = 1, n
+      a(i) = a(i) + 1
+    end do
+    !$omp end parallel do
+    !ERROR: The OpenMP directive 'TASK' is not allowed in a PURE procedure
+    !$omp task
+    !$omp end task
+  end subroutine
+end module
diff --git a/llvm/include/llvm/Frontend/Directive/DirectiveBase.td b/llvm/include/llvm/Frontend/Directive/DirectiveBase.td
index 8c4fb1325400b..d41741bdd03c2 100644
--- a/llvm/include/llvm/Frontend/Directive/DirectiveBase.td
+++ b/llvm/include/llvm/Frontend/Directive/DirectiveBase.td
@@ -267,4 +267,9 @@ class Directive<list<Spelling> ss> {
 
   // The languages that allow this directive. Default: all languages.
   list<SourceLanguage> languages = [L_C, L_Fortran];
+
+  // Whether the directive has the "pure" property, i.e. it is permitted to
+  // appear in a context that must be free of side effects, such as a Fortran
+  // PURE procedure. Defaults to false.
+  bit isPure = false;
 }
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.td b/llvm/include/llvm/Frontend/OpenMP/OMP.td
index dc2af1e62cfa4..4393ea37a9121 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.td
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td
@@ -680,6 +680,7 @@ def OMP_Assumes : Directive<[Spelling<"assumes">]> {
     VersionedClause<OMPC_NoOpenMPRoutines, 51>,
     VersionedClause<OMPC_NoParallelism, 51>,
   ];
+  let isPure = true;
 }
 def OMP_Assume : Directive<[Spelling<"assume">]> {
   let association = AS_Block;
@@ -693,6 +694,7 @@ def OMP_Assume : Directive<[Spelling<"assume">]> {
     VersionedClause<OMPC_NoOpenMPRoutines, 51>,
     VersionedClause<OMPC_NoParallelism, 51>,
   ];
+  let isPure = true;
 }
 def OMP_Atomic : Directive<[Spelling<"atomic">]> {
   let allowedOnceClauses = [
@@ -936,6 +938,7 @@ def OMP_Error : Directive<[Spelling<"error">]> {
   ];
   let association = AS_None;
   let category = CA_Utility;
+  let isPure = true;
 }
 def OMP_Flatten : Directive<[Spelling<"flatten">]> {
   let allowedClauses = [
@@ -1004,6 +1007,7 @@ def OMP_Fuse : Directive<[Spelling<"fuse">]> {
   let allowedLoopModifiers = [
     LM_Fused,
   ];
+  let isPure = true;
 }
 def OMP_Interchange : Directive<[Spelling<"interchange">]> {
   let allowedClauses = [
@@ -1017,6 +1021,7 @@ def OMP_Interchange : Directive<[Spelling<"interchange">]> {
   let allowedLoopModifiers = [
     LM_Interchanged,
   ];
+  let isPure = true;
 }
 def OMP_interop : Directive<[Spelling<"interop">]> {
   let allowedClauses = [
@@ -1067,6 +1072,7 @@ def OMP_Metadirective : Directive<[Spelling<"metadirective">]> {
   ];
   let association = AS_None;
   let category = CA_Meta;
+  let isPure = true;  
 }
 def OMP_Nothing : Directive<[Spelling<"nothing">]> {
   let allowedClauses = [
@@ -1077,6 +1083,7 @@ def OMP_Nothing : Directive<[Spelling<"nothing">]> {
   let allowedLoopModifiers = [
     LM_Identity,
   ];
+  let isPure = true;
 }
 def OMP_Ordered : Directive<[Spelling<"ordered">]> {
   let allowedClauses = [
@@ -1134,6 +1141,7 @@ def OMP_Reverse : Directive<[Spelling<"reverse">]> {
   let allowedLoopModifiers = [
     LM_Reversed,
   ];
+  let isPure = true;
 }
 def OMP_Scan : Directive<[Spelling<"scan">]> {
   let allowedOnceClauses = [
@@ -1432,6 +1440,7 @@ def OMP_Tile : Directive<[Spelling<"tile">]> {
     LM_Grid,
     LM_Intratile,
   ];
+  let isPure = true;
 }
 def OMP_Stripe : Directive<[Spelling<"stripe">]> {
   let allowedClauses = [
@@ -1446,6 +1455,7 @@ def OMP_Stripe : Directive<[Spelling<"stripe">]> {
     LM_Grid,
     LM_Offsets,
   ];
+  let isPure = true;
 }
 def OMP_Split : Directive<[Spelling<"split">]> {
   let allowedClauses = [
@@ -1462,6 +1472,7 @@ def OMP_Split : Directive<[Spelling<"split">]> {
   let allowedLoopModifiers = [
     LM_Split,
   ];
+  let isPure = true;
 }
 def OMP_Unknown : Directive<[Spelling<"unknown">]> {
   let isDefault = true;
@@ -1481,6 +1492,7 @@ def OMP_Unroll : Directive<[Spelling<"unroll">]> {
   let allowedLoopModifiers = [
     LM_Unrolled,
   ];
+  let isPure = true;
 }
 def OMP_Workshare : Directive<[Spelling<"workshare">]> {
   let allowedOnceClauses = [
diff --git a/llvm/include/llvm/TableGen/DirectiveEmitter.h b/llvm/include/llvm/TableGen/DirectiveEmitter.h
index 00b2cec453aef..9f851ffd2ffea 100644
--- a/llvm/include/llvm/TableGen/DirectiveEmitter.h
+++ b/llvm/include/llvm/TableGen/DirectiveEmitter.h
@@ -274,6 +274,8 @@ class Directive : public BaseRecord {
     return Def->getValueAsListOfDefs("allowedLoopModifiers");
   }
 
+  bool isPure() const { return Def->getValueAsBit("isPure"); }
+
   // Clang uses a different format for names of its directives enum.
   std::string getClangAccSpelling() const {
     StringRef Name = getSpellingForIdentifier();
diff --git a/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp b/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
index 390cb37cb3c94..833bd15ea4548 100644
--- a/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
@@ -899,6 +899,26 @@ static void generateGetDirectiveCategory(const DirectiveLanguage &DirLang,
   OS << "}\n";
 }
 
+static void generateIsDirectivePure(const DirectiveLanguage &DirLang,
+                                    raw_ostream &OS) {
+  OS << "constexpr bool isDirectivePure(Directive Dir) {\n";
+  OS << "  switch (Dir) {\n";
+
+  StringRef Prefix = DirLang.getDirectivePrefix();
+
+  for (const Record *R : DirLang.getDirectives()) {
+    Directive D(R);
+    if (!D.isPure())
+      continue;
+    OS << "  case " << getIdentifierName(R, Prefix) << ":\n";
+  }
+  OS << "    return true;\n";
+  OS << "  default:\n";
+  OS << "    return false;\n";
+  OS << "  } // switch (Dir)\n";
+  OS << "}\n";
+}
+
 static void generateGetDirectiveLanguages(const DirectiveLanguage &DirLang,
                                           raw_ostream &OS) {
   OS << "constexpr SourceLanguage getDirectiveLanguages(Directive D) {\n";
@@ -1383,6 +1403,8 @@ static void emitDirectivesConstexprImpl(const DirectiveLanguage &DirLang,
   OS << "\n";
   generateGetDirectiveCategory(DirLang, OS);
   OS << "\n";
+  generateIsDirectivePure(DirLang, OS);
+  OS << "\n";
   generateGetDirectiveLanguages(DirLang, OS);
 }
 

>From 6838e401fdd247502eeb4bc88aa010a0efef47f4 Mon Sep 17 00:00:00 2001
From: ShashwathiNavada <shashwathinavada at gmail.com>
Date: Tue, 28 Jul 2026 23:53:45 -0500
Subject: [PATCH 2/2] Added the property to the directives that was missed
 earlier

---
 llvm/include/llvm/Frontend/OpenMP/OMP.td | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.td b/llvm/include/llvm/Frontend/OpenMP/OMP.td
index 4393ea37a9121..7cf57c3fda68a 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.td
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td
@@ -819,6 +819,7 @@ def OMP_DeclareReduction : Directive<[Spelling<"declare reduction", 1, 52>,
   ];
   let association = AS_None;
   let category = CA_Declarative;
+  let isPure = true;
 }
 def OMP_DeclareSimd : Directive<[Spelling<"declare simd", 1, 52>,
                                  Spelling<"declare_simd", 60>]> {
@@ -836,6 +837,7 @@ def OMP_DeclareSimd : Directive<[Spelling<"declare simd", 1, 52>,
   ];
   let association = AS_Declaration;
   let category = CA_Declarative;
+  let isPure = true;
 }
 def OMP_DeclareTarget : Directive<[Spelling<"declare target", 1, 52>,
                                    Spelling<"declare_target", 60>]> {
@@ -851,6 +853,7 @@ def OMP_DeclareTarget : Directive<[Spelling<"declare target", 1, 52>,
   ];
   let association = AS_None;
   let category = CA_Declarative;
+  let isPure = true;
 }
 def OMP_DeclareVariant : Directive<[Spelling<"declare variant", 1, 52>,
                                     Spelling<"declare_variant", 60>]> {
@@ -864,6 +867,7 @@ def OMP_DeclareVariant : Directive<[Spelling<"declare variant", 1, 52>,
   let association = AS_Declaration;
   let category = CA_Declarative;
   let languages = [L_C];
+  let isPure = true;
 }
 def OMP_Depobj : Directive<[Spelling<"depobj">]> {
   let allowedClauses = [



More information about the flang-commits mailing list