[flang-commits] [flang] [llvm] [Flang][OpenMP] Support certain directives in PURE procedures (PR #212676)
via flang-commits
flang-commits at lists.llvm.org
Wed Jul 29 02:45:19 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-tablegen
Author: ShashwathiNavada
<details>
<summary>Changes</summary>
Add compiler support for directive restrictions in PURE procedures, permitting only those directives that have a "pure" property, including: metadirecitve, assume and assumes, nothing, error, and loop-transforming directives.
---
Full diff: https://github.com/llvm/llvm-project/pull/212676.diff
9 Files Affected:
- (modified) flang/lib/Semantics/check-omp-structure.cpp (+18)
- (modified) flang/lib/Semantics/check-omp-structure.h (+2)
- (added) flang/test/Semantics/OpenMP/pure-procedure.f90 (+76)
- (modified) llvm/include/llvm/Frontend/Directive/DirectiveBase.td (+5)
- (modified) llvm/include/llvm/Frontend/OpenMP/OMP.td (+16)
- (modified) llvm/include/llvm/TableGen/DirectiveEmitter.h (+2)
- (modified) llvm/test/TableGen/directive1.td (+8)
- (modified) llvm/test/TableGen/directive2.td (+8)
- (modified) llvm/utils/TableGen/Basic/DirectiveEmitter.cpp (+22)
``````````diff
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index a12e12725dd28..cc7888067fecf 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..7cf57c3fda68a 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 = [
@@ -817,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>]> {
@@ -834,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>]> {
@@ -849,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>]> {
@@ -862,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 = [
@@ -936,6 +942,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 +1011,7 @@ def OMP_Fuse : Directive<[Spelling<"fuse">]> {
let allowedLoopModifiers = [
LM_Fused,
];
+ let isPure = true;
}
def OMP_Interchange : Directive<[Spelling<"interchange">]> {
let allowedClauses = [
@@ -1017,6 +1025,7 @@ def OMP_Interchange : Directive<[Spelling<"interchange">]> {
let allowedLoopModifiers = [
LM_Interchanged,
];
+ let isPure = true;
}
def OMP_interop : Directive<[Spelling<"interop">]> {
let allowedClauses = [
@@ -1067,6 +1076,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 +1087,7 @@ def OMP_Nothing : Directive<[Spelling<"nothing">]> {
let allowedLoopModifiers = [
LM_Identity,
];
+ let isPure = true;
}
def OMP_Ordered : Directive<[Spelling<"ordered">]> {
let allowedClauses = [
@@ -1134,6 +1145,7 @@ def OMP_Reverse : Directive<[Spelling<"reverse">]> {
let allowedLoopModifiers = [
LM_Reversed,
];
+ let isPure = true;
}
def OMP_Scan : Directive<[Spelling<"scan">]> {
let allowedOnceClauses = [
@@ -1432,6 +1444,7 @@ def OMP_Tile : Directive<[Spelling<"tile">]> {
LM_Grid,
LM_Intratile,
];
+ let isPure = true;
}
def OMP_Stripe : Directive<[Spelling<"stripe">]> {
let allowedClauses = [
@@ -1446,6 +1459,7 @@ def OMP_Stripe : Directive<[Spelling<"stripe">]> {
LM_Grid,
LM_Offsets,
];
+ let isPure = true;
}
def OMP_Split : Directive<[Spelling<"split">]> {
let allowedClauses = [
@@ -1462,6 +1476,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 +1496,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/test/TableGen/directive1.td b/llvm/test/TableGen/directive1.td
index a82ca6e2451ce..40c5e96d080ba 100644
--- a/llvm/test/TableGen/directive1.td
+++ b/llvm/test/TableGen/directive1.td
@@ -190,6 +190,14 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
// CHECK-NEXT: #endif
// CHECK-NEXT: }
// CHECK-EMPTY:
+// CHECK-NEXT: constexpr bool isDirectivePure(Directive Dir) {
+// CHECK-NEXT: switch (Dir) {
+// CHECK-NEXT: return true;
+// CHECK-NEXT: default:
+// CHECK-NEXT: return false;
+// CHECK-NEXT: } // switch (Dir)
+// CHECK-NEXT: }
+// CHECK-EMPTY:
// CHECK-NEXT: constexpr SourceLanguage getDirectiveLanguages(Directive D) {
// CHECK-NEXT: switch (D) {
// CHECK-NEXT: case TDLD_dira:
diff --git a/llvm/test/TableGen/directive2.td b/llvm/test/TableGen/directive2.td
index 76252e5f92303..2943a8eb3f1a6 100644
--- a/llvm/test/TableGen/directive2.td
+++ b/llvm/test/TableGen/directive2.td
@@ -155,6 +155,14 @@ def TDL_DirA : Directive<[Spelling<"dira">]> {
// CHECK-NEXT: #endif
// CHECK-NEXT: }
// CHECK-EMPTY:
+// CHECK-NEXT: constexpr bool isDirectivePure(Directive Dir) {
+// CHECK-NEXT: switch (Dir) {
+// CHECK-NEXT: return true;
+// CHECK-NEXT: default:
+// CHECK-NEXT: return false;
+// CHECK-NEXT: } // switch (Dir)
+// CHECK-NEXT: }
+// CHECK-EMPTY:
// CHECK-NEXT: constexpr SourceLanguage getDirectiveLanguages(Directive D) {
// CHECK-NEXT: switch (D) {
// CHECK-NEXT: case TDLD_dira:
diff --git a/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp b/llvm/utils/TableGen/Basic/DirectiveEmitter.cpp
index 9c52a687e5f96..33a725f31b11b 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);
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/212676
More information about the flang-commits
mailing list