Skip to content

Commit 1d2a713

Browse files
committed
[flang][OpenMP] Support tasks' implicit firstprivate DSA
Handle implicit firstprivate DSAs on task generating constructs. Fixes #64480
1 parent fcf86cc commit 1d2a713

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
@@ -27,8 +27,10 @@ void DataSharingProcessor::processStep1(
2727
llvm::SmallVectorImpl<const Fortran::semantics::Symbol *> *privateSyms) {
2828
collectSymbolsForPrivatization();
2929
collectDefaultSymbols();
30+
collectImplicitSymbols();
3031
privatize(clauseOps, privateSyms);
3132
defaultPrivatize(clauseOps, privateSyms);
33+
implicitPrivatize(clauseOps, privateSyms);
3234
insertBarrier();
3335
}
3436

@@ -302,6 +304,48 @@ void DataSharingProcessor::insertLastPrivateCompare(mlir::Operation *op) {
302304
}
303305
}
304306

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

339421
void DataSharingProcessor::collectDefaultSymbols() {
@@ -342,13 +424,22 @@ void DataSharingProcessor::collectDefaultSymbols() {
342424
if (const auto *defaultClause =
343425
std::get_if<omp::clause::Default>(&clause.u)) {
344426
if (defaultClause->v == DataSharingAttribute::Private)
345-
collectSymbols(Fortran::semantics::Symbol::Flag::OmpPrivate);
427+
collectSymbols(Fortran::semantics::Symbol::Flag::OmpPrivate,
428+
defaultSymbols);
346429
else if (defaultClause->v == DataSharingAttribute::Firstprivate)
347-
collectSymbols(Fortran::semantics::Symbol::Flag::OmpFirstPrivate);
430+
collectSymbols(Fortran::semantics::Symbol::Flag::OmpFirstPrivate,
431+
defaultSymbols);
348432
}
349433
}
350434
}
351435

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

392483
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)