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 clause nodes as CIR code. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include <type_traits> |
14 | |
15 | #include "CIRGenFunction.h" |
16 | |
17 | #include "clang/AST/ExprCXX.h" |
18 | |
19 | #include "mlir/Dialect/Arith/IR/Arith.h" |
20 | #include "mlir/Dialect/OpenACC/OpenACC.h" |
21 | #include "llvm/ADT/TypeSwitch.h" |
22 | |
23 | using namespace clang; |
24 | using namespace clang::CIRGen; |
25 | |
26 | namespace { |
27 | // Simple type-trait to see if the first template arg is one of the list, so we |
28 | // can tell whether to `if-constexpr` a bunch of stuff. |
29 | template <typename ToTest, typename T, typename... Tys> |
30 | constexpr bool isOneOfTypes = |
31 | std::is_same_v<ToTest, T> || isOneOfTypes<ToTest, Tys...>; |
32 | template <typename ToTest, typename T> |
33 | constexpr bool isOneOfTypes<ToTest, T> = std::is_same_v<ToTest, T>; |
34 | |
35 | // Holds information for emitting clauses for a combined construct. We |
36 | // instantiate the clause emitter with this type so that it can use |
37 | // if-constexpr to specially handle these. |
38 | template <typename CompOpTy> struct CombinedConstructClauseInfo { |
39 | using ComputeOpTy = CompOpTy; |
40 | ComputeOpTy computeOp; |
41 | mlir::acc::LoopOp loopOp; |
42 | }; |
43 | template <typename ToTest> constexpr bool isCombinedType = false; |
44 | template <typename T> |
45 | constexpr bool isCombinedType<CombinedConstructClauseInfo<T>> = true; |
46 | |
47 | template <typename OpTy> |
48 | class OpenACCClauseCIREmitter final |
49 | : public OpenACCClauseVisitor<OpenACCClauseCIREmitter<OpTy>> { |
50 | // Necessary for combined constructs. |
51 | template <typename FriendOpTy> friend class OpenACCClauseCIREmitter; |
52 | |
53 | OpTy &operation; |
54 | CIRGen::CIRGenFunction &cgf; |
55 | CIRGen::CIRGenBuilderTy &builder; |
56 | |
57 | // This is necessary since a few of the clauses emit differently based on the |
58 | // directive kind they are attached to. |
59 | OpenACCDirectiveKind dirKind; |
60 | // TODO(cir): This source location should be able to go away once the NYI |
61 | // diagnostics are gone. |
62 | SourceLocation dirLoc; |
63 | |
64 | llvm::SmallVector<mlir::acc::DeviceType> lastDeviceTypeValues; |
65 | // Keep track of the async-clause so that we can shortcut updating the data |
66 | // operands async clauses. |
67 | bool hasAsyncClause = false; |
68 | // Keep track of the data operands so that we can update their async clauses. |
69 | llvm::SmallVector<mlir::Operation *> dataOperands; |
70 | |
71 | void clauseNotImplemented(const OpenACCClause &c) { |
72 | cgf.cgm.errorNYI(loc: c.getSourceRange(), feature: "OpenACC Clause" , name: c.getClauseKind()); |
73 | } |
74 | |
75 | void setLastDeviceTypeClause(const OpenACCDeviceTypeClause &clause) { |
76 | lastDeviceTypeValues.clear(); |
77 | |
78 | llvm::for_each(clause.getArchitectures(), |
79 | [this](const DeviceTypeArgument &arg) { |
80 | lastDeviceTypeValues.push_back( |
81 | decodeDeviceType(arg.getIdentifierInfo())); |
82 | }); |
83 | } |
84 | |
85 | mlir::Value emitIntExpr(const Expr *intExpr) { |
86 | mlir::Value expr = cgf.emitScalarExpr(intExpr); |
87 | mlir::Location exprLoc = cgf.cgm.getLoc(intExpr->getBeginLoc()); |
88 | |
89 | mlir::IntegerType targetType = mlir::IntegerType::get( |
90 | &cgf.getMLIRContext(), cgf.getContext().getIntWidth(intExpr->getType()), |
91 | intExpr->getType()->isSignedIntegerOrEnumerationType() |
92 | ? mlir::IntegerType::SignednessSemantics::Signed |
93 | : mlir::IntegerType::SignednessSemantics::Unsigned); |
94 | |
95 | auto conversionOp = builder.create<mlir::UnrealizedConversionCastOp>( |
96 | exprLoc, targetType, expr); |
97 | return conversionOp.getResult(0); |
98 | } |
99 | |
100 | // 'condition' as an OpenACC grammar production is used for 'if' and (some |
101 | // variants of) 'self'. It needs to be emitted as a signless-1-bit value, so |
102 | // this function emits the expression, then sets the unrealized conversion |
103 | // cast correctly, and returns the completed value. |
104 | mlir::Value createCondition(const Expr *condExpr) { |
105 | mlir::Value condition = cgf.evaluateExprAsBool(condExpr); |
106 | mlir::Location exprLoc = cgf.cgm.getLoc(condExpr->getBeginLoc()); |
107 | mlir::IntegerType targetType = mlir::IntegerType::get( |
108 | &cgf.getMLIRContext(), /*width=*/1, |
109 | mlir::IntegerType::SignednessSemantics::Signless); |
110 | auto conversionOp = builder.create<mlir::UnrealizedConversionCastOp>( |
111 | exprLoc, targetType, condition); |
112 | return conversionOp.getResult(0); |
113 | } |
114 | |
115 | mlir::Value createConstantInt(mlir::Location loc, unsigned width, |
116 | int64_t value) { |
117 | mlir::IntegerType ty = mlir::IntegerType::get( |
118 | &cgf.getMLIRContext(), width, |
119 | mlir::IntegerType::SignednessSemantics::Signless); |
120 | auto constOp = builder.create<mlir::arith::ConstantOp>( |
121 | loc, builder.getIntegerAttr(ty, value)); |
122 | |
123 | return constOp.getResult(); |
124 | } |
125 | |
126 | mlir::Value createConstantInt(SourceLocation loc, unsigned width, |
127 | int64_t value) { |
128 | return createConstantInt(cgf.cgm.getLoc(loc), width, value); |
129 | } |
130 | |
131 | mlir::acc::DeviceType decodeDeviceType(const IdentifierInfo *ii) { |
132 | // '*' case leaves no identifier-info, just a nullptr. |
133 | if (!ii) |
134 | return mlir::acc::DeviceType::Star; |
135 | return llvm::StringSwitch<mlir::acc::DeviceType>(ii->getName()) |
136 | .CaseLower("default" , mlir::acc::DeviceType::Default) |
137 | .CaseLower("host" , mlir::acc::DeviceType::Host) |
138 | .CaseLower("multicore" , mlir::acc::DeviceType::Multicore) |
139 | .CasesLower("nvidia" , "acc_device_nvidia" , |
140 | mlir::acc::DeviceType::Nvidia) |
141 | .CaseLower("radeon" , mlir::acc::DeviceType::Radeon); |
142 | } |
143 | |
144 | mlir::acc::GangArgType decodeGangType(OpenACCGangKind gk) { |
145 | switch (gk) { |
146 | case OpenACCGangKind::Num: |
147 | return mlir::acc::GangArgType::Num; |
148 | case OpenACCGangKind::Dim: |
149 | return mlir::acc::GangArgType::Dim; |
150 | case OpenACCGangKind::Static: |
151 | return mlir::acc::GangArgType::Static; |
152 | } |
153 | llvm_unreachable("unknown gang kind" ); |
154 | } |
155 | |
156 | template <typename U = void, |
157 | typename = std::enable_if_t<isCombinedType<OpTy>, U>> |
158 | void applyToLoopOp(const OpenACCClause &c) { |
159 | mlir::OpBuilder::InsertionGuard guardCase(builder); |
160 | builder.setInsertionPoint(operation.loopOp); |
161 | OpenACCClauseCIREmitter<mlir::acc::LoopOp> loopEmitter{ |
162 | operation.loopOp, cgf, builder, dirKind, dirLoc}; |
163 | loopEmitter.lastDeviceTypeValues = lastDeviceTypeValues; |
164 | loopEmitter.Visit(&c); |
165 | } |
166 | |
167 | template <typename U = void, |
168 | typename = std::enable_if_t<isCombinedType<OpTy>, U>> |
169 | void applyToComputeOp(const OpenACCClause &c) { |
170 | mlir::OpBuilder::InsertionGuard guardCase(builder); |
171 | builder.setInsertionPoint(operation.computeOp); |
172 | OpenACCClauseCIREmitter<typename OpTy::ComputeOpTy> computeEmitter{ |
173 | operation.computeOp, cgf, builder, dirKind, dirLoc}; |
174 | |
175 | computeEmitter.lastDeviceTypeValues = lastDeviceTypeValues; |
176 | |
177 | // Async handler uses the first data operand to figure out where to insert |
178 | // its information if it is present. This ensures that the new handler will |
179 | // correctly set the insertion point for async. |
180 | if (!dataOperands.empty()) |
181 | computeEmitter.dataOperands.push_back(dataOperands.front()); |
182 | computeEmitter.Visit(&c); |
183 | |
184 | // Make sure all of the new data operands are kept track of here. The |
185 | // combined constructs always apply 'async' to only the compute component, |
186 | // so we need to collect these. |
187 | dataOperands.append(computeEmitter.dataOperands); |
188 | } |
189 | |
190 | struct DataOperandInfo { |
191 | mlir::Location beginLoc; |
192 | mlir::Value varValue; |
193 | std::string name; |
194 | llvm::SmallVector<mlir::Value> bounds; |
195 | }; |
196 | |
197 | mlir::Value createBound(mlir::Location boundLoc, mlir::Value lowerBound, |
198 | mlir::Value upperBound, mlir::Value extent) { |
199 | // Arrays always have a start-idx of 0. |
200 | mlir::Value startIdx = createConstantInt(boundLoc, 64, 0); |
201 | // Stride is always 1 in C/C++. |
202 | mlir::Value stride = createConstantInt(boundLoc, 64, 1); |
203 | |
204 | auto bound = builder.create<mlir::acc::DataBoundsOp>(boundLoc, lowerBound, |
205 | upperBound); |
206 | bound.getStartIdxMutable().assign(startIdx); |
207 | if (extent) |
208 | bound.getExtentMutable().assign(extent); |
209 | bound.getStrideMutable().assign(stride); |
210 | |
211 | return bound; |
212 | } |
213 | |
214 | // A helper function that gets the information from an operand to a data |
215 | // clause, so that it can be used to emit the data operations. |
216 | DataOperandInfo getDataOperandInfo(OpenACCDirectiveKind dk, const Expr *e) { |
217 | // TODO: OpenACC: Cache was different enough as to need a separate |
218 | // `ActOnCacheVar`, so we are going to need to do some investigations here |
219 | // when it comes to implement this for cache. |
220 | if (dk == OpenACCDirectiveKind::Cache) { |
221 | cgf.cgm.errorNYI(e->getSourceRange(), |
222 | "OpenACC data operand for 'cache' directive" ); |
223 | return {cgf.cgm.getLoc(e->getBeginLoc()), {}, {}, {}}; |
224 | } |
225 | |
226 | const Expr *curVarExpr = e->IgnoreParenImpCasts(); |
227 | |
228 | mlir::Location exprLoc = cgf.cgm.getLoc(curVarExpr->getBeginLoc()); |
229 | llvm::SmallVector<mlir::Value> bounds; |
230 | |
231 | std::string exprString; |
232 | llvm::raw_string_ostream os(exprString); |
233 | e->printPretty(os, nullptr, cgf.getContext().getPrintingPolicy()); |
234 | |
235 | // Assemble the list of bounds. |
236 | while (isa<ArraySectionExpr, ArraySubscriptExpr>(Val: curVarExpr)) { |
237 | mlir::Location boundLoc = cgf.cgm.getLoc(curVarExpr->getBeginLoc()); |
238 | mlir::Value lowerBound; |
239 | mlir::Value upperBound; |
240 | mlir::Value extent; |
241 | |
242 | if (const auto *section = dyn_cast<ArraySectionExpr>(Val: curVarExpr)) { |
243 | if (const Expr *lb = section->getLowerBound()) |
244 | lowerBound = emitIntExpr(lb); |
245 | else |
246 | lowerBound = createConstantInt(boundLoc, 64, 0); |
247 | |
248 | if (const Expr *len = section->getLength()) { |
249 | extent = emitIntExpr(len); |
250 | } else { |
251 | QualType baseTy = ArraySectionExpr::getBaseOriginalType( |
252 | Base: section->getBase()->IgnoreParenImpCasts()); |
253 | // We know this is the case as implicit lengths are only allowed for |
254 | // array types with a constant size, or a dependent size. AND since |
255 | // we are codegen we know we're not dependent. |
256 | auto *arrayTy = cgf.getContext().getAsConstantArrayType(T: baseTy); |
257 | // Rather than trying to calculate the extent based on the |
258 | // lower-bound, we can just emit this as an upper bound. |
259 | upperBound = |
260 | createConstantInt(boundLoc, 64, arrayTy->getLimitedSize() - 1); |
261 | } |
262 | |
263 | curVarExpr = section->getBase()->IgnoreParenImpCasts(); |
264 | } else { |
265 | const auto *subscript = cast<ArraySubscriptExpr>(Val: curVarExpr); |
266 | |
267 | lowerBound = emitIntExpr(subscript->getIdx()); |
268 | // Length of an array index is always 1. |
269 | extent = createConstantInt(boundLoc, 64, 1); |
270 | curVarExpr = subscript->getBase()->IgnoreParenImpCasts(); |
271 | } |
272 | |
273 | bounds.push_back(createBound(boundLoc, lowerBound, upperBound, extent)); |
274 | } |
275 | |
276 | if (const auto *memExpr = dyn_cast<MemberExpr>(Val: curVarExpr)) |
277 | return {exprLoc, cgf.emitMemberExpr(e: memExpr).getPointer(), exprString, |
278 | std::move(bounds)}; |
279 | |
280 | // Sema has made sure that only 4 types of things can get here, array |
281 | // subscript, array section, member expr, or DRE to a var decl (or the |
282 | // former 3 wrapping a var-decl), so we should be able to assume this is |
283 | // right. |
284 | const auto *dre = cast<DeclRefExpr>(Val: curVarExpr); |
285 | return {exprLoc, cgf.emitDeclRefLValue(e: dre).getPointer(), exprString, |
286 | std::move(bounds)}; |
287 | } |
288 | |
289 | template <typename BeforeOpTy, typename AfterOpTy> |
290 | void addDataOperand(const Expr *varOperand, mlir::acc::DataClause dataClause, |
291 | bool structured, bool implicit) { |
292 | DataOperandInfo opInfo = getDataOperandInfo(dk: dirKind, e: varOperand); |
293 | |
294 | // TODO: OpenACC: we should comprehend the 'modifier-list' here for the data |
295 | // operand. At the moment, we don't have a uniform way to assign these |
296 | // properly, and the dialect cannot represent anything other than 'readonly' |
297 | // and 'zero' on copyin/copyout/create, so for now, we skip it. |
298 | |
299 | auto beforeOp = |
300 | builder.create<BeforeOpTy>(opInfo.beginLoc, opInfo.varValue, structured, |
301 | implicit, opInfo.name, opInfo.bounds); |
302 | operation.getDataClauseOperandsMutable().append(beforeOp.getResult()); |
303 | |
304 | AfterOpTy afterOp; |
305 | { |
306 | mlir::OpBuilder::InsertionGuard guardCase(builder); |
307 | builder.setInsertionPointAfter(operation); |
308 | |
309 | if constexpr (std::is_same_v<AfterOpTy, mlir::acc::DeleteOp> || |
310 | std::is_same_v<AfterOpTy, mlir::acc::DetachOp>) { |
311 | // Detach/Delete ops don't have the variable reference here, so they |
312 | // take 1 fewer argument to their build function. |
313 | afterOp = builder.create<AfterOpTy>( |
314 | opInfo.beginLoc, beforeOp.getResult(), structured, implicit, |
315 | opInfo.name, opInfo.bounds); |
316 | } else { |
317 | afterOp = builder.create<AfterOpTy>( |
318 | opInfo.beginLoc, beforeOp.getResult(), opInfo.varValue, structured, |
319 | implicit, opInfo.name, opInfo.bounds); |
320 | } |
321 | } |
322 | |
323 | // Set the 'rest' of the info for both operations. |
324 | beforeOp.setDataClause(dataClause); |
325 | afterOp.setDataClause(dataClause); |
326 | |
327 | // Make sure we record these, so 'async' values can be updated later. |
328 | dataOperands.push_back(beforeOp.getOperation()); |
329 | dataOperands.push_back(afterOp.getOperation()); |
330 | } |
331 | |
332 | template <typename BeforeOpTy> |
333 | void addDataOperand(const Expr *varOperand, mlir::acc::DataClause dataClause, |
334 | bool structured, bool implicit) { |
335 | DataOperandInfo opInfo = getDataOperandInfo(dk: dirKind, e: varOperand); |
336 | auto beforeOp = |
337 | builder.create<BeforeOpTy>(opInfo.beginLoc, opInfo.varValue, structured, |
338 | implicit, opInfo.name, opInfo.bounds); |
339 | operation.getDataClauseOperandsMutable().append(beforeOp.getResult()); |
340 | |
341 | // Set the 'rest' of the info for the operation. |
342 | beforeOp.setDataClause(dataClause); |
343 | // Make sure we record these, so 'async' values can be updated later. |
344 | dataOperands.push_back(beforeOp.getOperation()); |
345 | } |
346 | |
347 | // Helper function that covers for the fact that we don't have this function |
348 | // on all operation types. |
349 | mlir::ArrayAttr getAsyncOnlyAttr() { |
350 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
351 | mlir::acc::KernelsOp, mlir::acc::DataOp>) |
352 | return operation.getAsyncOnlyAttr(); |
353 | else if constexpr (isCombinedType<OpTy>) |
354 | return operation.computeOp.getAsyncOnlyAttr(); |
355 | |
356 | // Note: 'wait' has async as well, but it cannot have data clauses, so we |
357 | // don't have to handle them here. |
358 | |
359 | llvm_unreachable("getting asyncOnly when clause not valid on operation?" ); |
360 | } |
361 | |
362 | // Helper function that covers for the fact that we don't have this function |
363 | // on all operation types. |
364 | mlir::ArrayAttr getAsyncOperandsDeviceTypeAttr() { |
365 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
366 | mlir::acc::KernelsOp, mlir::acc::DataOp>) |
367 | return operation.getAsyncOperandsDeviceTypeAttr(); |
368 | else if constexpr (isCombinedType<OpTy>) |
369 | return operation.computeOp.getAsyncOperandsDeviceTypeAttr(); |
370 | |
371 | // Note: 'wait' has async as well, but it cannot have data clauses, so we |
372 | // don't have to handle them here. |
373 | |
374 | llvm_unreachable( |
375 | "getting asyncOperandsDeviceType when clause not valid on operation?" ); |
376 | } |
377 | |
378 | // Helper function that covers for the fact that we don't have this function |
379 | // on all operation types. |
380 | mlir::OperandRange getAsyncOperands() { |
381 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
382 | mlir::acc::KernelsOp, mlir::acc::DataOp>) |
383 | return operation.getAsyncOperands(); |
384 | else if constexpr (isCombinedType<OpTy>) |
385 | return operation.computeOp.getAsyncOperands(); |
386 | |
387 | // Note: 'wait' has async as well, but it cannot have data clauses, so we |
388 | // don't have to handle them here. |
389 | |
390 | llvm_unreachable( |
391 | "getting asyncOperandsDeviceType when clause not valid on operation?" ); |
392 | } |
393 | |
394 | // The 'data' clauses all require that we add the 'async' values from the |
395 | // operation to them. We've collected the data operands along the way, so use |
396 | // that list to get the current 'async' values. |
397 | void updateDataOperandAsyncValues() { |
398 | if (!hasAsyncClause || dataOperands.empty()) |
399 | return; |
400 | |
401 | for (mlir::Operation *dataOp : dataOperands) { |
402 | llvm::TypeSwitch<mlir::Operation *, void>(dataOp) |
403 | .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS>([&](auto op) { |
404 | op.setAsyncOnlyAttr(getAsyncOnlyAttr()); |
405 | op.setAsyncOperandsDeviceTypeAttr(getAsyncOperandsDeviceTypeAttr()); |
406 | op.getAsyncOperandsMutable().assign(getAsyncOperands()); |
407 | }) |
408 | .Default([&](mlir::Operation *) { |
409 | llvm_unreachable("Not a data operation?" ); |
410 | }); |
411 | } |
412 | } |
413 | |
414 | public: |
415 | OpenACCClauseCIREmitter(OpTy &operation, CIRGen::CIRGenFunction &cgf, |
416 | CIRGen::CIRGenBuilderTy &builder, |
417 | OpenACCDirectiveKind dirKind, SourceLocation dirLoc) |
418 | : operation(operation), cgf(cgf), builder(builder), dirKind(dirKind), |
419 | dirLoc(dirLoc) {} |
420 | |
421 | void VisitClause(const OpenACCClause &clause) { |
422 | clauseNotImplemented(c: clause); |
423 | } |
424 | |
425 | // The entry point for the CIR emitter. All users should use this rather than |
426 | // 'visitClauseList', as this also handles the things that have to happen |
427 | // 'after' the clauses are all visited. |
428 | void emitClauses(ArrayRef<const OpenACCClause *> clauses) { |
429 | this->VisitClauseList(clauses); |
430 | updateDataOperandAsyncValues(); |
431 | } |
432 | |
433 | void VisitDefaultClause(const OpenACCDefaultClause &clause) { |
434 | // This type-trait checks if 'op'(the first arg) is one of the mlir::acc |
435 | // operations listed in the rest of the arguments. |
436 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
437 | mlir::acc::KernelsOp, mlir::acc::DataOp>) { |
438 | switch (clause.getDefaultClauseKind()) { |
439 | case OpenACCDefaultClauseKind::None: |
440 | operation.setDefaultAttr(mlir::acc::ClauseDefaultValue::None); |
441 | break; |
442 | case OpenACCDefaultClauseKind::Present: |
443 | operation.setDefaultAttr(mlir::acc::ClauseDefaultValue::Present); |
444 | break; |
445 | case OpenACCDefaultClauseKind::Invalid: |
446 | break; |
447 | } |
448 | } else if constexpr (isCombinedType<OpTy>) { |
449 | applyToComputeOp(clause); |
450 | } else { |
451 | llvm_unreachable("Unknown construct kind in VisitDefaultClause" ); |
452 | } |
453 | } |
454 | |
455 | void VisitDeviceTypeClause(const OpenACCDeviceTypeClause &clause) { |
456 | setLastDeviceTypeClause(clause); |
457 | |
458 | if constexpr (isOneOfTypes<OpTy, mlir::acc::InitOp, |
459 | mlir::acc::ShutdownOp>) { |
460 | llvm::for_each( |
461 | clause.getArchitectures(), [this](const DeviceTypeArgument &arg) { |
462 | operation.addDeviceType(builder.getContext(), |
463 | decodeDeviceType(arg.getIdentifierInfo())); |
464 | }); |
465 | } else if constexpr (isOneOfTypes<OpTy, mlir::acc::SetOp>) { |
466 | assert(!operation.getDeviceTypeAttr() && "already have device-type?" ); |
467 | assert(clause.getArchitectures().size() <= 1); |
468 | |
469 | if (!clause.getArchitectures().empty()) |
470 | operation.setDeviceType( |
471 | decodeDeviceType(clause.getArchitectures()[0].getIdentifierInfo())); |
472 | } else if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, |
473 | mlir::acc::SerialOp, mlir::acc::KernelsOp, |
474 | mlir::acc::DataOp, mlir::acc::LoopOp>) { |
475 | // Nothing to do here, these constructs don't have any IR for these, as |
476 | // they just modify the other clauses IR. So setting of |
477 | // `lastDeviceTypeValues` (done above) is all we need. |
478 | } else if constexpr (isCombinedType<OpTy>) { |
479 | // Nothing to do here either, combined constructs are just going to use |
480 | // 'lastDeviceTypeValues' to set the value for the child visitor. |
481 | } else { |
482 | // TODO: When we've implemented this for everything, switch this to an |
483 | // unreachable. update, data, routine constructs remain. |
484 | return clauseNotImplemented(c: clause); |
485 | } |
486 | } |
487 | |
488 | void VisitNumWorkersClause(const OpenACCNumWorkersClause &clause) { |
489 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, |
490 | mlir::acc::KernelsOp>) { |
491 | operation.addNumWorkersOperand(builder.getContext(), |
492 | emitIntExpr(clause.getIntExpr()), |
493 | lastDeviceTypeValues); |
494 | } else if constexpr (isCombinedType<OpTy>) { |
495 | applyToComputeOp(clause); |
496 | } else { |
497 | llvm_unreachable("Unknown construct kind in VisitNumGangsClause" ); |
498 | } |
499 | } |
500 | |
501 | void VisitVectorLengthClause(const OpenACCVectorLengthClause &clause) { |
502 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, |
503 | mlir::acc::KernelsOp>) { |
504 | operation.addVectorLengthOperand(builder.getContext(), |
505 | emitIntExpr(clause.getIntExpr()), |
506 | lastDeviceTypeValues); |
507 | } else if constexpr (isCombinedType<OpTy>) { |
508 | applyToComputeOp(clause); |
509 | } else { |
510 | llvm_unreachable("Unknown construct kind in VisitVectorLengthClause" ); |
511 | } |
512 | } |
513 | |
514 | void VisitAsyncClause(const OpenACCAsyncClause &clause) { |
515 | hasAsyncClause = true; |
516 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
517 | mlir::acc::KernelsOp, mlir::acc::DataOp>) { |
518 | if (!clause.hasIntExpr()) |
519 | operation.addAsyncOnly(builder.getContext(), lastDeviceTypeValues); |
520 | else { |
521 | |
522 | mlir::Value intExpr; |
523 | { |
524 | // Async int exprs can be referenced by the data operands, which means |
525 | // that the int-exprs have to appear before them. IF there is a data |
526 | // operand already, set the insertion point to 'before' it. |
527 | mlir::OpBuilder::InsertionGuard guardCase(builder); |
528 | if (!dataOperands.empty()) |
529 | builder.setInsertionPoint(dataOperands.front()); |
530 | intExpr = emitIntExpr(clause.getIntExpr()); |
531 | } |
532 | operation.addAsyncOperand(builder.getContext(), intExpr, |
533 | lastDeviceTypeValues); |
534 | } |
535 | } else if constexpr (isOneOfTypes<OpTy, mlir::acc::WaitOp>) { |
536 | // Wait doesn't have a device_type, so its handling here is slightly |
537 | // different. |
538 | if (!clause.hasIntExpr()) |
539 | operation.setAsync(true); |
540 | else |
541 | operation.getAsyncOperandMutable().append( |
542 | emitIntExpr(clause.getIntExpr())); |
543 | } else if constexpr (isCombinedType<OpTy>) { |
544 | applyToComputeOp(clause); |
545 | } else { |
546 | // TODO: When we've implemented this for everything, switch this to an |
547 | // unreachable. Combined constructs remain. Data, enter data, exit data, |
548 | // update constructs remain. |
549 | return clauseNotImplemented(c: clause); |
550 | } |
551 | } |
552 | |
553 | void VisitSelfClause(const OpenACCSelfClause &clause) { |
554 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
555 | mlir::acc::KernelsOp>) { |
556 | if (clause.isEmptySelfClause()) { |
557 | operation.setSelfAttr(true); |
558 | } else if (clause.isConditionExprClause()) { |
559 | assert(clause.hasConditionExpr()); |
560 | operation.getSelfCondMutable().append( |
561 | createCondition(clause.getConditionExpr())); |
562 | } else { |
563 | llvm_unreachable("var-list version of self shouldn't get here" ); |
564 | } |
565 | } else if constexpr (isCombinedType<OpTy>) { |
566 | applyToComputeOp(clause); |
567 | } else { |
568 | // TODO: When we've implemented this for everything, switch this to an |
569 | // unreachable. update construct remains. |
570 | return clauseNotImplemented(c: clause); |
571 | } |
572 | } |
573 | |
574 | void VisitIfClause(const OpenACCIfClause &clause) { |
575 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
576 | mlir::acc::KernelsOp, mlir::acc::InitOp, |
577 | mlir::acc::ShutdownOp, mlir::acc::SetOp, |
578 | mlir::acc::DataOp, mlir::acc::WaitOp, |
579 | mlir::acc::HostDataOp>) { |
580 | operation.getIfCondMutable().append( |
581 | createCondition(clause.getConditionExpr())); |
582 | } else if constexpr (isCombinedType<OpTy>) { |
583 | applyToComputeOp(clause); |
584 | } else { |
585 | // 'if' applies to most of the constructs, but hold off on lowering them |
586 | // until we can write tests/know what we're doing with codegen to make |
587 | // sure we get it right. |
588 | // TODO: When we've implemented this for everything, switch this to an |
589 | // unreachable. Enter data, exit data, host_data, update constructs |
590 | // remain. |
591 | return clauseNotImplemented(c: clause); |
592 | } |
593 | } |
594 | |
595 | void VisitIfPresentClause(const OpenACCIfPresentClause &clause) { |
596 | if constexpr (isOneOfTypes<OpTy, mlir::acc::HostDataOp>) { |
597 | operation.setIfPresent(true); |
598 | } else if constexpr (isOneOfTypes<OpTy, mlir::acc::UpdateOp>) { |
599 | // Last unimplemented one here, so just put it in this way instead. |
600 | return clauseNotImplemented(c: clause); |
601 | } else { |
602 | llvm_unreachable("unknown construct kind in VisitIfPresentClause" ); |
603 | } |
604 | } |
605 | |
606 | void VisitDeviceNumClause(const OpenACCDeviceNumClause &clause) { |
607 | if constexpr (isOneOfTypes<OpTy, mlir::acc::InitOp, mlir::acc::ShutdownOp, |
608 | mlir::acc::SetOp>) { |
609 | operation.getDeviceNumMutable().append(emitIntExpr(clause.getIntExpr())); |
610 | } else { |
611 | llvm_unreachable( |
612 | "init, shutdown, set, are only valid device_num constructs" ); |
613 | } |
614 | } |
615 | |
616 | void VisitNumGangsClause(const OpenACCNumGangsClause &clause) { |
617 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, |
618 | mlir::acc::KernelsOp>) { |
619 | llvm::SmallVector<mlir::Value> values; |
620 | for (const Expr *E : clause.getIntExprs()) |
621 | values.push_back(emitIntExpr(E)); |
622 | |
623 | operation.addNumGangsOperands(builder.getContext(), values, |
624 | lastDeviceTypeValues); |
625 | } else if constexpr (isCombinedType<OpTy>) { |
626 | applyToComputeOp(clause); |
627 | } else { |
628 | llvm_unreachable("Unknown construct kind in VisitNumGangsClause" ); |
629 | } |
630 | } |
631 | |
632 | void VisitWaitClause(const OpenACCWaitClause &clause) { |
633 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
634 | mlir::acc::KernelsOp, mlir::acc::DataOp>) { |
635 | if (!clause.hasExprs()) { |
636 | operation.addWaitOnly(builder.getContext(), lastDeviceTypeValues); |
637 | } else { |
638 | llvm::SmallVector<mlir::Value> values; |
639 | if (clause.hasDevNumExpr()) |
640 | values.push_back(emitIntExpr(clause.getDevNumExpr())); |
641 | for (const Expr *E : clause.getQueueIdExprs()) |
642 | values.push_back(emitIntExpr(E)); |
643 | operation.addWaitOperands(builder.getContext(), clause.hasDevNumExpr(), |
644 | values, lastDeviceTypeValues); |
645 | } |
646 | } else if constexpr (isCombinedType<OpTy>) { |
647 | applyToComputeOp(clause); |
648 | } else { |
649 | // TODO: When we've implemented this for everything, switch this to an |
650 | // unreachable. Enter data, exit data, update constructs remain. |
651 | return clauseNotImplemented(c: clause); |
652 | } |
653 | } |
654 | |
655 | void VisitDefaultAsyncClause(const OpenACCDefaultAsyncClause &clause) { |
656 | if constexpr (isOneOfTypes<OpTy, mlir::acc::SetOp>) { |
657 | operation.getDefaultAsyncMutable().append( |
658 | emitIntExpr(clause.getIntExpr())); |
659 | } else { |
660 | llvm_unreachable("set, is only valid device_num constructs" ); |
661 | } |
662 | } |
663 | |
664 | void VisitSeqClause(const OpenACCSeqClause &clause) { |
665 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
666 | operation.addSeq(builder.getContext(), lastDeviceTypeValues); |
667 | } else if constexpr (isCombinedType<OpTy>) { |
668 | applyToLoopOp(clause); |
669 | } else { |
670 | // TODO: When we've implemented this for everything, switch this to an |
671 | // unreachable. Routine construct remains. |
672 | return clauseNotImplemented(c: clause); |
673 | } |
674 | } |
675 | |
676 | void VisitAutoClause(const OpenACCAutoClause &clause) { |
677 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
678 | operation.addAuto(builder.getContext(), lastDeviceTypeValues); |
679 | } else if constexpr (isCombinedType<OpTy>) { |
680 | applyToLoopOp(clause); |
681 | } else { |
682 | // TODO: When we've implemented this for everything, switch this to an |
683 | // unreachable. Routine, construct remains. |
684 | return clauseNotImplemented(c: clause); |
685 | } |
686 | } |
687 | |
688 | void VisitIndependentClause(const OpenACCIndependentClause &clause) { |
689 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
690 | operation.addIndependent(builder.getContext(), lastDeviceTypeValues); |
691 | } else if constexpr (isCombinedType<OpTy>) { |
692 | applyToLoopOp(clause); |
693 | } else { |
694 | // TODO: When we've implemented this for everything, switch this to an |
695 | // unreachable. Routine construct remains. |
696 | return clauseNotImplemented(c: clause); |
697 | } |
698 | } |
699 | |
700 | void VisitCollapseClause(const OpenACCCollapseClause &clause) { |
701 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
702 | llvm::APInt value = |
703 | clause.getIntExpr()->EvaluateKnownConstInt(Ctx: cgf.cgm.getASTContext()); |
704 | |
705 | value = value.sextOrTrunc(width: 64); |
706 | operation.setCollapseForDeviceTypes(builder.getContext(), |
707 | lastDeviceTypeValues, value); |
708 | } else if constexpr (isCombinedType<OpTy>) { |
709 | applyToLoopOp(clause); |
710 | } else { |
711 | llvm_unreachable("Unknown construct kind in VisitCollapseClause" ); |
712 | } |
713 | } |
714 | |
715 | void VisitTileClause(const OpenACCTileClause &clause) { |
716 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
717 | llvm::SmallVector<mlir::Value> values; |
718 | |
719 | for (const Expr *e : clause.getSizeExprs()) { |
720 | mlir::Location exprLoc = cgf.cgm.getLoc(e->getBeginLoc()); |
721 | |
722 | // We represent the * as -1. Additionally, this is a constant, so we |
723 | // can always just emit it as 64 bits to avoid having to do any more |
724 | // work to determine signedness or size. |
725 | if (isa<OpenACCAsteriskSizeExpr>(Val: e)) { |
726 | values.push_back(createConstantInt(exprLoc, 64, -1)); |
727 | } else { |
728 | llvm::APInt curValue = |
729 | e->EvaluateKnownConstInt(Ctx: cgf.cgm.getASTContext()); |
730 | values.push_back(createConstantInt( |
731 | exprLoc, 64, curValue.sextOrTrunc(64).getSExtValue())); |
732 | } |
733 | } |
734 | |
735 | operation.setTileForDeviceTypes(builder.getContext(), |
736 | lastDeviceTypeValues, values); |
737 | } else if constexpr (isCombinedType<OpTy>) { |
738 | applyToLoopOp(clause); |
739 | } else { |
740 | llvm_unreachable("Unknown construct kind in VisitTileClause" ); |
741 | } |
742 | } |
743 | |
744 | void VisitWorkerClause(const OpenACCWorkerClause &clause) { |
745 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
746 | if (clause.hasIntExpr()) |
747 | operation.addWorkerNumOperand(builder.getContext(), |
748 | emitIntExpr(clause.getIntExpr()), |
749 | lastDeviceTypeValues); |
750 | else |
751 | operation.addEmptyWorker(builder.getContext(), lastDeviceTypeValues); |
752 | |
753 | } else if constexpr (isCombinedType<OpTy>) { |
754 | applyToLoopOp(clause); |
755 | } else { |
756 | // TODO: When we've implemented this for everything, switch this to an |
757 | // unreachable. Combined constructs remain. |
758 | return clauseNotImplemented(c: clause); |
759 | } |
760 | } |
761 | |
762 | void VisitVectorClause(const OpenACCVectorClause &clause) { |
763 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
764 | if (clause.hasIntExpr()) |
765 | operation.addVectorOperand(builder.getContext(), |
766 | emitIntExpr(clause.getIntExpr()), |
767 | lastDeviceTypeValues); |
768 | else |
769 | operation.addEmptyVector(builder.getContext(), lastDeviceTypeValues); |
770 | |
771 | } else if constexpr (isCombinedType<OpTy>) { |
772 | applyToLoopOp(clause); |
773 | } else { |
774 | // TODO: When we've implemented this for everything, switch this to an |
775 | // unreachable. Combined constructs remain. |
776 | return clauseNotImplemented(c: clause); |
777 | } |
778 | } |
779 | |
780 | void VisitGangClause(const OpenACCGangClause &clause) { |
781 | if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { |
782 | if (clause.getNumExprs() == 0) { |
783 | operation.addEmptyGang(builder.getContext(), lastDeviceTypeValues); |
784 | } else { |
785 | llvm::SmallVector<mlir::Value> values; |
786 | llvm::SmallVector<mlir::acc::GangArgType> argTypes; |
787 | for (unsigned i : llvm::index_range(0u, clause.getNumExprs())) { |
788 | auto [kind, expr] = clause.getExpr(I: i); |
789 | mlir::Location exprLoc = cgf.cgm.getLoc(expr->getBeginLoc()); |
790 | argTypes.push_back(decodeGangType(kind)); |
791 | if (kind == OpenACCGangKind::Dim) { |
792 | llvm::APInt curValue = |
793 | expr->EvaluateKnownConstInt(Ctx: cgf.cgm.getASTContext()); |
794 | // The value is 1, 2, or 3, but the type isn't necessarily smaller |
795 | // than 64. |
796 | curValue = curValue.sextOrTrunc(width: 64); |
797 | values.push_back( |
798 | createConstantInt(exprLoc, 64, curValue.getSExtValue())); |
799 | } else if (isa<OpenACCAsteriskSizeExpr>(Val: expr)) { |
800 | values.push_back(createConstantInt(exprLoc, 64, -1)); |
801 | } else { |
802 | values.push_back(emitIntExpr(expr)); |
803 | } |
804 | } |
805 | |
806 | operation.addGangOperands(builder.getContext(), lastDeviceTypeValues, |
807 | argTypes, values); |
808 | } |
809 | } else if constexpr (isCombinedType<OpTy>) { |
810 | applyToLoopOp(clause); |
811 | } else { |
812 | llvm_unreachable("Unknown construct kind in VisitGangClause" ); |
813 | } |
814 | } |
815 | |
816 | void VisitCopyClause(const OpenACCCopyClause &clause) { |
817 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
818 | mlir::acc::KernelsOp>) { |
819 | for (auto var : clause.getVarList()) |
820 | addDataOperand<mlir::acc::CopyinOp, mlir::acc::CopyoutOp>( |
821 | var, mlir::acc::DataClause::acc_copy, /*structured=*/true, |
822 | /*implicit=*/false); |
823 | } else if constexpr (isCombinedType<OpTy>) { |
824 | applyToComputeOp(clause); |
825 | } else { |
826 | // TODO: When we've implemented this for everything, switch this to an |
827 | // unreachable. data, declare, combined constructs remain. |
828 | return clauseNotImplemented(c: clause); |
829 | } |
830 | } |
831 | |
832 | void VisitUseDeviceClause(const OpenACCUseDeviceClause &clause) { |
833 | if constexpr (isOneOfTypes<OpTy, mlir::acc::HostDataOp>) { |
834 | for (auto var : clause.getVarList()) |
835 | addDataOperand<mlir::acc::UseDeviceOp>( |
836 | var, mlir::acc::DataClause::acc_use_device, |
837 | /*structured=*/true, /*implicit=*/false); |
838 | } else { |
839 | llvm_unreachable("Unknown construct kind in VisitUseDeviceClause" ); |
840 | } |
841 | } |
842 | |
843 | void VisitDevicePtrClause(const OpenACCDevicePtrClause &clause) { |
844 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
845 | mlir::acc::KernelsOp>) { |
846 | for (auto var : clause.getVarList()) |
847 | addDataOperand<mlir::acc::DevicePtrOp>( |
848 | var, mlir::acc::DataClause::acc_deviceptr, /*structured=*/true, |
849 | /*implicit=*/false); |
850 | } else if constexpr (isCombinedType<OpTy>) { |
851 | applyToComputeOp(clause); |
852 | } else { |
853 | // TODO: When we've implemented this for everything, switch this to an |
854 | // unreachable. data, declare remain. |
855 | return clauseNotImplemented(c: clause); |
856 | } |
857 | } |
858 | |
859 | void VisitNoCreateClause(const OpenACCNoCreateClause &clause) { |
860 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
861 | mlir::acc::KernelsOp>) { |
862 | for (auto var : clause.getVarList()) |
863 | addDataOperand<mlir::acc::NoCreateOp, mlir::acc::DeleteOp>( |
864 | var, mlir::acc::DataClause::acc_no_create, /*structured=*/true, |
865 | /*implicit=*/false); |
866 | } else if constexpr (isCombinedType<OpTy>) { |
867 | applyToComputeOp(clause); |
868 | } else { |
869 | // TODO: When we've implemented this for everything, switch this to an |
870 | // unreachable. data remains. |
871 | return clauseNotImplemented(c: clause); |
872 | } |
873 | } |
874 | |
875 | void VisitPresentClause(const OpenACCPresentClause &clause) { |
876 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
877 | mlir::acc::KernelsOp>) { |
878 | for (auto var : clause.getVarList()) |
879 | addDataOperand<mlir::acc::PresentOp, mlir::acc::DeleteOp>( |
880 | var, mlir::acc::DataClause::acc_present, /*structured=*/true, |
881 | /*implicit=*/false); |
882 | } else if constexpr (isCombinedType<OpTy>) { |
883 | applyToComputeOp(clause); |
884 | } else { |
885 | // TODO: When we've implemented this for everything, switch this to an |
886 | // unreachable. data & declare remain. |
887 | return clauseNotImplemented(c: clause); |
888 | } |
889 | } |
890 | |
891 | void VisitAttachClause(const OpenACCAttachClause &clause) { |
892 | if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, |
893 | mlir::acc::KernelsOp>) { |
894 | for (auto var : clause.getVarList()) |
895 | addDataOperand<mlir::acc::AttachOp, mlir::acc::DetachOp>( |
896 | var, mlir::acc::DataClause::acc_attach, /*structured=*/true, |
897 | /*implicit=*/false); |
898 | } else if constexpr (isCombinedType<OpTy>) { |
899 | applyToComputeOp(clause); |
900 | } else { |
901 | // TODO: When we've implemented this for everything, switch this to an |
902 | // unreachable. data, enter data remain. |
903 | return clauseNotImplemented(c: clause); |
904 | } |
905 | } |
906 | }; |
907 | |
908 | template <typename OpTy> |
909 | auto makeClauseEmitter(OpTy &op, CIRGen::CIRGenFunction &cgf, |
910 | CIRGen::CIRGenBuilderTy &builder, |
911 | OpenACCDirectiveKind dirKind, SourceLocation dirLoc) { |
912 | return OpenACCClauseCIREmitter<OpTy>(op, cgf, builder, dirKind, dirLoc); |
913 | } |
914 | } // namespace |
915 | |
916 | template <typename Op> |
917 | void CIRGenFunction::emitOpenACCClauses( |
918 | Op &op, OpenACCDirectiveKind dirKind, SourceLocation dirLoc, |
919 | ArrayRef<const OpenACCClause *> clauses) { |
920 | mlir::OpBuilder::InsertionGuard guardCase(builder); |
921 | |
922 | // Sets insertion point before the 'op', since every new expression needs to |
923 | // be before the operation. |
924 | builder.setInsertionPoint(op); |
925 | makeClauseEmitter(op, *this, builder, dirKind, dirLoc).emitClauses(clauses); |
926 | } |
927 | |
928 | #define EXPL_SPEC(N) \ |
929 | template void CIRGenFunction::emitOpenACCClauses<N>( \ |
930 | N &, OpenACCDirectiveKind, SourceLocation, \ |
931 | ArrayRef<const OpenACCClause *>); |
932 | EXPL_SPEC(mlir::acc::ParallelOp) |
933 | EXPL_SPEC(mlir::acc::SerialOp) |
934 | EXPL_SPEC(mlir::acc::KernelsOp) |
935 | EXPL_SPEC(mlir::acc::LoopOp) |
936 | EXPL_SPEC(mlir::acc::DataOp) |
937 | EXPL_SPEC(mlir::acc::InitOp) |
938 | EXPL_SPEC(mlir::acc::ShutdownOp) |
939 | EXPL_SPEC(mlir::acc::SetOp) |
940 | EXPL_SPEC(mlir::acc::WaitOp) |
941 | EXPL_SPEC(mlir::acc::HostDataOp) |
942 | #undef EXPL_SPEC |
943 | |
944 | template <typename ComputeOp, typename LoopOp> |
945 | void CIRGenFunction::emitOpenACCClauses( |
946 | ComputeOp &op, LoopOp &loopOp, OpenACCDirectiveKind dirKind, |
947 | SourceLocation dirLoc, ArrayRef<const OpenACCClause *> clauses) { |
948 | static_assert(std::is_same_v<mlir::acc::LoopOp, LoopOp>); |
949 | |
950 | CombinedConstructClauseInfo<ComputeOp> inf{op, loopOp}; |
951 | // We cannot set the insertion point here and do so in the emitter, but make |
952 | // sure we reset it with the 'guard' anyway. |
953 | mlir::OpBuilder::InsertionGuard guardCase(builder); |
954 | makeClauseEmitter(inf, *this, builder, dirKind, dirLoc).emitClauses(clauses); |
955 | } |
956 | |
957 | #define EXPL_SPEC(N) \ |
958 | template void CIRGenFunction::emitOpenACCClauses<N, mlir::acc::LoopOp>( \ |
959 | N &, mlir::acc::LoopOp &, OpenACCDirectiveKind, SourceLocation, \ |
960 | ArrayRef<const OpenACCClause *>); |
961 | |
962 | EXPL_SPEC(mlir::acc::ParallelOp) |
963 | EXPL_SPEC(mlir::acc::SerialOp) |
964 | EXPL_SPEC(mlir::acc::KernelsOp) |
965 | #undef EXPL_SPEC |
966 | |