Skip to content

Commit a81f808

Browse files
authored
Unstructured OpenMP code (#1178)
Address several problems with OpenMP constructs and unstructured code. In fir, code that contains a GOTO, EXIT, or any of a number of other branches is _unstructured_. OpenMP generally prohibits branching into or out of an OpenMP construct, but allows unstructured branches where the source and target are both local to the construct. A structured loop is implemented with a fir.do_loop op, and a structured IF is implemented with a fir.if op. Unstructured loops and IFs are implemented with explicit branches between basic blocks. This PR allows an OpenMP construct to immediately follow unstructured code (see PR 1077), and allows an OpenMP construct to contain unstructured code (see Issue 1120). The same issues are likely present in OpenACC code. The infrastructure changes in this PR should also be valid for OpenACC code, but file OpenACC.cpp is not changed. This PR has a partial fix for a problem with nested parallelism, but there are additional problems to address.
1 parent 1946a1c commit a81f808

File tree

8 files changed

+227
-49
lines changed

8 files changed

+227
-49
lines changed

flang/include/flang/Lower/PFTBuilder.h

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,8 @@ using Constructs =
134134

135135
using Directives =
136136
std::tuple<parser::CompilerDirective, parser::OpenACCConstruct,
137-
parser::OpenMPConstruct, parser::OmpEndLoopDirective,
138-
parser::OpenMPDeclarativeConstruct,
139-
parser::OpenACCDeclarativeConstruct>;
137+
parser::OpenACCDeclarativeConstruct, parser::OpenMPConstruct,
138+
parser::OpenMPDeclarativeConstruct, parser::OmpEndLoopDirective>;
140139

141140
template <typename A>
142141
static constexpr bool isActionStmt{common::HasMember<A, ActionStmts>};
@@ -168,6 +167,11 @@ static constexpr bool isNopConstructStmt{common::HasMember<
168167
parser::EndIfStmt, parser::SelectRankCaseStmt,
169168
parser::TypeGuardStmt>>};
170169

170+
template <typename A>
171+
static constexpr bool isExecutableDirective{common::HasMember<
172+
A, std::tuple<parser::CompilerDirective, parser::OpenACCConstruct,
173+
parser::OpenMPConstruct>>};
174+
171175
template <typename A>
172176
static constexpr bool isFunctionLike{common::HasMember<
173177
A, std::tuple<parser::MainProgram, parser::FunctionSubprogram,
@@ -246,6 +250,11 @@ struct Evaluation : EvaluationVariant {
246250
return pft::isNopConstructStmt<std::decay_t<decltype(r)>>;
247251
}});
248252
}
253+
constexpr bool isExecutableDirective() const {
254+
return visit(common::visitors{[](auto &r) {
255+
return pft::isExecutableDirective<std::decay_t<decltype(r)>>;
256+
}});
257+
}
249258

