1 //===- ConstructDecompositionT.h -- Decomposing compound constructs -------===// 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 // Given a compound construct with a set of clauses, generate the list of 9 // constituent leaf constructs, each with a list of clauses that apply to it. 10 // 11 // Note: Clauses that are not originally present, but that are implied by the 12 // OpenMP spec are materialized, and are present in the output. 13 // 14 // Note: Composite constructs will also be broken up into leaf constructs. 15 // If composite constructs require processing as a whole, the lists of clauses 16 // for each leaf constituent should be merged. 17 //===----------------------------------------------------------------------===// 18 #ifndef LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H 19 #define LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H 20 21 #include "llvm/ADT/ArrayRef.h" 22 #include "llvm/ADT/STLExtras.h" 23 #include "llvm/ADT/SmallVector.h" 24 #include "llvm/ADT/iterator_range.h" 25 #include "llvm/Frontend/OpenMP/ClauseT.h" 26 #include "llvm/Frontend/OpenMP/OMP.h" 27 28 #include <iterator> 29 #include <list> 30 #include <optional> 31 #include <tuple> 32 #include <type_traits> 33 #include <unordered_map> 34 #include <unordered_set> 35 #include <utility> 36 #include <variant> 37 38 static inline llvm::ArrayRef<llvm::omp::Directive> getWorksharing() { 39 static llvm::omp::Directive worksharing[] = { 40 llvm::omp::Directive::OMPD_do, llvm::omp::Directive::OMPD_for, 41 llvm::omp::Directive::OMPD_scope, llvm::omp::Directive::OMPD_sections, 42 llvm::omp::Directive::OMPD_single, llvm::omp::Directive::OMPD_workshare, 43 }; 44 return worksharing; 45 } 46 47 static inline llvm::ArrayRef<llvm::omp::Directive> getWorksharingLoop() { 48 static llvm::omp::Directive worksharingLoop[] = { 49 llvm::omp::Directive::OMPD_do, 50 llvm::omp::Directive::OMPD_for, 51 }; 52 return worksharingLoop; 53 } 54 55 namespace detail { 56 template <typename Container, typename Predicate> 57 typename std::remove_reference_t<Container>::iterator 58 find_unique(Container &&container, Predicate &&pred) { 59 auto first = std::find_if(container.begin(), container.end(), pred); 60 if (first == container.end()) 61 return first; 62 auto second = std::find_if(std::next(first), container.end(), pred); 63 if (second == container.end()) 64 return first; 65 return container.end(); 66 } 67 } // namespace detail 68 69 namespace tomp { 70 71 // ClauseType - Either instance of ClauseT, or a type derived from ClauseT. 72 // 73 // This is the clause representation in the code using this infrastructure. 74 // 75 // HelperType - A class that implements two member functions: 76 // 77 // // Return the base object of the given object, if any. 78 // std::optional<Object> getBaseObject(const Object &object) const 79 // // Return the iteration variable of the outermost loop associated 80 // // with the construct being worked on, if any. 81 // std::optional<Object> getLoopIterVar() const 82 template <typename ClauseType, typename HelperType> 83 struct ConstructDecompositionT { 84 using ClauseTy = ClauseType; 85 86 using TypeTy = typename ClauseTy::TypeTy; 87 using IdTy = typename ClauseTy::IdTy; 88 using ExprTy = typename ClauseTy::ExprTy; 89 using HelperTy = HelperType; 90 using ObjectTy = tomp::ObjectT<IdTy, ExprTy>; 91 92 using ClauseSet = std::unordered_set<const ClauseTy *>; 93 94 ConstructDecompositionT(uint32_t ver, HelperType &helper, 95 llvm::omp::Directive dir, 96 llvm::ArrayRef<ClauseTy> clauses) 97 : version(ver), construct(dir), helper(helper) { 98 for (const ClauseTy &clause : clauses) 99 nodes.push_back(&clause); 100 101 bool success = split(); 102 if (!success) 103 return; 104 105 // Copy the individual leaf directives with their clauses to the 106 // output list. Copy by value, since we don't own the storage 107 // with the input clauses, and the internal representation uses 108 // clause addresses. 109 for (auto &leaf : leafs) { 110 output.push_back({leaf.id, {}}); 111 auto &out = output.back(); 112 for (const ClauseTy *c : leaf.clauses) 113 out.clauses.push_back(*c); 114 } 115 } 116 117 tomp::ListT<DirectiveWithClauses<ClauseType>> output; 118 119 private: 120 bool split(); 121 122 struct LeafReprInternal { 123 llvm::omp::Directive id = llvm::omp::Directive::OMPD_unknown; 124 tomp::type::ListT<const ClauseTy *> clauses; 125 }; 126 127 LeafReprInternal *findDirective(llvm::omp::Directive dirId) { 128 auto found = llvm::find_if( 129 leafs, [&](const LeafReprInternal &leaf) { return leaf.id == dirId; }); 130 return found != leafs.end() ? &*found : nullptr; 131 } 132 133 ClauseSet *findClausesWith(const ObjectTy &object) { 134 if (auto found = syms.find(object.id()); found != syms.end()) 135 return &found->second; 136 return nullptr; 137 } 138 139 template <typename S> 140 ClauseTy *makeClause(llvm::omp::Clause clauseId, S &&specific) { 141 implicit.push_back(typename ClauseTy::BaseT{clauseId, std::move(specific)}); 142 return &implicit.back(); 143 } 144 145 void addClauseSymsToMap(const ObjectTy &object, const ClauseTy *); 146 void addClauseSymsToMap(const tomp::ObjectListT<IdTy, ExprTy> &objects, 147 const ClauseTy *); 148 void addClauseSymsToMap(const TypeTy &item, const ClauseTy *); 149 void addClauseSymsToMap(const ExprTy &item, const ClauseTy *); 150 void addClauseSymsToMap(const tomp::clause::MapT<TypeTy, IdTy, ExprTy> &item, 151 const ClauseTy *); 152 153 template <typename U> 154 void addClauseSymsToMap(const std::optional<U> &item, const ClauseTy *); 155 template <typename U> 156 void addClauseSymsToMap(const tomp::ListT<U> &item, const ClauseTy *); 157 template <typename... U, size_t... Is> 158 void addClauseSymsToMap(const std::tuple<U...> &item, const ClauseTy *, 159 std::index_sequence<Is...> = {}); 160 template <typename U> 161 std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, void> 162 addClauseSymsToMap(U &&item, const ClauseTy *); 163 164 template <typename U> 165 std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value, void> 166 addClauseSymsToMap(U &&item, const ClauseTy *); 167 168 template <typename U> 169 std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value, void> 170 addClauseSymsToMap(U &&item, const ClauseTy *); 171 172 template <typename U> 173 std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value, void> 174 addClauseSymsToMap(U &&item, const ClauseTy *); 175 176 template <typename U> 177 std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value, void> 178 addClauseSymsToMap(U &&item, const ClauseTy *); 179 180 template <typename U> 181 std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value, void> 182 addClauseSymsToMap(U &&item, const ClauseTy *); 183 184 // Apply a clause to the only directive that allows it. If there are no 185 // directives that allow it, or if there is more that one, do not apply 186 // anything and return false, otherwise return true. 187 bool applyToUnique(const ClauseTy *node); 188 189 // Apply a clause to the first directive in given range that allows it. 190 // If such a directive does not exist, return false, otherwise return true. 191 template <typename Iterator> 192 bool applyToFirst(const ClauseTy *node, llvm::iterator_range<Iterator> range); 193 194 // Apply a clause to the innermost directive that allows it. If such a 195 // directive does not exist, return false, otherwise return true. 196 bool applyToInnermost(const ClauseTy *node); 197 198 // Apply a clause to the outermost directive that allows it. If such a 199 // directive does not exist, return false, otherwise return true. 200 bool applyToOutermost(const ClauseTy *node); 201 202 template <typename Predicate> 203 bool applyIf(const ClauseTy *node, Predicate shouldApply); 204 205 bool applyToAll(const ClauseTy *node); 206 207 template <typename Clause> 208 bool applyClause(Clause &&clause, const ClauseTy *node); 209 210 bool applyClause(const tomp::clause::CollapseT<TypeTy, IdTy, ExprTy> &clause, 211 const ClauseTy *); 212 bool applyClause(const tomp::clause::PrivateT<TypeTy, IdTy, ExprTy> &clause, 213 const ClauseTy *); 214 bool 215 applyClause(const tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy> &clause, 216 const ClauseTy *); 217 bool 218 applyClause(const tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy> &clause, 219 const ClauseTy *); 220 bool applyClause(const tomp::clause::SharedT<TypeTy, IdTy, ExprTy> &clause, 221 const ClauseTy *); 222 bool applyClause(const tomp::clause::DefaultT<TypeTy, IdTy, ExprTy> &clause, 223 const ClauseTy *); 224 bool 225 applyClause(const tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy> &clause, 226 const ClauseTy *); 227 bool applyClause(const tomp::clause::OrderT<TypeTy, IdTy, ExprTy> &clause, 228 const ClauseTy *); 229 bool applyClause(const tomp::clause::AllocateT<TypeTy, IdTy, ExprTy> &clause, 230 const ClauseTy *); 231 bool applyClause(const tomp::clause::ReductionT<TypeTy, IdTy, ExprTy> &clause, 232 const ClauseTy *); 233 bool applyClause(const tomp::clause::IfT<TypeTy, IdTy, ExprTy> &clause, 234 const ClauseTy *); 235 bool applyClause(const tomp::clause::LinearT<TypeTy, IdTy, ExprTy> &clause, 236 const ClauseTy *); 237 bool applyClause(const tomp::clause::NowaitT<TypeTy, IdTy, ExprTy> &clause, 238 const ClauseTy *); 239 bool 240 applyClause(const tomp::clause::OmpxAttributeT<TypeTy, IdTy, ExprTy> &clause, 241 const ClauseTy *); 242 bool applyClause(const tomp::clause::OmpxBareT<TypeTy, IdTy, ExprTy> &clause, 243 const ClauseTy *); 244 245 uint32_t version; 246 llvm::omp::Directive construct; 247 HelperType &helper; 248 ListT<LeafReprInternal> leafs; 249 tomp::ListT<const ClauseTy *> nodes; 250 std::list<ClauseTy> implicit; // Container for materialized implicit clauses. 251 // Inserting must preserve element addresses. 252 std::unordered_map<IdTy, ClauseSet> syms; 253 std::unordered_set<IdTy> mapBases; 254 }; 255 256 // Deduction guide 257 template <typename ClauseType, typename HelperType> 258 ConstructDecompositionT(uint32_t, HelperType &, llvm::omp::Directive, 259 llvm::ArrayRef<ClauseType>) 260 -> ConstructDecompositionT<ClauseType, HelperType>; 261 262 template <typename C, typename H> 263 void ConstructDecompositionT<C, H>::addClauseSymsToMap(const ObjectTy &object, 264 const ClauseTy *node) { 265 syms[object.id()].insert(node); 266 } 267 268 template <typename C, typename H> 269 void ConstructDecompositionT<C, H>::addClauseSymsToMap( 270 const tomp::ObjectListT<IdTy, ExprTy> &objects, const ClauseTy *node) { 271 for (auto &object : objects) 272 syms[object.id()].insert(node); 273 } 274 275 template <typename C, typename H> 276 void ConstructDecompositionT<C, H>::addClauseSymsToMap(const TypeTy &item, 277 const ClauseTy *node) { 278 // Nothing to do for types. 279 } 280 281 template <typename C, typename H> 282 void ConstructDecompositionT<C, H>::addClauseSymsToMap(const ExprTy &item, 283 const ClauseTy *node) { 284 // Nothing to do for expressions. 285 } 286 287 template <typename C, typename H> 288 void ConstructDecompositionT<C, H>::addClauseSymsToMap( 289 const tomp::clause::MapT<TypeTy, IdTy, ExprTy> &item, 290 const ClauseTy *node) { 291 auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(item.t); 292 addClauseSymsToMap(objects, node); 293 for (auto &object : objects) { 294 if (auto base = helper.getBaseObject(object)) 295 mapBases.insert(base->id()); 296 } 297 } 298 299 template <typename C, typename H> 300 template <typename U> 301 void ConstructDecompositionT<C, H>::addClauseSymsToMap( 302 const std::optional<U> &item, const ClauseTy *node) { 303 if (item) 304 addClauseSymsToMap(*item, node); 305 } 306 307 template <typename C, typename H> 308 template <typename U> 309 void ConstructDecompositionT<C, H>::addClauseSymsToMap( 310 const tomp::ListT<U> &item, const ClauseTy *node) { 311 for (auto &s : item) 312 addClauseSymsToMap(s, node); 313 } 314 315 template <typename C, typename H> 316 template <typename... U, size_t... Is> 317 void ConstructDecompositionT<C, H>::addClauseSymsToMap( 318 const std::tuple<U...> &item, const ClauseTy *node, 319 std::index_sequence<Is...>) { 320 (void)node; // Silence strange warning from GCC. 321 (addClauseSymsToMap(std::get<Is>(item), node), ...); 322 } 323 324 template <typename C, typename H> 325 template <typename U> 326 std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>, void> 327 ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item, 328 const ClauseTy *node) { 329 // Nothing to do for enums. 330 } 331 332 template <typename C, typename H> 333 template <typename U> 334 std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value, void> 335 ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item, 336 const ClauseTy *node) { 337 // Nothing to do for an empty class. 338 } 339 340 template <typename C, typename H> 341 template <typename U> 342 std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value, void> 343 ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item, 344 const ClauseTy *node) { 345 // Nothing to do for an incomplete class (they're empty). 346 } 347 348 template <typename C, typename H> 349 template <typename U> 350 std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value, void> 351 ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item, 352 const ClauseTy *node) { 353 addClauseSymsToMap(item.v, node); 354 } 355 356 template <typename C, typename H> 357 template <typename U> 358 std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value, void> 359 ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item, 360 const ClauseTy *node) { 361 constexpr size_t tuple_size = 362 std::tuple_size_v<llvm::remove_cvref_t<decltype(item.t)>>; 363 addClauseSymsToMap(item.t, node, std::make_index_sequence<tuple_size>{}); 364 } 365 366 template <typename C, typename H> 367 template <typename U> 368 std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value, void> 369 ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item, 370 const ClauseTy *node) { 371 std::visit([&](auto &&s) { addClauseSymsToMap(s, node); }, item.u); 372 } 373 374 // Apply a clause to the only directive that allows it. If there are no 375 // directives that allow it, or if there is more that one, do not apply 376 // anything and return false, otherwise return true. 377 template <typename C, typename H> 378 bool ConstructDecompositionT<C, H>::applyToUnique(const ClauseTy *node) { 379 auto unique = detail::find_unique(leafs, [=](const auto &leaf) { 380 return llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version); 381 }); 382 383 if (unique != leafs.end()) { 384 unique->clauses.push_back(node); 385 return true; 386 } 387 return false; 388 } 389 390 // Apply a clause to the first directive in given range that allows it. 391 // If such a directive does not exist, return false, otherwise return true. 392 template <typename C, typename H> 393 template <typename Iterator> 394 bool ConstructDecompositionT<C, H>::applyToFirst( 395 const ClauseTy *node, llvm::iterator_range<Iterator> range) { 396 if (range.empty()) 397 return false; 398 399 for (auto &leaf : range) { 400 if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version)) 401 continue; 402 leaf.clauses.push_back(node); 403 return true; 404 } 405 return false; 406 } 407 408 // Apply a clause to the innermost directive that allows it. If such a 409 // directive does not exist, return false, otherwise return true. 410 template <typename C, typename H> 411 bool ConstructDecompositionT<C, H>::applyToInnermost(const ClauseTy *node) { 412 return applyToFirst(node, llvm::reverse(leafs)); 413 } 414 415 // Apply a clause to the outermost directive that allows it. If such a 416 // directive does not exist, return false, otherwise return true. 417 template <typename C, typename H> 418 bool ConstructDecompositionT<C, H>::applyToOutermost(const ClauseTy *node) { 419 return applyToFirst(node, llvm::iterator_range(leafs)); 420 } 421 422 template <typename C, typename H> 423 template <typename Predicate> 424 bool ConstructDecompositionT<C, H>::applyIf(const ClauseTy *node, 425 Predicate shouldApply) { 426 bool applied = false; 427 for (auto &leaf : leafs) { 428 if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version)) 429 continue; 430 if (!shouldApply(leaf)) 431 continue; 432 leaf.clauses.push_back(node); 433 applied = true; 434 } 435 436 return applied; 437 } 438 439 template <typename C, typename H> 440 bool ConstructDecompositionT<C, H>::applyToAll(const ClauseTy *node) { 441 return applyIf(node, [](auto) { return true; }); 442 } 443 444 template <typename C, typename H> 445 template <typename Specific> 446 bool ConstructDecompositionT<C, H>::applyClause(Specific &&specific, 447 const ClauseTy *node) { 448 // The default behavior is to find the unique directive to which the 449 // given clause may be applied. If there are no such directives, or 450 // if there are multiple ones, flag an error. 451 // From "OpenMP Application Programming Interface", Version 5.2: 452 // S Some clauses are permitted only on a single leaf construct of the 453 // S combined or composite construct, in which case the effect is as if 454 // S the clause is applied to that specific construct. (p339, 31-33) 455 if (applyToUnique(node)) 456 return true; 457 458 return false; 459 } 460 461 // COLLAPSE 462 // [5.2:93:20-21] 463 // Directives: distribute, do, for, loop, simd, taskloop 464 // 465 // [5.2:339:35] 466 // (35) The collapse clause is applied once to the combined or composite 467 // construct. 468 template <typename C, typename H> 469 bool ConstructDecompositionT<C, H>::applyClause( 470 const tomp::clause::CollapseT<TypeTy, IdTy, ExprTy> &clause, 471 const ClauseTy *node) { 472 // Apply "collapse" to the innermost directive. If it's not one that 473 // allows it flag an error. 474 if (!leafs.empty()) { 475 auto &last = leafs.back(); 476 477 if (llvm::omp::isAllowedClauseForDirective(last.id, node->id, version)) { 478 last.clauses.push_back(node); 479 return true; 480 } 481 } 482 483 return false; 484 } 485 486 // PRIVATE 487 // [5.2:111:5-7] 488 // Directives: distribute, do, for, loop, parallel, scope, sections, simd, 489 // single, target, task, taskloop, teams 490 // 491 // [5.2:340:1-2] 492 // (1) The effect of the 1 private clause is as if it is applied only to the 493 // innermost leaf construct that permits it. 494 template <typename C, typename H> 495 bool ConstructDecompositionT<C, H>::applyClause( 496 const tomp::clause::PrivateT<TypeTy, IdTy, ExprTy> &clause, 497 const ClauseTy *node) { 498 return applyToInnermost(node); 499 } 500 501 // FIRSTPRIVATE 502 // [5.2:112:5-7] 503 // Directives: distribute, do, for, parallel, scope, sections, single, target, 504 // task, taskloop, teams 505 // 506 // [5.2:340:3-20] 507 // (3) The effect of the firstprivate clause is as if it is applied to one or 508 // more leaf constructs as follows: 509 // (5) To the distribute construct if it is among the constituent constructs; 510 // (6) To the teams construct if it is among the constituent constructs and the 511 // distribute construct is not; 512 // (8) To a worksharing construct that accepts the clause if one is among the 513 // constituent constructs; 514 // (9) To the taskloop construct if it is among the constituent constructs; 515 // (10) To the parallel construct if it is among the constituent constructs and 516 // neither a taskloop construct nor a worksharing construct that accepts 517 // the clause is among them; 518 // (12) To the target construct if it is among the constituent constructs and 519 // the same list item neither appears in a lastprivate clause nor is the 520 // base variable or base pointer of a list item that appears in a map 521 // clause. 522 // 523 // (15) If the parallel construct is among the constituent constructs and the 524 // effect is not as if the firstprivate clause is applied to it by the above 525 // rules, then the effect is as if the shared clause with the same list item is 526 // applied to the parallel construct. 527 // (17) If the teams construct is among the constituent constructs and the 528 // effect is not as if the firstprivate clause is applied to it by the above 529 // rules, then the effect is as if the shared clause with the same list item is 530 // applied to the teams construct. 531 template <typename C, typename H> 532 bool ConstructDecompositionT<C, H>::applyClause( 533 const tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy> &clause, 534 const ClauseTy *node) { 535 bool applied = false; 536 537 // [5.2:340:3-6] 538 auto dirDistribute = findDirective(llvm::omp::OMPD_distribute); 539 auto dirTeams = findDirective(llvm::omp::OMPD_teams); 540 if (dirDistribute != nullptr) { 541 dirDistribute->clauses.push_back(node); 542 applied = true; 543 // [5.2:340:17] 544 if (dirTeams != nullptr) { 545 auto *shared = makeClause( 546 llvm::omp::Clause::OMPC_shared, 547 tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/clause.v}); 548 dirTeams->clauses.push_back(shared); 549 } 550 } else if (dirTeams != nullptr) { 551 dirTeams->clauses.push_back(node); 552 applied = true; 553 } 554 555 // [5.2:340:8] 556 auto findWorksharing = [&]() { 557 auto worksharing = getWorksharing(); 558 for (auto &leaf : leafs) { 559 auto found = llvm::find(worksharing, leaf.id); 560 if (found != std::end(worksharing)) 561 return &leaf; 562 } 563 return static_cast<typename decltype(leafs)::value_type *>(nullptr); 564 }; 565 566 auto dirWorksharing = findWorksharing(); 567 if (dirWorksharing != nullptr) { 568 dirWorksharing->clauses.push_back(node); 569 applied = true; 570 } 571 572 // [5.2:340:9] 573 auto dirTaskloop = findDirective(llvm::omp::OMPD_taskloop); 574 if (dirTaskloop != nullptr) { 575 dirTaskloop->clauses.push_back(node); 576 applied = true; 577 } 578 579 // [5.2:340:10] 580 auto dirParallel = findDirective(llvm::omp::OMPD_parallel); 581 if (dirParallel != nullptr) { 582 if (dirTaskloop == nullptr && dirWorksharing == nullptr) { 583 dirParallel->clauses.push_back(node); 584 applied = true; 585 } else { 586 // [5.2:340:15] 587 auto *shared = makeClause( 588 llvm::omp::Clause::OMPC_shared, 589 tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/clause.v}); 590 dirParallel->clauses.push_back(shared); 591 } 592 } 593 594 // [5.2:340:12] 595 auto inLastprivate = [&](const ObjectTy &object) { 596 if (ClauseSet *set = findClausesWith(object)) { 597 return llvm::find_if(*set, [](const ClauseTy *c) { 598 return c->id == llvm::omp::Clause::OMPC_lastprivate; 599 }) != set->end(); 600 } 601 return false; 602 }; 603 604 auto dirTarget = findDirective(llvm::omp::OMPD_target); 605 if (dirTarget != nullptr) { 606 tomp::ObjectListT<IdTy, ExprTy> objects; 607 llvm::copy_if( 608 clause.v, std::back_inserter(objects), [&](const ObjectTy &object) { 609 return !inLastprivate(object) && !mapBases.count(object.id()); 610 }); 611 if (!objects.empty()) { 612 auto *firstp = makeClause( 613 llvm::omp::Clause::OMPC_firstprivate, 614 tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>{/*List=*/objects}); 615 dirTarget->clauses.push_back(firstp); 616 applied = true; 617 } 618 } 619 620 // "task" is not handled by any of the cases above. 621 if (auto dirTask = findDirective(llvm::omp::OMPD_task)) { 622 dirTask->clauses.push_back(node); 623 applied = true; 624 } 625 626 return applied; 627 } 628 629 // LASTPRIVATE 630 // [5.2:115:7-8] 631 // Directives: distribute, do, for, loop, sections, simd, taskloop 632 // 633 // [5.2:340:21-30] 634 // (21) The effect of the lastprivate clause is as if it is applied to all leaf 635 // constructs that permit the clause. 636 // (22) If the parallel construct is among the constituent constructs and the 637 // list item is not also specified in the firstprivate clause, then the effect 638 // of the lastprivate clause is as if the shared clause with the same list item 639 // is applied to the parallel construct. 640 // (24) If the teams construct is among the constituent constructs and the list 641 // item is not also specified in the firstprivate clause, then the effect of the 642 // lastprivate clause is as if the shared clause with the same list item is 643 // applied to the teams construct. 644 // (27) If the target construct is among the constituent constructs and the list 645 // item is not the base variable or base pointer of a list item that appears in 646 // a map clause, the effect of the lastprivate clause is as if the same list 647 // item appears in a map clause with a map-type of tofrom. 648 template <typename C, typename H> 649 bool ConstructDecompositionT<C, H>::applyClause( 650 const tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy> &clause, 651 const ClauseTy *node) { 652 bool applied = false; 653 654 // [5.2:340:21] 655 applied = applyToAll(node); 656 if (!applied) 657 return false; 658 659 auto inFirstprivate = [&](const ObjectTy &object) { 660 if (ClauseSet *set = findClausesWith(object)) { 661 return llvm::find_if(*set, [](const ClauseTy *c) { 662 return c->id == llvm::omp::Clause::OMPC_firstprivate; 663 }) != set->end(); 664 } 665 return false; 666 }; 667 668 auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.t); 669 670 // Prepare list of objects that could end up in a "shared" clause. 671 tomp::ObjectListT<IdTy, ExprTy> sharedObjects; 672 llvm::copy_if( 673 objects, std::back_inserter(sharedObjects), 674 [&](const ObjectTy &object) { return !inFirstprivate(object); }); 675 676 if (!sharedObjects.empty()) { 677 // [5.2:340:22] 678 if (auto dirParallel = findDirective(llvm::omp::OMPD_parallel)) { 679 auto *shared = makeClause( 680 llvm::omp::Clause::OMPC_shared, 681 tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects}); 682 dirParallel->clauses.push_back(shared); 683 applied = true; 684 } 685 686 // [5.2:340:24] 687 if (auto dirTeams = findDirective(llvm::omp::OMPD_teams)) { 688 auto *shared = makeClause( 689 llvm::omp::Clause::OMPC_shared, 690 tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects}); 691 dirTeams->clauses.push_back(shared); 692 applied = true; 693 } 694 } 695 696 // [5.2:340:27] 697 if (auto dirTarget = findDirective(llvm::omp::OMPD_target)) { 698 tomp::ObjectListT<IdTy, ExprTy> tofrom; 699 llvm::copy_if( 700 objects, std::back_inserter(tofrom), 701 [&](const ObjectTy &object) { return !mapBases.count(object.id()); }); 702 703 if (!tofrom.empty()) { 704 using MapType = 705 typename tomp::clause::MapT<TypeTy, IdTy, ExprTy>::MapType; 706 auto *map = 707 makeClause(llvm::omp::Clause::OMPC_map, 708 tomp::clause::MapT<TypeTy, IdTy, ExprTy>{ 709 {/*MapType=*/MapType::Tofrom, 710 /*MapTypeModifier=*/std::nullopt, 711 /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt, 712 /*LocatorList=*/std::move(tofrom)}}); 713 dirTarget->clauses.push_back(map); 714 applied = true; 715 } 716 } 717 718 return applied; 719 } 720 721 // SHARED 722 // [5.2:110:5-6] 723 // Directives: parallel, task, taskloop, teams 724 // 725 // [5.2:340:31-32] 726 // (31) The effect of the shared, default, thread_limit, or order clause is as 727 // if it is applied to all leaf constructs that permit the clause. 728 template <typename C, typename H> 729 bool ConstructDecompositionT<C, H>::applyClause( 730 const tomp::clause::SharedT<TypeTy, IdTy, ExprTy> &clause, 731 const ClauseTy *node) { 732 // [5.2:340:31] 733 return applyToAll(node); 734 } 735 736 // DEFAULT 737 // [5.2:109:5-6] 738 // Directives: parallel, task, taskloop, teams 739 // 740 // [5.2:340:31-32] 741 // (31) The effect of the shared, default, thread_limit, or order clause is as 742 // if it is applied to all leaf constructs that permit the clause. 743 template <typename C, typename H> 744 bool ConstructDecompositionT<C, H>::applyClause( 745 const tomp::clause::DefaultT<TypeTy, IdTy, ExprTy> &clause, 746 const ClauseTy *node) { 747 // [5.2:340:31] 748 return applyToAll(node); 749 } 750 751 // THREAD_LIMIT 752 // [5.2:277:14-15] 753 // Directives: target, teams 754 // 755 // [5.2:340:31-32] 756 // (31) The effect of the shared, default, thread_limit, or order clause is as 757 // if it is applied to all leaf constructs that permit the clause. 758 template <typename C, typename H> 759 bool ConstructDecompositionT<C, H>::applyClause( 760 const tomp::clause::ThreadLimitT<TypeTy, IdTy, ExprTy> &clause, 761 const ClauseTy *node) { 762 // [5.2:340:31] 763 return applyToAll(node); 764 } 765 766 // ORDER 767 // [5.2:234:3-4] 768 // Directives: distribute, do, for, loop, simd 769 // 770 // [5.2:340:31-32] 771 // (31) The effect of the shared, default, thread_limit, or order clause is as 772 // if it is applied to all leaf constructs that permit the clause. 773 template <typename C, typename H> 774 bool ConstructDecompositionT<C, H>::applyClause( 775 const tomp::clause::OrderT<TypeTy, IdTy, ExprTy> &clause, 776 const ClauseTy *node) { 777 // [5.2:340:31] 778 return applyToAll(node); 779 } 780 781 // ALLOCATE 782 // [5.2:178:7-9] 783 // Directives: allocators, distribute, do, for, parallel, scope, sections, 784 // single, target, task, taskgroup, taskloop, teams 785 // 786 // [5.2:340:33-35] 787 // (33) The effect of the allocate clause is as if it is applied to all leaf 788 // constructs that permit the clause and to which a data-sharing attribute 789 // clause that may create a private copy of the same list item is applied. 790 template <typename C, typename H> 791 bool ConstructDecompositionT<C, H>::applyClause( 792 const tomp::clause::AllocateT<TypeTy, IdTy, ExprTy> &clause, 793 const ClauseTy *node) { 794 // This one needs to be applied at the end, once we know which clauses are 795 // assigned to which leaf constructs. 796 797 // [5.2:340:33] 798 auto canMakePrivateCopy = [](llvm::omp::Clause id) { 799 switch (id) { 800 // Clauses with "privatization" property: 801 case llvm::omp::Clause::OMPC_firstprivate: 802 case llvm::omp::Clause::OMPC_in_reduction: 803 case llvm::omp::Clause::OMPC_lastprivate: 804 case llvm::omp::Clause::OMPC_linear: 805 case llvm::omp::Clause::OMPC_private: 806 case llvm::omp::Clause::OMPC_reduction: 807 case llvm::omp::Clause::OMPC_task_reduction: 808 return true; 809 default: 810 return false; 811 } 812 }; 813 814 bool applied = applyIf(node, [&](const auto &leaf) { 815 return llvm::any_of(leaf.clauses, [&](const ClauseTy *n) { 816 return canMakePrivateCopy(n->id); 817 }); 818 }); 819 820 return applied; 821 } 822 823 // REDUCTION 824 // [5.2:134:17-18] 825 // Directives: do, for, loop, parallel, scope, sections, simd, taskloop, teams 826 // 827 // [5.2:340:36-37], [5.2:341:1-13] 828 // (36) The effect of the reduction clause is as if it is applied to all leaf 829 // constructs that permit the clause, except for the following constructs: 830 // (1) The parallel construct, when combined with the sections, 831 // worksharing-loop, loop, or taskloop construct; and 832 // (3) The teams construct, when combined with the loop construct. 833 // (4) For the parallel and teams constructs above, the effect of the reduction 834 // clause instead is as if each list item or, for any list item that is an array 835 // item, its corresponding base array or base pointer appears in a shared clause 836 // for the construct. 837 // (6) If the task reduction-modifier is specified, the effect is as if it only 838 // modifies the behavior of the reduction clause on the innermost leaf construct 839 // that accepts the modifier (see Section 5.5.8). 840 // (8) If the inscan reduction-modifier is specified, the effect is as if it 841 // modifies the behavior of the reduction clause on all constructs of the 842 // combined construct to which the clause is applied and that accept the 843 // modifier. 844 // (10) If a list item in a reduction clause on a combined target construct does 845 // not have the same base variable or base pointer as a list item in a map 846 // clause on the construct, then the effect is as if the list item in the 847 // reduction clause appears as a list item in a map clause with a map-type of 848 // tofrom. 849 template <typename C, typename H> 850 bool ConstructDecompositionT<C, H>::applyClause( 851 const tomp::clause::ReductionT<TypeTy, IdTy, ExprTy> &clause, 852 const ClauseTy *node) { 853 using ReductionTy = tomp::clause::ReductionT<TypeTy, IdTy, ExprTy>; 854 855 // [5.2:340:36], [5.2:341:1], [5.2:341:3] 856 bool applyToParallel = true, applyToTeams = true; 857 858 auto dirParallel = findDirective(llvm::omp::Directive::OMPD_parallel); 859 if (dirParallel) { 860 auto exclusions = llvm::concat<const llvm::omp::Directive>( 861 getWorksharingLoop(), tomp::ListT<llvm::omp::Directive>{ 862 llvm::omp::Directive::OMPD_loop, 863 llvm::omp::Directive::OMPD_sections, 864 llvm::omp::Directive::OMPD_taskloop, 865 }); 866 auto present = [&](llvm::omp::Directive id) { 867 return findDirective(id) != nullptr; 868 }; 869 870 if (llvm::any_of(exclusions, present)) 871 applyToParallel = false; 872 } 873 874 auto dirTeams = findDirective(llvm::omp::Directive::OMPD_teams); 875 if (dirTeams) { 876 // The only exclusion is OMPD_loop. 877 if (findDirective(llvm::omp::Directive::OMPD_loop)) 878 applyToTeams = false; 879 } 880 881 using ReductionModifier = typename ReductionTy::ReductionModifier; 882 using ReductionIdentifiers = typename ReductionTy::ReductionIdentifiers; 883 884 auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.t); 885 auto &modifier = std::get<std::optional<ReductionModifier>>(clause.t); 886 887 // Apply the reduction clause first to all directives according to the spec. 888 // If the reduction was applied at least once, proceed with the data sharing 889 // side-effects. 890 bool applied = false; 891 892 // [5.2:341:6], [5.2:341:8] 893 auto isValidModifier = [](llvm::omp::Directive dir, ReductionModifier mod, 894 bool alreadyApplied) { 895 switch (mod) { 896 case ReductionModifier::Inscan: 897 // According to [5.2:135:11-13], "inscan" only applies to 898 // worksharing-loop, worksharing-loop-simd, or "simd" constructs. 899 return dir == llvm::omp::Directive::OMPD_simd || 900 llvm::is_contained(getWorksharingLoop(), dir); 901 case ReductionModifier::Task: 902 if (alreadyApplied) 903 return false; 904 // According to [5.2:135:16-18], "task" only applies to "parallel" and 905 // worksharing constructs. 906 return dir == llvm::omp::Directive::OMPD_parallel || 907 llvm::is_contained(getWorksharing(), dir); 908 case ReductionModifier::Default: 909 return true; 910 } 911 llvm_unreachable("Unexpected modifier"); 912 }; 913 914 auto *unmodified = makeClause( 915 llvm::omp::Clause::OMPC_reduction, 916 ReductionTy{ 917 {/*ReductionModifier=*/std::nullopt, 918 /*ReductionIdentifiers=*/std::get<ReductionIdentifiers>(clause.t), 919 /*List=*/objects}}); 920 921 ReductionModifier effective = modifier.value_or(ReductionModifier::Default); 922 bool effectiveApplied = false; 923 // Walk over the leaf constructs starting from the innermost, and apply 924 // the clause as required by the spec. 925 for (auto &leaf : llvm::reverse(leafs)) { 926 if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version)) 927 continue; 928 if (!applyToParallel && &leaf == dirParallel) 929 continue; 930 if (!applyToTeams && &leaf == dirTeams) 931 continue; 932 // Some form of the clause will be applied past this point. 933 if (isValidModifier(leaf.id, effective, effectiveApplied)) { 934 // Apply clause with modifier. 935 leaf.clauses.push_back(node); 936 effectiveApplied = true; 937 } else { 938 // Apply clause without modifier. 939 leaf.clauses.push_back(unmodified); 940 } 941 // The modifier must be applied to some construct. 942 applied = effectiveApplied; 943 } 944 945 if (!applied) 946 return false; 947 948 tomp::ObjectListT<IdTy, ExprTy> sharedObjects; 949 llvm::transform(objects, std::back_inserter(sharedObjects), 950 [&](const ObjectTy &object) { 951 auto maybeBase = helper.getBaseObject(object); 952 return maybeBase ? *maybeBase : object; 953 }); 954 955 // [5.2:341:4] 956 if (!sharedObjects.empty()) { 957 if (dirParallel && !applyToParallel) { 958 auto *shared = makeClause( 959 llvm::omp::Clause::OMPC_shared, 960 tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects}); 961 dirParallel->clauses.push_back(shared); 962 } 963 if (dirTeams && !applyToTeams) { 964 auto *shared = makeClause( 965 llvm::omp::Clause::OMPC_shared, 966 tomp::clause::SharedT<TypeTy, IdTy, ExprTy>{/*List=*/sharedObjects}); 967 dirTeams->clauses.push_back(shared); 968 } 969 } 970 971 // [5.2:341:10] 972 auto dirTarget = findDirective(llvm::omp::Directive::OMPD_target); 973 if (dirTarget && leafs.size() > 1) { 974 tomp::ObjectListT<IdTy, ExprTy> tofrom; 975 llvm::copy_if(objects, std::back_inserter(tofrom), 976 [&](const ObjectTy &object) { 977 if (auto maybeBase = helper.getBaseObject(object)) 978 return !mapBases.count(maybeBase->id()); 979 return !mapBases.count(object.id()); // XXX is this ok? 980 }); 981 if (!tofrom.empty()) { 982 using MapType = 983 typename tomp::clause::MapT<TypeTy, IdTy, ExprTy>::MapType; 984 auto *map = makeClause( 985 llvm::omp::Clause::OMPC_map, 986 tomp::clause::MapT<TypeTy, IdTy, ExprTy>{ 987 {/*MapType=*/MapType::Tofrom, /*MapTypeModifier=*/std::nullopt, 988 /*Mapper=*/std::nullopt, /*Iterator=*/std::nullopt, 989 /*LocatorList=*/std::move(tofrom)}}); 990 991 dirTarget->clauses.push_back(map); 992 applied = true; 993 } 994 } 995 996 return applied; 997 } 998 999 // IF 1000 // [5.2:72:7-9] 1001 // Directives: cancel, parallel, simd, target, target data, target enter data, 1002 // target exit data, target update, task, taskloop 1003 // 1004 // [5.2:72:15-18] 1005 // (15) For combined or composite constructs, the if clause only applies to the 1006 // semantics of the construct named in the directive-name-modifier. 1007 // (16) For a combined or composite construct, if no directive-name-modifier is 1008 // specified then the if clause applies to all constituent constructs to which 1009 // an if clause can apply. 1010 template <typename C, typename H> 1011 bool ConstructDecompositionT<C, H>::applyClause( 1012 const tomp::clause::IfT<TypeTy, IdTy, ExprTy> &clause, 1013 const ClauseTy *node) { 1014 using DirectiveNameModifier = 1015 typename clause::IfT<TypeTy, IdTy, ExprTy>::DirectiveNameModifier; 1016 using IfExpression = typename clause::IfT<TypeTy, IdTy, ExprTy>::IfExpression; 1017 auto &modifier = std::get<std::optional<DirectiveNameModifier>>(clause.t); 1018 1019 if (modifier) { 1020 llvm::omp::Directive dirId = *modifier; 1021 auto *unmodified = 1022 makeClause(llvm::omp::Clause::OMPC_if, 1023 tomp::clause::IfT<TypeTy, IdTy, ExprTy>{ 1024 {/*DirectiveNameModifier=*/std::nullopt, 1025 /*IfExpression=*/std::get<IfExpression>(clause.t)}}); 1026 1027 if (auto *hasDir = findDirective(dirId)) { 1028 hasDir->clauses.push_back(unmodified); 1029 return true; 1030 } 1031 return false; 1032 } 1033 1034 return applyToAll(node); 1035 } 1036 1037 // LINEAR 1038 // [5.2:118:1-2] 1039 // Directives: declare simd, do, for, simd 1040 // 1041 // [5.2:341:15-22] 1042 // (15.1) The effect of the linear clause is as if it is applied to the 1043 // innermost leaf construct. 1044 // (15.2) Additionally, if the list item is not the iteration variable of a simd 1045 // or worksharing-loop SIMD construct, the effect on the outer leaf constructs 1046 // is as if the list item was specified in firstprivate and lastprivate clauses 1047 // on the combined or composite construct, with the rules specified above 1048 // applied. 1049 // (19) If a list item of the linear clause is the iteration variable of a simd 1050 // or worksharing-loop SIMD construct and it is not declared in the construct, 1051 // the effect on the outer leaf constructs is as if the list item was specified 1052 // in a lastprivate clause on the combined or composite construct with the rules 1053 // specified above applied. 1054 template <typename C, typename H> 1055 bool ConstructDecompositionT<C, H>::applyClause( 1056 const tomp::clause::LinearT<TypeTy, IdTy, ExprTy> &clause, 1057 const ClauseTy *node) { 1058 // [5.2:341:15.1] 1059 if (!applyToInnermost(node)) 1060 return false; 1061 1062 // [5.2:341:15.2], [5.2:341:19] 1063 auto dirSimd = findDirective(llvm::omp::Directive::OMPD_simd); 1064 std::optional<ObjectTy> iterVar = helper.getLoopIterVar(); 1065 const auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.t); 1066 1067 // Lists of objects that will be used to construct "firstprivate" and 1068 // "lastprivate" clauses. 1069 tomp::ObjectListT<IdTy, ExprTy> first, last; 1070 1071 for (const ObjectTy &object : objects) { 1072 last.push_back(object); 1073 if (!dirSimd || !iterVar || object.id() != iterVar->id()) 1074 first.push_back(object); 1075 } 1076 1077 if (!first.empty()) { 1078 auto *firstp = makeClause( 1079 llvm::omp::Clause::OMPC_firstprivate, 1080 tomp::clause::FirstprivateT<TypeTy, IdTy, ExprTy>{/*List=*/first}); 1081 nodes.push_back(firstp); // Appending to the main clause list. 1082 } 1083 if (!last.empty()) { 1084 auto *lastp = 1085 makeClause(llvm::omp::Clause::OMPC_lastprivate, 1086 tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy>{ 1087 {/*LastprivateModifier=*/std::nullopt, /*List=*/last}}); 1088 nodes.push_back(lastp); // Appending to the main clause list. 1089 } 1090 return true; 1091 } 1092 1093 // NOWAIT 1094 // [5.2:308:11-13] 1095 // Directives: dispatch, do, for, interop, scope, sections, single, target, 1096 // target enter data, target exit data, target update, taskwait, workshare 1097 // 1098 // [5.2:341:23] 1099 // (23) The effect of the nowait clause is as if it is applied to the outermost 1100 // leaf construct that permits it. 1101 template <typename C, typename H> 1102 bool ConstructDecompositionT<C, H>::applyClause( 1103 const tomp::clause::NowaitT<TypeTy, IdTy, ExprTy> &clause, 1104 const ClauseTy *node) { 1105 return applyToOutermost(node); 1106 } 1107 1108 template <typename C, typename H> 1109 bool ConstructDecompositionT<C, H>::applyClause( 1110 const tomp::clause::OmpxBareT<TypeTy, IdTy, ExprTy> &clause, 1111 const ClauseTy *node) { 1112 return applyToOutermost(node); 1113 } 1114 1115 template <typename C, typename H> 1116 bool ConstructDecompositionT<C, H>::applyClause( 1117 const tomp::clause::OmpxAttributeT<TypeTy, IdTy, ExprTy> &clause, 1118 const ClauseTy *node) { 1119 return applyToAll(node); 1120 } 1121 1122 template <typename C, typename H> bool ConstructDecompositionT<C, H>::split() { 1123 bool success = true; 1124 1125 auto isImplicit = [this](const ClauseTy *node) { 1126 return llvm::any_of( 1127 implicit, [node](const ClauseTy &clause) { return &clause == node; }); 1128 }; 1129 1130 for (llvm::omp::Directive leaf : 1131 llvm::omp::getLeafConstructsOrSelf(construct)) 1132 leafs.push_back(LeafReprInternal{leaf, /*clauses=*/{}}); 1133 1134 for (const ClauseTy *node : nodes) 1135 addClauseSymsToMap(*node, node); 1136 1137 // First we need to apply LINEAR, because it can generate additional 1138 // "firstprivate" and "lastprivate" clauses that apply to the combined/ 1139 // composite construct. 1140 // Collect them separately, because they may modify the clause list. 1141 llvm::SmallVector<const ClauseTy *> linears; 1142 for (const ClauseTy *node : nodes) { 1143 if (node->id == llvm::omp::Clause::OMPC_linear) 1144 linears.push_back(node); 1145 } 1146 for (const auto *node : linears) { 1147 success = success && 1148 applyClause(std::get<tomp::clause::LinearT<TypeTy, IdTy, ExprTy>>( 1149 node->u), 1150 node); 1151 } 1152 1153 // "allocate" clauses need to be applied last since they need to see 1154 // which directives have data-privatizing clauses. 1155 auto skip = [](const ClauseTy *node) { 1156 switch (node->id) { 1157 case llvm::omp::Clause::OMPC_allocate: 1158 case llvm::omp::Clause::OMPC_linear: 1159 return true; 1160 default: 1161 return false; 1162 } 1163 }; 1164 1165 // Apply (almost) all clauses. 1166 for (const ClauseTy *node : nodes) { 1167 if (skip(node)) 1168 continue; 1169 bool result = 1170 std::visit([&](auto &&s) { return applyClause(s, node); }, node->u); 1171 if (!isImplicit(node)) 1172 success = success && result; 1173 } 1174 1175 // Apply "allocate". 1176 for (const ClauseTy *node : nodes) { 1177 if (node->id != llvm::omp::Clause::OMPC_allocate) 1178 continue; 1179 success = 1180 success && 1181 std::visit([&](auto &&s) { return applyClause(s, node); }, node->u); 1182 } 1183 1184 return success; 1185 } 1186 1187 } // namespace tomp 1188 1189 #endif // LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H 1190