[flang-commits] [flang] [flang][OpenMP] Support omx/ompx extension sentinels (PR #218475)
CHANDRA GHALE via flang-commits
flang-commits at lists.llvm.org
Mon Aug 24 12:54:09 PDT 2026
https://github.com/chandraghale updated https://github.com/llvm/llvm-project/pull/218475
>From 01a82a1316fd971e6f279aaffad1225dcdedaa82 Mon Sep 17 00:00:00 2001
From: Chandra Ghale <ghale at pe34genoa.hpc.amslabs.hpecorp.net>
Date: Mon, 24 Aug 2026 12:52:50 -0500
Subject: [PATCH 1/2] Support omx/ompx extension sentinels
---
flang/docs/ReleaseNotes.md | 5 +++
flang/include/flang/Parser/parse-tree.h | 12 ++++-
flang/lib/Parser/openmp-parsers.cpp | 28 ++++++++++--
flang/lib/Parser/parsing.cpp | 25 ++++++++---
flang/lib/Parser/prescan.h | 7 ++-
flang/lib/Parser/unparse.cpp | 2 +-
flang/lib/Semantics/check-omp-structure.cpp | 18 +++++++-
.../OpenMP/sentinel-extension-ignored.f90 | 34 ++++++++++++++
flang/test/Parser/OpenMP/sentinel-ompx.f90 | 28 ++++++++++++
flang/test/Parser/OpenMP/sentinel-omx.f | 45 +++++++++++++++++++
10 files changed, 189 insertions(+), 15 deletions(-)
create mode 100644 flang/test/Parser/OpenMP/sentinel-extension-ignored.f90
create mode 100644 flang/test/Parser/OpenMP/sentinel-ompx.f90
create mode 100644 flang/test/Parser/OpenMP/sentinel-omx.f
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 0ccb34be20571..ce6b987f7f6e4 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -33,6 +33,11 @@ page](https://llvm.org/releases/).
## Non-comprehensive list of changes in this release
+- Added support for the OpenMP implementation-defined extension sentinels
+ (OpenMP 5.2, section 3.1): `!$omx`, `c$omx` and `*$omx` in fixed source form
+ and `!$ompx` in free source form. These sentinels are recognized like their
+ `omp` counterparts when OpenMP is enabled.
+
## New Compiler Flags
- The warning flags with prefixes -Wopen-mp and -Wopen-acc have been deprecated in favor of corrected spellings with the respective prefixes -Wopenmp and -Wopenacc. Removal of the deprecated options is planned for LLVM 25 (July 2027).
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index ee6288539395c..dfb35967a353d 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5530,9 +5530,19 @@ struct OpenMPMisplacedEndDirective : public OmpEndDirective {
OpenMPMisplacedEndDirective, OmpEndDirective);
};
-// Unrecognized string after the !$OMP sentinel.
+// Unrecognized string after an OpenMP sentinel. isExtensionSentinel is true
+// when the sentinel was an implementation-defined extension sentinel (!$omx in
+// fixed source form or !$ompx in free source form, OpenMP 5.2 section 3.1); in
+// that case an unrecognized directive is ignored with a warning instead of
+// being reported as an error. The bool constructor is explicit so that the
+// node is not implicitly constructible from a bool: such a conversion would
+// make parse-tree visitors that provide a catch-all handler ambiguous.
struct OpenMPInvalidDirective {
using EmptyTrait = std::true_type;
+ OpenMPInvalidDirective() = default;
+ explicit OpenMPInvalidDirective(bool isExtension)
+ : isExtensionSentinel{isExtension} {}
+ bool isExtensionSentinel{false};
CharBlock source;
};
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 7016c688a572d..1df495d08a793 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -52,7 +52,18 @@ using DirectiveSet =
state.GetLocation(), std::min<size_t>(64, state.BytesRemaining()));
}
-constexpr auto startOmpLine = skipStuffBeforeStatement >> "!$OMP "_sptok;
+// Accept the standard "!$omp" sentinel as well as the implementation-defined
+// extension sentinels "!$omx" (fixed form) and "!$ompx" (free form) defined in
+// OpenMP 5.2, section 3.1. The prescanner normalizes the comment character of
+// fixed-form sentinels (c$omx, *$omx) to '!'. The sentinels are matched with
+// the "_id" literal (not "_sptok") so that a sentinel is not accepted as a
+// prefix of a longer one: "!$OMP" must not match the "!$omp" that begins
+// "!$ompx". The "_id" match fails when the token is immediately followed by
+// another identifier character.
+constexpr auto ompxSentinel = "!$OMPX"_id || "!$OMX"_id;
+constexpr auto ompSentinel = "!$OMP"_id;
+constexpr auto startOmpLine =
+ skipStuffBeforeStatement >> (ompxSentinel || ompSentinel);
constexpr auto endOmpLine = space >> endOfLine;
constexpr auto logicalConstantExpr{logical(constantExpr)};
@@ -2632,7 +2643,16 @@ static constexpr DirectiveSet GetAllDirectives() { //
TYPE_PARSER(construct<OpenMPMisplacedEndDirective>(
OmpEndDirectiveParser{GetAllDirectives()}))
-TYPE_PARSER(startOmpLine >>
- sourced(construct<OpenMPInvalidDirective>(
- maybe("BEGIN"_sptok) >> !OmpDirectiveNameParser{} >> SkipTo<'\n'>{})))
+// A string following an OpenMP sentinel that is not a recognized directive.
+// When it follows an implementation-defined extension sentinel (!$omx / !$ompx)
+// the node is flagged (true) so that semantics ignores it with a warning rather
+// than reporting an error, allowing portable use of vendor extensions
+// (OpenMP 5.2, section 3.1).
+TYPE_PARSER(skipStuffBeforeStatement >>
+ ((ompxSentinel >>
+ sourced(construct<OpenMPInvalidDirective>(maybe("BEGIN"_sptok) >>
+ !OmpDirectiveNameParser{} >> SkipTo<'\n'>{} >> pure(true)))) ||
+ (ompSentinel >>
+ sourced(construct<OpenMPInvalidDirective>(maybe("BEGIN"_sptok) >>
+ !OmpDirectiveNameParser{} >> SkipTo<'\n'>{} >> pure(false))))))
} // namespace Fortran::parser
diff --git a/flang/lib/Parser/parsing.cpp b/flang/lib/Parser/parsing.cpp
index 667d8d9297ecb..964e4b01e1b89 100644
--- a/flang/lib/Parser/parsing.cpp
+++ b/flang/lib/Parser/parsing.cpp
@@ -90,6 +90,10 @@ const SourceFile *Parsing::Prescan(const std::string &path, Options options) {
if (options.features.IsEnabled(LanguageFeature::OpenMP) ||
(options.prescanAndReformat && noneOfTheAbove)) {
prescanner.AddCompilerDirectiveSentinel("$omp");
+ // Implementation-defined extension sentinels (OpenMP 5.2, section 3.1):
+ // "$omx" in fixed form (!$omx, c$omx, *$omx) and "$ompx" in free form.
+ prescanner.AddCompilerDirectiveSentinel("$omx");
+ prescanner.AddCompilerDirectiveSentinel("$ompx");
prescanner.AddCompilerDirectiveSentinel("$"); // OMP conditional line
}
if (options.features.IsEnabled(LanguageFeature::CUDA) ||
@@ -132,12 +136,13 @@ void Parsing::EmitPreprocessedSource(
bool inContinuation{false};
bool lineWasBlankBefore{true};
const AllSources &allSources{allCooked().allSources()};
- // All directives that flang supports are known to have a length of 4 chars,
- // except for OpenMP conditional compilation lines (!$).
- constexpr int directiveNameLength{4};
// We need to know the current directive in order to provide correct
- // continuation for the directive
+ // continuation for the directive. The sentinel is accumulated until the
+ // blank that follows it, so sentinels of any length are handled: the
+ // 4-character sentinels (e.g. "$omp", "$omx", "$acc") as well as the
+ // 5-character implementation-defined extension sentinel "$ompx".
std::string directive;
+ bool inDirectiveSentinelRegion{false};
for (const char &atChar : cooked().AsCharBlock()) {
char ch{atChar};
if (ch == '\n') {
@@ -149,6 +154,7 @@ void Parsing::EmitPreprocessedSource(
lineWasBlankBefore = true;
++sourceLine;
directive.clear();
+ inDirectiveSentinelRegion = false;
} else {
auto provenance{cooked().GetProvenanceRange(CharBlock{&atChar, 1})};
@@ -172,13 +178,18 @@ void Parsing::EmitPreprocessedSource(
// which signifies a comment (directive) in both source forms.
inDirective = true;
inDirectiveSentinel = true;
+ inDirectiveSentinelRegion = true;
} else if (inDirective && !ompConditionalLine &&
- directive.size() < directiveNameLength) {
+ inDirectiveSentinelRegion) {
if (IsLetter(ch) || ch == '$' || ch == '@') {
directive += getOriginalChar(ch);
inDirectiveSentinel = true;
- } else if (directive == "$"s) {
- ompConditionalLine = true;
+ } else {
+ // The blank that terminates the sentinel has been reached.
+ inDirectiveSentinelRegion = false;
+ if (directive == "$"s) {
+ ompConditionalLine = true;
+ }
}
}
diff --git a/flang/lib/Parser/prescan.h b/flang/lib/Parser/prescan.h
index 8f4f390d4ea37..42311cd128f4d 100644
--- a/flang/lib/Parser/prescan.h
+++ b/flang/lib/Parser/prescan.h
@@ -198,7 +198,12 @@ class Prescanner {
return InOpenMPConditionalLine() || InOpenACCOrCUDAConditionalLine();
}
bool IsOpenMPDirective() const {
- return directiveSentinel_ && std::strcmp(directiveSentinel_, "$omp") == 0;
+ return directiveSentinel_ &&
+ (std::strcmp(directiveSentinel_, "$omp") == 0 ||
+ // Implementation-defined extension sentinels (OpenMP 5.2, 3.1):
+ // "$omx" (fixed form) and "$ompx" (free form).
+ std::strcmp(directiveSentinel_, "$omx") == 0 ||
+ std::strcmp(directiveSentinel_, "$ompx") == 0);
}
bool InFixedFormSource() const {
return inFixedForm_ && !inPreprocessorDirective_ && !InCompilerDirective();
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 42f042e470e81..b79679ca4500a 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2758,7 +2758,7 @@ class UnparseVisitor {
}
void Unparse(const OpenMPInvalidDirective &x) {
BeginOpenMP();
- Word("!$OMP ");
+ Word(x.isExtensionSentinel ? "!$OMPX " : "!$OMP ");
Put(parser::ToUpperCaseLetters(x.source.ToString()));
Put("\n");
EndOpenMP();
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index d87b2f1983de6..6fa2effffe7b0 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5798,7 +5798,23 @@ void OmpStructureChecker::Leave(const parser::OpenMPMisplacedEndDirective &x) {
}
void OmpStructureChecker::Enter(const parser::OpenMPInvalidDirective &x) {
- context_.Say(x.source, "Invalid OpenMP directive"_err_en_US);
+ if (x.isExtensionSentinel) {
+ // A directive following an implementation-defined extension sentinel
+ // (!$omx / !$ompx, OpenMP 5.2 section 3.1) that is not recognized is
+ // ignored with a warning, so that programs using vendor extensions remain
+ // portable to implementations that do not support them. Use Say() rather
+ // than Warn() here because the latter resolves the enclosing scope of the
+ // source location, which may not exist for a directive that appears before
+ // the first statement of an otherwise empty program unit.
+ if (context_.ShouldWarn(common::UsageWarning::IgnoredDirective)) {
+ context_
+ .Say(x.source,
+ "Unrecognized OpenMP extension directive was ignored"_warn_en_US)
+ .set_usageWarning(common::UsageWarning::IgnoredDirective);
+ }
+ } else {
+ context_.Say(x.source, "Invalid OpenMP directive"_err_en_US);
+ }
PushContextAndClauseSets(x.source, llvm::omp::Directive::OMPD_unknown);
}
diff --git a/flang/test/Parser/OpenMP/sentinel-extension-ignored.f90 b/flang/test/Parser/OpenMP/sentinel-extension-ignored.f90
new file mode 100644
index 0000000000000..fe2fd9b0ea8d9
--- /dev/null
+++ b/flang/test/Parser/OpenMP/sentinel-extension-ignored.f90
@@ -0,0 +1,34 @@
+! An implementation-defined extension sentinel (!$omx in fixed form, !$ompx in
+! free form; OpenMP 5.2, section 3.1) that is followed by a directive which the
+! implementation does not recognize is ignored with a warning rather than being
+! reported as an error. This keeps programs that use vendor extensions portable
+! to implementations that do not support them.
+
+! RUN: %flang_fc1 -fopenmp -fdebug-unparse %s 2>&1 | FileCheck %s
+
+! Diagnostics are emitted before the unparsed program. An unrecognized
+! extension directive produces a warning, not an error.
+! CHECK: warning: Unrecognized OpenMP extension directive was ignored
+! CHECK-SAME: [-Wignored-directive]
+! Regression: an unrecognized extension directive that appears before the first
+! statement of a program unit must warn without crashing (the source location of
+! the ignored directive lies outside the range of any statement scope).
+! CHECK: warning: Unrecognized OpenMP extension directive was ignored
+
+! The unrecognized extension directives are ignored but unparsed with the
+! extension sentinel preserved.
+! CHECK: SUBROUTINE ompx_unrecognized
+! CHECK: !$OMPX SOME_VENDOR_DIRECTIVE
+! CHECK: END SUBROUTINE
+! CHECK: !$OMPX ANOTHER_VENDOR_DIRECTIVE
+! CHECK: END PROGRAM
+
+! No error diagnostics are produced for the unrecognized extension directives.
+! CHECK-NOT: error:
+
+subroutine ompx_unrecognized
+ !$ompx some_vendor_directive
+end subroutine
+
+!$ompx another_vendor_directive
+end
diff --git a/flang/test/Parser/OpenMP/sentinel-ompx.f90 b/flang/test/Parser/OpenMP/sentinel-ompx.f90
new file mode 100644
index 0000000000000..4c30d312f95eb
--- /dev/null
+++ b/flang/test/Parser/OpenMP/sentinel-ompx.f90
@@ -0,0 +1,28 @@
+! Implementation-defined extension sentinel "ompx" in free source form
+! (OpenMP 5.2, section 3.1.2). The !$ompx sentinel introduces implementation-
+! defined extension directives and is recognized like the !$omp sentinel.
+
+! RUN: %flang_fc1 -E -fopenmp %s 2>&1 | FileCheck %s --check-prefix=CHECK-E
+! RUN: %flang_fc1 -fopenmp -fdebug-unparse %s 2>&1 | FileCheck %s --check-prefix=CHECK-OMP
+! RUN: %flang_fc1 -fdebug-unparse %s 2>&1 | FileCheck %s --check-prefix=CHECK-NO-OMP
+
+subroutine ompx_sub(a, b, c)
+ real :: a, b, c
+ !$ompx parallel &
+ !$ompx private(a, b, c)
+ a = b + c
+ !$ompx end parallel
+end subroutine
+
+! The 5-character sentinel is preserved contiguously (not split as "!$omp x"),
+! so the -E output round-trips.
+! CHECK-E:{{^}}!$ompx parallel private(a, b, c)
+! CHECK-E:{{^}}!$ompx end parallel
+
+! With -fopenmp the extension directive is parsed as an OpenMP directive.
+! CHECK-OMP: !$OMP PARALLEL PRIVATE(a,b,c)
+! CHECK-OMP: !$OMP END PARALLEL
+
+! Without -fopenmp the extension directive is ignored as a comment.
+! CHECK-NO-OMP: SUBROUTINE ompx_sub
+! CHECK-NO-OMP-NOT: !$OMP
diff --git a/flang/test/Parser/OpenMP/sentinel-omx.f b/flang/test/Parser/OpenMP/sentinel-omx.f
new file mode 100644
index 0000000000000..fbb415ad6b46a
--- /dev/null
+++ b/flang/test/Parser/OpenMP/sentinel-omx.f
@@ -0,0 +1,45 @@
+! Implementation-defined extension sentinel "omx" in fixed source form
+! (OpenMP 5.2, section 3.1.1). The sentinels !$omx, c$omx and *$omx introduce
+! implementation-defined extension directives and are recognized like !$omp;
+! the prescanner normalizes the fixed-form comment character (c, *) to '!'.
+
+! RUN: %flang_fc1 -E -fopenmp %s 2>&1 | FileCheck %s --check-prefix=CHECK-E
+! RUN: %flang_fc1 -fopenmp -fdebug-unparse %s 2>&1 | FileCheck %s --check-prefix=CHECK-OMP
+! RUN: %flang_fc1 -fdebug-unparse %s 2>&1 | FileCheck %s --check-prefix=CHECK-NO-OMP
+
+ subroutine omx_forms(a, n)
+ integer :: n
+ real :: a(n)
+!$omx parallel
+c$omx barrier
+ call work(a, n)
+*$omx end parallel
+ end subroutine
+
+! A continuation line for an omx directive uses a non-blank in column 6.
+ subroutine omx_cont(a, b, c)
+ real :: a, b, c
+c$omx parallel
+c$omx+ private(a, b,
+c$omx+ c)
+ a = b + c
+c$omx end parallel
+ end subroutine
+
+! The comment character is normalized to '!' and the omx sentinel is preserved.
+! CHECK-E:{{^}}!$omx parallel
+! CHECK-E:{{^}}!$omx barrier
+! CHECK-E:{{^}}!$omx end parallel
+! CHECK-E:{{^}}!$omx parallel private(a, b, c)
+! CHECK-E:{{^}}!$omx end parallel
+
+! With -fopenmp the extension directives are parsed as OpenMP directives.
+! CHECK-OMP: !$OMP PARALLEL
+! CHECK-OMP: !$OMP BARRIER
+! CHECK-OMP: !$OMP END PARALLEL
+! CHECK-OMP: !$OMP PARALLEL PRIVATE(a,b,c)
+! CHECK-OMP: !$OMP END PARALLEL
+
+! Without -fopenmp the extension directives are ignored as comments.
+! CHECK-NO-OMP: SUBROUTINE omx_forms
+! CHECK-NO-OMP-NOT: !$OMP
>From f12863828ae22625969aa71fb3e4ac74a39ffa92 Mon Sep 17 00:00:00 2001
From: Chandra Ghale <ghale at pe34genoa.hpc.amslabs.hpecorp.net>
Date: Mon, 24 Aug 2026 14:53:36 -0500
Subject: [PATCH 2/2] formatting
---
flang/lib/Parser/openmp-parsers.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index e4f226dd8524a..2a979afc86d4a 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -2857,7 +2857,7 @@ TYPE_PARSER(construct<OpenMPMisplacedEndDirective>(
//
// This is a fallback that is attempted before the enclosing program unit is
// reparsed as an execution-part construct, so the standard "!$omp" branch uses
-// the strict "_id" spelling: it must not prefix-match the "!$omp" that begins an
+// the strict "_id" spelling: it must not prefix-match the "!$omp" that begins
// "!$ompx" line, otherwise a valid extension directive such as "!$ompx barrier"
// would be misreported as an invalid "!$omp" directive instead of being handled
// as a real construct.
More information about the flang-commits
mailing list