250259
/// Return the predicate: "This is a non-initial, non-terminal construct
251260
/// statement." For an IfConstruct, this is ElseIfStmt and ElseStmt.
@@ -297,11 +306,12 @@ struct Evaluation : EvaluationVariant {
297306

298307
// FIR generation looks primarily at PFT ActionStmt and ConstructStmt leaf
299308
// nodes. Members such as lexicalSuccessor and block are applicable only
300-
// to these nodes. The controlSuccessor member is used for nonlexical
301-
// successors, such as linking to a GOTO target. For multiway branches,
302-
// it is set to the first target. Successor and exit links always target
303-
// statements. An internal Construct node has a constructExit link that
304-
// applies to exits from anywhere within the construct.
309+
// to these nodes, plus some directives. The controlSuccessor member is
310+
// used for nonlexical successors, such as linking to a GOTO target. For
311+
// multiway branches, it is set to the first target. Successor and exit
312+
// links always target statements or directives. An internal Construct
313+
// node has a constructExit link that applies to exits from anywhere within
314+
// the construct.
305315
//
306316
// An unstructured construct is one that contains some form of goto. This
307317
// is indicated by the isUnstructured member flag, which may be set on a
@@ -329,8 +339,8 @@ struct Evaluation : EvaluationVariant {
329339
std::optional<parser::Label> label{};
330340
std::unique_ptr<EvaluationList> evaluationList; // nested evaluations
331341
Evaluation *parentConstruct{nullptr}; // set for nodes below the top level
332-
Evaluation *lexicalSuccessor{nullptr}; // set for ActionStmt, ConstructStmt
333-
Evaluation *controlSuccessor{nullptr}; // set for some statements
342+
Evaluation *lexicalSuccessor{nullptr}; // set for leaf nodes, some directives
343+
Evaluation *controlSuccessor{nullptr}; // set for some leaf nodes
334344
Evaluation *constructExit{nullptr}; // set for constructs
335345
bool isNewBlock{false}; // evaluation begins a new basic block
336346
bool isUnstructured{false}; // evaluation has unstructured control flow

flang/lib/Lower/Bridge.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,9 +2517,16 @@ class FirConverter : public Fortran::lower::AbstractConverter {
25172517
/// Unconditionally switch code insertion to a new block.
25182518
void startBlock(mlir::Block *newBlock) {
25192519
assert(newBlock && "missing block");
2520+
// Default termination for the current block is a fallthrough branch to
2521+
// the new block.
25202522
if (blockIsUnterminated())
2521-
genFIRBranch(newBlock); // default termination is a fallthrough branch
2522-
builder->setInsertionPointToEnd(newBlock); // newBlock might not be empty
2523+
genFIRBranch(newBlock);
2524+
// Some blocks may be re/started more than once, and might not be empty.
2525+
// If the new block already has (only) a terminator, set the insertion
2526+
// point to the start of the block. Otherwise set it to the end.
2527+
builder->setInsertionPointToStart(newBlock);
2528+
if (blockIsUnterminated())
2529+
builder->setInsertionPointToEnd(newBlock);
25232530
}
25242531

25252532
/// Conditionally switch code insertion to a new block.

flang/lib/Lower/OpenMP.cpp

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,42 @@ static void genObjectList(const Fortran::parser::OmpObjectList &objectList,
106106
}
107107
}
108108

109+
/// Create empty blocks for the current region.
110+
/// These blocks replace blocks parented to an enclosing region.
111+
void createEmptyRegionBlocks(
112+
fir::FirOpBuilder &firOpBuilder,
113+
std::list<Fortran::lower::pft::Evaluation> &evaluationList) {
114+
auto *region = &firOpBuilder.getRegion();
115+
for (auto &eval : evaluationList) {
116+
if (eval.block) {
117+
if (eval.block->empty()) {
118+
eval.block->erase();
119+
eval.block = firOpBuilder.createBlock(region);
120+
} else {
121+
[[maybe_unused]] auto &terminatorOp = eval.block->back();
122+
assert((mlir::isa<mlir::omp::TerminatorOp>(terminatorOp) ||
123+
mlir::isa<mlir::omp::YieldOp>(terminatorOp)) &&
124+
"expected terminator op");
125+
// FIXME: Some subset of cases may need to insert a branch,
126+
// although this could be handled elsewhere.
127+
// if (?) {
128+
// auto insertPt = firOpBuilder.saveInsertionPoint();
129+
// firOpBuilder.setInsertionPointAfter(region->getParentOp());
130+
// firOpBuilder.create<mlir::BranchOp>(
131+
// terminatorOp.getLoc(), eval.block);
132+
// firOpBuilder.restoreInsertionPoint(insertPt);
133+
// }
134+
}
135+
}
136+
if (eval.hasNestedEvaluations())
137+
createEmptyRegionBlocks(firOpBuilder, eval.getNestedEvaluations());
138+
}
139+
}
140+
109141
template <typename Op>
110142
static void createBodyOfOp(
111143
Op &op, Fortran::lower::AbstractConverter &converter, mlir::Location &loc,
144+
Fortran::lower::pft::Evaluation &eval,
112145
const Fortran::parser::OmpClauseList *clauses = nullptr,
113146
const SmallVector<const Fortran::semantics::Symbol *> &args = {}) {
114147
auto &firOpBuilder = converter.getFirOpBuilder();
@@ -135,6 +168,8 @@ static void createBodyOfOp(
135168
}
136169
auto &block = op.getRegion().back();
137170
firOpBuilder.setInsertionPointToStart(&block);
171+
if (eval.lowerAsUnstructured())
172+
createEmptyRegionBlocks(firOpBuilder, eval.getNestedEvaluations());
138173
// Ensure the block is well-formed by inserting terminators.
139174
if constexpr (std::is_same_v<Op, omp::WsLoopOp>) {
140175
mlir::ValueRange results;
@@ -331,7 +366,7 @@ static void createParallelOp(Fortran::lower::AbstractConverter &converter,
331366
// Avoid multiple privatization: If Parallel is part of a combined construct
332367
// then privatization will be performed later when the other part of the
333368
// combined construct is processed.
334-
createBodyOfOp<omp::ParallelOp>(parallelOp, converter, currentLocation,
369+
createBodyOfOp<omp::ParallelOp>(parallelOp, converter, currentLocation, eval,
335370
isCombined ? nullptr : &opClauseList);
336371
}
337372

@@ -352,7 +387,7 @@ genOMP(Fortran::lower::AbstractConverter &converter,
352387
auto &firOpBuilder = converter.getFirOpBuilder();
353388
auto currentLocation = converter.getCurrentLocation();
354389
auto masterOp = firOpBuilder.create<mlir::omp::MasterOp>(currentLocation);
355-
createBodyOfOp<omp::MasterOp>(masterOp, converter, currentLocation);
390+
createBodyOfOp<omp::MasterOp>(masterOp, converter, currentLocation, eval);
356391
}
357392
}
358393

@@ -596,12 +631,13 @@ static void genOMP(Fortran::lower::AbstractConverter &converter,
596631
wsLoopOp.nowaitAttr(firOpBuilder.getUnitAttr());
597632
}
598633

599-
createBodyOfOp<omp::WsLoopOp>(wsLoopOp, converter, currentLocation,
634+
createBodyOfOp<omp::WsLoopOp>(wsLoopOp, converter, currentLocation, eval,
600635
&wsLoopOpClauseList, iv);
601636
}
602637

603638
static void
604639
genOMP(Fortran::lower::AbstractConverter &converter,
640+
Fortran::lower::pft::Evaluation &eval,
605641
const Fortran::parser::OpenMPCriticalConstruct &criticalConstruct) {
606642
auto &firOpBuilder = converter.getFirOpBuilder();
607643
auto currentLocation = converter.getCurrentLocation();
@@ -639,7 +675,7 @@ genOMP(Fortran::lower::AbstractConverter &converter,
639675
firOpBuilder.getContext(), global.sym_name()));
640676
}
641677
}();
642-
createBodyOfOp<omp::CriticalOp>(criticalOp, converter, currentLocation);
678+
createBodyOfOp<omp::CriticalOp>(criticalOp, converter, currentLocation, eval);
643679
}
644680

645681
void Fortran::lower::genOpenMPConstruct(
@@ -675,7 +711,9 @@ void Fortran::lower::genOpenMPConstruct(
675711
TODO(converter.getCurrentLocation(), "OpenMPAtomicConstruct");
676712
},
677713
[&](const Fortran::parser::OpenMPCriticalConstruct
678-
&criticalConstruct) { genOMP(converter, criticalConstruct); },
714+
&criticalConstruct) {
715+
genOMP(converter, eval, criticalConstruct);
716+
},
679717
},
680718
ompConstruct.u);
681719
}

