18#ifndef LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H
19#define LLVM_FRONTEND_OPENMP_CONSTRUCTDECOMPOSITIONT_H
33#include <unordered_map>
34#include <unordered_set>
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,
48 static llvm::omp::Directive worksharingLoop[] = {
49 llvm::omp::Directive::OMPD_do,
50 llvm::omp::Directive::OMPD_for,
52 return worksharingLoop;
56template <
typename Container,
typename Predicate>
57typename std::remove_reference_t<Container>::iterator
59 auto first = std::find_if(container.begin(), container.end(),
pred);
60 if (first == container.end())
62 auto second = std::find_if(std::next(first), container.end(),
pred);
63 if (second == container.end())
65 return container.end();
82template <
typename ClauseType,
typename HelperType>
86 using TypeTy =
typename ClauseTy::TypeTy;
87 using IdTy =
typename ClauseTy::IdTy;
88 using ExprTy =
typename ClauseTy::ExprTy;
92 using ClauseSet = std::unordered_set<const ClauseTy *>;
95 llvm::omp::Directive dir,
97 : version(ver), construct(dir), helper(helper) {
99 nodes.push_back(&clause);
101 bool success = split();
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);
122 struct LeafReprInternal {
123 llvm::omp::Directive
id = llvm::omp::Directive::OMPD_unknown;
127 LeafReprInternal *findDirective(llvm::omp::Directive dirId) {
129 leafs, [&](
const LeafReprInternal &leaf) {
return leaf.id == dirId; });
130 return found != leafs.end() ? &*found :
nullptr;
134 if (
auto found = syms.find(
object.id()); found != syms.end())
135 return &found->second;
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();
153 template <
typename U>
154 void addClauseSymsToMap(
const std::optional<U> &item,
const ClauseTy *);
155 template <
typename U>
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 *);
164 template <
typename U>
165 std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value,
void>
166 addClauseSymsToMap(U &&item,
const ClauseTy *);
168 template <
typename U>
169 std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value,
void>
170 addClauseSymsToMap(U &&item,
const ClauseTy *);
172 template <
typename U>
173 std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value,
void>
174 addClauseSymsToMap(U &&item,
const ClauseTy *);
176 template <
typename U>
177 std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value,
void>
178 addClauseSymsToMap(U &&item,
const ClauseTy *);
180 template <
typename U>
181 std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value,
void>
182 addClauseSymsToMap(U &&item,
const ClauseTy *);
187 bool applyToUnique(
const ClauseTy *node);
191 template <
typename Iterator>
196 bool applyToInnermost(
const ClauseTy *node);
200 bool applyToOutermost(
const ClauseTy *node);
202 template <
typename Predicate>
205 bool applyToAll(
const ClauseTy *node);
207 template <
typename Clause>
208 bool applyClause(Clause &&clause,
const ClauseTy *node);
244 llvm::omp::Directive construct;
246 ListT<LeafReprInternal> leafs;
248 std::list<ClauseTy> implicit;
250 std::unordered_map<IdTy, ClauseSet> syms;
251 std::unordered_set<IdTy> mapBases;
255template <
typename ClauseType,
typename HelperType>
260template <
typename C,
typename H>
262 const ClauseTy *node) {
263 syms[
object.id()].insert(node);
266template <
typename C,
typename H>
267void ConstructDecompositionT<C, H>::addClauseSymsToMap(
269 for (
auto &
object : objects)
270 syms[
object.id()].insert(node);
273template <
typename C,
typename H>
274void ConstructDecompositionT<C, H>::addClauseSymsToMap(
const TypeTy &item,
275 const ClauseTy *node) {
279template <
typename C,
typename H>
280void ConstructDecompositionT<C, H>::addClauseSymsToMap(
const ExprTy &item,
281 const ClauseTy *node) {
285template <
typename C,
typename H>
286void ConstructDecompositionT<C, H>::addClauseSymsToMap(
288 const ClauseTy *node) {
289 auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(item.
t);
290 addClauseSymsToMap(objects, node);
291 for (
auto &
object : objects) {
292 if (
auto base = helper.getBaseObject(
object))
293 mapBases.insert(base->id());
297template <
typename C,
typename H>
299void ConstructDecompositionT<C, H>::addClauseSymsToMap(
300 const std::optional<U> &item,
const ClauseTy *node) {
302 addClauseSymsToMap(*item, node);
305template <
typename C,
typename H>
307void ConstructDecompositionT<C, H>::addClauseSymsToMap(
310 addClauseSymsToMap(s, node);
313template <
typename C,
typename H>
314template <
typename...
U,
size_t... Is>
315void ConstructDecompositionT<C, H>::addClauseSymsToMap(
316 const std::tuple<U...> &item,
const ClauseTy *node,
317 std::index_sequence<Is...>) {
319 (addClauseSymsToMap(std::get<Is>(item), node), ...);
322template <
typename C,
typename H>
324std::enable_if_t<std::is_enum_v<llvm::remove_cvref_t<U>>,
void>
325ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
326 const ClauseTy *node) {
330template <
typename C,
typename H>
332std::enable_if_t<llvm::remove_cvref_t<U>::EmptyTrait::value,
void>
333ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
334 const ClauseTy *node) {
338template <
typename C,
typename H>
340std::enable_if_t<llvm::remove_cvref_t<U>::IncompleteTrait::value,
void>
341ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
342 const ClauseTy *node) {
346template <
typename C,
typename H>
348std::enable_if_t<llvm::remove_cvref_t<U>::WrapperTrait::value,
void>
349ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
350 const ClauseTy *node) {
351 addClauseSymsToMap(item.v, node);
354template <
typename C,
typename H>
356std::enable_if_t<llvm::remove_cvref_t<U>::TupleTrait::value,
void>
357ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
358 const ClauseTy *node) {
359 constexpr size_t tuple_size =
361 addClauseSymsToMap(item.t, node, std::make_index_sequence<tuple_size>{});
364template <
typename C,
typename H>
366std::enable_if_t<llvm::remove_cvref_t<U>::UnionTrait::value,
void>
367ConstructDecompositionT<C, H>::addClauseSymsToMap(U &&item,
368 const ClauseTy *node) {
369 std::visit([&](
auto &&s) { addClauseSymsToMap(s, node); }, item.u);
375template <
typename C,
typename H>
376bool ConstructDecompositionT<C, H>::applyToUnique(
const ClauseTy *node) {
378 return llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version);
381 if (unique != leafs.end()) {
382 unique->clauses.push_back(node);
390template <
typename C,
typename H>
391template <
typename Iterator>
392bool ConstructDecompositionT<C, H>::applyToFirst(
397 for (
auto &leaf : range) {
398 if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version))
400 leaf.clauses.push_back(node);
408template <
typename C,
typename H>
409bool ConstructDecompositionT<C, H>::applyToInnermost(
const ClauseTy *node) {
415template <
typename C,
typename H>
416bool ConstructDecompositionT<C, H>::applyToOutermost(
const ClauseTy *node) {
420template <
typename C,
typename H>
421template <
typename Predicate>
422bool ConstructDecompositionT<C, H>::applyIf(
const ClauseTy *node,
424 bool applied =
false;
425 for (
auto &leaf : leafs) {
426 if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version))
430 leaf.clauses.push_back(node);
437template <
typename C,
typename H>
438bool ConstructDecompositionT<C, H>::applyToAll(
const ClauseTy *node) {
439 return applyIf(node, [](
auto) {
return true; });
442template <
typename C,
typename H>
443template <
typename Specific>
453 if (applyToUnique(node))
466template <
typename C,
typename H>
469 const ClauseTy *node) {
472 if (!leafs.empty()) {
473 auto &last = leafs.back();
475 if (llvm::omp::isAllowedClauseForDirective(last.id, node->id, version)) {
476 last.clauses.push_back(node);
492template <
typename C,
typename H>
493bool ConstructDecompositionT<C, H>::applyClause(
495 const ClauseTy *node) {
496 return applyToInnermost(node);
529template <
typename C,
typename H>
530bool ConstructDecompositionT<C, H>::applyClause(
532 const ClauseTy *node) {
533 bool applied =
false;
536 auto dirDistribute = findDirective(llvm::omp::OMPD_distribute);
537 auto dirTeams = findDirective(llvm::omp::OMPD_teams);
538 if (dirDistribute !=
nullptr) {
539 dirDistribute->clauses.push_back(node);
542 if (dirTeams !=
nullptr) {
543 auto *shared = makeClause(
544 llvm::omp::Clause::OMPC_shared,
546 dirTeams->clauses.push_back(shared);
548 }
else if (dirTeams !=
nullptr) {
549 dirTeams->clauses.push_back(node);
554 auto findWorksharing = [&]() {
556 for (
auto &leaf : leafs) {
557 auto found =
llvm::find(worksharing, leaf.id);
558 if (found != std::end(worksharing))
561 return static_cast<typename decltype(leafs)::value_type *
>(
nullptr);
564 auto dirWorksharing = findWorksharing();
565 if (dirWorksharing !=
nullptr) {
566 dirWorksharing->clauses.push_back(node);
571 auto dirTaskloop = findDirective(llvm::omp::OMPD_taskloop);
572 if (dirTaskloop !=
nullptr) {
573 dirTaskloop->clauses.push_back(node);
578 auto dirParallel = findDirective(llvm::omp::OMPD_parallel);
579 if (dirParallel !=
nullptr) {
580 if (dirTaskloop ==
nullptr && dirWorksharing ==
nullptr) {
581 dirParallel->clauses.push_back(node);
585 auto *shared = makeClause(
586 llvm::omp::Clause::OMPC_shared,
588 dirParallel->clauses.push_back(shared);
593 auto inLastprivate = [&](
const ObjectTy &object) {
594 if (ClauseSet *set = findClausesWith(
object)) {
596 return c->id == llvm::omp::Clause::OMPC_lastprivate;
602 auto dirTarget = findDirective(llvm::omp::OMPD_target);
603 if (dirTarget !=
nullptr) {
606 clause.
v, std::back_inserter(objects), [&](
const ObjectTy &
object) {
607 return !inLastprivate(object) && !mapBases.count(object.id());
609 if (!objects.
empty()) {
610 auto *firstp = makeClause(
611 llvm::omp::Clause::OMPC_firstprivate,
613 dirTarget->clauses.push_back(firstp);
619 if (
auto dirTask = findDirective(llvm::omp::OMPD_task)) {
620 dirTask->clauses.push_back(node);
646template <
typename C,
typename H>
647bool ConstructDecompositionT<C, H>::applyClause(
649 const ClauseTy *node) {
650 bool applied =
false;
653 applied = applyToAll(node);
657 auto inFirstprivate = [&](
const ObjectTy &object) {
658 if (ClauseSet *set = findClausesWith(
object)) {
660 return c->id == llvm::omp::Clause::OMPC_firstprivate;
666 auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.
t);
671 objects, std::back_inserter(sharedObjects),
672 [&](
const ObjectTy &
object) {
return !inFirstprivate(
object); });
674 if (!sharedObjects.empty()) {
676 if (
auto dirParallel = findDirective(llvm::omp::OMPD_parallel)) {
677 auto *shared = makeClause(
678 llvm::omp::Clause::OMPC_shared,
680 dirParallel->clauses.push_back(shared);
685 if (
auto dirTeams = findDirective(llvm::omp::OMPD_teams)) {
686 auto *shared = makeClause(
687 llvm::omp::Clause::OMPC_shared,
689 dirTeams->clauses.push_back(shared);
695 if (
auto dirTarget = findDirective(llvm::omp::OMPD_target)) {
698 objects, std::back_inserter(tofrom),
699 [&](
const ObjectTy &
object) {
return !mapBases.count(
object.
id()); });
701 if (!tofrom.
empty()) {
705 makeClause(llvm::omp::Clause::OMPC_map,
709 std::nullopt, std::nullopt,
710 std::move(tofrom)}});
711 dirTarget->clauses.push_back(map);
726template <
typename C,
typename H>
727bool ConstructDecompositionT<C, H>::applyClause(
729 const ClauseTy *node) {
731 return applyToAll(node);
741template <
typename C,
typename H>
742bool ConstructDecompositionT<C, H>::applyClause(
744 const ClauseTy *node) {
746 return applyToAll(node);
756template <
typename C,
typename H>
757bool ConstructDecompositionT<C, H>::applyClause(
759 const ClauseTy *node) {
761 return applyToAll(node);
771template <
typename C,
typename H>
772bool ConstructDecompositionT<C, H>::applyClause(
774 const ClauseTy *node) {
776 return applyToAll(node);
788template <
typename C,
typename H>
789bool ConstructDecompositionT<C, H>::applyClause(
791 const ClauseTy *node) {
796 auto canMakePrivateCopy = [](llvm::omp::Clause id) {
799 case llvm::omp::Clause::OMPC_firstprivate:
800 case llvm::omp::Clause::OMPC_in_reduction:
801 case llvm::omp::Clause::OMPC_lastprivate:
802 case llvm::omp::Clause::OMPC_linear:
803 case llvm::omp::Clause::OMPC_private:
804 case llvm::omp::Clause::OMPC_reduction:
805 case llvm::omp::Clause::OMPC_task_reduction:
812 bool applied = applyIf(node, [&](
const auto &leaf) {
813 return llvm::any_of(leaf.clauses, [&](
const ClauseTy *n) {
814 return canMakePrivateCopy(n->id);
847template <
typename C,
typename H>
848bool ConstructDecompositionT<C, H>::applyClause(
850 const ClauseTy *node) {
854 bool applyToParallel =
true, applyToTeams =
true;
856 auto dirParallel = findDirective(llvm::omp::Directive::OMPD_parallel);
858 auto exclusions = llvm::concat<const llvm::omp::Directive>(
860 llvm::omp::Directive::OMPD_loop,
861 llvm::omp::Directive::OMPD_sections,
862 llvm::omp::Directive::OMPD_taskloop,
864 auto present = [&](llvm::omp::Directive id) {
865 return findDirective(
id) !=
nullptr;
869 applyToParallel =
false;
872 auto dirTeams = findDirective(llvm::omp::Directive::OMPD_teams);
875 if (findDirective(llvm::omp::Directive::OMPD_loop))
876 applyToTeams =
false;
879 using ReductionModifier =
typename ReductionTy::ReductionModifier;
880 using ReductionIdentifiers =
typename ReductionTy::ReductionIdentifiers;
882 auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.
t);
883 auto &modifier = std::get<std::optional<ReductionModifier>>(clause.
t);
888 bool applied =
false;
891 auto isValidModifier = [](llvm::omp::Directive dir, ReductionModifier
mod,
892 bool alreadyApplied) {
894 case ReductionModifier::Inscan:
897 return dir == llvm::omp::Directive::OMPD_simd ||
899 case ReductionModifier::Task:
904 return dir == llvm::omp::Directive::OMPD_parallel ||
906 case ReductionModifier::Default:
912 auto *unmodified = makeClause(
913 llvm::omp::Clause::OMPC_reduction,
916 std::get<ReductionIdentifiers>(clause.
t),
919 ReductionModifier effective =
920 modifier.has_value() ? *modifier : ReductionModifier::Default;
921 bool effectiveApplied =
false;
925 if (!llvm::omp::isAllowedClauseForDirective(leaf.id, node->id, version))
927 if (!applyToParallel && &leaf == dirParallel)
929 if (!applyToTeams && &leaf == dirTeams)
932 if (isValidModifier(leaf.id, effective, effectiveApplied)) {
934 leaf.clauses.push_back(node);
935 effectiveApplied =
true;
938 leaf.clauses.push_back(unmodified);
941 applied = effectiveApplied;
949 [&](
const ObjectTy &
object) {
950 auto maybeBase = helper.getBaseObject(
object);
951 return maybeBase ? *maybeBase : object;
955 if (!sharedObjects.
empty()) {
956 if (dirParallel && !applyToParallel) {
957 auto *shared = makeClause(
958 llvm::omp::Clause::OMPC_shared,
960 dirParallel->clauses.push_back(shared);
962 if (dirTeams && !applyToTeams) {
963 auto *shared = makeClause(
964 llvm::omp::Clause::OMPC_shared,
966 dirTeams->clauses.push_back(shared);
971 auto dirTarget = findDirective(llvm::omp::Directive::OMPD_target);
972 if (dirTarget && leafs.size() > 1) {
975 [&](
const ObjectTy &
object) {
976 if (
auto maybeBase = helper.getBaseObject(
object))
977 return !mapBases.count(maybeBase->id());
978 return !mapBases.count(
object.id());
980 if (!tofrom.
empty()) {
983 auto *map = makeClause(
984 llvm::omp::Clause::OMPC_map,
986 {MapType::Tofrom, std::nullopt,
987 std::nullopt, std::nullopt,
988 std::move(tofrom)}});
990 dirTarget->clauses.push_back(map);
1009template <
typename C,
typename H>
1010bool ConstructDecompositionT<C, H>::applyClause(
1012 const ClauseTy *node) {
1013 using DirectiveNameModifier =
1016 auto &modifier = std::get<std::optional<DirectiveNameModifier>>(clause.
t);
1019 llvm::omp::Directive dirId = *modifier;
1021 makeClause(llvm::omp::Clause::OMPC_if,
1024 std::get<IfExpression>(clause.
t)}});
1026 if (
auto *hasDir = findDirective(dirId)) {
1027 hasDir->clauses.push_back(unmodified);
1033 return applyToAll(node);
1053template <
typename C,
typename H>
1054bool ConstructDecompositionT<C, H>::applyClause(
1056 const ClauseTy *node) {
1058 if (!applyToInnermost(node))
1062 auto dirSimd = findDirective(llvm::omp::Directive::OMPD_simd);
1063 std::optional<ObjectTy> iterVar = helper.getLoopIterVar();
1064 const auto &objects = std::get<tomp::ObjectListT<IdTy, ExprTy>>(clause.
t);
1070 for (
const ObjectTy &
object : objects) {
1071 last.push_back(
object);
1072 if (!dirSimd || !iterVar ||
object.
id() != iterVar->id())
1073 first.push_back(
object);
1076 if (!first.empty()) {
1077 auto *firstp = makeClause(
1078 llvm::omp::Clause::OMPC_firstprivate,
1080 nodes.push_back(firstp);
1082 if (!
last.empty()) {
1084 makeClause(llvm::omp::Clause::OMPC_lastprivate,
1086 {std::nullopt,
last}});
1087 nodes.push_back(lastp);
1100template <
typename C,
typename H>
1101bool ConstructDecompositionT<C, H>::applyClause(
1103 const ClauseTy *node) {
1104 return applyToOutermost(node);
1107template <
typename C,
typename H>
1108bool ConstructDecompositionT<C, H>::applyClause(
1110 const ClauseTy *node) {
1111 return applyToAll(node);
1114template <
typename C,
typename H>
bool ConstructDecompositionT<C, H>::split() {
1117 auto isImplicit = [
this](
const ClauseTy *node) {
1119 implicit, [node](
const ClauseTy &clause) {
return &clause == node; });
1122 for (llvm::omp::Directive leaf :
1124 leafs.push_back(LeafReprInternal{leaf, {}});
1126 for (
const ClauseTy *node :
nodes)
1127 addClauseSymsToMap(*node, node);
1134 for (
const ClauseTy *node :
nodes) {
1135 if (node->id == llvm::omp::Clause::OMPC_linear)
1138 for (
const auto *node : linears) {
1147 auto skip = [](
const ClauseTy *node) {
1149 case llvm::omp::Clause::OMPC_allocate:
1150 case llvm::omp::Clause::OMPC_linear:
1158 for (
const ClauseTy *node :
nodes) {
1162 std::visit([&](
auto &&s) {
return applyClause(s, node); }, node->u);
1163 if (!isImplicit(node))
1164 success = success && result;
1168 for (
const ClauseTy *node :
nodes) {
1169 if (node->id != llvm::omp::Clause::OMPC_allocate)
1173 std::visit([&](
auto &&s) {
return applyClause(s, node); }, node->u);
Unify divergent function exit nodes
static llvm::ArrayRef< llvm::omp::Directive > getWorksharing()
static llvm::ArrayRef< llvm::omp::Directive > getWorksharingLoop()
static bool shouldApply(Function &F, ProfileSummaryInfo &PSI)
static bool skip(DataExtractor &Data, uint64_t &Offset, bool SkippedRanges)
Skip an InlineInfo object in the specified data at the specified offset.
This file defines the SmallVector class.
ArrayRef - Represent a constant reference to an array (0 or more elements consecutively in memory),...
void push_back(const T &Elt)
A range adaptor for a pair of iterators.
This provides a very simple, boring adaptor for a begin and end iterator into a range type.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
std::remove_reference_t< Container >::iterator find_unique(Container &&container, Predicate &&pred)
ArrayRef< Directive > getLeafConstructsOrSelf(Directive D)
auto find(R &&Range, const T &Val)
Provide wrappers to std::find which take ranges instead of having to pass begin/end explicitly.
LLVM_ATTRIBUTE_ALWAYS_INLINE DynamicAPInt mod(const DynamicAPInt &LHS, const DynamicAPInt &RHS)
is always non-negative.
auto unique(Range &&R, Predicate P)
OutputIt copy_if(R &&Range, OutputIt Out, UnaryPredicate P)
Provide wrappers to std::copy_if which take ranges instead of having to pass begin/end explicitly.
OutputIt transform(R &&Range, OutputIt d_first, UnaryFunction F)
Wrapper function around std::transform to apply a function to a range and store the result elsewhere.
bool any_of(R &&range, UnaryPredicate P)
Provide wrappers to std::any_of which take ranges instead of having to pass begin/end explicitly.
auto reverse(ContainerTy &&C)
typename llvm::remove_cvref< T >::type remove_cvref_t
auto find_if(R &&Range, UnaryPredicate P)
Provide wrappers to std::find_if which take ranges instead of having to pass begin/end explicitly.
bool is_contained(R &&Range, const E &Element)
Returns true if Element is found in Range.
LogicalResult success(bool IsSuccess=true)
Utility function to generate a LogicalResult.
typename ClauseTy::ExprTy ExprTy
std::unordered_set< const ClauseTy * > ClauseSet
typename ClauseTy::TypeTy TypeTy
typename ClauseTy::IdTy IdTy
tomp::ObjectT< IdTy, ExprTy > ObjectTy
ConstructDecompositionT(uint32_t ver, HelperType &helper, llvm::omp::Directive dir, llvm::ArrayRef< ClauseTy > clauses)
tomp::ListT< DirectiveWithClauses< ClauseType > > output
std::tuple< OPT(DirectiveNameModifier), IfExpression > t
type::DirectiveName DirectiveNameModifier
std::tuple< OPT(LastprivateModifier), List > t
std::tuple< OPT(StepSimpleModifier), OPT(StepComplexModifier), OPT(LinearModifier), List > t
std::tuple< OPT(MapType), OPT(MapTypeModifiers), OPT(Mappers), OPT(Iterator), LocatorList > t
std::tuple< OPT(ReductionModifier), ReductionIdentifiers, List > t