Skip to content

Commit 1e9625e

Browse files
authored
[flang][OpenMP] Support tasks' implicit firstprivate DSA (#85989)
Handle implicit firstprivate DSAs on task generating constructs. Fixes #64480
1 parent b2c2fef commit 1e9625e

File tree

9 files changed

+642
-40
lines changed

9 files changed

+642
-40
lines changed

flang/include/flang/Semantics/symbol.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,8 @@ class Symbol {
745745
OmpCommonBlock, OmpReduction, OmpAligned, OmpNontemporal, OmpAllocate,
746746
OmpDeclarativeAllocateDirective, OmpExecutableAllocateDirective,
747747
OmpDeclareSimd, OmpDeclareTarget, OmpThreadprivate, OmpDeclareReduction,
748-
OmpFlushed, OmpCriticalLock, OmpIfSpecified, OmpNone, OmpPreDetermined);
748+
OmpFlushed, OmpCriticalLock, OmpIfSpecified, OmpNone, OmpPreDetermined,
749+
OmpImplicit);
749750
using Flags = common::EnumSet<Flag, Flag_enumSize>;
750751

751752
const Scope &owner() const { return *owner_; }

flang/lib/Lower/OpenMP/DataSharingProcessor.cpp

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ void DataSharingProcessor::processStep1(
2828
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
2929
collectSymbolsForPrivatization();
3030
collectDefaultSymbols();
31+
collectImplicitSymbols();
3132
privatize(clauseOps, privateSyms);
3233
defaultPrivatize(clauseOps, privateSyms);
34+
implicitPrivatize(clauseOps, privateSyms);
3335
insertBarrier();
3436
}
3537

@@ -303,6 +305,48 @@ void DataSharingProcessor::insertLastPrivateCompare(mlir::Operation *op) {
303305
}
304306
}
305307