flang/lib/Lower/PFTBuilder.cpp

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,11 @@ class PFTBuilder {
296296
resetFunctionState();
297297
}
298298

299-
/// Initialize a new construct and make it the builder's focus.
299+
/// Initialize a new construct or directive and make it the builder's focus.
300300
template <typename A>
301-
bool enterConstructOrDirective(const A &construct) {
302-
auto &eval =
303-
addEvaluation(lower::pft::Evaluation{construct, pftParentStack.back()});
301+
bool enterConstructOrDirective(const A &constructOrDirective) {
302+
auto &eval = addEvaluation(
303+
lower::pft::Evaluation{constructOrDirective, pftParentStack.back()});
304304
eval.evaluationList.reset(new lower::pft::EvaluationList);
305305
pushEvaluationList(eval.evaluationList.get());
306306
pftParentStack.emplace_back(eval);
@@ -310,6 +310,17 @@ class PFTBuilder {
310310

311311
void exitConstructOrDirective() {
312312
rewriteIfGotos();
313+
auto *eval = constructAndDirectiveStack.back();
314+
if (eval->isExecutableDirective()) {
315+
// A construct at the end of an (unstructured) OpenACC or OpenMP
316+
// construct region must have an exit target inside the region.
317+
auto &evaluationList = *eval->evaluationList;
318+
if (!evaluationList.empty() && evaluationList.back().isConstruct()) {
319+
static const parser::ContinueStmt exitTarget{};
320+
addEvaluation(
321+
lower::pft::Evaluation{exitTarget, pftParentStack.back(), {}, {}});
322+
}
323+
}
313324
popEvaluationList();
314325
pftParentStack.pop_back();
315326
constructAndDirectiveStack.pop_back();
@@ -372,7 +383,8 @@ class PFTBuilder {
372383
auto &entryPointList = eval.getOwningProcedure()->entryPointList;
373384
evaluationListStack.back()->emplace_back(std::move(eval));
374385
lower::pft::Evaluation *p = &evaluationListStack.back()->back();
375-
if (p->isActionStmt() || p->isConstructStmt() || p->isEndStmt()) {
386+
if (p->isActionStmt() || p->isConstructStmt() || p->isEndStmt() ||
387+
p->isExecutableDirective()) {
376388
if (lastLexicalEvaluation) {
377389
lastLexicalEvaluation->lexicalSuccessor = p;
378390
p->printIndex = lastLexicalEvaluation->printIndex + 1;
@@ -1017,33 +1029,32 @@ class PFTDumper {
10171029
const lower::pft::Evaluation &eval,
10181030
const std::string &indentString, int indent = 1) {
10191031
llvm::StringRef name = evaluationName(eval);
1020-
std::string bang = eval.isUnstructured ? "!" : "";
1021-
if (eval.isConstruct() || eval.isDirective()) {
1022-
outputStream << indentString << "<<" << name << bang << ">>";
1023-
if (eval.constructExit)
1024-
outputStream << " -> " << eval.constructExit->printIndex;
1025-
outputStream << '\n';
1026-
dumpEvaluationList(outputStream, *eval.evaluationList, indent + 1);
1027-
outputStream << indentString << "<<End " << name << bang << ">>\n";
1028-
return;
1029-
}
1032+
llvm::StringRef newBlock = eval.isNewBlock ? "^" : "";
1033+
llvm::StringRef bang = eval.isUnstructured ? "!" : "";
10301034
outputStream << indentString;
10311035
if (eval.printIndex)
10321036
outputStream << eval.printIndex << ' ';
1033-
if (eval.isNewBlock)
1034-
outputStream << '^';
1035-
outputStream << name << bang;
1036-
if (eval.isActionStmt() || eval.isConstructStmt()) {
1037-
if (eval.negateCondition)
1038-
outputStream << " [negate]";
1039-
if (eval.controlSuccessor)
1040-
outputStream << " -> " << eval.controlSuccessor->printIndex;
1041-
} else if (eval.isA<parser::EntryStmt>() && eval.lexicalSuccessor) {
1037+
if (eval.hasNestedEvaluations())
1038+
outputStream << "<<" << newBlock << name << bang << ">>";
1039+
else
1040+
outputStream << newBlock << name << bang;
1041+
if (eval.negateCondition)
1042+
outputStream << " [negate]";
1043+
if (eval.constructExit)
1044+
outputStream << " -> " << eval.constructExit->printIndex;
1045+
else if (eval.controlSuccessor)
1046+
outputStream << " -> " << eval.controlSuccessor->printIndex;
1047+
else if (eval.isA<parser::EntryStmt>() && eval.lexicalSuccessor)
10421048
outputStream << " -> " << eval.lexicalSuccessor->printIndex;
1043-
}
10441049
if (!eval.position.empty())
10451050
outputStream << ": " << eval.position.ToString();
1051+
else if (auto *dir = eval.getIf<Fortran::parser::CompilerDirective>())
1052+
outputStream << ": !" << dir->source.ToString();
10461053
outputStream << '\n';
1054+
if (eval.hasNestedEvaluations()) {
1055+
dumpEvaluationList(outputStream, *eval.evaluationList, indent + 1);
1056+
outputStream << indentString << "<<End " << name << bang << ">>\n";
1057+
}
10471058
}
10481059

10491060
void dumpEvaluation(llvm::raw_ostream &ostream,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
! Test unstructured code adjacent to and inside OpenMP constructs.
2+
3+
! RUN: bbc %s -fopenmp -o "-" | FileCheck %s
4+
5+
! CHECK-LABEL: func @_QPss1{{.*}} {
6+
! CHECK: br ^bb1
7+
! CHECK: ^bb1: // 2 preds: ^bb0, ^bb3
8+
! CHECK: cond_br %{{[0-9]*}}, ^bb2, ^bb4
9+
! CHECK: ^bb2: // pred: ^bb1
10+
! CHECK: cond_br %{{[0-9]*}}, ^bb4, ^bb3
11+
! CHECK: ^bb3: // pred: ^bb2
12+
! CHECK: @_FortranAioBeginExternalListOutput
13+
! CHECK: br ^bb1
14+
! CHECK: ^bb4: // 2 preds: ^bb1, ^bb2
15+
! CHECK: omp.master {
16+
! CHECK: @_FortranAioBeginExternalListOutput
17+
! CHECK: omp.terminator
18+
! CHECK: }
19+
! CHECK: @_FortranAioBeginExternalListOutput
20+
! CHECK: }
21+
subroutine ss1(n) ! unstructured code followed by a structured OpenMP construct
22+
do i = 1, 3
23+
if (i .eq. n) exit
24+
print*, 'ss1-A', i
25+
enddo
26+
!$omp master
27+
print*, 'ss1-B', i
28+
!$omp end master
29+
print*
30+
end
31+
32+
! CHECK-LABEL: func @_QPss2{{.*}} {
33+
! CHECK: omp.master {
34+
! CHECK: @_FortranAioBeginExternalListOutput
35+
! CHECK: br ^bb1
36+
! CHECK: ^bb1: // 2 preds: ^bb0, ^bb3
37+
! CHECK: cond_br %{{[0-9]*}}, ^bb2, ^bb4
38+
! CHECK: ^bb2: // pred: ^bb1
39+
! CHECK: cond_br %{{[0-9]*}}, ^bb4, ^bb3
40+
! CHECK: ^bb3: // pred: ^bb2
41+
! CHECK: @_FortranAioBeginExternalListOutput
42+
! CHECK: br ^bb1
43+
! CHECK: ^bb4: // 2 preds: ^bb1, ^bb2
44+
! CHECK: omp.terminator
45+
! CHECK: }
46+
! CHECK: @_FortranAioBeginExternalListOutput
47+
! CHECK: @_FortranAioBeginExternalListOutput
48+
! CHECK: }
49+
subroutine ss2(n) ! unstructured OpenMP construct; loop exit inside construct
50+
!$omp master
51+
print*, 'ss2-A', n
52+
do i = 1, 3
53+
if (i .eq. n) exit
54+
print*, 'ss2-B', i
55+
enddo
56+
!$omp end master
57+
print*, 'ss2-C', i
58+
print*
59+
end
60+
61+
! CHECK-LABEL: func @_QPss3{{.*}} {
62+
! CHECK: omp.parallel {
63+
! CHECK: br ^bb1
64+
! CHECK: ^bb1: // 2 preds: ^bb0, ^bb2
65+
! CHECK: cond_br %{{[0-9]*}}, ^bb2, ^bb3
66+
! CHECK: ^bb2: // pred: ^bb1
67+
! CHECK: omp.wsloop {{.*}} {
68+
! CHECK: @_FortranAioBeginExternalListOutput
69+
! CHECK: omp.yield
70+
! CHECK: }
71+
! CHECK: omp.wsloop {{.*}} {
72+
! CHECK: br ^bb1
73+
! CHECK: ^bb1: // 2 preds: ^bb0, ^bb3
74+
! CHECK: cond_br %{{[0-9]*}}, ^bb2, ^bb4
75+
! CHECK: ^bb2: // pred: ^bb1
76+
! CHECK: cond_br %{{[0-9]*}}, ^bb4, ^bb3
77+
! CHECK: ^bb3: // pred: ^bb2
78+
! CHECK: @_FortranAioBeginExternalListOutput
79+
! CHECK: br ^bb1
80+
! CHECK: ^bb4: // 2 preds: ^bb1, ^bb2
81+
! CHECK: omp.yield
82+
! CHECK: }
83+
! CHECK: br ^bb1
84+
! CHECK: ^bb3: // pred: ^bb1
85+
! CHECK: omp.terminator
86+
! CHECK: }
87+
! CHECK: }
88+
subroutine ss3(n) ! nested unstructured OpenMP constructs
89+
!$omp parallel
90+
do i = 1, 3
91+
!$omp do
92+
do k = 1, 3
93+
print*, 'ss3-A', k
94+
enddo
95+
!$omp end do
96+
!$omp do
97+
do j = 1, 3
98+
do k = 1, 3
99+
if (k .eq. n) exit
100+
print*, 'ss3-B', k
101+
enddo
102+
enddo
103+
!$omp end do
104+
enddo
105+
!$omp end parallel
106+
end
107+
108+
! CHECK-LABEL: func @_QQmain
109+
program p
110+
call ss1(2)
111+
call ss2(2)
112+
call ss3(2)
113+
end

0 commit comments

Comments
 (0)