clang 24.0.0git
CIRGenStmtOpenACC.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Emit OpenACC Stmt nodes as CIR code.
10//
11//===----------------------------------------------------------------------===//
12
13#include "CIRGenBuilder.h"
14#include "CIRGenFunction.h"
15#include "mlir/Dialect/OpenACC/OpenACC.h"
18
19using namespace clang;
20using namespace clang::CIRGen;
21using namespace cir;
22using namespace mlir::acc;
23
24template <typename Op, typename TermOp>
25mlir::LogicalResult CIRGenFunction::emitOpenACCOpAssociatedStmt(
26 mlir::Location start, mlir::Location end, OpenACCDirectiveKind dirKind,
27 llvm::ArrayRef<const OpenACCClause *> clauses, const Stmt *associatedStmt) {
28 mlir::LogicalResult res = mlir::success();
29
30 llvm::SmallVector<mlir::Type> retTy;
31 llvm::SmallVector<mlir::Value> operands;
32 auto op = Op::create(builder, start, retTy, operands);
33
34 emitOpenACCClauses(op, dirKind, clauses);
35
36 {
37 mlir::Block &block = op.getRegion().emplaceBlock();
38 mlir::OpBuilder::InsertionGuard guardCase(builder);
39 builder.setInsertionPointToEnd(&block);
40
41 LexicalScope ls{*this, start, builder.getInsertionBlock()};
42 if (associatedStmt)
43 res = emitStmt(associatedStmt, /*useCurrentScope=*/true);
44
45 TermOp::create(builder, end);
46 }
47 return res;
48}
49
50namespace {
51template <typename Op> struct CombinedType;
52template <> struct CombinedType<ParallelOp> {
53 static constexpr mlir::acc::CombinedConstructsType value =
54 mlir::acc::CombinedConstructsType::ParallelLoop;
55};
56template <> struct CombinedType<SerialOp> {
57 static constexpr mlir::acc::CombinedConstructsType value =
58 mlir::acc::CombinedConstructsType::SerialLoop;
59};
60template <> struct CombinedType<KernelsOp> {
61 static constexpr mlir::acc::CombinedConstructsType value =
62 mlir::acc::CombinedConstructsType::KernelsLoop;
63};
64} // namespace
65
66template <typename Op, typename TermOp>
67mlir::LogicalResult CIRGenFunction::emitOpenACCOpCombinedConstruct(
68 mlir::Location start, mlir::Location end, OpenACCDirectiveKind dirKind,
69 llvm::ArrayRef<const OpenACCClause *> clauses, const Stmt *loopStmt) {
70 mlir::LogicalResult res = mlir::success();
71
72 llvm::SmallVector<mlir::Type> retTy;
73 llvm::SmallVector<mlir::Value> operands;
74
75 auto computeOp = Op::create(builder, start, retTy, operands);
76 computeOp.setCombinedAttr(builder.getUnitAttr());
77 mlir::acc::LoopOp loopOp;
78
79 // First, emit the bodies of both operations, with the loop inside the body of
80 // the combined construct.
81 {
82 mlir::Block &block = computeOp.getRegion().emplaceBlock();
83 mlir::OpBuilder::InsertionGuard guardCase(builder);
84 builder.setInsertionPointToEnd(&block);
85
86 LexicalScope ls{*this, start, builder.getInsertionBlock()};
87 auto loopOp = LoopOp::create(builder, start, retTy, operands);
88 loopOp.setCombinedAttr(mlir::acc::CombinedConstructsTypeAttr::get(
89 builder.getContext(), CombinedType<Op>::value));
90
91 {
92 mlir::Block &innerBlock = loopOp.getRegion().emplaceBlock();
93 mlir::OpBuilder::InsertionGuard guardCase(builder);
94 builder.setInsertionPointToEnd(&innerBlock);
95
96 LexicalScope ls{*this, start, builder.getInsertionBlock()};
97 ActiveOpenACCLoopRAII activeLoop{*this, &loopOp};
98
99 if (loopStmt)
100 res = emitStmt(loopStmt, /*useCurrentScope=*/true);
101
102 mlir::acc::YieldOp::create(builder, end);
103 }
104
105 emitOpenACCClauses(computeOp, loopOp, dirKind, clauses);
106
107 updateLoopOpParallelism(loopOp, /*isOrphan=*/false, dirKind);
108
109 TermOp::create(builder, end);
110 }
111
112 return res;
113}
114
115template <typename Op>
116Op CIRGenFunction::emitOpenACCOp(
117 mlir::Location start, OpenACCDirectiveKind dirKind,
118 llvm::ArrayRef<const OpenACCClause *> clauses) {
119 llvm::SmallVector<mlir::Type> retTy;
120 llvm::SmallVector<mlir::Value> operands;
121 auto op = Op::create(builder, start, retTy, operands);
122
123 emitOpenACCClauses(op, dirKind, clauses);
124 return op;
125}
126
127mlir::LogicalResult
129 mlir::Location start = getLoc(s.getSourceRange().getBegin());
130 mlir::Location end = getLoc(s.getSourceRange().getEnd());
131
132 switch (s.getDirectiveKind()) {
134 return emitOpenACCOpAssociatedStmt<ParallelOp, mlir::acc::YieldOp>(
135 start, end, s.getDirectiveKind(), s.clauses(), s.getStructuredBlock());
137 return emitOpenACCOpAssociatedStmt<SerialOp, mlir::acc::YieldOp>(
138 start, end, s.getDirectiveKind(), s.clauses(), s.getStructuredBlock());
140 return emitOpenACCOpAssociatedStmt<KernelsOp, mlir::acc::TerminatorOp>(
141 start, end, s.getDirectiveKind(), s.clauses(), s.getStructuredBlock());
142 default:
143 llvm_unreachable("invalid compute construct kind");
144 }
145}
146
147mlir::LogicalResult
149 mlir::Location start = getLoc(s.getSourceRange().getBegin());
150 mlir::Location end = getLoc(s.getSourceRange().getEnd());
151
152 return emitOpenACCOpAssociatedStmt<DataOp, mlir::acc::TerminatorOp>(
153 start, end, s.getDirectiveKind(), s.clauses(), s.getStructuredBlock());
154}
155
156mlir::LogicalResult
158 mlir::Location start = getLoc(s.getSourceRange().getBegin());
159 emitOpenACCOp<InitOp>(start, s.getDirectiveKind(), s.clauses());
160 return mlir::success();
161}
162
163mlir::LogicalResult
165 mlir::Location start = getLoc(s.getSourceRange().getBegin());
166 emitOpenACCOp<SetOp>(start, s.getDirectiveKind(), s.clauses());
167 return mlir::success();
168}
169
171 const OpenACCShutdownConstruct &s) {
172 mlir::Location start = getLoc(s.getSourceRange().getBegin());
173 emitOpenACCOp<ShutdownOp>(start, s.getDirectiveKind(), s.clauses());
174 return mlir::success();
175}
176
177mlir::LogicalResult
179 mlir::Location start = getLoc(s.getSourceRange().getBegin());
180 auto waitOp = emitOpenACCOp<WaitOp>(start, s.getDirectiveKind(), s.clauses());
181
182 auto createIntExpr = [this](const Expr *intExpr) {
183 mlir::Value expr = emitScalarExpr(intExpr);
184 mlir::Location exprLoc = cgm.getLoc(intExpr->getBeginLoc());
185
186 mlir::IntegerType targetType = mlir::IntegerType::get(
187 &getMLIRContext(), getContext().getIntWidth(intExpr->getType()),
188 intExpr->getType()->isSignedIntegerOrEnumerationType()
189 ? mlir::IntegerType::SignednessSemantics::Signed
190 : mlir::IntegerType::SignednessSemantics::Unsigned);
191
192 return builder.createBuiltinIntCast(exprLoc, expr, targetType);
193 };
194
195 // Emit the correct 'wait' clauses.
196 {
197 mlir::OpBuilder::InsertionGuard guardCase(builder);
198 builder.setInsertionPoint(waitOp);
199
200 if (s.hasDevNumExpr())
201 waitOp.getWaitDevnumMutable().append(createIntExpr(s.getDevNumExpr()));
202
203 for (Expr *QueueExpr : s.getQueueIdExprs())
204 waitOp.getWaitOperandsMutable().append(createIntExpr(QueueExpr));
205 }
206
207 return mlir::success();
208}
209
211 const OpenACCCombinedConstruct &s) {
212 mlir::Location start = getLoc(s.getSourceRange().getBegin());
213 mlir::Location end = getLoc(s.getSourceRange().getEnd());
214
215 switch (s.getDirectiveKind()) {
217 return emitOpenACCOpCombinedConstruct<ParallelOp, mlir::acc::YieldOp>(
218 start, end, s.getDirectiveKind(), s.clauses(), s.getLoop());
220 return emitOpenACCOpCombinedConstruct<SerialOp, mlir::acc::YieldOp>(
221 start, end, s.getDirectiveKind(), s.clauses(), s.getLoop());
223 return emitOpenACCOpCombinedConstruct<KernelsOp, mlir::acc::TerminatorOp>(
224 start, end, s.getDirectiveKind(), s.clauses(), s.getLoop());
225 default:
226 llvm_unreachable("invalid compute construct kind");
227 }
228}
229
231 const OpenACCHostDataConstruct &s) {
232 mlir::Location start = getLoc(s.getSourceRange().getBegin());
233 mlir::Location end = getLoc(s.getSourceRange().getEnd());
234
235 return emitOpenACCOpAssociatedStmt<HostDataOp, mlir::acc::TerminatorOp>(
236 start, end, s.getDirectiveKind(), s.clauses(), s.getStructuredBlock());
237}
238
240 const OpenACCEnterDataConstruct &s) {
241 mlir::Location start = getLoc(s.getSourceRange().getBegin());
242 emitOpenACCOp<EnterDataOp>(start, s.getDirectiveKind(), s.clauses());
243 return mlir::success();
244}
245
247 const OpenACCExitDataConstruct &s) {
248 mlir::Location start = getLoc(s.getSourceRange().getBegin());
249 emitOpenACCOp<ExitDataOp>(start, s.getDirectiveKind(), s.clauses());
250 return mlir::success();
251}
252
253mlir::LogicalResult
255 mlir::Location start = getLoc(s.getSourceRange().getBegin());
256 emitOpenACCOp<UpdateOp>(start, s.getDirectiveKind(), s.clauses());
257 return mlir::success();
258}
259
260mlir::LogicalResult
262 // The 'cache' directive 'may' be at the top of a loop by standard, but
263 // doesn't have to be. Additionally, there is nothing that requires this be a
264 // loop affected by an OpenACC pragma. Sema doesn't do any level of
265 // enforcement here, since it isn't particularly valuable to do so thanks to
266 // that. Instead, we treat cache as a 'noop' if there is no acc.loop to apply
267 // it to.
268 if (!activeLoopOp)
269 return mlir::success();
270
271 mlir::acc::LoopOp loopOp = *activeLoopOp;
272
273 mlir::OpBuilder::InsertionGuard guard(builder);
274 builder.setInsertionPoint(loopOp);
275
276 for (const Expr *var : s.getVarList()) {
279
280 auto cacheOp = CacheOp::create(builder, opInfo.beginLoc, opInfo.varValue,
281 /*structured=*/false, /*implicit=*/false,
282 opInfo.name, opInfo.bounds);
283
284 loopOp.getCacheOperandsMutable().append(cacheOp.getResult());
285 }
286
287 return mlir::success();
288}
289
290const VarDecl *getLValueDecl(const Expr *e) {
291 // We are going to assume that after stripping implicit casts, that the LValue
292 // is just a DRE around the var-decl.
293
294 e = e->IgnoreImpCasts();
295
296 const auto *dre = cast<DeclRefExpr>(e);
297 return cast<VarDecl>(dre->getDecl());
298}
299
300static mlir::acc::AtomicReadOp
302 mlir::Location start,
304 // Atomic 'read' only permits 'v = x', where v and x are both scalar L
305 // values. The getAssociatedStmtInfo strips off implicit casts, which
306 // includes implicit conversions and L-to-R-Value conversions, so we can
307 // just emit it as an L value. The Flang implementation has no problem with
308 // different types, so it appears that the dialect can handle the
309 // conversions.
310 mlir::Value v = cgf.emitLValue(inf.V).getPointer();
311 mlir::Value x = cgf.emitLValue(inf.X).getPointer();
312 mlir::Type resTy = cgf.convertType(inf.V->getType());
313 return mlir::acc::AtomicReadOp::create(builder, start, x, v, resTy,
314 /*ifCond=*/{});
315}
316
317static mlir::acc::AtomicWriteOp
319 mlir::Location start,
321 mlir::Value x = cgf.emitLValue(inf.X).getPointer();
322 mlir::Value expr = cgf.emitAnyExpr(inf.RefExpr).getValue();
323 return mlir::acc::AtomicWriteOp::create(builder, start, x, expr,
324 /*ifCond=*/{});
325}
326
327static std::pair<mlir::LogicalResult, mlir::acc::AtomicUpdateOp>
329 mlir::Location start, mlir::Location end,
331 mlir::Value x = cgf.emitLValue(inf.X).getPointer();
332 auto op = mlir::acc::AtomicUpdateOp::create(builder, start, x, /*ifCond=*/{});
333
334 mlir::LogicalResult res = mlir::success();
335 {
336 mlir::OpBuilder::InsertionGuard guardCase(builder);
337 mlir::Type argTy = cast<cir::PointerType>(x.getType()).getPointee();
338 std::array<mlir::Type, 1> recipeType{argTy};
339 std::array<mlir::Location, 1> recipeLoc{start};
340 auto *recipeBlock = builder.createBlock(
341 &op.getRegion(), op.getRegion().end(), recipeType, recipeLoc);
342 builder.setInsertionPointToEnd(recipeBlock);
343 // Since we have an initial value that we know is a scalar type, we can
344 // just emit the entire statement here after sneaking-in our 'alloca' in
345 // the right place, then loading out of it. Flang does a lot less work
346 // (probably does its own emitting!), but we have more complicated AST
347 // nodes to worry about, so we can just count on opt to remove the extra
348 // alloca/load/store set.
349 auto alloca = cir::AllocaOp::create(
350 builder, start, x.getType(), "x_var",
351 cgf.cgm.getSize(
352 cgf.getContext().getTypeAlignInChars(inf.X->getType())));
353
354 alloca.setInitAttr(builder.getUnitAttr());
355 builder.CIRBaseBuilderTy::createStore(start, recipeBlock->getArgument(0),
356 alloca);
357
358 const VarDecl *xval = getLValueDecl(inf.X);
359 CIRGenFunction::DeclMapRevertingRAII declMapRAII{cgf, xval};
361 xval, Address{alloca, argTy, cgf.getContext().getDeclAlign(xval)});
362
363 if (inf.WholeExpr)
364 res = cgf.emitStmt(inf.WholeExpr, /*useCurrentScope=*/true);
365
366 auto load = cir::LoadOp::create(builder, start, {alloca});
367 mlir::acc::YieldOp::create(builder, end, {load});
368 }
369
370 return {res, op};
371}
372
373mlir::LogicalResult
375 // While Atomic is an 'associated statement' construct, it 'steals' the
376 // expression it is associated with rather than emitting it inside of it. So
377 // it has custom emit logic.
378 mlir::Location start = getLoc(s.getSourceRange().getBegin());
379 mlir::Location end = getLoc(s.getSourceRange().getEnd());
381
382 switch (s.getAtomicKind()) {
385 mlir::acc::AtomicReadOp op =
386 emitAtomicRead(*this, builder, start, inf.First);
387 emitOpenACCClauses(op, s.getDirectiveKind(), s.clauses());
388 return mlir::success();
389 }
392 auto op = emitAtomicWrite(*this, builder, start, inf.First);
393 emitOpenACCClauses(op, s.getDirectiveKind(), s.clauses());
394 return mlir::success();
395 }
399 auto [res, op] = emitAtomicUpdate(*this, builder, start, end, inf.First);
400 emitOpenACCClauses(op, s.getDirectiveKind(), s.clauses());
401 return res;
402 }
404 // Atomic-capture is made up of two statements, either an update = read,
405 // read + update, or read + write. As a result, the IR represents the
406 // capture region as having those two 'inside' of it.
407 auto op = mlir::acc::AtomicCaptureOp::create(builder, start, /*ifCond=*/{});
408 emitOpenACCClauses(op, s.getDirectiveKind(), s.clauses());
409 mlir::LogicalResult res = mlir::success();
410 {
411 mlir::OpBuilder::InsertionGuard guardCase(builder);
412
413 mlir::Block *block =
414 builder.createBlock(&op.getRegion(), op.getRegion().end(), {}, {});
415
416 builder.setInsertionPointToStart(block);
417
418 auto terminator = mlir::acc::TerminatorOp::create(builder, end);
419
420 // The AtomicCaptureOp only permits the two acc.atomic.* operations inside
421 // of it, so all other parts of the expression need to be emitted before
422 // the AtomicCaptureOp, then moved into place.
423 builder.setInsertionPoint(op);
424
425 switch (inf.Form) {
426 default:
427 llvm_unreachable("invalid form for Capture");
429 mlir::acc::AtomicReadOp first =
430 emitAtomicRead(*this, builder, start, inf.First);
431 mlir::acc::AtomicWriteOp second =
432 emitAtomicWrite(*this, builder, start, inf.Second);
433
434 first->moveBefore(terminator);
435 second->moveBefore(terminator);
436 break;
437 }
439 mlir::acc::AtomicReadOp first =
440 emitAtomicRead(*this, builder, start, inf.First);
441 auto [this_res, second] =
442 emitAtomicUpdate(*this, builder, start, end, inf.Second);
443 res = this_res;
444
445 first->moveBefore(terminator);
446 second->moveBefore(terminator);
447 break;
448 }
450 auto [this_res, first] =
451 emitAtomicUpdate(*this, builder, start, end, inf.First);
452 res = this_res;
453 mlir::acc::AtomicReadOp second =
454 emitAtomicRead(*this, builder, start, inf.Second);
455
456 first->moveBefore(terminator);
457 second->moveBefore(terminator);
458 break;
459 }
460 }
461 }
462 return res;
463 }
464 }
465
466 llvm_unreachable("unknown OpenACC atomic kind");
467}
static mlir::acc::AtomicReadOp emitAtomicRead(CIRGenFunction &cgf, CIRGenBuilderTy &builder, mlir::Location start, const OpenACCAtomicConstruct::SingleStmtInfo &inf)
static std::pair< mlir::LogicalResult, mlir::acc::AtomicUpdateOp > emitAtomicUpdate(CIRGenFunction &cgf, CIRGenBuilderTy &builder, mlir::Location start, mlir::Location end, const OpenACCAtomicConstruct::SingleStmtInfo &inf)
static mlir::acc::AtomicWriteOp emitAtomicWrite(CIRGenFunction &cgf, CIRGenBuilderTy &builder, mlir::Location start, const OpenACCAtomicConstruct::SingleStmtInfo &inf)
const VarDecl * getLValueDecl(const Expr *e)
This file defines OpenACC AST classes for statement-level contructs.
const StmtInfo getAssociatedStmtInfo() const
OpenACCAtomicKind getAtomicKind() const
ArrayRef< Expr * > getVarList() const
Stmt * getStructuredBlock()
bool hasDevNumExpr() const
ArrayRef< Expr * > getQueueIdExprs() const
Expr * getDevNumExpr() const
CharUnits getTypeAlignInChars(QualType T) const
Return the ABI-specified alignment of a (complete) type T, in characters.
CharUnits getDeclAlign(const Decl *D, bool ForAlignof=false) const
Return a conservative estimate of the alignment of the specified decl D.
mlir::Type convertType(clang::QualType t)
mlir::LogicalResult emitOpenACCDataConstruct(const OpenACCDataConstruct &s)
mlir::LogicalResult emitOpenACCCombinedConstruct(const OpenACCCombinedConstruct &s)
mlir::LogicalResult emitOpenACCWaitConstruct(const OpenACCWaitConstruct &s)
mlir::LogicalResult emitOpenACCUpdateConstruct(const OpenACCUpdateConstruct &s)
mlir::LogicalResult emitOpenACCCacheConstruct(const OpenACCCacheConstruct &s)
void replaceAddrOfLocalVar(const clang::VarDecl *vd, Address addr)
LValue emitLValue(const clang::Expr *e)
Emit code to compute a designator that specifies the location of the expression.
mlir::Location getLoc(clang::SourceLocation srcLoc)
Helpers to convert Clang's SourceLocation to a MLIR Location.
mlir::LogicalResult emitOpenACCInitConstruct(const OpenACCInitConstruct &s)
mlir::LogicalResult emitOpenACCSetConstruct(const OpenACCSetConstruct &s)
OpenACCDataOperandInfo getOpenACCDataOperandInfo(const Expr *e)
mlir::LogicalResult emitOpenACCComputeConstruct(const OpenACCComputeConstruct &s)
mlir::LogicalResult emitOpenACCShutdownConstruct(const OpenACCShutdownConstruct &s)
mlir::LogicalResult emitOpenACCHostDataConstruct(const OpenACCHostDataConstruct &s)
mlir::Value emitScalarExpr(const clang::Expr *e, bool ignoreResultAssign=false)
Emit the computation of the specified expression of scalar type.
mlir::MLIRContext & getMLIRContext()
mlir::LogicalResult emitOpenACCEnterDataConstruct(const OpenACCEnterDataConstruct &s)
RValue emitAnyExpr(const clang::Expr *e, AggValueSlot aggSlot=AggValueSlot::ignored(), bool ignoreResult=false)
Emit code to compute the specified expression which can have any type.
clang::ASTContext & getContext() const
mlir::LogicalResult emitStmt(const clang::Stmt *s, bool useCurrentScope, llvm::ArrayRef< const Attr * > attrs={})
mlir::LogicalResult emitOpenACCExitDataConstruct(const OpenACCExitDataConstruct &s)
mlir::LogicalResult emitOpenACCAtomicConstruct(const OpenACCAtomicConstruct &s)
mlir::IntegerAttr getSize(CharUnits size)
mlir::Value getPointer() const
mlir::Value getValue() const
Return the value of this scalar value.
Definition CIRGenValue.h:57
This represents one expression.
Definition Expr.h:112
Expr * IgnoreImpCasts() LLVM_READONLY
Skip past any implicit casts which might surround this expression until reaching a fixed point.
Definition Expr.cpp:3081
QualType getType() const
Definition Expr.h:144
Stmt - This represents one statement.
Definition Stmt.h:85
Represents a variable declaration or definition.
Definition Decl.h:932
const internal::VariadicDynCastAllOfMatcher< Stmt, Expr > expr
Matches expressions.
Top level wrappers for InstallAPI frontend operations.
OpenACCDirectiveKind
OpenACCComputeConstruct(OpenACCDirectiveKind K, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef< const OpenACCClause * > Clauses, Stmt *StructuredBlock)
U cast(CodeGen::Address addr)
Definition Address.h:327
enum OpenACCAtomicConstruct::StmtInfo::StmtForm Form
Represents a scope, including function bodies, compound statements, and the substatements of if/while...