1 //===-- OpenMP.cpp -- Open MP directive lowering --------------------------===// 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 // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "flang/Lower/OpenMP.h" 14 #include "flang/Common/idioms.h" 15 #include "flang/Lower/Bridge.h" 16 #include "flang/Lower/PFTBuilder.h" 17 #include "flang/Lower/StatementContext.h" 18 #include "flang/Lower/Todo.h" 19 #include "flang/Optimizer/Builder/BoxValue.h" 20 #include "flang/Optimizer/Builder/FIRBuilder.h" 21 #include "flang/Parser/parse-tree.h" 22 #include "flang/Semantics/tools.h" 23 #include "mlir/Dialect/OpenMP/OpenMPDialect.h" 24 #include "llvm/Frontend/OpenMP/OMPConstants.h" 25 26 using namespace mlir; 27 28 int64_t Fortran::lower::getCollapseValue( 29 const Fortran::parser::OmpClauseList &clauseList) { 30 for (const auto &clause : clauseList.v) { 31 if (const auto &collapseClause = 32 std::get_if<Fortran::parser::OmpClause::Collapse>(&clause.u)) { 33 const auto *expr = Fortran::semantics::GetExpr(collapseClause->v); 34 return Fortran::evaluate::ToInt64(*expr).value(); 35 } 36 } 37 return 1; 38 } 39 40 static const Fortran::parser::Name * 41 getDesignatorNameIfDataRef(const Fortran::parser::Designator &designator) { 42 const auto *dataRef = std::get_if<Fortran::parser::DataRef>(&designator.u); 43 return dataRef ? std::get_if<Fortran::parser::Name>(&dataRef->u) : nullptr; 44 } 45 46 template <typename T> 47 static void createPrivateVarSyms(Fortran::lower::AbstractConverter &converter, 48 const T *clause) { 49 Fortran::semantics::Symbol *sym = nullptr; 50 const Fortran::parser::OmpObjectList &ompObjectList = clause->v; 51 for (const Fortran::parser::OmpObject &ompObject : ompObjectList.v) { 52 std::visit( 53 Fortran::common::visitors{ 54 [&](const Fortran::parser::Designator &designator) { 55 if (const Fortran::parser::Name *name = 56 getDesignatorNameIfDataRef(designator)) { 57 sym = name->symbol; 58 } 59 }, 60 [&](const Fortran::parser::Name &name) { sym = name.symbol; }}, 61 ompObject.u); 62 63 // Privatization for symbols which are pre-determined (like loop index 64 // variables) happen separately, for everything else privatize here 65 if constexpr (std::is_same_v<T, Fortran::parser::OmpClause::Firstprivate>) { 66 converter.copyHostAssociateVar(*sym); 67 } else { 68 bool success = converter.createHostAssociateVarClone(*sym); 69 (void)success; 70 assert(success && "Privatization failed due to existing binding"); 71 } 72 } 73 } 74 75 static void privatizeVars(Fortran::lower::AbstractConverter &converter, 76 const Fortran::parser::OmpClauseList &opClauseList) { 77 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); 78 auto insPt = firOpBuilder.saveInsertionPoint(); 79 firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock()); 80 for (const Fortran::parser::OmpClause &clause : opClauseList.v) { 81 if (const auto &privateClause = 82 std::get_if<Fortran::parser::OmpClause::Private>(&clause.u)) { 83 createPrivateVarSyms(converter, privateClause); 84 } else if (const auto &firstPrivateClause = 85 std::get_if<Fortran::parser::OmpClause::Firstprivate>( 86 &clause.u)) { 87 createPrivateVarSyms(converter, firstPrivateClause); 88 } 89 } 90 firOpBuilder.restoreInsertionPoint(insPt); 91 } 92 93 static void genObjectList(const Fortran::parser::OmpObjectList &objectList, 94 Fortran::lower::AbstractConverter &converter, 95 llvm::SmallVectorImpl<Value> &operands) { 96 auto addOperands = [&](Fortran::lower::SymbolRef sym) { 97 const mlir::Value variable = converter.getSymbolAddress(sym); 98 if (variable) { 99 operands.push_back(variable); 100 } else { 101 if (const auto *details = 102 sym->detailsIf<Fortran::semantics::HostAssocDetails>()) { 103 operands.push_back(converter.getSymbolAddress(details->symbol())); 104 converter.copySymbolBinding(details->symbol(), sym); 105 } 106 } 107 }; 108 for (const Fortran::parser::OmpObject &ompObject : objectList.v) { 109 std::visit(Fortran::common::visitors{ 110 [&](const Fortran::parser::Designator &designator) { 111 if (const Fortran::parser::Name *name = 112 getDesignatorNameIfDataRef(designator)) { 113 addOperands(*name->symbol); 114 } 115 }, 116 [&](const Fortran::parser::Name &name) { 117 addOperands(*name.symbol); 118 }}, 119 ompObject.u); 120 } 121 } 122 123 static mlir::Type getLoopVarType(Fortran::lower::AbstractConverter &converter, 124 std::size_t loopVarTypeSize) { 125 // OpenMP runtime requires 32-bit or 64-bit loop variables. 126 loopVarTypeSize = loopVarTypeSize * 8; 127 if (loopVarTypeSize < 32) { 128 loopVarTypeSize = 32; 129 } else if (loopVarTypeSize > 64) { 130 loopVarTypeSize = 64; 131 mlir::emitWarning(converter.getCurrentLocation(), 132 "OpenMP loop iteration variable cannot have more than 64 " 133 "bits size and will be narrowed into 64 bits."); 134 } 135 assert((loopVarTypeSize == 32 || loopVarTypeSize == 64) && 136 "OpenMP loop iteration variable size must be transformed into 32-bit " 137 "or 64-bit"); 138 return converter.getFirOpBuilder().getIntegerType(loopVarTypeSize); 139 } 140 141 /// Create the body (block) for an OpenMP Operation. 142 /// 143 /// \param [in] op - the operation the body belongs to. 144 /// \param [inout] converter - converter to use for the clauses. 145 /// \param [in] loc - location in source code. 146 /// \oaran [in] clauses - list of clauses to process. 147 /// \param [in] args - block arguments (induction variable[s]) for the 148 //// region. 149 /// \param [in] outerCombined - is this an outer operation - prevents 150 /// privatization. 151 template <typename Op> 152 static void 153 createBodyOfOp(Op &op, Fortran::lower::AbstractConverter &converter, 154 mlir::Location &loc, 155 const Fortran::parser::OmpClauseList *clauses = nullptr, 156 const SmallVector<const Fortran::semantics::Symbol *> &args = {}, 157 bool outerCombined = false) { 158 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); 159 // If arguments for the region are provided then create the block with those 160 // arguments. Also update the symbol's address with the mlir argument values. 161 // e.g. For loops the arguments are the induction variable. And all further 162 // uses of the induction variable should use this mlir value. 163 if (args.size()) { 164 std::size_t loopVarTypeSize = 0; 165 for (const Fortran::semantics::Symbol *arg : args) 166 loopVarTypeSize = std::max(loopVarTypeSize, arg->GetUltimate().size()); 167 mlir::Type loopVarType = getLoopVarType(converter, loopVarTypeSize); 168 SmallVector<Type> tiv; 169 SmallVector<Location> locs; 170 for (int i = 0; i < (int)args.size(); i++) { 171 tiv.push_back(loopVarType); 172 locs.push_back(loc); 173 } 174 firOpBuilder.createBlock(&op.getRegion(), {}, tiv, locs); 175 int argIndex = 0; 176 for (const Fortran::semantics::Symbol *arg : args) { 177 fir::ExtendedValue exval = op.getRegion().front().getArgument(argIndex); 178 converter.bindSymbol(*arg, exval); 179 argIndex++; 180 } 181 } else { 182 firOpBuilder.createBlock(&op.getRegion()); 183 } 184 auto &block = op.getRegion().back(); 185 firOpBuilder.setInsertionPointToStart(&block); 186 187 // Insert the terminator. 188 if constexpr (std::is_same_v<Op, omp::WsLoopOp>) { 189 mlir::ValueRange results; 190 firOpBuilder.create<mlir::omp::YieldOp>(loc, results); 191 } else { 192 firOpBuilder.create<mlir::omp::TerminatorOp>(loc); 193 } 194 195 // Reset the insertion point to the start of the first block. 196 firOpBuilder.setInsertionPointToStart(&block); 197 // Handle privatization. Do not privatize if this is the outer operation. 198 if (clauses && !outerCombined) 199 privatizeVars(converter, *clauses); 200 } 201 202 static void genOMP(Fortran::lower::AbstractConverter &converter, 203 Fortran::lower::pft::Evaluation &eval, 204 const Fortran::parser::OpenMPSimpleStandaloneConstruct 205 &simpleStandaloneConstruct) { 206 const auto &directive = 207 std::get<Fortran::parser::OmpSimpleStandaloneDirective>( 208 simpleStandaloneConstruct.t); 209 switch (directive.v) { 210 default: 211 break; 212 case llvm::omp::Directive::OMPD_barrier: 213 converter.getFirOpBuilder().create<mlir::omp::BarrierOp>( 214 converter.getCurrentLocation()); 215 break; 216 case llvm::omp::Directive::OMPD_taskwait: 217 converter.getFirOpBuilder().create<mlir::omp::TaskwaitOp>( 218 converter.getCurrentLocation()); 219 break; 220 case llvm::omp::Directive::OMPD_taskyield: 221 converter.getFirOpBuilder().create<mlir::omp::TaskyieldOp>( 222 converter.getCurrentLocation()); 223 break; 224 case llvm::omp::Directive::OMPD_target_enter_data: 225 TODO(converter.getCurrentLocation(), "OMPD_target_enter_data"); 226 case llvm::omp::Directive::OMPD_target_exit_data: 227 TODO(converter.getCurrentLocation(), "OMPD_target_exit_data"); 228 case llvm::omp::Directive::OMPD_target_update: 229 TODO(converter.getCurrentLocation(), "OMPD_target_update"); 230 case llvm::omp::Directive::OMPD_ordered: 231 TODO(converter.getCurrentLocation(), "OMPD_ordered"); 232 } 233 } 234 235 static void 236 genAllocateClause(Fortran::lower::AbstractConverter &converter, 237 const Fortran::parser::OmpAllocateClause &ompAllocateClause, 238 SmallVector<Value> &allocatorOperands, 239 SmallVector<Value> &allocateOperands) { 240 auto &firOpBuilder = converter.getFirOpBuilder(); 241 auto currentLocation = converter.getCurrentLocation(); 242 Fortran::lower::StatementContext stmtCtx; 243 244 mlir::Value allocatorOperand; 245 const Fortran::parser::OmpObjectList &ompObjectList = 246 std::get<Fortran::parser::OmpObjectList>(ompAllocateClause.t); 247 const auto &allocatorValue = 248 std::get<std::optional<Fortran::parser::OmpAllocateClause::Allocator>>( 249 ompAllocateClause.t); 250 // Check if allocate clause has allocator specified. If so, add it 251 // to list of allocators, otherwise, add default allocator to 252 // list of allocators. 253 if (allocatorValue) { 254 allocatorOperand = fir::getBase(converter.genExprValue( 255 *Fortran::semantics::GetExpr(allocatorValue->v), stmtCtx)); 256 allocatorOperands.insert(allocatorOperands.end(), ompObjectList.v.size(), 257 allocatorOperand); 258 } else { 259 allocatorOperand = firOpBuilder.createIntegerConstant( 260 currentLocation, firOpBuilder.getI32Type(), 1); 261 allocatorOperands.insert(allocatorOperands.end(), ompObjectList.v.size(), 262 allocatorOperand); 263 } 264 genObjectList(ompObjectList, converter, allocateOperands); 265 } 266 267 static void 268 genOMP(Fortran::lower::AbstractConverter &converter, 269 Fortran::lower::pft::Evaluation &eval, 270 const Fortran::parser::OpenMPStandaloneConstruct &standaloneConstruct) { 271 std::visit( 272 Fortran::common::visitors{ 273 [&](const Fortran::parser::OpenMPSimpleStandaloneConstruct 274 &simpleStandaloneConstruct) { 275 genOMP(converter, eval, simpleStandaloneConstruct); 276 }, 277 [&](const Fortran::parser::OpenMPFlushConstruct &flushConstruct) { 278 SmallVector<Value, 4> operandRange; 279 if (const auto &ompObjectList = 280 std::get<std::optional<Fortran::parser::OmpObjectList>>( 281 flushConstruct.t)) 282 genObjectList(*ompObjectList, converter, operandRange); 283 const auto &memOrderClause = std::get<std::optional< 284 std::list<Fortran::parser::OmpMemoryOrderClause>>>( 285 flushConstruct.t); 286 if (memOrderClause.has_value() && memOrderClause->size() > 0) 287 TODO(converter.getCurrentLocation(), 288 "Handle OmpMemoryOrderClause"); 289 converter.getFirOpBuilder().create<mlir::omp::FlushOp>( 290 converter.getCurrentLocation(), operandRange); 291 }, 292 [&](const Fortran::parser::OpenMPCancelConstruct &cancelConstruct) { 293 TODO(converter.getCurrentLocation(), "OpenMPCancelConstruct"); 294 }, 295 [&](const Fortran::parser::OpenMPCancellationPointConstruct 296 &cancellationPointConstruct) { 297 TODO(converter.getCurrentLocation(), "OpenMPCancelConstruct"); 298 }, 299 }, 300 standaloneConstruct.u); 301 } 302 303 static omp::ClauseProcBindKindAttr genProcBindKindAttr( 304 fir::FirOpBuilder &firOpBuilder, 305 const Fortran::parser::OmpClause::ProcBind *procBindClause) { 306 omp::ClauseProcBindKind pbKind; 307 switch (procBindClause->v.v) { 308 case Fortran::parser::OmpProcBindClause::Type::Master: 309 pbKind = omp::ClauseProcBindKind::Master; 310 break; 311 case Fortran::parser::OmpProcBindClause::Type::Close: 312 pbKind = omp::ClauseProcBindKind::Close; 313 break; 314 case Fortran::parser::OmpProcBindClause::Type::Spread: 315 pbKind = omp::ClauseProcBindKind::Spread; 316 break; 317 case Fortran::parser::OmpProcBindClause::Type::Primary: 318 pbKind = omp::ClauseProcBindKind::Primary; 319 break; 320 } 321 return omp::ClauseProcBindKindAttr::get(firOpBuilder.getContext(), pbKind); 322 } 323 324 /* When parallel is used in a combined construct, then use this function to 325 * create the parallel operation. It handles the parallel specific clauses 326 * and leaves the rest for handling at the inner operations. 327 * TODO: Refactor clause handling 328 */ 329 template <typename Directive> 330 static void 331 createCombinedParallelOp(Fortran::lower::AbstractConverter &converter, 332 Fortran::lower::pft::Evaluation &eval, 333 const Directive &directive) { 334 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); 335 mlir::Location currentLocation = converter.getCurrentLocation(); 336 Fortran::lower::StatementContext stmtCtx; 337 llvm::ArrayRef<mlir::Type> argTy; 338 mlir::Value ifClauseOperand, numThreadsClauseOperand; 339 SmallVector<Value> allocatorOperands, allocateOperands; 340 mlir::omp::ClauseProcBindKindAttr procBindKindAttr; 341 const auto &opClauseList = 342 std::get<Fortran::parser::OmpClauseList>(directive.t); 343 // TODO: Handle the following clauses 344 // 1. default 345 // 2. copyin 346 // Note: rest of the clauses are handled when the inner operation is created 347 for (const Fortran::parser::OmpClause &clause : opClauseList.v) { 348 if (const auto &ifClause = 349 std::get_if<Fortran::parser::OmpClause::If>(&clause.u)) { 350 auto &expr = std::get<Fortran::parser::ScalarLogicalExpr>(ifClause->v.t); 351 mlir::Value ifVal = fir::getBase( 352 converter.genExprValue(*Fortran::semantics::GetExpr(expr), stmtCtx)); 353 ifClauseOperand = firOpBuilder.createConvert( 354 currentLocation, firOpBuilder.getI1Type(), ifVal); 355 } else if (const auto &numThreadsClause = 356 std::get_if<Fortran::parser::OmpClause::NumThreads>( 357 &clause.u)) { 358 numThreadsClauseOperand = fir::getBase(converter.genExprValue( 359 *Fortran::semantics::GetExpr(numThreadsClause->v), stmtCtx)); 360 } else if (const auto &procBindClause = 361 std::get_if<Fortran::parser::OmpClause::ProcBind>( 362 &clause.u)) { 363 procBindKindAttr = genProcBindKindAttr(firOpBuilder, procBindClause); 364 } 365 } 366 // Create and insert the operation. 367 auto parallelOp = firOpBuilder.create<mlir::omp::ParallelOp>( 368 currentLocation, argTy, ifClauseOperand, numThreadsClauseOperand, 369 allocateOperands, allocatorOperands, /*reduction_vars=*/ValueRange(), 370 /*reductions=*/nullptr, procBindKindAttr); 371 372 createBodyOfOp<omp::ParallelOp>(parallelOp, converter, currentLocation, 373 &opClauseList, /*iv=*/{}, 374 /*isCombined=*/true); 375 } 376 377 static void 378 genOMP(Fortran::lower::AbstractConverter &converter, 379 Fortran::lower::pft::Evaluation &eval, 380 const Fortran::parser::OpenMPBlockConstruct &blockConstruct) { 381 const auto &beginBlockDirective = 382 std::get<Fortran::parser::OmpBeginBlockDirective>(blockConstruct.t); 383 const auto &blockDirective = 384 std::get<Fortran::parser::OmpBlockDirective>(beginBlockDirective.t); 385 const auto &endBlockDirective = 386 std::get<Fortran::parser::OmpEndBlockDirective>(blockConstruct.t); 387 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); 388 mlir::Location currentLocation = converter.getCurrentLocation(); 389 390 Fortran::lower::StatementContext stmtCtx; 391 llvm::ArrayRef<mlir::Type> argTy; 392 mlir::Value ifClauseOperand, numThreadsClauseOperand, finalClauseOperand, 393 priorityClauseOperand; 394 mlir::omp::ClauseProcBindKindAttr procBindKindAttr; 395 SmallVector<Value> allocateOperands, allocatorOperands; 396 mlir::UnitAttr nowaitAttr, untiedAttr, mergeableAttr; 397 398 const auto &opClauseList = 399 std::get<Fortran::parser::OmpClauseList>(beginBlockDirective.t); 400 for (const auto &clause : opClauseList.v) { 401 if (const auto &ifClause = 402 std::get_if<Fortran::parser::OmpClause::If>(&clause.u)) { 403 auto &expr = std::get<Fortran::parser::ScalarLogicalExpr>(ifClause->v.t); 404 mlir::Value ifVal = fir::getBase( 405 converter.genExprValue(*Fortran::semantics::GetExpr(expr), stmtCtx)); 406 ifClauseOperand = firOpBuilder.createConvert( 407 currentLocation, firOpBuilder.getI1Type(), ifVal); 408 } else if (const auto &numThreadsClause = 409 std::get_if<Fortran::parser::OmpClause::NumThreads>( 410 &clause.u)) { 411 // OMPIRBuilder expects `NUM_THREAD` clause as a `Value`. 412 numThreadsClauseOperand = fir::getBase(converter.genExprValue( 413 *Fortran::semantics::GetExpr(numThreadsClause->v), stmtCtx)); 414 } else if (const auto &procBindClause = 415 std::get_if<Fortran::parser::OmpClause::ProcBind>( 416 &clause.u)) { 417 procBindKindAttr = genProcBindKindAttr(firOpBuilder, procBindClause); 418 } else if (const auto &allocateClause = 419 std::get_if<Fortran::parser::OmpClause::Allocate>( 420 &clause.u)) { 421 genAllocateClause(converter, allocateClause->v, allocatorOperands, 422 allocateOperands); 423 } else if (std::get_if<Fortran::parser::OmpClause::Private>(&clause.u) || 424 std::get_if<Fortran::parser::OmpClause::Firstprivate>( 425 &clause.u)) { 426 // Privatisation clauses are handled elsewhere. 427 continue; 428 } else if (std::get_if<Fortran::parser::OmpClause::Threads>(&clause.u)) { 429 // Nothing needs to be done for threads clause. 430 continue; 431 } else if (const auto &finalClause = 432 std::get_if<Fortran::parser::OmpClause::Final>(&clause.u)) { 433 mlir::Value finalVal = fir::getBase(converter.genExprValue( 434 *Fortran::semantics::GetExpr(finalClause->v), stmtCtx)); 435 finalClauseOperand = firOpBuilder.createConvert( 436 currentLocation, firOpBuilder.getI1Type(), finalVal); 437 } else if (std::get_if<Fortran::parser::OmpClause::Untied>(&clause.u)) { 438 untiedAttr = firOpBuilder.getUnitAttr(); 439 } else if (std::get_if<Fortran::parser::OmpClause::Mergeable>(&clause.u)) { 440 mergeableAttr = firOpBuilder.getUnitAttr(); 441 } else if (const auto &priorityClause = 442 std::get_if<Fortran::parser::OmpClause::Priority>( 443 &clause.u)) { 444 priorityClauseOperand = fir::getBase(converter.genExprValue( 445 *Fortran::semantics::GetExpr(priorityClause->v), stmtCtx)); 446 } else { 447 TODO(currentLocation, "OpenMP Block construct clauses"); 448 } 449 } 450 451 for (const auto &clause : 452 std::get<Fortran::parser::OmpClauseList>(endBlockDirective.t).v) { 453 if (std::get_if<Fortran::parser::OmpClause::Nowait>(&clause.u)) 454 nowaitAttr = firOpBuilder.getUnitAttr(); 455 } 456 457 if (blockDirective.v == llvm::omp::OMPD_parallel) { 458 // Create and insert the operation. 459 auto parallelOp = firOpBuilder.create<mlir::omp::ParallelOp>( 460 currentLocation, argTy, ifClauseOperand, numThreadsClauseOperand, 461 allocateOperands, allocatorOperands, /*reduction_vars=*/ValueRange(), 462 /*reductions=*/nullptr, procBindKindAttr); 463 createBodyOfOp<omp::ParallelOp>(parallelOp, converter, currentLocation, 464 &opClauseList); 465 } else if (blockDirective.v == llvm::omp::OMPD_master) { 466 auto masterOp = 467 firOpBuilder.create<mlir::omp::MasterOp>(currentLocation, argTy); 468 createBodyOfOp<omp::MasterOp>(masterOp, converter, currentLocation); 469 } else if (blockDirective.v == llvm::omp::OMPD_single) { 470 auto singleOp = firOpBuilder.create<mlir::omp::SingleOp>( 471 currentLocation, allocateOperands, allocatorOperands, nowaitAttr); 472 createBodyOfOp<omp::SingleOp>(singleOp, converter, currentLocation); 473 } else if (blockDirective.v == llvm::omp::OMPD_ordered) { 474 auto orderedOp = firOpBuilder.create<mlir::omp::OrderedRegionOp>( 475 currentLocation, /*simd=*/nullptr); 476 createBodyOfOp<omp::OrderedRegionOp>(orderedOp, converter, currentLocation); 477 } else if (blockDirective.v == llvm::omp::OMPD_task) { 478 auto taskOp = firOpBuilder.create<mlir::omp::TaskOp>( 479 currentLocation, ifClauseOperand, finalClauseOperand, untiedAttr, 480 mergeableAttr, /*in_reduction_vars=*/ValueRange(), 481 /*in_reductions=*/nullptr, priorityClauseOperand, allocateOperands, 482 allocatorOperands); 483 createBodyOfOp(taskOp, converter, currentLocation, &opClauseList); 484 } else { 485 TODO(converter.getCurrentLocation(), "Unhandled block directive"); 486 } 487 } 488 489 static void genOMP(Fortran::lower::AbstractConverter &converter, 490 Fortran::lower::pft::Evaluation &eval, 491 const Fortran::parser::OpenMPLoopConstruct &loopConstruct) { 492 493 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); 494 mlir::Location currentLocation = converter.getCurrentLocation(); 495 llvm::SmallVector<mlir::Value> lowerBound, upperBound, step, linearVars, 496 linearStepVars, reductionVars; 497 mlir::Value scheduleChunkClauseOperand; 498 mlir::Attribute scheduleClauseOperand, collapseClauseOperand, 499 noWaitClauseOperand, orderedClauseOperand, orderClauseOperand; 500 const auto &wsLoopOpClauseList = std::get<Fortran::parser::OmpClauseList>( 501 std::get<Fortran::parser::OmpBeginLoopDirective>(loopConstruct.t).t); 502 503 const auto ompDirective = 504 std::get<Fortran::parser::OmpLoopDirective>( 505 std::get<Fortran::parser::OmpBeginLoopDirective>(loopConstruct.t).t) 506 .v; 507 if (llvm::omp::OMPD_parallel_do == ompDirective) { 508 createCombinedParallelOp<Fortran::parser::OmpBeginLoopDirective>( 509 converter, eval, 510 std::get<Fortran::parser::OmpBeginLoopDirective>(loopConstruct.t)); 511 } else if (llvm::omp::OMPD_do != ompDirective) { 512 TODO(converter.getCurrentLocation(), "Construct enclosing do loop"); 513 } 514 515 // Collect the loops to collapse. 516 auto *doConstructEval = &eval.getFirstNestedEvaluation(); 517 518 std::int64_t collapseValue = 519 Fortran::lower::getCollapseValue(wsLoopOpClauseList); 520 std::size_t loopVarTypeSize = 0; 521 SmallVector<const Fortran::semantics::Symbol *> iv; 522 do { 523 auto *doLoop = &doConstructEval->getFirstNestedEvaluation(); 524 auto *doStmt = doLoop->getIf<Fortran::parser::NonLabelDoStmt>(); 525 assert(doStmt && "Expected do loop to be in the nested evaluation"); 526 const auto &loopControl = 527 std::get<std::optional<Fortran::parser::LoopControl>>(doStmt->t); 528 const Fortran::parser::LoopControl::Bounds *bounds = 529 std::get_if<Fortran::parser::LoopControl::Bounds>(&loopControl->u); 530 assert(bounds && "Expected bounds for worksharing do loop"); 531 Fortran::lower::StatementContext stmtCtx; 532 lowerBound.push_back(fir::getBase(converter.genExprValue( 533 *Fortran::semantics::GetExpr(bounds->lower), stmtCtx))); 534 upperBound.push_back(fir::getBase(converter.genExprValue( 535 *Fortran::semantics::GetExpr(bounds->upper), stmtCtx))); 536 if (bounds->step) { 537 step.push_back(fir::getBase(converter.genExprValue( 538 *Fortran::semantics::GetExpr(bounds->step), stmtCtx))); 539 } else { // If `step` is not present, assume it as `1`. 540 step.push_back(firOpBuilder.createIntegerConstant( 541 currentLocation, firOpBuilder.getIntegerType(32), 1)); 542 } 543 iv.push_back(bounds->name.thing.symbol); 544 loopVarTypeSize = std::max(loopVarTypeSize, 545 bounds->name.thing.symbol->GetUltimate().size()); 546 547 collapseValue--; 548 doConstructEval = 549 &*std::next(doConstructEval->getNestedEvaluations().begin()); 550 } while (collapseValue > 0); 551 552 // The types of lower bound, upper bound, and step are converted into the 553 // type of the loop variable if necessary. 554 mlir::Type loopVarType = getLoopVarType(converter, loopVarTypeSize); 555 for (unsigned it = 0; it < (unsigned)lowerBound.size(); it++) { 556 lowerBound[it] = firOpBuilder.createConvert(currentLocation, loopVarType, 557 lowerBound[it]); 558 upperBound[it] = firOpBuilder.createConvert(currentLocation, loopVarType, 559 upperBound[it]); 560 step[it] = 561 firOpBuilder.createConvert(currentLocation, loopVarType, step[it]); 562 } 563 564 // FIXME: Add support for following clauses: 565 // 1. linear 566 // 2. order 567 // 3. schedule (with chunk) 568 auto wsLoopOp = firOpBuilder.create<mlir::omp::WsLoopOp>( 569 currentLocation, lowerBound, upperBound, step, linearVars, linearStepVars, 570 reductionVars, /*reductions=*/nullptr, 571 scheduleClauseOperand.dyn_cast_or_null<omp::ClauseScheduleKindAttr>(), 572 scheduleChunkClauseOperand, /*schedule_modifiers=*/nullptr, 573 /*simd_modifier=*/nullptr, 574 collapseClauseOperand.dyn_cast_or_null<IntegerAttr>(), 575 noWaitClauseOperand.dyn_cast_or_null<UnitAttr>(), 576 orderedClauseOperand.dyn_cast_or_null<IntegerAttr>(), 577 orderClauseOperand.dyn_cast_or_null<omp::ClauseOrderKindAttr>(), 578 /*inclusive=*/firOpBuilder.getUnitAttr()); 579 580 // Handle attribute based clauses. 581 for (const Fortran::parser::OmpClause &clause : wsLoopOpClauseList.v) { 582 if (const auto &orderedClause = 583 std::get_if<Fortran::parser::OmpClause::Ordered>(&clause.u)) { 584 if (orderedClause->v.has_value()) { 585 const auto *expr = Fortran::semantics::GetExpr(orderedClause->v); 586 const std::optional<std::int64_t> orderedClauseValue = 587 Fortran::evaluate::ToInt64(*expr); 588 wsLoopOp.ordered_valAttr( 589 firOpBuilder.getI64IntegerAttr(*orderedClauseValue)); 590 } else { 591 wsLoopOp.ordered_valAttr(firOpBuilder.getI64IntegerAttr(0)); 592 } 593 } else if (const auto &collapseClause = 594 std::get_if<Fortran::parser::OmpClause::Collapse>( 595 &clause.u)) { 596 const auto *expr = Fortran::semantics::GetExpr(collapseClause->v); 597 const std::optional<std::int64_t> collapseValue = 598 Fortran::evaluate::ToInt64(*expr); 599 wsLoopOp.collapse_valAttr(firOpBuilder.getI64IntegerAttr(*collapseValue)); 600 } else if (const auto &scheduleClause = 601 std::get_if<Fortran::parser::OmpClause::Schedule>( 602 &clause.u)) { 603 mlir::MLIRContext *context = firOpBuilder.getContext(); 604 const auto &scheduleType = scheduleClause->v; 605 const auto &scheduleKind = 606 std::get<Fortran::parser::OmpScheduleClause::ScheduleType>( 607 scheduleType.t); 608 switch (scheduleKind) { 609 case Fortran::parser::OmpScheduleClause::ScheduleType::Static: 610 wsLoopOp.schedule_valAttr(omp::ClauseScheduleKindAttr::get( 611 context, omp::ClauseScheduleKind::Static)); 612 break; 613 case Fortran::parser::OmpScheduleClause::ScheduleType::Dynamic: 614 wsLoopOp.schedule_valAttr(omp::ClauseScheduleKindAttr::get( 615 context, omp::ClauseScheduleKind::Dynamic)); 616 break; 617 case Fortran::parser::OmpScheduleClause::ScheduleType::Guided: 618 wsLoopOp.schedule_valAttr(omp::ClauseScheduleKindAttr::get( 619 context, omp::ClauseScheduleKind::Guided)); 620 break; 621 case Fortran::parser::OmpScheduleClause::ScheduleType::Auto: 622 wsLoopOp.schedule_valAttr(omp::ClauseScheduleKindAttr::get( 623 context, omp::ClauseScheduleKind::Auto)); 624 break; 625 case Fortran::parser::OmpScheduleClause::ScheduleType::Runtime: 626 wsLoopOp.schedule_valAttr(omp::ClauseScheduleKindAttr::get( 627 context, omp::ClauseScheduleKind::Runtime)); 628 break; 629 } 630 } 631 } 632 // In FORTRAN `nowait` clause occur at the end of `omp do` directive. 633 // i.e 634 // !$omp do 635 // <...> 636 // !$omp end do nowait 637 if (const auto &endClauseList = 638 std::get<std::optional<Fortran::parser::OmpEndLoopDirective>>( 639 loopConstruct.t)) { 640 const auto &clauseList = 641 std::get<Fortran::parser::OmpClauseList>((*endClauseList).t); 642 for (const Fortran::parser::OmpClause &clause : clauseList.v) 643 if (std::get_if<Fortran::parser::OmpClause::Nowait>(&clause.u)) 644 wsLoopOp.nowaitAttr(firOpBuilder.getUnitAttr()); 645 } 646 647 createBodyOfOp<omp::WsLoopOp>(wsLoopOp, converter, currentLocation, 648 &wsLoopOpClauseList, iv); 649 } 650 651 static void 652 genOMP(Fortran::lower::AbstractConverter &converter, 653 Fortran::lower::pft::Evaluation &eval, 654 const Fortran::parser::OpenMPCriticalConstruct &criticalConstruct) { 655 fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder(); 656 mlir::Location currentLocation = converter.getCurrentLocation(); 657 std::string name; 658 const Fortran::parser::OmpCriticalDirective &cd = 659 std::get<Fortran::parser::OmpCriticalDirective>(criticalConstruct.t); 660 if (std::get<std::optional<Fortran::parser::Name>>(cd.t).has_value()) { 661 name = 662 std::get<std::optional<Fortran::parser::Name>>(cd.t).value().ToString(); 663 } 664 665 uint64_t hint = 0; 666 const auto &clauseList = std::get<Fortran::parser::OmpClauseList>(cd.t); 667 for (const Fortran::parser::OmpClause &clause : clauseList.v) 668 if (auto hintClause = 669 std::get_if<Fortran::parser::OmpClause::Hint>(&clause.u)) { 670 const auto *expr = Fortran::semantics::GetExpr(hintClause->v); 671 hint = *Fortran::evaluate::ToInt64(*expr); 672 break; 673 } 674 675 mlir::omp::CriticalOp criticalOp = [&]() { 676 if (name.empty()) { 677 return firOpBuilder.create<mlir::omp::CriticalOp>(currentLocation, 678 FlatSymbolRefAttr()); 679 } else { 680 mlir::ModuleOp module = firOpBuilder.getModule(); 681 mlir::OpBuilder modBuilder(module.getBodyRegion()); 682 auto global = module.lookupSymbol<mlir::omp::CriticalDeclareOp>(name); 683 if (!global) 684 global = modBuilder.create<mlir::omp::CriticalDeclareOp>( 685 currentLocation, name, hint); 686 return firOpBuilder.create<mlir::omp::CriticalOp>( 687 currentLocation, mlir::FlatSymbolRefAttr::get( 688 firOpBuilder.getContext(), global.sym_name())); 689 } 690 }(); 691 createBodyOfOp<omp::CriticalOp>(criticalOp, converter, currentLocation); 692 } 693 694 static void 695 genOMP(Fortran::lower::AbstractConverter &converter, 696 Fortran::lower::pft::Evaluation &eval, 697 const Fortran::parser::OpenMPSectionConstruct §ionConstruct) { 698 699 auto &firOpBuilder = converter.getFirOpBuilder(); 700 auto currentLocation = converter.getCurrentLocation(); 701 mlir::omp::SectionOp sectionOp = 702 firOpBuilder.create<mlir::omp::SectionOp>(currentLocation); 703 createBodyOfOp<omp::SectionOp>(sectionOp, converter, currentLocation); 704 } 705 706 // TODO: Add support for reduction 707 static void 708 genOMP(Fortran::lower::AbstractConverter &converter, 709 Fortran::lower::pft::Evaluation &eval, 710 const Fortran::parser::OpenMPSectionsConstruct §ionsConstruct) { 711 auto &firOpBuilder = converter.getFirOpBuilder(); 712 auto currentLocation = converter.getCurrentLocation(); 713 SmallVector<Value> reductionVars, allocateOperands, allocatorOperands; 714 mlir::UnitAttr noWaitClauseOperand; 715 const auto §ionsClauseList = std::get<Fortran::parser::OmpClauseList>( 716 std::get<Fortran::parser::OmpBeginSectionsDirective>(sectionsConstruct.t) 717 .t); 718 for (const Fortran::parser::OmpClause &clause : sectionsClauseList.v) { 719 720 // Reduction Clause 721 if (std::get_if<Fortran::parser::OmpClause::Reduction>(&clause.u)) { 722 TODO(currentLocation, "OMPC_Reduction"); 723 724 // Allocate clause 725 } else if (const auto &allocateClause = 726 std::get_if<Fortran::parser::OmpClause::Allocate>( 727 &clause.u)) { 728 genAllocateClause(converter, allocateClause->v, allocatorOperands, 729 allocateOperands); 730 } 731 } 732 const auto &endSectionsClauseList = 733 std::get<Fortran::parser::OmpEndSectionsDirective>(sectionsConstruct.t); 734 const auto &clauseList = 735 std::get<Fortran::parser::OmpClauseList>(endSectionsClauseList.t); 736 for (const auto &clause : clauseList.v) { 737 // Nowait clause 738 if (std::get_if<Fortran::parser::OmpClause::Nowait>(&clause.u)) { 739 noWaitClauseOperand = firOpBuilder.getUnitAttr(); 740 } 741 } 742 743 llvm::omp::Directive dir = 744 std::get<Fortran::parser::OmpSectionsDirective>( 745 std::get<Fortran::parser::OmpBeginSectionsDirective>( 746 sectionsConstruct.t) 747 .t) 748 .v; 749 750 // Parallel Sections Construct 751 if (dir == llvm::omp::Directive::OMPD_parallel_sections) { 752 createCombinedParallelOp<Fortran::parser::OmpBeginSectionsDirective>( 753 converter, eval, 754 std::get<Fortran::parser::OmpBeginSectionsDirective>( 755 sectionsConstruct.t)); 756 auto sectionsOp = firOpBuilder.create<mlir::omp::SectionsOp>( 757 currentLocation, /*reduction_vars*/ ValueRange(), 758 /*reductions=*/nullptr, allocateOperands, allocatorOperands, 759 /*nowait=*/nullptr); 760 createBodyOfOp(sectionsOp, converter, currentLocation); 761 762 // Sections Construct 763 } else if (dir == llvm::omp::Directive::OMPD_sections) { 764 auto sectionsOp = firOpBuilder.create<mlir::omp::SectionsOp>( 765 currentLocation, reductionVars, /*reductions = */ nullptr, 766 allocateOperands, allocatorOperands, noWaitClauseOperand); 767 createBodyOfOp<omp::SectionsOp>(sectionsOp, converter, currentLocation); 768 } 769 } 770 771 static void genOmpAtomicHintAndMemoryOrderClauses( 772 Fortran::lower::AbstractConverter &converter, 773 const Fortran::parser::OmpAtomicClauseList &clauseList, 774 mlir::IntegerAttr &hint, 775 mlir::omp::ClauseMemoryOrderKindAttr &memory_order) { 776 auto &firOpBuilder = converter.getFirOpBuilder(); 777 for (const auto &clause : clauseList.v) { 778 if (auto ompClause = std::get_if<Fortran::parser::OmpClause>(&clause.u)) { 779 if (auto hintClause = 780 std::get_if<Fortran::parser::OmpClause::Hint>(&ompClause->u)) { 781 const auto *expr = Fortran::semantics::GetExpr(hintClause->v); 782 uint64_t hintExprValue = *Fortran::evaluate::ToInt64(*expr); 783 hint = firOpBuilder.getI64IntegerAttr(hintExprValue); 784 } 785 } else if (auto ompMemoryOrderClause = 786 std::get_if<Fortran::parser::OmpMemoryOrderClause>( 787 &clause.u)) { 788 if (std::get_if<Fortran::parser::OmpClause::Acquire>( 789 &ompMemoryOrderClause->v.u)) { 790 memory_order = mlir::omp::ClauseMemoryOrderKindAttr::get( 791 firOpBuilder.getContext(), omp::ClauseMemoryOrderKind::Acquire); 792 } else if (std::get_if<Fortran::parser::OmpClause::Relaxed>( 793 &ompMemoryOrderClause->v.u)) { 794 memory_order = mlir::omp::ClauseMemoryOrderKindAttr::get( 795 firOpBuilder.getContext(), omp::ClauseMemoryOrderKind::Relaxed); 796 } else if (std::get_if<Fortran::parser::OmpClause::SeqCst>( 797 &ompMemoryOrderClause->v.u)) { 798 memory_order = mlir::omp::ClauseMemoryOrderKindAttr::get( 799 firOpBuilder.getContext(), omp::ClauseMemoryOrderKind::Seq_cst); 800 } else if (std::get_if<Fortran::parser::OmpClause::Release>( 801 &ompMemoryOrderClause->v.u)) { 802 memory_order = mlir::omp::ClauseMemoryOrderKindAttr::get( 803 firOpBuilder.getContext(), omp::ClauseMemoryOrderKind::Release); 804 } 805 } 806 } 807 } 808 809 static void 810 genOmpAtomicWrite(Fortran::lower::AbstractConverter &converter, 811 Fortran::lower::pft::Evaluation &eval, 812 const Fortran::parser::OmpAtomicWrite &atomicWrite) { 813 auto &firOpBuilder = converter.getFirOpBuilder(); 814 auto currentLocation = converter.getCurrentLocation(); 815 mlir::Value address; 816 // If no hint clause is specified, the effect is as if 817 // hint(omp_sync_hint_none) had been specified. 818 mlir::IntegerAttr hint = nullptr; 819 mlir::omp::ClauseMemoryOrderKindAttr memory_order = nullptr; 820 const Fortran::parser::OmpAtomicClauseList &rightHandClauseList = 821 std::get<2>(atomicWrite.t); 822 const Fortran::parser::OmpAtomicClauseList &leftHandClauseList = 823 std::get<0>(atomicWrite.t); 824 const auto &assignmentStmtExpr = 825 std::get<Fortran::parser::Expr>(std::get<3>(atomicWrite.t).statement.t); 826 const auto &assignmentStmtVariable = std::get<Fortran::parser::Variable>( 827 std::get<3>(atomicWrite.t).statement.t); 828 Fortran::lower::StatementContext stmtCtx; 829 auto value = fir::getBase(converter.genExprValue( 830 *Fortran::semantics::GetExpr(assignmentStmtExpr), stmtCtx)); 831 if (auto varDesignator = std::get_if< 832 Fortran::common::Indirection<Fortran::parser::Designator>>( 833 &assignmentStmtVariable.u)) { 834 if (const auto *name = getDesignatorNameIfDataRef(varDesignator->value())) { 835 address = converter.getSymbolAddress(*name->symbol); 836 } 837 } 838 839 genOmpAtomicHintAndMemoryOrderClauses(converter, leftHandClauseList, hint, 840 memory_order); 841 genOmpAtomicHintAndMemoryOrderClauses(converter, rightHandClauseList, hint, 842 memory_order); 843 firOpBuilder.create<mlir::omp::AtomicWriteOp>(currentLocation, address, value, 844 hint, memory_order); 845 } 846 847 static void genOmpAtomicRead(Fortran::lower::AbstractConverter &converter, 848 Fortran::lower::pft::Evaluation &eval, 849 const Fortran::parser::OmpAtomicRead &atomicRead) { 850 auto &firOpBuilder = converter.getFirOpBuilder(); 851 auto currentLocation = converter.getCurrentLocation(); 852 mlir::Value to_address; 853 mlir::Value from_address; 854 // If no hint clause is specified, the effect is as if 855 // hint(omp_sync_hint_none) had been specified. 856 mlir::IntegerAttr hint = nullptr; 857 mlir::omp::ClauseMemoryOrderKindAttr memory_order = nullptr; 858 const Fortran::parser::OmpAtomicClauseList &rightHandClauseList = 859 std::get<2>(atomicRead.t); 860 const Fortran::parser::OmpAtomicClauseList &leftHandClauseList = 861 std::get<0>(atomicRead.t); 862 const auto &assignmentStmtExpr = 863 std::get<Fortran::parser::Expr>(std::get<3>(atomicRead.t).statement.t); 864 const auto &assignmentStmtVariable = std::get<Fortran::parser::Variable>( 865 std::get<3>(atomicRead.t).statement.t); 866 if (auto exprDesignator = std::get_if< 867 Fortran::common::Indirection<Fortran::parser::Designator>>( 868 &assignmentStmtExpr.u)) { 869 if (const auto *name = 870 getDesignatorNameIfDataRef(exprDesignator->value())) { 871 from_address = converter.getSymbolAddress(*name->symbol); 872 } 873 } 874 875 if (auto varDesignator = std::get_if< 876 Fortran::common::Indirection<Fortran::parser::Designator>>( 877 &assignmentStmtVariable.u)) { 878 if (const auto *name = getDesignatorNameIfDataRef(varDesignator->value())) { 879 to_address = converter.getSymbolAddress(*name->symbol); 880 } 881 } 882 883 genOmpAtomicHintAndMemoryOrderClauses(converter, leftHandClauseList, hint, 884 memory_order); 885 genOmpAtomicHintAndMemoryOrderClauses(converter, rightHandClauseList, hint, 886 memory_order); 887 firOpBuilder.create<mlir::omp::AtomicReadOp>(currentLocation, from_address, 888 to_address, hint, memory_order); 889 } 890 891 static void 892 genOMP(Fortran::lower::AbstractConverter &converter, 893 Fortran::lower::pft::Evaluation &eval, 894 const Fortran::parser::OpenMPAtomicConstruct &atomicConstruct) { 895 std::visit(Fortran::common::visitors{ 896 [&](const Fortran::parser::OmpAtomicRead &atomicRead) { 897 genOmpAtomicRead(converter, eval, atomicRead); 898 }, 899 [&](const Fortran::parser::OmpAtomicWrite &atomicWrite) { 900 genOmpAtomicWrite(converter, eval, atomicWrite); 901 }, 902 [&](const auto &) { 903 TODO(converter.getCurrentLocation(), 904 "Atomic update & capture"); 905 }, 906 }, 907 atomicConstruct.u); 908 } 909 910 void Fortran::lower::genOpenMPConstruct( 911 Fortran::lower::AbstractConverter &converter, 912 Fortran::lower::pft::Evaluation &eval, 913 const Fortran::parser::OpenMPConstruct &ompConstruct) { 914 915 std::visit( 916 common::visitors{ 917 [&](const Fortran::parser::OpenMPStandaloneConstruct 918 &standaloneConstruct) { 919 genOMP(converter, eval, standaloneConstruct); 920 }, 921 [&](const Fortran::parser::OpenMPSectionsConstruct 922 §ionsConstruct) { 923 genOMP(converter, eval, sectionsConstruct); 924 }, 925 [&](const Fortran::parser::OpenMPSectionConstruct §ionConstruct) { 926 genOMP(converter, eval, sectionConstruct); 927 }, 928 [&](const Fortran::parser::OpenMPLoopConstruct &loopConstruct) { 929 genOMP(converter, eval, loopConstruct); 930 }, 931 [&](const Fortran::parser::OpenMPDeclarativeAllocate 932 &execAllocConstruct) { 933 TODO(converter.getCurrentLocation(), "OpenMPDeclarativeAllocate"); 934 }, 935 [&](const Fortran::parser::OpenMPExecutableAllocate 936 &execAllocConstruct) { 937 TODO(converter.getCurrentLocation(), "OpenMPExecutableAllocate"); 938 }, 939 [&](const Fortran::parser::OpenMPBlockConstruct &blockConstruct) { 940 genOMP(converter, eval, blockConstruct); 941 }, 942 [&](const Fortran::parser::OpenMPAtomicConstruct &atomicConstruct) { 943 genOMP(converter, eval, atomicConstruct); 944 }, 945 [&](const Fortran::parser::OpenMPCriticalConstruct 946 &criticalConstruct) { 947 genOMP(converter, eval, criticalConstruct); 948 }, 949 }, 950 ompConstruct.u); 951 } 952 953 void Fortran::lower::genOpenMPDeclarativeConstruct( 954 Fortran::lower::AbstractConverter &converter, 955 Fortran::lower::pft::Evaluation &eval, 956 const Fortran::parser::OpenMPDeclarativeConstruct &ompDeclConstruct) { 957 958 std::visit( 959 common::visitors{ 960 [&](const Fortran::parser::OpenMPDeclarativeAllocate 961 &declarativeAllocate) { 962 TODO(converter.getCurrentLocation(), "OpenMPDeclarativeAllocate"); 963 }, 964 [&](const Fortran::parser::OpenMPDeclareReductionConstruct 965 &declareReductionConstruct) { 966 TODO(converter.getCurrentLocation(), 967 "OpenMPDeclareReductionConstruct"); 968 }, 969 [&](const Fortran::parser::OpenMPDeclareSimdConstruct 970 &declareSimdConstruct) { 971 TODO(converter.getCurrentLocation(), "OpenMPDeclareSimdConstruct"); 972 }, 973 [&](const Fortran::parser::OpenMPDeclareTargetConstruct 974 &declareTargetConstruct) { 975 TODO(converter.getCurrentLocation(), 976 "OpenMPDeclareTargetConstruct"); 977 }, 978 [&](const Fortran::parser::OpenMPThreadprivate &threadprivate) { 979 TODO(converter.getCurrentLocation(), "OpenMPThreadprivate"); 980 }, 981 }, 982 ompDeclConstruct.u); 983 } 984