[flang-commits] [flang] [Flang][OpenMP] Add semantic support for Loop Sequences and OpenMP loop fuse (PR #161213)
via flang-commits
flang-commits at lists.llvm.org
Mon Sep 29 08:11:07 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-parser
Author: Ferran Toda (NouTimbaler)
<details>
<summary>Changes</summary>
This patch adds semantics for the `omp fuse` directive in flang, as specified in OpenMP 6.0. This patch also enables semantic support for loop sequences which are needed for the fuse directive along with semantics for the `looprange` clause. These changes are only semantic.
Relevant tests have been added , and previous behavior is retained with no changes.
---
Patch is 46.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/161213.diff
20 Files Affected:
- (modified) flang/include/flang/Parser/parse-tree.h (+2-2)
- (modified) flang/include/flang/Semantics/openmp-directive-sets.h (+7)
- (modified) flang/lib/Lower/OpenMP/Clauses.cpp (+1-1)
- (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+5-4)
- (modified) flang/lib/Lower/OpenMP/Utils.cpp (+4-4)
- (modified) flang/lib/Parser/openmp-parsers.cpp (+1)
- (modified) flang/lib/Parser/unparse.cpp (+1-2)
- (modified) flang/lib/Semantics/canonicalize-omp.cpp (+57-35)
- (modified) flang/lib/Semantics/check-omp-loop.cpp (+44-15)
- (modified) flang/lib/Semantics/check-omp-structure.cpp (+5-3)
- (modified) flang/lib/Semantics/check-omp-structure.h (+1)
- (modified) flang/lib/Semantics/resolve-directives.cpp (+82-75)
- (modified) flang/lib/Semantics/rewrite-parse-tree.cpp (+26-18)
- (added) flang/test/Parser/OpenMP/fuse-looprange.f90 (+38)
- (added) flang/test/Parser/OpenMP/fuse.f90 (+28)
- (added) flang/test/Parser/OpenMP/loop-transformation-construct04.f90 (+78)
- (added) flang/test/Parser/OpenMP/loop-transformation-construct05.f90 (+86)
- (added) flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 (+31)
- (modified) flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 (+2-2)
- (added) flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 (+95)
``````````diff
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 325ca9b4a227b..67bcbbd923375 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5177,7 +5177,7 @@ using NestedConstruct =
struct OpenMPLoopConstruct {
TUPLE_CLASS_BOILERPLATE(OpenMPLoopConstruct);
OpenMPLoopConstruct(OmpBeginLoopDirective &&a)
- : t({std::move(a), std::nullopt, std::nullopt}) {}
+ : t({std::move(a), std::list<NestedConstruct>(), std::nullopt}) {}
const OmpBeginLoopDirective &BeginDir() const {
return std::get<OmpBeginLoopDirective>(t);
@@ -5185,7 +5185,7 @@ struct OpenMPLoopConstruct {
const std::optional<OmpEndLoopDirective> &EndDir() const {
return std::get<std::optional<OmpEndLoopDirective>>(t);
}
- std::tuple<OmpBeginLoopDirective, std::optional<NestedConstruct>,
+ std::tuple<OmpBeginLoopDirective, std::list<NestedConstruct>,
std::optional<OmpEndLoopDirective>>
t;
};
diff --git a/flang/include/flang/Semantics/openmp-directive-sets.h b/flang/include/flang/Semantics/openmp-directive-sets.h
index 01e8481e05721..609a7be700c28 100644
--- a/flang/include/flang/Semantics/openmp-directive-sets.h
+++ b/flang/include/flang/Semantics/openmp-directive-sets.h
@@ -275,10 +275,17 @@ static const OmpDirectiveSet loopConstructSet{
Directive::OMPD_teams_distribute_parallel_do_simd,
Directive::OMPD_teams_distribute_simd,
Directive::OMPD_teams_loop,
+ Directive::OMPD_fuse,
Directive::OMPD_tile,
Directive::OMPD_unroll,
};
+static const OmpDirectiveSet loopTransformationSet{
+ Directive::OMPD_tile,
+ Directive::OMPD_unroll,
+ Directive::OMPD_fuse,
+};
+
static const OmpDirectiveSet nonPartialVarSet{
Directive::OMPD_allocate,
Directive::OMPD_allocators,
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index fac37a372caaf..ce516ce10815b 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1038,7 +1038,7 @@ Link make(const parser::OmpClause::Link &inp,
LoopRange make(const parser::OmpClause::Looprange &inp,
semantics::SemanticsContext &semaCtx) {
- llvm_unreachable("Unimplemented: looprange");
+ TODO_NOLOC("looprange clause");
}
Map make(const parser::OmpClause::Map &inp,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 1cb3335abbd06..c3598f6846822 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3360,6 +3360,7 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
newOp = genTeamsOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue,
item);
break;
+ case llvm::omp::Directive::OMPD_fuse:
case llvm::omp::Directive::OMPD_tile: {
unsigned version = semaCtx.langOptions().OpenMPVersion;
if (!semaCtx.langOptions().OpenMPSimd)
@@ -3814,12 +3815,12 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation = converter.genLocation(beginSpec.source);
- auto &optLoopCons =
- std::get<std::optional<parser::NestedConstruct>>(loopConstruct.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(loopConstruct.t);
+ for (auto &loopCons : loopConsList) {
if (auto *ompNestedLoopCons{
std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
- &*optLoopCons)}) {
+ &loopCons)}) {
llvm::omp::Directive nestedDirective =
parser::omp::GetOmpDirectiveName(*ompNestedLoopCons).v;
switch (nestedDirective) {
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 83b7ccb1ce0ee..30e90b2aed50b 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -607,13 +607,13 @@ static void processTileSizesFromOpenMPConstruct(
if (!ompCons)
return;
if (auto *ompLoop{std::get_if<parser::OpenMPLoopConstruct>(&ompCons->u)}) {
- const auto &nestedOptional =
- std::get<std::optional<parser::NestedConstruct>>(ompLoop->t);
- assert(nestedOptional.has_value() &&
+ const auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(ompLoop->t);
+ assert(loopConsList.size() == 1 &&
"Expected a DoConstruct or OpenMPLoopConstruct");
const auto *innerConstruct =
std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
- &(nestedOptional.value()));
+ &(loopConsList.front()));
if (innerConstruct) {
const auto &innerLoopDirective = innerConstruct->value();
const parser::OmpDirectiveSpecification &innerBeginSpec =
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 9507021057476..0082f1180a60d 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -2019,6 +2019,7 @@ static constexpr DirectiveSet GetLoopDirectives() {
unsigned(Directive::OMPD_teams_distribute_parallel_do_simd),
unsigned(Directive::OMPD_teams_distribute_simd),
unsigned(Directive::OMPD_teams_loop),
+ unsigned(Directive::OMPD_fuse),
unsigned(Directive::OMPD_tile),
unsigned(Directive::OMPD_unroll),
};
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 0511f5bdf7478..7d4b9f903b1d1 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2720,8 +2720,7 @@ class UnparseVisitor {
}
void Unparse(const OpenMPLoopConstruct &x) {
Walk(std::get<OmpBeginLoopDirective>(x.t));
- Walk(std::get<std::optional<std::variant<DoConstruct,
- common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t));
+ Walk(std::get<std::list<parser::NestedConstruct>>(x.t));
Walk(std::get<std::optional<OmpEndLoopDirective>>(x.t));
}
void Unparse(const BasedPointer &x) {
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index c884658bf464a..74b3fa978a53c 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -10,6 +10,7 @@
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/semantics.h"
+#include "flang/Semantics/openmp-directive-sets.h"
// After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP
// Constructs more structured which provide explicit scopes for later
@@ -137,30 +138,42 @@ class CanonicalizationOfOmp {
"A DO loop must follow the %s directive"_err_en_US,
parser::ToUpperCaseLetters(dirName.source.ToString()));
};
- auto tileUnrollError = [](const parser::OmpDirectiveName &dirName,
+ auto transformUnrollError = [](const parser::OmpDirectiveName &dirName,
parser::Messages &messages) {
messages.Say(dirName.source,
- "If a loop construct has been fully unrolled, it cannot then be tiled"_err_en_US,
+ "If a loop construct has been fully unrolled, it cannot then be further transformed"_err_en_US,
parser::ToUpperCaseLetters(dirName.source.ToString()));
};
+ auto missingEndFuse = [](auto &dir, auto &messages) {
+ messages.Say(dir.source,
+ "The %s construct requires the END FUSE directive"_err_en_US,
+ parser::ToUpperCaseLetters(dir.source.ToString()));
+ };
+
+ bool endFuseNeeded = beginName.v == llvm::omp::Directive::OMPD_fuse;
nextIt = it;
- while (++nextIt != block.end()) {
+ nextIt++;
+ while (nextIt != block.end()) {
// Ignore compiler directives.
- if (GetConstructIf<parser::CompilerDirective>(*nextIt))
+ if (GetConstructIf<parser::CompilerDirective>(*nextIt)) {
+ nextIt++;
continue;
+ }
if (auto *doCons{GetConstructIf<parser::DoConstruct>(*nextIt)}) {
if (doCons->GetLoopControl()) {
// move DoConstruct
- std::get<std::optional<std::variant<parser::DoConstruct,
- common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t) =
- std::move(*doCons);
+ std::get<std::list<parser::NestedConstruct>>(x.t).push_back(
+ std::move(*doCons));
nextIt = block.erase(nextIt);
// try to match OmpEndLoopDirective
if (nextIt != block.end()) {
if (auto *endDir{
GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
+ auto &endDirName = endDir->DirName();
+ if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
+ endFuseNeeded = false;
std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
std::move(*endDir);
nextIt = block.erase(nextIt);
@@ -170,6 +183,7 @@ class CanonicalizationOfOmp {
messages_.Say(beginName.source,
"DO loop after the %s directive must have loop control"_err_en_US,
parser::ToUpperCaseLetters(beginName.source.ToString()));
+ endFuseNeeded = false;
}
} else if (auto *ompLoopCons{
GetOmpIf<parser::OpenMPLoopConstruct>(*nextIt)}) {
@@ -177,10 +191,29 @@ class CanonicalizationOfOmp {
// OpenMP Loop Construct and the DO loop itself
auto &nestedBeginDirective = ompLoopCons->BeginDir();
auto &nestedBeginName = nestedBeginDirective.DirName();
- if ((nestedBeginName.v == llvm::omp::Directive::OMPD_unroll ||
- nestedBeginName.v == llvm::omp::Directive::OMPD_tile) &&
- !(nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
- beginName.v == llvm::omp::Directive::OMPD_tile)) {
+ if (llvm::omp::loopTransformationSet.test(nestedBeginName.v)) {
+ if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
+ llvm::omp::loopTransformationSet.test(beginName.v)) {
+ // if a loop has been unrolled, the user can not then tile that loop
+ // as it has been unrolled
+ const parser::OmpClauseList &unrollClauseList{
+ nestedBeginDirective.Clauses()};
+ if (unrollClauseList.v.empty()) {
+ // if the clause list is empty for an unroll construct, we assume
+ // the loop is being fully unrolled
+ transformUnrollError(beginName, messages_);
+ endFuseNeeded = false;
+ } else {
+ // parse the clauses for the unroll directive to find the full
+ // clause
+ for (auto &clause : unrollClauseList.v) {
+ if (clause.Id() == llvm::omp::OMPC_full) {
+ transformUnrollError(beginName, messages_);
+ endFuseNeeded = false;
+ }
+ }
+ }
+ }
// iterate through the remaining block items to find the end directive
// for the unroll/tile directive.
parser::Block::iterator endIt;
@@ -190,6 +223,8 @@ class CanonicalizationOfOmp {
GetConstructIf<parser::OmpEndLoopDirective>(*endIt)}) {
auto &endDirName = endDir->DirName();
if (endDirName.v == beginName.v) {
+ if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
+ endFuseNeeded = false;
std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
std::move(*endDir);
endIt = block.erase(endIt);
@@ -199,43 +234,30 @@ class CanonicalizationOfOmp {
++endIt;
}
RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
- auto &ompLoop = std::get<std::optional<parser::NestedConstruct>>(x.t);
- ompLoop =
- std::optional<parser::NestedConstruct>{parser::NestedConstruct{
- common::Indirection{std::move(*ompLoopCons)}}};
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ loopConsList.push_back(parser::NestedConstruct{
+ common::Indirection{std::move(*ompLoopCons)}});
nextIt = block.erase(nextIt);
- } else if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
- beginName.v == llvm::omp::Directive::OMPD_tile) {
- // if a loop has been unrolled, the user can not then tile that loop
- // as it has been unrolled
- const parser::OmpClauseList &unrollClauseList{
- nestedBeginDirective.Clauses()};
- if (unrollClauseList.v.empty()) {
- // if the clause list is empty for an unroll construct, we assume
- // the loop is being fully unrolled
- tileUnrollError(beginName, messages_);
- } else {
- // parse the clauses for the unroll directive to find the full
- // clause
- for (auto &clause : unrollClauseList.v) {
- if (clause.Id() == llvm::omp::OMPC_full) {
- tileUnrollError(beginName, messages_);
- }
- }
- }
} else {
messages_.Say(nestedBeginName.source,
"Only Loop Transformation Constructs or Loop Nests can be nested within Loop Constructs"_err_en_US,
parser::ToUpperCaseLetters(nestedBeginName.source.ToString()));
+ endFuseNeeded = false;
}
} else {
missingDoConstruct(beginName, messages_);
+ endFuseNeeded = false;
}
+ if (endFuseNeeded)
+ continue;
// If we get here, we either found a loop, or issued an error message.
return;
}
if (nextIt == block.end()) {
- missingDoConstruct(beginName, messages_);
+ if (endFuseNeeded)
+ missingEndFuse(beginName, messages_);
+ else
+ missingDoConstruct(beginName, messages_);
}
}
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index c9d0495850b6e..70c41b5442828 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -286,10 +286,9 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
}
SetLoopInfo(x);
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
- if (const auto &doConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ for (auto &loopCons : loopConsList) {
+ if (const auto &doConstruct{std::get_if<parser::DoConstruct>(&loopCons)}) {
const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
CheckNoBranching(doBlock, beginName.v, beginName.source);
}
@@ -306,6 +305,9 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
beginName.v == llvm::omp::Directive::OMPD_distribute_simd) {
CheckDistLinear(x);
}
+ if (beginName.v == llvm::omp::Directive::OMPD_fuse) {
+ CheckLooprangeBounds(x);
+ }
}
const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -315,10 +317,10 @@ const parser::Name OmpStructureChecker::GetLoopIndex(
}
void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ if (loopConsList.size() == 1) {
if (const auto &loopConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
const parser::DoConstruct *loop{&*loopConstruct};
if (loop && loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -330,10 +332,10 @@ void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ for (auto &loopCons : loopConsList) {
if (const auto &loopConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ std::get_if<parser::DoConstruct>(&loopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
@@ -418,10 +420,11 @@ void OmpStructureChecker::CheckDistLinear(
// Match the loop index variables with the collected symbols from linear
// clauses.
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ for (auto &loopCons : loopConsList) {
+ std::int64_t collapseVal_ = collapseVal;
if (const auto &loopConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ std::get_if<parser::DoConstruct>(&loopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -429,8 +432,8 @@ void OmpStructureChecker::CheckDistLinear(
// Remove the symbol from the collected set
indexVars.erase(&itrVal.symbol->GetUltimate());
}
- collapseVal--;
- if (collapseVal == 0) {
+ collapseVal_--;
+ if (collapseVal_ == 0) {
break;
}
}
@@ -453,6 +456,32 @@ void OmpStructureChecker::CheckDistLinear(
}
}
+void OmpStructureChecker::CheckLooprangeBounds(
+ const parser::OpenMPLoopConstruct &x) {
+ const parser::OmpClauseList &clauseList = x.BeginDir().Clauses();
+ if (!clauseList.v.empty()) {
+ for (auto &clause : clauseList.v) {
+ if (const auto *lrClause{
+ std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
+ if (const auto first{GetIntValue(std::get<0>((lrClause->v).t))}) {
+ if (const auto count{GetIntValue(std::get<1>((lrClause->v).t))}) {
+ auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(x.t);
+ if (loopConsList.size() < (unsigned)(*first + *count - 1)) {
+ context_.Say(clause.source,
+ "The loop range indicated in the %s clause"
+ " must not be out of the bounds of the Loop Sequence"
+ " following the construct."_err_en_US,
+ parser::ToUpperCaseLetters(clause.source.ToString()));
+ }
+ }
+ }
+ return;
+ }
+ }
+ }
+}
+
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index db030bbe1f023..7980ec886604d 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -3107,9 +3107,11 @@ CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Safelen, OMPC_safelen)
CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Simdlen, OMPC_simdlen)
void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
- context_.Say(GetContext().clauseSource,
- "LOOPRANGE clause is not implemented yet"_err_en_US,
- ContextDirectiveAsFortran());
+ CheckAllowedClause(llvm::omp::Clause::OMPC_looprange);
+ auto &first = std::get<0>(x.v.t);
+ auto &count = std::get<1>(x.v.t);
+ RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
+ RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first);
}
// Restrictions specific to each clause are implemented apart from the
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 193784555a887..3705b95c15b48 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -297,6 +297,7 @@ class OmpStructureChecker
void CheckAtomicWrite(const parser::OpenMPAtomicConstruct &x);
void CheckAtomicUpdate(...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/161213
More information about the flang-commits
mailing list