Skip to content

Commit b119120

Browse files
committed
[clang][OpenMP] Use OpenMPIRBuilder for workshare loops.
Initial support for using the OpenMPIRBuilder by clang to generate loops using the OpenMPIRBuilder. This initial support is intentionally limited to: * Only the worksharing-loop directive. * Recognizes only the nowait clause. * No loop nests with more than one loop. * Untested with templates, exceptions. * Semantic checking left to the existing infrastructure. This patch introduces a new AST node, OMPCanonicalLoop, which becomes parent of any loop that has to adheres to the restrictions as specified by the OpenMP standard. These restrictions allow OMPCanonicalLoop to provide the following additional information that depends on base language semantics: * The distance function: How many loop iterations there will be before entering the loop nest. * The loop variable function: Conversion from a logical iteration number to the loop variable. These allow the OpenMPIRBuilder to act solely using logical iteration numbers without needing to be concerned with iterator semantics between calling the distance function and determining what the value of the loop variable ought to be. Any OpenMP logical should be done by the OpenMPIRBuilder such that it can be reused MLIR OpenMP dialect and thus by flang. The distance and loop variable function are implemented using lambdas (or more exactly: CapturedStmt because lambda implementation is more interviewed with the parser). It is up to the OpenMPIRBuilder how they are called which depends on what is done with the loop. By default, these are emitted as outlined functions but we might think about emitting them inline as the OpenMPRuntime does. For compatibility with the current OpenMP implementation, even though not necessary for the OpenMPIRBuilder, OMPCanonicalLoop can still be nested within OMPLoopDirectives' CapturedStmt. Although OMPCanonicalLoop's are not currently generated when the OpenMPIRBuilder is not enabled, these can just be skipped when not using the OpenMPIRBuilder in case we don't want to make the AST dependent on the EnableOMPBuilder setting. Loop nests with more than one loop require support by the OpenMPIRBuilder (D93268). A simple implementation of non-rectangular loop nests would add another lambda function that returns whether a loop iteration of the rectangular overapproximation is also within its non-rectangular subset. Reviewed By: jdenny Differential Revision: https://reviews.llvm.org/D94973
1 parent 657a58a commit b119120

30 files changed

+3233
-1816
lines changed

