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(c.getSourceRange(), "OpenACC Clause", c.getClauseKind()); 73 } 74 75 void setLastDeviceTypeClause(const OpenACCDeviceTypeClause &clause) { 76 lastDeviceTypeValues.clear(); 77 78 for (const DeviceTypeArgument &arg : clause.getArchitectures()) 79 lastDeviceTypeValues.push_back(decodeDeviceType(arg.getIdentifierInfo())); 80 } 81 82 mlir::Value emitIntExpr(const Expr *intExpr) { 83 return cgf.emitOpenACCIntExpr(intExpr); 84 } 85 86 // 'condition' as an OpenACC grammar production is used for 'if' and (some 87 // variants of) 'self'. It needs to be emitted as a signless-1-bit value, so 88 // this function emits the expression, then sets the unrealized conversion 89 // cast correctly, and returns the completed value. 90 mlir::Value createCondition(const Expr *condExpr) { 91 mlir::Value condition = cgf.evaluateExprAsBool(condExpr); 92 mlir::Location exprLoc = cgf.cgm.getLoc(condExpr->getBeginLoc()); 93 mlir::IntegerType targetType = mlir::IntegerType::get( 94 &cgf.getMLIRContext(), /*width=*/1, 95 mlir::IntegerType::SignednessSemantics::Signless); 96 auto conversionOp = builder.create<mlir::UnrealizedConversionCastOp>( 97 exprLoc, targetType, condition); 98 return conversionOp.getResult(0); 99 } 100 101 mlir::Value createConstantInt(mlir::Location loc, unsigned width, 102 int64_t value) { 103 return cgf.createOpenACCConstantInt(loc, width, value); 104 mlir::IntegerType ty = mlir::IntegerType::get( 105 &cgf.getMLIRContext(), width, 106 mlir::IntegerType::SignednessSemantics::Signless); 107 auto constOp = builder.create<mlir::arith::ConstantOp>( 108 loc, builder.getIntegerAttr(ty, value)); 109 110 return constOp.getResult(); 111 } 112 113 mlir::Value createConstantInt(SourceLocation loc, unsigned width, 114 int64_t value) { 115 return createConstantInt(cgf.cgm.getLoc(loc), width, value); 116 } 117 118 mlir::acc::DeviceType decodeDeviceType(const IdentifierInfo *ii) { 119 // '*' case leaves no identifier-info, just a nullptr. 120 if (!ii) 121 return mlir::acc::DeviceType::Star; 122 return llvm::StringSwitch<mlir::acc::DeviceType>(ii->getName()) 123 .CaseLower("default", mlir::acc::DeviceType::Default) 124 .CaseLower("host", mlir::acc::DeviceType::Host) 125 .CaseLower("multicore", mlir::acc::DeviceType::Multicore) 126 .CasesLower("nvidia", "acc_device_nvidia", 127 mlir::acc::DeviceType::Nvidia) 128 .CaseLower("radeon", mlir::acc::DeviceType::Radeon); 129 } 130 131 mlir::acc::GangArgType decodeGangType(OpenACCGangKind gk) { 132 switch (gk) { 133 case OpenACCGangKind::Num: 134 return mlir::acc::GangArgType::Num; 135 case OpenACCGangKind::Dim: 136 return mlir::acc::GangArgType::Dim; 137 case OpenACCGangKind::Static: 138 return mlir::acc::GangArgType::Static; 139 } 140 llvm_unreachable("unknown gang kind"); 141 } 142 143 template <typename U = void, 144 typename = std::enable_if_t<isCombinedType<OpTy>, U>> 145 void applyToLoopOp(const OpenACCClause &c) { 146 mlir::OpBuilder::InsertionGuard guardCase(builder); 147 builder.setInsertionPoint(operation.loopOp); 148 OpenACCClauseCIREmitter<mlir::acc::LoopOp> loopEmitter{ 149 operation.loopOp, cgf, builder, dirKind, dirLoc}; 150 loopEmitter.lastDeviceTypeValues = lastDeviceTypeValues; 151 loopEmitter.Visit(&c); 152 } 153 154 template <typename U = void, 155 typename = std::enable_if_t<isCombinedType<OpTy>, U>> 156 void applyToComputeOp(const OpenACCClause &c) { 157 mlir::OpBuilder::InsertionGuard guardCase(builder); 158 builder.setInsertionPoint(operation.computeOp); 159 OpenACCClauseCIREmitter<typename OpTy::ComputeOpTy> computeEmitter{ 160 operation.computeOp, cgf, builder, dirKind, dirLoc}; 161 162 computeEmitter.lastDeviceTypeValues = lastDeviceTypeValues; 163 164 // Async handler uses the first data operand to figure out where to insert 165 // its information if it is present. This ensures that the new handler will 166 // correctly set the insertion point for async. 167 if (!dataOperands.empty()) 168 computeEmitter.dataOperands.push_back(dataOperands.front()); 169 computeEmitter.Visit(&c); 170 171 // Make sure all of the new data operands are kept track of here. The 172 // combined constructs always apply 'async' to only the compute component, 173 // so we need to collect these. 174 dataOperands.append(computeEmitter.dataOperands); 175 } 176 177 mlir::acc::DataClauseModifier 178 convertModifiers(OpenACCModifierKind modifiers) { 179 using namespace mlir::acc; 180 static_assert(static_cast<int>(OpenACCModifierKind::Zero) == 181 static_cast<int>(DataClauseModifier::zero) && 182 static_cast<int>(OpenACCModifierKind::Readonly) == 183 static_cast<int>(DataClauseModifier::readonly) && 184 static_cast<int>(OpenACCModifierKind::AlwaysIn) == 185 static_cast<int>(DataClauseModifier::alwaysin) && 186 static_cast<int>(OpenACCModifierKind::AlwaysOut) == 187 static_cast<int>(DataClauseModifier::alwaysout) && 188 static_cast<int>(OpenACCModifierKind::Capture) == 189 static_cast<int>(DataClauseModifier::capture)); 190 191 DataClauseModifier mlirModifiers{}; 192 193 // The MLIR representation of this represents `always` as `alwaysin` + 194 // `alwaysout`. So do a small fixup here. 195 if (isOpenACCModifierBitSet(modifiers, OpenACCModifierKind::Always)) { 196 mlirModifiers = mlirModifiers | DataClauseModifier::always; 197 modifiers &= ~OpenACCModifierKind::Always; 198 } 199 200 mlirModifiers = mlirModifiers | static_cast<DataClauseModifier>(modifiers); 201 return mlirModifiers; 202 } 203 204 template <typename BeforeOpTy, typename AfterOpTy> 205 void addDataOperand(const Expr *varOperand, mlir::acc::DataClause dataClause, 206 OpenACCModifierKind modifiers, bool structured, 207 bool implicit) { 208 CIRGenFunction::OpenACCDataOperandInfo opInfo = 209 cgf.getOpenACCDataOperandInfo(varOperand); 210 211 auto beforeOp = 212 builder.create<BeforeOpTy>(opInfo.beginLoc, opInfo.varValue, structured, 213 implicit, opInfo.name, opInfo.bounds); 214 operation.getDataClauseOperandsMutable().append(beforeOp.getResult()); 215 216 AfterOpTy afterOp; 217 { 218 mlir::OpBuilder::InsertionGuard guardCase(builder); 219 builder.setInsertionPointAfter(operation); 220 221 if constexpr (std::is_same_v<AfterOpTy, mlir::acc::DeleteOp> || 222 std::is_same_v<AfterOpTy, mlir::acc::DetachOp>) { 223 // Detach/Delete ops don't have the variable reference here, so they 224 // take 1 fewer argument to their build function. 225 afterOp = builder.create<AfterOpTy>( 226 opInfo.beginLoc, beforeOp.getResult(), structured, implicit, 227 opInfo.name, opInfo.bounds); 228 } else { 229 afterOp = builder.create<AfterOpTy>( 230 opInfo.beginLoc, beforeOp.getResult(), opInfo.varValue, structured, 231 implicit, opInfo.name, opInfo.bounds); 232 } 233 } 234 235 // Set the 'rest' of the info for both operations. 236 beforeOp.setDataClause(dataClause); 237 afterOp.setDataClause(dataClause); 238 beforeOp.setModifiers(convertModifiers(modifiers)); 239 afterOp.setModifiers(convertModifiers(modifiers)); 240 241 // Make sure we record these, so 'async' values can be updated later. 242 dataOperands.push_back(beforeOp.getOperation()); 243 dataOperands.push_back(afterOp.getOperation()); 244 } 245 246 template <typename BeforeOpTy> 247 void addDataOperand(const Expr *varOperand, mlir::acc::DataClause dataClause, 248 OpenACCModifierKind modifiers, bool structured, 249 bool implicit) { 250 CIRGenFunction::OpenACCDataOperandInfo opInfo = 251 cgf.getOpenACCDataOperandInfo(varOperand); 252 auto beforeOp = 253 builder.create<BeforeOpTy>(opInfo.beginLoc, opInfo.varValue, structured, 254 implicit, opInfo.name, opInfo.bounds); 255 operation.getDataClauseOperandsMutable().append(beforeOp.getResult()); 256 257 // Set the 'rest' of the info for the operation. 258 beforeOp.setDataClause(dataClause); 259 beforeOp.setModifiers(convertModifiers(modifiers)); 260 261 // Make sure we record these, so 'async' values can be updated later. 262 dataOperands.push_back(beforeOp.getOperation()); 263 } 264 265 // Helper function that covers for the fact that we don't have this function 266 // on all operation types. 267 mlir::ArrayAttr getAsyncOnlyAttr() { 268 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 269 mlir::acc::KernelsOp, mlir::acc::DataOp, 270 mlir::acc::UpdateOp>) { 271 return operation.getAsyncOnlyAttr(); 272 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::EnterDataOp, 273 mlir::acc::ExitDataOp>) { 274 if (!operation.getAsyncAttr()) 275 return mlir::ArrayAttr{}; 276 277 llvm::SmallVector<mlir::Attribute> devTysTemp; 278 devTysTemp.push_back(mlir::acc::DeviceTypeAttr::get( 279 builder.getContext(), mlir::acc::DeviceType::None)); 280 return mlir::ArrayAttr::get(builder.getContext(), devTysTemp); 281 } else if constexpr (isCombinedType<OpTy>) { 282 return operation.computeOp.getAsyncOnlyAttr(); 283 } 284 285 // Note: 'wait' has async as well, but it cannot have data clauses, so we 286 // don't have to handle them here. 287 288 llvm_unreachable("getting asyncOnly when clause not valid on operation?"); 289 } 290 291 // Helper function that covers for the fact that we don't have this function 292 // on all operation types. 293 mlir::ArrayAttr getAsyncOperandsDeviceTypeAttr() { 294 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 295 mlir::acc::KernelsOp, mlir::acc::DataOp, 296 mlir::acc::UpdateOp>) { 297 return operation.getAsyncOperandsDeviceTypeAttr(); 298 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::EnterDataOp, 299 mlir::acc::ExitDataOp>) { 300 if (!operation.getAsyncOperand()) 301 return mlir::ArrayAttr{}; 302 303 llvm::SmallVector<mlir::Attribute> devTysTemp; 304 devTysTemp.push_back(mlir::acc::DeviceTypeAttr::get( 305 builder.getContext(), mlir::acc::DeviceType::None)); 306 return mlir::ArrayAttr::get(builder.getContext(), devTysTemp); 307 } else if constexpr (isCombinedType<OpTy>) { 308 return operation.computeOp.getAsyncOperandsDeviceTypeAttr(); 309 } 310 311 // Note: 'wait' has async as well, but it cannot have data clauses, so we 312 // don't have to handle them here. 313 314 llvm_unreachable( 315 "getting asyncOperandsDeviceType when clause not valid on operation?"); 316 } 317 318 // Helper function that covers for the fact that we don't have this function 319 // on all operation types. 320 mlir::OperandRange getAsyncOperands() { 321 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 322 mlir::acc::KernelsOp, mlir::acc::DataOp, 323 mlir::acc::UpdateOp>) 324 return operation.getAsyncOperands(); 325 else if constexpr (isOneOfTypes<OpTy, mlir::acc::EnterDataOp, 326 mlir::acc::ExitDataOp>) 327 return operation.getAsyncOperandMutable(); 328 else if constexpr (isCombinedType<OpTy>) 329 return operation.computeOp.getAsyncOperands(); 330 331 // Note: 'wait' has async as well, but it cannot have data clauses, so we 332 // don't have to handle them here. 333 334 llvm_unreachable( 335 "getting asyncOperandsDeviceType when clause not valid on operation?"); 336 } 337 338 // The 'data' clauses all require that we add the 'async' values from the 339 // operation to them. We've collected the data operands along the way, so use 340 // that list to get the current 'async' values. 341 void updateDataOperandAsyncValues() { 342 if (!hasAsyncClause || dataOperands.empty()) 343 return; 344 345 for (mlir::Operation *dataOp : dataOperands) { 346 llvm::TypeSwitch<mlir::Operation *, void>(dataOp) 347 .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS>([&](auto op) { 348 op.setAsyncOnlyAttr(getAsyncOnlyAttr()); 349 op.setAsyncOperandsDeviceTypeAttr(getAsyncOperandsDeviceTypeAttr()); 350 op.getAsyncOperandsMutable().assign(getAsyncOperands()); 351 }) 352 .Default([&](mlir::Operation *) { 353 llvm_unreachable("Not a data operation?"); 354 }); 355 } 356 } 357 358 public: 359 OpenACCClauseCIREmitter(OpTy &operation, CIRGen::CIRGenFunction &cgf, 360 CIRGen::CIRGenBuilderTy &builder, 361 OpenACCDirectiveKind dirKind, SourceLocation dirLoc) 362 : operation(operation), cgf(cgf), builder(builder), dirKind(dirKind), 363 dirLoc(dirLoc) {} 364 365 void VisitClause(const OpenACCClause &clause) { 366 clauseNotImplemented(clause); 367 } 368 369 // The entry point for the CIR emitter. All users should use this rather than 370 // 'visitClauseList', as this also handles the things that have to happen 371 // 'after' the clauses are all visited. 372 void emitClauses(ArrayRef<const OpenACCClause *> clauses) { 373 this->VisitClauseList(clauses); 374 updateDataOperandAsyncValues(); 375 } 376 377 void VisitDefaultClause(const OpenACCDefaultClause &clause) { 378 // This type-trait checks if 'op'(the first arg) is one of the mlir::acc 379 // operations listed in the rest of the arguments. 380 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 381 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 382 switch (clause.getDefaultClauseKind()) { 383 case OpenACCDefaultClauseKind::None: 384 operation.setDefaultAttr(mlir::acc::ClauseDefaultValue::None); 385 break; 386 case OpenACCDefaultClauseKind::Present: 387 operation.setDefaultAttr(mlir::acc::ClauseDefaultValue::Present); 388 break; 389 case OpenACCDefaultClauseKind::Invalid: 390 break; 391 } 392 } else if constexpr (isCombinedType<OpTy>) { 393 applyToComputeOp(clause); 394 } else { 395 llvm_unreachable("Unknown construct kind in VisitDefaultClause"); 396 } 397 } 398 399 void VisitDeviceTypeClause(const OpenACCDeviceTypeClause &clause) { 400 setLastDeviceTypeClause(clause); 401 402 if constexpr (isOneOfTypes<OpTy, mlir::acc::InitOp, 403 mlir::acc::ShutdownOp>) { 404 for (const DeviceTypeArgument &arg : clause.getArchitectures()) 405 operation.addDeviceType(builder.getContext(), 406 decodeDeviceType(arg.getIdentifierInfo())); 407 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::SetOp>) { 408 assert(!operation.getDeviceTypeAttr() && "already have device-type?"); 409 assert(clause.getArchitectures().size() <= 1); 410 411 if (!clause.getArchitectures().empty()) 412 operation.setDeviceType( 413 decodeDeviceType(clause.getArchitectures()[0].getIdentifierInfo())); 414 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, 415 mlir::acc::SerialOp, mlir::acc::KernelsOp, 416 mlir::acc::DataOp, mlir::acc::LoopOp, 417 mlir::acc::UpdateOp>) { 418 // Nothing to do here, these constructs don't have any IR for these, as 419 // they just modify the other clauses IR. So setting of 420 // `lastDeviceTypeValues` (done above) is all we need. 421 } else if constexpr (isCombinedType<OpTy>) { 422 // Nothing to do here either, combined constructs are just going to use 423 // 'lastDeviceTypeValues' to set the value for the child visitor. 424 } else { 425 // TODO: When we've implemented this for everything, switch this to an 426 // unreachable. routine construct remains. 427 return clauseNotImplemented(clause); 428 } 429 } 430 431 void VisitNumWorkersClause(const OpenACCNumWorkersClause &clause) { 432 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, 433 mlir::acc::KernelsOp>) { 434 operation.addNumWorkersOperand(builder.getContext(), 435 emitIntExpr(clause.getIntExpr()), 436 lastDeviceTypeValues); 437 } else if constexpr (isCombinedType<OpTy>) { 438 applyToComputeOp(clause); 439 } else { 440 llvm_unreachable("Unknown construct kind in VisitNumGangsClause"); 441 } 442 } 443 444 void VisitVectorLengthClause(const OpenACCVectorLengthClause &clause) { 445 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, 446 mlir::acc::KernelsOp>) { 447 operation.addVectorLengthOperand(builder.getContext(), 448 emitIntExpr(clause.getIntExpr()), 449 lastDeviceTypeValues); 450 } else if constexpr (isCombinedType<OpTy>) { 451 applyToComputeOp(clause); 452 } else { 453 llvm_unreachable("Unknown construct kind in VisitVectorLengthClause"); 454 } 455 } 456 457 void VisitAsyncClause(const OpenACCAsyncClause &clause) { 458 hasAsyncClause = true; 459 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 460 mlir::acc::KernelsOp, mlir::acc::DataOp, 461 mlir::acc::EnterDataOp, mlir::acc::ExitDataOp, 462 mlir::acc::UpdateOp>) { 463 if (!clause.hasIntExpr()) { 464 operation.addAsyncOnly(builder.getContext(), lastDeviceTypeValues); 465 } else { 466 467 mlir::Value intExpr; 468 { 469 // Async int exprs can be referenced by the data operands, which means 470 // that the int-exprs have to appear before them. IF there is a data 471 // operand already, set the insertion point to 'before' it. 472 mlir::OpBuilder::InsertionGuard guardCase(builder); 473 if (!dataOperands.empty()) 474 builder.setInsertionPoint(dataOperands.front()); 475 intExpr = emitIntExpr(clause.getIntExpr()); 476 } 477 operation.addAsyncOperand(builder.getContext(), intExpr, 478 lastDeviceTypeValues); 479 } 480 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::WaitOp>) { 481 // Wait doesn't have a device_type, so its handling here is slightly 482 // different. 483 if (!clause.hasIntExpr()) 484 operation.setAsync(true); 485 else 486 operation.getAsyncOperandMutable().append( 487 emitIntExpr(clause.getIntExpr())); 488 } else if constexpr (isCombinedType<OpTy>) { 489 applyToComputeOp(clause); 490 } else { 491 // TODO: When we've implemented this for everything, switch this to an 492 // unreachable. Combined constructs remain. update construct remains. 493 return clauseNotImplemented(clause); 494 } 495 } 496 497 void VisitSelfClause(const OpenACCSelfClause &clause) { 498 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 499 mlir::acc::KernelsOp>) { 500 if (clause.isEmptySelfClause()) { 501 operation.setSelfAttr(true); 502 } else if (clause.isConditionExprClause()) { 503 assert(clause.hasConditionExpr()); 504 operation.getSelfCondMutable().append( 505 createCondition(clause.getConditionExpr())); 506 } else { 507 llvm_unreachable("var-list version of self shouldn't get here"); 508 } 509 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::UpdateOp>) { 510 assert(!clause.isEmptySelfClause() && !clause.isConditionExprClause() && 511 "var-list version of self required for update"); 512 for (const Expr *var : clause.getVarList()) 513 addDataOperand<mlir::acc::GetDevicePtrOp, mlir::acc::UpdateHostOp>( 514 var, mlir::acc::DataClause::acc_update_self, {}, 515 /*structured=*/false, /*implicit=*/false); 516 } else if constexpr (isCombinedType<OpTy>) { 517 applyToComputeOp(clause); 518 } else { 519 llvm_unreachable("Unknown construct kind in VisitSelfClause"); 520 } 521 } 522 523 void VisitHostClause(const OpenACCHostClause &clause) { 524 if constexpr (isOneOfTypes<OpTy, mlir::acc::UpdateOp>) { 525 for (const Expr *var : clause.getVarList()) 526 addDataOperand<mlir::acc::GetDevicePtrOp, mlir::acc::UpdateHostOp>( 527 var, mlir::acc::DataClause::acc_update_host, {}, 528 /*structured=*/false, /*implicit=*/false); 529 } else { 530 llvm_unreachable("Unknown construct kind in VisitHostClause"); 531 } 532 } 533 534 void VisitDeviceClause(const OpenACCDeviceClause &clause) { 535 if constexpr (isOneOfTypes<OpTy, mlir::acc::UpdateOp>) { 536 for (const Expr *var : clause.getVarList()) 537 addDataOperand<mlir::acc::UpdateDeviceOp>( 538 var, mlir::acc::DataClause::acc_update_device, {}, 539 /*structured=*/false, /*implicit=*/false); 540 } else { 541 llvm_unreachable("Unknown construct kind in VisitDeviceClause"); 542 } 543 } 544 545 void VisitIfClause(const OpenACCIfClause &clause) { 546 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 547 mlir::acc::KernelsOp, mlir::acc::InitOp, 548 mlir::acc::ShutdownOp, mlir::acc::SetOp, 549 mlir::acc::DataOp, mlir::acc::WaitOp, 550 mlir::acc::HostDataOp, mlir::acc::EnterDataOp, 551 mlir::acc::ExitDataOp, mlir::acc::UpdateOp>) { 552 operation.getIfCondMutable().append( 553 createCondition(clause.getConditionExpr())); 554 } else if constexpr (isCombinedType<OpTy>) { 555 applyToComputeOp(clause); 556 } else { 557 llvm_unreachable("Unknown construct kind in VisitIfClause"); 558 } 559 } 560 561 void VisitIfPresentClause(const OpenACCIfPresentClause &clause) { 562 if constexpr (isOneOfTypes<OpTy, mlir::acc::HostDataOp, 563 mlir::acc::UpdateOp>) { 564 operation.setIfPresent(true); 565 } else { 566 llvm_unreachable("unknown construct kind in VisitIfPresentClause"); 567 } 568 } 569 570 void VisitDeviceNumClause(const OpenACCDeviceNumClause &clause) { 571 if constexpr (isOneOfTypes<OpTy, mlir::acc::InitOp, mlir::acc::ShutdownOp, 572 mlir::acc::SetOp>) { 573 operation.getDeviceNumMutable().append(emitIntExpr(clause.getIntExpr())); 574 } else { 575 llvm_unreachable( 576 "init, shutdown, set, are only valid device_num constructs"); 577 } 578 } 579 580 void VisitNumGangsClause(const OpenACCNumGangsClause &clause) { 581 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, 582 mlir::acc::KernelsOp>) { 583 llvm::SmallVector<mlir::Value> values; 584 for (const Expr *E : clause.getIntExprs()) 585 values.push_back(emitIntExpr(E)); 586 587 operation.addNumGangsOperands(builder.getContext(), values, 588 lastDeviceTypeValues); 589 } else if constexpr (isCombinedType<OpTy>) { 590 applyToComputeOp(clause); 591 } else { 592 llvm_unreachable("Unknown construct kind in VisitNumGangsClause"); 593 } 594 } 595 596 void VisitWaitClause(const OpenACCWaitClause &clause) { 597 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 598 mlir::acc::KernelsOp, mlir::acc::DataOp, 599 mlir::acc::EnterDataOp, mlir::acc::ExitDataOp, 600 mlir::acc::UpdateOp>) { 601 if (!clause.hasExprs()) { 602 operation.addWaitOnly(builder.getContext(), lastDeviceTypeValues); 603 } else { 604 llvm::SmallVector<mlir::Value> values; 605 if (clause.hasDevNumExpr()) 606 values.push_back(emitIntExpr(clause.getDevNumExpr())); 607 for (const Expr *E : clause.getQueueIdExprs()) 608 values.push_back(emitIntExpr(E)); 609 operation.addWaitOperands(builder.getContext(), clause.hasDevNumExpr(), 610 values, lastDeviceTypeValues); 611 } 612 } else if constexpr (isCombinedType<OpTy>) { 613 applyToComputeOp(clause); 614 } else { 615 // TODO: When we've implemented this for everything, switch this to an 616 // unreachable. update construct remains. 617 return clauseNotImplemented(clause); 618 } 619 } 620 621 void VisitDefaultAsyncClause(const OpenACCDefaultAsyncClause &clause) { 622 if constexpr (isOneOfTypes<OpTy, mlir::acc::SetOp>) { 623 operation.getDefaultAsyncMutable().append( 624 emitIntExpr(clause.getIntExpr())); 625 } else { 626 llvm_unreachable("set, is only valid device_num constructs"); 627 } 628 } 629 630 void VisitSeqClause(const OpenACCSeqClause &clause) { 631 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 632 operation.addSeq(builder.getContext(), lastDeviceTypeValues); 633 } else if constexpr (isCombinedType<OpTy>) { 634 applyToLoopOp(clause); 635 } else { 636 // TODO: When we've implemented this for everything, switch this to an 637 // unreachable. Routine construct remains. 638 return clauseNotImplemented(clause); 639 } 640 } 641 642 void VisitAutoClause(const OpenACCAutoClause &clause) { 643 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 644 operation.addAuto(builder.getContext(), lastDeviceTypeValues); 645 } else if constexpr (isCombinedType<OpTy>) { 646 applyToLoopOp(clause); 647 } else { 648 // TODO: When we've implemented this for everything, switch this to an 649 // unreachable. Routine, construct remains. 650 return clauseNotImplemented(clause); 651 } 652 } 653 654 void VisitIndependentClause(const OpenACCIndependentClause &clause) { 655 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 656 operation.addIndependent(builder.getContext(), lastDeviceTypeValues); 657 } else if constexpr (isCombinedType<OpTy>) { 658 applyToLoopOp(clause); 659 } else { 660 // TODO: When we've implemented this for everything, switch this to an 661 // unreachable. Routine construct remains. 662 return clauseNotImplemented(clause); 663 } 664 } 665 666 void VisitCollapseClause(const OpenACCCollapseClause &clause) { 667 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 668 llvm::APInt value = 669 clause.getIntExpr()->EvaluateKnownConstInt(cgf.cgm.getASTContext()); 670 671 value = value.sextOrTrunc(64); 672 operation.setCollapseForDeviceTypes(builder.getContext(), 673 lastDeviceTypeValues, value); 674 } else if constexpr (isCombinedType<OpTy>) { 675 applyToLoopOp(clause); 676 } else { 677 llvm_unreachable("Unknown construct kind in VisitCollapseClause"); 678 } 679 } 680 681 void VisitTileClause(const OpenACCTileClause &clause) { 682 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 683 llvm::SmallVector<mlir::Value> values; 684 685 for (const Expr *e : clause.getSizeExprs()) { 686 mlir::Location exprLoc = cgf.cgm.getLoc(e->getBeginLoc()); 687 688 // We represent the * as -1. Additionally, this is a constant, so we 689 // can always just emit it as 64 bits to avoid having to do any more 690 // work to determine signedness or size. 691 if (isa<OpenACCAsteriskSizeExpr>(e)) { 692 values.push_back(createConstantInt(exprLoc, 64, -1)); 693 } else { 694 llvm::APInt curValue = 695 e->EvaluateKnownConstInt(cgf.cgm.getASTContext()); 696 values.push_back(createConstantInt( 697 exprLoc, 64, curValue.sextOrTrunc(64).getSExtValue())); 698 } 699 } 700 701 operation.setTileForDeviceTypes(builder.getContext(), 702 lastDeviceTypeValues, values); 703 } else if constexpr (isCombinedType<OpTy>) { 704 applyToLoopOp(clause); 705 } else { 706 llvm_unreachable("Unknown construct kind in VisitTileClause"); 707 } 708 } 709 710 void VisitWorkerClause(const OpenACCWorkerClause &clause) { 711 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 712 if (clause.hasIntExpr()) 713 operation.addWorkerNumOperand(builder.getContext(), 714 emitIntExpr(clause.getIntExpr()), 715 lastDeviceTypeValues); 716 else 717 operation.addEmptyWorker(builder.getContext(), lastDeviceTypeValues); 718 719 } else if constexpr (isCombinedType<OpTy>) { 720 applyToLoopOp(clause); 721 } else { 722 // TODO: When we've implemented this for everything, switch this to an 723 // unreachable. Combined constructs remain. 724 return clauseNotImplemented(clause); 725 } 726 } 727 728 void VisitVectorClause(const OpenACCVectorClause &clause) { 729 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 730 if (clause.hasIntExpr()) 731 operation.addVectorOperand(builder.getContext(), 732 emitIntExpr(clause.getIntExpr()), 733 lastDeviceTypeValues); 734 else 735 operation.addEmptyVector(builder.getContext(), lastDeviceTypeValues); 736 737 } else if constexpr (isCombinedType<OpTy>) { 738 applyToLoopOp(clause); 739 } else { 740 // TODO: When we've implemented this for everything, switch this to an 741 // unreachable. Combined constructs remain. 742 return clauseNotImplemented(clause); 743 } 744 } 745 746 void VisitGangClause(const OpenACCGangClause &clause) { 747 if constexpr (isOneOfTypes<OpTy, mlir::acc::LoopOp>) { 748 if (clause.getNumExprs() == 0) { 749 operation.addEmptyGang(builder.getContext(), lastDeviceTypeValues); 750 } else { 751 llvm::SmallVector<mlir::Value> values; 752 llvm::SmallVector<mlir::acc::GangArgType> argTypes; 753 for (unsigned i : llvm::index_range(0u, clause.getNumExprs())) { 754 auto [kind, expr] = clause.getExpr(i); 755 mlir::Location exprLoc = cgf.cgm.getLoc(expr->getBeginLoc()); 756 argTypes.push_back(decodeGangType(kind)); 757 if (kind == OpenACCGangKind::Dim) { 758 llvm::APInt curValue = 759 expr->EvaluateKnownConstInt(cgf.cgm.getASTContext()); 760 // The value is 1, 2, or 3, but the type isn't necessarily smaller 761 // than 64. 762 curValue = curValue.sextOrTrunc(64); 763 values.push_back( 764 createConstantInt(exprLoc, 64, curValue.getSExtValue())); 765 } else if (isa<OpenACCAsteriskSizeExpr>(expr)) { 766 values.push_back(createConstantInt(exprLoc, 64, -1)); 767 } else { 768 values.push_back(emitIntExpr(expr)); 769 } 770 } 771 772 operation.addGangOperands(builder.getContext(), lastDeviceTypeValues, 773 argTypes, values); 774 } 775 } else if constexpr (isCombinedType<OpTy>) { 776 applyToLoopOp(clause); 777 } else { 778 llvm_unreachable("Unknown construct kind in VisitGangClause"); 779 } 780 } 781 782 void VisitCopyClause(const OpenACCCopyClause &clause) { 783 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 784 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 785 for (const Expr *var : clause.getVarList()) 786 addDataOperand<mlir::acc::CopyinOp, mlir::acc::CopyoutOp>( 787 var, mlir::acc::DataClause::acc_copy, clause.getModifierList(), 788 /*structured=*/true, 789 /*implicit=*/false); 790 } else if constexpr (isCombinedType<OpTy>) { 791 applyToComputeOp(clause); 792 } else { 793 // TODO: When we've implemented this for everything, switch this to an 794 // unreachable. declare construct remains. 795 return clauseNotImplemented(clause); 796 } 797 } 798 799 void VisitCopyInClause(const OpenACCCopyInClause &clause) { 800 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 801 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 802 for (const Expr *var : clause.getVarList()) 803 addDataOperand<mlir::acc::CopyinOp, mlir::acc::DeleteOp>( 804 var, mlir::acc::DataClause::acc_copyin, clause.getModifierList(), 805 /*structured=*/true, 806 /*implicit=*/false); 807 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::EnterDataOp>) { 808 for (const Expr *var : clause.getVarList()) 809 addDataOperand<mlir::acc::CopyinOp>( 810 var, mlir::acc::DataClause::acc_copyin, clause.getModifierList(), 811 /*structured=*/false, /*implicit=*/false); 812 } else if constexpr (isCombinedType<OpTy>) { 813 applyToComputeOp(clause); 814 } else { 815 // TODO: When we've implemented this for everything, switch this to an 816 // unreachable. declare construct remains. 817 return clauseNotImplemented(clause); 818 } 819 } 820 821 void VisitCopyOutClause(const OpenACCCopyOutClause &clause) { 822 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 823 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 824 for (const Expr *var : clause.getVarList()) 825 addDataOperand<mlir::acc::CreateOp, mlir::acc::CopyoutOp>( 826 var, mlir::acc::DataClause::acc_copyout, clause.getModifierList(), 827 /*structured=*/true, 828 /*implicit=*/false); 829 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::ExitDataOp>) { 830 for (const Expr *var : clause.getVarList()) 831 addDataOperand<mlir::acc::GetDevicePtrOp, mlir::acc::CopyoutOp>( 832 var, mlir::acc::DataClause::acc_copyout, clause.getModifierList(), 833 /*structured=*/false, 834 /*implicit=*/false); 835 } else if constexpr (isCombinedType<OpTy>) { 836 applyToComputeOp(clause); 837 } else { 838 // TODO: When we've implemented this for everything, switch this to an 839 // unreachable. declare construct remains. 840 return clauseNotImplemented(clause); 841 } 842 } 843 844 void VisitCreateClause(const OpenACCCreateClause &clause) { 845 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 846 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 847 for (const Expr *var : clause.getVarList()) 848 addDataOperand<mlir::acc::CreateOp, mlir::acc::DeleteOp>( 849 var, mlir::acc::DataClause::acc_create, clause.getModifierList(), 850 /*structured=*/true, 851 /*implicit=*/false); 852 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::EnterDataOp>) { 853 for (const Expr *var : clause.getVarList()) 854 addDataOperand<mlir::acc::CreateOp>( 855 var, mlir::acc::DataClause::acc_create, clause.getModifierList(), 856 /*structured=*/false, /*implicit=*/false); 857 } else if constexpr (isCombinedType<OpTy>) { 858 applyToComputeOp(clause); 859 } else { 860 // TODO: When we've implemented this for everything, switch this to an 861 // unreachable. declare construct remains. 862 return clauseNotImplemented(clause); 863 } 864 } 865 866 void VisitDeleteClause(const OpenACCDeleteClause &clause) { 867 if constexpr (isOneOfTypes<OpTy, mlir::acc::ExitDataOp>) { 868 for (const Expr *var : clause.getVarList()) 869 addDataOperand<mlir::acc::GetDevicePtrOp, mlir::acc::DeleteOp>( 870 var, mlir::acc::DataClause::acc_delete, {}, 871 /*structured=*/false, 872 /*implicit=*/false); 873 } else { 874 llvm_unreachable("Unknown construct kind in VisitDeleteClause"); 875 } 876 } 877 878 void VisitDetachClause(const OpenACCDetachClause &clause) { 879 if constexpr (isOneOfTypes<OpTy, mlir::acc::ExitDataOp>) { 880 for (const Expr *var : clause.getVarList()) 881 addDataOperand<mlir::acc::GetDevicePtrOp, mlir::acc::DetachOp>( 882 var, mlir::acc::DataClause::acc_detach, {}, 883 /*structured=*/false, 884 /*implicit=*/false); 885 } else { 886 llvm_unreachable("Unknown construct kind in VisitDetachClause"); 887 } 888 } 889 890 void VisitFinalizeClause(const OpenACCFinalizeClause &clause) { 891 if constexpr (isOneOfTypes<OpTy, mlir::acc::ExitDataOp>) { 892 operation.setFinalize(true); 893 } else { 894 llvm_unreachable("Unknown construct kind in VisitFinalizeClause"); 895 } 896 } 897 898 void VisitUseDeviceClause(const OpenACCUseDeviceClause &clause) { 899 if constexpr (isOneOfTypes<OpTy, mlir::acc::HostDataOp>) { 900 for (const Expr *var : clause.getVarList()) 901 addDataOperand<mlir::acc::UseDeviceOp>( 902 var, mlir::acc::DataClause::acc_use_device, {}, /*structured=*/true, 903 /*implicit=*/false); 904 } else { 905 llvm_unreachable("Unknown construct kind in VisitUseDeviceClause"); 906 } 907 } 908 909 void VisitDevicePtrClause(const OpenACCDevicePtrClause &clause) { 910 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 911 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 912 for (const Expr *var : clause.getVarList()) 913 addDataOperand<mlir::acc::DevicePtrOp>( 914 var, mlir::acc::DataClause::acc_deviceptr, {}, 915 /*structured=*/true, 916 /*implicit=*/false); 917 } else if constexpr (isCombinedType<OpTy>) { 918 applyToComputeOp(clause); 919 } else { 920 // TODO: When we've implemented this for everything, switch this to an 921 // unreachable. declare remains. 922 return clauseNotImplemented(clause); 923 } 924 } 925 926 void VisitNoCreateClause(const OpenACCNoCreateClause &clause) { 927 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 928 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 929 for (const Expr *var : clause.getVarList()) 930 addDataOperand<mlir::acc::NoCreateOp, mlir::acc::DeleteOp>( 931 var, mlir::acc::DataClause::acc_no_create, {}, /*structured=*/true, 932 /*implicit=*/false); 933 } else if constexpr (isCombinedType<OpTy>) { 934 applyToComputeOp(clause); 935 } else { 936 llvm_unreachable("Unknown construct kind in VisitNoCreateClause"); 937 } 938 } 939 940 void VisitPresentClause(const OpenACCPresentClause &clause) { 941 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 942 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 943 for (const Expr *var : clause.getVarList()) 944 addDataOperand<mlir::acc::PresentOp, mlir::acc::DeleteOp>( 945 var, mlir::acc::DataClause::acc_present, {}, /*structured=*/true, 946 /*implicit=*/false); 947 } else if constexpr (isCombinedType<OpTy>) { 948 applyToComputeOp(clause); 949 } else { 950 // TODO: When we've implemented this for everything, switch this to an 951 // unreachable. declare remains. 952 return clauseNotImplemented(clause); 953 } 954 } 955 956 void VisitAttachClause(const OpenACCAttachClause &clause) { 957 if constexpr (isOneOfTypes<OpTy, mlir::acc::ParallelOp, mlir::acc::SerialOp, 958 mlir::acc::KernelsOp, mlir::acc::DataOp>) { 959 for (const Expr *var : clause.getVarList()) 960 addDataOperand<mlir::acc::AttachOp, mlir::acc::DetachOp>( 961 var, mlir::acc::DataClause::acc_attach, {}, /*structured=*/true, 962 /*implicit=*/false); 963 } else if constexpr (isOneOfTypes<OpTy, mlir::acc::EnterDataOp>) { 964 for (const Expr *var : clause.getVarList()) 965 addDataOperand<mlir::acc::AttachOp>( 966 var, mlir::acc::DataClause::acc_attach, {}, 967 /*structured=*/false, /*implicit=*/false); 968 } else if constexpr (isCombinedType<OpTy>) { 969 applyToComputeOp(clause); 970 } else { 971 llvm_unreachable("Unknown construct kind in VisitAttachClause"); 972 } 973 } 974 }; 975 976 template <typename OpTy> 977 auto makeClauseEmitter(OpTy &op, CIRGen::CIRGenFunction &cgf, 978 CIRGen::CIRGenBuilderTy &builder, 979 OpenACCDirectiveKind dirKind, SourceLocation dirLoc) { 980 return OpenACCClauseCIREmitter<OpTy>(op, cgf, builder, dirKind, dirLoc); 981 } 982 } // namespace 983 984 template <typename Op> 985 void CIRGenFunction::emitOpenACCClauses( 986 Op &op, OpenACCDirectiveKind dirKind, SourceLocation dirLoc, 987 ArrayRef<const OpenACCClause *> clauses) { 988 mlir::OpBuilder::InsertionGuard guardCase(builder); 989 990 // Sets insertion point before the 'op', since every new expression needs to 991 // be before the operation. 992 builder.setInsertionPoint(op); 993 makeClauseEmitter(op, *this, builder, dirKind, dirLoc).emitClauses(clauses); 994 } 995 996 #define EXPL_SPEC(N) \ 997 template void CIRGenFunction::emitOpenACCClauses<N>( \ 998 N &, OpenACCDirectiveKind, SourceLocation, \ 999 ArrayRef<const OpenACCClause *>); 1000 EXPL_SPEC(mlir::acc::ParallelOp) 1001 EXPL_SPEC(mlir::acc::SerialOp) 1002 EXPL_SPEC(mlir::acc::KernelsOp) 1003 EXPL_SPEC(mlir::acc::LoopOp) 1004 EXPL_SPEC(mlir::acc::DataOp) 1005 EXPL_SPEC(mlir::acc::InitOp) 1006 EXPL_SPEC(mlir::acc::ShutdownOp) 1007 EXPL_SPEC(mlir::acc::SetOp) 1008 EXPL_SPEC(mlir::acc::WaitOp) 1009 EXPL_SPEC(mlir::acc::HostDataOp) 1010 EXPL_SPEC(mlir::acc::EnterDataOp) 1011 EXPL_SPEC(mlir::acc::ExitDataOp) 1012 EXPL_SPEC(mlir::acc::UpdateOp) 1013 #undef EXPL_SPEC 1014 1015 template <typename ComputeOp, typename LoopOp> 1016 void CIRGenFunction::emitOpenACCClauses( 1017 ComputeOp &op, LoopOp &loopOp, OpenACCDirectiveKind dirKind, 1018 SourceLocation dirLoc, ArrayRef<const OpenACCClause *> clauses) { 1019 static_assert(std::is_same_v<mlir::acc::LoopOp, LoopOp>); 1020 1021 CombinedConstructClauseInfo<ComputeOp> inf{op, loopOp}; 1022 // We cannot set the insertion point here and do so in the emitter, but make 1023 // sure we reset it with the 'guard' anyway. 1024 mlir::OpBuilder::InsertionGuard guardCase(builder); 1025 makeClauseEmitter(inf, *this, builder, dirKind, dirLoc).emitClauses(clauses); 1026 } 1027 1028 #define EXPL_SPEC(N) \ 1029 template void CIRGenFunction::emitOpenACCClauses<N, mlir::acc::LoopOp>( \ 1030 N &, mlir::acc::LoopOp &, OpenACCDirectiveKind, SourceLocation, \ 1031 ArrayRef<const OpenACCClause *>); 1032 1033 EXPL_SPEC(mlir::acc::ParallelOp) 1034 EXPL_SPEC(mlir::acc::SerialOp) 1035 EXPL_SPEC(mlir::acc::KernelsOp) 1036 #undef EXPL_SPEC 1037