308+
static const Fortran::parser::CharBlock *
309+
getSource(const Fortran::semantics::SemanticsContext &semaCtx,
310+
const Fortran::lower::pft::Evaluation &eval) {
311+
const Fortran::parser::CharBlock *source = nullptr;
312+
313+
auto ompConsVisit = [&](const Fortran::parser::OpenMPConstruct &x) {
314+
std::visit(Fortran::common::visitors{
315+
[&](const Fortran::parser::OpenMPSectionsConstruct &x) {
316+
source = &std::get<0>(x.t).source;
317+
},
318+
[&](const Fortran::parser::OpenMPLoopConstruct &x) {
319+
source = &std::get<0>(x.t).source;
320+
},
321+
[&](const Fortran::parser::OpenMPBlockConstruct &x) {
322+
source = &std::get<0>(x.t).source;
323+
},
324+
[&](const Fortran::parser::OpenMPCriticalConstruct &x) {
325+
source = &std::get<0>(x.t).source;
326+
},
327+
[&](const Fortran::parser::OpenMPAtomicConstruct &x) {
328+
std::visit([&](const auto &x) { source = &x.source; },
329+
x.u);
330+
},
331+
[&](const auto &x) { source = &x.source; },
332+
},
333+
x.u);
334+
};
335+
336+
eval.visit(Fortran::common::visitors{
337+
[&](const Fortran::parser::OpenMPConstruct &x) { ompConsVisit(x); },
338+
[&](const Fortran::parser::OpenMPDeclarativeConstruct &x) {
339+
source = &x.source;
340+
},
341+
[&](const Fortran::parser::OmpEndLoopDirective &x) {
342+
source = &x.source;
343+
},
344+
[&](const auto &x) {},
345+
});
346+
347+
return source;
348+
}
349+
306350
void DataSharingProcessor::collectSymbolsInNestedRegions(
307351
Fortran::lower::pft::Evaluation &eval,
308352
Fortran::semantics::Symbol::Flag flag,
@@ -330,11 +374,49 @@ void DataSharingProcessor::collectSymbolsInNestedRegions(
330374
// Later, in current context, all symbols in the set
331375
// `defaultSymbols` - `symbolsInNestedRegions` will be privatized.
332376
void DataSharingProcessor::collectSymbols(
333-
Fortran::semantics::Symbol::Flag flag) {
334-
converter.collectSymbolSet(eval, defaultSymbols, flag,
377+
Fortran::semantics::Symbol::Flag flag,
378+
llvm::SetVector<const Fortran::semantics::Symbol *> &symbols) {
379+
// Collect all scopes associated with 'eval'.
380+
llvm::SetVector<const Fortran::semantics::Scope *> clauseScopes;
381+
std::function<void(const Fortran::semantics::Scope *)> collectScopes =
382+
[&](const Fortran::semantics::Scope *scope) {
383+
clauseScopes.insert(scope);
384+
for (const Fortran::semantics::Scope &child : scope->children())
385+
collectScopes(&child);
386+
};
387+
const Fortran::parser::CharBlock *source =
388+
clauses.empty() ? getSource(semaCtx, eval) : &clauses.front().source;
389+
const Fortran::semantics::Scope *curScope = nullptr;
390+
if (source && !source->empty()) {
391+
curScope = &semaCtx.FindScope(*source);
392+
collectScopes(curScope);
393+
}
394+
// Collect all symbols referenced in the evaluation being processed,
395+
// that matches 'flag'.
396+
llvm::SetVector<const Fortran::semantics::Symbol *> allSymbols;
397+
converter.collectSymbolSet(eval, allSymbols, flag,
335398
/*collectSymbols=*/true,
336399
/*collectHostAssociatedSymbols=*/true);
400+
llvm::SetVector<const Fortran::semantics::Symbol *> symbolsInNestedRegions;
337401
collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
402+
// Filter-out symbols that must not be privatized.
403+
bool collectImplicit = flag == Fortran::semantics::Symbol::Flag::OmpImplicit;
404+
auto isPrivatizable = [](const Fortran::semantics::Symbol &sym) -> bool {
405+
return !Fortran::semantics::IsProcedure(sym) &&
406+
!sym.GetUltimate().has<Fortran::semantics::DerivedTypeDetails>() &&
407+
!sym.GetUltimate().has<Fortran::semantics::NamelistDetails>() &&
408+
!Fortran::semantics::IsImpliedDoIndex(sym.GetUltimate());
409+
};
410+
for (const auto *sym : allSymbols) {
411+
assert(curScope && "couldn't find current scope");
412+
if (isPrivatizable(*sym) && !symbolsInNestedRegions.contains(sym) &&
413+
!privatizedSymbols.contains(sym) &&
414+
!sym->test(Fortran::semantics::Symbol::Flag::OmpPreDetermined) &&
415+
(collectImplicit ||
416+
!sym->test(Fortran::semantics::Symbol::Flag::OmpImplicit)) &&
417+
clauseScopes.contains(&sym->owner()))
418+
symbols.insert(sym);
419+
}
338420
}
339421

340422
void DataSharingProcessor::collectDefaultSymbols() {
@@ -343,13 +425,22 @@ void DataSharingProcessor::collectDefaultSymbols() {
343425
if (const auto *defaultClause =
344426
std::get_if<omp::clause::Default>(&clause.u)) {
345427
if (defaultClause->v == DataSharingAttribute::Private)
346-
collectSymbols(Fortran::semantics::Symbol::Flag::OmpPrivate);
428+
collectSymbols(Fortran::semantics::Symbol::Flag::OmpPrivate,
429+
defaultSymbols);
347430
else if (defaultClause->v == DataSharingAttribute::Firstprivate)
348-
collectSymbols(Fortran::semantics::Symbol::Flag::OmpFirstPrivate);
431+
collectSymbols(Fortran::semantics::Symbol::Flag::OmpFirstPrivate,
432+
defaultSymbols);
349433
}
350434
}
351435
}
352436

437+
void DataSharingProcessor::collectImplicitSymbols() {
438+
// There will be no implicit symbols when a default clause is present.
439+
if (defaultSymbols.empty())
440+
collectSymbols(Fortran::semantics::Symbol::Flag::OmpImplicit,
441+
implicitSymbols);
442+
}
443+
353444
void DataSharingProcessor::privatize(
354445
mlir::omp::PrivateClauseOps *clauseOps,
355446
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
@@ -379,15 +470,15 @@ void DataSharingProcessor::copyLastPrivatize(mlir::Operation *op) {
379470
void DataSharingProcessor::defaultPrivatize(
380471
mlir::omp::PrivateClauseOps *clauseOps,
381472
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
382-
for (const Fortran::semantics::Symbol *sym : defaultSymbols) {
383-
if (!Fortran::semantics::IsProcedure(*sym) &&
384-
!sym->GetUltimate().has<Fortran::semantics::DerivedTypeDetails>() &&
385-
!sym->GetUltimate().has<Fortran::semantics::NamelistDetails>() &&
386-
!Fortran::semantics::IsImpliedDoIndex(sym->GetUltimate()) &&
387-
!symbolsInNestedRegions.contains(sym) &&
388-
!privatizedSymbols.contains(sym))
389-
doPrivatize(sym, clauseOps, privateSyms);
390-
}
473+
for (const Fortran::semantics::Symbol *sym : defaultSymbols)
474+
doPrivatize(sym, clauseOps, privateSyms);
475+
}
476+
477+
void DataSharingProcessor::implicitPrivatize(
478+
mlir::omp::PrivateClauseOps *clauseOps,
479+
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
480+
for (const Fortran::semantics::Symbol *sym : implicitSymbols)
481+
doPrivatize(sym, clauseOps, privateSyms);
391482
}
392483

393484
void DataSharingProcessor::doPrivatize(

flang/lib/Lower/OpenMP/DataSharingProcessor.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,21 @@ class DataSharingProcessor {
3939
// Symbols in private, firstprivate, and/or lastprivate clauses.
4040
llvm::SetVector<const Fortran::semantics::Symbol *> privatizedSymbols;
4141
llvm::SetVector<const Fortran::semantics::Symbol *> defaultSymbols;
42-
llvm::SetVector<const Fortran::semantics::Symbol *> symbolsInNestedRegions;
42+
llvm::SetVector<const Fortran::semantics::Symbol *> implicitSymbols;
4343
llvm::DenseMap<const Fortran::semantics::Symbol *, mlir::omp::PrivateClauseOp>
4444
symToPrivatizer;
4545
Fortran::lower::AbstractConverter &converter;
46+
Fortran::semantics::SemanticsContext &semaCtx;
4647
fir::FirOpBuilder &firOpBuilder;
4748
omp::List<omp::Clause> clauses;
4849
Fortran::lower::pft::Evaluation &eval;
4950
bool useDelayedPrivatization;
5051
Fortran::lower::SymMap *symTable;
5152

5253
bool needBarrier();
53-
void collectSymbols(Fortran::semantics::Symbol::Flag flag);
54+
void
55+
collectSymbols(Fortran::semantics::Symbol::Flag flag,
56+
llvm::SetVector<const Fortran::semantics::Symbol *> &symbols);
5457
void collectSymbolsInNestedRegions(
5558
Fortran::lower::pft::Evaluation &eval,
5659
Fortran::semantics::Symbol::Flag flag,
@@ -62,12 +65,16 @@ class DataSharingProcessor {
6265
void collectSymbolsForPrivatization();
6366
void insertBarrier();
6467
void collectDefaultSymbols();
68+
void collectImplicitSymbols();
6569
void privatize(
6670
mlir::omp::PrivateClauseOps *clauseOps,
6771
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms);
6872
void defaultPrivatize(
6973
mlir::omp::PrivateClauseOps *clauseOps,
7074
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms);
75+
void implicitPrivatize(
76+
mlir::omp::PrivateClauseOps *clauseOps,
77+
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms);
7178
void doPrivatize(
7279
const Fortran::semantics::Symbol *sym,
7380
mlir::omp::PrivateClauseOps *clauseOps,
@@ -89,7 +96,7 @@ class DataSharingProcessor {
8996
Fortran::lower::pft::Evaluation &eval,
9097
bool useDelayedPrivatization = false,
9198
Fortran::lower::SymMap *symTable = nullptr)
92-
: hasLastPrivateOp(false), converter(converter),
99+
: hasLastPrivateOp(false), converter(converter), semaCtx(semaCtx),
93100
firOpBuilder(converter.getFirOpBuilder()), clauses(clauses), eval(eval),
94101
useDelayedPrivatization(useDelayedPrivatization), symTable(symTable) {}
95102

flang/lib/Semantics/resolve-directives.cpp

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,34 +2028,108 @@ void OmpAttributeVisitor::Post(const parser::Name &name) {
20282028
if (found->test(semantics::Symbol::Flag::OmpThreadprivate))
20292029
return;
20302030
}
2031-
std::vector<Symbol *> defaultDSASymbols;
2031+
2032+
// Implicitly determined DSAs
2033+
// OMP 5.2 5.1.1 - Variables Referenced in a Construct
2034+
Symbol *lastDeclSymbol = nullptr;
2035+
std::optional<Symbol::Flag> prevDSA;
20322036
for (int dirDepth{0}; dirDepth < (int)dirContext_.size(); ++dirDepth) {
20332037
DirContext &dirContext = dirContext_[dirDepth];
2034-
bool hasDataSharingAttr{false};
2038+
std::optional<Symbol::Flag> dsa;
2039+
20352040
for (auto symMap : dirContext.objectWithDSA) {
20362041
// if the `symbol` already has a data-sharing attribute
20372042
if (symMap.first->name() == name.symbol->name()) {
2038-
hasDataSharingAttr = true;
2043+
dsa = symMap.second;
20392044
break;
20402045
}
20412046
}
2042-
if (hasDataSharingAttr) {
2043-
if (defaultDSASymbols.size())
2044-
symbol = &MakeAssocSymbol(symbol->name(), *defaultDSASymbols.back(),
2047+
2048+
// When handling each implicit rule, either a new private symbol is
2049+
// declared or the last declared symbol is used.
2050+
// In the latter case, it's necessary to insert a new symbol in the scope
2051+
// being processed, associated with the last declared symbol.
2052+
// This captures the fact that, although we are using the last declared
2053+
// symbol, its DSA could be different in this scope.
2054+
// Also, because of how symbols are collected in lowering, not inserting
2055+
// a new symbol in this scope could lead to the conclusion that the
2056+
// symbol was declared in this construct, which would result in wrong
2057+
// privatization code being generated.
2058+
// Consider the following example:
2059+
//
2060+
// !$omp parallel default(private) ! p1
2061+
// !$omp parallel default(private) shared(x) ! p2
2062+
// x = 10
2063+
// !$omp end parallel
2064+
// !$omp end parallel
2065+
//
2066+
// If a new x symbol was not inserted in the inner parallel construct
2067+
// (p2), it would use the x symbol definition from the enclosing scope.
2068+
// Then, when p2's default symbols were collected in lowering, the x
2069+
// symbol from the outer parallel construct (p1) would be collected, as
2070+
// it would have the private flag set (note that symbols that don't have
2071+
// any private flag are considered as shared).
2072+
// This would make x appear to be defined in p2, causing it to be
2073+
// privatized in p2 and its privatization in p1 to be skipped.
2074+
auto declNewSymbol = [&](Symbol::Flag flag) {
2075+
Symbol *hostSymbol =
2076+
lastDeclSymbol ? lastDeclSymbol : &symbol->GetUltimate();
2077+
lastDeclSymbol = DeclarePrivateAccessEntity(
2078+
*hostSymbol, flag, context_.FindScope(dirContext.directiveSource));
2079+
return lastDeclSymbol;
2080+
};
2081+
auto useLastDeclSymbol = [&]() {
2082+
if (lastDeclSymbol)
2083+
MakeAssocSymbol(symbol->name(), *lastDeclSymbol,
20452084
context_.FindScope(dirContext.directiveSource));
2085+
};
2086+
2087+
if (dsa.has_value()) {
2088+
useLastDeclSymbol();
2089+
prevDSA = dsa;
20462090
continue;
20472091
}
20482092

2049-
if (dirContext.defaultDSA == semantics::Symbol::Flag::OmpPrivate ||
2050-
dirContext.defaultDSA == semantics::Symbol::Flag::OmpFirstPrivate) {
2051-
Symbol *hostSymbol = defaultDSASymbols.size() ? defaultDSASymbols.back()
2052-
: &symbol->GetUltimate();
2053-
defaultDSASymbols.push_back(
2054-
DeclarePrivateAccessEntity(*hostSymbol, dirContext.defaultDSA,
2055-
context_.FindScope(dirContext.directiveSource)));
2056-
} else if (defaultDSASymbols.size())
2057-
symbol = &MakeAssocSymbol(symbol->name(), *defaultDSASymbols.back(),
2058-
context_.FindScope(dirContext.directiveSource));
2093+
bool taskGenDir = llvm::omp::taskGeneratingSet.test(dirContext.directive);
2094+
bool targetDir = llvm::omp::allTargetSet.test(dirContext.directive);
2095+
bool parallelDir = llvm::omp::allParallelSet.test(dirContext.directive);
2096+
2097+
if (dirContext.defaultDSA == Symbol::Flag::OmpPrivate ||
2098+
dirContext.defaultDSA == Symbol::Flag::OmpFirstPrivate ||
2099+
dirContext.defaultDSA == Symbol::Flag::OmpShared) {
2100+
// 1) default
2101+
// Allowed only with parallel, teams and task generating constructs.
2102+
assert(parallelDir || taskGenDir ||
2103+
llvm::omp::allTeamsSet.test(dirContext.directive));
2104+
if (dirContext.defaultDSA != Symbol::Flag::OmpShared)
2105+
declNewSymbol(dirContext.defaultDSA);
2106+
else
2107+
useLastDeclSymbol();
2108+
dsa = dirContext.defaultDSA;
2109+
} else if (parallelDir) {
2110+
// 2) parallel -> shared
2111+
useLastDeclSymbol();
2112+
dsa = Symbol::Flag::OmpShared;
2113+
} else if (!taskGenDir && !targetDir) {
2114+
// 3) enclosing context
2115+
useLastDeclSymbol();
2116+
dsa = prevDSA;
2117+
} else if (targetDir) {
2118+
// TODO 4) not mapped target variable -> firstprivate
2119+
dsa = prevDSA;
2120+
} else if (taskGenDir) {
2121+
// TODO 5) dummy arg in orphaned taskgen construct -> firstprivate
2122+
if (prevDSA == Symbol::Flag::OmpShared) {
2123+
// 6) shared in enclosing context -> shared
2124+
useLastDeclSymbol();
2125+
dsa = Symbol::Flag::OmpShared;
2126+
} else {
2127+
// 7) firstprivate
2128+
dsa = Symbol::Flag::OmpFirstPrivate;
2129+
declNewSymbol(*dsa)->set(Symbol::Flag::OmpImplicit);
2130+
}
2131+
}
2132+
prevDSA = dsa;
20592133
}
20602134
} // within OpenMP construct
20612135
}

flang/test/Lower/OpenMP/default-clause-byref.f90

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,14 +240,12 @@ subroutine nested_default_clause_tests
240240
!CHECK: omp.terminator
241241
!CHECK: }
242242
!CHECK: omp.parallel {
243-
!CHECK: %[[PRIVATE_INNER_Z:.*]] = fir.alloca i32 {bindc_name = "z", pinned, uniq_name = "_QFnested_default_clause_testsEz"}
244-
!CHECK: %[[PRIVATE_INNER_Z_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_Z]] {uniq_name = "_QFnested_default_clause_testsEz"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
245243
!CHECK: %[[PRIVATE_INNER_W:.*]] = fir.alloca i32 {bindc_name = "w", pinned, uniq_name = "_QFnested_default_clause_testsEw"}
246244
!CHECK: %[[PRIVATE_INNER_W_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_W]] {uniq_name = "_QFnested_default_clause_testsEw"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
247245
!CHECK: %[[PRIVATE_INNER_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFnested_default_clause_testsEx"}
248246
!CHECK: %[[PRIVATE_INNER_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_X]] {uniq_name = "_QFnested_default_clause_testsEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
249247
!CHECK: %[[TEMP_1:.*]] = fir.load %[[PRIVATE_INNER_X_DECL]]#0 : !fir.ref<i32>
250-
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_INNER_Z_DECL]]#0 : !fir.ref<i32>
248+
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_Z_DECL]]#0 : !fir.ref<i32>
251249
!CHECK: %[[RESULT:.*]] = arith.addi %{{.*}}, %{{.*}} : i32
252250
!CHECK: hlfir.assign %[[RESULT]] to %[[PRIVATE_INNER_W_DECL]]#0 : i32, !fir.ref<i32>
253251
!CHECK: omp.terminator

flang/test/Lower/OpenMP/default-clause.f90

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,14 +250,12 @@ subroutine nested_default_clause_test1
250250
!CHECK: omp.terminator
251251
!CHECK: }
252252
!CHECK: omp.parallel {
253-
!CHECK: %[[PRIVATE_INNER_Z:.*]] = fir.alloca i32 {bindc_name = "z", pinned, uniq_name = "_QFnested_default_clause_test2Ez"}
254-
!CHECK: %[[PRIVATE_INNER_Z_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_Z]] {uniq_name = "_QFnested_default_clause_test2Ez"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
255253
!CHECK: %[[PRIVATE_INNER_W:.*]] = fir.alloca i32 {bindc_name = "w", pinned, uniq_name = "_QFnested_default_clause_test2Ew"}
256254
!CHECK: %[[PRIVATE_INNER_W_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_W]] {uniq_name = "_QFnested_default_clause_test2Ew"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
257255
!CHECK: %[[PRIVATE_INNER_X:.*]] = fir.alloca i32 {bindc_name = "x", pinned, uniq_name = "_QFnested_default_clause_test2Ex"}
258256
!CHECK: %[[PRIVATE_INNER_X_DECL:.*]]:2 = hlfir.declare %[[PRIVATE_INNER_X]] {uniq_name = "_QFnested_default_clause_test2Ex"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
259257
!CHECK: %[[TEMP_1:.*]] = fir.load %[[PRIVATE_INNER_X_DECL]]#0 : !fir.ref<i32>
260-
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_INNER_Z_DECL]]#0 : !fir.ref<i32>
258+
!CHECK: %[[TEMP_2:.*]] = fir.load %[[PRIVATE_Z_DECL]]#0 : !fir.ref<i32>
261259
!CHECK: %[[RESULT:.*]] = arith.addi %{{.*}}, %{{.*}} : i32
262260
!CHECK: hlfir.assign %[[RESULT]] to %[[PRIVATE_INNER_W_DECL]]#0 : i32, !fir.ref<i32>
263261
!CHECK: omp.terminator

0 commit comments

Comments
 (0)