clang/include/clang-c/Index.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2572,7 +2572,11 @@ enum CXCursorKind {
25722572
*/
25732573
CXCursor_OMPTileDirective = 288,
25742574

2575-
CXCursor_LastStmt = CXCursor_OMPTileDirective,
2575+
/** OpenMP canonical loop.
2576+
*/
2577+
CXCursor_OMPCanonicalLoop = 289,
2578+
2579+
CXCursor_LastStmt = CXCursor_OMPCanonicalLoop,
25762580

25772581
/**
25782582
* Cursor that represents the translation unit itself.

clang/include/clang/AST/RecursiveASTVisitor.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2787,6 +2787,14 @@ bool RecursiveASTVisitor<Derived>::TraverseOMPExecutableDirective(
27872787
return true;
27882788
}
27892789

2790+
DEF_TRAVERSE_STMT(OMPCanonicalLoop, {
2791+
if (!getDerived().shouldVisitImplicitCode()) {
2792+
// Visit only the syntactical loop.
2793+
TRY_TO(TraverseStmt(S->getLoopStmt()));
2794+
ShouldVisitChildren = false;
2795+
}
2796+
})
2797+
27902798
template <typename Derived>
27912799
bool
27922800
RecursiveASTVisitor<Derived>::TraverseOMPLoopDirective(OMPLoopDirective *S) {

clang/include/clang/AST/StmtOpenMP.h

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,238 @@ namespace clang {
2828
// AST classes for directives.
2929
//===----------------------------------------------------------------------===//
3030

31+
/// Representation of an OpenMP canonical loop.
32+
///
33+
/// OpenMP 1.0 C/C++, section 2.4.1 for Construct; canonical-shape
34+
/// OpenMP 2.0 C/C++, section 2.4.1 for Construct; canonical-shape
35+
/// OpenMP 2.5, section 2.5.1 Loop Construct; canonical form
36+
/// OpenMP 3.1, section 2.5.1 Loop Construct; canonical form
37+
/// OpenMP 4.0, section 2.6 Canonical Loop Form
38+
/// OpenMP 4.5, section 2.6 Canonical Loop Form
39+
/// OpenMP 5.0, section 2.9.1 Canonical Loop Form
40+
/// OpenMP 5.1, section 2.11.1 Canonical Loop Nest Form
41+
///
42+
/// An OpenMP canonical loop is a for-statement or range-based for-statement
43+
/// with additional requirements that ensure that the number of iterations is
44+
/// known before entering the loop and allow skipping to an arbitrary iteration.
45+
/// The OMPCanonicalLoop AST node wraps a ForStmt or CXXForRangeStmt that is
46+
/// known to fulfill OpenMP's canonical loop requirements because of being
47+
/// associated to an OMPLoopBasedDirective. That is, the general structure is:
48+
///
49+
/// OMPLoopBasedDirective
50+
/// [`- CapturedStmt ]
51+
/// [ `- CapturedDecl]
52+
/// ` OMPCanonicalLoop
53+
/// `- ForStmt/CXXForRangeStmt
54+
/// `- Stmt
55+
///
56+
/// One or multiple CapturedStmt/CapturedDecl pairs may be inserted by some
57+
/// directives such as OMPParallelForDirective, but others do not need them
58+
/// (such as OMPTileDirective). In The OMPCanonicalLoop and
59+
/// ForStmt/CXXForRangeStmt pair is repeated for loop associated with the
60+
/// directive. A OMPCanonicalLoop must not appear in the AST unless associated
61+
/// with a OMPLoopBasedDirective. In an imperfectly nested loop nest, the
62+
/// OMPCanonicalLoop may also be wrapped in a CompoundStmt:
63+
///
64+
/// [...]
65+
/// ` OMPCanonicalLoop
66+
/// `- ForStmt/CXXForRangeStmt
67+
/// `- CompoundStmt
68+
/// |- Leading in-between code (if any)
69+
/// |- OMPCanonicalLoop
70+
/// | `- ForStmt/CXXForRangeStmt
71+
/// | `- ...
72+
/// `- Trailing in-between code (if any)
73+
///
74+
/// The leading/trailing in-between code must not itself be a OMPCanonicalLoop
75+
/// to avoid confusion which loop belongs to the nesting.
76+
///
77+
/// There are three different kinds of iteration variables for different
78+
/// purposes:
79+
/// * Loop user variable: The user-accessible variable with different value for
80+
/// each iteration.
81+
/// * Loop iteration variable: The variable used to identify a loop iteration;
82+
/// for range-based for-statement, this is the hidden iterator '__begin'. For
83+
/// other loops, it is identical to the loop user variable. Must be a
84+
/// random-access iterator, pointer or integer type.
85+
/// * Logical iteration counter: Normalized loop counter starting at 0 and
86+
/// incrementing by one at each iteration. Allows abstracting over the type
87+
/// of the loop iteration variable and is always an unsigned integer type
88+
/// appropriate to represent the range of the loop iteration variable. Its
89+
/// value corresponds to the logical iteration number in the OpenMP
90+
/// specification.
91+
///
92+
/// This AST node provides two captured statements:
93+
/// * The distance function which computes the number of iterations.
94+
/// * The loop user variable function that computes the loop user variable when
95+
/// given a logical iteration number.
96+
///
97+
/// These captured statements provide the link between C/C++ semantics and the
98+
/// logical iteration counters used by the OpenMPIRBuilder which is
99+
/// language-agnostic and therefore does not know e.g. how to advance a
100+
/// random-access iterator. The OpenMPIRBuilder will use this information to
101+
/// apply simd, workshare-loop, distribute, taskloop and loop directives to the
102+
/// loop. For compatibility with the non-OpenMPIRBuilder codegen path, an
103+
/// OMPCanonicalLoop can itself also be wrapped into the CapturedStmts of an
104+
/// OMPLoopDirective and skipped when searching for the associated syntactical
105+
/// loop.
106+
///
107+
/// Example:
108+
/// <code>
109+
/// std::vector<std::string> Container{1,2,3};
110+
/// for (std::string Str : Container)
111+
/// Body(Str);
112+
/// </code>
113+
/// which is syntactic sugar for approximately:
114+
/// <code>
115+
/// auto &&__range = Container;
116+
/// auto __begin = std::begin(__range);
117+
/// auto __end = std::end(__range);
118+
/// for (; __begin != __end; ++__begin) {
119+
/// std::String Str = *__begin;
120+
/// Body(Str);
121+
/// }
122+
/// </code>
123+
/// In this example, the loop user variable is `Str`, the loop iteration
124+
/// variable is `__begin` of type `std::vector<std::string>::iterator` and the
125+
/// logical iteration number type is `size_t` (unsigned version of
126+
/// `std::vector<std::string>::iterator::difference_type` aka `ptrdiff_t`).
127+
/// Therefore, the distance function will be
128+
/// <code>
129+
/// [&](size_t &Result) { Result = __end - __begin; }
130+
/// </code>
131+
/// and the loop variable function is
132+
/// <code>
133+
/// [&,__begin](std::vector<std::string>::iterator &Result, size_t Logical) {
134+
/// Result = __begin + Logical;
135+
/// }
136+
/// </code>
137+
/// The variable `__begin`, aka the loop iteration variable, is captured by
138+
/// value because it is modified in the loop body, but both functions require
139+
/// the initial value. The OpenMP specification explicitly leaves unspecified
140+
/// when the loop expressions are evaluated such that a capture by reference is
141+
/// sufficient.
142+
class OMPCanonicalLoop : public Stmt {
143+
friend class ASTStmtReader;
144+
friend class ASTStmtWriter;
145+
146+
/// Children of this AST node.
147+
enum {
148+
LOOP_STMT,
149+
DISTANCE_FUNC,
150+
LOOPVAR_FUNC,
151+
LOOPVAR_REF,
152+
LastSubStmt = LOOPVAR_REF
153+
};
154+
155+
private:
156+
/// This AST node's children.
157+
Stmt *SubStmts[LastSubStmt + 1] = {};
158+
159+
OMPCanonicalLoop() : Stmt(StmtClass::OMPCanonicalLoopClass) {}
160+
161+
public:
162+
/// Create a new OMPCanonicalLoop.
163+
static OMPCanonicalLoop *create(const ASTContext &Ctx, Stmt *LoopStmt,
164+
CapturedStmt *DistanceFunc,
165+
CapturedStmt *LoopVarFunc,
166+
DeclRefExpr *LoopVarRef) {
167+
OMPCanonicalLoop *S = new (Ctx) OMPCanonicalLoop();
168+
S->setLoopStmt(LoopStmt);
169+
S->setDistanceFunc(DistanceFunc);
170+
S->setLoopVarFunc(LoopVarFunc);
171+
S->setLoopVarRef(LoopVarRef);
172+
return S;
173+
}
174+
175+
/// Create an empty OMPCanonicalLoop for deserialization.
176+
static OMPCanonicalLoop *createEmpty(const ASTContext &Ctx) {
177+
return new (Ctx) OMPCanonicalLoop();
178+
}
179+
180+
static bool classof(const Stmt *S) {
181+
return S->getStmtClass() == StmtClass::OMPCanonicalLoopClass;
182+
}
183+
184+
SourceLocation getBeginLoc() const { return getLoopStmt()->getBeginLoc(); }
185+
SourceLocation getEndLoc() const { return getLoopStmt()->getEndLoc(); }
186+
187+
/// Return this AST node's children.
188+
/// @{
189+
child_range children() {
190+
return child_range(&SubStmts[0], &SubStmts[0] + LastSubStmt + 1);
191+
}
192+
const_child_range children() const {
193+
return const_child_range(&SubStmts[0], &SubStmts[0] + LastSubStmt + 1);
194+
}
195+
/// @}
196+
197+
/// The wrapped syntactic loop statement (ForStmt or CXXForRangeStmt).
198+
/// @{
199+
Stmt *getLoopStmt() { return SubStmts[LOOP_STMT]; }
200+
const Stmt *getLoopStmt() const { return SubStmts[LOOP_STMT]; }
201+
void setLoopStmt(Stmt *S) {
202+
assert((isa<ForStmt>(S) || isa<CXXForRangeStmt>(S)) &&
203+
"Canonical loop must be a for loop (range-based or otherwise)");
204+
SubStmts[LOOP_STMT] = S;
205+
}
206+
/// @}
207+
208+
/// The function that computes the number of loop iterations. Can be evaluated
209+
/// before entering the loop but after the syntactical loop's init
210+
/// statement(s).
211+
///
212+
/// Function signature: void(LogicalTy &Result)
213+
/// Any values necessary to compute the distance are captures of the closure.
214+
/// @{
215+
CapturedStmt *getDistanceFunc() {
216+
return cast<CapturedStmt>(SubStmts[DISTANCE_FUNC]);
217+
}
218+
const CapturedStmt *getDistanceFunc() const {
219+
return cast<CapturedStmt>(SubStmts[DISTANCE_FUNC]);
220+
}
221+
void setDistanceFunc(CapturedStmt *S) {
222+
assert(S && "Expected non-null captured statement");
223+
SubStmts[DISTANCE_FUNC] = S;
224+
}
225+
/// @}
226+
227+
/// The function that computes the loop user variable from a logical iteration
228+
/// counter. Can be evaluated as first statement in the loop.
229+
///
230+
/// Function signature: void(LoopVarTy &Result, LogicalTy Number)
231+
/// Any other values required to compute the loop user variable (such as start
232+
/// value, step size) are captured by the closure. In particular, the initial
233+
/// value of loop iteration variable is captured by value to be unaffected by
234+
/// previous iterations.
235+
/// @{
236+
CapturedStmt *getLoopVarFunc() {
237+
return cast<CapturedStmt>(SubStmts[LOOPVAR_FUNC]);
238+
}
239+
const CapturedStmt *getLoopVarFunc() const {
240+
return cast<CapturedStmt>(SubStmts[LOOPVAR_FUNC]);
241+
}
242+
void setLoopVarFunc(CapturedStmt *S) {
243+
assert(S && "Expected non-null captured statement");
244+
SubStmts[LOOPVAR_FUNC] = S;
245+
}
246+
/// @}
247+
248+
/// Reference to the loop user variable as accessed in the loop body.
249+
/// @{
250+
DeclRefExpr *getLoopVarRef() {
251+
return cast<DeclRefExpr>(SubStmts[LOOPVAR_REF]);
252+
}
253+
const DeclRefExpr *getLoopVarRef() const {
254+
return cast<DeclRefExpr>(SubStmts[LOOPVAR_REF]);
255+
}
256+
void setLoopVarRef(DeclRefExpr *E) {
257+
assert(E && "Expected non-null loop variable");
258+
SubStmts[LOOPVAR_REF] = E;
259+
}
260+
/// @}
261+
};
262+
31263
/// This is a basic class for representing single OpenMP executable
32264
/// directive.
33265
///

clang/include/clang/Basic/StmtNodes.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def MSDependentExistsStmt : StmtNode<Stmt>;
216216
def AsTypeExpr : StmtNode<Expr>;
217217

218218
// OpenMP Directives.
219+
def OMPCanonicalLoop : StmtNode<Stmt>;
219220
def OMPExecutableDirective : StmtNode<Stmt, 1>;
220221
def OMPLoopBasedDirective : StmtNode<OMPExecutableDirective, 1>;
221222
def OMPLoopDirective : StmtNode<OMPLoopBasedDirective, 1>;

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10486,6 +10486,11 @@ class Sema final {
1048610486

1048710487
/// Initialization of captured region for OpenMP region.
1048810488
void ActOnOpenMPRegionStart(OpenMPDirectiveKind DKind, Scope *CurScope);
10489+
10490+
/// Called for syntactical loops (ForStmt or CXXForRangeStmt) associated to
10491+
/// an OpenMP loop directive.
10492+
StmtResult ActOnOpenMPCanonicalLoop(Stmt *AStmt);
10493+
1048910494
/// End of OpenMP region.
1049010495
///
1049110496
/// \param S Statement associated with the current OpenMP region.

0 commit comments

Comments
 (0)